kore

Kore is a web application platform for writing scalable, concurrent web based processes in C or Python.
Commits | Files | Refs | README | LICENSE | git clone https://git.kore.io/kore.git

cli.c (59698B)



      1 /*
      2  * Copyright (c) 2014-2022 Joris Vink <joris@coders.se>
      3  *
      4  * Permission to use, copy, modify, and distribute this software for any
      5  * purpose with or without fee is hereby granted, provided that the above
      6  * copyright notice and this permission notice appear in all copies.
      7  *
      8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
      9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 #include <sys/param.h>
     18 #include <sys/types.h>
     19 #include <sys/stat.h>
     20 #include <sys/queue.h>
     21 #include <sys/wait.h>
     22 #include <sys/mman.h>
     23 #include <sys/time.h>
     24 
     25 /*
     26  * Extremely ugly hack to get past glibc 2.43 its insane implementation
     27  * of C23 features shoved down a C99 code base.
     28  */
     29 #if defined(__GLIBC__)
     30 #undef __GLIBC_USE_ISOC23
     31 #include <string.h>
     32 #endif
     33 
     34 #if !defined(KODEV_MINIMAL)
     35 #include <openssl/err.h>
     36 #include <openssl/pem.h>
     37 #include <openssl/x509v3.h>
     38 #endif
     39 
     40 #include <ctype.h>
     41 #include <errno.h>
     42 #include <dirent.h>
     43 #include <libgen.h>
     44 #include <inttypes.h>
     45 #include <fcntl.h>
     46 #include <time.h>
     47 #include <stdarg.h>
     48 #include <signal.h>
     49 #include <stdio.h>
     50 #include <stdlib.h>
     51 #include <string.h>
     52 #include <unistd.h>
     53 #include <utime.h>
     54 
     55 /*
     56  * Turn off deprecated function warnings when building against OpenSSL 3.
     57  *
     58  * The OpenSSL 3 library deprecated most low-level functions in favour
     59  * for their higher level APIs.
     60  *
     61  * I am planning a replacement, but for now we can still make it build
     62  * and function by ignoring these warnings completely.
     63  *
     64  * The functions in question are:
     65  *	- SHA256_Init, SHA256_Update, SHA256_Final
     66  *	- RSA_new, RSA_generate_key_ex
     67  *	- EVP_PKEY_assign
     68  */
     69 #if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3
     70 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
     71 #endif
     72 
     73 #define errno_s			strerror(errno)
     74 #define ssl_errno_s		ERR_error_string(ERR_get_error(), NULL)
     75 
     76 #define LD_FLAGS_MAX		300
     77 #define CFLAGS_MAX		300
     78 #define CXXFLAGS_MAX		CFLAGS_MAX
     79 
     80 #define BUILD_NOBUILD		0
     81 #define BUILD_C			1
     82 #define BUILD_CXX		2
     83 
     84 #define CLANGDB_FILE_PATH	 "compile_commands.json"
     85 
     86 struct cli_buf {
     87 	u_int8_t		*data;
     88 	size_t			length;
     89 	size_t			offset;
     90 };
     91 
     92 struct mime_type {
     93 	char			*ext;
     94 	char			*type;
     95 	TAILQ_ENTRY(mime_type)	list;
     96 };
     97 
     98 TAILQ_HEAD(mime_list, mime_type);
     99 
    100 struct buildopt {
    101 	char			*name;
    102 	char			*kore_source;
    103 	char			*kore_flavor;
    104 	int			flavor_nohttp;
    105 	int			single_binary;
    106 	struct cli_buf		*cflags;
    107 	struct cli_buf		*cxxflags;
    108 	struct cli_buf		*ldflags;
    109 	TAILQ_ENTRY(buildopt)	list;
    110 };
    111 
    112 TAILQ_HEAD(buildopt_list, buildopt);
    113 
    114 struct cmd {
    115 	const char		*name;
    116 	const char		*descr;
    117 	void			(*cb)(int, char **);
    118 };
    119 
    120 struct filegen {
    121 	void			(*cb)(void);
    122 };
    123 
    124 struct cfile {
    125 	struct stat		st;
    126 	int			build;
    127 	char			*name;
    128 	char			*fpath;
    129 	char			*opath;
    130 	TAILQ_ENTRY(cfile)	list;
    131 };
    132 
    133 TAILQ_HEAD(cfile_list, cfile);
    134 
    135 static struct cli_buf	*cli_buf_alloc(size_t);
    136 static void		cli_buf_free(struct cli_buf *);
    137 static char		*cli_buf_stringify(struct cli_buf *, size_t *);
    138 static void		cli_buf_append(struct cli_buf *, const void *, size_t);
    139 static void		cli_buf_appendf(struct cli_buf *, const char *, ...)
    140 			    __attribute__((format (printf, 2, 3)));
    141 static void		cli_buf_appendv(struct cli_buf *, const char *,
    142 			    va_list) __attribute__((format (printf, 2, 0)));
    143 
    144 static void		*cli_malloc(size_t);
    145 static char		*cli_strdup(const char *);
    146 static void		*cli_realloc(void *, size_t);
    147 
    148 static char		*cli_text_trim(char *, size_t);
    149 static char		*cli_read_line(FILE *, char *, size_t);
    150 static long long	cli_strtonum(const char *, long long, long long);
    151 static int		cli_split_string(char *, const char *, char **, size_t);
    152 static int		cli_generate_compiler_args(struct cfile *, char **,
    153 			    char **, size_t);
    154 
    155 static void		usage(void) __attribute__((noreturn));
    156 static void		fatal(const char *, ...) __attribute__((noreturn))
    157 			    __attribute__((format (printf, 1, 2)));
    158 
    159 static void		cli_file_close(int);
    160 static void		cli_run_kore(void);
    161 static void		cli_run_kore_python(void);
    162 static void		cli_compile_kore(void *);
    163 static void		cli_link_application(void *);
    164 static void		cli_compile_source_file(void *);
    165 static void		cli_mkdir(const char *, int);
    166 static int		cli_dir_exists(const char *);
    167 static int		cli_file_exists(const char *);
    168 static void		cli_cleanup_files(const char *);
    169 static void		cli_build_cflags(struct buildopt *);
    170 static void		cli_build_cxxflags(struct buildopt *);
    171 static void		cli_build_ldflags(struct buildopt *);
    172 static void		cli_file_read(int, char **, size_t *);
    173 static void		cli_file_writef(int, const char *, ...)
    174 			    __attribute__((format (printf, 2, 3)));
    175 static void		cli_file_open(const char *, int, int *);
    176 static void		cli_file_remove(char *, struct dirent *);
    177 static void		cli_build_asset(char *, struct dirent *);
    178 static void		cli_file_write(int, const void *, size_t);
    179 static int		cli_vasprintf(char **, const char *, ...)
    180 			    __attribute__((format (printf, 2, 3)));
    181 static void		cli_spawn_proc(void (*cb)(void *), void *);
    182 static void		cli_write_asset(const char *, const char *,
    183 			    struct buildopt *);
    184 static void		cli_register_kore_file(char *, struct dirent *);
    185 static void		cli_register_source_file(char *, struct dirent *);
    186 static int		cli_file_requires_build(struct stat *, const char *);
    187 static void		cli_find_files(const char *,
    188 			    void (*cb)(char *, struct dirent *));
    189 static void		cli_add_source_file(char *, char *, char *,
    190 			    struct stat *, int);
    191 
    192 static struct buildopt	*cli_buildopt_default(void);
    193 static struct buildopt	*cli_buildopt_new(const char *);
    194 static struct buildopt	*cli_buildopt_find(const char *);
    195 static void		cli_buildopt_cleanup(void);
    196 static void		cli_buildopt_parse(const char *);
    197 static void		cli_buildopt_cflags(struct buildopt *, const char *);
    198 static void		cli_buildopt_cxxflags(struct buildopt *, const char *);
    199 static void		cli_buildopt_ldflags(struct buildopt *, const char *);
    200 static void		cli_buildopt_single_binary(struct buildopt *,
    201 			    const char *);
    202 static void		cli_buildopt_kore_source(struct buildopt *,
    203 			    const char *);
    204 static void		cli_buildopt_kore_flavor(struct buildopt *,
    205 			    const char *);
    206 static void		cli_buildopt_mime(struct buildopt *, const char *);
    207 
    208 static void		cli_build_flags_common(struct buildopt *,
    209 			    struct cli_buf *);
    210 
    211 static void		cli_flavor_load(void);
    212 static void		cli_flavor_change(const char *);
    213 static void		cli_kore_load_file(const char *, struct buildopt *,
    214 			    char **, size_t *);
    215 
    216 static void		cli_run(int, char **);
    217 static void		cli_help(int, char **);
    218 static void		cli_info(int, char **);
    219 static void		cli_build(int, char **);
    220 static void		cli_build_help(void);
    221 static void		cli_clean(int, char **);
    222 static void		cli_source(int, char **);
    223 static void		cli_reload(int, char **);
    224 static void		cli_flavor(int, char **);
    225 static void		cli_cflags(int, char **);
    226 static void		cli_ldflags(int, char **);
    227 static void		cli_genasset(int, char **);
    228 static void		cli_genasset_help(void);
    229 static void		cli_build_clangdb(const char *);
    230 
    231 #if !defined(KODEV_MINIMAL)
    232 static void		cli_create(int, char **);
    233 static void		cli_create_help(void);
    234 
    235 static void		file_create_src(void);
    236 static void		file_create_config(void);
    237 static void		file_create_gitignore(void);
    238 static void		file_create_python_src(void);
    239 
    240 static void		cli_generate_certs(void);
    241 static void		cli_file_create(const char *, const char *, size_t);
    242 #endif
    243 
    244 static struct cmd cmds[] = {
    245 	{ "help",	"this help text",			cli_help },
    246 	{ "run",	"run an application (-nr implied)",	cli_run },
    247 	{ "gen",	"generate asset file for compilation",	cli_genasset },
    248 	{ "reload",	"reload the application (SIGHUP)",	cli_reload },
    249 	{ "info",	"show info on kore on this system",	cli_info },
    250 	{ "build",	"build an application",			cli_build },
    251 	{ "clean",	"cleanup the build files",		cli_clean },
    252 	{ "source",	"print the path to kore sources",	cli_source },
    253 #if !defined(KODEV_MINIMAL)
    254 	{ "create",	"create a new application skeleton",	cli_create },
    255 #endif
    256 	{ "flavor",	"switch between build flavors",		cli_flavor },
    257 	{ "cflags",	"show kore CFLAGS",			cli_cflags },
    258 	{ "ldflags",	"show kore LDFLAGS",			cli_ldflags },
    259 	{ NULL,		NULL,					NULL }
    260 };
    261 
    262 #if !defined(KODEV_MINIMAL)
    263 static struct filegen gen_files[] = {
    264 	{ file_create_src },
    265 	{ file_create_config },
    266 	{ file_create_gitignore },
    267 	{ NULL }
    268 };
    269 
    270 static const char *gen_dirs[] = {
    271 	"src",
    272 	"cert",
    273 	"conf",
    274 	"assets",
    275 	NULL
    276 };
    277 
    278 static const char *python_gen_dirs[] = {
    279 	"cert",
    280 	NULL
    281 };
    282 
    283 static struct filegen python_gen_files[] = {
    284 	{ file_create_python_src },
    285 	{ file_create_gitignore },
    286 	{ NULL }
    287 };
    288 
    289 static const char *http_serveable_function =
    290 	"int\n"
    291 	"asset_serve_%s_%s(struct http_request *req)\n"
    292 	"{\n"
    293 	"	http_serveable(req, asset_%s_%s, asset_len_%s_%s,\n"
    294 	"	    asset_sha256_%s_%s, \"%s\");\n"
    295 	"	return (KORE_RESULT_OK);\n"
    296 	"}\n";
    297 
    298 static const char *src_data =
    299 	"#include <kore/kore.h>\n"
    300 	"#include <kore/http.h>\n"
    301 	"\n"
    302 	"int\t\tpage(struct http_request *);\n"
    303 	"\n"
    304 	"int\n"
    305 	"page(struct http_request *req)\n"
    306 	"{\n"
    307 	"\thttp_response(req, 200, NULL, 0);\n"
    308 	"\treturn (KORE_RESULT_OK);\n"
    309 	"}\n";
    310 
    311 static const char *config_data =
    312 	"# %s configuration\n"
    313 	"\n"
    314 	"server tls {\n"
    315 	"\tbind 127.0.0.1 8888\n"
    316 	"}\n"
    317 	"\n"
    318 	"load\t\t./%s.so\n"
    319 	"\n"
    320 	"domain * {\n"
    321 	"\tattach\t\ttls\n"
    322 	"\n"
    323 	"\tcertfile\tcert/server.pem\n"
    324 	"\tcertkey\t\tcert/key.pem\n"
    325 	"\n"
    326 	"\troute / {\n"
    327 	"\t\thandler page\n"
    328 	"\t}\n"
    329 	"\n"
    330 	"}\n";
    331 
    332 static const char *build_data =
    333 	"# %s build config\n"
    334 	"# You can switch flavors using: kodev flavor [newflavor]\n"
    335 	"\n"
    336 	"# Set to yes if you wish to produce a single binary instead\n"
    337 	"# of a dynamic library. If you set this to yes you must also\n"
    338 	"# set kore_source together with kore_flavor.\n"
    339 	"#single_binary=no\n"
    340 	"#kore_source=/home/joris/src/kore\n"
    341 	"#kore_flavor=\n"
    342 	"\n"
    343 	"# The flags below are shared between flavors\n"
    344 	"cflags=-Wall -Wmissing-declarations -Wshadow\n"
    345 	"cflags=-Wstrict-prototypes -Wmissing-prototypes\n"
    346 	"cflags=-Wpointer-arith -Wcast-qual -Wsign-compare\n"
    347 	"\n"
    348 	"cxxflags=-Wall -Wmissing-declarations -Wshadow\n"
    349 	"cxxflags=-Wpointer-arith -Wcast-qual -Wsign-compare\n"
    350 	"\n"
    351 	"# Mime types for assets served via the builtin asset_serve_*\n"
    352 	"#mime_add=txt:text/plain; charset=utf-8\n"
    353 	"#mime_add=png:image/png\n"
    354 	"#mime_add=html:text/html; charset=utf-8\n"
    355 	"\n"
    356 	"dev {\n"
    357 	"	# These flags are added to the shared ones when\n"
    358 	"	# you build the \"dev\" flavor.\n"
    359 	"	cflags=-g\n"
    360 	"	cxxflags=-g\n"
    361 	"}\n"
    362 	"\n"
    363 	"#prod {\n"
    364 	"#	You can specify additional flags here which are only\n"
    365 	"#	included if you build with the \"prod\" flavor.\n"
    366 	"#}\n";
    367 
    368 static const char *python_init_data =
    369 	"from .app import koreapp\n";
    370 
    371 static const char *python_app_data =
    372 	"import kore\n"
    373 	"\n"
    374 	"class KoreApp:\n"
    375 	"    def configure(self, args):\n"
    376 	"        kore.config.deployment = \"development\"\n"
    377 	"        kore.server(\"default\", ip=\"127.0.0.1\", port=\"8888\")\n"
    378 	"\n"
    379 	"        d = kore.domain(\"*\",\n"
    380 	"            attach=\"default\",\n"
    381 	"            key=\"cert/key.pem\",\n"
    382 	"            cert=\"cert/server.pem\",\n"
    383 	"        )\n"
    384 	"\n"
    385 	"        d.route(\"/\", self.index, methods=[\"get\"])\n"
    386 	"\n"
    387 	"    async def index(self, req):\n"
    388 	"        req.response(200, b'')\n"
    389 	"\n"
    390 	"koreapp = KoreApp()";
    391 
    392 static const char *gitignore = "*.o\n.flavor\n.objs\n%s.so\nassets.h\ncert\n";
    393 
    394 #endif /* !KODEV_MINIMAL */
    395 
    396 static int			s_fd = -1;
    397 static char			*appl = NULL;
    398 static int			run_after = 0;
    399 static char			*compiler_c = "cc";
    400 static char			*compiler_cpp = "c++";
    401 static char			*compiler_ld = "cc";
    402 static const char		*prefix = PREFIX;
    403 static struct mime_list		mime_types;
    404 static struct cfile_list	source_files;
    405 static struct buildopt_list	build_options;
    406 static int			source_files_count;
    407 static int			cxx_files_count;
    408 static struct cmd		*command = NULL;
    409 static int			cflags_count = 0;
    410 static int			genasset_cmd = 0;
    411 static int			cxxflags_count = 0;
    412 static int			ldflags_count = 0;
    413 static char			*flavor = NULL;
    414 static char			*out_dir = ".";
    415 static char			*object_dir = ".objs";
    416 static char			*cflags[CFLAGS_MAX];
    417 static char			*cxxflags[CXXFLAGS_MAX];
    418 static char			*ldflags[LD_FLAGS_MAX];
    419 
    420 static void
    421 usage(void)
    422 {
    423 	int		i;
    424 
    425 	fprintf(stderr, "Usage: kodev [command]\n");
    426 #if defined(KODEV_MINIMAL)
    427 	fprintf(stderr, "minimal (only build commands supported)\n");
    428 #endif
    429 	fprintf(stderr, "\nAvailable commands:\n");
    430 
    431 	for (i = 0; cmds[i].name != NULL; i++)
    432 		printf("\t%s\t%s\n", cmds[i].name, cmds[i].descr);
    433 
    434 	fprintf(stderr, "\nFind more information on https://kore.io\n");
    435 	exit(1);
    436 }
    437 
    438 int
    439 main(int argc, char **argv)
    440 {
    441 	int		i;
    442 	char		*env;
    443 
    444 	if (argc < 2)
    445 		usage();
    446 
    447 	argc--;
    448 	argv++;
    449 
    450 	if ((env = getenv("KORE_PREFIX")) != NULL)
    451 		prefix = env;
    452 
    453 	if ((env = getenv("KORE_OBJDIR")) != NULL)
    454 		object_dir = env;
    455 
    456 	if ((env = getenv("KODEV_OUTPUT")) != NULL)
    457 		out_dir = env;
    458 
    459 	(void)umask(S_IWGRP | S_IWOTH);
    460 
    461 	for (i = 0; cmds[i].name != NULL; i++) {
    462 		if (!strcmp(argv[0], cmds[i].name)) {
    463 			command = &cmds[i];
    464 			cmds[i].cb(argc, argv);
    465 			break;
    466 		}
    467 	}
    468 
    469 	if (cmds[i].name == NULL) {
    470 		fprintf(stderr, "unknown command: %s\n", argv[0]);
    471 		usage();
    472 	}
    473 
    474 	return (0);
    475 }
    476 
    477 static void
    478 cli_help(int argc, char **argv)
    479 {
    480 	usage();
    481 }
    482 
    483 #if !defined(KODEV_MINIMAL)
    484 static void
    485 cli_create_help(void)
    486 {
    487 	printf("Usage: kodev create [-p] [name]\n");
    488 	printf("Synopsis:\n");
    489 	printf("  Create a new application skeleton directory structure.\n");
    490 	printf("\n");
    491 	printf("  Optional flags:\n");
    492 	printf("\t-p = generate a python application skeleton\n");
    493 
    494 	exit(1);
    495 }
    496 
    497 static void
    498 cli_create(int argc, char **argv)
    499 {
    500 	char			*fpath;
    501 	const char		**dirs;
    502 	struct filegen		*files;
    503 	int			i, ch, python;
    504 
    505 	python = 0;
    506 
    507 	while ((ch = getopt(argc, argv, "hp")) != -1) {
    508 		switch (ch) {
    509 		case 'h':
    510 			cli_create_help();
    511 			break;
    512 		case 'p':
    513 			python = 1;
    514 			break;
    515 		default:
    516 			cli_create_help();
    517 			break;
    518 		}
    519 	}
    520 
    521 	argc -= optind;
    522 	argv += optind;
    523 
    524 	if (argc != 1)
    525 		cli_create_help();
    526 
    527 	appl = argv[0];
    528 	cli_mkdir(appl, 0755);
    529 
    530 	if (python) {
    531 		dirs = python_gen_dirs;
    532 		files = python_gen_files;
    533 	} else {
    534 		dirs = gen_dirs;
    535 		files = gen_files;
    536 	}
    537 
    538 	for (i = 0; dirs[i] != NULL; i++) {
    539 		(void)cli_vasprintf(&fpath, "%s/%s", appl, dirs[i]);
    540 		cli_mkdir(fpath, 0755);
    541 		free(fpath);
    542 	}
    543 
    544 	for (i = 0; files[i].cb != NULL; i++)
    545 		files[i].cb();
    546 
    547 	if (chdir(appl) == -1)
    548 		fatal("chdir(%s): %s", appl, errno_s);
    549 
    550 	cli_generate_certs();
    551 
    552 	printf("%s created successfully!\n", appl);
    553 	printf("WARNING: DO NOT USE THE GENERATED CERTIFICATE IN PRODUCTION\n");
    554 }
    555 #endif
    556 
    557 static void
    558 cli_flavor(int argc, char **argv)
    559 {
    560 	struct buildopt		*bopt;
    561 	char			pwd[MAXPATHLEN], *conf;
    562 
    563 	if (getcwd(pwd, sizeof(pwd)) == NULL)
    564 		fatal("could not get cwd: %s", errno_s);
    565 
    566 	appl = basename(pwd);
    567 	(void)cli_vasprintf(&conf, "conf/%s.conf", appl);
    568 	if (!cli_dir_exists("conf") || !cli_file_exists(conf))
    569 		fatal("%s doesn't appear to be a kore app", appl);
    570 	free(conf);
    571 
    572 	TAILQ_INIT(&build_options);
    573 	TAILQ_INIT(&mime_types);
    574 	(void)cli_buildopt_new("_default");
    575 	cli_buildopt_parse("conf/build.conf");
    576 
    577 	if (argc < 2) {
    578 		cli_flavor_load();
    579 		TAILQ_FOREACH(bopt, &build_options, list) {
    580 			if (!strcmp(bopt->name, "_default"))
    581 				continue;
    582 			if (!strcmp(bopt->name, flavor)) {
    583 				printf("* %s\n", bopt->name);
    584 			} else {
    585 				printf("  %s\n", bopt->name);
    586 			}
    587 		}
    588 	} else {
    589 		cli_flavor_change(argv[1]);
    590 		printf("changed build flavor to: %s\n", argv[1]);
    591 	}
    592 
    593 	cli_buildopt_cleanup();
    594 }
    595 
    596 static void
    597 cli_build_clangdb(const char *pwd)
    598 {
    599 	struct cfile		*cf;
    600 	int			fd, i, nargs, genpath_len;
    601 	char			*args[64 + CFLAGS_MAX], *genpath, *ext;
    602 
    603 	printf("generating %s...\n", CLANGDB_FILE_PATH);
    604 
    605 	genpath_len = cli_vasprintf(&genpath, "%s/", object_dir);
    606 
    607 	cli_file_open(CLANGDB_FILE_PATH, O_CREAT | O_TRUNC | O_WRONLY, &fd);
    608 	cli_file_writef(fd, "[\n");
    609 
    610 	TAILQ_FOREACH(cf, &source_files, list) {
    611 		int tempbuild = cf->build;
    612 
    613 		/* Exclude generated source files. */
    614 		if (!strncmp(cf->fpath, genpath, genpath_len))
    615 			continue;
    616 
    617 		if (cf->build == BUILD_NOBUILD) {
    618 			if ((ext = strrchr(cf->fpath, '.')) == NULL)
    619 				continue;
    620 
    621 			/*
    622 			 * Temporarily rewrite build to our file type to
    623 			 * include unchanged files.
    624 			 */
    625 			if (!strcmp(ext, ".cpp"))
    626 				cf->build = BUILD_CXX;
    627 			else if (!strcmp(ext, ".c"))
    628 				cf->build = BUILD_C;
    629 			else
    630 				continue;
    631 		}
    632 
    633 		cli_file_writef(fd, "\t{\n");
    634 		cli_file_writef(fd, "\t\t\"arguments\": [\n");
    635 
    636 		nargs = cli_generate_compiler_args(cf, NULL, args,
    637 		    64 + CFLAGS_MAX);
    638 
    639 		for (i = 0; i < nargs; i++) {
    640 			cli_file_writef(fd, "\t\t\t\"%s\"%s\n",
    641 				args[i], i == nargs - 1 ? "" : ",");
    642 		}
    643 
    644 		cli_file_writef(fd, "\t\t],\n");
    645 		cli_file_writef(fd, "\t\t\"directory\": \"%s\",\n", pwd);
    646 		cli_file_writef(fd, "\t\t\"file\": \"%s\"\n", cf->fpath);
    647 		cli_file_writef(fd, "\t}%s\n",
    648 		    cf == TAILQ_LAST(&source_files, cfile_list) ? "" : ",");
    649 
    650 		cf->build = tempbuild;
    651 	}
    652 
    653 	cli_file_writef(fd, "]\n");
    654 	cli_file_close(fd);
    655 
    656 	free(genpath);
    657 
    658 	printf("%s generated successfully...\n", CLANGDB_FILE_PATH);
    659 }
    660 
    661 static void
    662 cli_build_help(void)
    663 {
    664 	printf("Usage: kodev build [-c]\n");
    665 	printf("Synopsis:\n");
    666 	printf("  Build a kore application in current working directory.\n");
    667 	printf("\n");
    668 	printf("  Optional flags:\n");
    669 	printf("\t-c = generate Clang compilation database after build\n");
    670 
    671 	exit(1);
    672 }
    673 
    674 static void
    675 cli_build(int argc, char **argv)
    676 {
    677 #if !defined(KODEV_MINIMAL)
    678 	int			l;
    679 	char			*data;
    680 #endif
    681 	struct dirent		dp;
    682 	struct cfile		*cf;
    683 	struct buildopt		*bopt;
    684 	struct timeval		times[2];
    685 	char			*build_path;
    686 	char			*vsrc, *vobj;
    687 	int			requires_relink;
    688 	char			*sofile, *config;
    689 	char			*assets_path, *p, *src_path;
    690 	char			pwd[PATH_MAX], *assets_header;
    691 	int				ch, clangdb;
    692 
    693 	clangdb = 0;
    694 
    695 	while ((ch = getopt(argc, argv, "ch")) != -1) {
    696 		switch (ch) {
    697 		case 'h':
    698 			cli_build_help();
    699 			break;
    700 		case 'c':
    701 			clangdb = 1;
    702 			break;
    703 		default:
    704 			cli_build_help();
    705 			break;
    706 		}
    707 	}
    708 
    709 	if (getcwd(pwd, sizeof(pwd)) == NULL)
    710 		fatal("could not get cwd: %s", errno_s);
    711 
    712 	appl = cli_strdup(basename(pwd));
    713 
    714 	if ((p = getenv("CC")) != NULL) {
    715 		compiler_c = p;
    716 		compiler_ld = p;
    717 	}
    718 
    719 	if ((p = getenv("CXX")) != NULL) {
    720 		compiler_cpp = p;
    721 		compiler_ld = p;
    722 	}
    723 
    724 	source_files_count = 0;
    725 	cxx_files_count = 0;
    726 	TAILQ_INIT(&source_files);
    727 	TAILQ_INIT(&build_options);
    728 	TAILQ_INIT(&mime_types);
    729 
    730 	(void)cli_vasprintf(&src_path, "src");
    731 	(void)cli_vasprintf(&assets_path, "assets");
    732 	(void)cli_vasprintf(&config, "conf/%s.conf", appl);
    733 	(void)cli_vasprintf(&build_path, "conf/build.conf");
    734 	(void)cli_vasprintf(&assets_header, "%s/assets.h", object_dir);
    735 
    736 	if (!cli_dir_exists(src_path) || !cli_file_exists(config))
    737 		fatal("%s doesn't appear to be a kore app", appl);
    738 
    739 	cli_flavor_load();
    740 	bopt = cli_buildopt_new("_default");
    741 
    742 #if !defined(KODEV_MINIMAL)
    743 	if (!cli_file_exists(build_path)) {
    744 		l = cli_vasprintf(&data, build_data, appl);
    745 		cli_file_create("conf/build.conf", data, l);
    746 		free(data);
    747 	}
    748 #endif
    749 
    750 	cli_find_files(src_path, cli_register_source_file);
    751 	free(src_path);
    752 
    753 	cli_buildopt_parse(build_path);
    754 	free(build_path);
    755 
    756 	if (!cli_dir_exists(object_dir))
    757 		cli_mkdir(object_dir, 0755);
    758 
    759 	if (bopt->single_binary) {
    760 		if (bopt->kore_source == NULL)
    761 			fatal("single_binary set but not kore_source");
    762 
    763 		printf("building kore (%s)\n", bopt->kore_source);
    764 		cli_spawn_proc(cli_compile_kore, bopt);
    765 
    766 		(void)cli_vasprintf(&src_path, "%s/src", bopt->kore_source);
    767 		cli_find_files(src_path, cli_register_kore_file);
    768 		free(src_path);
    769 
    770 		(void)cli_vasprintf(&vsrc, "%s/version.c", object_dir);
    771 		(void)cli_vasprintf(&vobj, "%s/version.o", object_dir);
    772 
    773 		cli_add_source_file("version.c",
    774 		    vsrc, vobj, NULL, BUILD_NOBUILD);
    775 	}
    776 
    777 	printf("building %s (%s)\n", appl, flavor);
    778 
    779 	cli_build_cflags(bopt);
    780 	cli_build_cxxflags(bopt);
    781 	cli_build_ldflags(bopt);
    782 
    783 	(void)unlink(assets_header);
    784 
    785 	/* Generate the assets. */
    786 	cli_file_open(assets_header, O_CREAT | O_TRUNC | O_WRONLY, &s_fd);
    787 	cli_file_writef(s_fd, "#ifndef __H_KORE_ASSETS_H\n");
    788 	cli_file_writef(s_fd, "#define __H_KORE_ASSETS_H\n");
    789 
    790 	if (cli_dir_exists(assets_path))
    791 		cli_find_files(assets_path, cli_build_asset);
    792 
    793 	if (bopt->single_binary) {
    794 		memset(&dp, 0, sizeof(dp));
    795 		dp.d_type = DT_REG;
    796 		printf("adding config %s\n", config);
    797 		(void)snprintf(dp.d_name,
    798 		    sizeof(dp.d_name), "builtin_kore.conf");
    799 		cli_build_asset(config, &dp);
    800 	}
    801 
    802 	cli_file_writef(s_fd, "\n#endif\n");
    803 	cli_file_close(s_fd);
    804 
    805 	free(assets_path);
    806 	free(config);
    807 
    808 	if (cxx_files_count > 0)
    809 		compiler_ld = compiler_cpp;
    810 
    811 	requires_relink = 0;
    812 	TAILQ_FOREACH(cf, &source_files, list) {
    813 		if (cf->build == BUILD_NOBUILD)
    814 			continue;
    815 
    816 		printf("compiling %s\n", cf->name);
    817 		cli_spawn_proc(cli_compile_source_file, cf);
    818 
    819 		times[0].tv_usec = 0;
    820 		times[0].tv_sec = cf->st.st_mtime;
    821 		times[1] = times[0];
    822 
    823 		if (utimes(cf->opath, times) == -1)
    824 			printf("utime(%s): %s\n", cf->opath, errno_s);
    825 
    826 		requires_relink++;
    827 	}
    828 
    829 	free(assets_header);
    830 
    831 #if !defined(KODEV_MINIMAL)
    832 	if (bopt->kore_flavor == NULL ||
    833 	    !strstr(bopt->kore_flavor, "NOTLS=1")) {
    834 		if (!cli_dir_exists("cert")) {
    835 			cli_mkdir("cert", 0700);
    836 			cli_generate_certs();
    837 		}
    838 	}
    839 #endif
    840 
    841 	if (bopt->single_binary) {
    842 		requires_relink++;
    843 		(void)cli_vasprintf(&sofile, "%s/%s", out_dir, appl);
    844 	} else {
    845 		(void)cli_vasprintf(&sofile, "%s/%s.so", out_dir, appl);
    846 	}
    847 
    848 	if (!cli_file_exists(sofile) && source_files_count > 0)
    849 		requires_relink++;
    850 
    851 	free(sofile);
    852 
    853 	if (requires_relink) {
    854 		cli_spawn_proc(cli_link_application, bopt);
    855 		printf("%s built successfully!\n", appl);
    856 	} else {
    857 		printf("nothing to be done!\n");
    858 	}
    859 
    860 	if (clangdb)
    861 		cli_build_clangdb(pwd);
    862 
    863 	if (run_after == 0)
    864 		cli_buildopt_cleanup();
    865 }
    866 
    867 static void
    868 cli_source(int argc, char **argv)
    869 {
    870 	printf("%s/share/kore/\n", prefix);
    871 }
    872 
    873 static void
    874 cli_clean(int argc, char **argv)
    875 {
    876 	struct buildopt		*bopt;
    877 	char			pwd[PATH_MAX], *bin;
    878 
    879 	if (cli_dir_exists(object_dir))
    880 		cli_cleanup_files(object_dir);
    881 
    882 	if (getcwd(pwd, sizeof(pwd)) == NULL)
    883 		fatal("could not get cwd: %s", errno_s);
    884 
    885 	appl = basename(pwd);
    886 
    887 	TAILQ_INIT(&mime_types);
    888 	TAILQ_INIT(&build_options);
    889 
    890 	cli_flavor_load();
    891 	bopt = cli_buildopt_new("_default");
    892 	cli_buildopt_parse("conf/build.conf");
    893 
    894 	if (bopt->single_binary)
    895 		(void)cli_vasprintf(&bin, "%s/%s", out_dir, appl);
    896 	else
    897 		(void)cli_vasprintf(&bin, "%s/%s.so", out_dir, appl);
    898 
    899 	if (unlink(bin) == -1 && errno != ENOENT)
    900 		printf("couldn't unlink %s: %s", bin, errno_s);
    901 
    902 	free(bin);
    903 }
    904 
    905 static void
    906 cli_run(int argc, char **argv)
    907 {
    908 	if (cli_file_exists("__init__.py")) {
    909 		cli_run_kore_python();
    910 		return;
    911 	}
    912 
    913 	run_after = 1;
    914 	cli_build(argc, argv);
    915 
    916 	/*
    917 	 * We are exec()'ing kore again, while we could technically set
    918 	 * the right cli options manually and just continue running.
    919 	 */
    920 	cli_run_kore();
    921 }
    922 
    923 static void
    924 cli_reload(int argc, char **argv)
    925 {
    926 	int		fd;
    927 	size_t		len;
    928 	pid_t		pid;
    929 	char		*buf;
    930 
    931 	cli_file_open("kore.pid", O_RDONLY, &fd);
    932 	cli_file_read(fd, &buf, &len);
    933 	cli_file_close(fd);
    934 
    935 	if (len == 0)
    936 		fatal("reload: pid file is empty");
    937 
    938 	buf[len - 1] = '\0';
    939 
    940 	pid = cli_strtonum(buf, 0, UINT_MAX);
    941 
    942 	if (kill(pid, SIGHUP) == -1)
    943 		fatal("failed to reload: %s", errno_s);
    944 
    945 	printf("reloaded application\n");
    946 }
    947 
    948 static void
    949 cli_info(int argc, char **argv)
    950 {
    951 	size_t			len;
    952 	struct buildopt		*bopt;
    953 	char			*features;
    954 
    955 	TAILQ_INIT(&mime_types);
    956 	TAILQ_INIT(&build_options);
    957 
    958 	cli_flavor_load();
    959 	bopt = cli_buildopt_new("_default");
    960 	cli_buildopt_parse("conf/build.conf");
    961 
    962 	printf("active flavor\t %s\n", flavor);
    963 	printf("output type  \t %s\n",
    964 	    (bopt->single_binary) ? "binary" : "dso");
    965 
    966 	if (bopt->single_binary) {
    967 		printf("kore features\t %s\n", bopt->kore_flavor);
    968 		printf("kore source  \t %s\n", bopt->kore_source);
    969 	} else {
    970 		cli_kore_load_file("features", bopt, &features, &len);
    971 		printf("kore binary  \t %s/bin/kore\n", prefix);
    972 		printf("kore features\t %.*s\n", (int)len, features);
    973 		free(features);
    974 	}
    975 }
    976 
    977 static void
    978 cli_cflags(int argc, char **argv)
    979 {
    980 	struct cli_buf	*buf;
    981 
    982 	buf = cli_buf_alloc(128);
    983 	cli_build_flags_common(NULL, buf);
    984 	printf("%.*s\n", (int)buf->offset, buf->data);
    985 	cli_buf_free(buf);
    986 }
    987 
    988 static void
    989 cli_ldflags(int argc, char **argv)
    990 {
    991 	char		*p;
    992 	size_t		len;
    993 
    994 	cli_kore_load_file("linker", NULL, &p, &len);
    995 	printf("%.*s ", (int)len, p);
    996 
    997 #if defined(__MACH__)
    998 	printf("-dynamiclib -undefined dynamic_lookup -flat_namespace ");
    999 #else
   1000 	printf("-shared ");
   1001 #endif
   1002 	printf("\n");
   1003 
   1004 	free(p);
   1005 }
   1006 
   1007 static void
   1008 cli_genasset(int argc, char **argv)
   1009 {
   1010 	struct stat		st;
   1011 	struct dirent		dp;
   1012 	char			*hdr;
   1013 
   1014 	genasset_cmd = 1;
   1015 	TAILQ_INIT(&build_options);
   1016 	(void)cli_buildopt_new("_default");
   1017 
   1018 	if (getenv("KORE_OBJDIR") == NULL)
   1019 		object_dir = out_dir;
   1020 
   1021 	if (argv[1] == NULL)
   1022 		cli_genasset_help();
   1023 
   1024 	(void)cli_vasprintf(&hdr, "%s/assets.h", out_dir);
   1025 	(void)unlink(hdr);
   1026 
   1027 	cli_file_open(hdr, O_CREAT | O_TRUNC | O_WRONLY, &s_fd);
   1028 	cli_file_writef(s_fd, "#ifndef __H_KORE_ASSETS_H\n");
   1029 	cli_file_writef(s_fd, "#define __H_KORE_ASSETS_H\n");
   1030 
   1031 	if (stat(argv[1], &st) == -1)
   1032 		fatal("%s: %s", argv[1], errno_s);
   1033 
   1034 	if (S_ISDIR(st.st_mode)) {
   1035 		if (cli_dir_exists(argv[1]))
   1036 			cli_find_files(argv[1], cli_build_asset);
   1037 	} else if (S_ISREG(st.st_mode)) {
   1038 		memset(&dp, 0, sizeof(dp));
   1039 		dp.d_type = DT_REG;
   1040 		(void)snprintf(dp.d_name, sizeof(dp.d_name), "%s",
   1041 		    basename(argv[1]));
   1042 		cli_build_asset(argv[1], &dp);
   1043 	} else {
   1044 		fatal("%s is not a directory or regular file", argv[1]);
   1045 	}
   1046 
   1047 	cli_file_writef(s_fd, "\n#endif\n");
   1048 	cli_file_close(s_fd);
   1049 }
   1050 
   1051 static void
   1052 cli_genasset_help(void)
   1053 {
   1054 	printf("Usage: kodev genasset [source]\n");
   1055 	printf("Synopsis:\n");
   1056 	printf("  Generates asset file(s) to be used for compilation.\n");
   1057 	printf("  The source can be a single file or directory.\n");
   1058 	printf("\n");
   1059 	printf("This command honors the KODEV_OUTPUT environment variable.\n");
   1060 	printf("This command honors the KORE_OBJDIR environment variable.\n");
   1061 
   1062 	exit(1);
   1063 }
   1064 
   1065 #if !defined(KODEV_MINIMAL)
   1066 static void
   1067 file_create_python_src(void)
   1068 {
   1069 	char		*name;
   1070 
   1071 	(void)cli_vasprintf(&name, "%s/__init__.py", appl);
   1072 	cli_file_create(name, python_init_data, strlen(python_init_data));
   1073 	free(name);
   1074 
   1075 	(void)cli_vasprintf(&name, "%s/app.py", appl);
   1076 	cli_file_create(name, python_app_data, strlen(python_app_data));
   1077 	free(name);
   1078 }
   1079 
   1080 static void
   1081 file_create_src(void)
   1082 {
   1083 	char		*name;
   1084 
   1085 	(void)cli_vasprintf(&name, "%s/src/%s.c", appl, appl);
   1086 	cli_file_create(name, src_data, strlen(src_data));
   1087 	free(name);
   1088 }
   1089 
   1090 static void
   1091 file_create_config(void)
   1092 {
   1093 	int		l;
   1094 	char		*name, *data;
   1095 
   1096 	(void)cli_vasprintf(&name, "%s/conf/%s.conf", appl, appl);
   1097 	l = cli_vasprintf(&data, config_data, appl, appl);
   1098 	cli_file_create(name, data, l);
   1099 	free(name);
   1100 	free(data);
   1101 
   1102 	(void)cli_vasprintf(&name, "%s/conf/build.conf", appl);
   1103 	l = cli_vasprintf(&data, build_data, appl);
   1104 	cli_file_create(name, data, l);
   1105 	free(name);
   1106 	free(data);
   1107 }
   1108 
   1109 static void
   1110 file_create_gitignore(void)
   1111 {
   1112 	int		l;
   1113 	char		*name, *data;
   1114 
   1115 	(void)cli_vasprintf(&name, "%s/.gitignore", appl);
   1116 	l = cli_vasprintf(&data, gitignore, appl);
   1117 	cli_file_create(name, data, l);
   1118 	free(name);
   1119 	free(data);
   1120 }
   1121 #endif
   1122 
   1123 static void
   1124 cli_mkdir(const char *fpath, int mode)
   1125 {
   1126 	if (mkdir(fpath, mode) == -1)
   1127 		fatal("cli_mkdir(%s): %s", fpath, errno_s);
   1128 }
   1129 
   1130 static int
   1131 cli_file_exists(const char *fpath)
   1132 {
   1133 	struct stat		st;
   1134 
   1135 	if (stat(fpath, &st) == -1)
   1136 		return (0);
   1137 
   1138 	if (!S_ISREG(st.st_mode))
   1139 		return (0);
   1140 
   1141 	return (1);
   1142 }
   1143 
   1144 static int
   1145 cli_file_requires_build(struct stat *fst, const char *opath)
   1146 {
   1147 	struct stat	ost;
   1148 
   1149 	if (stat(opath, &ost) == -1) {
   1150 		if (errno == ENOENT)
   1151 			return (1);
   1152 		fatal("stat(%s): %s", opath, errno_s);
   1153 	}
   1154 
   1155 	return (fst->st_mtime != ost.st_mtime);
   1156 }
   1157 
   1158 static int
   1159 cli_dir_exists(const char *fpath)
   1160 {
   1161 	struct stat		st;
   1162 
   1163 	if (stat(fpath, &st) == -1)
   1164 		return (0);
   1165 
   1166 	if (!S_ISDIR(st.st_mode))
   1167 		return (0);
   1168 
   1169 	return (1);
   1170 }
   1171 
   1172 static void
   1173 cli_file_open(const char *fpath, int flags, int *fd)
   1174 {
   1175 	if ((*fd = open(fpath, flags, 0644)) == -1)
   1176 		fatal("cli_file_open(%s): %s", fpath, errno_s);
   1177 }
   1178 
   1179 static void
   1180 cli_file_read(int fd, char **buf, size_t *len)
   1181 {
   1182 	struct stat	st;
   1183 	char		*p;
   1184 	ssize_t		ret;
   1185 	size_t		offset, bytes;
   1186 
   1187 	if (fstat(fd, &st) == -1)
   1188 		fatal("fstat(): %s", errno_s);
   1189 
   1190 	if (st.st_size > USHRT_MAX)
   1191 		fatal("cli_file_read: way too big");
   1192 
   1193 	offset = 0;
   1194 	bytes = st.st_size;
   1195 	p = cli_malloc(bytes);
   1196 
   1197 	while (offset != bytes) {
   1198 		ret = read(fd, p + offset, bytes - offset);
   1199 		if (ret == -1) {
   1200 			if (errno == EINTR)
   1201 				continue;
   1202 			fatal("read(): %s", errno_s);
   1203 		}
   1204 
   1205 		if (ret == 0)
   1206 			fatal("unexpected EOF");
   1207 
   1208 		offset += (size_t)ret;
   1209 	}
   1210 
   1211 	*buf = p;
   1212 	*len = bytes;
   1213 }
   1214 
   1215 static void
   1216 cli_file_close(int fd)
   1217 {
   1218 	if (close(fd) == -1)
   1219 		printf("warning: close() %s\n", errno_s);
   1220 }
   1221 
   1222 static void
   1223 cli_file_writef(int fd, const char *fmt, ...)
   1224 {
   1225 	int		l;
   1226 	char		*buf;
   1227 	va_list		args;
   1228 
   1229 	va_start(args, fmt);
   1230 	l = vasprintf(&buf, fmt, args);
   1231 	va_end(args);
   1232 
   1233 	if (l == -1)
   1234 		fatal("cli_file_writef");
   1235 
   1236 	cli_file_write(fd, buf, l);
   1237 	free(buf);
   1238 }
   1239 
   1240 static void
   1241 cli_file_write(int fd, const void *buf, size_t len)
   1242 {
   1243 	ssize_t		r;
   1244 	const u_int8_t	*d;
   1245 	size_t		written;
   1246 
   1247 	d = buf;
   1248 	written = 0;
   1249 	while (written != len) {
   1250 		r = write(fd, d + written, len - written);
   1251 		if (r == -1) {
   1252 			if (errno == EINTR)
   1253 				continue;
   1254 			fatal("cli_file_write: %s", errno_s);
   1255 		}
   1256 
   1257 		written += r;
   1258 	}
   1259 }
   1260 
   1261 #if !defined(KODEV_MINIMAL)
   1262 static void
   1263 cli_file_create(const char *name, const char *data, size_t len)
   1264 {
   1265 	int		fd;
   1266 
   1267 	cli_file_open(name, O_CREAT | O_TRUNC | O_WRONLY, &fd);
   1268 	cli_file_write(fd, data, len);
   1269 	cli_file_close(fd);
   1270 
   1271 	printf("created %s\n", name);
   1272 }
   1273 #endif
   1274 
   1275 static void
   1276 cli_write_asset(const char *n, const char *e, struct buildopt *bopt)
   1277 {
   1278 	cli_file_writef(s_fd, "extern const u_int8_t asset_%s_%s[];\n", n, e);
   1279 	cli_file_writef(s_fd, "extern const u_int32_t asset_len_%s_%s;\n", n, e);
   1280 	cli_file_writef(s_fd, "extern const time_t asset_mtime_%s_%s;\n", n, e);
   1281 
   1282 #if !defined(KODEV_MINIMAL)
   1283 	cli_file_writef(s_fd, "extern const char *asset_sha256_%s_%s;\n", n, e);
   1284 #endif
   1285 
   1286 	if (bopt->flavor_nohttp == 0) {
   1287 		cli_file_writef(s_fd,
   1288 		    "int asset_serve_%s_%s(struct http_request *);\n", n, e);
   1289 	}
   1290 }
   1291 
   1292 static void
   1293 cli_build_asset(char *fpath, struct dirent *dp)
   1294 {
   1295 	u_int8_t		*d;
   1296 	struct stat		st;
   1297 #if !defined(KODEV_MINIMAL)
   1298 	SHA256_CTX		sctx;
   1299 	int			i, len;
   1300 	struct mime_type	*mime;
   1301 	const char		*mime_type;
   1302 	u_int8_t		digest[SHA256_DIGEST_LENGTH];
   1303 	char			hash[(SHA256_DIGEST_LENGTH * 2) + 1];
   1304 #endif
   1305 	off_t			off;
   1306 	void			*base;
   1307 	struct buildopt		*bopt;
   1308 	int			in, out;
   1309 	char			*cpath, *ext, *opath, *p, *name;
   1310 
   1311 	bopt = cli_buildopt_default();
   1312 
   1313 	/* Ignore hidden files and some editor files */
   1314 	if (dp->d_name[0] == '.' ||
   1315 	    strrchr(dp->d_name, '~') || strrchr(dp->d_name, '#')) {
   1316 		return;
   1317 	}
   1318 
   1319 	name = cli_strdup(dp->d_name);
   1320 
   1321 	/* Grab the extension as we're using it in the symbol name. */
   1322 	if ((ext = strrchr(name, '.')) == NULL)
   1323 		fatal("couldn't find ext in %s", name);
   1324 
   1325 	/* Replace dots, spaces, etc etc with underscores. */
   1326 	for (p = name; *p != '\0'; p++) {
   1327 		if (*p == '.' || isspace((unsigned char)*p) || *p == '-')
   1328 			*p = '_';
   1329 	}
   1330 
   1331 	/* Grab inode information. */
   1332 	if (stat(fpath, &st) == -1)
   1333 		fatal("stat: %s %s", fpath, errno_s);
   1334 
   1335 	/* If this file was empty, skip it. */
   1336 	if (st.st_size == 0) {
   1337 		printf("skipping empty asset %s\n", name);
   1338 		free(name);
   1339 		return;
   1340 	}
   1341 
   1342 	(void)cli_vasprintf(&opath, "%s/%s.o", object_dir, name);
   1343 	(void)cli_vasprintf(&cpath, "%s/%s.c", object_dir, name);
   1344 
   1345 	/* Check if the file needs to be built. */
   1346 	if (!cli_file_requires_build(&st, opath)) {
   1347 		*(ext)++ = '\0';
   1348 		cli_write_asset(name, ext, bopt);
   1349 		*ext = '_';
   1350 
   1351 		cli_add_source_file(name, cpath, opath, &st, BUILD_NOBUILD);
   1352 		free(name);
   1353 		return;
   1354 	}
   1355 
   1356 	/* Open the file we're converting. */
   1357 	cli_file_open(fpath, O_RDONLY, &in);
   1358 
   1359 	/* mmap our in file. */
   1360 	if ((base = mmap(NULL, st.st_size,
   1361 	    PROT_READ, MAP_PRIVATE, in, 0)) == MAP_FAILED)
   1362 		fatal("mmap: %s %s", fpath, errno_s);
   1363 
   1364 	/* Create the c file where we will write too. */
   1365 	cli_file_open(cpath, O_CREAT | O_TRUNC | O_WRONLY, &out);
   1366 
   1367 	/* No longer need name so cut off the extension. */
   1368 	printf("building asset %s\n", dp->d_name);
   1369 	*(ext)++ = '\0';
   1370 
   1371 	/* Start generating the file. */
   1372 	cli_file_writef(out, "/* Auto generated */\n");
   1373 	cli_file_writef(out, "#include <sys/types.h>\n\n");
   1374 	cli_file_writef(out, "#include <kore/kore.h>\n");
   1375 	cli_file_writef(out, "#include <kore/http.h>\n\n");
   1376 	cli_file_writef(out, "#include \"assets.h\"\n\n");
   1377 
   1378 	/* Write the file data as a byte array. */
   1379 	cli_file_writef(out, "const u_int8_t asset_%s_%s[] = {\n", name, ext);
   1380 	d = base;
   1381 	for (off = 0; off < st.st_size; off++)
   1382 		cli_file_writef(out, "0x%02x,", *d++);
   1383 
   1384 	/*
   1385 	 * Always NUL-terminate the asset, even if this NUL is not included in
   1386 	 * the actual length. This way assets can be cast to char * without
   1387 	 * any additional thinking for the developer.
   1388 	 */
   1389 	cli_file_writef(out, "0x00");
   1390 
   1391 #if !defined(KODEV_MINIMAL)
   1392 	/* Calculate the SHA256 digest of the contents. */
   1393 	(void)SHA256_Init(&sctx);
   1394 	(void)SHA256_Update(&sctx, base, st.st_size);
   1395 	(void)SHA256_Final(digest, &sctx);
   1396 
   1397 	for (i = 0; i < (int)sizeof(digest); i++) {
   1398 		len = snprintf(hash + (i * 2), sizeof(hash) - (i * 2),
   1399 		    "%02x", digest[i]);
   1400 		if (len == -1 || (size_t)len >= sizeof(hash))
   1401 			fatal("failed to convert SHA256 digest to hex");
   1402 	}
   1403 
   1404 	mime = NULL;
   1405 	TAILQ_FOREACH(mime, &mime_types, list) {
   1406 		if (!strcasecmp(mime->ext, ext))
   1407 			break;
   1408 	}
   1409 
   1410 	if (mime != NULL)
   1411 		mime_type = mime->type;
   1412 	else
   1413 		mime_type = "text/plain";
   1414 #endif
   1415 
   1416 	/* Add the meta data. */
   1417 	cli_file_writef(out, "};\n\n");
   1418 	cli_file_writef(out, "const u_int32_t asset_len_%s_%s = %" PRIu32 ";\n",
   1419 	    name, ext, (u_int32_t)st.st_size);
   1420 	cli_file_writef(out,
   1421 	    "const time_t asset_mtime_%s_%s = %" PRId64 ";\n",
   1422 	    name, ext, (int64_t)st.st_mtime);
   1423 
   1424 #if !defined(KODEV_MINIMAL)
   1425 	if (bopt->flavor_nohttp == 0) {
   1426 		cli_file_writef(out,
   1427 		    "const char *asset_sha256_%s_%s = \"\\\"%s\\\"\";\n",
   1428 		    name, ext, hash);
   1429 		cli_file_writef(out, http_serveable_function,
   1430 		    name, ext, name, ext, name, ext, name, ext, mime_type);
   1431 	}
   1432 #endif
   1433 
   1434 	/* Write the file symbols into assets.h so they can be used. */
   1435 	cli_write_asset(name, ext, bopt);
   1436 
   1437 	/* Cleanup static file source. */
   1438 	if (munmap(base, st.st_size) == -1)
   1439 		fatal("munmap: %s %s", fpath, errno_s);
   1440 
   1441 	/* Cleanup fds */
   1442 	cli_file_close(in);
   1443 	cli_file_close(out);
   1444 
   1445 	/* Restore the original name */
   1446 	*--ext = '.';
   1447 
   1448 	/* Register the .c file now (cpath is free'd later). */
   1449 	if (genasset_cmd == 0)
   1450 		cli_add_source_file(name, cpath, opath, &st, BUILD_C);
   1451 
   1452 	free(name);
   1453 }
   1454 
   1455 static void
   1456 cli_add_source_file(char *name, char *fpath, char *opath, struct stat *st,
   1457     int build)
   1458 {
   1459 	struct cfile		*cf;
   1460 
   1461 	source_files_count++;
   1462 	cf = cli_malloc(sizeof(*cf));
   1463 
   1464 	if (st != NULL)
   1465 		cf->st = *st;
   1466 	else
   1467 		memset(&cf->st, 0, sizeof(cf->st));
   1468 
   1469 	cf->build = build;
   1470 	cf->fpath = fpath;
   1471 	cf->opath = opath;
   1472 	cf->name = cli_strdup(name);
   1473 
   1474 	TAILQ_INSERT_TAIL(&source_files, cf, list);
   1475 }
   1476 
   1477 static void
   1478 cli_register_source_file(char *fpath, struct dirent *dp)
   1479 {
   1480 	struct stat		st;
   1481 	char			*ext, *opath;
   1482 	int			build;
   1483 
   1484 	if ((ext = strrchr(fpath, '.')) == NULL ||
   1485 	    (strcmp(ext, ".c") && strcmp(ext, ".cpp")))
   1486 		return;
   1487 
   1488 	if (stat(fpath, &st) == -1)
   1489 		fatal("stat(%s): %s", fpath, errno_s);
   1490 
   1491 	if (!strcmp(ext, ".cpp"))
   1492 		cxx_files_count++;
   1493 
   1494 	(void)cli_vasprintf(&opath, "%s/%s.o", object_dir, dp->d_name);
   1495 	if (!cli_file_requires_build(&st, opath)) {
   1496 		build = BUILD_NOBUILD;
   1497 	} else if (!strcmp(ext, ".cpp")) {
   1498 		build = BUILD_CXX;
   1499 	} else {
   1500 		build = BUILD_C;
   1501 	}
   1502 
   1503 	cli_add_source_file(dp->d_name, fpath, opath, &st, build);
   1504 }
   1505 
   1506 static void
   1507 cli_register_kore_file(char *fpath, struct dirent *dp)
   1508 {
   1509 	struct stat		st, ost;
   1510 	char			*opath, *ext, *fname;
   1511 
   1512 	if ((ext = strrchr(fpath, '.')) == NULL || strcmp(ext, ".c"))
   1513 		return;
   1514 
   1515 	if (stat(fpath, &st) == -1)
   1516 		fatal("stat(%s): %s", fpath, errno_s);
   1517 
   1518 	*ext = '\0';
   1519 	if ((fname = basename(fpath)) == NULL)
   1520 		fatal("basename failed");
   1521 
   1522 	(void)cli_vasprintf(&opath, "%s/%s.o", object_dir, fname);
   1523 
   1524 	/* Silently ignore non existing object files for kore source files. */
   1525 	if (stat(opath, &ost) == -1) {
   1526 		free(opath);
   1527 		return;
   1528 	}
   1529 
   1530 	cli_add_source_file(dp->d_name, fpath, opath, &st, BUILD_NOBUILD);
   1531 }
   1532 
   1533 static void
   1534 cli_file_remove(char *fpath, struct dirent *dp)
   1535 {
   1536 	if (unlink(fpath) == -1)
   1537 		fprintf(stderr, "couldn't unlink %s: %s", fpath, errno_s);
   1538 }
   1539 
   1540 static void
   1541 cli_find_files(const char *path, void (*cb)(char *, struct dirent *))
   1542 {
   1543 	DIR			*d;
   1544 	struct stat		st;
   1545 	struct dirent		*dp;
   1546 	char			*fpath;
   1547 
   1548 	if ((d = opendir(path)) == NULL)
   1549 		fatal("cli_find_files: opendir(%s): %s", path, errno_s);
   1550 
   1551 	while ((dp = readdir(d)) != NULL) {
   1552 		if (!strcmp(dp->d_name, ".") ||
   1553 		    !strcmp(dp->d_name, ".."))
   1554 			continue;
   1555 
   1556 		(void)cli_vasprintf(&fpath, "%s/%s", path, dp->d_name);
   1557 		if (stat(fpath, &st) == -1) {
   1558 			fprintf(stderr, "stat(%s): %s\n", fpath, errno_s);
   1559 			free(fpath);
   1560 			continue;
   1561 		}
   1562 
   1563 		if (S_ISDIR(st.st_mode)) {
   1564 			cli_find_files(fpath, cb);
   1565 			free(fpath);
   1566 		} else if (S_ISREG(st.st_mode)) {
   1567 			cb(fpath, dp);
   1568 		} else {
   1569 			fprintf(stderr, "ignoring %s\n", fpath);
   1570 			free(fpath);
   1571 		}
   1572 	}
   1573 
   1574 	closedir(d);
   1575 }
   1576 
   1577 #if !defined(KODEV_MINIMAL)
   1578 static void
   1579 cli_generate_certs(void)
   1580 {
   1581 	BIGNUM			*e;
   1582 	FILE			*fp;
   1583 	time_t			now;
   1584 	X509_NAME		*name;
   1585 	EVP_PKEY		*pkey;
   1586 	X509			*x509;
   1587 	RSA			*kpair;
   1588 	char			issuer[64];
   1589 
   1590 	/* Create new certificate. */
   1591 	if ((x509 = X509_new()) == NULL)
   1592 		fatal("X509_new(): %s", ssl_errno_s);
   1593 
   1594 	/* Generate version 3. */
   1595 	if (!X509_set_version(x509, 2))
   1596 		fatal("X509_set_version(): %s", ssl_errno_s);
   1597 
   1598 	/* Generate RSA keys. */
   1599 	if ((pkey = EVP_PKEY_new()) == NULL)
   1600 		fatal("EVP_PKEY_new(): %s", ssl_errno_s);
   1601 	if ((kpair = RSA_new()) == NULL)
   1602 		fatal("RSA_new(): %s", ssl_errno_s);
   1603 	if ((e = BN_new()) == NULL)
   1604 		fatal("BN_new(): %s", ssl_errno_s);
   1605 
   1606 	if (!BN_set_word(e, 65537))
   1607 		fatal("BN_set_word(): %s", ssl_errno_s);
   1608 	if (!RSA_generate_key_ex(kpair, 2048, e, NULL))
   1609 		fatal("RSA_generate_key_ex(): %s", ssl_errno_s);
   1610 
   1611 	BN_free(e);
   1612 
   1613 	if (!EVP_PKEY_assign_RSA(pkey, kpair))
   1614 		fatal("EVP_PKEY_assign_RSA(): %s", ssl_errno_s);
   1615 
   1616 	/* Set serial number to current timestamp. */
   1617 	time(&now);
   1618 	if (!ASN1_INTEGER_set(X509_get_serialNumber(x509), now))
   1619 		fatal("ASN1_INTEGER_set(): %s", ssl_errno_s);
   1620 
   1621 	/* Not before and not after dates. */
   1622 	if (!X509_gmtime_adj(X509_get_notBefore(x509), 0))
   1623 		fatal("X509_gmtime_adj(): %s", ssl_errno_s);
   1624 	if (!X509_gmtime_adj(X509_get_notAfter(x509),
   1625 	    (long)60 * 60 * 24 * 3000))
   1626 		fatal("X509_gmtime_adj(): %s", ssl_errno_s);
   1627 
   1628 	/* Attach the pkey to the certificate. */
   1629 	if (!X509_set_pubkey(x509, pkey))
   1630 		fatal("X509_set_pubkey(): %s", ssl_errno_s);
   1631 
   1632 	/* Set certificate information. */
   1633 	if ((name = X509_get_subject_name(x509)) == NULL)
   1634 		fatal("X509_get_subject_name(): %s", ssl_errno_s);
   1635 
   1636 	(void)snprintf(issuer, sizeof(issuer), "kore autogen: %s", appl);
   1637 	if (!X509_NAME_add_entry_by_txt(name, "C",
   1638 	    MBSTRING_ASC, (const unsigned char *)"SE", -1, -1, 0))
   1639 		fatal("X509_NAME_add_entry_by_txt(): C %s", ssl_errno_s);
   1640 	if (!X509_NAME_add_entry_by_txt(name, "O",
   1641 	    MBSTRING_ASC, (const unsigned char *)issuer, -1, -1, 0))
   1642 		fatal("X509_NAME_add_entry_by_txt(): O %s", ssl_errno_s);
   1643 	if (!X509_NAME_add_entry_by_txt(name, "CN",
   1644 	    MBSTRING_ASC, (const unsigned char *)"localhost", -1, -1, 0))
   1645 		fatal("X509_NAME_add_entry_by_txt(): CN %s", ssl_errno_s);
   1646 
   1647 	if (!X509_set_issuer_name(x509, name))
   1648 		fatal("X509_set_issuer_name(): %s", ssl_errno_s);
   1649 
   1650 	if (!X509_sign(x509, pkey, EVP_sha256()))
   1651 		fatal("X509_sign(): %s", ssl_errno_s);
   1652 
   1653 	if ((fp = fopen("cert/key.pem", "w")) == NULL)
   1654 		fatal("fopen(cert/key.pem): %s", errno_s);
   1655 	if (!PEM_write_PrivateKey(fp, pkey, NULL, NULL, 0, NULL, NULL))
   1656 		fatal("PEM_write_PrivateKey(): %s", ssl_errno_s);
   1657 	fclose(fp);
   1658 
   1659 	if ((fp = fopen("cert/server.pem", "w")) == NULL)
   1660 		fatal("fopen(cert/server.pem): %s", errno_s);
   1661 	if (!PEM_write_X509(fp, x509))
   1662 		fatal("PEM_write_X509(%s)", errno_s);
   1663 	fclose(fp);
   1664 
   1665 	EVP_PKEY_free(pkey);
   1666 	X509_free(x509);
   1667 }
   1668 #endif
   1669 
   1670 static int
   1671 cli_generate_compiler_args(struct cfile *cf, char **cout,
   1672     char **args, size_t elm)
   1673 {
   1674 	char		*compiler, **flags;
   1675 	int		idx, i, flags_count;
   1676 
   1677 	switch (cf->build) {
   1678 	case BUILD_C:
   1679 		flags = cflags;
   1680 		compiler = compiler_c;
   1681 		flags_count = cflags_count;
   1682 		break;
   1683 	case BUILD_CXX:
   1684 		flags = cxxflags;
   1685 		compiler = compiler_cpp;
   1686 		flags_count = cxxflags_count;
   1687 		break;
   1688 	default:
   1689 		fatal("%s: unexpected file type: %d", __func__, cf->build);
   1690 		/* NOTREACHED */
   1691 	}
   1692 
   1693 	if ((size_t)flags_count + 2 >= elm)
   1694 		fatal("%s: flags %d >= %zu", __func__, flags_count, elm);
   1695 
   1696 	if (cout != NULL)
   1697 		*cout = compiler;
   1698 
   1699 	idx = 0;
   1700 	args[idx++] = compiler;
   1701 
   1702 	for (i = 0; i < flags_count; i++)
   1703 		args[idx++] = flags[i];
   1704 
   1705 	args[idx++] = "-I";
   1706 	args[idx++] = object_dir;
   1707 	args[idx++] = "-c";
   1708 	args[idx++] = cf->fpath;
   1709 	args[idx++] = "-o";
   1710 	args[idx++] = cf->opath;
   1711 	args[idx] = NULL;
   1712 
   1713 	return (idx);
   1714 }
   1715 
   1716 static void
   1717 cli_compile_source_file(void *arg)
   1718 {
   1719 	char		*compiler;
   1720 	char		*args[64 + CFLAGS_MAX];
   1721 
   1722 	cli_generate_compiler_args(arg, &compiler, args, 64 + CFLAGS_MAX);
   1723 
   1724 	execvp(compiler, args);
   1725 	fatal("failed to start '%s': %s", compiler, errno_s);
   1726 }
   1727 
   1728 static void
   1729 cli_link_application(void *arg)
   1730 {
   1731 	struct cfile		*cf;
   1732 	struct buildopt		*bopt;
   1733 	int			idx, i;
   1734 	char			*output;
   1735 	char			*args[source_files_count + 11 + LD_FLAGS_MAX];
   1736 
   1737 	bopt = arg;
   1738 
   1739 	if (bopt->single_binary)
   1740 		(void)cli_vasprintf(&output, "%s/%s", out_dir, appl);
   1741 	else
   1742 		(void)cli_vasprintf(&output, "%s/%s.so", out_dir, appl);
   1743 
   1744 	idx = 0;
   1745 	args[idx++] = compiler_ld;
   1746 
   1747 	TAILQ_FOREACH(cf, &source_files, list)
   1748 		args[idx++] = cf->opath;
   1749 
   1750 	for (i = 0; i < ldflags_count; i++)
   1751 		args[idx++] = ldflags[i];
   1752 
   1753 	args[idx++] = "-o";
   1754 	args[idx++] = output;
   1755 	args[idx] = NULL;
   1756 
   1757 	execvp(compiler_ld, args);
   1758 	fatal("failed to start '%s': %s", compiler_ld, errno_s);
   1759 }
   1760 
   1761 static void
   1762 cli_compile_kore(void *arg)
   1763 {
   1764 	struct buildopt		*bopt = arg;
   1765 	int			idx, i, fcnt;
   1766 	char			pwd[MAXPATHLEN], *obj, *args[20], *flavors[7];
   1767 
   1768 	if (object_dir[0] != '/') {
   1769 		if (getcwd(pwd, sizeof(pwd)) == NULL)
   1770 			fatal("could not get cwd: %s", errno_s);
   1771 		(void)cli_vasprintf(&obj, "OBJDIR=%s/%s", pwd, object_dir);
   1772 	} else {
   1773 		(void)cli_vasprintf(&obj, "OBJDIR=%s", object_dir);
   1774 	}
   1775 
   1776 	if (putenv(obj) != 0)
   1777 		fatal("cannot set OBJDIR for building kore");
   1778 
   1779 	fcnt = cli_split_string(bopt->kore_flavor, " ", flavors, 7);
   1780 
   1781 #if defined(OpenBSD) || defined(__FreeBSD_version) || \
   1782     defined(NetBSD) || defined(__DragonFly_version)
   1783 	args[0] = "gmake";
   1784 #else
   1785 	args[0] = "make";
   1786 #endif
   1787 
   1788 	args[1] = "-s";
   1789 	args[2] = "-C";
   1790 	args[3] = bopt->kore_source;
   1791 	args[4] = "objects";
   1792 
   1793 	idx = 5;
   1794 	for (i = 0; i < fcnt; i++) {
   1795 		printf("using flavor %s\n", flavors[i]);
   1796 		args[idx++] = flavors[i];
   1797 	}
   1798 
   1799 	args[idx++] = "KORE_SINGLE_BINARY=1";
   1800 	args[idx] = NULL;
   1801 
   1802 	execvp(args[0], args);
   1803 	fatal("failed to start '%s': %s", args[0], errno_s);
   1804 }
   1805 
   1806 static void
   1807 cli_run_kore_python(void)
   1808 {
   1809 	char		*args[5], *cmd;
   1810 	char		pwd[MAXPATHLEN];
   1811 
   1812 	(void)cli_vasprintf(&cmd, "%s/bin/kore", prefix);
   1813 
   1814 	if (getcwd(pwd, sizeof(pwd)) == NULL)
   1815 		fatal("could not get cwd: %s", errno_s);
   1816 
   1817 	args[0] = cmd;
   1818 	args[1] = pwd;
   1819 	args[2] = NULL;
   1820 
   1821 	execvp(args[0], args);
   1822 	fatal("failed to start '%s': %s", args[0], errno_s);
   1823 
   1824 }
   1825 
   1826 static void
   1827 cli_run_kore(void)
   1828 {
   1829 	struct buildopt		*bopt;
   1830 	char			*args[4], *cpath, *cmd, *flags;
   1831 
   1832 	bopt = cli_buildopt_default();
   1833 
   1834 	if (bopt->single_binary) {
   1835 		cpath = NULL;
   1836 		flags = "-nr";
   1837 		(void)cli_vasprintf(&cmd, "./%s", appl);
   1838 	} else {
   1839 		flags = "-nrc";
   1840 		(void)cli_vasprintf(&cmd, "%s/bin/kore", prefix);
   1841 		(void)cli_vasprintf(&cpath, "conf/%s.conf", appl);
   1842 	}
   1843 
   1844 	args[0] = cmd;
   1845 	args[1] = flags;
   1846 
   1847 	if (cpath != NULL) {
   1848 		args[2] = cpath;
   1849 		args[3] = NULL;
   1850 	} else {
   1851 		args[2] = NULL;
   1852 	}
   1853 
   1854 	execvp(args[0], args);
   1855 	fatal("failed to start '%s': %s", args[0], errno_s);
   1856 }
   1857 
   1858 static void
   1859 cli_buildopt_parse(const char *path)
   1860 {
   1861 	FILE			*fp;
   1862 	const char		*env;
   1863 	struct buildopt		*bopt;
   1864 	char			buf[BUFSIZ], *p, *t;
   1865 
   1866 	if ((fp = fopen(path, "r")) == NULL)
   1867 		fatal("cli_buildopt_parse: fopen(%s): %s", path, errno_s);
   1868 
   1869 	bopt = NULL;
   1870 
   1871 	while ((p = cli_read_line(fp, buf, sizeof(buf))) != NULL) {
   1872 		if (strlen(p) == 0)
   1873 			continue;
   1874 
   1875 		if (bopt != NULL && !strcmp(p, "}")) {
   1876 			bopt = NULL;
   1877 			continue;
   1878 		}
   1879 
   1880 		if (bopt == NULL) {
   1881 			if ((t = strchr(p, '=')) != NULL)
   1882 				goto parse_option;
   1883 			if ((t = strchr(p, ' ')) == NULL)
   1884 				fatal("unexpected '%s'", p);
   1885 			*(t)++ = '\0';
   1886 			if (strcmp(t, "{"))
   1887 				fatal("expected '{', got '%s'", t);
   1888 			bopt = cli_buildopt_new(p);
   1889 			continue;
   1890 		}
   1891 
   1892 		if ((t = strchr(p, '=')) == NULL) {
   1893 			printf("bad buildopt line: '%s'\n", p);
   1894 			continue;
   1895 		}
   1896 
   1897 parse_option:
   1898 		*(t)++ = '\0';
   1899 
   1900 		p = cli_text_trim(p, strlen(p));
   1901 		t = cli_text_trim(t, strlen(t));
   1902 
   1903 		if (!strcasecmp(p, "cflags")) {
   1904 			cli_buildopt_cflags(bopt, t);
   1905 		} else if (!strcasecmp(p, "cxxflags")) {
   1906 			cli_buildopt_cxxflags(bopt, t);
   1907 		} else if (!strcasecmp(p, "ldflags")) {
   1908 			cli_buildopt_ldflags(bopt, t);
   1909 		} else if (!strcasecmp(p, "single_binary")) {
   1910 			cli_buildopt_single_binary(bopt, t);
   1911 		} else if (!strcasecmp(p, "kore_source")) {
   1912 			cli_buildopt_kore_source(bopt, t);
   1913 		} else if (!strcasecmp(p, "kore_flavor")) {
   1914 			cli_buildopt_kore_flavor(bopt, t);
   1915 		} else if (!strcasecmp(p, "mime_add")) {
   1916 			cli_buildopt_mime(bopt, t);
   1917 		} else {
   1918 			printf("ignoring unknown option '%s'\n", p);
   1919 		}
   1920 	}
   1921 
   1922 	fclose(fp);
   1923 
   1924 	if ((env = getenv("KORE_SOURCE")) != NULL)
   1925 		cli_buildopt_kore_source(NULL, env);
   1926 
   1927 	if ((env = getenv("KORE_FLAVOR")) != NULL)
   1928 		cli_buildopt_kore_flavor(NULL, env);
   1929 }
   1930 
   1931 static struct buildopt *
   1932 cli_buildopt_new(const char *name)
   1933 {
   1934 	struct buildopt		*bopt;
   1935 
   1936 	bopt = cli_malloc(sizeof(*bopt));
   1937 	bopt->cflags = NULL;
   1938 	bopt->cxxflags = NULL;
   1939 	bopt->ldflags = NULL;
   1940 	bopt->flavor_nohttp = 0;
   1941 	bopt->single_binary = 0;
   1942 	bopt->kore_flavor = NULL;
   1943 	bopt->name = cli_strdup(name);
   1944 
   1945 	(void)cli_vasprintf(&bopt->kore_source, "%s/share/kore/", prefix);
   1946 
   1947 	TAILQ_INSERT_TAIL(&build_options, bopt, list);
   1948 	return (bopt);
   1949 }
   1950 
   1951 static struct buildopt *
   1952 cli_buildopt_find(const char *name)
   1953 {
   1954 	struct buildopt		*bopt;
   1955 
   1956 	TAILQ_FOREACH(bopt, &build_options, list) {
   1957 		if (!strcmp(bopt->name, name))
   1958 			return (bopt);
   1959 	}
   1960 
   1961 	return (NULL);
   1962 }
   1963 
   1964 static struct buildopt *
   1965 cli_buildopt_default(void)
   1966 {
   1967 	struct buildopt		*bopt;
   1968 
   1969 	if ((bopt = cli_buildopt_find("_default")) == NULL)
   1970 		fatal("no _default buildopt options");
   1971 
   1972 	return (bopt);
   1973 }
   1974 
   1975 static void
   1976 cli_buildopt_cleanup(void)
   1977 {
   1978 	struct buildopt		*bopt, *next;
   1979 	struct mime_type	*mime, *mnext;
   1980 
   1981 	for (bopt = TAILQ_FIRST(&build_options); bopt != NULL; bopt = next) {
   1982 		next = TAILQ_NEXT(bopt, list);
   1983 		TAILQ_REMOVE(&build_options, bopt, list);
   1984 
   1985 		if (bopt->cflags != NULL)
   1986 			cli_buf_free(bopt->cflags);
   1987 		if (bopt->cxxflags != NULL)
   1988 			cli_buf_free(bopt->cxxflags);
   1989 		if (bopt->ldflags != NULL)
   1990 			cli_buf_free(bopt->ldflags);
   1991 		if (bopt->kore_source != NULL)
   1992 			free(bopt->kore_source);
   1993 		if (bopt->kore_flavor != NULL)
   1994 			free(bopt->kore_flavor);
   1995 		free(bopt);
   1996 	}
   1997 
   1998 	for (mime = TAILQ_FIRST(&mime_types); mime != NULL; mime = mnext) {
   1999 		mnext = TAILQ_NEXT(mime, list);
   2000 		TAILQ_REMOVE(&mime_types, mime, list);
   2001 		free(mime->type);
   2002 		free(mime->ext);
   2003 		free(mime);
   2004 	}
   2005 }
   2006 
   2007 static void
   2008 cli_buildopt_cflags(struct buildopt *bopt, const char *string)
   2009 {
   2010 	if (bopt == NULL)
   2011 		bopt = cli_buildopt_default();
   2012 
   2013 	if (bopt->cflags == NULL)
   2014 		bopt->cflags = cli_buf_alloc(128);
   2015 
   2016 	cli_buf_appendf(bopt->cflags, "%s ", string);
   2017 }
   2018 
   2019 static void
   2020 cli_buildopt_cxxflags(struct buildopt *bopt, const char *string)
   2021 {
   2022 	if (bopt == NULL)
   2023 		bopt = cli_buildopt_default();
   2024 
   2025 	if (bopt->cxxflags == NULL)
   2026 		bopt->cxxflags = cli_buf_alloc(128);
   2027 
   2028 	cli_buf_appendf(bopt->cxxflags, "%s ", string);
   2029 }
   2030 
   2031 static void
   2032 cli_buildopt_ldflags(struct buildopt *bopt, const char *string)
   2033 {
   2034 	if (bopt == NULL)
   2035 		bopt = cli_buildopt_default();
   2036 
   2037 	if (bopt->ldflags == NULL)
   2038 		bopt->ldflags = cli_buf_alloc(128);
   2039 
   2040 	cli_buf_appendf(bopt->ldflags, "%s ", string);
   2041 }
   2042 
   2043 static void
   2044 cli_buildopt_single_binary(struct buildopt *bopt, const char *string)
   2045 {
   2046 	if (bopt == NULL)
   2047 		bopt = cli_buildopt_default();
   2048 	else
   2049 		fatal("single_binary only supported in global context");
   2050 
   2051 	if (!strcmp(string, "yes"))
   2052 		bopt->single_binary = 1;
   2053 	else
   2054 		bopt->single_binary = 0;
   2055 }
   2056 
   2057 static void
   2058 cli_buildopt_kore_source(struct buildopt *bopt, const char *string)
   2059 {
   2060 	if (bopt == NULL)
   2061 		bopt = cli_buildopt_default();
   2062 	else
   2063 		fatal("kore_source only supported in global context");
   2064 
   2065 	if (bopt->kore_source != NULL)
   2066 		free(bopt->kore_source);
   2067 
   2068 	bopt->kore_source = cli_strdup(string);
   2069 }
   2070 
   2071 static void
   2072 cli_buildopt_kore_flavor(struct buildopt *bopt, const char *string)
   2073 {
   2074 	int		cnt, i;
   2075 	char		*p, *copy, *flavors[10];
   2076 
   2077 	if (bopt == NULL)
   2078 		bopt = cli_buildopt_default();
   2079 	else
   2080 		fatal("kore_flavor only supported in global context");
   2081 
   2082 	if (bopt->kore_flavor != NULL)
   2083 		free(bopt->kore_flavor);
   2084 
   2085 	copy = cli_strdup(string);
   2086 	cnt = cli_split_string(copy, " ", flavors, 10);
   2087 
   2088 	for (i = 0; i < cnt; i++) {
   2089 		if ((p = strchr(flavors[i], '=')) == NULL)
   2090 			fatal("invalid flavor %s", string);
   2091 
   2092 		*p = '\0';
   2093 
   2094 		if (!strcmp(flavors[i], "NOHTTP"))
   2095 			bopt->flavor_nohttp = 1;
   2096 	}
   2097 
   2098 	bopt->kore_flavor = cli_strdup(string);
   2099 	free(copy);
   2100 }
   2101 
   2102 static void
   2103 cli_buildopt_mime(struct buildopt *bopt, const char *ext)
   2104 {
   2105 	struct mime_type	*mime;
   2106 	char			*type;
   2107 
   2108 	if (bopt == NULL)
   2109 		bopt = cli_buildopt_default();
   2110 	else
   2111 		fatal("mime_add only supported in global context");
   2112 
   2113 	if ((type = strchr(ext, ':')) == NULL)
   2114 		fatal("no type given in %s", ext);
   2115 
   2116 	*(type)++ = '\0';
   2117 	TAILQ_FOREACH(mime, &mime_types, list) {
   2118 		if (!strcmp(mime->ext, ext))
   2119 			fatal("duplicate extension %s found", ext);
   2120 	}
   2121 
   2122 	mime = cli_malloc(sizeof(*mime));
   2123 	mime->ext = cli_strdup(ext);
   2124 	mime->type = cli_strdup(type);
   2125 
   2126 	TAILQ_INSERT_TAIL(&mime_types, mime, list);
   2127 }
   2128 
   2129 static void
   2130 cli_build_flags_common(struct buildopt *bopt, struct cli_buf *buf)
   2131 {
   2132 	size_t		len;
   2133 	char		*data;
   2134 
   2135 	cli_buf_appendf(buf, "-fPIC ");
   2136 
   2137 	if (bopt != NULL)
   2138 		cli_buf_appendf(buf, "-Isrc -Isrc/includes ");
   2139 
   2140 	if (bopt == NULL || bopt->single_binary == 0)
   2141 		cli_buf_appendf(buf, "-I%s/include ", prefix);
   2142 	else
   2143 		cli_buf_appendf(buf, "-I%s/include ", bopt->kore_source);
   2144 
   2145 	if (bopt == NULL || bopt->single_binary == 0) {
   2146 		cli_kore_load_file("features", bopt, &data, &len);
   2147 		cli_buf_append(buf, data, len);
   2148 		cli_buf_appendf(buf, " ");
   2149 		free(data);
   2150 	}
   2151 }
   2152 
   2153 static void
   2154 cli_build_cflags(struct buildopt *bopt)
   2155 {
   2156 	size_t			len;
   2157 	struct buildopt		*obopt;
   2158 	char			*string, *buf, *env;
   2159 
   2160 	if ((obopt = cli_buildopt_find(flavor)) == NULL)
   2161 		fatal("no such build flavor: %s", flavor);
   2162 
   2163 	if (bopt->cflags == NULL)
   2164 		bopt->cflags = cli_buf_alloc(128);
   2165 
   2166 	cli_build_flags_common(bopt, bopt->cflags);
   2167 
   2168 	if (obopt != NULL && obopt->cflags != NULL) {
   2169 		cli_buf_append(bopt->cflags, obopt->cflags->data,
   2170 		    obopt->cflags->offset);
   2171 	}
   2172 
   2173 	if (bopt->single_binary) {
   2174 		cli_kore_load_file("features", bopt, &buf, &len);
   2175 		cli_buf_append(bopt->cflags, buf, len);
   2176 		cli_buf_appendf(bopt->cflags, " ");
   2177 		free(buf);
   2178 	}
   2179 
   2180 	if ((env = getenv("CFLAGS")) != NULL)
   2181 		cli_buf_appendf(bopt->cflags, "%s", env);
   2182 
   2183 	string = cli_buf_stringify(bopt->cflags, NULL);
   2184 	printf("CFLAGS=%s\n", string);
   2185 	cflags_count = cli_split_string(string, " ", cflags, CFLAGS_MAX);
   2186 }
   2187 
   2188 static void
   2189 cli_build_cxxflags(struct buildopt *bopt)
   2190 {
   2191 	struct buildopt		*obopt;
   2192 	char			*string, *env;
   2193 
   2194 	if ((obopt = cli_buildopt_find(flavor)) == NULL)
   2195 		fatal("no such build flavor: %s", flavor);
   2196 
   2197 	if (bopt->cxxflags == NULL)
   2198 		bopt->cxxflags = cli_buf_alloc(128);
   2199 
   2200 	cli_build_flags_common(bopt, bopt->cxxflags);
   2201 
   2202 	if (obopt != NULL && obopt->cxxflags != NULL) {
   2203 		cli_buf_append(bopt->cxxflags, obopt->cxxflags->data,
   2204 		    obopt->cxxflags->offset);
   2205 	}
   2206 
   2207 	if ((env = getenv("CXXFLAGS")) != NULL)
   2208 		cli_buf_appendf(bopt->cxxflags, "%s", env);
   2209 
   2210 	string = cli_buf_stringify(bopt->cxxflags, NULL);
   2211 	if (cxx_files_count > 0)
   2212 		printf("CXXFLAGS=%s\n", string);
   2213 	cxxflags_count = cli_split_string(string, " ", cxxflags, CXXFLAGS_MAX);
   2214 }
   2215 
   2216 static void
   2217 cli_build_ldflags(struct buildopt *bopt)
   2218 {
   2219 	int			fd;
   2220 	size_t			len;
   2221 	struct buildopt		*obopt;
   2222 	char			*string, *buf, *env, *path;
   2223 
   2224 	if ((obopt = cli_buildopt_find(flavor)) == NULL)
   2225 		fatal("no such build flavor: %s", flavor);
   2226 
   2227 	if (bopt->ldflags == NULL)
   2228 		bopt->ldflags = cli_buf_alloc(128);
   2229 
   2230 	if (bopt->single_binary == 0) {
   2231 #if defined(__MACH__)
   2232 		cli_buf_appendf(bopt->ldflags,
   2233 		    "-dynamiclib -undefined dynamic_lookup -flat_namespace ");
   2234 #else
   2235 		cli_buf_appendf(bopt->ldflags, "-shared ");
   2236 #endif
   2237 	} else {
   2238 		(void)cli_vasprintf(&path, "%s/ldflags", object_dir);
   2239 		cli_file_open(path, O_RDONLY, &fd);
   2240 		cli_file_read(fd, &buf, &len);
   2241 		cli_file_close(fd);
   2242 		if (len == 0)
   2243 			fatal("ldflags is empty");
   2244 		len--;
   2245 
   2246 		cli_buf_append(bopt->ldflags, buf, len);
   2247 		cli_buf_appendf(bopt->ldflags, " ");
   2248 		free(buf);
   2249 	}
   2250 
   2251 	if (obopt != NULL && obopt->ldflags != NULL) {
   2252 		cli_buf_append(bopt->ldflags, obopt->ldflags->data,
   2253 		    obopt->ldflags->offset);
   2254 	}
   2255 
   2256 	if ((env = getenv("LDFLAGS")) != NULL)
   2257 		cli_buf_appendf(bopt->ldflags, "%s", env);
   2258 
   2259 	string = cli_buf_stringify(bopt->ldflags, NULL);
   2260 	printf("LDFLAGS=%s\n", string);
   2261 	ldflags_count = cli_split_string(string, " ", ldflags, LD_FLAGS_MAX);
   2262 }
   2263 
   2264 static void
   2265 cli_flavor_load(void)
   2266 {
   2267 	FILE		*fp;
   2268 	char		buf[BUFSIZ], pwd[MAXPATHLEN], *p, *conf, *env;
   2269 
   2270 	if ((env = getenv("KORE_BUILD_FLAVOR")) != NULL) {
   2271 		flavor = cli_strdup(env);
   2272 		return;
   2273 	}
   2274 
   2275 	if (getcwd(pwd, sizeof(pwd)) == NULL)
   2276 		fatal("could not get cwd: %s", errno_s);
   2277 
   2278 	appl = basename(pwd);
   2279 	if (appl == NULL)
   2280 		fatal("basename: %s", errno_s);
   2281 	appl = cli_strdup(appl);
   2282 	(void)cli_vasprintf(&conf, "conf/%s.conf", appl);
   2283 
   2284 	if (!cli_dir_exists("conf") || !cli_file_exists(conf))
   2285 		fatal("%s doesn't appear to be a kore app", appl);
   2286 	free(conf);
   2287 
   2288 	if ((fp = fopen(".flavor", "r")) == NULL) {
   2289 		flavor = cli_strdup("dev");
   2290 		return;
   2291 	}
   2292 
   2293 	if (fgets(buf, sizeof(buf), fp) == NULL)
   2294 		fatal("failed to read flavor from file");
   2295 
   2296 	if ((p = strchr(buf, '\n')) != NULL)
   2297 		*p = '\0';
   2298 
   2299 	flavor = cli_strdup(buf);
   2300 	(void)fclose(fp);
   2301 }
   2302 
   2303 static void
   2304 cli_kore_load_file(const char *name, struct buildopt *bopt,
   2305     char **out, size_t *outlen)
   2306 {
   2307 	int		fd;
   2308 	size_t		len;
   2309 	char		*path, *data;
   2310 
   2311 	if (bopt != NULL && bopt->single_binary) {
   2312 		(void)cli_vasprintf(&path, "%s/%s", object_dir, name);
   2313 	} else {
   2314 		(void)cli_vasprintf(&path, "%s/share/kore/%s", prefix, name);
   2315 	}
   2316 
   2317 	cli_file_open(path, O_RDONLY, &fd);
   2318 	cli_file_read(fd, &data, &len);
   2319 	cli_file_close(fd);
   2320 	free(path);
   2321 
   2322 	if (len == 0)
   2323 		fatal("%s is empty", name);
   2324 
   2325 	len--;
   2326 
   2327 	*out = data;
   2328 	*outlen = len;
   2329 }
   2330 
   2331 static void
   2332 cli_flavor_change(const char *name)
   2333 {
   2334 	FILE			*fp;
   2335 	int			ret;
   2336 	struct buildopt		*bopt;
   2337 
   2338 	if ((bopt = cli_buildopt_find(name)) == NULL)
   2339 		fatal("no such flavor: %s", name);
   2340 
   2341 	if ((fp = fopen(".flavor.tmp", "w")) == NULL)
   2342 		fatal("failed to open temporary file to save flavor");
   2343 
   2344 	ret = fprintf(fp, "%s\n", name);
   2345 	if (ret == -1 || (size_t)ret != (strlen(name) + 1))
   2346 		fatal("failed to write new build flavor");
   2347 
   2348 	(void)fclose(fp);
   2349 
   2350 	if (rename(".flavor.tmp", ".flavor") == -1)
   2351 		fatal("failed to replace build flavor");
   2352 
   2353 	cli_clean(0, NULL);
   2354 }
   2355 
   2356 static void
   2357 cli_spawn_proc(void (*cb)(void *), void *arg)
   2358 {
   2359 	pid_t		pid;
   2360 	int		status;
   2361 
   2362 	pid = fork();
   2363 	switch (pid) {
   2364 	case -1:
   2365 		fatal("cli_compile_cfile: fork() %s", errno_s);
   2366 		/* NOTREACHED */
   2367 	case 0:
   2368 		cb(arg);
   2369 		fatal("cli_spawn_proc: %s", errno_s);
   2370 		/* NOTREACHED */
   2371 	default:
   2372 		break;
   2373 	}
   2374 
   2375 	if (waitpid(pid, &status, 0) == -1)
   2376 		fatal("couldn't wait for child %d", pid);
   2377 
   2378 	if (WEXITSTATUS(status) || WTERMSIG(status) || WCOREDUMP(status))
   2379 		fatal("subprocess trouble, check output");
   2380 }
   2381 
   2382 static int
   2383 cli_vasprintf(char **out, const char *fmt, ...)
   2384 {
   2385 	int		l;
   2386 	va_list		args;
   2387 
   2388 	va_start(args, fmt);
   2389 	l = vasprintf(out, fmt, args);
   2390 	va_end(args);
   2391 
   2392 	if (l == -1)
   2393 		fatal("cli_vasprintf");
   2394 
   2395 	return (l);
   2396 }
   2397 
   2398 static void
   2399 cli_cleanup_files(const char *spath)
   2400 {
   2401 	cli_find_files(spath, cli_file_remove);
   2402 
   2403 	if (rmdir(spath) == -1 && errno != ENOENT)
   2404 		printf("couldn't rmdir %s\n", spath);
   2405 }
   2406 
   2407 static void *
   2408 cli_malloc(size_t len)
   2409 {
   2410 	void		*ptr;
   2411 
   2412 	if ((ptr = calloc(1, len)) == NULL)
   2413 		fatal("calloc: %s", errno_s);
   2414 
   2415 	return (ptr);
   2416 }
   2417 
   2418 static void *
   2419 cli_realloc(void *ptr, size_t len)
   2420 {
   2421 	void		*nptr;
   2422 
   2423 	if ((nptr = realloc(ptr, len)) == NULL)
   2424 		fatal("realloc: %s", errno_s);
   2425 
   2426 	return (nptr);
   2427 }
   2428 
   2429 static char *
   2430 cli_strdup(const char *string)
   2431 {
   2432 	char		*copy;
   2433 
   2434 	if ((copy = strdup(string)) == NULL)
   2435 		fatal("strdup: %s", errno_s);
   2436 
   2437 	return (copy);
   2438 }
   2439 
   2440 struct cli_buf *
   2441 cli_buf_alloc(size_t initial)
   2442 {
   2443 	struct cli_buf		*buf;
   2444 
   2445 	buf = cli_malloc(sizeof(*buf));
   2446 
   2447 	if (initial > 0)
   2448 		buf->data = cli_malloc(initial);
   2449 	else
   2450 		buf->data = NULL;
   2451 
   2452 	buf->length = initial;
   2453 	buf->offset = 0;
   2454 
   2455 	return (buf);
   2456 }
   2457 
   2458 void
   2459 cli_buf_free(struct cli_buf *buf)
   2460 {
   2461 	free(buf->data);
   2462 	buf->data = NULL;
   2463 	buf->offset = 0;
   2464 	buf->length = 0;
   2465 	free(buf);
   2466 }
   2467 
   2468 void
   2469 cli_buf_append(struct cli_buf *buf, const void *d, size_t len)
   2470 {
   2471 	if ((buf->offset + len) < len)
   2472 		fatal("overflow in cli_buf_append");
   2473 
   2474 	if ((buf->offset + len) > buf->length) {
   2475 		buf->length += len;
   2476 		buf->data = cli_realloc(buf->data, buf->length);
   2477 	}
   2478 
   2479 	memcpy((buf->data + buf->offset), d, len);
   2480 	buf->offset += len;
   2481 }
   2482 
   2483 void
   2484 cli_buf_appendv(struct cli_buf *buf, const char *fmt, va_list args)
   2485 {
   2486 	int		l;
   2487 	va_list		copy;
   2488 	char		*b, sb[BUFSIZ];
   2489 
   2490 	va_copy(copy, args);
   2491 
   2492 	l = vsnprintf(sb, sizeof(sb), fmt, args);
   2493 	if (l == -1)
   2494 		fatal("cli_buf_appendv(): vsnprintf error");
   2495 
   2496 	if ((size_t)l >= sizeof(sb)) {
   2497 		l = vasprintf(&b, fmt, copy);
   2498 		if (l == -1)
   2499 			fatal("cli_buf_appendv(): error or truncation");
   2500 	} else {
   2501 		b = sb;
   2502 	}
   2503 
   2504 	cli_buf_append(buf, b, l);
   2505 	if (b != sb)
   2506 		free(b);
   2507 
   2508 	va_end(copy);
   2509 }
   2510 
   2511 void
   2512 cli_buf_appendf(struct cli_buf *buf, const char *fmt, ...)
   2513 {
   2514 	va_list		args;
   2515 
   2516 	va_start(args, fmt);
   2517 	cli_buf_appendv(buf, fmt, args);
   2518 	va_end(args);
   2519 }
   2520 
   2521 char *
   2522 cli_buf_stringify(struct cli_buf *buf, size_t *len)
   2523 {
   2524 	char		c;
   2525 
   2526 	if (len != NULL)
   2527 		*len = buf->offset;
   2528 
   2529 	c = '\0';
   2530 	cli_buf_append(buf, &c, sizeof(c));
   2531 
   2532 	return ((char *)buf->data);
   2533 }
   2534 
   2535 static int
   2536 cli_split_string(char *input, const char *delim, char **out, size_t ele)
   2537 {
   2538 	int		count;
   2539 	char		**ap;
   2540 
   2541 	if (ele == 0)
   2542 		return (0);
   2543 
   2544 	count = 0;
   2545 	for (ap = out; ap < &out[ele - 1] &&
   2546 	    (*ap = strsep(&input, delim)) != NULL;) {
   2547 		if (**ap != '\0') {
   2548 			ap++;
   2549 			count++;
   2550 		}
   2551 	}
   2552 
   2553 	*ap = NULL;
   2554 	return (count);
   2555 }
   2556 
   2557 static char *
   2558 cli_read_line(FILE *fp, char *in, size_t len)
   2559 {
   2560 	char	*p, *t;
   2561 
   2562 	if (fgets(in, len, fp) == NULL)
   2563 		return (NULL);
   2564 
   2565 	p = in;
   2566 	in[strcspn(in, "\n")] = '\0';
   2567 
   2568 	while (isspace(*(unsigned char *)p))
   2569 		p++;
   2570 
   2571 	if (p[0] == '#' || p[0] == '\0') {
   2572 		p[0] = '\0';
   2573 		return (p);
   2574 	}
   2575 
   2576 	for (t = p; *t != '\0'; t++) {
   2577 		if (*t == '\t')
   2578 			*t = ' ';
   2579 	}
   2580 
   2581 	return (p);
   2582 }
   2583 
   2584 static char *
   2585 cli_text_trim(char *string, size_t len)
   2586 {
   2587 	char		*end;
   2588 
   2589 	if (len == 0)
   2590 		return (string);
   2591 
   2592 	end = (string + len) - 1;
   2593 	while (isspace(*(unsigned char *)string) && string < end)
   2594 		string++;
   2595 
   2596 	while (isspace(*(unsigned char *)end) && end > string)
   2597 		*(end)-- = '\0';
   2598 
   2599 	return (string);
   2600 }
   2601 
   2602 static long long
   2603 cli_strtonum(const char *str, long long min, long long max)
   2604 {
   2605 	long long	l;
   2606 	char		*ep;
   2607 
   2608 	if (min > max)
   2609 		fatal("cli_strtonum: min > max");
   2610 
   2611 	errno = 0;
   2612 	l = strtoll(str, &ep, 10);
   2613 	if (errno != 0 || str == ep || *ep != '\0')
   2614 		fatal("strtoll(): %s", errno_s);
   2615 
   2616 	if (l < min)
   2617 		fatal("cli_strtonum: value < min");
   2618 
   2619 	if (l > max)
   2620 		fatal("cli_strtonum: value > max");
   2621 
   2622 	return (l);
   2623 }
   2624 
   2625 static void
   2626 fatal(const char *fmt, ...)
   2627 {
   2628 	va_list		args;
   2629 	char		buf[2048];
   2630 
   2631 	va_start(args, fmt);
   2632 	(void)vsnprintf(buf, sizeof(buf), fmt, args);
   2633 	va_end(args);
   2634 
   2635 	if (command != NULL)
   2636 		printf("kore %s: %s\n", command->name, buf);
   2637 	else
   2638 		printf("kore: %s\n", buf);
   2639 
   2640 	exit(1);
   2641 }