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