Pharos 0.7.23
Modern Web Framework & Web App Hosting Service.
Loading...
Searching...
No Matches
native_dynamic_relational.c
Go to the documentation of this file.
2
4#include "native_fs.h"
5#include "native_support.h"
7
8#include <sqlite3.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12
13#define PHAROS_SQLITE_BUSY_TIMEOUT_MS_RUNTIME 15000
14#ifndef _WIN32
15#include <dlfcn.h>
16#endif
17
18/**
19 * @brief Carries resolved PostgreSQL runtime connection settings.
20 */
21typedef struct {
22 char *host;
23 char *port;
24 char *database;
25 char *user;
27 char *tls_mode;
29 char *tls_cert;
30 char *tls_key;
32
33typedef struct pg_conn PGconn;
34typedef struct pg_result PGresult;
35
36typedef struct {
37#ifndef _WIN32
38 void *handle;
39#endif
40 PGconn *(*PQconnectdb)(const char *conninfo);
41 int (*PQstatus)(const PGconn *conn);
42 char *(*PQerrorMessage)(const PGconn *conn);
43 PGresult *(*PQexec)(PGconn *conn, const char *query);
44 int (*PQresultStatus)(const PGresult *res);
45 int (*PQntuples)(const PGresult *res);
46 int (*PQnfields)(const PGresult *res);
47 char *(*PQgetvalue)(const PGresult *res, int row_number, int column_number);
48 void (*PQclear)(PGresult *res);
49 void (*PQfinish)(PGconn *conn);
51
52#define PHAROS_CONN_STATUS_OK_RUNTIME 0
53#define PHAROS_PGRES_COMMAND_OK_RUNTIME 1
54#define PHAROS_PGRES_TUPLES_OK_RUNTIME 2
55#define PHAROS_PGRES_SINGLE_TUPLE_RUNTIME 9
56
57static yyjson_val *migration_backend_record_runtime_local(yyjson_val *root, const char *backend_value) {
58 yyjson_val *backends = native_json_obj_get_array(root, "backends");
59 yyjson_arr_iter iter;
60 yyjson_val *item = NULL;
61 if (backends == NULL || backend_value == NULL || !yyjson_arr_iter_init(backends, &iter)) {
62 return NULL;
63 }
64 while ((item = yyjson_arr_iter_next(&iter)) != NULL) {
65 yyjson_val *backend = native_json_obj_get(item, "backend");
66 if (yyjson_is_str(backend) && yyjson_equals_str(backend, backend_value)) {
67 return item;
68 }
69 }
70 return NULL;
71}
72
73static char *migration_fixture_path_dup_runtime_local(const char *app_root) {
74 return app_root != NULL ? path_join(app_root, "data/framework/migration-fixture-v1.json") : NULL;
75}
76
77static char *runtime_state_export_sqlite_path_dup_runtime_local(const char *app_root) {
78 return app_root != NULL ? path_join(app_root, "data/framework/runtime-state-export-sqlite.sql") : NULL;
79}
80
81static char *runtime_state_import_sqlite_path_dup_runtime_local(const char *app_root) {
82 return app_root != NULL ? path_join(app_root, "data/framework/runtime-state-import-sqlite.sql") : NULL;
83}
84
86 return app_root != NULL ? path_join(app_root, "data/framework/runtime-state-export-postgresql.sql") : NULL;
87}
88
90 return app_root != NULL ? path_join(app_root, "data/framework/runtime-state-import-postgresql.sql") : NULL;
91}
92
94 if (db == NULL) {
95 return 69;
96 }
97 sqlite3_busy_timeout(db, PHAROS_SQLITE_BUSY_TIMEOUT_MS_RUNTIME);
98 if (sqlite3_exec(db, "PRAGMA journal_mode=WAL;", NULL, NULL, NULL) != SQLITE_OK) {
99 return 69;
100 }
101 if (sqlite3_exec(db, "PRAGMA synchronous=NORMAL;", NULL, NULL, NULL) != SQLITE_OK) {
102 return 69;
103 }
104 return 0;
105}
106
107static char *sqlite_exec_first_value_dup_runtime_local(const char *sqlite_path, const char *sql_text) {
108 sqlite3 *db = NULL;
109 sqlite3_stmt *stmt = NULL;
110 char *value = NULL;
111 int step_result = SQLITE_DONE;
112 if (sqlite_path == NULL || sql_text == NULL) {
113 return NULL;
114 }
115 if (sqlite3_open(sqlite_path, &db) != SQLITE_OK) {
116 if (db != NULL) {
117 sqlite3_close(db);
118 }
119 return NULL;
120 }
122 sqlite3_close(db);
123 return NULL;
124 }
125 if (sqlite3_prepare_v2(db, sql_text, -1, &stmt, NULL) != SQLITE_OK) {
126 sqlite3_close(db);
127 return NULL;
128 }
129 step_result = sqlite3_step(stmt);
130 if (step_result == SQLITE_ROW) {
131 const unsigned char *text = sqlite3_column_text(stmt, 0);
132 value = strdup(text != NULL ? (const char *)text : "");
133 }
134 sqlite3_finalize(stmt);
135 sqlite3_close(db);
136 return value;
137}
138
139static int sqlite_exec_runtime_local(const char *sqlite_path, const char *sql_text) {
140 sqlite3 *db = NULL;
141 char *error_message = NULL;
142 int code = 69;
143 if (sqlite_path == NULL || sql_text == NULL) {
144 return 64;
145 }
146 if (sqlite3_open(sqlite_path, &db) != SQLITE_OK) {
147 if (db != NULL) {
148 sqlite3_close(db);
149 }
150 return 69;
151 }
153 sqlite3_close(db);
154 return 69;
155 }
156 if (sqlite3_exec(db, sql_text, NULL, NULL, &error_message) == SQLITE_OK) {
157 code = 0;
158 }
159 sqlite3_free(error_message);
160 sqlite3_close(db);
161 return code;
162}
163
164static int sqlite_apply_file_list_runtime_local(const char *sqlite_path, const char *app_root, yyjson_val *list) {
165 yyjson_arr_iter iter;
166 yyjson_val *item = NULL;
167 if (sqlite_path == NULL || app_root == NULL || list == NULL || !yyjson_arr_iter_init(list, &iter)) {
168 return 69;
169 }
170 while ((item = yyjson_arr_iter_next(&iter)) != NULL) {
171 char *resolved_path = NULL;
172 char *sql_text = NULL;
173 int code = 0;
174 if (!yyjson_is_str(item)) {
175 return 69;
176 }
177 resolved_path = resolve_relative_to(app_root, yyjson_get_str(item));
178 sql_text = resolved_path != NULL ? read_file_text(resolved_path) : NULL;
179 free(resolved_path);
180 if (sql_text == NULL) {
181 free(sql_text);
182 return 66;
183 }
184 code = sqlite_exec_runtime_local(sqlite_path, sql_text);
185 free(sql_text);
186 if (code != 0) {
187 return code;
188 }
189 }
190 return 0;
191}
192
193static char *checksum_string_dup_runtime_local(const char *value) {
194 const unsigned char *cursor = (const unsigned char *)(value != NULL ? value : "");
195 unsigned long long hash = 1469598103934665603ULL;
196 char *out = (char *)malloc(17);
197 if (out == NULL) {
198 return NULL;
199 }
200 while (*cursor != '\0') {
201 hash ^= (unsigned long long)(*cursor++);
202 hash *= 1099511628211ULL;
203 }
204 snprintf(out, 17, "%016llx", hash);
205 return out;
206}
207
208static char *migration_expected_checksum_dup_runtime_local(const char *app_root, yyjson_val *migration_record, const char *backend) {
209 yyjson_val *backend_files = NULL;
210 yyjson_val *backend_record = NULL;
211 yyjson_val *forward_files = NULL;
212 yyjson_arr_iter iter;
213 yyjson_val *item = NULL;
214 Buffer combined;
215 char *forward_checksum = NULL;
216 char *migration_id = NULL;
217 char *payload = NULL;
218 char *result = NULL;
219
220 if (app_root == NULL || migration_record == NULL || backend == NULL) {
221 return NULL;
222 }
223
224 backend_files = native_json_obj_get(migration_record, "backend_files");
225 backend_record = backend_files != NULL && yyjson_is_obj(backend_files) ? native_json_obj_get(backend_files, backend) : NULL;
226 forward_files = backend_record != NULL ? native_json_obj_get_array(backend_record, "forward_sql_files") : NULL;
227
228 buffer_init(&combined);
229 if (forward_files != NULL && yyjson_arr_iter_init(forward_files, &iter)) {
230 while ((item = yyjson_arr_iter_next(&iter)) != NULL) {
231 char *resolved_path = NULL;
232 char *file_text = NULL;
233 if (!yyjson_is_str(item)) {
234 buffer_free(&combined);
235 return NULL;
236 }
237 resolved_path = resolve_relative_to(app_root, yyjson_get_str(item));
238 if (resolved_path != NULL && path_exists(resolved_path)) {
239 file_text = read_file_text(resolved_path);
240 if (file_text != NULL && buffer_append(&combined, file_text, strlen(file_text)) != 0) {
241 free(file_text);
242 free(resolved_path);
243 buffer_free(&combined);
244 return NULL;
245 }
246 free(file_text);
247 }
248 if (resolved_path != NULL) {
249 if (buffer_append(&combined, "\n-- pharos-path: ", strlen("\n-- pharos-path: ")) != 0
250 || buffer_append(&combined, resolved_path, strlen(resolved_path)) != 0
251 || buffer_append(&combined, "\n", 1) != 0) {
252 free(resolved_path);
253 buffer_free(&combined);
254 return NULL;
255 }
256 }
257 free(resolved_path);
258 }
259 }
260
261 forward_checksum = checksum_string_dup_runtime_local(combined.data != NULL ? combined.data : "");
262 buffer_free(&combined);
263 migration_id = native_json_obj_get_string_dup(migration_record, "migration_id");
264 if (migration_id == NULL || forward_checksum == NULL) {
265 free(migration_id);
266 free(forward_checksum);
267 return NULL;
268 }
269
270 payload = (char *)malloc(strlen(migration_id) + strlen(backend) + strlen(forward_checksum) + 64);
271 if (payload != NULL) {
272 snprintf(payload, strlen(migration_id) + strlen(backend) + strlen(forward_checksum) + 64,
273 "migration_id=%s\nbackend=%s\nforward_checksum=%s",
274 migration_id,
275 backend,
276 forward_checksum);
277 result = checksum_string_dup_runtime_local(payload);
278 }
279
280 free(payload);
281 free(migration_id);
282 free(forward_checksum);
283 return result;
284}
285
287 if (config == NULL) {
288 return;
289 }
290 free(config->host);
291 free(config->port);
292 free(config->database);
293 free(config->user);
294 free(config->password_env_name);
295 free(config->tls_mode);
296 free(config->tls_root_cert);
297 free(config->tls_cert);
298 free(config->tls_key);
299 memset(config, 0, sizeof(*config));
300}
301
303 const char *app_root,
304 const char *runtime_conf_path,
306) {
307 if (app_root == NULL || app_root[0] == '\0' || config == NULL) {
308 return 0;
309 }
310 memset(config, 0, sizeof(*config));
311 config->host = native_dynamic_config_merged_value_dup(app_root, runtime_conf_path, "db_host");
312 config->port = native_dynamic_config_merged_value_dup(app_root, runtime_conf_path, "db_port");
313 config->database = native_dynamic_config_merged_value_dup(app_root, runtime_conf_path, "db_name");
314 config->user = native_dynamic_config_merged_value_dup(app_root, runtime_conf_path, "db_user");
315 config->password_env_name = native_dynamic_config_merged_value_dup(app_root, runtime_conf_path, "db_password_env");
316 config->tls_mode = native_dynamic_config_merged_value_dup(app_root, runtime_conf_path, "db_tls_mode");
317 config->tls_root_cert = native_dynamic_config_merged_value_dup(app_root, runtime_conf_path, "db_tls_root_cert");
318 config->tls_cert = native_dynamic_config_merged_value_dup(app_root, runtime_conf_path, "db_tls_cert");
319 config->tls_key = native_dynamic_config_merged_value_dup(app_root, runtime_conf_path, "db_tls_key");
320 if (config->tls_mode == NULL || config->tls_mode[0] == '\0') {
321 free(config->tls_mode);
322 config->tls_mode = strdup("require");
323 }
324 if (config->host == NULL || config->host[0] == '\0'
325 || config->port == NULL || config->port[0] == '\0'
326 || config->database == NULL || config->database[0] == '\0'
327 || config->user == NULL || config->user[0] == '\0'
328 || config->tls_mode == NULL || config->tls_mode[0] == '\0') {
330 return 0;
331 }
332 if (!(streq(config->tls_mode, "disable") || streq(config->tls_mode, "allow") || streq(config->tls_mode, "prefer")
333 || streq(config->tls_mode, "require") || streq(config->tls_mode, "verify-ca") || streq(config->tls_mode, "verify-full"))) {
335 return 0;
336 }
337 if ((config->tls_root_cert != NULL && config->tls_root_cert[0] != '\0' && !path_exists(config->tls_root_cert))
338 || (config->tls_cert != NULL && config->tls_cert[0] != '\0' && !path_exists(config->tls_cert))
339 || (config->tls_key != NULL && config->tls_key[0] != '\0' && !path_exists(config->tls_key))) {
341 return 0;
342 }
343 return 1;
344}
345
346static int conninfo_append_kv_runtime_local(Buffer *buffer, const char *key, const char *value, int escape_quotes) {
347 const char *cursor = value != NULL ? value : "";
348 if (buffer == NULL || key == NULL || key[0] == '\0' || value == NULL || value[0] == '\0') {
349 return 0;
350 }
351 if (buffer->len > 0 && buffer_append(buffer, " ", 1) != 0) {
352 return 0;
353 }
354 if (buffer_append(buffer, key, strlen(key)) != 0
355 || buffer_append(buffer, "='", 2) != 0) {
356 return 0;
357 }
358 while (*cursor != '\0') {
359 if (escape_quotes && (*cursor == '\\' || *cursor == '\'')) {
360 if (buffer_append(buffer, "\\", 1) != 0) {
361 return 0;
362 }
363 }
364 if (buffer_append(buffer, cursor, 1) != 0) {
365 return 0;
366 }
367 cursor++;
368 }
369 return buffer_append(buffer, "'", 1) == 0;
370}
371
372#ifndef _WIN32
373static void *open_libpq_handle_candidate_runtime_local(const char *path) {
374 if (path == NULL || path[0] == '\0') {
375 return NULL;
376 }
377 return dlopen(path, RTLD_NOW | RTLD_LOCAL);
378}
379#endif
380
382#ifndef _WIN32
383 static const char *const basenames[] = {"libpq.5.dylib", "libpq.dylib", "libpq.so.5", "libpq.so", NULL};
384 const char *prefix = NULL;
385 size_t index = 0;
386#endif
387 if (api == NULL) {
388 return 0;
389 }
390 memset(api, 0, sizeof(*api));
391#ifdef _WIN32
392 return 0;
393#else
394 prefix = getenv("PHAROS_LIBPQ_PREFIX");
395 if (prefix != NULL && prefix[0] != '\0') {
396 char *lib_dir = path_join(prefix, "lib");
397 if (lib_dir != NULL) {
398 for (index = 0; basenames[index] != NULL && api->handle == NULL; index++) {
399 char *candidate = path_join(lib_dir, basenames[index]);
400 if (candidate != NULL && path_exists(candidate)) {
402 }
403 free(candidate);
404 }
405 free(lib_dir);
406 }
407 }
408 for (index = 0; basenames[index] != NULL && api->handle == NULL; index++) {
409 api->handle = open_libpq_handle_candidate_runtime_local(basenames[index]);
410 }
411 if (api->handle == NULL) {
412 return 0;
413 }
414 api->PQconnectdb = (PGconn *(*)(const char *))dlsym(api->handle, "PQconnectdb");
415 api->PQstatus = (int (*)(const PGconn *))dlsym(api->handle, "PQstatus");
416 api->PQerrorMessage = (char *(*)(const PGconn *))dlsym(api->handle, "PQerrorMessage");
417 api->PQexec = (PGresult *(*)(PGconn *, const char *))dlsym(api->handle, "PQexec");
418 api->PQresultStatus = (int (*)(const PGresult *))dlsym(api->handle, "PQresultStatus");
419 api->PQntuples = (int (*)(const PGresult *))dlsym(api->handle, "PQntuples");
420 api->PQnfields = (int (*)(const PGresult *))dlsym(api->handle, "PQnfields");
421 api->PQgetvalue = (char *(*)(const PGresult *, int, int))dlsym(api->handle, "PQgetvalue");
422 api->PQclear = (void (*)(PGresult *))dlsym(api->handle, "PQclear");
423 api->PQfinish = (void (*)(PGconn *))dlsym(api->handle, "PQfinish");
424 if (api->PQconnectdb == NULL || api->PQstatus == NULL || api->PQerrorMessage == NULL || api->PQexec == NULL
425 || api->PQresultStatus == NULL || api->PQntuples == NULL || api->PQnfields == NULL || api->PQgetvalue == NULL
426 || api->PQclear == NULL || api->PQfinish == NULL) {
427 dlclose(api->handle);
428 memset(api, 0, sizeof(*api));
429 return 0;
430 }
431 return 1;
432#endif
433}
434
436#ifndef _WIN32
437 if (api != NULL && api->handle != NULL) {
438 dlclose(api->handle);
439 }
440#endif
441 if (api != NULL) {
442 memset(api, 0, sizeof(*api));
443 }
444}
445
447 Buffer buffer;
448 const char *password_value = NULL;
449 char *result = NULL;
450 if (config == NULL) {
451 return NULL;
452 }
453 buffer_init(&buffer);
454 if (!conninfo_append_kv_runtime_local(&buffer, "host", config->host, 1)
455 || !conninfo_append_kv_runtime_local(&buffer, "port", config->port, 1)
456 || !conninfo_append_kv_runtime_local(&buffer, "dbname", config->database, 1)
457 || !conninfo_append_kv_runtime_local(&buffer, "user", config->user, 1)
458 || !conninfo_append_kv_runtime_local(&buffer, "sslmode", config->tls_mode, 1)) {
459 buffer_free(&buffer);
460 return NULL;
461 }
462 if (config->password_env_name != NULL && config->password_env_name[0] != '\0') {
463 password_value = getenv(config->password_env_name);
464 if (password_value != NULL && password_value[0] != '\0' && !conninfo_append_kv_runtime_local(&buffer, "password", password_value, 1)) {
465 buffer_free(&buffer);
466 return NULL;
467 }
468 }
469 if ((config->tls_root_cert != NULL && config->tls_root_cert[0] != '\0' && !conninfo_append_kv_runtime_local(&buffer, "sslrootcert", config->tls_root_cert, 1))
470 || (config->tls_cert != NULL && config->tls_cert[0] != '\0' && !conninfo_append_kv_runtime_local(&buffer, "sslcert", config->tls_cert, 1))
471 || (config->tls_key != NULL && config->tls_key[0] != '\0' && !conninfo_append_kv_runtime_local(&buffer, "sslkey", config->tls_key, 1))) {
472 buffer_free(&buffer);
473 return NULL;
474 }
475 result = buffer.data;
476 if (result == NULL) {
477 result = strdup("");
478 }
479 buffer.data = NULL;
480 buffer_free(&buffer);
481 return result;
482}
483
485 const NativeDynamicLibpqApi *api,
486 PGresult *result,
487 char **output_out
488) {
489 Buffer buffer;
490 int row_count = 0;
491 int column_count = 0;
492 int row_index = 0;
493 int column_index = 0;
494 if (output_out != NULL) {
495 *output_out = NULL;
496 }
497 if (api == NULL || result == NULL || output_out == NULL) {
498 return 64;
499 }
500 buffer_init(&buffer);
501 row_count = api->PQntuples(result);
502 column_count = api->PQnfields(result);
503 for (row_index = 0; row_index < row_count; row_index++) {
504 for (column_index = 0; column_index < column_count; column_index++) {
505 const char *value = api->PQgetvalue(result, row_index, column_index);
506 if (column_index > 0 && buffer_append(&buffer, "|", 1) != 0) {
507 buffer_free(&buffer);
508 return 69;
509 }
510 if (value != NULL && buffer_append(&buffer, value, strlen(value)) != 0) {
511 buffer_free(&buffer);
512 return 69;
513 }
514 }
515 if (row_index + 1 < row_count && buffer_append(&buffer, "\n", 1) != 0) {
516 buffer_free(&buffer);
517 return 69;
518 }
519 }
520 *output_out = buffer.data != NULL ? buffer.data : strdup("");
521 buffer.data = NULL;
522 buffer_free(&buffer);
523 if (*output_out == NULL) {
524 return 69;
525 }
526 trim_in_place(*output_out);
527 return 0;
528}
529
532 const char *sql_text,
533 int capture_output,
534 char **output_out
535) {
536 NativeDynamicLibpqApi api = {0};
537 PGconn *connection = NULL;
538 PGresult *result = NULL;
539 char *conninfo = NULL;
540 int status = 0;
541 int code = 69;
542 if (output_out != NULL) {
543 *output_out = NULL;
544 }
545 if (config == NULL || sql_text == NULL) {
546 return 64;
547 }
549 return 69;
550 }
551 conninfo = postgresql_conninfo_dup_runtime_local(config);
552 connection = conninfo != NULL ? api.PQconnectdb(conninfo) : NULL;
553 if (connection == NULL || api.PQstatus(connection) != PHAROS_CONN_STATUS_OK_RUNTIME) {
554 fprintf(stderr, "postgresql_runtime_error stage=connect message=%s\n", connection != NULL && api.PQerrorMessage != NULL ? api.PQerrorMessage(connection) : "connection_failed");
555 goto cleanup;
556 }
557 result = api.PQexec(connection, sql_text);
558 if (result == NULL) {
559 fprintf(stderr, "postgresql_runtime_error stage=exec message=%s\n", api.PQerrorMessage != NULL ? api.PQerrorMessage(connection) : "exec_failed");
560 goto cleanup;
561 }
562 status = api.PQresultStatus(result);
563 if (capture_output) {
565 fprintf(stderr, "postgresql_runtime_error stage=capture status=%d message=%s\n", status, api.PQerrorMessage != NULL ? api.PQerrorMessage(connection) : "capture_failed");
566 goto cleanup;
567 }
568 code = capture_postgresql_result_output_runtime_local(&api, result, output_out);
570 code = 0;
571 } else {
572 fprintf(stderr, "postgresql_runtime_error stage=status status=%d message=%s\n", status, api.PQerrorMessage != NULL ? api.PQerrorMessage(connection) : "status_failed");
573 }
574cleanup:
575 if (result != NULL) {
576 api.PQclear(result);
577 }
578 if (connection != NULL) {
579 api.PQfinish(connection);
580 }
581 free(conninfo);
583 return code;
584}
585
588 const char *app_root,
589 yyjson_val *list
590) {
591 yyjson_arr_iter iter;
592 yyjson_val *item = NULL;
593 if (config == NULL || app_root == NULL || list == NULL || !yyjson_arr_iter_init(list, &iter)) {
594 return 69;
595 }
596 while ((item = yyjson_arr_iter_next(&iter)) != NULL) {
597 char *resolved_path = NULL;
598 char *sql_text = NULL;
599 int code = 0;
600 if (!yyjson_is_str(item)) {
601 return 69;
602 }
603 resolved_path = resolve_relative_to(app_root, yyjson_get_str(item));
604 sql_text = resolved_path != NULL ? read_file_text(resolved_path) : NULL;
605 free(resolved_path);
606 if (sql_text == NULL) {
607 free(sql_text);
608 return 66;
609 }
610 code = run_postgresql_sql_capture_runtime_local(config, sql_text, 0, NULL);
611 free(sql_text);
612 if (code != 0) {
613 return code;
614 }
615 }
616 return 0;
617}
618
619static char *runtime_state_placeholder_sql_dup_runtime_local(const char *sql_template, const char *escaped_state_json) {
620 const char *placeholder = NULL;
621 size_t prefix_len = 0;
622 size_t suffix_len = 0;
623 char *sql_text = NULL;
624 if (sql_template == NULL || escaped_state_json == NULL) {
625 return NULL;
626 }
627 placeholder = strstr(sql_template, "__STATE_JSON__");
628 if (placeholder == NULL) {
629 return NULL;
630 }
631 prefix_len = (size_t)(placeholder - sql_template);
632 suffix_len = strlen(placeholder + strlen("__STATE_JSON__"));
633 sql_text = (char *)malloc(prefix_len + strlen(escaped_state_json) + suffix_len + 1);
634 if (sql_text == NULL) {
635 return NULL;
636 }
637 memcpy(sql_text, sql_template, prefix_len);
638 memcpy(sql_text + prefix_len, escaped_state_json, strlen(escaped_state_json));
639 memcpy(sql_text + prefix_len + strlen(escaped_state_json), placeholder + strlen("__STATE_JSON__"), suffix_len + 1);
640 return sql_text;
641}
642
643static char *sql_literal_escape_dup_runtime_local(const char *value) {
644 const char *src = value != NULL ? value : "";
645 size_t extra = 0;
646 char *out = NULL;
647 char *dst = NULL;
648 while (*src != '\0') {
649 if (*src == '\'') {
650 extra++;
651 }
652 src++;
653 }
654 src = value != NULL ? value : "";
655 out = (char *)malloc(strlen(src) + extra + 1);
656 if (out == NULL) {
657 return NULL;
658 }
659 dst = out;
660 while (*src != '\0') {
661 if (*src == '\'') {
662 *dst++ = '\'';
663 }
664 *dst++ = *src++;
665 }
666 *dst = '\0';
667 return out;
668}
669
671 if (sync == NULL) {
672 return;
673 }
674 free(sync->before_signature);
675 sync->before_signature = NULL;
676 sync->enabled = 0;
677}
678
679char *native_dynamic_relational_backend_dup(const char *app_root, const char *runtime_conf_path) {
680 char *backend = native_dynamic_config_merged_value_dup(app_root, runtime_conf_path, "db_backend");
681 if (backend != NULL && (streq(backend, "sqlite") || streq(backend, "postgresql"))) {
682 return backend;
683 }
684 free(backend);
685 return NULL;
686}
687
689 const char *app_root,
690 const char *runtime_conf_path,
691 const char *app_id
692) {
693 char *sqlite_path = NULL;
694 if (app_root == NULL || app_root[0] == '\0') {
695 return NULL;
696 }
697 sqlite_path = native_dynamic_config_merged_value_dup(app_root, runtime_conf_path, "db_path");
698 if (sqlite_path != NULL && sqlite_path[0] != '\0') {
699 return sqlite_path;
700 }
701 free(sqlite_path);
702 {
703 char buffer[4096];
704 snprintf(buffer, sizeof(buffer), "/var/lib/pharos/%s.sqlite3", app_id != NULL && app_id[0] != '\0' ? app_id : "app");
705 return strdup(buffer);
706 }
707}
708
710 const char *app_root,
711 const char *runtime_conf_path,
712 const char *app_id
713) {
714 char *backend = native_dynamic_relational_backend_dup(app_root, runtime_conf_path);
715 char *sqlite_path = NULL;
716 char *parent_dir = NULL;
717 int ready = 0;
718 if (backend == NULL || !streq(backend, "sqlite")) {
719 free(backend);
720 return 0;
721 }
722 sqlite_path = native_dynamic_relational_sqlite_path_dup(app_root, runtime_conf_path, app_id);
723 parent_dir = sqlite_path != NULL ? path_dirname(sqlite_path) : NULL;
724 if (sqlite_path != NULL && parent_dir != NULL && (path_exists(sqlite_path) || ensure_directory(parent_dir) == 0)) {
725 ready = 1;
726 }
727 free(backend);
728 free(sqlite_path);
729 free(parent_dir);
730 return ready;
731}
732
734 const char *app_root,
735 const char *runtime_conf_path
736) {
737 char *backend = native_dynamic_relational_backend_dup(app_root, runtime_conf_path);
739 NativeDynamicLibpqApi api = {0};
740 int ready = 0;
741 if (backend != NULL && streq(backend, "postgresql")
742 && load_postgresql_runtime_config_runtime_local(app_root, runtime_conf_path, &config)
744 ready = 1;
745 }
746 free(backend);
749 return ready;
750}
751
753 const char *app_root,
754 const char *runtime_conf_path
755) {
756 char *backend = NULL;
757 char *migration_fixture_path = NULL;
758 yyjson_doc *migration_doc = NULL;
759 yyjson_val *migration_root = NULL;
760 yyjson_val *backend_record = NULL;
761 yyjson_val *schema_files = NULL;
762 yyjson_val *seed_files = NULL;
763 yyjson_val *refresh_seed_files = NULL;
764 yyjson_val *migrations = NULL;
765 char *auth_present = NULL;
766 char *security_present = NULL;
767 char *user_count = NULL;
769 int code = 69;
770
771 if (app_root == NULL || app_root[0] == '\0') {
772 return 64;
773 }
774 backend = native_dynamic_relational_backend_dup(app_root, runtime_conf_path);
775 if (backend == NULL || !streq(backend, "postgresql")) {
776 code = 64;
777 goto cleanup;
778 }
779 if (!load_postgresql_runtime_config_runtime_local(app_root, runtime_conf_path, &config)) {
780 code = 69;
781 goto cleanup;
782 }
783
784 migration_fixture_path = migration_fixture_path_dup_runtime_local(app_root);
785 if (migration_fixture_path == NULL) {
786 code = 69;
787 goto cleanup;
788 }
789 migration_doc = native_json_doc_load_file(migration_fixture_path);
790 if (migration_doc == NULL) {
791 code = 66;
792 goto cleanup;
793 }
794 migration_root = yyjson_doc_get_root(migration_doc);
795 backend_record = migration_backend_record_runtime_local(migration_root, "postgresql");
796 schema_files = backend_record != NULL ? native_json_obj_get_array(backend_record, "schema_files") : NULL;
797 seed_files = backend_record != NULL ? native_json_obj_get_array(backend_record, "seed_files") : NULL;
798 refresh_seed_files = backend_record != NULL ? native_json_obj_get_array(backend_record, "refresh_seed_files") : NULL;
799 migrations = native_json_obj_get_array(migration_root, "migrations");
800
802 "SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'auth_user');",
803 1,
804 &auth_present);
806 "SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'identity_password_reset');",
807 1,
808 &security_present);
809
810 if ((auth_present == NULL || !streq(auth_present, "t")) || (security_present == NULL || !streq(security_present, "t"))) {
811 code = postgresql_apply_file_list_runtime_local(&config, app_root, schema_files);
812 if (code != 0) {
813 goto cleanup;
814 }
815 }
816
817 if (run_postgresql_sql_capture_runtime_local(&config, "SELECT COUNT(*) FROM auth_user;", 1, &user_count) != 0 || user_count == NULL) {
818 free(user_count);
819 user_count = strdup("0");
820 }
821 if (user_count == NULL) {
822 code = 69;
823 goto cleanup;
824 }
825 if (streq(user_count, "0")) {
826 code = postgresql_apply_file_list_runtime_local(&config, app_root, seed_files);
827 if (code != 0) {
828 goto cleanup;
829 }
830 }
831 code = postgresql_apply_file_list_runtime_local(&config, app_root, refresh_seed_files);
832 if (code != 0) {
833 goto cleanup;
834 }
835
836 if (migrations != NULL) {
837 yyjson_arr_iter iter;
838 yyjson_val *item = NULL;
839 if (!yyjson_arr_iter_init(migrations, &iter)) {
840 code = 69;
841 goto cleanup;
842 }
843 while ((item = yyjson_arr_iter_next(&iter)) != NULL) {
844 yyjson_val *item_backend_files = native_json_obj_get(item, "backend_files");
845 yyjson_val *item_backend = item_backend_files != NULL ? native_json_obj_get(item_backend_files, "postgresql") : NULL;
846 yyjson_val *forward_files = item_backend != NULL ? native_json_obj_get_array(item_backend, "forward_sql_files") : NULL;
847 char *migration_id = NULL;
848 char *checksum_value = NULL;
849 char *escaped_migration_id = NULL;
850 char *escaped_checksum = NULL;
851 char *sql = NULL;
852 long schema_version = 0;
853 if (forward_files == NULL || yyjson_arr_size(forward_files) == 0) {
854 continue;
855 }
856 migration_id = native_json_obj_get_string_dup(item, "migration_id");
857 schema_version = native_json_obj_get_long_default(item, "schema_version", 0, NULL);
858 checksum_value = migration_expected_checksum_dup_runtime_local(app_root, item, "postgresql");
859 escaped_migration_id = sql_literal_escape_dup_runtime_local(migration_id != NULL ? migration_id : "");
860 escaped_checksum = sql_literal_escape_dup_runtime_local(checksum_value != NULL ? checksum_value : "");
861 if (escaped_migration_id == NULL || escaped_checksum == NULL) {
862 free(migration_id);
863 free(checksum_value);
864 free(escaped_migration_id);
865 free(escaped_checksum);
866 code = 69;
867 goto cleanup;
868 }
869 sql = (char *)malloc(strlen(escaped_migration_id) * 2 + strlen(escaped_checksum) * 2 + 640);
870 if (sql == NULL) {
871 free(migration_id);
872 free(checksum_value);
873 free(escaped_migration_id);
874 free(escaped_checksum);
875 code = 69;
876 goto cleanup;
877 }
878 snprintf(sql, strlen(escaped_migration_id) * 2 + strlen(escaped_checksum) * 2 + 640,
879 "INSERT INTO pharos_schema_migration (migration_id, schema_version, backend, checksum_sha256) VALUES ('%s', %ld, 'postgresql', '%s') ON CONFLICT (migration_id) DO NOTHING;\nUPDATE pharos_schema_migration SET checksum_sha256 = '%s' WHERE migration_id = '%s' AND backend = 'postgresql' AND (checksum_sha256 IS NULL OR BTRIM(checksum_sha256) = '');",
880 escaped_migration_id,
881 schema_version,
882 escaped_checksum,
883 escaped_checksum,
884 escaped_migration_id);
885 if (run_postgresql_sql_capture_runtime_local(&config, sql, 0, NULL) != 0) {
886 free(migration_id);
887 free(checksum_value);
888 free(escaped_migration_id);
889 free(escaped_checksum);
890 free(sql);
891 code = 69;
892 goto cleanup;
893 }
894 free(migration_id);
895 free(checksum_value);
896 free(escaped_migration_id);
897 free(escaped_checksum);
898 free(sql);
899 }
900 }
901
902 code = 0;
903cleanup:
904 free(backend);
905 free(migration_fixture_path);
906 free(auth_present);
907 free(security_present);
908 free(user_count);
909 native_json_doc_free(migration_doc);
911 return code;
912}
913
915 const char *app_root,
916 const char *runtime_conf_path,
917 const char *app_id
918) {
919 char *backend = NULL;
920 char *sqlite_path = NULL;
921 char *parent_dir = NULL;
922 char *migration_fixture_path = NULL;
923 yyjson_doc *migration_doc = NULL;
924 yyjson_val *migration_root = NULL;
925 yyjson_val *backend_record = NULL;
926 yyjson_val *schema_files = NULL;
927 yyjson_val *seed_files = NULL;
928 yyjson_val *refresh_seed_files = NULL;
929 yyjson_val *migrations = NULL;
930 char *auth_present = NULL;
931 char *security_present = NULL;
932 char *user_count = NULL;
933 int code = 69;
934
935 if (app_root == NULL || app_root[0] == '\0') {
936 return 64;
937 }
938 backend = native_dynamic_relational_backend_dup(app_root, runtime_conf_path);
939 if (backend == NULL || !streq(backend, "sqlite")) {
940 code = 64;
941 goto cleanup;
942 }
943 sqlite_path = native_dynamic_relational_sqlite_path_dup(app_root, runtime_conf_path, app_id);
944 parent_dir = sqlite_path != NULL ? path_dirname(sqlite_path) : NULL;
945 if (sqlite_path == NULL || parent_dir == NULL || ensure_directory(parent_dir) != 0) {
946 code = 69;
947 goto cleanup;
948 }
949 migration_fixture_path = migration_fixture_path_dup_runtime_local(app_root);
950 if (migration_fixture_path == NULL) {
951 code = 69;
952 goto cleanup;
953 }
954 migration_doc = native_json_doc_load_file(migration_fixture_path);
955 if (migration_doc == NULL) {
956 code = 66;
957 goto cleanup;
958 }
959 migration_root = yyjson_doc_get_root(migration_doc);
960 backend_record = migration_backend_record_runtime_local(migration_root, "sqlite");
961 schema_files = backend_record != NULL ? native_json_obj_get_array(backend_record, "schema_files") : NULL;
962 seed_files = backend_record != NULL ? native_json_obj_get_array(backend_record, "seed_files") : NULL;
963 refresh_seed_files = backend_record != NULL ? native_json_obj_get_array(backend_record, "refresh_seed_files") : NULL;
964 migrations = native_json_obj_get_array(migration_root, "migrations");
965
966 auth_present = sqlite_exec_first_value_dup_runtime_local(sqlite_path, "SELECT name FROM sqlite_master WHERE type='table' AND name='auth_user';");
967 security_present = sqlite_exec_first_value_dup_runtime_local(sqlite_path, "SELECT name FROM sqlite_master WHERE type='table' AND name='identity_password_reset';");
968 if ((auth_present == NULL || auth_present[0] == '\0') || (security_present == NULL || security_present[0] == '\0')) {
969 code = sqlite_apply_file_list_runtime_local(sqlite_path, app_root, schema_files);
970 if (code != 0) {
971 goto cleanup;
972 }
973 }
974 user_count = sqlite_exec_first_value_dup_runtime_local(sqlite_path, "SELECT COUNT(*) FROM auth_user;");
975 if (user_count == NULL || streq(user_count, "0")) {
976 code = sqlite_apply_file_list_runtime_local(sqlite_path, app_root, seed_files);
977 if (code != 0) {
978 goto cleanup;
979 }
980 }
981 code = sqlite_apply_file_list_runtime_local(sqlite_path, app_root, refresh_seed_files);
982 if (code != 0) {
983 goto cleanup;
984 }
985
986 if (migrations != NULL) {
987 yyjson_arr_iter iter;
988 yyjson_val *item = NULL;
989 if (!yyjson_arr_iter_init(migrations, &iter)) {
990 code = 69;
991 goto cleanup;
992 }
993 while ((item = yyjson_arr_iter_next(&iter)) != NULL) {
994 yyjson_val *item_backend_files = native_json_obj_get(item, "backend_files");
995 yyjson_val *item_backend = item_backend_files != NULL ? native_json_obj_get(item_backend_files, "sqlite") : NULL;
996 yyjson_val *forward_files = item_backend != NULL ? native_json_obj_get_array(item_backend, "forward_sql_files") : NULL;
997 char *migration_id = NULL;
998 char *checksum_value = NULL;
999 char *escaped_migration_id = NULL;
1000 char *escaped_checksum = NULL;
1001 char *sql = NULL;
1002 long schema_version = 0;
1003 if (forward_files == NULL || yyjson_arr_size(forward_files) == 0) {
1004 continue;
1005 }
1006 migration_id = native_json_obj_get_string_dup(item, "migration_id");
1007 schema_version = native_json_obj_get_long_default(item, "schema_version", 0, NULL);
1008 checksum_value = migration_expected_checksum_dup_runtime_local(app_root, item, "sqlite");
1009 escaped_migration_id = sql_literal_escape_dup_runtime_local(migration_id != NULL ? migration_id : "");
1010 escaped_checksum = sql_literal_escape_dup_runtime_local(checksum_value != NULL ? checksum_value : "");
1011 if (escaped_migration_id == NULL || escaped_checksum == NULL) {
1012 free(migration_id);
1013 free(checksum_value);
1014 free(escaped_migration_id);
1015 free(escaped_checksum);
1016 code = 69;
1017 goto cleanup;
1018 }
1019 sql = (char *)malloc(strlen(escaped_migration_id) * 2 + strlen(escaped_checksum) * 2 + 512);
1020 if (sql == NULL) {
1021 free(migration_id);
1022 free(checksum_value);
1023 free(escaped_migration_id);
1024 free(escaped_checksum);
1025 code = 69;
1026 goto cleanup;
1027 }
1028 snprintf(sql, strlen(escaped_migration_id) * 2 + strlen(escaped_checksum) * 2 + 512,
1029 "INSERT OR IGNORE INTO pharos_schema_migration (migration_id, schema_version, backend, checksum_sha256) VALUES ('%s', %ld, 'sqlite', '%s');\nUPDATE pharos_schema_migration SET checksum_sha256 = '%s' WHERE migration_id = '%s' AND backend = 'sqlite' AND (checksum_sha256 IS NULL OR TRIM(checksum_sha256) = '');",
1030 escaped_migration_id,
1031 schema_version,
1032 escaped_checksum,
1033 escaped_checksum,
1034 escaped_migration_id);
1035 if (sqlite_exec_runtime_local(sqlite_path, sql) != 0) {
1036 free(migration_id);
1037 free(checksum_value);
1038 free(escaped_migration_id);
1039 free(escaped_checksum);
1040 free(sql);
1041 code = 69;
1042 goto cleanup;
1043 }
1044 free(migration_id);
1045 free(checksum_value);
1046 free(escaped_migration_id);
1047 free(escaped_checksum);
1048 free(sql);
1049 }
1050 }
1051
1052 code = 0;
1053cleanup:
1054 free(backend);
1055 free(sqlite_path);
1056 free(parent_dir);
1057 free(migration_fixture_path);
1058 free(auth_present);
1059 free(security_present);
1060 free(user_count);
1061 native_json_doc_free(migration_doc);
1062 return code;
1063}
1064
1066 const char *app_root,
1067 const char *runtime_conf_path,
1068 const char *app_id
1069) {
1070 char *backend = native_dynamic_relational_backend_dup(app_root, runtime_conf_path);
1071 char *sqlite_path = NULL;
1072 char *sql_path = NULL;
1073 char *sql_text = NULL;
1074 char *export_json = NULL;
1076 if (backend == NULL) {
1077 return NULL;
1078 }
1079 if (streq(backend, "sqlite")) {
1080 sqlite_path = native_dynamic_relational_sqlite_path_dup(app_root, runtime_conf_path, app_id);
1082 sql_text = sql_path != NULL ? read_file_text(sql_path) : NULL;
1083 if (sqlite_path == NULL || sqlite_path[0] == '\0' || sql_text == NULL) {
1084 goto cleanup;
1085 }
1086 export_json = sqlite_exec_first_value_dup_runtime_local(sqlite_path, sql_text);
1087 } else if (streq(backend, "postgresql")) {
1088 if (!load_postgresql_runtime_config_runtime_local(app_root, runtime_conf_path, &pg)) {
1089 goto cleanup;
1090 }
1092 sql_text = sql_path != NULL ? read_file_text(sql_path) : NULL;
1093 if (sql_text == NULL || run_postgresql_sql_capture_runtime_local(&pg, sql_text, 1, &export_json) != 0) {
1094 free(export_json);
1095 export_json = NULL;
1096 }
1097 }
1098cleanup:
1099 free(backend);
1100 free(sqlite_path);
1101 free(sql_path);
1102 free(sql_text);
1104 return export_json;
1105}
1106
1108 const char *app_root,
1109 const char *runtime_conf_path,
1110 const char *app_id
1111) {
1112 char *backend = native_dynamic_relational_backend_dup(app_root, runtime_conf_path);
1113 char *sqlite_path = NULL;
1114 char *table_present = NULL;
1115 char *count_text = NULL;
1117 if (backend == NULL) {
1118 return NULL;
1119 }
1120 if (streq(backend, "sqlite")) {
1121 sqlite_path = native_dynamic_relational_sqlite_path_dup(app_root, runtime_conf_path, app_id);
1122 if (sqlite_path != NULL && sqlite_path[0] != '\0') {
1123 table_present = sqlite_exec_first_value_dup_runtime_local(sqlite_path, "SELECT name FROM sqlite_master WHERE type='table' AND name='audit_event';");
1124 if (table_present == NULL || table_present[0] == '\0') {
1125 count_text = strdup("0");
1126 } else {
1127 count_text = sqlite_exec_first_value_dup_runtime_local(sqlite_path, "SELECT COUNT(*) FROM audit_event;");
1128 }
1129 }
1130 } else if (streq(backend, "postgresql")) {
1131 if (load_postgresql_runtime_config_runtime_local(app_root, runtime_conf_path, &pg)) {
1132 char *table_exists = NULL;
1134 "SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'audit_event');",
1135 1,
1136 &table_exists) == 0) {
1137 if (table_exists != NULL && streq(table_exists, "t")) {
1138 run_postgresql_sql_capture_runtime_local(&pg, "SELECT COUNT(*) FROM audit_event;", 1, &count_text);
1139 } else {
1140 count_text = strdup("0");
1141 }
1142 }
1143 free(table_exists);
1144 }
1145 }
1146 free(backend);
1147 free(sqlite_path);
1148 free(table_present);
1150 return count_text;
1151}
1152
1154 const char *app_root,
1155 const char *runtime_conf_path,
1156 const char *app_id,
1157 const char *state_path,
1158 const char *expected_audit_count
1159) {
1160 char *backend = NULL;
1161 char *sqlite_path = NULL;
1162 char *current_audit_count = NULL;
1163 char *state_json = NULL;
1164 char *escaped_state_json = NULL;
1165 char *sql_template_path = NULL;
1166 char *sql_template = NULL;
1167 char *sql_text = NULL;
1169 int code = 69;
1170 if (app_root == NULL || app_root[0] == '\0' || state_path == NULL || state_path[0] == '\0') {
1171 return 64;
1172 }
1173 backend = native_dynamic_relational_backend_dup(app_root, runtime_conf_path);
1174 if (backend == NULL) {
1175 return 69;
1176 }
1177 if (expected_audit_count != NULL && expected_audit_count[0] != '\0') {
1178 current_audit_count = native_dynamic_relational_runtime_audit_count_dup(app_root, runtime_conf_path, app_id);
1179 if (current_audit_count == NULL || !streq(current_audit_count, expected_audit_count)) {
1180 code = 73;
1181 goto cleanup;
1182 }
1183 }
1184 state_json = read_file_text(state_path);
1185 escaped_state_json = sql_literal_escape_dup_runtime_local(state_json != NULL ? state_json : "");
1186 if (streq(backend, "sqlite")) {
1187 sqlite_path = native_dynamic_relational_sqlite_path_dup(app_root, runtime_conf_path, app_id);
1188 sql_template_path = runtime_state_import_sqlite_path_dup_runtime_local(app_root);
1189 sql_template = sql_template_path != NULL ? read_file_text(sql_template_path) : NULL;
1190 sql_text = runtime_state_placeholder_sql_dup_runtime_local(sql_template, escaped_state_json != NULL ? escaped_state_json : "");
1191 if (sqlite_path == NULL || sqlite_path[0] == '\0' || sql_text == NULL) {
1192 goto cleanup;
1193 }
1194 code = sqlite_exec_runtime_local(sqlite_path, sql_text);
1195 } else if (streq(backend, "postgresql")) {
1196 if (!load_postgresql_runtime_config_runtime_local(app_root, runtime_conf_path, &pg)) {
1197 goto cleanup;
1198 }
1199 sql_template_path = runtime_state_import_postgresql_path_dup_runtime_local(app_root);
1200 sql_template = sql_template_path != NULL ? read_file_text(sql_template_path) : NULL;
1201 sql_text = runtime_state_placeholder_sql_dup_runtime_local(sql_template, escaped_state_json != NULL ? escaped_state_json : "");
1202 if (sql_text == NULL) {
1203 goto cleanup;
1204 }
1205 code = run_postgresql_sql_capture_runtime_local(&pg, sql_text, 0, NULL);
1206 }
1207cleanup:
1208 free(backend);
1209 free(sqlite_path);
1210 free(current_audit_count);
1211 free(state_json);
1212 free(escaped_state_json);
1213 free(sql_template_path);
1214 free(sql_template);
1215 free(sql_text);
1217 return code;
1218}
1219
1221 unsigned char *bytes = NULL;
1222 size_t length = 0;
1223 size_t index = 0;
1224 unsigned long long hash = 1469598103934665603ULL;
1225 char *signature = NULL;
1226 if (path == NULL || path[0] == '\0') {
1227 return NULL;
1228 }
1229 if (!path_exists(path)) {
1230 return strdup("missing");
1231 }
1232 bytes = read_file_bytes_dup_runtime(path, &length);
1233 if (bytes == NULL) {
1234 return NULL;
1235 }
1236 for (index = 0; index < length; index++) {
1237 hash ^= (unsigned long long)bytes[index];
1238 hash *= 1099511628211ULL;
1239 }
1240 signature = (char *)malloc(40);
1241 if (signature != NULL) {
1242 snprintf(signature, 40, "%016llx:%llu", hash, (unsigned long long)length);
1243 }
1244 free(bytes);
1245 return signature;
1246}
1247
1249 const AppRuntime *app,
1251) {
1252 char *backend = NULL;
1253 char *export_json = NULL;
1254 char *state_dir = NULL;
1255 int code = 0;
1256 if (sync != NULL) {
1257 memset(sync, 0, sizeof(*sync));
1258 }
1259 if (app == NULL || app->app_root == NULL || app->state_path == NULL || sync == NULL) {
1260 return 0;
1261 }
1263 if (backend == NULL) {
1264 free(backend);
1265 return 0;
1266 }
1267 if (streq(backend, "sqlite")) {
1269 fprintf(stderr, "relational_prepare_failed step=sqlite_runtime_ready app_id=%s\n", app->app_id != NULL ? app->app_id : "");
1270 code = 69;
1271 goto cleanup;
1272 }
1274 if (code != 0) {
1275 fprintf(stderr, "relational_prepare_failed step=sqlite_runtime_prepare app_id=%s code=%d\n", app->app_id != NULL ? app->app_id : "", code);
1276 }
1277 } else if (streq(backend, "postgresql")) {
1279 fprintf(stderr, "relational_prepare_failed step=postgresql_runtime_ready app_id=%s\n", app->app_id != NULL ? app->app_id : "");
1280 code = 69;
1281 goto cleanup;
1282 }
1284 if (code != 0) {
1285 fprintf(stderr, "relational_prepare_failed step=postgresql_runtime_prepare app_id=%s code=%d\n", app->app_id != NULL ? app->app_id : "", code);
1286 }
1287 }
1288 if (code != 0) {
1289 goto cleanup;
1290 }
1292 if (export_json == NULL) {
1293 fprintf(stderr, "relational_prepare_failed step=runtime_export app_id=%s backend=%s\n", app->app_id != NULL ? app->app_id : "", backend != NULL ? backend : "");
1294 code = 69;
1295 goto cleanup;
1296 }
1297 state_dir = path_dirname(app->state_path);
1298 if (state_dir == NULL || ensure_directory(state_dir) != 0 || write_text_file(app->state_path, export_json) != 0) {
1299 fprintf(stderr, "relational_prepare_failed step=write_state app_id=%s path=%s\n", app->app_id != NULL ? app->app_id : "", app->state_path != NULL ? app->state_path : "");
1300 code = 69;
1301 goto cleanup;
1302 }
1304 sync->enabled = 1;
1305 code = sync->before_signature != NULL ? 0 : 69;
1306cleanup:
1307 free(backend);
1308 free(export_json);
1309 free(state_dir);
1310 if (code != 0) {
1312 }
1313 return code;
1314}
1315
1317 const AppRuntime *app,
1319) {
1320 char *after_signature = NULL;
1321 int code = 0;
1322 if (app == NULL || sync == NULL || !sync->enabled) {
1323 return 0;
1324 }
1326 if (after_signature == NULL) {
1327 return 69;
1328 }
1329 if (sync->before_signature != NULL && !streq(after_signature, sync->before_signature)) {
1331 app->app_root,
1332 app->runtime_conf_path,
1333 app->app_id,
1334 app->state_path,
1335 NULL
1336 );
1337 if (code != 0) {
1338 fprintf(stderr, "relational_sync_back_failed app_id=%s code=%d\n", app->app_id != NULL ? app->app_id : "", code);
1339 }
1340 }
1341 free(after_signature);
1342 return code;
1343}
char * native_dynamic_config_merged_value_dup(const char *app_root, const char *runtime_conf_path, const char *key)
Loads one merged dynamic config value with runtime-conf precedence over app-local config.
Declares Pharos-owned dynamic runtime config resolution helpers.
static void free_native_libpq_api_runtime_local(NativeDynamicLibpqApi *api)
#define PHAROS_PGRES_TUPLES_OK_RUNTIME
int native_dynamic_relational_runtime_import_state(const char *app_root, const char *runtime_conf_path, const char *app_id, const char *state_path, const char *expected_audit_count)
Imports canonical runtime state JSON into the configured relational backend.
static yyjson_val * migration_backend_record_runtime_local(yyjson_val *root, const char *backend_value)
static char * checksum_string_dup_runtime_local(const char *value)
static void free_postgresql_runtime_config_runtime_local(NativeDynamicPostgresqlRuntimeConfig *config)
int native_dynamic_relational_postgresql_runtime_ready(const char *app_root, const char *runtime_conf_path)
Returns whether PostgreSQL runtime prerequisites are satisfied.
char * native_dynamic_relational_file_signature_dup(const char *path)
Computes a stable signature for one file path.
int native_dynamic_relational_postgresql_runtime_prepare(const char *app_root, const char *runtime_conf_path)
Prepares the PostgreSQL schema, seed data, and migration ledger for one dynamic app.
int native_dynamic_relational_sqlite_runtime_prepare(const char *app_root, const char *runtime_conf_path, const char *app_id)
Prepares the sqlite schema, seed data, and migration ledger for one dynamic app.
static int capture_postgresql_result_output_runtime_local(const NativeDynamicLibpqApi *api, PGresult *result, char **output_out)
#define PHAROS_PGRES_COMMAND_OK_RUNTIME
static char * sqlite_exec_first_value_dup_runtime_local(const char *sqlite_path, const char *sql_text)
static void * open_libpq_handle_candidate_runtime_local(const char *path)
static char * postgresql_conninfo_dup_runtime_local(const NativeDynamicPostgresqlRuntimeConfig *config)
static char * migration_fixture_path_dup_runtime_local(const char *app_root)
char * native_dynamic_relational_runtime_audit_count_dup(const char *app_root, const char *runtime_conf_path, const char *app_id)
Returns the current audit-event count from the configured relational backend.
int native_dynamic_relational_sqlite_runtime_ready(const char *app_root, const char *runtime_conf_path, const char *app_id)
Returns whether sqlite runtime prerequisites are satisfied.
void native_dynamic_relational_request_sync_free(NativeDynamicRelationalRequestSync *sync)
Clears heap-owned fields in a request-local relational sync record.
static char * runtime_state_export_postgresql_path_dup_runtime_local(const char *app_root)
static char * runtime_state_placeholder_sql_dup_runtime_local(const char *sql_template, const char *escaped_state_json)
char * native_dynamic_relational_runtime_export_json_dup(const char *app_root, const char *runtime_conf_path, const char *app_id)
Exports canonical runtime state JSON from the configured relational backend.
struct pg_conn PGconn
static int sqlite_configure_connection_runtime_local(sqlite3 *db)
static char * migration_expected_checksum_dup_runtime_local(const char *app_root, yyjson_val *migration_record, const char *backend)
static int sqlite_apply_file_list_runtime_local(const char *sqlite_path, const char *app_root, yyjson_val *list)
int native_dynamic_relational_prepare_request_state(const AppRuntime *app, NativeDynamicRelationalRequestSync *sync)
Exports relational state into the app state file before request handling when enabled.
static char * sql_literal_escape_dup_runtime_local(const char *value)
static char * runtime_state_import_sqlite_path_dup_runtime_local(const char *app_root)
static int conninfo_append_kv_runtime_local(Buffer *buffer, const char *key, const char *value, int escape_quotes)
#define PHAROS_CONN_STATUS_OK_RUNTIME
static int sqlite_exec_runtime_local(const char *sqlite_path, const char *sql_text)
char * native_dynamic_relational_backend_dup(const char *app_root, const char *runtime_conf_path)
Returns the configured relational backend when it is recognized.
struct pg_result PGresult
static char * runtime_state_export_sqlite_path_dup_runtime_local(const char *app_root)
static int load_native_libpq_api_runtime_local(NativeDynamicLibpqApi *api)
static int load_postgresql_runtime_config_runtime_local(const char *app_root, const char *runtime_conf_path, NativeDynamicPostgresqlRuntimeConfig *config)
#define PHAROS_SQLITE_BUSY_TIMEOUT_MS_RUNTIME
static char * runtime_state_import_postgresql_path_dup_runtime_local(const char *app_root)
int native_dynamic_relational_sync_back_if_changed(const AppRuntime *app, const NativeDynamicRelationalRequestSync *sync)
Imports the app state file back into the relational backend when it changed.
static int run_postgresql_sql_capture_runtime_local(const NativeDynamicPostgresqlRuntimeConfig *config, const char *sql_text, int capture_output, char **output_out)
char * native_dynamic_relational_sqlite_path_dup(const char *app_root, const char *runtime_conf_path, const char *app_id)
Returns the effective sqlite database path for a dynamic app.
#define PHAROS_PGRES_SINGLE_TUPLE_RUNTIME
static int postgresql_apply_file_list_runtime_local(const NativeDynamicPostgresqlRuntimeConfig *config, const char *app_root, yyjson_val *list)
Declares Pharos-owned native relational runtime helpers for dynamic apps.
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
Declares native filesystem helper routines.
unsigned char * read_file_bytes_dup_runtime(const char *path, size_t *out_size)
Reads the full content of a binary file into a heap-allocated buffer.
void buffer_init(Buffer *buffer)
Initializes a growable buffer to an empty state.
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.
void buffer_free(Buffer *buffer)
Releases memory owned by a growable buffer.
int streq(const char *a, const char *b)
Tests whether two strings are exactly equal.
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.
int buffer_append(Buffer *buffer, const void *data, size_t len)
Appends bytes to a growable buffer and maintains a trailing NUL byte.
char * read_file_text(const char *path)
Reads an entire file into a newly allocated text buffer.
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.
yyjson_doc * native_json_doc_load_file(const char *path)
Parses a JSON file into an owned yyjson document.
long native_json_obj_get_long_default(yyjson_val *obj, const char *key, long default_value, int *found_out)
Reads a numeric object member or returns a fallback value.
void native_json_doc_free(yyjson_doc *doc)
Releases a document returned by the native yyjson helpers.
char * native_json_obj_get_string_dup(yyjson_val *obj, const char *key)
Duplicates a string-valued key from a JSON object.
yyjson_val * native_json_obj_get_array(yyjson_val *obj, const char *key)
Looks up an array-valued key from a JSON object value.
yyjson_val * native_json_obj_get(yyjson_val *obj, const char *key)
Looks up a key from a JSON object value.
Declares shared helper wrappers around vendored yyjson parsing and serialization APIs.
Loaded runtime metadata for one Pharos app artifact.
Growable byte buffer used by parsing and string assembly helpers.
size_t len
char * data
PGresult *(* PQexec)(PGconn *conn, const char *query)
int(* PQresultStatus)(const PGresult *res)
int(* PQnfields)(const PGresult *res)
void(* PQclear)(PGresult *res)
char *(* PQgetvalue)(const PGresult *res, int row_number, int column_number)
int(* PQntuples)(const PGresult *res)
char *(* PQerrorMessage)(const PGconn *conn)
void(* PQfinish)(PGconn *conn)
PGconn *(* PQconnectdb)(const char *conninfo)
int(* PQstatus)(const PGconn *conn)
Carries resolved PostgreSQL runtime connection settings.
Carries request-local relational bridge state for one native request.
yyjson_api_inline bool yyjson_is_str(const yyjson_val *val)
Definition yyjson.h:5390
yyjson_api_inline bool yyjson_is_obj(const yyjson_val *val)
Definition yyjson.h:5398
yyjson_api_inline yyjson_val * yyjson_doc_get_root(const yyjson_doc *doc)
Definition yyjson.h:5323
yyjson_api_inline bool yyjson_equals_str(const yyjson_val *val, const char *str)
Definition yyjson.h:5477
yyjson_api_inline yyjson_val * yyjson_arr_iter_next(yyjson_arr_iter *iter)
Definition yyjson.h:5672
yyjson_api_inline size_t yyjson_arr_size(const yyjson_val *arr)
Definition yyjson.h:5599
yyjson_api_inline const char * yyjson_get_str(const yyjson_val *val)
Definition yyjson.h:5469
yyjson_api_inline bool yyjson_arr_iter_init(const yyjson_val *arr, yyjson_arr_iter *iter)
Definition yyjson.h:5650