amduat-api/src/amduatd_space_doctor.c

861 lines
29 KiB
C

#include "amduatd_space_doctor.h"
#include "amduat/asl/record.h"
#include "amduat/enc/asl_log.h"
#include "amduat/enc/asl1_core_codec.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char *data;
size_t len;
size_t cap;
} amduatd_doctor_strbuf_t;
static void amduatd_doctor_strbuf_free(amduatd_doctor_strbuf_t *b) {
if (b == NULL) {
return;
}
free(b->data);
b->data = NULL;
b->len = 0u;
b->cap = 0u;
}
static bool amduatd_doctor_strbuf_reserve(amduatd_doctor_strbuf_t *b,
size_t extra) {
size_t need;
size_t next_cap;
char *next;
if (b == NULL) {
return false;
}
if (extra == 0u) {
return true;
}
if (b->len > SIZE_MAX - extra) {
return false;
}
need = b->len + extra;
if (need <= b->cap) {
return true;
}
next_cap = b->cap != 0u ? b->cap : 256u;
while (next_cap < need) {
if (next_cap > (SIZE_MAX / 2u)) {
next_cap = need;
break;
}
next_cap *= 2u;
}
next = (char *)realloc(b->data, next_cap);
if (next == NULL) {
return false;
}
b->data = next;
b->cap = next_cap;
return true;
}
static bool amduatd_doctor_strbuf_append(amduatd_doctor_strbuf_t *b,
const char *data,
size_t len) {
if (b == NULL) {
return false;
}
if (data == NULL) {
len = 0u;
}
if (!amduatd_doctor_strbuf_reserve(b, len + 1u)) {
return false;
}
if (len != 0u) {
memcpy(b->data + b->len, data, len);
}
b->len += len;
b->data[b->len] = '\0';
return true;
}
static bool amduatd_doctor_strbuf_append_cstr(amduatd_doctor_strbuf_t *b,
const char *s) {
return amduatd_doctor_strbuf_append(b,
s != NULL ? s : "",
s != NULL ? strlen(s) : 0u);
}
static bool amduatd_doctor_strbuf_append_char(amduatd_doctor_strbuf_t *b,
char c) {
return amduatd_doctor_strbuf_append(b, &c, 1u);
}
static char *amduatd_doctor_strdup(const char *s) {
size_t len;
char *copy;
if (s == NULL) {
return NULL;
}
len = strlen(s);
copy = (char *)malloc(len + 1u);
if (copy == NULL) {
return NULL;
}
memcpy(copy, s, len);
copy[len] = '\0';
return copy;
}
static const char *amduatd_space_doctor_status_name(
amduatd_space_doctor_status_t status) {
switch (status) {
case AMDUATD_DOCTOR_OK:
return "ok";
case AMDUATD_DOCTOR_WARN:
return "warn";
case AMDUATD_DOCTOR_FAIL:
return "fail";
case AMDUATD_DOCTOR_SKIPPED:
return "skipped";
default:
return "unknown";
}
}
static void amduatd_space_doctor_report_init(
amduatd_space_doctor_report_t *report) {
if (report == NULL) {
return;
}
memset(report, 0, sizeof(*report));
report->backend = AMDUATD_STORE_BACKEND_FS;
}
static void amduatd_space_doctor_report_clear(
amduatd_space_doctor_report_t *report) {
if (report == NULL) {
return;
}
for (size_t i = 0u; i < report->checks_len; ++i) {
free(report->checks[i].name);
free(report->checks[i].detail);
}
free(report->checks);
amduatd_space_doctor_report_init(report);
}
void amduatd_space_doctor_report_free(amduatd_space_doctor_report_t *report) {
amduatd_space_doctor_report_clear(report);
}
static bool amduatd_space_doctor_report_add(
amduatd_space_doctor_report_t *report,
const char *name,
amduatd_space_doctor_status_t status,
const char *detail) {
size_t next_len;
amduatd_space_doctor_check_t *next;
amduatd_space_doctor_check_t *entry;
if (report == NULL || name == NULL) {
return false;
}
if (report->checks_len > SIZE_MAX - 1u) {
return false;
}
next_len = report->checks_len + 1u;
next = (amduatd_space_doctor_check_t *)realloc(
report->checks, next_len * sizeof(*next));
if (next == NULL) {
return false;
}
report->checks = next;
entry = &report->checks[report->checks_len];
memset(entry, 0, sizeof(*entry));
entry->name = amduatd_doctor_strdup(name);
entry->detail = amduatd_doctor_strdup(detail != NULL ? detail : "");
if (entry->name == NULL || entry->detail == NULL) {
free(entry->name);
free(entry->detail);
return false;
}
entry->status = status;
report->checks_len = next_len;
switch (status) {
case AMDUATD_DOCTOR_OK:
report->ok_count++;
break;
case AMDUATD_DOCTOR_WARN:
report->warn_count++;
break;
case AMDUATD_DOCTOR_FAIL:
report->fail_count++;
break;
case AMDUATD_DOCTOR_SKIPPED:
report->skipped_count++;
break;
default:
break;
}
return true;
}
static bool amduatd_space_doctor_build_collection_head_name(
const char *name,
char **out_name) {
size_t name_len;
size_t total_len;
char *buffer;
size_t offset = 0u;
if (name == NULL || out_name == NULL) {
return false;
}
if (!amduat_asl_pointer_name_is_valid(name)) {
return false;
}
name_len = strlen(name);
total_len = 11u + name_len + 5u + 1u;
buffer = (char *)malloc(total_len);
if (buffer == NULL) {
return false;
}
memcpy(buffer + offset, "collection/", 11u);
offset += 11u;
memcpy(buffer + offset, name, name_len);
offset += name_len;
memcpy(buffer + offset, "/head", 5u);
offset += 5u;
buffer[offset] = '\0';
*out_name = buffer;
return true;
}
static bool amduatd_space_doctor_build_collection_log_head_name(
const char *name,
char **out_name) {
size_t name_len;
size_t log_name_len;
size_t total_len;
char *buffer;
size_t offset = 0u;
if (name == NULL || out_name == NULL) {
return false;
}
if (!amduat_asl_pointer_name_is_valid(name)) {
return false;
}
name_len = strlen(name);
log_name_len = 11u + name_len + 4u;
total_len = 4u + log_name_len + 5u + 1u;
buffer = (char *)malloc(total_len);
if (buffer == NULL) {
return false;
}
memcpy(buffer + offset, "log/", 4u);
offset += 4u;
memcpy(buffer + offset, "collection/", 11u);
offset += 11u;
memcpy(buffer + offset, name, name_len);
offset += name_len;
memcpy(buffer + offset, "/log", 4u);
offset += 4u;
memcpy(buffer + offset, "/head", 5u);
offset += 5u;
buffer[offset] = '\0';
*out_name = buffer;
return true;
}
enum {
AMDUATD_EDGE_INDEX_MAGIC_LEN = 8,
AMDUATD_EDGE_INDEX_VERSION = 1
};
static const uint8_t k_amduatd_edge_index_magic[AMDUATD_EDGE_INDEX_MAGIC_LEN] = {
'A', 'S', 'L', 'E', 'I', 'X', '1', '\0'
};
typedef struct {
bool has_graph_ref;
amduat_reference_t graph_ref;
} amduatd_edge_index_state_view_t;
static void amduatd_edge_index_state_view_free(
amduatd_edge_index_state_view_t *state) {
if (state == NULL) {
return;
}
if (state->has_graph_ref) {
amduat_reference_free(&state->graph_ref);
}
memset(state, 0, sizeof(*state));
}
static bool amduatd_doctor_read_u32_le(const uint8_t *data,
size_t len,
size_t *offset,
uint32_t *out) {
if (len - *offset < 4u) {
return false;
}
*out = (uint32_t)data[*offset] |
((uint32_t)data[*offset + 1u] << 8) |
((uint32_t)data[*offset + 2u] << 16) |
((uint32_t)data[*offset + 3u] << 24);
*offset += 4u;
return true;
}
static bool amduatd_doctor_read_u64_le(const uint8_t *data,
size_t len,
size_t *offset,
uint64_t *out) {
if (len - *offset < 8u) {
return false;
}
*out = (uint64_t)data[*offset] |
((uint64_t)data[*offset + 1u] << 8) |
((uint64_t)data[*offset + 2u] << 16) |
((uint64_t)data[*offset + 3u] << 24) |
((uint64_t)data[*offset + 4u] << 32) |
((uint64_t)data[*offset + 5u] << 40) |
((uint64_t)data[*offset + 6u] << 48) |
((uint64_t)data[*offset + 7u] << 56);
*offset += 8u;
return true;
}
static bool amduatd_edge_index_state_view_decode(
amduat_octets_t payload,
amduatd_edge_index_state_view_t *out_state) {
size_t offset = 0u;
uint32_t version = 0u;
uint32_t ref_len = 0u;
amduat_octets_t ref_bytes;
uint64_t ignored_offset = 0u;
if (out_state == NULL) {
return false;
}
memset(out_state, 0, sizeof(*out_state));
if (payload.data == NULL ||
payload.len < AMDUATD_EDGE_INDEX_MAGIC_LEN + 4u + 8u + 4u) {
return false;
}
if (memcmp(payload.data, k_amduatd_edge_index_magic,
AMDUATD_EDGE_INDEX_MAGIC_LEN) != 0) {
return false;
}
offset += AMDUATD_EDGE_INDEX_MAGIC_LEN;
if (!amduatd_doctor_read_u32_le(payload.data, payload.len, &offset, &version) ||
version != AMDUATD_EDGE_INDEX_VERSION) {
return false;
}
if (!amduatd_doctor_read_u64_le(payload.data, payload.len, &offset,
&ignored_offset) ||
!amduatd_doctor_read_u32_le(payload.data, payload.len, &offset, &ref_len)) {
return false;
}
(void)ignored_offset;
if (payload.len - offset < ref_len) {
return false;
}
if (ref_len != 0u) {
ref_bytes = amduat_octets(payload.data + offset, ref_len);
if (!amduat_enc_asl1_core_decode_reference_v1(ref_bytes,
&out_state->graph_ref)) {
amduatd_edge_index_state_view_free(out_state);
return false;
}
out_state->has_graph_ref = true;
offset += ref_len;
}
return offset == payload.len;
}
static amduatd_store_backend_t amduatd_space_doctor_backend_from_store(
const amduat_asl_store_t *store) {
if (store == NULL) {
return AMDUATD_STORE_BACKEND_FS;
}
if (store->ops.log_scan != NULL && store->ops.current_state != NULL) {
return AMDUATD_STORE_BACKEND_INDEX;
}
return AMDUATD_STORE_BACKEND_FS;
}
typedef struct {
const char *label;
char *pointer_name;
bool pointer_exists;
amduat_reference_t pointer_ref;
} amduatd_doctor_pointer_t;
static void amduatd_space_doctor_pointer_free(amduatd_doctor_pointer_t *ptr) {
if (ptr == NULL) {
return;
}
free(ptr->pointer_name);
ptr->pointer_name = NULL;
ptr->pointer_exists = false;
amduat_reference_free(&ptr->pointer_ref);
}
bool amduatd_space_doctor_run(amduat_asl_store_t *store,
amduat_asl_pointer_store_t *pointer_store,
const amduatd_space_t *effective_space,
const amduatd_cfg_t *cfg,
const amduatd_fed_cfg_t *fed_cfg,
amduatd_space_doctor_report_t *out_report) {
amduatd_doctor_pointer_t pointers[3];
amduat_octets_t edges_collection = amduat_octets(NULL, 0u);
amduat_octets_t edges_index_head = amduat_octets(NULL, 0u);
char *collection_head = NULL;
char *collection_log_head = NULL;
bool names_ok = true;
bool ok = false;
size_t i;
if (out_report == NULL) {
return false;
}
amduatd_space_doctor_report_init(out_report);
if (store == NULL || pointer_store == NULL || cfg == NULL) {
return false;
}
(void)cfg;
out_report->backend = amduatd_space_doctor_backend_from_store(store);
if (effective_space != NULL &&
effective_space->enabled &&
effective_space->space_id.data != NULL) {
out_report->scoped = true;
snprintf(out_report->space_id, sizeof(out_report->space_id), "%s",
(const char *)effective_space->space_id.data);
}
memset(pointers, 0, sizeof(pointers));
pointers[0].label = "edges_index_head";
pointers[1].label = "edges_collection_snapshot_head";
pointers[2].label = "edges_collection_log_head";
for (i = 0u; i < 3u; ++i) {
memset(&pointers[i].pointer_ref, 0, sizeof(pointers[i].pointer_ref));
}
if (!amduatd_space_edges_collection_name(effective_space, &edges_collection) ||
!amduatd_space_edges_index_head_name(effective_space, &edges_index_head)) {
(void)amduatd_space_doctor_report_add(out_report,
"pointer_name_validation",
AMDUATD_DOCTOR_FAIL,
"failed to scope pointer names");
names_ok = false;
goto doctor_after_names;
}
if (!amduatd_space_doctor_build_collection_head_name(
(const char *)edges_collection.data, &collection_head) ||
!amduatd_space_doctor_build_collection_log_head_name(
(const char *)edges_collection.data, &collection_log_head)) {
(void)amduatd_space_doctor_report_add(out_report,
"pointer_name_validation",
AMDUATD_DOCTOR_FAIL,
"failed to build collection heads");
names_ok = false;
goto doctor_after_names;
}
if (!amduat_asl_pointer_name_is_valid(
(const char *)edges_collection.data) ||
!amduat_asl_pointer_name_is_valid(
(const char *)edges_index_head.data) ||
!amduat_asl_pointer_name_is_valid(collection_head) ||
!amduat_asl_pointer_name_is_valid(collection_log_head)) {
(void)amduatd_space_doctor_report_add(out_report,
"pointer_name_validation",
AMDUATD_DOCTOR_FAIL,
"invalid pointer name encoding");
names_ok = false;
goto doctor_after_names;
}
(void)amduatd_space_doctor_report_add(out_report,
"pointer_name_validation",
AMDUATD_DOCTOR_OK,
"scoped pointer names valid");
doctor_after_names:
if (!names_ok) {
(void)amduatd_space_doctor_report_add(out_report,
"edges_index_head",
AMDUATD_DOCTOR_SKIPPED,
"invalid pointer names");
(void)amduatd_space_doctor_report_add(out_report,
"edges_collection_snapshot_head",
AMDUATD_DOCTOR_SKIPPED,
"invalid pointer names");
(void)amduatd_space_doctor_report_add(out_report,
"edges_collection_log_head",
AMDUATD_DOCTOR_SKIPPED,
"invalid pointer names");
(void)amduatd_space_doctor_report_add(out_report,
"cas_edges_index_head",
AMDUATD_DOCTOR_SKIPPED,
"invalid pointer names");
(void)amduatd_space_doctor_report_add(out_report,
"cas_edges_collection_snapshot_head",
AMDUATD_DOCTOR_SKIPPED,
"invalid pointer names");
(void)amduatd_space_doctor_report_add(out_report,
"cas_edges_collection_log_head",
AMDUATD_DOCTOR_SKIPPED,
"invalid pointer names");
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_state_parse",
AMDUATD_DOCTOR_SKIPPED,
"invalid pointer names");
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_graph_ref",
AMDUATD_DOCTOR_SKIPPED,
"invalid pointer names");
goto doctor_index_checks;
}
pointers[0].pointer_name = amduatd_doctor_strdup(
(const char *)edges_index_head.data);
pointers[1].pointer_name = amduatd_doctor_strdup(collection_head);
pointers[2].pointer_name = amduatd_doctor_strdup(collection_log_head);
if (pointers[0].pointer_name == NULL ||
pointers[1].pointer_name == NULL ||
pointers[2].pointer_name == NULL) {
(void)amduatd_space_doctor_report_add(out_report,
"pointer_store",
AMDUATD_DOCTOR_FAIL,
"oom");
goto doctor_cleanup;
}
for (i = 0u; i < 3u; ++i) {
bool exists = false;
amduat_asl_pointer_error_t perr =
amduat_asl_pointer_get(pointer_store,
pointers[i].pointer_name,
&exists,
&pointers[i].pointer_ref);
if (perr != AMDUAT_ASL_POINTER_OK) {
char detail[256];
snprintf(detail, sizeof(detail), "pointer error: %s",
pointers[i].pointer_name);
(void)amduatd_space_doctor_report_add(out_report,
pointers[i].label,
AMDUATD_DOCTOR_FAIL,
detail);
continue;
}
pointers[i].pointer_exists = exists;
if (exists) {
char detail[256];
snprintf(detail, sizeof(detail), "present: %s",
pointers[i].pointer_name);
(void)amduatd_space_doctor_report_add(out_report,
pointers[i].label,
AMDUATD_DOCTOR_OK,
detail);
} else {
char detail[256];
snprintf(detail, sizeof(detail), "missing: %s",
pointers[i].pointer_name);
(void)amduatd_space_doctor_report_add(out_report,
pointers[i].label,
AMDUATD_DOCTOR_WARN,
detail);
}
}
for (i = 0u; i < 3u; ++i) {
const char *check_name = NULL;
switch (i) {
case 0u:
check_name = "cas_edges_index_head";
break;
case 1u:
check_name = "cas_edges_collection_snapshot_head";
break;
case 2u:
check_name = "cas_edges_collection_log_head";
break;
default:
check_name = "cas_pointer_ref";
break;
}
if (!pointers[i].pointer_exists) {
(void)amduatd_space_doctor_report_add(out_report,
check_name,
AMDUATD_DOCTOR_SKIPPED,
"pointer missing");
continue;
}
{
amduat_artifact_t artifact;
memset(&artifact, 0, sizeof(artifact));
if (amduat_asl_store_get(store, pointers[i].pointer_ref, &artifact) !=
AMDUAT_ASL_STORE_OK) {
(void)amduatd_space_doctor_report_add(out_report,
check_name,
AMDUATD_DOCTOR_FAIL,
"store get failed");
} else {
(void)amduatd_space_doctor_report_add(out_report,
check_name,
AMDUATD_DOCTOR_OK,
"ref readable");
}
amduat_artifact_free(&artifact);
}
}
if (!pointers[0].pointer_exists) {
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_state_parse",
AMDUATD_DOCTOR_SKIPPED,
"pointer missing");
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_graph_ref",
AMDUATD_DOCTOR_SKIPPED,
"pointer missing");
} else {
amduat_asl_record_t record;
amduatd_edge_index_state_view_t state;
const char *schema = "tgk/edge_index_state";
bool parsed = false;
memset(&record, 0, sizeof(record));
memset(&state, 0, sizeof(state));
if (amduat_asl_record_store_get(store,
pointers[0].pointer_ref,
&record) != AMDUAT_ASL_STORE_OK) {
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_state_parse",
AMDUATD_DOCTOR_FAIL,
"record load failed");
} else if (record.schema.len != strlen(schema) ||
memcmp(record.schema.data, schema, record.schema.len) != 0) {
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_state_parse",
AMDUATD_DOCTOR_FAIL,
"unexpected schema");
} else if (!amduatd_edge_index_state_view_decode(record.payload, &state)) {
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_state_parse",
AMDUATD_DOCTOR_FAIL,
"payload decode failed");
} else {
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_state_parse",
AMDUATD_DOCTOR_OK,
"edge index state decoded");
parsed = true;
}
if (parsed && state.has_graph_ref) {
amduat_artifact_t graph_artifact;
memset(&graph_artifact, 0, sizeof(graph_artifact));
if (amduat_asl_store_get(store, state.graph_ref, &graph_artifact) !=
AMDUAT_ASL_STORE_OK) {
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_graph_ref",
AMDUATD_DOCTOR_FAIL,
"graph ref missing");
} else {
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_graph_ref",
AMDUATD_DOCTOR_OK,
"graph ref readable");
}
amduat_artifact_free(&graph_artifact);
} else if (parsed) {
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_graph_ref",
AMDUATD_DOCTOR_SKIPPED,
"no graph ref");
} else {
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_graph_ref",
AMDUATD_DOCTOR_SKIPPED,
"edge index state unreadable");
}
amduatd_edge_index_state_view_free(&state);
amduat_asl_record_free(&record);
}
doctor_index_checks:
if (out_report->backend == AMDUATD_STORE_BACKEND_INDEX) {
amduat_asl_index_state_t state;
amduat_asl_log_record_t *records = NULL;
size_t record_count = 0u;
if (!amduat_asl_index_current_state(store, &state)) {
(void)amduatd_space_doctor_report_add(out_report,
"index_current_state",
AMDUATD_DOCTOR_FAIL,
"current_state failed");
} else {
(void)amduatd_space_doctor_report_add(out_report,
"index_current_state",
AMDUATD_DOCTOR_OK,
"current_state ok");
}
{
amduat_asl_store_error_t scan_err =
amduat_asl_log_scan(store, &records, &record_count);
if (scan_err == AMDUAT_ASL_STORE_OK) {
(void)amduatd_space_doctor_report_add(out_report,
"index_log_scan",
AMDUATD_DOCTOR_OK,
"log_scan ok");
} else {
(void)amduatd_space_doctor_report_add(out_report,
"index_log_scan",
AMDUATD_DOCTOR_FAIL,
"log_scan failed");
}
if (records != NULL) {
amduat_enc_asl_log_free(records, record_count);
}
}
} else {
(void)amduatd_space_doctor_report_add(out_report,
"index_current_state",
AMDUATD_DOCTOR_SKIPPED,
"requires index backend");
(void)amduatd_space_doctor_report_add(out_report,
"index_log_scan",
AMDUATD_DOCTOR_SKIPPED,
"requires index backend");
}
{
char detail[256];
bool fed_enabled = fed_cfg != NULL && fed_cfg->enabled;
bool require_index_backend = fed_enabled;
snprintf(detail, sizeof(detail),
"enabled=%s require_index_backend=%s",
fed_enabled ? "true" : "false",
require_index_backend ? "true" : "false");
(void)amduatd_space_doctor_report_add(out_report,
"federation",
AMDUATD_DOCTOR_OK,
detail);
}
ok = true;
doctor_cleanup:
for (i = 0u; i < 3u; ++i) {
amduatd_space_doctor_pointer_free(&pointers[i]);
}
free((void *)edges_collection.data);
free((void *)edges_index_head.data);
free(collection_head);
free(collection_log_head);
if (!ok) {
amduatd_space_doctor_report_clear(out_report);
}
return ok;
}
bool amduatd_space_doctor_report_json(
const amduatd_space_doctor_report_t *report,
char **out_json) {
amduatd_doctor_strbuf_t b;
char header[256];
if (out_json != NULL) {
*out_json = NULL;
}
if (report == NULL || out_json == NULL) {
return false;
}
memset(&b, 0, sizeof(b));
if (!amduatd_doctor_strbuf_append_cstr(&b, "{")) {
amduatd_doctor_strbuf_free(&b);
return false;
}
if (!amduatd_doctor_strbuf_append_cstr(&b, "\"effective_space\":{")) {
amduatd_doctor_strbuf_free(&b);
return false;
}
if (report->scoped) {
if (snprintf(header, sizeof(header),
"\"mode\":\"scoped\",\"space_id\":\"%s\"",
report->space_id) <= 0 ||
!amduatd_doctor_strbuf_append_cstr(&b, header)) {
amduatd_doctor_strbuf_free(&b);
return false;
}
} else {
if (!amduatd_doctor_strbuf_append_cstr(&b, "\"mode\":\"unscoped\"")) {
amduatd_doctor_strbuf_free(&b);
return false;
}
}
if (!amduatd_doctor_strbuf_append_cstr(&b, "},")) {
amduatd_doctor_strbuf_free(&b);
return false;
}
if (snprintf(header, sizeof(header),
"\"store_backend\":\"%s\",",
amduatd_store_backend_name(report->backend)) <= 0 ||
!amduatd_doctor_strbuf_append_cstr(&b, header)) {
amduatd_doctor_strbuf_free(&b);
return false;
}
if (!amduatd_doctor_strbuf_append_cstr(&b, "\"checks\":[")) {
amduatd_doctor_strbuf_free(&b);
return false;
}
for (size_t i = 0u; i < report->checks_len; ++i) {
const amduatd_space_doctor_check_t *check = &report->checks[i];
if (i != 0u) {
(void)amduatd_doctor_strbuf_append_char(&b, ',');
}
if (!amduatd_doctor_strbuf_append_cstr(&b, "{\"name\":\"") ||
!amduatd_doctor_strbuf_append_cstr(&b, check->name) ||
!amduatd_doctor_strbuf_append_cstr(&b, "\",\"status\":\"") ||
!amduatd_doctor_strbuf_append_cstr(
&b,
amduatd_space_doctor_status_name(check->status)) ||
!amduatd_doctor_strbuf_append_cstr(&b, "\",\"detail\":\"") ||
!amduatd_doctor_strbuf_append_cstr(&b, check->detail) ||
!amduatd_doctor_strbuf_append_cstr(&b, "\"}")) {
amduatd_doctor_strbuf_free(&b);
return false;
}
}
if (!amduatd_doctor_strbuf_append_cstr(&b, "],")) {
amduatd_doctor_strbuf_free(&b);
return false;
}
if (snprintf(header, sizeof(header),
"\"summary\":{"
"\"ok_count\":%llu,"
"\"warn_count\":%llu,"
"\"fail_count\":%llu,"
"\"skipped_count\":%llu"
"}}\n",
(unsigned long long)report->ok_count,
(unsigned long long)report->warn_count,
(unsigned long long)report->fail_count,
(unsigned long long)report->skipped_count) <= 0 ||
!amduatd_doctor_strbuf_append_cstr(&b, header)) {
amduatd_doctor_strbuf_free(&b);
return false;
}
*out_json = b.data;
return true;
}