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 }