Pharos 0.7.23
Modern Web Framework & Web App Hosting Service.
Loading...
Searching...
No Matches
pharos_runtime.c
Go to the documentation of this file.
1/**
2 * @file
3 * @brief Native Pharos runtime entrypoint and orchestration for serve, runtime, website, and ops command flows.
4 */
5
6#include <ctype.h>
7#include <errno.h>
8#include <fcntl.h>
9#include <limits.h>
10#include <stdint.h>
11#include <stdarg.h>
12#include <stdbool.h>
13#include <signal.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17#include <time.h>
18#ifndef _WIN32
19#include <grp.h>
20#include <strings.h>
21#endif
22
23#ifdef _WIN32
24#include <direct.h>
25#include <io.h>
26#include <process.h>
27#include <sys/stat.h>
28#include <winsock2.h>
29#include <ws2tcpip.h>
30#define PATH_SEP '\\'
31#define PHAROS_SOCKET SOCKET
32#define PHAROS_INVALID_SOCKET INVALID_SOCKET
33#define pharos_close_socket closesocket
34#define pharos_getcwd _getcwd
35#define pharos_access _access
36#define pharos_stat _stat
37#define pharos_mkdir(path, mode) _mkdir(path)
38#define pharos_unlink _unlink
39#define pharos_strdup _strdup
40
41#include <sys/time.h>
42
43static int win_gettimeofday(struct timeval *tv, void *tz) {
44 FILETIME ft;
45 ULARGE_INTEGER li;
46 GetSystemTimeAsFileTime(&ft);
47 li.LowPart = ft.dwLowDateTime;
48 li.HighPart = ft.dwHighDateTime;
49 tv->tv_sec = (long)((li.QuadPart - 116444736000000000ULL) / 10000000ULL);
50 tv->tv_usec = (long)((li.QuadPart / 10ULL) % 1000000ULL);
51 return 0;
52}
53#define gettimeofday win_gettimeofday
54#else
55#include <arpa/inet.h>
56#include <dirent.h>
57#include <netinet/in.h>
58#ifndef __wasm__
59#include <pthread.h>
60#include <sys/socket.h>
61#include <sys/un.h>
62#include <sys/wait.h>
63#endif
64#include <sys/stat.h>
65#include <sys/types.h>
66#include <sys/time.h>
67#include <unistd.h>
68#define PATH_SEP '/'
69#define PHAROS_SOCKET int
70#define PHAROS_INVALID_SOCKET -1
71#define pharos_close_socket close
72#define pharos_getcwd getcwd
73#define pharos_access access
74#define pharos_stat stat
75#define pharos_mkdir mkdir
76#define pharos_unlink unlink
77#define pharos_strdup strdup
78#endif
79
80#include "native_support.h"
81#include "native_json_config.h"
82#include "native_app_config.h"
83#include "native_http_log.h"
84#include "native_fs.h"
89#include "native_runtime_load.h"
93#include "native_serve_config.h"
94#include "native_shell_bridge.h"
95#include "native_http_request.h"
98#include "native_static_serve.h"
99#include "native_request_path.h"
100#include "native_spawn_argv.h"
103#include "native_listener.h"
104#include "native_connection.h"
106#include "native_ops_bridge.h"
111
112#ifndef PHAROS_TARGET_ID
113#define PHAROS_TARGET_ID "source-bootstrap"
114#endif
115
116#ifndef PHAROS_VERSION
117#define PHAROS_VERSION "0.7.23"
118#endif
119
120/**
121 * @brief Per-request worker state used while routing either runtime or app traffic.
122 */
129
130#define PHAROS_NATIVE_DEFAULT_WORKER_COUNT 16
131#define PHAROS_NATIVE_MIN_WORKER_COUNT 1
132#define PHAROS_NATIVE_MAX_WORKER_COUNT 256
133#define PHAROS_NATIVE_MIN_WORK_QUEUE_CAPACITY 32
134#define PHAROS_NATIVE_MAX_WORK_QUEUE_CAPACITY 8192
135#define PHAROS_NATIVE_DEFAULT_WORK_QUEUE_CAPACITY 128
136#define PHAROS_NATIVE_WORK_QUEUE_BYTES_PER_SLOT (8ULL * 1024ULL * 1024ULL)
137
138typedef struct {
140 size_t capacity;
141 size_t head;
142 size_t tail;
143 size_t count;
146#ifdef _WIN32
147 CRITICAL_SECTION mutex;
148 CONDITION_VARIABLE not_empty;
149 HANDLE *threads;
150#else
151 pthread_mutex_t mutex;
152 pthread_cond_t not_empty;
153 pthread_t *threads;
154#endif
156
157/**
158 * @brief Release a worker context and every owned request/runtime resource.
159 *
160 * @param context Per-request worker state to release.
161 */
162static void free_worker_context_native(WorkerContext *context);
163
164/**
165 * @brief Execute one accepted request through the active runtime/app handler.
166 *
167 * @param context Per-request worker state to process.
168 */
169static void process_worker_context_native(WorkerContext *context);
170
171/**
172 * @brief Initialize a bounded worker pool for native request serving.
173 *
174 * @param pool Pool state to initialize.
175 * @param worker_count Fixed number of worker threads to create.
176 * @param capacity Maximum queued requests.
177 * @return 0 on success, non-zero on failure.
178 */
179static int native_worker_pool_init(NativeWorkerPool *pool, size_t worker_count, size_t capacity);
180
181/**
182 * @brief Submit accepted request work to the bounded worker pool.
183 *
184 * @param pool Active worker pool.
185 * @param context Per-request work item to enqueue.
186 * @return 0 on success, 1 when saturated or shutting down, non-zero on internal failure.
187 */
189
190/**
191 * @brief Ask the worker pool to stop after draining queued work.
192 *
193 * @param pool Active worker pool.
194 */
196
197/**
198 * @brief Join all worker threads created for the pool.
199 *
200 * @param pool Active worker pool.
201 */
203
204/**
205 * @brief Destroy worker-pool resources after shutdown and join.
206 *
207 * @param pool Worker pool to destroy.
208 */
210
211/**
212 * @brief Send the standard overload response for saturated native serving.
213 *
214 * @param connection Accepted client connection.
215 */
217
218/**
219 * @brief Recursively write macOS no-index markers through a directory tree.
220 *
221 * @param root_path Directory tree to mark.
222 * @return 0 on success, non-zero on failure.
223 */
224static int write_noindex_markers_recursive_native(const char *root_path);
225
226/**
227 * @brief Resolve the fixed worker-thread count for one serve process.
228 *
229 * @param runtime_conf_path Runtime configuration path to consult for overrides.
230 * @return Positive worker-thread count.
231 */
232static size_t native_resolved_worker_count(const char *runtime_conf_path);
233
234/**
235 * @brief Resolve the bounded queued-request capacity for one serve process.
236 *
237 * @param runtime_conf_path Runtime configuration path to consult for overrides.
238 * @return Positive queued-request capacity.
239 */
240static size_t native_resolved_work_queue_capacity(const char *runtime_conf_path);
241
242typedef int (*NativeServeHandlerFn)(const ServeConfig *config);
243
244static char *global_repo_root = NULL;
245static char *global_binary_dir = NULL;
246static volatile sig_atomic_t global_shutdown_requested = 0;
248
252
256
260
261#ifndef _WIN32
263 (void)signum;
266}
267
271#ifdef SIGHUP
273#endif
274}
275#else
276static void install_native_signal_handlers(void) {
277}
278#endif
279
280
281
282static int native_known_website_supported(const char *target_id) {
283 (void)target_id;
284 return 0;
285}
286
287static int build_known_website_native(const char *target_id, const char *build_dir, char **report_path_out, char **artifact_dir_out) {
288 (void)target_id;
289 (void)build_dir;
290 (void)report_path_out;
291 (void)artifact_dir_out;
292 return 78;
293}
294
296 const char *app_root,
297 const char *build_dir_override,
298 const char *state_dir_override,
299 const char *state_path_override,
300 const char *base_path_override,
301 char **report_path_out,
302 char **artifact_dir_out
303);
304static int build_dev_docs_directory_native(const char *app_root, const char *artifact_dir);
305static int app_root_has_finalized_artifact_native(const char *app_root);
306static int maybe_seed_app_state_native(const ServeConfig *config, const char *app_root, const char *app_id, int force_seed);
307
308static int emit_website_status_native(const char *verb, const char *target_id, const char *build_dir, char **report_path_out) {
309 char *artifact_dir = native_website_artifact_dir(build_dir, target_id);
310 char *index_path = NULL;
311 char *report_path = NULL;
312 char *report_json = NULL;
313 int code = 0;
314 const char *report_id = "pharos-end-to-end-site-v1";
315 if (artifact_dir == NULL) {
316 return 69;
317 }
318 index_path = path_join(artifact_dir, "index.html");
319 if (index_path == NULL) {
320 free(artifact_dir);
321 return 69;
322 }
323 if (!path_exists(index_path)) {
324 code = build_known_website_native(target_id, build_dir, NULL, NULL);
325 if (code != 0) {
326 free(artifact_dir);
327 free(index_path);
328 return code;
329 }
330 }
331 report_path = native_website_report_path(build_dir, verb, target_id);
332 if (report_path == NULL) {
333 free(artifact_dir);
334 free(index_path);
335 return 69;
336 }
337 report_json = build_native_website_status_report_json(report_id, verb, target_id, artifact_dir);
338 if (report_json == NULL) {
339 free(artifact_dir);
340 free(index_path);
341 free(report_path);
342 return 69;
343 }
344 code = write_text_file(report_path, report_json);
345 free(report_json);
346 free(artifact_dir);
347 free(index_path);
348 if (code != 0) {
349 free(report_path);
350 return code;
351 }
352 if (report_path_out != NULL) *report_path_out = report_path; else free(report_path);
353 return 0;
354}
355
356static char *app_runtime_host_profile_native(const char *app_root) {
357 char *manifest_path = NULL;
358 char *manifest_text = NULL;
359 char *host_profile = NULL;
360 if (app_root == NULL) {
361 return NULL;
362 }
363 manifest_path = path_join(app_root, "pharos.app.json");
364 manifest_text = manifest_path != NULL ? read_file_text(manifest_path) : NULL;
365 if (manifest_text != NULL) {
366 host_profile = json_find_string_in_section(manifest_text, "runtime", "host_profile");
367 }
368 free(manifest_text);
369 free(manifest_path);
370 if (host_profile == NULL) {
371 return pharos_strdup("dynamic-app");
372 }
373 return host_profile;
374}
375
376
377
378static char *runtime_build_dir_override_native(const ServeConfig *config, const char *app_root) {
379 if (config != NULL && config->build_dir != NULL && config->build_dir[0] != '\0') {
380 return pharos_strdup(config->build_dir);
381 }
382 if (global_repo_root != NULL && global_repo_root[0] != '\0') {
383 return path_join(global_repo_root, "dist");
384 }
385 return app_build_dir_local_native(app_root);
386}
387
388/**
389 * @brief Prepares the artifact directory for an app at startup.
390 *
391 * Both @c dynamic-app and @c static-pages profiles now use the native artifact
392 * builder. Static pages are served directly from the finalized artifact and no
393 * longer require a shell-built app-local request host during native runtime
394 * preparation.
395 *
396 * @param app_root Filesystem path to the app root.
397 * @param app_id Application identifier.
398 * @param build_dir_override Build directory override (may be NULL).
399 * @param state_dir_override State directory override (may be NULL).
400 * @param state_path_override State file override (may be NULL).
401 * @param base_path_override URL base-path override (may be NULL).
402 * @param report_path_out Receives the build report path (may be NULL).
403 * @param artifact_dir_out Receives the artifact directory on success.
404 * @return 0 on success, non-zero on failure.
405 */
407 const char *app_root,
408 const char *app_id,
409 const char *build_dir_override,
410 const char *state_dir_override,
411 const char *state_path_override,
412 const char *base_path_override,
413 char **report_path_out,
414 char **artifact_dir_out
415) {
416 char *build_dir = NULL;
417 int code;
418 if (report_path_out != NULL) {
419 *report_path_out = NULL;
420 }
421 if (artifact_dir_out != NULL) {
422 *artifact_dir_out = NULL;
423 }
424 if (app_root == NULL || app_id == NULL) {
425 return 66;
426 }
427 build_dir = effective_app_build_dir_native(app_root, build_dir_override);
428 if (build_dir == NULL) {
429 return 69;
430 }
431 (void)app_id;
432 code = build_app_from_root_native(app_root, build_dir, state_dir_override, state_path_override, base_path_override, report_path_out, artifact_dir_out);
433 free(build_dir);
434 return code;
435}
436
437static const char *dev_docs_directory_index_html_native(void) {
438 return "<!DOCTYPE html>\n"
439 "<html lang=\"en\">\n"
440 "<head>\n"
441 "<meta charset=\"UTF-8\">\n"
442 "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
443 "<title>Pharos Docs</title>\n"
444 "<link rel=\"stylesheet\" href=\"/dev_docs/docs/assets/docs.css\">\n"
445 "</head>\n"
446 "<body>\n"
447 "<main>\n"
448 " <section class=\"banner\">\n"
449 " <img src=\"/dev_docs/docs/assets/pharos-framework-wordmark.svg\" alt=\"Pharos Framework\" class=\"wordmark\">\n"
450 " </section>\n"
451 " <section class=\"card\">\n"
452 " <h2>Primary entry points</h2>\n"
453 " <ul>\n"
454 " <li><a href=\"/dev_docs/\">Home</a></li>\n"
455 " <li><a href=\"/dev_docs/docs/pharos_framework.html\">Framework overview</a></li>\n"
456 " <li><a href=\"/dev_docs/docs/how-to-use-pharos-cli.html\">CLI guide</a></li>\n"
457 " <li><a href=\"/dev_docs/docs/operations/runtime-operations.html\">Runtime operations</a></li>\n"
458 " <li><a href=\"/dev_docs/docs/spec.html\">Framework specification</a></li>\n"
459 " <li><a href=\"/dev_docs/docs/platform-vision.html\">Platform vision</a></li>\n"
460 " <li><a href=\"/dev_docs/docs/html/index.html\">Doxygen source reference</a></li>\n"
461 " </ul>\n"
462 " </section>\n"
463
464 "</main>\n"
465 "</body>\n"
466 "</html>\n";
467}
468
469static int build_dev_docs_directory_native(const char *app_root, const char *artifact_dir) {
470#ifndef _WIN32
471 char *apps_root = NULL;
472 char *repo_root = NULL;
473 char *repo_docs = NULL;
474 char *repo_doxyfile = NULL;
475 char *target_docs = NULL;
476 char *target_docs_html = NULL;
477 char *target_docs_index = NULL;
478 char *generated_doxyfile = NULL;
479 char *markdown_renderer = NULL;
480 char *artifact_noindex = NULL;
481 char *target_docs_noindex = NULL;
482 char *target_docs_html_noindex = NULL;
483 char *doxyfile_text = NULL;
484 char *generated_doxyfile_text = NULL;
485 char *command = NULL;
486 char *output = NULL;
487 char *argv_list[4];
488 size_t needed = 0;
489 int code = 69;
490 if (app_root == NULL || artifact_dir == NULL) {
491 return 69;
492 }
493 apps_root = path_dirname(app_root);
494 repo_root = apps_root != NULL ? path_dirname(apps_root) : NULL;
495 repo_docs = repo_root != NULL ? path_join(repo_root, "docs") : NULL;
496 repo_doxyfile = repo_root != NULL ? path_join(repo_root, "Doxyfile") : NULL;
497 target_docs = path_join(artifact_dir, "docs");
498 target_docs_html = target_docs != NULL ? path_join(target_docs, "html") : NULL;
499 target_docs_index = target_docs != NULL ? path_join(target_docs, "index.html") : NULL;
500 generated_doxyfile = path_join(artifact_dir, "Doxyfile.dev_docs");
501 markdown_renderer = path_join(artifact_dir, "dev_docs_markdown_render.py");
502 artifact_noindex = path_join(artifact_dir, ".metadata_never_index");
503 target_docs_noindex = path_join(target_docs, ".metadata_never_index");
504 target_docs_html_noindex = path_join(target_docs_html, ".metadata_never_index");
505 if (repo_root == NULL || repo_docs == NULL || repo_doxyfile == NULL || target_docs == NULL || target_docs_html == NULL || target_docs_index == NULL || generated_doxyfile == NULL || markdown_renderer == NULL || artifact_noindex == NULL || target_docs_noindex == NULL || target_docs_html_noindex == NULL) {
506 code = 69;
507 goto cleanup;
508 }
509 if (!path_is_directory(repo_docs) || !path_exists(repo_doxyfile)) {
510 code = 66;
511 goto cleanup;
512 }
513 code = copy_directory_recursive(repo_docs, target_docs);
514 if (code != 0) {
515 goto cleanup;
516 }
517 remove_directory_recursive(target_docs_html);
518#ifdef __APPLE__
519 code = write_text_file(artifact_noindex, "");
520 if (code != 0) {
521 goto cleanup;
522 }
523 code = write_text_file(target_docs_noindex, "");
524 if (code != 0) {
525 goto cleanup;
526 }
527#endif
528 {
529 static const char *renderer_script =
530 "from pathlib import Path\n"
531 "import html\n"
532 "import re\n"
533 "import sys\n"
534 "ROOT = Path(sys.argv[1])\n"
535 "HTML_TEMPLATE = \"\"\"<!DOCTYPE html>\n"
536 "<html lang=\\\"en\\\">\n"
537 "<head>\n"
538 "<meta charset=\\\"UTF-8\\\">\n"
539 "<meta name=\\\"viewport\\\" content=\\\"width=device-width, initial-scale=1.0\\\">\n"
540 "<title>{title}</title>\n"
541 "<link rel=\"stylesheet\" href=\"{stylesheet_src}\">\n"
542 "</head>\n"
543 "<body>\n"
544 "<main>\n"
545 "<section class=\\\"banner\\\">\n"
546 "<img src=\\\"{wordmark_src}\\\" alt=\\\"Pharos Framework\\\" class=\\\"wordmark\\\">\n"
547 "<p class=\\\"backlink\\\"><a href=\\\"{back_href}\\\">← Back</a></p>\n"
548 "{body}\n"
549 "</section>\n"
550 "</main>\n"
551 "</body>\n"
552 "</html>\n"
553 "\"\"\"\n"
554 "LINK_RE = re.compile(r'\\[([^\\]]+)\\]\\(([^)]+)\\)')\n"
555 "INLINE_CODE_RE = re.compile(r'`([^`]+)`')\n"
556 "BOLD_RE = re.compile(r'\\*\\*([^*]+)\\*\\*')\n"
557 "ITALIC_RE = re.compile(r'(?<!\\*)\\*([^*]+)\\*(?!\\*)')\n"
558 "def rewrite_href(target):\n"
559 " if target.endswith('.md'): return target[:-3] + '.html'\n"
560 " if '.md#' in target: return target.replace('.md#', '.html#')\n"
561 " return target\n"
562 "def inline_markup(text):\n"
563 " escaped = html.escape(text)\n"
564 " escaped = LINK_RE.sub(lambda m: f'<a href=\\\"{html.escape(rewrite_href(m.group(2)), quote=True)}\\\">{m.group(1)}</a>', escaped)\n"
565 " escaped = INLINE_CODE_RE.sub(lambda m: f'<code>{m.group(1)}</code>', escaped)\n"
566 " escaped = BOLD_RE.sub(lambda m: f'<strong>{m.group(1)}</strong>', escaped)\n"
567 " escaped = ITALIC_RE.sub(lambda m: f'<em>{m.group(1)}</em>', escaped)\n"
568 " return escaped\n"
569 "def render_markdown(text, rel_path):\n"
570 " lines = text.splitlines()\n"
571 " out = []\n"
572 " in_code = False\n"
573 " code_lines = []\n"
574 " para = []\n"
575 " list_type = None\n"
576 " title = rel_path.stem.replace('-', ' ').replace('_', ' ').title()\n"
577 " def close_paragraph():\n"
578 " nonlocal para\n"
579 " if para:\n"
580 " out.append(f\"<p>{inline_markup(' '.join(para).strip())}</p>\")\n"
581 " para = []\n"
582 " def close_list():\n"
583 " nonlocal list_type\n"
584 " if list_type is not None:\n"
585 " out.append(f'</{list_type}>')\n"
586 " list_type = None\n"
587 " for line in lines:\n"
588 " stripped = line.rstrip('\\n')\n"
589 " if stripped.startswith('```'):\n"
590 " close_paragraph(); close_list()\n"
591 " if in_code:\n"
592 " out.append('<pre><code>' + html.escape('\\n'.join(code_lines)) + '</code></pre>')\n"
593 " code_lines = []\n"
594 " in_code = False\n"
595 " else:\n"
596 " in_code = True\n"
597 " continue\n"
598 " if in_code:\n"
599 " code_lines.append(stripped)\n"
600 " continue\n"
601 " if not stripped.strip():\n"
602 " close_paragraph(); close_list(); continue\n"
603 " heading_match = re.match(r'^(#{1,6})\\s+(.*)$', stripped)\n"
604 " if heading_match:\n"
605 " close_paragraph(); close_list()\n"
606 " level = len(heading_match.group(1))\n"
607 " heading_text = heading_match.group(2).strip()\n"
608 " if level == 1: title = heading_text\n"
609 " out.append(f'<h{level}>{inline_markup(heading_text)}</h{level}>')\n"
610 " continue\n"
611 " unordered_match = re.match(r'^\\s*[-*]\\s+(.*)$', stripped)\n"
612 " ordered_match = re.match(r'^\\s*\\d+\\.\\s+(.*)$', stripped)\n"
613 " if unordered_match or ordered_match:\n"
614 " close_paragraph()\n"
615 " next_type = 'ul' if unordered_match else 'ol'\n"
616 " item_text = unordered_match.group(1) if unordered_match else ordered_match.group(1)\n"
617 " if list_type != next_type:\n"
618 " close_list()\n"
619 " list_type = next_type\n"
620 " out.append(f'<{list_type}>')\n"
621 " out.append(f'<li>{inline_markup(item_text.strip())}</li>')\n"
622 " continue\n"
623 " para.append(stripped.strip())\n"
624 " close_paragraph(); close_list()\n"
625 " if in_code:\n"
626 " out.append('<pre><code>' + html.escape('\\n'.join(code_lines)) + '</code></pre>')\n"
627 " prefix = '' if rel_path.parent == Path('.') else '../' * len(rel_path.parent.parts)\n"
628 " back_href = 'index.html' if rel_path.parent == Path('.') else prefix + 'index.html'\n"
629 " wordmark_src = prefix + 'assets/pharos-framework-wordmark.svg'\n"
630 " stylesheet_src = prefix + 'assets/docs.css'\n"
631 " return HTML_TEMPLATE.format(title=html.escape(title), back_href=html.escape(back_href, quote=True), wordmark_src=html.escape(wordmark_src, quote=True), stylesheet_src=html.escape(stylesheet_src, quote=True), body='\\n'.join(out))\n"
632 "def rewrite_html_links(path):\n"
633 " text = path.read_text(encoding='utf-8')\n"
634 " text = text.replace('.md#', '.html#')\n"
635 " text = re.sub(r'([\"\\\'])((?:[^\"\\\']+/)?[^\"\\\']+\\.md)(#[^\"\\\']*)?\\1', lambda m: f'{m.group(1)}{rewrite_href(m.group(2))}{m.group(3) or \"\"}{m.group(1)}', text)\n"
636 " path.write_text(text, encoding='utf-8')\n"
637 "markdown_files = sorted(ROOT.rglob('*.md'))\n"
638 "for md_path in markdown_files:\n"
639 " rel = md_path.relative_to(ROOT)\n"
640 " html_path = md_path.with_suffix('.html')\n"
641 " html_path.write_text(render_markdown(md_path.read_text(encoding='utf-8'), rel), encoding='utf-8')\n"
642 "for html_path in sorted(ROOT.rglob('*.html')):\n"
643 " rewrite_html_links(html_path)\n"
644 "for md_path in markdown_files:\n"
645 " md_path.unlink()\n";
646 code = write_text_file(markdown_renderer, renderer_script);
647 if (code != 0) {
648 goto cleanup;
649 }
650 needed = strlen(markdown_renderer) + strlen(target_docs) + 32;
651 command = (char *)malloc(needed);
652 if (command == NULL) {
653 code = 69;
654 goto cleanup;
655 }
656 snprintf(command, needed, "python3 %s %s", markdown_renderer, target_docs);
657 argv_list[0] = "/bin/sh";
658 argv_list[1] = "-c";
659 argv_list[2] = command;
660 argv_list[3] = NULL;
661 code = capture_execv_output_native(argv_list, &output, NULL);
662 free(command);
663 command = NULL;
664 free(output);
665 output = NULL;
666 if (code != 0) {
667 goto cleanup;
668 }
669 }
670 code = write_text_file(target_docs_index, dev_docs_directory_index_html_native());
671 if (code != 0) {
672 goto cleanup;
673 }
674 doxyfile_text = read_file_text(repo_doxyfile);
675 if (doxyfile_text == NULL) {
676 code = 66;
677 goto cleanup;
678 }
679 needed = strlen(doxyfile_text) + strlen(target_docs) + 96;
680 generated_doxyfile_text = (char *)malloc(needed);
681 if (generated_doxyfile_text == NULL) {
682 code = 69;
683 goto cleanup;
684 }
685 snprintf(generated_doxyfile_text, needed, "%s\nOUTPUT_DIRECTORY = %s\nHTML_OUTPUT = html\n", doxyfile_text, target_docs);
686 code = write_text_file(generated_doxyfile, generated_doxyfile_text);
687 if (code != 0) {
688 goto cleanup;
689 }
690 needed = strlen(repo_root) + strlen(generated_doxyfile) + 32;
691 command = (char *)malloc(needed);
692 if (command == NULL) {
693 code = 69;
694 goto cleanup;
695 }
696 snprintf(command, needed, "cd %s && doxygen %s", repo_root, generated_doxyfile);
697 argv_list[0] = "/bin/sh";
698 argv_list[1] = "-c";
699 argv_list[2] = command;
700 argv_list[3] = NULL;
701 code = capture_execv_output_native(argv_list, &output, NULL);
702 if (code != 0 && output != NULL && output[0] != '\0') {
703 fprintf(stderr, "%s", output);
704 }
705#ifdef __APPLE__
706 if (code == 0 && path_is_directory(target_docs_html)) {
707 int noindex_code = write_text_file(target_docs_html_noindex, "");
708 if (noindex_code != 0) {
709 code = noindex_code;
710 }
711 }
712#endif
713cleanup:
714 if (generated_doxyfile != NULL && path_exists(generated_doxyfile)) {
715 pharos_unlink(generated_doxyfile);
716 }
717 if (markdown_renderer != NULL && path_exists(markdown_renderer)) {
718 pharos_unlink(markdown_renderer);
719 }
720 free(apps_root);
721 free(repo_root);
722 free(repo_docs);
723 free(repo_doxyfile);
724 free(target_docs);
725 free(target_docs_html);
726 free(target_docs_index);
727 free(generated_doxyfile);
728 free(markdown_renderer);
729 free(artifact_noindex);
730 free(target_docs_noindex);
731 free(target_docs_html_noindex);
732 free(doxyfile_text);
733 free(generated_doxyfile_text);
734 free(command);
735 free(output);
736 return code;
737#else
738 (void)app_root;
739 (void)artifact_dir;
740 return 78;
741#endif
742}
743
745 const char *app_root,
746 const char *build_dir_override,
747 const char *state_dir_override,
748 const char *state_path_override,
749 const char *base_path_override,
750 char **report_path_out,
751 char **artifact_dir_out
752) {
753 char *manifest_path = path_join(app_root, "pharos.app.json");
754 char *manifest_text = NULL;
755 char *app_id = NULL;
756 char *compiled_entrypoint = NULL;
757 char *host_profile = NULL;
758 char *state_path = NULL;
759 char *base_path = NULL;
760 char *payload_dir_rel = NULL;
761 char *static_dir_rel = NULL;
762 char *media_dir_rel = NULL;
763 char *log_path = NULL;
764 char *log_format = NULL;
765 char *error_report_path = NULL;
766 char *debug = NULL;
767 char *route_fixture_rel = NULL;
768 char *surface_fixture_rel = NULL;
769 char *workflow_fixture_rel = NULL;
770 char *form_fixture_rel = NULL;
771 char *policy_fixture_rel = NULL;
772 char *entity_fixture_rel = NULL;
773 char *query_fixture_rel = NULL;
774 char *migration_fixture_rel = NULL;
775 char *seed_sources_text = NULL;
776 char *preferred_backend = NULL;
777 char *development_backend = NULL;
778 char *testing_backend = NULL;
779 char *build_dir = NULL;
780 char *artifact_dir = NULL;
781 char *report_path = NULL;
782 char *index_path = NULL;
783 char *build_dir_noindex = NULL;
784
785 char *payload_dir = NULL;
786 char *static_dir = NULL;
787 char *media_dir = NULL;
788 char *templates_dir = NULL;
789 char *data_dir = NULL;
790 char *fixtures_dir = NULL;
791 char *app_manifest_source_path = NULL;
792 char *app_conf_source_path = NULL;
793 char *pharos_conf_source_path = NULL;
794 char *start_script_source_path = NULL;
795 char *app_manifest_target_path = NULL;
796 char *app_conf_target_path = NULL;
797 char *start_script_target_path = NULL;
798 char *target_static = NULL;
799 char *target_media = NULL;
800
801 char *index_html = NULL;
802 char *artifact_local_app_manifest_json = NULL;
803
804 char *route_fixture = NULL;
805 char *surface_fixture = NULL;
806 char *workflow_fixture = NULL;
807 char *form_fixture = NULL;
808 char *policy_fixture = NULL;
809 char *entity_fixture = NULL;
810 char *query_fixture = NULL;
811 char *migration_fixture = NULL;
812 char *route_fixture_text = NULL;
813 char *surface_fixture_text = NULL;
814 char *workflow_fixture_text = NULL;
815 int route_count = 0;
816 int surface_count = 0;
817 int workflow_count = 0;
818 int code = 0;
819 if (manifest_path == NULL || !path_exists(manifest_path)) {
820 free(manifest_path);
821 return 66;
822 }
823 manifest_text = read_file_text(manifest_path);
824 if (manifest_text == NULL) {
825 free(manifest_path);
826 return 66;
827 }
828 app_id = json_find_string(manifest_text, "app_id");
829 compiled_entrypoint = json_find_string_in_section(manifest_text, "runtime", "compiled_entrypoint");
830 host_profile = json_find_string_in_section(manifest_text, "runtime", "host_profile");
831 payload_dir_rel = json_find_string_in_section(manifest_text, "assets", "payload_dir");
832 static_dir_rel = json_find_string_in_section(manifest_text, "assets", "static_dir");
833 media_dir_rel = json_find_string_in_section(manifest_text, "assets", "media_dir");
834 route_fixture_rel = json_find_string_in_section(manifest_text, "fixtures", "route_fixture");
835 surface_fixture_rel = json_find_string_in_section(manifest_text, "fixtures", "surface_fixture");
836 workflow_fixture_rel = json_find_string_in_section(manifest_text, "fixtures", "workflow_fixture");
837 form_fixture_rel = json_find_string_in_section(manifest_text, "fixtures", "form_fixture");
838 policy_fixture_rel = json_find_string_in_section(manifest_text, "fixtures", "policy_fixture");
839 entity_fixture_rel = json_find_string_in_section(manifest_text, "database", "entity_fixture");
840 query_fixture_rel = json_find_string_in_section(manifest_text, "database", "query_fixture");
841 migration_fixture_rel = json_find_string_in_section(manifest_text, "database", "migration_fixture");
842 seed_sources_text = json_extract_array_in_section(manifest_text, "database", "seed_sources");
843 preferred_backend = json_find_string_in_section(manifest_text, "database", "preferred_backend");
844 development_backend = json_find_string_in_section(manifest_text, "database", "development_backend");
845 testing_backend = json_find_string_in_section(manifest_text, "database", "testing_backend");
846 if (app_id == NULL) {
847 code = 66;
848 goto cleanup;
849 }
850 build_dir = effective_app_build_dir_native(app_root, build_dir_override);
851 artifact_dir = effective_app_artifact_dir_native(app_root, build_dir, app_id);
852 payload_dir = payload_dir_rel != NULL ? path_join(app_root, payload_dir_rel) : NULL;
853 static_dir = static_dir_rel != NULL ? path_join(app_root, static_dir_rel) : NULL;
854 media_dir = media_dir_rel != NULL ? path_join(app_root, media_dir_rel) : NULL;
855 templates_dir = path_join(app_root, "templates");
856 data_dir = path_join(app_root, "data");
857 fixtures_dir = path_join(app_root, "fixtures");
858 app_manifest_source_path = path_join(app_root, "pharos.app.json");
859 app_conf_source_path = path_join(app_root, "app.conf");
860 pharos_conf_source_path = path_join(app_root, "pharos.conf");
861 start_script_source_path = path_join(app_root, "start.sh");
862 app_manifest_target_path = path_join(artifact_dir, "pharos.app.json");
863 app_conf_target_path = path_join(artifact_dir, "app.conf");
864 start_script_target_path = path_join(artifact_dir, "start.sh");
865 target_static = path_join(artifact_dir, "static");
866 target_media = path_join(artifact_dir, "media");
867 index_path = path_join(artifact_dir, "index.html");
868 report_path = native_website_report_path(build_dir, "build-app", app_id);
869 build_dir_noindex = path_join(build_dir, ".metadata_never_index");
870 if (build_dir == NULL || artifact_dir == NULL || target_static == NULL || target_media == NULL || index_path == NULL || report_path == NULL || build_dir_noindex == NULL || app_manifest_source_path == NULL || app_manifest_target_path == NULL || app_conf_target_path == NULL || start_script_source_path == NULL || start_script_target_path == NULL) {
871 code = 69;
872 goto cleanup;
873 }
874 remove_directory_recursive(artifact_dir);
875 if (ensure_directory(artifact_dir) != 0) {
876 code = 69;
877 goto cleanup;
878 }
879#ifdef __APPLE__
880 code = write_text_file(build_dir_noindex, "");
881 if (code != 0) {
882 goto cleanup;
883 }
884#endif
885
886 if (path_exists(app_conf_source_path)) {
887 code = copy_file_binary(app_conf_source_path, app_conf_target_path);
888 if (code != 0) goto cleanup;
889 } else if (path_exists(pharos_conf_source_path)) {
890 code = copy_file_binary(pharos_conf_source_path, app_conf_target_path);
891 if (code != 0) goto cleanup;
892 }
893 if (path_exists(start_script_source_path)) {
894 code = copy_file_binary(start_script_source_path, start_script_target_path);
895 if (code != 0) goto cleanup;
896 }
897 if (data_dir != NULL && path_is_directory(data_dir)) {
898 char *target_data = path_join(artifact_dir, "data");
899 if (target_data == NULL) {
900 code = 69;
901 goto cleanup;
902 }
903 code = copy_directory_recursive(data_dir, target_data);
904 free(target_data);
905 if (code != 0) goto cleanup;
906 }
907 if (fixtures_dir != NULL && path_is_directory(fixtures_dir)) {
908 char *target_fixtures = path_join(artifact_dir, "fixtures");
909 if (target_fixtures == NULL) {
910 code = 69;
911 goto cleanup;
912 }
913 code = copy_directory_recursive(fixtures_dir, target_fixtures);
914 free(target_fixtures);
915 if (code != 0) goto cleanup;
916 }
917 if (payload_dir != NULL && path_is_directory(payload_dir)) {
918 code = copy_directory_recursive(payload_dir, artifact_dir);
919 if (code != 0) goto cleanup;
920 }
921 if (static_dir != NULL && path_is_directory(static_dir)) {
922 code = copy_directory_recursive(static_dir, target_static);
923 if (code != 0) goto cleanup;
924 }
925 if (media_dir != NULL && path_is_directory(media_dir)) {
926 code = copy_directory_recursive(media_dir, target_media);
927 if (code != 0) goto cleanup;
928 }
929 if (templates_dir != NULL && path_is_directory(templates_dir)) {
930 char *target_templates = path_join(artifact_dir, "templates");
931 if (target_templates == NULL) {
932 code = 69;
933 goto cleanup;
934 }
935 code = copy_directory_recursive(templates_dir, target_templates);
936 free(target_templates);
937 if (code != 0) goto cleanup;
938 }
939 if (streq(app_id, "dev_docs")) {
940 code = build_dev_docs_directory_native(app_root, artifact_dir);
941 if (code != 0) goto cleanup;
942 }
943 route_fixture = route_fixture_rel != NULL ? path_join(app_root, route_fixture_rel) : NULL;
944 surface_fixture = surface_fixture_rel != NULL ? path_join(app_root, surface_fixture_rel) : NULL;
945 workflow_fixture = workflow_fixture_rel != NULL ? path_join(app_root, workflow_fixture_rel) : NULL;
946 form_fixture = form_fixture_rel != NULL ? path_join(app_root, form_fixture_rel) : NULL;
947 policy_fixture = policy_fixture_rel != NULL ? path_join(app_root, policy_fixture_rel) : NULL;
948 entity_fixture = entity_fixture_rel != NULL ? path_join(app_root, entity_fixture_rel) : NULL;
949 query_fixture = query_fixture_rel != NULL ? path_join(app_root, query_fixture_rel) : NULL;
950 migration_fixture = migration_fixture_rel != NULL ? path_join(app_root, migration_fixture_rel) : NULL;
951 route_fixture_text = route_fixture != NULL ? read_file_text(route_fixture) : NULL;
952 surface_fixture_text = surface_fixture != NULL ? read_file_text(surface_fixture) : NULL;
953 workflow_fixture_text = workflow_fixture != NULL ? read_file_text(workflow_fixture) : NULL;
954 route_count = count_substring_occurrences(route_fixture_text, "\"route_id\"");
955 surface_count = count_substring_occurrences(surface_fixture_text, "\"surface_id\"");
956 workflow_count = count_substring_occurrences(workflow_fixture_text, "\"workflow_id\"");
957 state_path = effective_app_state_path_native(app_root, build_dir, app_id, state_path_override, state_dir_override);
958 base_path = effective_app_base_path_native(app_root, base_path_override);
959 log_path = effective_app_log_path_native(app_root, app_id, NULL);
960 log_format = effective_app_log_format_native(app_root);
961 error_report_path = effective_app_error_report_path_native(app_root, app_id);
962 debug = effective_app_debug_native(app_root, NULL);
963 if (compiled_entrypoint != NULL && compiled_entrypoint[0] != '\0' && (
964#ifdef _WIN32
965 !(strlen(compiled_entrypoint) > 2 && compiled_entrypoint[1] == ':') &&
966#endif
967 compiled_entrypoint[0] != '/'
968 )) {
969 char *resolved = path_join(app_root, compiled_entrypoint);
970 free(compiled_entrypoint);
971 compiled_entrypoint = resolved;
972 }
973 index_html = build_native_app_index_html(app_id);
974 artifact_local_app_manifest_json = build_artifact_local_app_manifest_json(
975 manifest_text,
976 "pharos-native-runtime",
977 state_path,
978 base_path,
979 log_path,
980 log_format,
981 error_report_path,
982 debug,
983 "",
984 route_count,
985 surface_count,
986 workflow_count
987 );
988 if (index_html == NULL || artifact_local_app_manifest_json == NULL) {
989 code = 69;
990 goto cleanup;
991 }
992 if (!path_exists(index_path)) {
993 code = write_text_file(index_path, index_html);
994 }
995 if (code == 0) code = write_text_file(app_manifest_target_path, artifact_local_app_manifest_json);
996 if (code == 0) {
997 char *report_json = build_native_app_build_report_json(artifact_dir, app_id, state_path, base_path, log_path, log_format, error_report_path, debug, host_profile, compiled_entrypoint);
998 if (report_json == NULL) {
999 code = 69;
1000 goto cleanup;
1001 }
1002 code = write_text_file(report_path, report_json);
1003 free(report_json);
1004 }
1005cleanup:
1006 free(manifest_path);
1007 free(manifest_text);
1008 free(app_id);
1009 free(compiled_entrypoint);
1010 free(host_profile);
1011 free(state_path);
1012 free(base_path);
1013 free(log_path);
1014 free(log_format);
1015 free(error_report_path);
1016 free(debug);
1017 free(payload_dir_rel);
1018 free(static_dir_rel);
1019 free(media_dir_rel);
1020 free(route_fixture_rel);
1021 free(surface_fixture_rel);
1022 free(workflow_fixture_rel);
1023 free(form_fixture_rel);
1024 free(policy_fixture_rel);
1025 free(entity_fixture_rel);
1026 free(query_fixture_rel);
1027 free(migration_fixture_rel);
1028 free(seed_sources_text);
1029 free(preferred_backend);
1030 free(development_backend);
1031 free(testing_backend);
1032 free(build_dir);
1033 free(index_path);
1034 free(build_dir_noindex);
1035 free(payload_dir);
1036 free(static_dir);
1037 free(media_dir);
1038 free(templates_dir);
1039 free(data_dir);
1040 free(fixtures_dir);
1041 free(app_manifest_source_path);
1042 free(app_conf_source_path);
1043 free(pharos_conf_source_path);
1044 free(start_script_source_path);
1045 free(app_manifest_target_path);
1046 free(app_conf_target_path);
1047 free(start_script_target_path);
1048 free(target_static);
1049 free(target_media);
1050
1051 free(index_html);
1052 free(artifact_local_app_manifest_json);
1053 free(route_fixture);
1054 free(surface_fixture);
1055 free(workflow_fixture);
1056 free(form_fixture);
1057 free(policy_fixture);
1058 free(entity_fixture);
1059 free(query_fixture);
1060 free(migration_fixture);
1061 free(route_fixture_text);
1062 free(surface_fixture_text);
1063 free(workflow_fixture_text);
1064 if (code != 0) {
1065 free(artifact_dir);
1066 free(report_path);
1067 return code;
1068 }
1069 if (report_path_out != NULL) *report_path_out = report_path; else free(report_path);
1070 if (artifact_dir_out != NULL) *artifact_dir_out = artifact_dir; else free(artifact_dir);
1071 return 0;
1072}
1073
1074
1075
1076static int add_runtime_app_native(RuntimeStack *stack, const ServeConfig *config, const char *app_root, const char *app_id_hint) {
1077 AppRuntime app;
1078 char *app_id = NULL;
1079 char *build_dir = NULL;
1080 char *artifact_dir = NULL;
1081
1082 if (stack->count >= MAX_APPS) {
1083 return 0;
1084 }
1085 if (app_id_hint != NULL && app_id_hint[0] != '\0') {
1086 app_id = pharos_strdup(app_id_hint);
1087 } else {
1088 char *manifest_path = path_join(app_root, "pharos.app.json");
1089 char *manifest_text = manifest_path != NULL ? read_file_text(manifest_path) : NULL;
1090 if (manifest_text != NULL) {
1091 app_id = json_find_string(manifest_text, "app_id");
1092 }
1093 free(manifest_path);
1094 free(manifest_text);
1095 }
1096 if (app_id == NULL) {
1097 return 66;
1098 }
1099 build_dir = runtime_build_dir_override_native(config, app_root);
1100 if (build_dir == NULL) {
1101 free(app_id);
1102 return 69;
1103 }
1104 if (maybe_seed_app_state_native(config, app_root, app_id, 1) != 0) {
1105 free(app_id);
1106 free(build_dir);
1107 return 69;
1108 }
1110 int load_existing_code = load_app_runtime_from_artifact_native(app_root, &app);
1111 if (load_existing_code == 0) {
1112 if (finalize_loaded_app_runtime_native(&app, config, app_root, build_dir) != 0) {
1113 free_app_runtime(&app);
1114 free(app_id);
1115 free(build_dir);
1116 return 69;
1117 }
1118 stack->apps[stack->count++] = app;
1119 free(app_id);
1120 free(build_dir);
1121 return 0;
1122 }
1123 }
1124 artifact_dir = effective_app_artifact_dir_native(app_root, build_dir, app_id);
1125 if (artifact_dir != NULL) {
1126 int load_existing_code = load_app_runtime_from_artifact_native(artifact_dir, &app);
1127 if (load_existing_code == 0) {
1128 if (finalize_loaded_app_runtime_native(&app, config, app_root, build_dir) != 0) {
1129 free_app_runtime(&app);
1130 free(app_id);
1131 free(build_dir);
1132 free(artifact_dir);
1133 return 69;
1134 }
1135 stack->apps[stack->count++] = app;
1136 free(app_id);
1137 free(build_dir);
1138 free(artifact_dir);
1139 return 0;
1140 }
1141 free(artifact_dir);
1142 artifact_dir = NULL;
1143 }
1144 {
1145 int prepare_code = prepare_app_artifact_native(app_root, app_id, build_dir, config != NULL ? config->state_dir : NULL, config != NULL ? config->state_path : NULL, config != NULL ? config->base_path : NULL, NULL, &artifact_dir);
1146 if (prepare_code != 0) {
1147 free(app_id);
1148 free(build_dir);
1149 return 66;
1150 }
1151 }
1152 if (artifact_dir == NULL) {
1153 free(app_id);
1154 free(build_dir);
1155 return 69;
1156 }
1157 {
1158 int load_code = load_app_runtime_from_artifact_native(artifact_dir, &app);
1159 if (load_code == 0) {
1160 if (finalize_loaded_app_runtime_native(&app, config, app_root, build_dir) != 0) {
1161 free_app_runtime(&app);
1162 free(app_id);
1163 free(build_dir);
1164 free(artifact_dir);
1165 return 69;
1166 }
1167 stack->apps[stack->count++] = app;
1168 free(app_id);
1169 free(build_dir);
1170 free(artifact_dir);
1171 return 0;
1172 }
1173 }
1174 free(app_id);
1175 free(build_dir);
1176 free(artifact_dir);
1177 return 66;
1178}
1179
1180static char *executable_dir_from_argv0(const char *argv0) {
1181 char *cwd;
1182 char *path;
1183 char *dir;
1184 char *canonical;
1185 if (argv0 == NULL) {
1187 }
1188 if (
1189#ifdef _WIN32
1190 (strlen(argv0) > 2 && argv0[1] == ':') ||
1191#endif
1192 argv0[0] == '/' || argv0[0] == '.'
1193 ) {
1194 path = pharos_strdup(argv0);
1195 } else {
1197 path = path_join(cwd, argv0);
1198 free(cwd);
1199 }
1200 dir = path_dirname(path);
1201 free(path);
1202 canonical = path_canonicalize(dir);
1203 free(dir);
1204 return canonical;
1205}
1206
1207static char *find_repo_root(const char *argv0) {
1208 char *search_dir = executable_dir_from_argv0(argv0);
1209 while (search_dir != NULL) {
1210 char *scripts = path_join(search_dir, "scripts/pharos_runtime.sh");
1211 char *nested = path_join(search_dir, "pharos/scripts/pharos_runtime.sh");
1212 if (scripts != NULL && path_exists(scripts)) {
1213 char *repo = path_canonicalize(search_dir);
1214 free(nested);
1215 free(scripts);
1216 free(search_dir);
1217 return repo;
1218 }
1219 if (nested != NULL && path_exists(nested)) {
1220 char *repo = path_join(search_dir, "pharos");
1221 char *canonical_repo = path_canonicalize(repo);
1222 free(repo);
1223 free(nested);
1224 free(scripts);
1225 free(search_dir);
1226 return canonical_repo;
1227 }
1228 free(nested);
1229 free(scripts);
1230 if (streq(search_dir, "/") || streq(search_dir, ".") || strlen(search_dir) <= 1) {
1231 break;
1232 }
1233#ifdef _WIN32
1234 if (strlen(search_dir) == 3 && search_dir[1] == ':' && (search_dir[2] == '\\' || search_dir[2] == '/')) {
1235 break;
1236 }
1237#endif
1238 {
1239 char *parent = path_dirname(search_dir);
1240 if (parent == NULL || streq(parent, search_dir)) {
1241 free(parent);
1242 break;
1243 }
1244 free(search_dir);
1245 search_dir = parent;
1246 }
1247 }
1248 free(search_dir);
1249 search_dir = current_working_directory();
1250 while (search_dir != NULL) {
1251 char *scripts = path_join(search_dir, "scripts/pharos_runtime.sh");
1252 char *nested = path_join(search_dir, "pharos/scripts/pharos_runtime.sh");
1253 if (scripts != NULL && path_exists(scripts)) {
1254 char *repo = path_canonicalize(search_dir);
1255 free(nested);
1256 free(scripts);
1257 free(search_dir);
1258 return repo;
1259 }
1260 if (nested != NULL && path_exists(nested)) {
1261 char *repo = path_join(search_dir, "pharos");
1262 char *canonical_repo = path_canonicalize(repo);
1263 free(repo);
1264 free(nested);
1265 free(scripts);
1266 free(search_dir);
1267 return canonical_repo;
1268 }
1269 free(nested);
1270 free(scripts);
1271 if (streq(search_dir, "/") || streq(search_dir, ".") || strlen(search_dir) <= 1) {
1272 break;
1273 }
1274#ifdef _WIN32
1275 if (strlen(search_dir) == 3 && search_dir[1] == ':' && (search_dir[2] == '\\' || search_dir[2] == '/')) {
1276 break;
1277 }
1278#endif
1279 {
1280 char *parent = path_dirname(search_dir);
1281 if (parent == NULL || streq(parent, search_dir)) {
1282 free(parent);
1283 break;
1284 }
1285 free(search_dir);
1286 search_dir = parent;
1287 }
1288 }
1289 free(search_dir);
1290 return NULL;
1291}
1292
1293static char *resolve_value_or_default(const char *value, const char *fallback) {
1294 if (value == NULL || value[0] == '\0') {
1295 return pharos_strdup(fallback);
1296 }
1297 return pharos_strdup(value);
1298}
1299
1300static int app_root_has_finalized_artifact_native(const char *app_root) {
1301 char *manifest_path = NULL;
1302 int ready = 0;
1303 if (app_root == NULL || app_root[0] == '\0') {
1304 return 0;
1305 }
1306 manifest_path = path_join(app_root, "pharos.app.json");
1307 if (manifest_path != NULL && path_exists(manifest_path)) {
1308 ready = 1;
1309 }
1310 free(manifest_path);
1311 return ready;
1312}
1313
1314static char *resolve_runtime_socket_path_native(const ServeConfig *config, const char *runtime_conf_path, const char *runtime_conf_dir) {
1315 char *socket_value = NULL;
1316 char *resolved_socket_path = NULL;
1317 if (config != NULL && config->socket_path != NULL) {
1318 return pharos_strdup(config->socket_path);
1319 }
1320 if (runtime_conf_path == NULL) {
1321 return NULL;
1322 }
1323 socket_value = conf_lookup_value(runtime_conf_path, "socket_path");
1324 if (socket_value == NULL) {
1325 return NULL;
1326 }
1327 resolved_socket_path = resolve_relative_to(runtime_conf_dir, socket_value);
1328 free(socket_value);
1329 return resolved_socket_path;
1330}
1331
1332static int emit_text_file_to_stdout_native(const char *file_path) {
1333 char *content = NULL;
1334 if (file_path == NULL) {
1335 return 0;
1336 }
1337 content = read_file_text(file_path);
1338 if (content == NULL) {
1339 return 69;
1340 }
1341 fputs(content, stdout);
1342 free(content);
1343 return 0;
1344}
1345
1346static char *resolve_seed_entrypoint_native(const char *app_root, const char *target_id, const char *build_dir_override) {
1347 char *host_profile = NULL;
1348 char *manifest_path = NULL;
1349 char *manifest_text = NULL;
1350 char *seed_entrypoint = NULL;
1351 if (app_root == NULL || target_id == NULL) {
1352 return NULL;
1353 }
1354 host_profile = app_runtime_host_profile_native(app_root);
1355 /**
1356 * Static-pages apps are now built and served directly from their finalized
1357 * artifacts in the native runtime path and do not require seed-entrypoint
1358 * compatibility hosts because they have no app-owned mutable runtime state.
1359 */
1360 if (host_profile != NULL && streq(host_profile, "static-pages")) {
1361 free(host_profile);
1362 return NULL;
1363 }
1364 free(host_profile);
1365 manifest_path = path_join(app_root, "pharos.app.json");
1366 manifest_text = manifest_path != NULL ? read_file_text(manifest_path) : NULL;
1367 free(manifest_path);
1368 if (manifest_text != NULL) {
1369 seed_entrypoint = json_find_string_in_section(manifest_text, "runtime", "compiled_entrypoint");
1370 free(manifest_text);
1371 }
1372 if (seed_entrypoint == NULL || seed_entrypoint[0] == '\0') {
1373 char buffer[4096];
1374 free(seed_entrypoint);
1375 snprintf(buffer, sizeof(buffer), "%s/runtime/%s_native_host", app_root, target_id);
1376 return pharos_strdup(buffer);
1377 }
1378 if (seed_entrypoint[0] == '/') {
1379 return seed_entrypoint;
1380 }
1381 {
1382 char *resolved_seed = path_join(app_root, seed_entrypoint);
1383 free(seed_entrypoint);
1384 return resolved_seed;
1385 }
1386}
1387
1388static int maybe_seed_app_state_native(const ServeConfig *config, const char *app_root, const char *app_id, int force_seed) {
1389 char *seed_entrypoint;
1390 char *resolved_state;
1391 char *resolved_base;
1392 char *build_dir = NULL;
1393 char *pharos_bin_path = NULL;
1394 char *argv_list[9];
1395 const char *env_keys[2];
1396 const char *env_values[2];
1397 size_t env_count = 0;
1398 char *output = NULL;
1399 int argc = 0;
1400 int code = 0;
1401 if (config == NULL || app_root == NULL || app_id == NULL || app_id[0] == '\0' || (!config->seed_state && !force_seed)) {
1402 return 0;
1403 }
1404 build_dir = runtime_build_dir_override_native(config, app_root);
1405 seed_entrypoint = resolve_seed_entrypoint_native(app_root, app_id, build_dir);
1406 if (seed_entrypoint == NULL || !path_exists(seed_entrypoint)) {
1407 free(build_dir);
1408 free(seed_entrypoint);
1409 return 0;
1410 }
1411 resolved_state = effective_app_state_path_native(
1412 app_root,
1413 build_dir != NULL ? build_dir : "dist",
1414 app_id,
1415 config->state_path,
1416 config->state_dir
1417 );
1418 resolved_base = effective_app_base_path_native(app_root, config->base_path);
1419 argv_list[argc++] = seed_entrypoint;
1420 argv_list[argc++] = "--build-dir";
1421 argv_list[argc++] = build_dir != NULL ? build_dir : "dist";
1422 argv_list[argc++] = "--state-path";
1423 argv_list[argc++] = resolved_state != NULL ? resolved_state : "";
1424 argv_list[argc++] = "--base-path";
1425 argv_list[argc++] = resolved_base != NULL ? resolved_base : "/";
1426 argv_list[argc++] = "--seed-state";
1427 argv_list[argc] = NULL;
1428 if (global_binary_dir != NULL && global_binary_dir[0] != '\0') {
1429 pharos_bin_path = path_join(global_binary_dir, "pharos");
1430 if (pharos_bin_path != NULL && path_exists(pharos_bin_path)) {
1431 env_keys[env_count] = "PHAROS_BIN";
1432 env_values[env_count] = pharos_bin_path;
1433 env_count++;
1434 }
1435 }
1436 if (config->conf_path != NULL && config->conf_path[0] != '\0') {
1437 env_keys[env_count] = "PHAROS_RUNTIME_CONF_PATH";
1438 env_values[env_count] = config->conf_path;
1439 env_count++;
1440 }
1441 code = capture_execv_output_env_native(argv_list, env_keys, env_values, env_count, &output, NULL);
1442 if (code != 0 && output != NULL && output[0] != '\0') {
1443 fprintf(stderr, "%s", output);
1444 }
1445 free(output);
1446 free(pharos_bin_path);
1447 free(build_dir);
1448 free(resolved_state);
1449 free(resolved_base);
1450 free(seed_entrypoint);
1451 return code;
1452}
1453
1455 char *app_root = NULL;
1456 char *artifact_dir_native = NULL;
1457 char *status_output = NULL;
1458 char *artifact_dir;
1459 int code;
1460 if (config != NULL && config->app_dir != NULL && config->app_dir[0] != '\0' && config->target_id != NULL) {
1461 char *direct_manifest = path_join(config->app_dir, "pharos.app.json");
1462 char *direct_manifest_text = direct_manifest != NULL ? read_file_text(direct_manifest) : NULL;
1463 char *direct_app_id = direct_manifest_text != NULL ? json_find_string(direct_manifest_text, "app_id") : NULL;
1464 char *direct_build_artifact_dir = direct_manifest_text != NULL ? json_find_string_in_section(direct_manifest_text, "runtime", "build_artifact_dir") : NULL;
1465 if (direct_app_id != NULL && streq(direct_app_id, config->target_id) && direct_build_artifact_dir != NULL && streq(direct_build_artifact_dir, ".")) {
1466 char *direct_root = path_canonicalize(config->app_dir);
1467 char *direct_build_dir = NULL;
1468 if (direct_root == NULL) {
1469 direct_root = pharos_strdup(config->app_dir);
1470 }
1471 if (direct_root != NULL) {
1472 direct_build_dir = effective_app_build_dir_native(direct_root, config->build_dir);
1473 }
1474 if (direct_root != NULL && direct_build_dir != NULL) {
1475 code = load_app_runtime_from_artifact_native(direct_root, app);
1476 if (code == 0) {
1477 if (finalize_loaded_app_runtime_native(app, config, direct_root, direct_build_dir) == 0) {
1478 free(direct_build_artifact_dir);
1479 free(direct_app_id);
1480 free(direct_manifest_text);
1481 free(direct_manifest);
1482 free(direct_build_dir);
1483 free(direct_root);
1484 return 0;
1485 }
1486 free_app_runtime(app);
1487 }
1488 }
1489 free(direct_build_dir);
1490 free(direct_root);
1491 } else if (direct_app_id != NULL && streq(direct_app_id, config->target_id)) {
1492 app_root = path_canonicalize(config->app_dir);
1493 if (app_root == NULL) {
1494 app_root = pharos_strdup(config->app_dir);
1495 }
1496 }
1497 free(direct_build_artifact_dir);
1498 free(direct_app_id);
1499 free(direct_manifest_text);
1500 free(direct_manifest);
1501 }
1502 if (app_root == NULL) {
1504 }
1505 if (app_root != NULL) {
1506 char *build_dir = effective_app_build_dir_native(app_root, config->build_dir);
1507 AppLocalConfigNative local_config;
1508 int packaged_artifact_mode = 0;
1509 memset(&local_config, 0, sizeof(local_config));
1510 if (build_dir == NULL) {
1511 free(app_root);
1512 return 69;
1513 }
1514 if (load_app_local_config_native(app_root, &local_config) == 0) {
1515 if (local_config.manifest_build_artifact_dir != NULL && streq(local_config.manifest_build_artifact_dir, ".")) {
1516 packaged_artifact_mode = 1;
1517 }
1518 free_app_local_config_native(&local_config);
1519 }
1520 if (packaged_artifact_mode) {
1521 code = load_app_runtime_from_artifact_native(app_root, app);
1522 if (code == 0) {
1523 if (finalize_loaded_app_runtime_native(app, config, app_root, build_dir) != 0) {
1524 free(build_dir);
1525 free(app_root);
1526 return 69;
1527 }
1528 free(build_dir);
1529 free(app_root);
1530 return 0;
1531 }
1532 } else {
1533 if (maybe_seed_app_state_native(config, app_root, config->target_id, 1) != 0) {
1534 free(build_dir);
1535 free(app_root);
1536 return 69;
1537 }
1538 code = prepare_app_artifact_native(app_root, config->target_id, build_dir, config->state_dir, config->state_path, config->base_path, NULL, &artifact_dir_native);
1539 if (code == 0 && artifact_dir_native != NULL) {
1540 code = load_app_runtime_from_artifact_native(artifact_dir_native, app);
1541 free(artifact_dir_native);
1542 artifact_dir_native = NULL;
1543 if (code == 0) {
1544 if (finalize_loaded_app_runtime_native(app, config, app_root, build_dir) != 0) {
1545 free(build_dir);
1546 free(app_root);
1547 return 69;
1548 }
1549 free(build_dir);
1550 free(app_root);
1551 return 0;
1552 }
1553 }
1554 }
1555 free(build_dir);
1556 free(app_root);
1557 }
1558 code = shell_status_or_build_native(global_repo_root, config, 0, &status_output);
1559 if (code != 0) {
1560 fprintf(stderr, "%s", status_output != NULL ? status_output : "failed to build/status Pharos app\n");
1561 free(status_output);
1562 return code;
1563 }
1564 artifact_dir = json_find_string(status_output, "artifact_dir");
1565 free(status_output);
1566 if (artifact_dir == NULL) {
1567 return 66;
1568 }
1569 code = load_app_runtime_from_artifact_native(artifact_dir, app);
1570 free(artifact_dir);
1571 if (code != 0) {
1572 return code;
1573 }
1574 if (finalize_loaded_app_runtime_native(app, config, NULL, NULL) != 0) {
1575 return 69;
1576 }
1577 return 0;
1578}
1579
1580#ifndef _WIN32
1581static int spawn_app_request(const AppRuntime *app, const HttpRequest *request, char **response, size_t *response_len, char **stderr_text, size_t *stderr_len) {
1582 char *temp_body_path = NULL;
1583 char *argv_list[PHAROS_APP_REQUEST_ARGV_CAPACITY];
1584 const char *env_keys[2];
1585 const char *env_values[2];
1586 size_t env_count = 0;
1587 size_t argc = 0;
1588 int code;
1589 if (stderr_text != NULL) {
1590 *stderr_text = NULL;
1591 }
1592 if (stderr_len != NULL) {
1593 *stderr_len = 0;
1594 }
1595 if (request->body_file_path != NULL && request->body_file_path[0] != '\0') {
1596 temp_body_path = pharos_strdup(request->body_file_path);
1597 if (temp_body_path == NULL) {
1598 return 69;
1599 }
1600 } else if (request->body_len > 0) {
1601 if (write_native_temp_body_file(request->body, request->body_len, &temp_body_path) != 0) {
1602 return 69;
1603 }
1604 }
1606 app,
1607 request->method,
1608 request->target,
1609 request->cookie,
1610 request->headers,
1611 request->content_type,
1612 request->body,
1613 request->is_multipart,
1614 temp_body_path,
1615 argv_list,
1617 &argc
1618 ) != 0) {
1619 cleanup_native_temp_body_file(&temp_body_path);
1620 return 69;
1621 }
1622 if (app->runtime_conf_path != NULL && app->runtime_conf_path[0] != '\0') {
1623 env_keys[env_count] = "PHAROS_RUNTIME_CONF_PATH";
1624 env_values[env_count] = app->runtime_conf_path;
1625 env_count++;
1626 }
1627 if (global_binary_dir != NULL && global_binary_dir[0] != '\0') {
1628 static char pharos_bin_path[4096];
1629#ifdef _WIN32
1630 snprintf(pharos_bin_path, sizeof(pharos_bin_path), "%s\\pharos.exe", global_binary_dir);
1631#else
1632 snprintf(pharos_bin_path, sizeof(pharos_bin_path), "%s/pharos", global_binary_dir);
1633#endif
1634 env_keys[env_count] = "PHAROS_BIN";
1635 env_values[env_count] = pharos_bin_path;
1636 env_count++;
1637 }
1638 code = capture_execv_output_split_env_native(argv_list, env_keys, env_values, env_count, response, response_len, stderr_text, stderr_len);
1639 cleanup_native_temp_body_file(&temp_body_path);
1640 return code;
1641}
1642#else
1643static int spawn_app_request(const AppRuntime *app, const HttpRequest *request, char **response, size_t *response_len, char **stderr_text, size_t *stderr_len) {
1644 (void)app;
1645 (void)request;
1646 (void)response;
1647 (void)response_len;
1648 (void)stderr_text;
1649 (void)stderr_len;
1650 return 78;
1651}
1652#endif
1653
1654static char *resolve_website_root(const ServeConfig *config) {
1655 char *native_artifact_dir = NULL;
1656 char *status_output = NULL;
1657 char *artifact_dir = NULL;
1658 int code = shell_status_or_build_native(global_repo_root, config, 0, &status_output);
1659 if (config != NULL && native_known_website_supported(config->target_id)) {
1660 if (build_known_website_native(config->target_id, config->build_dir != NULL ? config->build_dir : "dist", NULL, &native_artifact_dir) == 0) {
1661 return native_artifact_dir;
1662 }
1663 }
1664 if (code != 0) {
1665 fprintf(stderr, "%s", status_output != NULL ? status_output : "failed to build/status Pharos website\n");
1666 free(status_output);
1667 return NULL;
1668 }
1669 artifact_dir = json_find_string(status_output, "artifact_dir");
1670 free(status_output);
1671 return artifact_dir;
1672}
1673
1674static int load_runtime_stack(const ServeConfig *config, RuntimeStack *stack) {
1675 char *runtime_conf_path = NULL;
1676 char *runtime_conf_dir = NULL;
1677 char *apps_dir_value = NULL;
1678 char *build_dir_value = NULL;
1679 char *state_dir_value = NULL;
1680 char *state_path_value = NULL;
1681 char *base_path_value = NULL;
1682 char *resolved_build_dir = NULL;
1683 char *resolved_state_dir = NULL;
1684 char *resolved_state_path = NULL;
1685 ServeConfig effective_config;
1686 memset(stack, 0, sizeof(*stack));
1687 memset(&effective_config, 0, sizeof(effective_config));
1688 if (config != NULL) {
1689 effective_config = *config;
1690 }
1691 if (config->conf_path != NULL) {
1692 runtime_conf_path = resolve_relative_to(current_working_directory(), config->conf_path);
1693 runtime_conf_dir = path_dirname(runtime_conf_path);
1694 apps_dir_value = conf_lookup_value(runtime_conf_path, "apps_dir");
1695 if (config->build_dir == NULL || config->build_dir[0] == '\0') {
1696 build_dir_value = conf_lookup_value(runtime_conf_path, "build_dir");
1697 resolved_build_dir = build_dir_value != NULL ? resolve_relative_to(runtime_conf_dir, build_dir_value) : NULL;
1698 effective_config.build_dir = resolved_build_dir;
1699 }
1700 if (config->state_dir == NULL || config->state_dir[0] == '\0') {
1701 state_dir_value = conf_lookup_value(runtime_conf_path, "state_dir");
1702 resolved_state_dir = state_dir_value != NULL ? resolve_relative_to(runtime_conf_dir, state_dir_value) : NULL;
1703 effective_config.state_dir = resolved_state_dir;
1704 }
1705 if (config->state_path == NULL || config->state_path[0] == '\0') {
1706 state_path_value = conf_lookup_value(runtime_conf_path, "state_path");
1707 resolved_state_path = state_path_value != NULL ? resolve_relative_to(runtime_conf_dir, state_path_value) : NULL;
1708 effective_config.state_path = resolved_state_path;
1709 }
1710 if (config->base_path == NULL || config->base_path[0] == '\0') {
1711 base_path_value = conf_lookup_value(runtime_conf_path, "base_path");
1712 effective_config.base_path = base_path_value;
1713 }
1714 }
1715 {
1716 char *apps_dir = apps_dir_value != NULL ? resolve_relative_to(runtime_conf_dir, apps_dir_value) : current_working_directory();
1717 free(apps_dir_value);
1718 stack->host = runtime_conf_path != NULL ? resolve_value_or_default(config->host, conf_lookup_value(runtime_conf_path, "host")) : resolve_value_or_default(config->host, "127.0.0.1");
1719 stack->port = runtime_conf_path != NULL ? resolve_value_or_default(config->port, conf_lookup_value(runtime_conf_path, "port")) : resolve_value_or_default(config->port, "7323");
1720 stack->socket_path = resolve_runtime_socket_path_native(config, runtime_conf_path, runtime_conf_dir);
1721#ifndef _WIN32
1722 DIR *dir = opendir(apps_dir);
1723 struct dirent *entry;
1724 if (dir == NULL) {
1725 free(apps_dir);
1726 free(runtime_conf_path);
1727 free(runtime_conf_dir);
1728 return 66;
1729 }
1730 {
1731 char *root_manifest = path_join(apps_dir, "pharos.app.json");
1732 char *root_manifest_text = root_manifest != NULL ? read_file_text(root_manifest) : NULL;
1733 char *root_app_id = root_manifest_text != NULL ? json_find_string(root_manifest_text, "app_id") : NULL;
1734 if (root_app_id != NULL && root_app_id[0] != '\0') {
1735 add_runtime_app_native(stack, &effective_config, apps_dir, root_app_id);
1736 }
1737 free(root_manifest);
1738 free(root_manifest_text);
1739 free(root_app_id);
1740 }
1741 while ((entry = readdir(dir)) != NULL && stack->count < MAX_APPS) {
1742 char *app_root;
1743 char *app_manifest_path;
1744 char *app_manifest_text;
1745 char *app_id;
1746 if (streq(entry->d_name, ".") || streq(entry->d_name, "..")) {
1747 continue;
1748 }
1749 app_root = path_join(apps_dir, entry->d_name);
1750 if (!path_is_directory(app_root)) {
1751 free(app_root);
1752 continue;
1753 }
1754 app_manifest_path = path_join(app_root, "pharos.app.json");
1755 if (!path_exists(app_manifest_path)) {
1756 free(app_root);
1757 free(app_manifest_path);
1758 continue;
1759 }
1760 app_manifest_text = read_file_text(app_manifest_path);
1761 free(app_manifest_path);
1762 if (app_manifest_text == NULL) {
1763 free(app_root);
1764 continue;
1765 }
1766 app_id = json_find_string(app_manifest_text, "app_id");
1767 free(app_manifest_text);
1768 if (app_id == NULL) {
1769 free(app_root);
1770 continue;
1771 }
1772 add_runtime_app_native(stack, &effective_config, app_root, app_id);
1773 free(app_root);
1774 free(app_id);
1775 }
1776 closedir(dir);
1777#endif
1778 free(apps_dir);
1779 }
1780 free(runtime_conf_path);
1781 free(runtime_conf_dir);
1782 free(build_dir_value);
1783 free(state_dir_value);
1784 free(state_path_value);
1785 free(base_path_value);
1786 free(resolved_build_dir);
1787 free(resolved_state_dir);
1788 free(resolved_state_path);
1789 if (stack->count == 0) {
1790 return 66;
1791 }
1792 return 0;
1793}
1794
1795
1796
1798 char *response = NULL;
1799 char *stderr_text = NULL;
1800 char *static_target = NULL;
1801 char *request_target = NULL;
1802 char *log_target = NULL;
1803 char *response_status = NULL;
1804 size_t response_len = 0;
1805 size_t stderr_len = 0;
1806 if (streq(context->app.host_profile != NULL ? context->app.host_profile : "", "static-pages")) {
1807 if (!streq(context->request.method, "GET") && !streq(context->request.method, "HEAD")) {
1808 native_send_text_response(&context->connection, 405, "Method Not Allowed", "method not allowed\n");
1809 } else {
1810 static_target = native_request_target_for_mount(context->app.base_path, context->request.target);
1811 if (static_target == NULL) {
1812 native_send_text_response(&context->connection, 500, "Internal Server Error", "memory allocation failed\n");
1813 } else {
1814 native_serve_static_file(&context->connection, context->app.artifact_dir, static_target, &context->request, context->app.log_path);
1815 }
1816 }
1817 } else if (streq(context->app.host_profile != NULL ? context->app.host_profile : "", "dynamic-app")) {
1818 HttpRequest dynamic_request = context->request;
1819 request_target = native_request_target_for_mount(context->app.base_path, context->request.target);
1820 log_target = request_target != NULL ? pharos_strdup(request_target) : NULL;
1821 if (request_target != NULL) {
1822 dynamic_request.target = request_target;
1823 }
1824 int dyn_status = native_dynamic_runtime_handle_request(&context->app, &dynamic_request, &context->connection);
1825 if (dyn_status >= 200 && dyn_status < 400) {
1827 context->app.log_path,
1828 &context->request,
1829 log_target != NULL ? log_target : context->request.target,
1830 dyn_status
1831 );
1832 } else {
1833 char dyn_detail[128];
1834 snprintf(dyn_detail, sizeof(dyn_detail), "native_dynamic_status=%d", dyn_status);
1836 context->app.log_path,
1837 context->app.error_report_path,
1838 context->app.app_id,
1839 &context->request,
1840 log_target != NULL ? log_target : context->request.target,
1841 dyn_status >= 200 && dyn_status < 300 ? "200 OK" : (dyn_status == 404 ? "404 Not Found" : "500 Internal Server Error"),
1842 "native_dynamic_runtime",
1843 log_target != NULL ? log_target : context->request.target,
1844 dyn_detail
1845 );
1846 }
1847 } else {
1848 HttpRequest spawned_request = context->request;
1849 request_target = native_request_target_for_mount(context->app.base_path, context->request.target);
1850 log_target = request_target != NULL ? pharos_strdup(request_target) : NULL;
1851 if (request_target != NULL) {
1852 spawned_request.target = request_target;
1853 }
1854 int code = spawn_app_request(&context->app, &spawned_request, &response, &response_len, &stderr_text, &stderr_len);
1855 if (code != 0 || response == NULL || response_len == 0) {
1856 char exit_detail[512];
1857 if (response != NULL && response_len > 0) {
1858 snprintf(
1859 exit_detail,
1860 sizeof(exit_detail),
1861 "host_exit_code=%d output_len=%zu entrypoint=%s",
1862 (int)code,
1863 response_len,
1864 context->app.compiled_entrypoint != NULL ? context->app.compiled_entrypoint : ""
1865 );
1866 } else {
1867 snprintf(
1868 exit_detail,
1869 sizeof(exit_detail),
1870 "host_exit_code=%d no_output entrypoint=%s",
1871 (int)code,
1872 context->app.compiled_entrypoint != NULL ? context->app.compiled_entrypoint : ""
1873 );
1874 }
1875 native_send_text_response(&context->connection, 502, "Bad Gateway", "Pharos compiled host request failed\n");
1877 context->app.log_path,
1878 context->app.error_report_path,
1879 context->app.app_id,
1880 &context->request,
1881 log_target != NULL ? log_target : context->request.target,
1882 "502 Bad Gateway",
1883 "app_host_exit",
1884 log_target != NULL ? log_target : context->request.target,
1885 exit_detail
1886 );
1887 if (stderr_text != NULL && stderr_len > 0) {
1889 context->app.log_path,
1890 context->app.error_report_path,
1891 context->app.app_id,
1892 &context->request,
1893 log_target != NULL ? log_target : context->request.target,
1894 "502 Bad Gateway",
1895 "app_host_stderr",
1896 log_target != NULL ? log_target : context->request.target,
1897 stderr_text
1898 );
1899 }
1900 } else {
1901 native_connection_write(&context->connection, response, response_len);
1902 response_status = http_status_line_from_response_native(response);
1903 if (stderr_text != NULL && stderr_len > 0) {
1905 context->app.log_path,
1906 context->app.error_report_path,
1907 context->app.app_id,
1908 &context->request,
1909 log_target != NULL ? log_target : context->request.target,
1910 response_status != NULL ? response_status : "200 OK",
1911 "app_host_stderr",
1912 log_target != NULL ? log_target : context->request.target,
1913 stderr_text
1914 );
1915 }
1916 if (response_status != NULL) {
1917 int response_code = http_status_code_from_response_native(response);
1918 if (response_code >= 200 && response_code < 400) {
1920 context->app.log_path,
1921 &context->request,
1922 log_target != NULL ? log_target : context->request.target,
1923 response_code
1924 );
1925 } else {
1927 context->app.log_path,
1928 context->app.error_report_path,
1929 context->app.app_id,
1930 &context->request,
1931 log_target != NULL ? log_target : context->request.target,
1932 response_status,
1933 "http_response",
1934 log_target != NULL ? log_target : context->request.target,
1935 NULL
1936 );
1937 }
1938 }
1939 }
1940 }
1941 free(response_status);
1942 free(log_target);
1943 free(request_target);
1944 free(static_target);
1945 free(response);
1946 free(stderr_text);
1947}
1948
1949/**
1950 * @brief Release a worker context and every owned request/runtime resource.
1951 *
1952 * @param context Per-request worker state to release.
1953 */
1955 if (context == NULL) {
1956 return;
1957 }
1959 free_app_runtime(&context->app);
1961 free(context);
1962}
1963
1964/**
1965 * @brief Clamp one size value into an inclusive range.
1966 *
1967 * @param value Candidate value.
1968 * @param minimum Inclusive lower bound.
1969 * @param maximum Inclusive upper bound.
1970 * @return Clamped size value.
1971 */
1972static size_t native_clamp_size_value(size_t value, size_t minimum, size_t maximum) {
1973 if (value < minimum) {
1974 return minimum;
1975 }
1976 if (value > maximum) {
1977 return maximum;
1978 }
1979 return value;
1980}
1981
1982/**
1983 * @brief Parse one positive size_t from config text.
1984 *
1985 * @param text Raw config value text.
1986 * @param value_out Parsed positive value on success.
1987 * @return 1 on success, 0 on invalid or missing input.
1988 */
1989static int native_parse_positive_size_value(const char *text, size_t *value_out) {
1990 char *end = NULL;
1991 unsigned long long parsed;
1992 if (text == NULL || text[0] == '\0' || value_out == NULL) {
1993 return 0;
1994 }
1995 errno = 0;
1996 parsed = strtoull(text, &end, 10);
1997 if (errno != 0 || end == text || end == NULL || *end != '\0' || parsed == 0ULL) {
1998 return 0;
1999 }
2000 if (parsed > (unsigned long long)SIZE_MAX) {
2001 return 0;
2002 }
2003 *value_out = (size_t)parsed;
2004 return 1;
2005}
2006
2007/**
2008 * @brief Read the configured positive size value for one runtime key.
2009 *
2010 * @param runtime_conf_path Runtime configuration path.
2011 * @param key Config key to read.
2012 * @param value_out Parsed positive value on success.
2013 * @return 1 on success, 0 when missing or invalid.
2014 */
2015static int native_conf_positive_size_value(const char *runtime_conf_path, const char *key, size_t *value_out) {
2016 char *raw_value = NULL;
2017 int parsed_ok = 0;
2018 if (runtime_conf_path == NULL || runtime_conf_path[0] == '\0' || key == NULL || key[0] == '\0') {
2019 return 0;
2020 }
2021 raw_value = conf_lookup_value(runtime_conf_path, key);
2022 parsed_ok = native_parse_positive_size_value(raw_value, value_out);
2023 free(raw_value);
2024 return parsed_ok;
2025}
2026
2027/**
2028 * @brief Detect total physical memory visible to the current host.
2029 *
2030 * @return Physical memory bytes, or 0 when unavailable.
2031 */
2032static unsigned long long native_detect_physical_memory_bytes(void) {
2033#ifdef _WIN32
2034 MEMORYSTATUSEX statex;
2035 memset(&statex, 0, sizeof(statex));
2036 statex.dwLength = sizeof(statex);
2037 if (GlobalMemoryStatusEx(&statex)) {
2038 return (unsigned long long)statex.ullTotalPhys;
2039 }
2040 return 0ULL;
2041#else
2042#ifdef _SC_PHYS_PAGES
2043#ifdef _SC_PAGESIZE
2044 {
2045 long pages = sysconf(_SC_PHYS_PAGES);
2046 long page_size = sysconf(_SC_PAGESIZE);
2047 if (pages > 0L && page_size > 0L) {
2048 return (unsigned long long)pages * (unsigned long long)page_size;
2049 }
2050 }
2051#endif
2052#endif
2053 return 0ULL;
2054#endif
2055}
2056
2057/**
2058 * @brief Resolve the default worker-thread count when config does not override it.
2059 *
2060 * @return Positive worker-thread count.
2061 */
2062static size_t native_default_worker_count(void) {
2063#ifdef _WIN32
2064 SYSTEM_INFO system_info;
2065 GetSystemInfo(&system_info);
2066 if (system_info.dwNumberOfProcessors > 0U) {
2067 return native_clamp_size_value((size_t)system_info.dwNumberOfProcessors, PHAROS_NATIVE_MIN_WORKER_COUNT, PHAROS_NATIVE_MAX_WORKER_COUNT);
2068 }
2069#else
2070#ifdef _SC_NPROCESSORS_ONLN
2071 {
2072 long cpu_count = sysconf(_SC_NPROCESSORS_ONLN);
2073 if (cpu_count > 0L) {
2075 }
2076 }
2077#endif
2078#endif
2080}
2081
2082/**
2083 * @brief Resolve the default queue capacity from physical memory.
2084 *
2085 * @return Positive queued-request capacity.
2086 */
2088 unsigned long long memory_bytes = native_detect_physical_memory_bytes();
2089 if (memory_bytes == 0ULL) {
2091 }
2092 {
2093 unsigned long long slots = memory_bytes / PHAROS_NATIVE_WORK_QUEUE_BYTES_PER_SLOT;
2094 size_t bounded_slots;
2095 if (slots == 0ULL) {
2097 }
2098 if (slots > (unsigned long long)SIZE_MAX) {
2100 } else {
2101 bounded_slots = (size_t)slots;
2102 }
2104 bounded_slots,
2107 );
2108 }
2109}
2110
2111/**
2112 * @brief Resolve the fixed worker-thread count for one serve process.
2113 *
2114 * @param runtime_conf_path Runtime configuration path to consult for overrides.
2115 * @return Positive worker-thread count.
2116 */
2117static size_t native_resolved_worker_count(const char *runtime_conf_path) {
2118 size_t configured = 0;
2119 if (native_conf_positive_size_value(runtime_conf_path, "worker_count", &configured)) {
2121 }
2123}
2124
2125/**
2126 * @brief Resolve the bounded queued-request capacity for one serve process.
2127 *
2128 * @param runtime_conf_path Runtime configuration path to consult for overrides.
2129 * @return Positive queued-request capacity.
2130 */
2131static size_t native_resolved_work_queue_capacity(const char *runtime_conf_path) {
2132 size_t configured = 0;
2133 if (native_conf_positive_size_value(runtime_conf_path, "worker_queue_capacity", &configured)) {
2135 configured,
2138 );
2139 }
2141}
2142
2143/**
2144 * @brief Execute one accepted request through the active runtime/app handler.
2145 *
2146 * @param context Per-request worker state to process.
2147 */
2149 if (context == NULL) {
2150 return;
2151 }
2154}
2155
2156/**
2157 * @brief Send the standard overload response for saturated native serving.
2158 *
2159 * @param connection Accepted client connection.
2160 */
2162 native_send_text_response(connection, 503, "Service Unavailable", "native worker pool saturated\n");
2163}
2164
2165#ifdef _WIN32
2166/**
2167 * @brief Run queued request work items until the worker pool shuts down.
2168 *
2169 * @param opaque Active worker pool.
2170 * @return Windows thread exit code.
2171 */
2172static unsigned __stdcall native_worker_pool_thread_main(void *opaque) {
2173 NativeWorkerPool *pool = (NativeWorkerPool *)opaque;
2174 for (;;) {
2175 WorkerContext *context = NULL;
2176 EnterCriticalSection(&pool->mutex);
2177 while (pool->count == 0 && !pool->shutting_down) {
2178 SleepConditionVariableCS(&pool->not_empty, &pool->mutex, INFINITE);
2179 }
2180 if (pool->count > 0) {
2181 context = pool->items[pool->head];
2182 pool->items[pool->head] = NULL;
2183 pool->head = (pool->head + 1U) % pool->capacity;
2184 pool->count--;
2185 } else if (pool->shutting_down) {
2186 LeaveCriticalSection(&pool->mutex);
2187 break;
2188 }
2189 LeaveCriticalSection(&pool->mutex);
2191 }
2192 return 0;
2193}
2194#else
2195/**
2196 * @brief Run queued request work items until the worker pool shuts down.
2197 *
2198 * @param opaque Active worker pool.
2199 * @return Unused thread return pointer.
2200 */
2201static void *native_worker_pool_thread_main(void *opaque) {
2202 NativeWorkerPool *pool = (NativeWorkerPool *)opaque;
2203 for (;;) {
2204 WorkerContext *context = NULL;
2205 pthread_mutex_lock(&pool->mutex);
2206 while (pool->count == 0 && !pool->shutting_down) {
2207 pthread_cond_wait(&pool->not_empty, &pool->mutex);
2208 }
2209 if (pool->count > 0) {
2210 context = pool->items[pool->head];
2211 pool->items[pool->head] = NULL;
2212 pool->head = (pool->head + 1U) % pool->capacity;
2213 pool->count--;
2214 } else if (pool->shutting_down) {
2215 pthread_mutex_unlock(&pool->mutex);
2216 break;
2217 }
2218 pthread_mutex_unlock(&pool->mutex);
2220 }
2221 return NULL;
2222}
2223#endif
2224
2225/**
2226 * @brief Initialize a bounded worker pool for native request serving.
2227 *
2228 * @param pool Pool state to initialize.
2229 * @param worker_count Fixed number of worker threads to create.
2230 * @param capacity Maximum queued requests.
2231 * @return 0 on success, non-zero on failure.
2232 */
2233static int native_worker_pool_init(NativeWorkerPool *pool, size_t worker_count, size_t capacity) {
2234 size_t index;
2235 if (pool == NULL || worker_count == 0 || capacity == 0) {
2236 return 69;
2237 }
2238 memset(pool, 0, sizeof(*pool));
2239 pool->items = (WorkerContext **)calloc(capacity, sizeof(WorkerContext *));
2240 if (pool->items == NULL) {
2241 return 69;
2242 }
2243 pool->capacity = capacity;
2244 pool->worker_count = worker_count;
2245#ifdef _WIN32
2246 pool->threads = (HANDLE *)calloc(worker_count, sizeof(HANDLE));
2247 if (pool->threads == NULL) {
2248 free(pool->items);
2249 memset(pool, 0, sizeof(*pool));
2250 return 69;
2251 }
2252 InitializeCriticalSection(&pool->mutex);
2253 InitializeConditionVariable(&pool->not_empty);
2254 for (index = 0; index < worker_count; index++) {
2255 uintptr_t thread_handle = _beginthreadex(NULL, 0, native_worker_pool_thread_main, pool, 0, NULL);
2256 if (thread_handle == 0) {
2257 pool->worker_count = index;
2261 return 69;
2262 }
2263 pool->threads[index] = (HANDLE)thread_handle;
2264 }
2265#else
2266 pool->threads = (pthread_t *)calloc(worker_count, sizeof(pthread_t));
2267 if (pool->threads == NULL) {
2268 free(pool->items);
2269 memset(pool, 0, sizeof(*pool));
2270 return 69;
2271 }
2272 if (pthread_mutex_init(&pool->mutex, NULL) != 0) {
2273 free(pool->threads);
2274 free(pool->items);
2275 memset(pool, 0, sizeof(*pool));
2276 return 69;
2277 }
2278 if (pthread_cond_init(&pool->not_empty, NULL) != 0) {
2279 pthread_mutex_destroy(&pool->mutex);
2280 free(pool->threads);
2281 free(pool->items);
2282 memset(pool, 0, sizeof(*pool));
2283 return 69;
2284 }
2285 for (index = 0; index < worker_count; index++) {
2286 if (pthread_create(&pool->threads[index], NULL, native_worker_pool_thread_main, pool) != 0) {
2287 pool->worker_count = index;
2291 return 69;
2292 }
2293 }
2294#endif
2295 return 0;
2296}
2297
2298/**
2299 * @brief Submit accepted request work to the bounded worker pool.
2300 *
2301 * @param pool Active worker pool.
2302 * @param context Per-request work item to enqueue.
2303 * @return 0 on success, 1 when saturated or shutting down, non-zero on internal failure.
2304 */
2306 int result = 0;
2307 if (pool == NULL || context == NULL) {
2308 return 69;
2309 }
2310#ifdef _WIN32
2311 EnterCriticalSection(&pool->mutex);
2312 if (pool->shutting_down || pool->count >= pool->capacity) {
2313 result = 1;
2314 } else {
2315 pool->items[pool->tail] = context;
2316 pool->tail = (pool->tail + 1U) % pool->capacity;
2317 pool->count++;
2318 WakeConditionVariable(&pool->not_empty);
2319 }
2320 LeaveCriticalSection(&pool->mutex);
2321#else
2322 if (pthread_mutex_lock(&pool->mutex) != 0) {
2323 return 69;
2324 }
2325 if (pool->shutting_down || pool->count >= pool->capacity) {
2326 result = 1;
2327 } else {
2328 pool->items[pool->tail] = context;
2329 pool->tail = (pool->tail + 1U) % pool->capacity;
2330 pool->count++;
2331 pthread_cond_signal(&pool->not_empty);
2332 }
2333 pthread_mutex_unlock(&pool->mutex);
2334#endif
2335 return result;
2336}
2337
2338/**
2339 * @brief Ask the worker pool to stop after draining queued work.
2340 *
2341 * @param pool Active worker pool.
2342 */
2344 if (pool == NULL) {
2345 return;
2346 }
2347#ifdef _WIN32
2348 EnterCriticalSection(&pool->mutex);
2349 pool->shutting_down = 1;
2350 WakeAllConditionVariable(&pool->not_empty);
2351 LeaveCriticalSection(&pool->mutex);
2352#else
2353 if (pthread_mutex_lock(&pool->mutex) != 0) {
2354 return;
2355 }
2356 pool->shutting_down = 1;
2357 pthread_cond_broadcast(&pool->not_empty);
2358 pthread_mutex_unlock(&pool->mutex);
2359#endif
2360}
2361
2362/**
2363 * @brief Join all worker threads created for the pool.
2364 *
2365 * @param pool Active worker pool.
2366 */
2368 size_t index;
2369 if (pool == NULL || pool->threads == NULL) {
2370 return;
2371 }
2372#ifdef _WIN32
2373 for (index = 0; index < pool->worker_count; index++) {
2374 if (pool->threads[index] != NULL) {
2375 WaitForSingleObject(pool->threads[index], INFINITE);
2376 CloseHandle(pool->threads[index]);
2377 pool->threads[index] = NULL;
2378 }
2379 }
2380#else
2381 for (index = 0; index < pool->worker_count; index++) {
2382 pthread_join(pool->threads[index], NULL);
2383 }
2384#endif
2385}
2386
2387/**
2388 * @brief Destroy worker-pool resources after shutdown and join.
2389 *
2390 * @param pool Worker pool to destroy.
2391 */
2393 size_t index;
2394 if (pool == NULL) {
2395 return;
2396 }
2397 if (pool->items != NULL) {
2398 for (index = 0; index < pool->count; index++) {
2399 size_t item_index = (pool->head + index) % pool->capacity;
2400 free_worker_context_native(pool->items[item_index]);
2401 }
2402 }
2403#ifdef _WIN32
2404 DeleteCriticalSection(&pool->mutex);
2405#else
2406 pthread_cond_destroy(&pool->not_empty);
2407 pthread_mutex_destroy(&pool->mutex);
2408#endif
2409 free(pool->threads);
2410 free(pool->items);
2411 memset(pool, 0, sizeof(*pool));
2412}
2413
2414static int write_noindex_markers_recursive_native(const char *root_path) {
2415#ifdef __APPLE__
2416 DIR *dir = NULL;
2417 struct dirent *entry;
2418 char *marker_path = NULL;
2419 int code = 0;
2420 if (root_path == NULL || root_path[0] == '\0' || !path_is_directory(root_path)) {
2421 return 66;
2422 }
2423 marker_path = path_join(root_path, ".metadata_never_index");
2424 if (marker_path == NULL) {
2425 return 69;
2426 }
2427 code = write_text_file(marker_path, "");
2428 free(marker_path);
2429 if (code != 0) {
2430 return code;
2431 }
2432 dir = opendir(root_path);
2433 if (dir == NULL) {
2434 return 69;
2435 }
2436 while ((entry = readdir(dir)) != NULL) {
2437 char *child_path;
2438 if (streq(entry->d_name, ".") || streq(entry->d_name, "..")) {
2439 continue;
2440 }
2441 child_path = path_join(root_path, entry->d_name);
2442 if (child_path == NULL) {
2443 code = 69;
2444 break;
2445 }
2446 if (path_is_directory(child_path)) {
2447 code = write_noindex_markers_recursive_native(child_path);
2448 }
2449 free(child_path);
2450 if (code != 0) {
2451 break;
2452 }
2453 }
2454 closedir(dir);
2455 return code;
2456#else
2457 (void)root_path;
2458 return 78;
2459#endif
2460}
2461
2462static void print_version(void) {
2463 printf("pharos %s %s\n", PHAROS_VERSION, PHAROS_TARGET_ID);
2464}
2465
2466static void print_help(void) {
2467 printf("pharos runtime\n\n");
2468 printf("command families:\n");
2469 printf(" targets bootstrap host target native json barcode\n");
2470 printf(" migrate db jobs integrations backup restore deploy drift\n");
2471 printf(" api admin build verify status run serve probe smoke doctor maintenance noindex\n\n");
2472 printf("usage:\n");
2473 printf(" pharos --help\n");
2474 printf(" pharos --version\n");
2475 printf(" pharos targets [text|json]\n");
2476 printf(" pharos bootstrap build [out-dir]\n");
2477 printf(" pharos bootstrap install [install-dir]\n");
2478 printf(" pharos host detect\n");
2479 printf(" pharos host build [source-bootstrap|native] [out-root]\n");
2480 printf(" pharos target build-all [source-bootstrap|native] [out-root]\n");
2481 printf(" pharos target build <target-id> [source-bootstrap|native] [out-root]\n");
2482 printf(" pharos native build-target <target-id> [out-root]\n");
2483 printf(" pharos native build-all [out-root]\n");
2484 printf(" pharos native verify-target <target-id> [out-root]\n");
2485 printf(" pharos native verify-all [out-root]\n");
2486 printf(" pharos native smoke-target <target-id> [out-root]\n");
2487 printf(" pharos native smoke-server <target-id> [out-root] [port] [site-root]\n");
2488 printf(" pharos native package-release [out-root]\n");
2489 printf(" pharos native verify-release-bundle [out-root]\n");
2490 printf(" pharos native toolchain\n");
2491 printf(" pharos json bundle manifest-slice-order --manifest PATH\n");
2492 printf(" pharos json bundle manifest-layer-for-slice --manifest PATH --slice NAME\n");
2493 printf(" pharos barcode decode IMAGE_PATH\n");
2494 printf(" pharos noindex PATH\n");
2495 printf(" pharos migrate <plan|status|seed|verify|apply|rollback> app <target-id> [--conf PATH] [--app-dir PATH] [--build-dir PATH] [--backend sqlite|postgresql] [--text]\n");
2496 printf(" pharos db seed app <target-id> [--conf PATH] [--app-dir PATH] [--build-dir PATH] [--backend sqlite|postgresql] [--text]\n");
2497 printf(" pharos jobs <inspect|run|replay> app <target-id> [--conf PATH] [--app-dir PATH] [--build-dir PATH] [--backend sqlite|postgresql] [--case CASE_ID] [--workflow WORKFLOW_ID] [--transition TRANSITION_ID] [--subject-id SUBJECT_ID] [--resource-id RESOURCE_ID] [--from-state STATE] [--idempotency-key KEY] [--trace-id TRACE_ID] [--execution-id ID] [--max-jobs N] [--retry-failed] [--requeue-stuck] [--text]\n");
2498 printf(" pharos integrations <inspect|deliver> app <target-id> [--conf PATH] [--app-dir PATH] [--build-dir PATH] [--state-dir PATH|--state-path PATH] [--adapter-id ADAPTER_ID] [--delivery-id ID] [--event-kind KIND] [--resource-id RESOURCE_ID] [--tenant-id TENANT_ID] [--subject-id SUBJECT_ID] [--payload-json JSON] [--retry-failed] [--max-deliveries N] [--text]\n");
2499 printf(" pharos backup app <target-id> [--conf PATH] [--build-dir PATH] [--backend sqlite|postgresql] [--output-dir PATH] [--text]\n");
2500 printf(" pharos restore app <target-id> [--conf PATH] [--build-dir PATH] [--backend sqlite|postgresql] --input-dir PATH [--text]\n");
2501 printf(" pharos deploy check app <target-id> [--conf PATH] [--build-dir PATH] [--backend sqlite|postgresql] [--text]\n");
2502 printf(" pharos deploy package deb --binary PATH --target-id TARGET_ID [--output-dir PATH] [--text]\n");
2503 printf(" pharos drift check app <target-id> [--conf PATH] [--build-dir PATH] [--backend sqlite|postgresql] [--text]\n");
2504 printf(" pharos api <generate|schema> app <target-id> [--build-dir PATH] [--surface SURFACE_ID] [--text]\n");
2505 printf(" pharos admin generate app <target-id> [--build-dir PATH] [--surface SURFACE_ID] [--text]\n");
2506 printf(" pharos build runtime <runtime-id> --conf PATH [--text]\n");
2507 printf(" pharos status runtime <runtime-id> --conf PATH [--text]\n");
2508 printf(" pharos serve runtime <runtime-id> --conf PATH [--socket-path PATH]\n");
2509 printf(" pharos build <website|app> <target-id> [--conf PATH] [--app-dir PATH] [--build-dir PATH] [--state-dir PATH|--state-path PATH] [--base-path PATH] [--log-path PATH] [--error-report-path PATH] [--debug BOOL] [--text]\n");
2510 printf(" pharos verify <website|app> <target-id> [--conf PATH] [--app-dir PATH] [--build-dir PATH] [--state-dir PATH|--state-path PATH] [--base-path PATH] [--log-path PATH] [--error-report-path PATH] [--debug BOOL] [--text]\n");
2511 printf(" pharos status <website|app> <target-id> [--conf PATH] [--app-dir PATH] [--build-dir PATH] [--state-dir PATH|--state-path PATH] [--base-path PATH] [--log-path PATH] [--error-report-path PATH] [--debug BOOL] [--text]\n");
2512 printf(" pharos run <website|app> <target-id> [--conf PATH] [--app-dir PATH] [--build-dir PATH] [--state-dir PATH|--state-path PATH] [--base-path PATH] [--log-path PATH] [--error-report-path PATH] [--debug BOOL] [--text]\n");
2513 printf(" pharos serve <website|app> <target-id> [--conf PATH] [--app-dir PATH] [--build-dir PATH] [--state-dir PATH|--state-path PATH] [--base-path PATH] [--log-path PATH] [--error-report-path PATH] [--debug BOOL] [--socket-path PATH] [--host HOST] [--port PORT]\n");
2514 printf(" pharos probe app <target-id> [--conf PATH] [--app-dir PATH] [--build-dir PATH] [--text]\n");
2515 printf(" pharos smoke app <target-id> [--conf PATH] [--app-dir PATH] [--build-dir PATH] [--text]\n");
2516 printf(" pharos doctor app <target-id> [--conf PATH] [--app-dir PATH] [--build-dir PATH] [--text]\n");
2517 printf(" pharos maintenance app <target-id> <action> [--conf PATH] [--app-dir PATH] [--build-dir PATH] [--state-dir PATH|--state-path PATH] [--base-path PATH] [--text]\n");
2518}
2519
2520
2521
2522static const char *native_http_parse_reason_phrase(int status_code) {
2523 switch (status_code) {
2524 case 400:
2525 return "Bad Request";
2526 case 413:
2527 return "Payload Too Large";
2528 case 414:
2529 return "URI Too Long";
2530 case 431:
2531 return "Request Header Fields Too Large";
2532 case 501:
2533 return "Not Implemented";
2534 default:
2535 return "Bad Request";
2536 }
2537}
2538
2539static const char *native_http_parse_error_body(int status_code) {
2540 switch (status_code) {
2541 case 413:
2542 return "request body exceeds native runtime limits\n";
2543 case 414:
2544 return "request target exceeds native runtime limits\n";
2545 case 431:
2546 return "request headers exceed native runtime limits\n";
2547 case 501:
2548 return "transfer encoding not supported\n";
2549 case 400:
2550 default:
2551 return "malformed request\n";
2552 }
2553}
2554
2555static int accept_http_request_native(int server_fd, NativeConnection *connection, HttpRequest *request) {
2556 int accept_status = native_connection_accept_next(server_fd, &global_shutdown_requested, connection);
2557 int parse_status;
2558 if (accept_status == 0) {
2559 return 0;
2560 }
2561 if (accept_status < 0) {
2562 return -1;
2563 }
2564 parse_status = parse_http_request_native(connection, request);
2565 if (parse_status != 0) {
2566 if (parse_status >= 400 && parse_status < 600) {
2568 connection,
2569 parse_status,
2570 native_http_parse_reason_phrase(parse_status),
2571 native_http_parse_error_body(parse_status)
2572 );
2573 }
2574 native_connection_close(connection);
2575 return -1;
2576 }
2577 return 1;
2578}
2579
2580static WorkerContext *build_worker_context_native(NativeConnection *connection, HttpRequest *request, const AppRuntime *source_app) {
2581 WorkerContext *worker = (WorkerContext *)calloc(1, sizeof(WorkerContext));
2582 if (worker == NULL) {
2583 return NULL;
2584 }
2586 worker->connection = *connection;
2587 native_connection_init(connection);
2588 worker->request = *request;
2589 memset(request, 0, sizeof(*request));
2590 if (clone_app_runtime_native(source_app, &worker->app) != 0) {
2592 free(worker);
2593 return NULL;
2594 }
2595 return worker;
2596}
2597
2598static int handle_static_root_builtin_request(NativeConnection *connection, const HttpRequest *request) {
2599 char body[128];
2600 if (strcmp(request->target, "/health") == 0) {
2601 native_send_text_response(connection, 200, "OK", "server_status=green\n");
2602 return 1;
2603 }
2604 if (strcmp(request->target, "/version") != 0) {
2605 return 0;
2606 }
2607 snprintf(body, sizeof(body), "pharos %s %s\n", PHAROS_VERSION, PHAROS_TARGET_ID);
2608 native_send_text_response(connection, 200, "OK", body);
2609 return 1;
2610}
2611
2612static int serve_static_root(const ServeConfig *config) {
2613 int code = 0;
2614 int server_fd = native_listener_open(config);
2615 if (server_fd < 0) {
2616 fprintf(stderr, "failed to open listener\n");
2617 return 69;
2618 }
2619 register_listener_cleanup(config, (PHAROS_SOCKET)server_fd);
2620 printf("pharos native server listening on %s root=%s\n", native_listener_endpoint(config), config->root);
2621 fflush(stdout);
2622 for (;;) {
2623 NativeConnection connection;
2624 HttpRequest request;
2625 native_connection_init(&connection);
2626 int request_status = accept_http_request_native(server_fd, &connection, &request);
2627 if (request_status == 0) {
2628 break;
2629 }
2630 if (request_status < 0) {
2631 continue;
2632 }
2633 if (strcmp(request.method, "GET") != 0 && strcmp(request.method, "HEAD") != 0 && !(strcmp(request.method, "POST") == 0 && starts_with(request.target, "/api/projects/"))) {
2634 native_send_text_response(&connection, 405, "Method Not Allowed", "method not allowed\n");
2635 free_http_request_native(&request);
2636 native_connection_close(&connection);
2637 continue;
2638 }
2639 if (handle_static_root_builtin_request(&connection, &request)) {
2640 free_http_request_native(&request);
2641 native_connection_close(&connection);
2642 continue;
2643 }
2644 native_serve_static_file(&connection, config->root, request.target, &request, config->log_path);
2645 free_http_request_native(&request);
2646 native_connection_close(&connection);
2647 }
2650 return code;
2651}
2652
2653static int serve_single_app(const ServeConfig *config) {
2654 AppRuntime app;
2655 NativeWorkerPool pool;
2656 size_t worker_count;
2657 size_t queue_capacity;
2658 int code = 0;
2659 int server_fd;
2660 memset(&pool, 0, sizeof(pool));
2661 if (load_app_runtime_for_single_app(config, &app) != 0) {
2662 fprintf(stderr, "failed to load Pharos app runtime for %s\n", config->target_id);
2663 return 66;
2664 }
2665 worker_count = native_resolved_worker_count(app.runtime_conf_path != NULL ? app.runtime_conf_path : config->conf_path);
2666 queue_capacity = native_resolved_work_queue_capacity(app.runtime_conf_path != NULL ? app.runtime_conf_path : config->conf_path);
2667 if (native_worker_pool_init(&pool, worker_count, queue_capacity) != 0) {
2668 free_app_runtime(&app);
2669 fprintf(stderr, "failed to initialize native worker pool\n");
2670 return 69;
2671 }
2672 server_fd = native_listener_open(config);
2673 if (server_fd < 0) {
2677 free_app_runtime(&app);
2678 fprintf(stderr, "failed to open listener\n");
2679 return 69;
2680 }
2681 register_listener_cleanup(config, (PHAROS_SOCKET)server_fd);
2682 printf(
2683 "pharos native app runtime listening on %s target=%s worker_pool=true workers=%zu queue_capacity=%zu\n",
2685 app.app_id,
2686 worker_count,
2687 queue_capacity
2688 );
2689 fflush(stdout);
2690 for (;;) {
2691 NativeConnection connection;
2692 HttpRequest request;
2693 WorkerContext *worker;
2694 native_connection_init(&connection);
2695 int request_status = accept_http_request_native(server_fd, &connection, &request);
2696 if (request_status == 0) {
2697 break;
2698 }
2699 if (request_status < 0) {
2700 continue;
2701 }
2702 worker = build_worker_context_native(&connection, &request, &app);
2703 if (worker == NULL) {
2704 native_send_text_response(&connection, 500, "Internal Server Error", "worker allocation failed\n");
2705 free_http_request_native(&request);
2706 native_connection_close(&connection);
2707 continue;
2708 }
2709 int enqueue_status = native_worker_pool_enqueue(&pool, worker);
2710 if (enqueue_status == 1) {
2713 continue;
2714 }
2715 if (enqueue_status != 0) {
2716 native_send_text_response(&worker->connection, 500, "Internal Server Error", "failed to enqueue worker\n");
2718 }
2719 }
2725 free_app_runtime(&app);
2726 return code;
2727}
2728
2729static int serve_runtime_stack_native(const ServeConfig *config) {
2730 RuntimeStack stack;
2731 NativeWorkerPool pool;
2732 size_t worker_count;
2733 size_t queue_capacity;
2734 int code = 0;
2735 int server_fd;
2736 struct timeval load_started;
2737 struct timeval load_finished;
2738 char *apps_dir = NULL;
2739 char *fingerprint = NULL;
2740 memset(&pool, 0, sizeof(pool));
2741 gettimeofday(&load_started, NULL);
2742 if (load_runtime_stack(config, &stack) != 0) {
2743 fprintf(stderr, "failed to load Pharos runtime stack\n");
2744 return 66;
2745 }
2746 gettimeofday(&load_finished, NULL);
2747 worker_count = native_resolved_worker_count(config->conf_path);
2748 queue_capacity = native_resolved_work_queue_capacity(config->conf_path);
2749 if (native_worker_pool_init(&pool, worker_count, queue_capacity) != 0) {
2750 free_runtime_stack(&stack);
2751 fprintf(stderr, "failed to initialize native worker pool\n");
2752 return 69;
2753 }
2754 server_fd = native_listener_open(config);
2755 if (server_fd < 0) {
2759 free_runtime_stack(&stack);
2760 fprintf(stderr, "failed to open listener\n");
2761 return 69;
2762 }
2763 register_listener_cleanup(config, (PHAROS_SOCKET)server_fd);
2764 apps_dir = runtime_apps_dir_native(config->conf_path);
2765 fingerprint = runtime_apps_fingerprint_native(apps_dir);
2766 {
2767 long startup_ms = (long)((load_finished.tv_sec - load_started.tv_sec) * 1000L + (load_finished.tv_usec - load_started.tv_usec) / 1000L);
2768 runtime_log_banner_native(&stack, config, startup_ms);
2769 }
2770 fprintf(
2771 stdout,
2772 "pharos native runtime worker_pool=true workers=%zu queue_capacity=%zu\n",
2773 worker_count,
2774 queue_capacity
2775 );
2776 fflush(stdout);
2777 for (;;) {
2778 char *next_fingerprint = runtime_apps_fingerprint_native(apps_dir);
2779 if (next_fingerprint != NULL && (fingerprint == NULL || strcmp(next_fingerprint, fingerprint) != 0)) {
2780 RuntimeStack refreshed_stack;
2781 memset(&refreshed_stack, 0, sizeof(refreshed_stack));
2782 if (load_runtime_stack(config, &refreshed_stack) == 0) {
2783 free_runtime_stack(&stack);
2784 stack = refreshed_stack;
2785 free(fingerprint);
2786 fingerprint = next_fingerprint;
2787 runtime_log_banner_native(&stack, config, 0L);
2788 } else {
2789 free_runtime_stack(&refreshed_stack);
2790 fprintf(stderr, "failed to refresh Pharos runtime stack\n");
2791 free(next_fingerprint);
2792 }
2793 } else {
2794 free(next_fingerprint);
2795 }
2796 NativeConnection connection;
2797 HttpRequest request;
2798 AppRuntime *matched;
2799 WorkerContext *worker;
2800 char *path_only;
2801 char *query;
2802 native_connection_init(&connection);
2803 int request_status = accept_http_request_native(server_fd, &connection, &request);
2804 if (request_status == 0) {
2805 break;
2806 }
2807 if (request_status < 0) {
2808 continue;
2809 }
2810 path_only = native_request_path_without_query(request.target);
2811 query = NULL;
2812 matched = path_only != NULL ? runtime_match_app_native(&stack, path_only) : NULL;
2813 free(path_only);
2814 (void)query;
2815 if (matched == NULL) {
2816 native_send_text_response(&connection, 404, "Not Found", "Not Found");
2817 free_http_request_native(&request);
2818 native_connection_close(&connection);
2819 continue;
2820 }
2821 worker = build_worker_context_native(&connection, &request, matched);
2822 if (worker == NULL) {
2823 native_send_text_response(&connection, 500, "Internal Server Error", "worker allocation failed\n");
2824 free_http_request_native(&request);
2825 native_connection_close(&connection);
2826 continue;
2827 }
2828 int enqueue_status = native_worker_pool_enqueue(&pool, worker);
2829 if (enqueue_status == 1) {
2832 continue;
2833 }
2834 if (enqueue_status != 0) {
2835 native_send_text_response(&worker->connection, 500, "Internal Server Error", "failed to enqueue worker\n");
2837 }
2838 }
2844 free(fingerprint);
2845 free(apps_dir);
2846 free_runtime_stack(&stack);
2847 return code;
2848}
2849
2850static int serve_website_target(const ServeConfig *config) {
2851 ServeConfig config_copy = *config;
2852 int code;
2853 char *artifact_root = resolve_website_root(&config_copy);
2854 if (artifact_root == NULL) {
2855 return 66;
2856 }
2857 free(config_copy.root);
2858 config_copy.root = artifact_root;
2859 code = serve_static_root(&config_copy);
2860 config_copy.root = NULL;
2861 return code;
2862}
2863
2865 static const struct {
2866 const char *kind;
2867 NativeServeHandlerFn handler;
2868 } handler_map[] = {
2869 { "website", serve_website_target },
2870 { "app", serve_single_app },
2871 { "runtime", serve_runtime_stack_native }
2872 };
2873 size_t index;
2874 for (index = 0; index < sizeof(handler_map) / sizeof(handler_map[0]); index++) {
2875 if (streq(kind, handler_map[index].kind)) {
2876 return handler_map[index].handler;
2877 }
2878 }
2879 return NULL;
2880}
2881
2882static int handle_serve(int argc, char **argv) {
2883 ServeConfig config;
2884 NativeServeHandlerFn handler;
2885 int code;
2886 parse_serve_config_native(argc, argv, &config);
2888 if (config.kind == NULL) {
2889 if (config.root == NULL) {
2890 print_help();
2891 free_serve_config_native(&config);
2892 return 64;
2893 }
2894 code = serve_static_root(&config);
2895 free_serve_config_native(&config);
2896 return code;
2897 }
2898 if (config.target_id == NULL) {
2899 fprintf(stderr, "missing target id for serve %s\n", config.kind);
2900 free_serve_config_native(&config);
2901 return 64;
2902 }
2903 handler = find_serve_handler_native(config.kind);
2904 if (handler == NULL) {
2905 free_serve_config_native(&config);
2906 return 64;
2907 }
2908 code = handler(&config);
2909 free_serve_config_native(&config);
2910 return code;
2911}
2912
2913static int website_verb_mode_native(const char *verb) {
2914 static const char *status_like_verbs[] = {
2915 "status", "verify", "run", "probe", "smoke", "doctor", "release",
2916 "restore", "compare", "snapshot", "export", "import", "diff",
2917 "sync", "clean"
2918 };
2919 size_t index;
2920 if (streq(verb, "build")) {
2921 return 1;
2922 }
2923 for (index = 0; index < sizeof(status_like_verbs) / sizeof(status_like_verbs[0]); index++) {
2924 if (streq(verb, status_like_verbs[index])) {
2925 return 2;
2926 }
2927 }
2928 return 0;
2929}
2930
2931static int handle_native_website_command(int argc, char **argv) {
2932 const char *verb;
2933 const char *target_kind;
2934 const char *target_id;
2935 const char *build_dir = "dist";
2936 int index;
2937 char *report_path = NULL;
2938 int code;
2939 if (argc < 3) {
2940 return 78;
2941 }
2942 verb = argv[0];
2943 target_kind = argv[1];
2944 target_id = argv[2];
2945 if (!streq(target_kind, "website") || !native_known_website_supported(target_id)) {
2946 return 78;
2947 }
2948 for (index = 3; index < argc; index++) {
2949 if (streq(argv[index], "--build-dir") && index + 1 < argc) {
2950 build_dir = argv[index + 1];
2951 index++;
2952 }
2953 }
2954 switch (website_verb_mode_native(verb)) {
2955 case 1:
2956 code = build_known_website_native(target_id, build_dir, &report_path, NULL);
2957 break;
2958 case 2:
2959 code = emit_website_status_native(verb, target_id, build_dir, &report_path);
2960 break;
2961 default:
2962 return 78;
2963 }
2964 if (code != 0) {
2965 return code;
2966 }
2967 if (report_path != NULL) {
2968 char *content = read_file_text(report_path);
2969 if (content != NULL) {
2970 fputs(content, stdout);
2971 free(content);
2972 }
2973 free(report_path);
2974 }
2975 return 0;
2976}
2977
2978static int runtime_verb_supported_native(const char *verb) {
2979 static const char *supported_verbs[] = {
2980 "build", "status", "verify", "run", "probe", "smoke", "doctor"
2981 };
2982 size_t index;
2983 for (index = 0; index < sizeof(supported_verbs) / sizeof(supported_verbs[0]); index++) {
2984 if (streq(verb, supported_verbs[index])) {
2985 return 1;
2986 }
2987 }
2988 return 0;
2989}
2990
2991static const char *native_app_runtime_report_id(const char *verb) {
2992 static const struct {
2993 const char *verb;
2994 const char *report_id;
2995 } report_map[] = {
2996 { "status", "pharos-status-v1" },
2997 { "verify", "pharos-local-app-build-v1" },
2998 { "run", "pharos-local-app-runtime-v1" },
2999 { "probe", "pharos-local-app-runtime-v1" },
3000 { "smoke", "pharos-local-app-runtime-v1" },
3001 { "doctor", "pharos-doctor-v1" }
3002 };
3003 size_t index;
3004 for (index = 0; index < sizeof(report_map) / sizeof(report_map[0]); index++) {
3005 if (streq(verb, report_map[index].verb)) {
3006 return report_map[index].report_id;
3007 }
3008 }
3009 return "pharos-local-app-runtime-v1";
3010}
3011
3012static int handle_native_runtime_app_command(const char *verb, ServeConfig *config) {
3013 AppRuntime app;
3014 char *app_root = NULL;
3015 char *artifact_dir = NULL;
3016 char *report_path = NULL;
3017 char *report_json = NULL;
3018 char *resolved_build_dir = NULL;
3019 char *build_dir_override = NULL;
3020 const char *report_id = native_app_runtime_report_id(verb);
3021 int code;
3022 if (verb == NULL || config == NULL || config->target_id == NULL) {
3023 return 78;
3024 }
3025 app_root = find_app_root_for_target(
3026 config->target_id,
3027 config->app_dir,
3030 );
3031 if (app_root == NULL) {
3032 fprintf(stderr, "unable to locate app root for app target: %s\n", config->target_id);
3033 return 66;
3034 }
3035 memset(&app, 0, sizeof(app));
3036 build_dir_override = runtime_build_dir_override_native(config, app_root);
3038 app_root,
3039 config->target_id,
3040 build_dir_override,
3041 config->state_dir,
3042 config->state_path,
3043 config->base_path,
3044 &report_path,
3045 &artifact_dir
3046 );
3047 if (code != 0) {
3048 free(build_dir_override);
3049 free(artifact_dir);
3050 free(app_root);
3051 return code;
3052 }
3053 if (maybe_seed_app_state_native(config, app_root, config->target_id, config->seed_state ? 1 : 0) != 0) {
3054 free(build_dir_override);
3055 free(report_path);
3056 free(artifact_dir);
3057 free(app_root);
3058 return 69;
3059 }
3060 code = load_app_runtime_from_artifact_native(artifact_dir, &app);
3061 if (code != 0) {
3062 free(build_dir_override);
3063 free(report_path);
3064 free(artifact_dir);
3065 free(app_root);
3066 return code;
3067 }
3068 resolved_build_dir = build_dir_override;
3069 if (resolved_build_dir == NULL ||
3070 finalize_loaded_app_runtime_native(&app, config, app_root, resolved_build_dir) != 0) {
3071 free(resolved_build_dir);
3072 free_app_runtime(&app);
3073 free(report_path);
3074 free(artifact_dir);
3075 free(app_root);
3076 return 69;
3077 }
3078 free(resolved_build_dir);
3079 if (streq(verb, "build")) {
3081 artifact_dir,
3082 app.app_id,
3083 app.state_path,
3084 app.base_path,
3085 app.log_path,
3086 app.log_format,
3088 app.debug,
3089 app.host_profile,
3091 );
3092 if (report_json != NULL && report_path != NULL) {
3093 if (write_text_file(report_path, report_json) != 0) {
3094 free(report_json);
3095 free_app_runtime(&app);
3096 free(report_path);
3097 free(artifact_dir);
3098 free(app_root);
3099 return 69;
3100 }
3101 }
3102 } else if (streq(verb, "doctor")) {
3105 config,
3106 artifact_dir,
3107 &app,
3109 );
3110 } else {
3112 report_id,
3113 verb,
3115 "native-host-target",
3116 artifact_dir,
3117 &app
3118 );
3119 }
3120 free_app_runtime(&app);
3121 free(report_path);
3122 free(artifact_dir);
3123 free(app_root);
3124 if (report_json == NULL) {
3125 return 69;
3126 }
3127 fputs(report_json, stdout);
3128 free(report_json);
3129 return 0;
3130}
3131
3132static int handle_native_runtime_command(int argc, char **argv) {
3133 const char *verb;
3134 ServeConfig config;
3135 RuntimeStack stack;
3136 char *apps_dir = NULL;
3137 char *report_path = NULL;
3138 char *report_json = NULL;
3139 int code = 0;
3140 if (argc < 2) {
3141 return 78;
3142 }
3143 verb = argv[0];
3144 if (!runtime_verb_supported_native(verb)) {
3145 return 78;
3146 }
3147 parse_serve_config_native(argc - 1, argv + 1, &config);
3148 if (config.kind == NULL || config.target_id == NULL) {
3149 free_serve_config_native(&config);
3150 return 78;
3151 }
3152 if (streq(config.kind, "app")) {
3153 code = handle_native_runtime_app_command(verb, &config);
3154 free_serve_config_native(&config);
3155 return code;
3156 }
3157 if (!streq(config.kind, "runtime")) {
3158 free_serve_config_native(&config);
3159 return 78;
3160 }
3161 memset(&stack, 0, sizeof(stack));
3162 code = load_runtime_stack(&config, &stack);
3163 if (code != 0) {
3164 free_serve_config_native(&config);
3165 return code;
3166 }
3167 apps_dir = runtime_apps_dir_native(config.conf_path);
3168 report_path = native_website_report_path(
3169 config.build_dir != NULL ? config.build_dir : "dist",
3170 verb,
3171 config.target_id
3172 );
3173 if (report_path == NULL) {
3174 free(apps_dir);
3175 free_runtime_stack(&stack);
3176 free_serve_config_native(&config);
3177 return 69;
3178 }
3179 report_json = streq(verb, "build")
3180 ? build_native_runtime_build_report_json(config.target_id, config.conf_path, apps_dir, stack.count)
3181 : build_native_runtime_status_report_json(&stack, apps_dir);
3182 if (report_json == NULL) {
3183 free(report_path);
3184 free(apps_dir);
3185 free_runtime_stack(&stack);
3186 free_serve_config_native(&config);
3187 return 69;
3188 }
3189 code = write_text_file(report_path, report_json);
3190 free(report_json);
3191 if (code == 0) {
3192 code = emit_text_file_to_stdout_native(report_path);
3193 }
3194 free(report_path);
3195 free(apps_dir);
3196 free_runtime_stack(&stack);
3197 free_serve_config_native(&config);
3198 return code;
3199}
3200
3201static int prepare_finalized_app_from_config(const ServeConfig *config, char **artifact_dir_out, AppRuntime *app_out) {
3202 char *app_root = NULL;
3203 char *artifact_dir = NULL;
3204 char *report_path = NULL;
3205 char *resolved_build_dir = NULL;
3206 int code;
3207 if (artifact_dir_out != NULL) {
3208 *artifact_dir_out = NULL;
3209 }
3210 if (app_out == NULL || config == NULL || config->target_id == NULL) {
3211 return 69;
3212 }
3213 memset(app_out, 0, sizeof(*app_out));
3215 if (app_root == NULL) {
3216 return 66;
3217 }
3218 code = prepare_app_artifact_native(app_root, config->target_id, config->build_dir, config->state_dir, config->state_path, config->base_path, &report_path, &artifact_dir);
3219 if (code != 0) {
3220 free(report_path);
3221 free(artifact_dir);
3222 free(app_root);
3223 return code;
3224 }
3225 code = load_app_runtime_from_artifact_native(artifact_dir, app_out);
3226 if (code != 0) {
3227 free(report_path);
3228 free(artifact_dir);
3229 free(app_root);
3230 return code;
3231 }
3232 resolved_build_dir = effective_app_build_dir_native(app_root, config->build_dir);
3233 if (resolved_build_dir == NULL || finalize_loaded_app_runtime_native(app_out, config, app_root, resolved_build_dir) != 0) {
3234 free(resolved_build_dir);
3235 free_app_runtime(app_out);
3236 free(report_path);
3237 free(artifact_dir);
3238 free(app_root);
3239 return 69;
3240 }
3241 free(resolved_build_dir);
3242 free(report_path);
3243 free(app_root);
3244 if (artifact_dir_out != NULL) {
3245 *artifact_dir_out = artifact_dir;
3246 } else {
3247 free(artifact_dir);
3248 }
3249 return 0;
3250}
3251
3252static char *native_option_value(int argc, char **argv, const char *first_name, const char *second_name) {
3253 int index;
3254 for (index = 0; index < argc - 1; index++) {
3255 if ((first_name != NULL && streq(argv[index], first_name)) || (second_name != NULL && streq(argv[index], second_name))) {
3256 return argv[index + 1];
3257 }
3258 }
3259 return NULL;
3260}
3261
3263 const char *option,
3264 const char *value,
3265 char **binary_path,
3266 char **target_id,
3267 char **output_dir
3268) {
3269 if (option == NULL || value == NULL) {
3270 return 0;
3271 }
3272 if (streq(option, "--binary")) {
3273 *binary_path = (char *)value;
3274 return 1;
3275 }
3276 if (streq(option, "--target-id")) {
3277 *target_id = (char *)value;
3278 return 1;
3279 }
3280 if (streq(option, "--output") || streq(option, "--output-dir")) {
3281 *output_dir = (char *)value;
3282 return 1;
3283 }
3284 return 0;
3285}
3286
3287static int handle_native_deploy_package_deb_command(int argc, char **argv) {
3288 char *script = path_join(global_repo_root, "scripts/package_deb.sh");
3289 char *binary_path = NULL;
3290 char *target_id = NULL;
3291 char *output_dir = NULL;
3292 char *argv_list[17];
3293 char *output = NULL;
3294 char *package_path_json;
3295 char *binary_json;
3296 char *target_json;
3297 char *report_json = NULL;
3298 int child_argc = 0;
3299 int index;
3300 int code;
3301 if (argc < 3 || argv == NULL || script == NULL) {
3302 free(script);
3303 return 64;
3304 }
3305 for (index = 3; index < argc; index++) {
3306 if (index + 1 >= argc) {
3307 continue;
3308 }
3310 argv[index],
3311 argv[index + 1],
3312 &binary_path,
3313 &target_id,
3314 &output_dir
3315 )) {
3316 index++;
3317 }
3318 }
3319 if (binary_path == NULL || target_id == NULL) {
3320 free(script);
3321 return 64;
3322 }
3323 argv_list[child_argc++] = "/bin/sh";
3324 argv_list[child_argc++] = script;
3325 argv_list[child_argc++] = "--binary";
3326 argv_list[child_argc++] = binary_path;
3327 argv_list[child_argc++] = "--target-id";
3328 argv_list[child_argc++] = target_id;
3329 if (output_dir != NULL) {
3330 argv_list[child_argc++] = "--output";
3331 argv_list[child_argc++] = output_dir;
3332 }
3333 argv_list[child_argc] = NULL;
3334 code = run_process_capture_native(argv_list, &output);
3335 free(script);
3336 if (output != NULL) {
3337 trim_in_place(output);
3338 }
3339 if (code != 0 || output == NULL || output[0] == '\0') {
3340 free(output);
3341 return code == 0 ? 69 : code;
3342 }
3343 package_path_json = json_string_dup_native(output);
3344 binary_json = json_string_dup_native(binary_path);
3345 target_json = json_string_dup_native(target_id);
3346 if (package_path_json != NULL && binary_json != NULL && target_json != NULL) {
3347 size_t needed = strlen(package_path_json) + strlen(binary_json) + strlen(target_json) + 192;
3348 report_json = (char *)malloc(needed);
3349 if (report_json != NULL) {
3350 snprintf(report_json, needed, "{\"report_id\":\"pharos-deploy-package-v1\",\"format\":\"deb\",\"target_id\":%s,\"binary\":%s,\"package_path\":%s,\"status\":\"packaged\"}", target_json, binary_json, package_path_json);
3351 }
3352 }
3353 free(output);
3354 free(package_path_json);
3355 free(binary_json);
3356 free(target_json);
3357 if (report_json == NULL) {
3358 return 69;
3359 }
3360 fputs(report_json, stdout);
3361 free(report_json);
3362 return 0;
3363}
3364
3365static int parse_native_app_ops_config(int argc, char **argv, int skip_args, ServeConfig *config) {
3366 parse_serve_config_native(argc - skip_args, argv + skip_args, config);
3367 if (config->kind == NULL || config->target_id == NULL || !streq(config->kind, "app")) {
3369 return 78;
3370 }
3371 return 0;
3372}
3373
3375 int argc,
3376 char **argv,
3377 int skip_args,
3378 ServeConfig *config,
3379 char **artifact_dir,
3380 AppRuntime *app
3381) {
3382 int code = parse_native_app_ops_config(argc, argv, skip_args, config);
3383 if (code != 0) {
3384 return code;
3385 }
3386 code = prepare_finalized_app_from_config(config, artifact_dir, app);
3387 if (code != 0) {
3389 }
3390 return code;
3391}
3392
3393static int emit_native_report_json(const char *report_json) {
3394 if (report_json == NULL) {
3395 return 69;
3396 }
3397 fputs(report_json, stdout);
3398 return 0;
3399}
3400
3401static int handle_native_backup_restore_ops_command(int argc, char **argv) {
3402 ServeConfig config;
3403 AppRuntime app;
3404 char *artifact_dir = NULL;
3405 char *report_json = NULL;
3406 char *backend_override;
3407 char *output_dir_override;
3408 char *input_dir;
3409 char *error_text = NULL;
3410 int code;
3411 code = prepare_native_app_ops_runtime(argc, argv, 1, &config, &artifact_dir, &app);
3412 if (code != 0) {
3413 return code;
3414 }
3415 backend_override = native_option_value(argc, argv, "--backend", NULL);
3416 output_dir_override = native_option_value(argc, argv, "--output-dir", "--output");
3417 input_dir = native_option_value(argc, argv, "--input-dir", NULL);
3418 report_json = streq(argv[0], "backup")
3419 ? build_native_backup_report_local_json(&config, &app, backend_override, output_dir_override, &error_text)
3420 : build_native_restore_report_local_json(&config, &app, backend_override, input_dir, &error_text);
3421 free_app_runtime(&app);
3422 free(artifact_dir);
3423 free_serve_config_native(&config);
3424 if (report_json != NULL) {
3425 code = emit_native_report_json(report_json);
3426 free(report_json);
3427 free(error_text);
3428 return code;
3429 }
3430 if (error_text != NULL && error_text[0] != '\0') {
3431 fprintf(stderr, "%s\n", error_text);
3432 }
3433 free(error_text);
3434 return 69;
3435}
3436
3437static int handle_native_drift_check_ops_command(int argc, char **argv) {
3438 ServeConfig config;
3439 AppRuntime app;
3440 char *artifact_dir = NULL;
3441 char *report_json = NULL;
3442 int code = prepare_native_app_ops_runtime(argc, argv, 2, &config, &artifact_dir, &app);
3443 if (code != 0) {
3444 return code;
3445 }
3446 report_json = build_native_app_drift_report_local_json(global_repo_root, &config, &app);
3447 free_app_runtime(&app);
3448 free(artifact_dir);
3449 free_serve_config_native(&config);
3450 if (report_json == NULL) {
3451 return 69;
3452 }
3453 code = emit_native_report_json(report_json);
3454 free(report_json);
3455 return code;
3456}
3457
3458static int handle_native_deploy_check_ops_command(int argc, char **argv) {
3459 ServeConfig config;
3460 AppRuntime app;
3461 char *artifact_dir = NULL;
3462 char *report_json = NULL;
3463 int code = prepare_native_app_ops_runtime(argc, argv, 2, &config, &artifact_dir, &app);
3464 if (code != 0) {
3465 return code;
3466 }
3469 &config,
3470 artifact_dir,
3471 &app,
3473 );
3474 free_app_runtime(&app);
3475 free(artifact_dir);
3476 free_serve_config_native(&config);
3477 if (report_json == NULL) {
3478 return 69;
3479 }
3480 code = emit_native_report_json(report_json);
3481 free(report_json);
3482 return code;
3483}
3484
3486 int argc,
3487 char **argv,
3488 const char *first,
3489 const char *second,
3490 const char *third,
3491 int min_argc
3492) {
3493 if (argc < min_argc || argv == NULL || argv[0] == NULL) {
3494 return 0;
3495 }
3496 if (first != NULL && !streq(argv[0], first)) {
3497 return 0;
3498 }
3499 if (second != NULL && (argc < 2 || !streq(argv[1], second))) {
3500 return 0;
3501 }
3502 if (third != NULL && (argc < 3 || !streq(argv[2], third))) {
3503 return 0;
3504 }
3505 return 1;
3506}
3507
3508static int handle_native_noindex_command(int argc, char **argv) {
3509 char *target_path;
3510 int code;
3511 if (argc != 2 || argv == NULL || argv[0] == NULL || argv[1] == NULL || !streq(argv[0], "noindex")) {
3512 return 78;
3513 }
3514 target_path = path_canonicalize(argv[1]);
3515 if (target_path == NULL) {
3516 target_path = pharos_strdup(argv[1]);
3517 }
3518 if (target_path == NULL) {
3519 return 69;
3520 }
3521 code = write_noindex_markers_recursive_native(target_path);
3522 if (code == 0) {
3523 printf("marked noindex: %s\n", target_path);
3524 }
3525 free(target_path);
3526 return code;
3527}
3528
3529static int handle_native_ops_command(int argc, char **argv) {
3530 static const struct {
3531 const char *first;
3532 const char *second;
3533 const char *third;
3534 int min_argc;
3535 int (*handler)(int argc, char **argv);
3536 } dispatch_table[] = {
3537 { "backup", "app", NULL, 3, handle_native_backup_restore_ops_command },
3538 { "restore", "app", NULL, 3, handle_native_backup_restore_ops_command },
3539 { "drift", "check", NULL, 4, handle_native_drift_check_ops_command },
3540 { "deploy", "check", NULL, 4, handle_native_deploy_check_ops_command },
3541 { "deploy", "package", "deb", 3, handle_native_deploy_package_deb_command }
3542 };
3543 size_t index;
3544 for (index = 0; index < sizeof(dispatch_table) / sizeof(dispatch_table[0]); index++) {
3546 argc,
3547 argv,
3548 dispatch_table[index].first,
3549 dispatch_table[index].second,
3550 dispatch_table[index].third,
3551 dispatch_table[index].min_argc
3552 )) {
3553 return dispatch_table[index].handler(argc, argv);
3554 }
3555 }
3556 return 78;
3557}
3558
3559/**
3560 * @brief Native Pharos CLI entrypoint.
3561 *
3562 * The runtime resolves the repository and binary roots, dispatches command families,
3563 * and starts the native serving path when requested.
3564 */
3565int main(int argc, char **argv) {
3566 int website_code;
3567 int runtime_code;
3568 int ops_code;
3570 if (argc <= 1) {
3571 print_help();
3572 return 64;
3573 }
3576 if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0) {
3577 print_help();
3578 free(global_repo_root);
3579 free(global_binary_dir);
3580 return 0;
3581 }
3582 if (strcmp(argv[1], "--version") == 0) {
3583 print_version();
3584 free(global_repo_root);
3585 free(global_binary_dir);
3586 return 0;
3587 }
3588 if (strcmp(argv[1], "serve") == 0) {
3589 int code = handle_serve(argc - 2, argv + 2);
3590 free(global_repo_root);
3591 free(global_binary_dir);
3592 return code;
3593 }
3594 if (strcmp(argv[1], "json") == 0) {
3595 int code = handle_native_json_command(argc - 2, argv + 2);
3596 free(global_repo_root);
3597 free(global_binary_dir);
3598 return code;
3599 }
3600 {
3601 int barcode_code = handle_native_barcode_command(argc - 1, argv + 1);
3602 if (barcode_code != 78) {
3603 free(global_repo_root);
3604 free(global_binary_dir);
3605 return barcode_code;
3606 }
3607 }
3608 {
3609 int noindex_code = handle_native_noindex_command(argc - 1, argv + 1);
3610 if (noindex_code != 78) {
3611 free(global_repo_root);
3612 free(global_binary_dir);
3613 return noindex_code;
3614 }
3615 }
3616 ops_code = handle_native_ops_command(argc - 1, argv + 1);
3617 if (ops_code != 78) {
3618 free(global_repo_root);
3619 free(global_binary_dir);
3620 return ops_code;
3621 }
3622 website_code = handle_native_website_command(argc - 1, argv + 1);
3623 if (website_code != 78) {
3624 free(global_repo_root);
3625 free(global_binary_dir);
3626 return website_code;
3627 }
3628 runtime_code = handle_native_runtime_command(argc - 1, argv + 1);
3629 if (runtime_code != 78) {
3630 free(global_repo_root);
3631 free(global_binary_dir);
3632 return runtime_code;
3633 }
3634 if (global_repo_root == NULL) {
3635 fprintf(stderr, "unable to locate pharos repo root for launcher delegation\n");
3636 free(global_binary_dir);
3637 return 72;
3638 }
3639 website_code = exec_runtime_script_native(global_repo_root, argc, argv);
3640 free(global_repo_root);
3641 free(global_binary_dir);
3642 return website_code;
3643}
void free_app_local_config_native(AppLocalConfigNative *config)
Releases heap-owned fields in an app-local configuration record.
int load_app_local_config_native(const char *app_root, AppLocalConfigNative *config)
Loads app-local configuration and manifest-derived path hints into a config record.
Declares app-local runtime configuration helpers and the app-local configuration record.
int clone_app_runtime_native(const AppRuntime *source, AppRuntime *target)
Deep-copies one loaded app runtime into another record.
Declares helpers for cloning AppRuntime values.
int finalize_loaded_app_runtime_native(AppRuntime *app, const ServeConfig *config, const char *app_root_override, const char *build_dir_override)
Applies overrides and derived runtime paths to a loaded app runtime record.
Declares helpers that finalize loaded app runtime configuration.
char * app_build_dir_local_native(const char *app_root)
Builds the default local build directory path for an app.
char * effective_app_build_dir_native(const char *app_root, const char *build_dir_override)
Resolves the effective build directory for an app.
char * effective_app_log_path_native(const char *app_root, const char *app_id, const char *log_path_override)
Resolves the effective log file path for an app.
char * effective_app_log_format_native(const char *app_root)
Resolves the effective log format for an app.
char * effective_app_debug_native(const char *app_root, const char *debug_override)
Resolves the effective debug setting for an app.
char * find_app_root_for_target(const char *target_id, const char *app_dir_hint, const char *repo_root, const char *binary_dir)
Locates an app root for a target using explicit app-dir hints and canonical apps directories.
char * effective_app_error_report_path_native(const char *app_root, const char *app_id)
Resolves the effective error report path for an app.
char * effective_app_state_path_native(const char *app_root, const char *build_dir, const char *app_id, const char *state_path_override, const char *state_dir_override)
Resolves the effective state file path for an app.
char * effective_app_artifact_dir_native(const char *app_root, const char *build_dir, const char *app_id)
Resolves the effective artifact directory for an app from framework build root plus app-owned contrac...
char * effective_app_base_path_native(const char *app_root, const char *base_path_override)
Resolves the effective URL base path for an app.
Declares helpers for locating app roots and deriving runtime paths.
char * build_native_app_index_html(const char *app_id)
Builds the default index.html payload for a native app artifact.
char * native_website_artifact_dir(const char *build_dir, const char *target_id)
Builds the artifact directory path for a generated website target.
char * json_string_dup_native(const char *value)
Duplicates a string while escaping it for JSON string output.
char * native_website_report_path(const char *build_dir, const char *verb, const char *target_id)
Builds the report file path for a website build or status operation.
int count_substring_occurrences(const char *text, const char *needle)
Counts how many times a substring occurs in a string.
char * build_native_app_build_report_json(const char *artifact_dir, const char *app_id, const char *state_path, const char *base_path, const char *log_path, const char *log_format, const char *error_report_path, const char *debug, const char *host_profile, const char *compiled_entrypoint)
Builds a JSON report describing a native app build result.
char * build_artifact_local_app_manifest_json(const char *manifest_text, const char *artifact_generated_by, const char *state_path, const char *base_path, const char *log_path, const char *log_format, const char *error_report_path, const char *debug, const char *compiled_entrypoint, int route_count, int surface_count, int workflow_count)
Builds an artifact-localized copy of the app-owned manifest.
char * build_native_website_status_report_json(const char *report_id, const char *verb, const char *target_id, const char *artifact_dir)
Builds a JSON status report for a native website artifact.
Declares helpers that build native artifact and report payload strings.
char * build_native_restore_report_local_json(const ServeConfig *config, const AppRuntime *app, const char *backend_override, const char *input_dir, char **error_out)
Builds a JSON report describing a native app restore operation.
char * build_native_backup_report_local_json(const ServeConfig *config, const AppRuntime *app, const char *backend_override, const char *output_dir_override, char **error_out)
Builds a JSON report describing a native app backup operation.
Declares native backup and restore report builders.
int handle_native_barcode_command(int argc, char **argv)
int native_connection_close(NativeConnection *connection)
Closes a native connection and releases its owned metadata.
int native_connection_accept_next(int server_fd, volatile sig_atomic_t *shutdown_requested, NativeConnection *connection)
Accepts the next client connection and records its transport metadata.
int native_connection_write(NativeConnection *connection, const void *buffer, size_t len)
Writes bytes to a native connection.
void native_connection_init(NativeConnection *connection)
Initializes a native connection record to an empty state.
Declares the native connection abstraction and transport metadata helpers.
int native_dynamic_runtime_handle_request(const AppRuntime *app, const HttpRequest *request, NativeConnection *connection)
Serves one dynamic-app request entirely in-process.
In-process dynamic-app request handler for the Pharos native runtime.
int ensure_directory(const char *path)
Creates a directory path and any missing parent directories.
Definition native_fs.c:31
int write_text_file(const char *path, const char *content)
Writes a text buffer to a file path.
Definition native_fs.c:66
int remove_directory_recursive(const char *target_dir)
Recursively removes a directory tree.
Definition native_fs.c:179
int copy_directory_recursive(const char *source_dir, const char *target_dir)
Recursively copies one directory tree into another location.
Definition native_fs.c:132
int copy_file_binary(const char *source_path, const char *target_path)
Copies one file as binary data.
Definition native_fs.c:93
Declares native filesystem helper routines.
void native_write_access_log(const char *log_path, const HttpRequest *request, const char *request_path, int status_code)
Writes one access log entry for a completed HTTP request.
void native_write_error_log(const char *log_path, const char *error_report_path, const char *app_id, const HttpRequest *request, const char *request_path, const char *status_text, const char *reason, const char *resource_path, const char *detail_text)
Writes one structured error log entry and its paired JSONL error report.
Declares native access log and error log helpers.
char * http_status_line_from_response_native(const char *response)
Extracts the HTTP status line from a raw HTTP response payload.
int http_status_code_from_response_native(const char *response)
Extracts the numeric HTTP status code from a raw HTTP response payload.
Declares HTTP logging and response formatting helpers.
void free_http_request_native(HttpRequest *request)
Releases heap-owned fields in a parsed HTTP request record.
int parse_http_request_native(NativeConnection *connection, HttpRequest *request)
Parses an HTTP request from a native connection into a request record.
Declares the parsed HTTP request record and request parsing helpers.
void native_send_text_response(NativeConnection *connection, int status, const char *status_text, const char *body)
Sends a plain-text HTTP response with explicit content length.
Declares native HTTP response writing helpers.
char * conf_lookup_value(const char *path, const char *key)
Reads a Pharos conf file and looks up one key.
char * json_extract_array_in_section(const char *text, const char *section, const char *key)
Extracts the raw JSON array text for a key inside a named section.
char * json_find_string(const char *text, const char *key)
Finds the first JSON string value associated with a key.
char * json_find_string_in_section(const char *text, const char *section, const char *key)
Finds a JSON string value for a key inside a named section object.
Declares lightweight JSON and conf value lookup helpers.
int handle_native_json_command(int argc, char **argv)
const char * native_listener_endpoint(const ServeConfig *config)
Returns a printable endpoint string for the active listener configuration.
void native_listener_register_cleanup(NativeListenerState *state, const ServeConfig *config, int server_fd, volatile sig_atomic_t *shutdown_requested)
Registers the currently active listener for later cleanup.
void native_listener_cleanup_active(NativeListenerState *state)
Closes and unlinks the currently registered listener resources.
void native_listener_state_init(NativeListenerState *state)
Initializes listener cleanup state to an inactive value.
int native_listener_open(const ServeConfig *config)
Opens a listener socket using the supplied serve configuration.
Declares native listener state and socket lifecycle helpers.
char * build_native_app_drift_report_local_json(const char *repo_root, const ServeConfig *config, const AppRuntime *app)
Builds a JSON drift report for one app using native ops bridges.
char * build_native_app_doctor_report_local_json(const char *repo_root, const ServeConfig *config, const char *artifact_dir, const AppRuntime *app, const char *host_target)
Builds a JSON doctor report for one app using native ops bridges.
char * build_native_app_deploy_check_report_local_json(const char *repo_root, const ServeConfig *config, const char *artifact_dir, const AppRuntime *app, const char *host_target)
Builds a JSON deploy-check report for one app using native ops bridges.
Declares native bridge helpers for Pharos ops commands.
int run_process_capture_native(char *const argv[], char **output)
Executes a process and captures its combined output text.
int capture_execv_output_env_native(char *const argv[], const char *env_keys[], const char *env_values[], size_t env_count, char **output, size_t *output_len)
Executes a process with injected environment values and captures its output.
int capture_execv_output_native(char *const argv[], char **output, size_t *output_len)
Executes a process and captures its combined output.
int capture_execv_output_split_env_native(char *const argv[], const char *env_keys[], const char *env_values[], size_t env_count, char **stdout_output, size_t *stdout_len, char **stderr_output, size_t *stderr_len)
Executes a process with injected environment values and captures stdout and stderr separately.
Declares child-process capture helpers.
char * native_request_path_without_query(const char *target)
Removes the query string component from a request target.
char * native_request_target_for_mount(const char *mount_path, const char *target)
Remaps a request target so it is relative to an app mount path.
Declares request target normalization helpers.
void free_runtime_stack(RuntimeStack *stack)
Releases all loaded app runtimes and listener metadata in a runtime stack.
void free_app_runtime(AppRuntime *app)
Releases heap-owned fields in a loaded app runtime record.
Declares cleanup helpers for native runtime-owned data structures.
int load_app_runtime_from_artifact_native(const char *artifact_dir, AppRuntime *app)
Loads runtime metadata for one app from an emitted artifact directory.
Declares helpers for loading native runtime metadata from manifests.
char * build_native_app_runtime_report_json(const char *report_id, const char *verb, const char *host_target, const char *host_mode, const char *artifact_dir, const AppRuntime *app)
Builds a JSON runtime report for one app artifact and host surface.
char * build_native_runtime_status_report_json(const RuntimeStack *stack, const char *apps_dir)
Builds a JSON status report for a loaded runtime stack.
char * build_native_runtime_build_report_json(const char *target_id, const char *conf_path, const char *apps_dir, size_t app_count)
Builds a JSON report describing a native runtime build result.
Declares builders for native runtime report payloads.
char * runtime_apps_dir_native(const char *conf_path)
Resolves the apps directory configured for a runtime config file.
char * runtime_apps_fingerprint_native(const char *apps_dir)
Builds a fingerprint string describing the current runtime apps directory state.
void runtime_log_banner_native(const RuntimeStack *stack, const ServeConfig *config, long load_ms)
Emits a startup banner describing the loaded runtime stack.
AppRuntime * runtime_match_app_native(RuntimeStack *stack, const char *path)
Finds the loaded app runtime that should handle a request path.
Declares runtime stack helpers for app discovery, reload detection, and request matching.
Defines shared native runtime data structures used across the Pharos C runtime.
#define MAX_APPS
int parse_serve_config_native(int argc, char **argv, ServeConfig *config)
Parses native serve CLI arguments into a serve configuration record.
void apply_serve_config_defaults_from_conf_native(ServeConfig *config)
Applies runtime-config-derived default values to an already parsed serve config.
void free_serve_config_native(ServeConfig *config)
Releases heap-owned fields in a serve configuration record.
Declares helpers for parsing and normalizing native serve configuration.
int exec_runtime_script_native(const char *repo_root, int argc, char **argv)
Executes the shared Pharos runtime script with the supplied arguments.
int shell_status_or_build_native(const char *repo_root, const ServeConfig *config, int use_build, char **output)
Runs the shell-side status or build flow for one native serve target.
Declares bridge helpers for invoking Pharos shell command surfaces.
int build_native_app_request_argv(const AppRuntime *app, const char *request_method, const char *request_target, const char *request_cookie, const char *request_headers, const char *request_content_type, const char *request_body, int request_is_multipart, const char *temp_body_path, char *argv_list[], size_t argv_capacity, size_t *argc_out)
Builds the argv vector used to execute one compiled app request.
Declares argv construction helpers for spawned app requests.
#define PHAROS_APP_REQUEST_ARGV_CAPACITY
int write_native_temp_body_file(const char *body, size_t body_len, char **temp_body_path_out)
Writes a request body to a temporary file for multipart request execution.
void cleanup_native_temp_body_file(char **temp_body_path_io)
Removes a temporary request-body file and clears its stored path.
Declares temporary request-body file helpers.
int native_serve_static_file(NativeConnection *connection, const char *root, const char *path, const HttpRequest *request, const char *log_path)
Serves one static artifact response from a root directory.
Declares native static artifact serving helpers.
char * path_join(const char *left, const char *right)
Joins two path segments using the native path separator.
void trim_in_place(char *text)
Trims leading and trailing ASCII whitespace from a string in place.
char * path_canonicalize(const char *path)
Normalizes a path by collapsing redundant separators and dot segments.
char * current_working_directory(void)
Returns the current working directory.
int streq(const char *a, const char *b)
Tests whether two strings are exactly equal.
int path_is_directory(const char *path)
Tests whether a filesystem path refers to a directory.
char * path_dirname(const char *path)
Returns the parent directory component of a path.
char * resolve_relative_to(const char *base_dir, const char *value)
Resolves a path value relative to a base directory when needed.
char * read_file_text(const char *path)
Reads an entire file into a newly allocated text buffer.
int starts_with(const char *value, const char *prefix)
Tests whether one string starts with another string.
int path_exists(const char *path)
Tests whether a filesystem path currently exists.
Declares shared low-level support helpers used across the Pharos native runtime.
static int handle_static_root_builtin_request(NativeConnection *connection, const HttpRequest *request)
static void install_native_signal_handlers(void)
#define pharos_strdup
static int apply_deb_package_option_native(const char *option, const char *value, char **binary_path, char **target_id, char **output_dir)
static void handle_app_or_runtime_request(WorkerContext *context)
static void clear_listener_cleanup_registration(void)
static size_t native_resolved_work_queue_capacity(const char *runtime_conf_path)
Resolve the bounded queued-request capacity for one serve process.
static int handle_native_drift_check_ops_command(int argc, char **argv)
static int load_app_runtime_for_single_app(const ServeConfig *config, AppRuntime *app)
static int handle_serve(int argc, char **argv)
static int website_verb_mode_native(const char *verb)
static int build_known_website_native(const char *target_id, const char *build_dir, char **report_path_out, char **artifact_dir_out)
static int add_runtime_app_native(RuntimeStack *stack, const ServeConfig *config, const char *app_root, const char *app_id_hint)
static int parse_native_app_ops_config(int argc, char **argv, int skip_args, ServeConfig *config)
#define PHAROS_NATIVE_MIN_WORKER_COUNT
static void pharos_native_shutdown_signal_handler(int signum)
static int handle_native_deploy_check_ops_command(int argc, char **argv)
#define PHAROS_NATIVE_WORK_QUEUE_BYTES_PER_SLOT
static const char * native_http_parse_reason_phrase(int status_code)
static void native_send_worker_pool_overload_response(NativeConnection *connection)
Send the standard overload response for saturated native serving.
static void print_version(void)
#define PHAROS_NATIVE_MAX_WORKER_COUNT
static const char * dev_docs_directory_index_html_native(void)
static NativeServeHandlerFn find_serve_handler_native(const char *kind)
static void cleanup_active_listener(void)
#define PHAROS_NATIVE_DEFAULT_WORKER_COUNT
int main(int argc, char **argv)
Native Pharos CLI entrypoint.
static void native_worker_pool_request_shutdown(NativeWorkerPool *pool)
Ask the worker pool to stop after draining queued work.
static int runtime_verb_supported_native(const char *verb)
static int prepare_app_artifact_native(const char *app_root, const char *app_id, const char *build_dir_override, const char *state_dir_override, const char *state_path_override, const char *base_path_override, char **report_path_out, char **artifact_dir_out)
Prepares the artifact directory for an app at startup.
static int build_dev_docs_directory_native(const char *app_root, const char *artifact_dir)
static char * global_binary_dir
#define PHAROS_NATIVE_DEFAULT_WORK_QUEUE_CAPACITY
static int prepare_native_app_ops_runtime(int argc, char **argv, int skip_args, ServeConfig *config, char **artifact_dir, AppRuntime *app)
#define PHAROS_NATIVE_MAX_WORK_QUEUE_CAPACITY
static int handle_native_ops_command(int argc, char **argv)
static int emit_text_file_to_stdout_native(const char *file_path)
static void free_worker_context_native(WorkerContext *context)
Release a worker context and every owned request/runtime resource.
#define PHAROS_VERSION
#define pharos_unlink
static size_t native_default_worker_count(void)
Resolve the default worker-thread count when config does not override it.
static int handle_native_runtime_command(int argc, char **argv)
static void process_worker_context_native(WorkerContext *context)
Execute one accepted request through the active runtime/app handler.
static int native_parse_positive_size_value(const char *text, size_t *value_out)
Parse one positive size_t from config text.
static char * native_option_value(int argc, char **argv, const char *first_name, const char *second_name)
static int serve_website_target(const ServeConfig *config)
static const char * native_app_runtime_report_id(const char *verb)
static char * resolve_website_root(const ServeConfig *config)
#define PHAROS_NATIVE_MIN_WORK_QUEUE_CAPACITY
static int native_known_website_supported(const char *target_id)
static int serve_single_app(const ServeConfig *config)
static int native_worker_pool_enqueue(NativeWorkerPool *pool, WorkerContext *context)
Submit accepted request work to the bounded worker pool.
static NativeListenerState global_listener_state
static int handle_native_backup_restore_ops_command(int argc, char **argv)
static void native_worker_pool_destroy(NativeWorkerPool *pool)
Destroy worker-pool resources after shutdown and join.
static int handle_native_noindex_command(int argc, char **argv)
static void native_worker_pool_join(NativeWorkerPool *pool)
Join all worker threads created for the pool.
int(* NativeServeHandlerFn)(const ServeConfig *config)
static const char * native_http_parse_error_body(int status_code)
static char * resolve_value_or_default(const char *value, const char *fallback)
static int emit_native_report_json(const char *report_json)
static size_t native_clamp_size_value(size_t value, size_t minimum, size_t maximum)
Clamp one size value into an inclusive range.
static unsigned long long native_detect_physical_memory_bytes(void)
Detect total physical memory visible to the current host.
static WorkerContext * build_worker_context_native(NativeConnection *connection, HttpRequest *request, const AppRuntime *source_app)
static size_t native_resolved_worker_count(const char *runtime_conf_path)
Resolve the fixed worker-thread count for one serve process.
static int serve_static_root(const ServeConfig *config)
static char * resolve_seed_entrypoint_native(const char *app_root, const char *target_id, const char *build_dir_override)
static int load_runtime_stack(const ServeConfig *config, RuntimeStack *stack)
static char * app_runtime_host_profile_native(const char *app_root)
static char * runtime_build_dir_override_native(const ServeConfig *config, const char *app_root)
static int prepare_finalized_app_from_config(const ServeConfig *config, char **artifact_dir_out, AppRuntime *app_out)
#define PHAROS_TARGET_ID
static int accept_http_request_native(int server_fd, NativeConnection *connection, HttpRequest *request)
static int native_conf_positive_size_value(const char *runtime_conf_path, const char *key, size_t *value_out)
Read the configured positive size value for one runtime key.
static int write_noindex_markers_recursive_native(const char *root_path)
Recursively write macOS no-index markers through a directory tree.
static char * find_repo_root(const char *argv0)
static void print_help(void)
static int match_native_ops_command(int argc, char **argv, const char *first, const char *second, const char *third, int min_argc)
static int handle_native_website_command(int argc, char **argv)
static void * native_worker_pool_thread_main(void *opaque)
Run queued request work items until the worker pool shuts down.
static int maybe_seed_app_state_native(const ServeConfig *config, const char *app_root, const char *app_id, int force_seed)
static int emit_website_status_native(const char *verb, const char *target_id, const char *build_dir, char **report_path_out)
static int spawn_app_request(const AppRuntime *app, const HttpRequest *request, char **response, size_t *response_len, char **stderr_text, size_t *stderr_len)
static char * global_repo_root
#define PHAROS_SOCKET
static int build_app_from_root_native(const char *app_root, const char *build_dir_override, const char *state_dir_override, const char *state_path_override, const char *base_path_override, char **report_path_out, char **artifact_dir_out)
static int handle_native_deploy_package_deb_command(int argc, char **argv)
static int serve_runtime_stack_native(const ServeConfig *config)
static size_t native_default_work_queue_capacity(void)
Resolve the default queue capacity from physical memory.
static void register_listener_cleanup(const ServeConfig *config, PHAROS_SOCKET server_fd)
static char * resolve_runtime_socket_path_native(const ServeConfig *config, const char *runtime_conf_path, const char *runtime_conf_dir)
static int app_root_has_finalized_artifact_native(const char *app_root)
static volatile sig_atomic_t global_shutdown_requested
static int native_worker_pool_init(NativeWorkerPool *pool, size_t worker_count, size_t capacity)
Initialize a bounded worker pool for native request serving.
static int handle_native_runtime_app_command(const char *verb, ServeConfig *config)
static char * executable_dir_from_argv0(const char *argv0)
App-local configuration values loaded from app-local config and manifest authority fields.
Loaded runtime metadata for one Pharos app artifact.
Parsed HTTP request fields extracted from a native connection.
Accepted client connection and its resolved transport metadata.
Tracks the active listener descriptor and socket cleanup target.
WorkerContext ** items
pthread_cond_t not_empty
pthread_t * threads
pthread_mutex_t mutex
In-memory collection of loaded app runtimes served by one listener.
AppRuntime apps[MAX_APPS]
Parsed native serve configuration used to bootstrap the runtime listener.
Per-request worker state used while routing either runtime or app traffic.
HttpRequest request
NativeConnection connection