1 #include "native_report.h"
16 #define FRIIDUMP_MKDIR(path) _mkdir(path)
17 #define FRIIDUMP_ACCESS(path) _access((path), 0)
18 #define FRIIDUMP_FILENO(file) _fileno(file)
19 #define FRIIDUMP_FSYNC(fd) _commit(fd)
23 #include <sys/types.h>
25 #define FRIIDUMP_MKDIR(path) mkdir((path), 0775)
26 #define FRIIDUMP_ACCESS(path) access((path), F_OK)
27 #define FRIIDUMP_FILENO(file) fileno(file)
28 #define FRIIDUMP_FSYNC(fd) fsync(fd)
31 static void report_copy(char *dst, size_t dst_size, const char *src) {
32 if (!dst || dst_size == 0)
36 snprintf(dst, dst_size, "%s", src);
39 static void report_error(char *dst, size_t dst_size, const char *message) {
40 report_copy(dst, dst_size, message ? message : "Unknown report error");
43 static bool report_valid_hex(const char *value, size_t length) {
45 if (!value || strlen(value) != length)
47 for (i = 0; i < length; i++) {
48 if (!isxdigit((unsigned char) value[i]))
55 static bool report_string_in_set(const char *value,
56 const char *const *values,
62 for (i = 0; i < value_count; i++) {
63 if (strcmp(value, values[i]) == 0)
69 static bool report_valid_uuid(const char *value) {
70 static const size_t hyphens[] = {8, 13, 18, 23};
75 if (!value || strlen(value) != 36)
80 for (i = 0; i < 36; i++) {
81 if (h < sizeof(hyphens) / sizeof(hyphens[0]) && i == hyphens[h]) {
87 if (!isxdigit((unsigned char) value[i]))
95 if (value[14] < '1' || value[14] > '5')
97 if (value[19] != '8' && value[19] != '9' &&
98 value[19] != 'a' && value[19] != 'A' &&
99 value[19] != 'b' && value[19] != 'B')
104 static bool report_valid_utc_timestamp(const char *value) {
107 if (!value || strlen(value) != 20)
109 for (i = 0; i < 20; i++) {
110 if (i == 4 || i == 7) {
113 } else if (i == 10) {
116 } else if (i == 13 || i == 16) {
119 } else if (i == 19) {
122 } else if (!isdigit((unsigned char) value[i])) {
129 static void report_utc_now(char *out, size_t out_size) {
133 if (!out || out_size == 0)
138 if (gmtime_s(&utc_tm, &now) != 0) {
143 if (!gmtime_r(&now, &utc_tm)) {
148 strftime(out, out_size, "%Y-%m-%dT%H:%M:%SZ", &utc_tm);
152 static bool report_random_bytes(unsigned char *bytes, size_t length) {
157 fd = open("/dev/urandom", O_RDONLY);
162 while (total < length) {
163 got = read(fd, bytes + total, length - total);
168 total += (size_t) got;
175 static bool report_uuid(char out[37]) {
180 status = CoCreateGuid(&guid);
185 "%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
186 (unsigned long) guid.Data1,
187 (unsigned int) guid.Data2,
188 (unsigned int) guid.Data3,
189 (unsigned int) guid.Data4[0],
190 (unsigned int) guid.Data4[1],
191 (unsigned int) guid.Data4[2],
192 (unsigned int) guid.Data4[3],
193 (unsigned int) guid.Data4[4],
194 (unsigned int) guid.Data4[5],
195 (unsigned int) guid.Data4[6],
196 (unsigned int) guid.Data4[7]);
201 if (!report_random_bytes(b, sizeof(b)))
204 b[6] = (unsigned char) ((b[6] & 0x0fU) | 0x40U);
205 b[8] = (unsigned char) ((b[8] & 0x3fU) | 0x80U);
208 "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
209 b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
210 b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]);
215 static bool report_directory_exists(const char *path) {
217 if (!path || !path[0])
219 if (stat(path, &st) != 0)
222 return (st.st_mode & _S_IFDIR) != 0;
224 return S_ISDIR(st.st_mode);
228 static bool report_make_directory(const char *path) {
229 char tmp[FRIIDUMP_REPORT_PATH_MAX];
232 if (!path || !path[0])
234 if (report_directory_exists(path))
237 report_copy(tmp, sizeof(tmp), path);
238 for (i = 1; tmp[i]; i++) {
239 if (tmp[i] != '/' && tmp[i] != '\\')
242 if (i == 2 && tmp[1] == ':')
248 if (tmp[0] && !report_directory_exists(tmp)) {
249 if (FRIIDUMP_MKDIR(tmp) != 0 && errno != EEXIST)
256 if (!report_directory_exists(tmp)) {
257 if (FRIIDUMP_MKDIR(tmp) != 0 && errno != EEXIST)
260 return report_directory_exists(tmp);
263 static char report_path_separator(const char *directory) {
273 static const char *report_path_leaf(const char *path) {
275 const char *backslash;
280 slash = strrchr(path, '/');
281 backslash = strrchr(path, '\\');
283 if (slash && slash + 1 > leaf)
285 if (backslash && backslash + 1 > leaf)
286 leaf = backslash + 1;
290 static bool report_path_parent(const char *path,
292 size_t output_size) {
296 if (!path || !path[0] || !output || output_size == 0)
299 leaf = report_path_leaf(path);
303 /* Keep the final separator. This preserves POSIX root paths, Windows
304 * drive roots, UNC paths, and relative subdirectories without needing
305 * platform-specific special cases. report_join_path() accepts a trailing
306 * separator and will not add another one. */
307 length = (size_t) (leaf - path);
308 if (length == 0 || length >= output_size)
311 memcpy(output, path, length);
312 output[length] = '\0';
316 static bool report_ends_with_case_insensitive(const char *value,
317 const char *suffix) {
319 size_t suffix_length;
322 if (!value || !suffix)
324 value_length = strlen(value);
325 suffix_length = strlen(suffix);
326 if (suffix_length > value_length)
328 value += value_length - suffix_length;
329 for (i = 0; i < suffix_length; i++) {
330 if (tolower((unsigned char) value[i]) !=
331 tolower((unsigned char) suffix[i]))
337 static bool report_join_path(const char *directory,
340 size_t output_size) {
341 size_t directory_length;
344 if (!directory || !directory[0] || !leaf || !leaf[0] ||
345 !output || output_size == 0)
348 directory_length = strlen(directory);
349 separator = report_path_separator(directory);
350 return snprintf(output, output_size,
353 (directory_length > 0 &&
354 directory[directory_length - 1] != '/' &&
355 directory[directory_length - 1] != '\\')
356 ? (separator == '\\' ? "\\" : "/")
358 leaf) < (int) output_size;
361 static void report_sanitize_leaf(const char *input, char *output, size_t output_size) {
363 bool previous_separator;
366 if (!output || output_size == 0)
373 previous_separator = false;
374 while (*input && used + 1 < output_size && used < 96) {
375 c = (unsigned char) *input++;
376 if (isalnum(c) || c == '-' || c == '_') {
377 output[used++] = (char) c;
378 previous_separator = false;
379 } else if (c == ' ' || c == '.' || c == '+' || c == '(' || c == ')' || c == '[' || c == ']') {
380 if (!previous_separator && used > 0) {
381 output[used++] = '_';
382 previous_separator = true;
386 while (used > 0 && output[used - 1] == '_')
391 static bool report_derived_leaf(const friidump_native_report *report,
395 size_t error_text_size) {
400 if (!report || !leaf || leaf_size == 0)
404 if (report->explicit_path) {
405 basis = report_path_leaf(report->requested_path);
407 report_error(error_text, error_text_size,
408 "Native report pathname has no filename");
411 if (strlen(basis) >= leaf_size) {
412 report_error(error_text, error_text_size,
413 "Native report filename is too long");
416 report_copy(leaf, leaf_size, basis);
420 basis = report_path_leaf(report->name_basis);
422 if (strlen(basis) >= leaf_size) {
423 report_error(error_text, error_text_size,
424 "Derived native report filename is too long");
427 report_copy(leaf, leaf_size, basis);
428 if (report_ends_with_case_insensitive(leaf, ".log"))
429 leaf[strlen(leaf) - 4] = '\0';
431 report_copy(leaf, leaf_size, "friidump");
433 basis = report->have_title
435 : (report->drive_known ? report->model : "friidump");
436 report_sanitize_leaf(basis, fallback, sizeof(fallback));
437 report_copy(leaf, leaf_size, fallback[0] ? fallback : "friidump");
440 length = strlen(leaf);
441 if (length + strlen(".friidump.json") >= leaf_size) {
442 report_error(error_text, error_text_size,
443 "Derived native report filename is too long");
446 strcat(leaf, ".friidump.json");
450 static bool report_collision_path(const char *path,
453 size_t output_size) {
454 static const char suffix[] = ".friidump.json";
458 if (!path || !path[0] || !run_id || !run_id[0] ||
459 !output || output_size == 0)
462 path_length = strlen(path);
463 stem_length = path_length;
464 if (report_ends_with_case_insensitive(path, suffix))
465 stem_length -= strlen(suffix);
467 if (stem_length + 1 + strlen(run_id) + strlen(suffix) + 1 > output_size)
470 memcpy(output, path, stem_length);
471 output[stem_length] = '\0';
473 strcat(output, run_id);
474 strcat(output, suffix);
478 static bool report_resolve_path(friidump_native_report *report,
480 size_t error_text_size) {
481 char leaf[FRIIDUMP_REPORT_PATH_MAX];
482 char basis_directory[FRIIDUMP_REPORT_PATH_MAX];
484 if (!report_derived_leaf(report, leaf, sizeof(leaf),
485 error_text, error_text_size))
488 if (report->explicit_path && !report->directory_override) {
489 report_copy(report->final_path, sizeof(report->final_path),
490 report->requested_path);
491 return report->final_path[0] != '\0';
494 if (report->directory_override) {
495 if (!report_make_directory(report->report_dir)) {
496 report_error(error_text, error_text_size,
497 "Could not create or access report directory");
500 if (!report_join_path(report->report_dir, leaf,
502 sizeof(report->final_path))) {
503 report_error(error_text, error_text_size,
504 "Generated report path is too long");
510 /* By default, keep the report beside the final FriiDump log (or the
511 * output path used as its fallback naming basis). A basename-only log
512 * still resolves in the current working directory. */
513 if (report_path_parent(report->name_basis,
515 sizeof(basis_directory))) {
516 if (!report_join_path(basis_directory, leaf,
518 sizeof(report->final_path))) {
519 report_error(error_text, error_text_size,
520 "Generated report path is too long");
526 report_copy(report->final_path, sizeof(report->final_path), leaf);
527 return report->final_path[0] != '\0';
530 static size_t report_utf8_sequence_length(const unsigned char *p) {
535 if (p[0] >= 0xc2 && p[0] <= 0xdf &&
536 p[1] >= 0x80 && p[1] <= 0xbf)
539 p[1] >= 0xa0 && p[1] <= 0xbf &&
540 p[2] >= 0x80 && p[2] <= 0xbf)
542 if (((p[0] >= 0xe1 && p[0] <= 0xec) ||
543 (p[0] >= 0xee && p[0] <= 0xef)) &&
544 p[1] >= 0x80 && p[1] <= 0xbf &&
545 p[2] >= 0x80 && p[2] <= 0xbf)
548 p[1] >= 0x80 && p[1] <= 0x9f &&
549 p[2] >= 0x80 && p[2] <= 0xbf)
552 p[1] >= 0x90 && p[1] <= 0xbf &&
553 p[2] >= 0x80 && p[2] <= 0xbf &&
554 p[3] >= 0x80 && p[3] <= 0xbf)
556 if (p[0] >= 0xf1 && p[0] <= 0xf3 &&
557 p[1] >= 0x80 && p[1] <= 0xbf &&
558 p[2] >= 0x80 && p[2] <= 0xbf &&
559 p[3] >= 0x80 && p[3] <= 0xbf)
562 p[1] >= 0x80 && p[1] <= 0x8f &&
563 p[2] >= 0x80 && p[2] <= 0xbf &&
564 p[3] >= 0x80 && p[3] <= 0xbf)
569 static void report_json_string(FILE *f, const char *value) {
570 const unsigned char *p;
575 for (p = (const unsigned char *) value; *p; p++) {
577 utf8_length = report_utf8_sequence_length(p);
578 if (utf8_length > 0) {
579 fwrite(p, 1, utf8_length, f);
580 p += utf8_length - 1;
582 fprintf(f, "\\u%04x", (unsigned int) *p);
587 case '\\': fputs("\\\\", f); break;
588 case '"': fputs("\\\"", f); break;
589 case '\b': fputs("\\b", f); break;
590 case '\f': fputs("\\f", f); break;
591 case '\n': fputs("\\n", f); break;
592 case '\r': fputs("\\r", f); break;
593 case '\t': fputs("\\t", f); break;
596 fprintf(f, "\\u%04x", (unsigned int) *p);
606 static void report_json_nullable_string(FILE *f, const char *value, bool present) {
608 report_json_string(f, value);
613 static void report_json_nullable_u64(FILE *f, uint64_t value, bool present) {
615 fprintf(f, "%llu", (unsigned long long) value);
620 static void report_json_nullable_double(FILE *f, double value, bool present) {
622 fprintf(f, "%.3f", value < 0.0 ? 0.0 : value);
627 static bool report_write_document(FILE *f, const friidump_native_report *r) {
629 const friidump_native_artifact *artifact;
632 fputs(" \"schema\": \"friidump-test-result.v1\",\n", f);
633 fputs(" \"format\": {\n", f);
634 fputs(" \"name\": \"FriiDump Interchange Format\",\n", f);
635 fputs(" \"generation\": 1,\n", f);
636 fputs(" \"serialization\": \"json\"\n", f);
638 fputs(" \"generated_utc\": ", f); report_json_string(f, r->generated_utc); fputs(",\n", f);
639 fputs(" \"generator\": {\n", f);
640 fputs(" \"name\": \"FriiDump\",\n", f);
641 fputs(" \"version\": ", f); report_json_string(f, r->generator_version); fputs(",\n", f);
642 fputs(" \"build_commit\": ", f); report_json_nullable_string(f, r->build_commit, r->build_commit_known); fputs("\n", f);
644 fputs(" \"run\": {\n", f);
645 fputs(" \"run_id\": ", f); report_json_string(f, r->run_id); fputs(",\n", f);
646 fputs(" \"started_utc\": ", f); report_json_nullable_string(f, r->started_utc, r->started_utc[0] != '\0'); fputs(",\n", f);
647 fputs(" \"completed_utc\": ", f); report_json_nullable_string(f, r->completed_utc, r->completed_utc[0] != '\0'); fputs(",\n", f);
648 fputs(" \"test_type\": ", f); report_json_string(f, r->test_type); fputs(",\n", f);
649 fputs(" \"result\": ", f); report_json_string(f, r->run_result); fputs("\n", f);
651 fputs(" \"drive\": {\n", f);
652 fputs(" \"vendor\": ", f); report_json_string(f, r->vendor); fputs(",\n", f);
653 fputs(" \"model\": ", f); report_json_string(f, r->model); fputs(",\n", f);
654 fputs(" \"firmware_revision\": ", f); report_json_string(f, r->firmware_revision); fputs(",\n", f);
655 fputs(" \"interface\": ", f); report_json_string(f, r->interface_name); fputs(",\n", f);
656 fputs(" \"device_path\": ", f); report_json_nullable_string(f, r->device_path, r->have_device_path); fputs(",\n", f);
657 fprintf(f, " \"firmware_modified\": %s,\n", r->firmware_modified ? "true" : "false");
658 fputs(" \"modification_note\": ", f); report_json_nullable_string(f, r->modification_note, r->have_modification_note); fputs("\n", f);
660 fputs(" \"media\": {\n", f);
661 fputs(" \"platform\": ", f); report_json_string(f, r->platform); fputs(",\n", f);
662 fputs(" \"title\": ", f); report_json_nullable_string(f, r->title, r->have_title); fputs(",\n", f);
663 fputs(" \"region\": ", f); report_json_nullable_string(f, r->region, r->have_region); fputs(",\n", f);
664 fputs(" \"disc_id\": ", f); report_json_nullable_string(f, r->disc_id, r->have_disc_id); fputs("\n", f);
666 fputs(" \"seed\": {\n", f);
667 fprintf(f, " \"attempted\": %s,\n", r->seed_attempted ? "true" : "false");
668 fputs(" \"result\": ", f); report_json_string(f, r->seed_result); fputs(",\n", f);
669 fputs(" \"duration_seconds\": ", f); report_json_nullable_double(f, r->seed_duration_seconds, r->have_seed_duration); fputs("\n", f);
671 fputs(" \"dump\": {\n", f);
672 fprintf(f, " \"attempted\": %s,\n", r->dump_attempted ? "true" : "false");
673 fputs(" \"result\": ", f); report_json_string(f, r->dump_result); fputs(",\n", f);
674 fputs(" \"sector_count\": ", f); report_json_nullable_u64(f, r->sector_count, r->have_sector_count); fputs(",\n", f);
675 fputs(" \"byte_count\": ", f); report_json_nullable_u64(f, r->byte_count, r->have_byte_count); fputs(",\n", f);
676 fputs(" \"duration_seconds\": ", f); report_json_nullable_double(f, r->dump_duration_seconds, r->have_dump_duration); fputs(",\n", f);
677 fputs(" \"failure_stage\": ", f); report_json_nullable_string(f, r->failure_stage, r->have_failure_stage); fputs(",\n", f);
678 fputs(" \"failure_sector\": ", f); report_json_nullable_u64(f, r->failure_sector, r->have_failure_sector); fputs(",\n", f);
679 fputs(" \"output_path\": ", f); report_json_nullable_string(f, r->output_path, r->have_output_path); fputs("\n", f);
681 fputs(" \"hashes\": {\n", f);
682 fputs(" \"crc32\": ", f); report_json_nullable_string(f, r->crc32, report_valid_hex(r->crc32, 8)); fputs(",\n", f);
683 fputs(" \"md5\": ", f); report_json_nullable_string(f, r->md5, report_valid_hex(r->md5, 32)); fputs(",\n", f);
684 fputs(" \"sha1\": ", f); report_json_nullable_string(f, r->sha1, report_valid_hex(r->sha1, 40)); fputs(",\n", f);
685 fputs(" \"sha256\": ", f); report_json_nullable_string(f, r->sha256, report_valid_hex(r->sha256, 64)); fputs("\n", f);
687 fputs(" \"reference\": {\n", f);
688 fputs(" \"provider\": ", f); report_json_nullable_string(f, r->reference_provider, r->have_reference_provider); fputs(",\n", f);
689 fputs(" \"result\": ", f); report_json_string(f, r->reference_result); fputs(",\n", f);
690 fputs(" \"reference_id\": ", f); report_json_nullable_string(f, r->reference_id, r->have_reference_id); fputs("\n", f);
692 fputs(" \"profile\": {\n", f);
693 fputs(" \"support_tier\": ", f); report_json_nullable_string(f, r->support_tier, r->have_support_tier); fputs(",\n", f);
694 fputs(" \"cdb_offset\": ", f); report_json_nullable_string(f, r->cdb_offset, r->have_cdb_offset); fputs(",\n", f);
695 fputs(" \"gate_address\": ", f); report_json_nullable_string(f, r->gate_address, r->have_gate_address); fputs("\n", f);
697 fputs(" \"notes\": [", f);
698 if (r->note_count > 0)
700 for (i = 0; i < r->note_count; i++) {
702 report_json_string(f, r->notes[i]);
703 fputs((i + 1 < r->note_count) ? ",\n" : "\n", f);
705 fputs(r->note_count > 0 ? " ],\n" : "],\n", f);
706 fputs(" \"artifacts\": [", f);
707 if (r->artifact_count > 0)
709 for (i = 0; i < r->artifact_count; i++) {
710 artifact = &r->artifacts[i];
712 fputs(" \"type\": ", f); report_json_string(f, artifact->type); fputs(",\n", f);
713 fputs(" \"path\": ", f); report_json_nullable_string(f, artifact->path, artifact->have_path); fputs(",\n", f);
714 fputs(" \"bytes\": ", f); report_json_nullable_u64(f, artifact->bytes, artifact->have_bytes); fputs(",\n", f);
715 fputs(" \"sha256\": ", f); report_json_nullable_string(f, artifact->sha256, artifact->have_sha256); fputs("\n", f);
716 fputs((i + 1 < r->artifact_count) ? " },\n" : " }\n", f);
718 fputs(r->artifact_count > 0 ? " ]\n" : "]\n", f);
721 return ferror(f) == 0;
724 void friidump_native_report_init(friidump_native_report *report, const char *generator_version) {
727 memset(report, 0, sizeof(*report));
728 report_copy(report->generator_version, sizeof(report->generator_version), generator_version ? generator_version : "unknown");
729 report_copy(report->test_type, sizeof(report->test_type), "other");
730 report_copy(report->run_result, sizeof(report->run_result), "fail");
731 report_copy(report->platform, sizeof(report->platform), "unknown");
732 report_copy(report->seed_result, sizeof(report->seed_result), "not_attempted");
733 report_copy(report->dump_result, sizeof(report->dump_result), "not_attempted");
734 report_copy(report->reference_result, sizeof(report->reference_result), "not_checked");
735 report_utc_now(report->started_utc, sizeof(report->started_utc));
736 if (!report_uuid(report->run_id))
737 report->run_id[0] = '\0';
740 bool friidump_native_report_enable_default(friidump_native_report *report) {
743 report->enabled = true;
747 bool friidump_native_report_enable_path(friidump_native_report *report, const char *path) {
748 if (!report || !path || !path[0] || strlen(path) >= sizeof(report->requested_path))
750 report->enabled = true;
751 report->explicit_path = true;
752 report_copy(report->requested_path, sizeof(report->requested_path), path);
756 bool friidump_native_report_enable_dir(friidump_native_report *report, const char *directory) {
757 if (!report || !directory || !directory[0] || strlen(directory) >= sizeof(report->report_dir))
759 report->enabled = true;
760 report->directory_override = true;
761 report_copy(report->report_dir, sizeof(report->report_dir), directory);
765 bool friidump_native_report_set_name_basis(friidump_native_report *report,
767 if (!report || !path || !path[0] || strlen(path) >= sizeof(report->name_basis))
769 report_copy(report->name_basis, sizeof(report->name_basis), path);
773 bool friidump_native_report_is_enabled(const friidump_native_report *report) {
774 return report && report->enabled;
777 void friidump_native_report_set_build_commit(friidump_native_report *report, const char *commit) {
780 report->build_commit_known = report_valid_hex(commit, 40);
781 report_copy(report->build_commit, sizeof(report->build_commit), report->build_commit_known ? commit : "");
784 void friidump_native_report_set_drive(friidump_native_report *report,
787 const char *firmware_revision,
788 const char *interface_name,
789 const char *device_path) {
792 report_copy(report->vendor, sizeof(report->vendor), vendor ? vendor : "");
793 report_copy(report->model, sizeof(report->model), model ? model : "");
794 report_copy(report->firmware_revision, sizeof(report->firmware_revision), firmware_revision ? firmware_revision : "");
795 report_copy(report->interface_name, sizeof(report->interface_name), interface_name ? interface_name : "");
796 report->have_device_path = device_path && device_path[0];
797 report_copy(report->device_path, sizeof(report->device_path), report->have_device_path ? device_path : "");
798 report->drive_known = report->model[0] != '\0' && report->firmware_revision[0] != '\0';
801 void friidump_native_report_set_firmware_modified(friidump_native_report *report,
806 report->firmware_modified = modified;
807 report->have_modification_note = note && note[0] && strlen(note) <= 1000;
808 report_copy(report->modification_note, sizeof(report->modification_note), report->have_modification_note ? note : "");
811 void friidump_native_report_set_media(friidump_native_report *report,
812 const char *platform,
815 const char *disc_id) {
818 report_copy(report->platform, sizeof(report->platform), platform && platform[0] ? platform : "unknown");
819 report->have_title = title && title[0];
820 report_copy(report->title, sizeof(report->title), report->have_title ? title : "");
821 report->have_region = region && region[0];
822 report_copy(report->region, sizeof(report->region), report->have_region ? region : "");
823 report->have_disc_id = disc_id && disc_id[0];
824 report_copy(report->disc_id, sizeof(report->disc_id), report->have_disc_id ? disc_id : "");
827 void friidump_native_report_set_profile(friidump_native_report *report,
828 const char *support_tier,
830 uint32_t gate_address) {
833 report->have_support_tier = support_tier && support_tier[0] && strcmp(support_tier, "none") != 0;
834 report_copy(report->support_tier, sizeof(report->support_tier), report->have_support_tier ? support_tier : "");
835 report->have_cdb_offset = cdb_offset != 0;
836 report->have_gate_address = gate_address != 0;
837 if (report->have_cdb_offset)
838 snprintf(report->cdb_offset, sizeof(report->cdb_offset), "0x%x", (unsigned int) cdb_offset);
839 if (report->have_gate_address)
840 snprintf(report->gate_address, sizeof(report->gate_address), "0x%x", (unsigned int) gate_address);
843 void friidump_native_report_set_seed(friidump_native_report *report,
847 double duration_seconds) {
850 report->seed_attempted = attempted;
851 report_copy(report->seed_result, sizeof(report->seed_result), result ? result : "not_attempted");
852 report->have_seed_duration = have_duration;
853 report->seed_duration_seconds = duration_seconds;
856 void friidump_native_report_set_dump(friidump_native_report *report,
859 bool have_sector_count,
860 uint64_t sector_count,
861 bool have_byte_count,
864 double duration_seconds,
865 const char *failure_stage,
866 bool have_failure_sector,
867 uint64_t failure_sector,
868 const char *output_path) {
871 report->dump_attempted = attempted;
872 report_copy(report->dump_result, sizeof(report->dump_result), result ? result : "not_attempted");
873 report->have_sector_count = have_sector_count;
874 report->sector_count = sector_count;
875 report->have_byte_count = have_byte_count;
876 report->byte_count = byte_count;
877 report->have_dump_duration = have_duration;
878 report->dump_duration_seconds = duration_seconds;
879 report->have_failure_stage = failure_stage && failure_stage[0];
880 report_copy(report->failure_stage, sizeof(report->failure_stage), report->have_failure_stage ? failure_stage : "");
881 report->have_failure_sector = have_failure_sector;
882 report->failure_sector = failure_sector;
883 report->have_output_path = output_path && output_path[0];
884 report_copy(report->output_path, sizeof(report->output_path), report->have_output_path ? output_path : "");
887 void friidump_native_report_set_hashes(friidump_native_report *report,
891 const char *sha256) {
894 report_copy(report->crc32, sizeof(report->crc32), crc32 ? crc32 : "");
895 report_copy(report->md5, sizeof(report->md5), md5 ? md5 : "");
896 report_copy(report->sha1, sizeof(report->sha1), sha1 ? sha1 : "");
897 report_copy(report->sha256, sizeof(report->sha256), sha256 ? sha256 : "");
900 void friidump_native_report_set_reference(friidump_native_report *report,
901 const char *provider,
903 const char *reference_id) {
906 report->have_reference_provider = provider && provider[0];
907 report_copy(report->reference_provider, sizeof(report->reference_provider), report->have_reference_provider ? provider : "");
908 report_copy(report->reference_result, sizeof(report->reference_result), result ? result : "not_checked");
909 report->have_reference_id = reference_id && reference_id[0];
910 report_copy(report->reference_id, sizeof(report->reference_id), report->have_reference_id ? reference_id : "");
913 void friidump_native_report_set_outcome(friidump_native_report *report,
914 const char *test_type,
915 const char *run_result) {
918 report_copy(report->test_type, sizeof(report->test_type), test_type ? test_type : "other");
919 report_copy(report->run_result, sizeof(report->run_result), run_result ? run_result : "fail");
922 bool friidump_native_report_add_note(friidump_native_report *report, const char *note) {
923 if (!report || !note || !note[0] ||
924 strlen(note) > FRIIDUMP_REPORT_TEXT_MAX ||
925 report->note_count >= FRIIDUMP_REPORT_NOTE_MAX)
927 report_copy(report->notes[report->note_count], sizeof(report->notes[report->note_count]), note);
928 report->note_count++;
932 bool friidump_native_report_add_artifact(friidump_native_report *report,
937 const char *sha256) {
938 friidump_native_artifact *artifact;
940 if (!report || !type || !type[0] || strlen(type) > 80 ||
941 report->artifact_count >= FRIIDUMP_REPORT_ARTIFACT_MAX)
943 if (path && strlen(path) > FRIIDUMP_REPORT_PATH_MAX)
945 if (sha256 && sha256[0] && !report_valid_hex(sha256, 64))
948 artifact = &report->artifacts[report->artifact_count];
949 memset(artifact, 0, sizeof(*artifact));
950 report_copy(artifact->type, sizeof(artifact->type), type);
951 artifact->have_path = path && path[0];
952 report_copy(artifact->path, sizeof(artifact->path), artifact->have_path ? path : "");
953 artifact->have_bytes = have_bytes;
954 artifact->bytes = bytes;
955 artifact->have_sha256 = sha256 && sha256[0];
956 report_copy(artifact->sha256, sizeof(artifact->sha256), artifact->have_sha256 ? sha256 : "");
957 report->artifact_count++;
962 static bool report_validate_for_write(const friidump_native_report *report,
964 size_t error_text_size) {
965 static const char *const platforms[] = {
966 "gamecube", "wii", "xbox", "dvd", "unknown"
968 static const char *const test_types[] = {
969 "full_dump", "seed_only", "partial_dump", "diagnostic_no_media",
970 "diagnostic_wrong_media", "media_transition", "other"
972 static const char *const run_results[] = {
973 "pass", "partial", "fail", "not_applicable"
975 static const char *const step_results[] = {
976 "pass", "partial", "fail", "not_attempted", "not_applicable"
978 static const char *const reference_results[] = {
979 "match", "mismatch", "not_checked", "not_applicable"
982 if (!report->generator_version[0]) {
983 report_error(error_text, error_text_size, "Generator version is empty");
986 if (!report_valid_uuid(report->run_id)) {
987 report_error(error_text, error_text_size, "Run UUID is missing or invalid");
990 if (!report_valid_utc_timestamp(report->started_utc) ||
991 !report_valid_utc_timestamp(report->completed_utc) ||
992 !report_valid_utc_timestamp(report->generated_utc)) {
993 report_error(error_text, error_text_size, "Report timestamps are missing or invalid");
996 if (strcmp(report->completed_utc, report->started_utc) < 0) {
997 report_error(error_text, error_text_size, "Completion timestamp precedes start timestamp");
1000 if (!report->drive_known) {
1001 report_error(error_text, error_text_size,
1002 "Drive identity is incomplete; no database-compatible report was written");
1005 if (report->firmware_modified && !report->have_modification_note) {
1006 report_error(error_text, error_text_size,
1007 "Modified firmware requires a modification note");
1010 if (!report_string_in_set(report->platform, platforms,
1011 sizeof(platforms) / sizeof(platforms[0])) ||
1012 !report_string_in_set(report->test_type, test_types,
1013 sizeof(test_types) / sizeof(test_types[0])) ||
1014 !report_string_in_set(report->run_result, run_results,
1015 sizeof(run_results) / sizeof(run_results[0])) ||
1016 !report_string_in_set(report->seed_result, step_results,
1017 sizeof(step_results) / sizeof(step_results[0])) ||
1018 !report_string_in_set(report->dump_result, step_results,
1019 sizeof(step_results) / sizeof(step_results[0])) ||
1020 !report_string_in_set(report->reference_result, reference_results,
1021 sizeof(reference_results) / sizeof(reference_results[0]))) {
1022 report_error(error_text, error_text_size, "One or more report enum values are invalid");
1025 if ((report->have_seed_duration &&
1026 (!isfinite(report->seed_duration_seconds) || report->seed_duration_seconds < 0.0)) ||
1027 (report->have_dump_duration &&
1028 (!isfinite(report->dump_duration_seconds) || report->dump_duration_seconds < 0.0))) {
1029 report_error(error_text, error_text_size, "A report duration is negative or non-finite");
1032 if (report->note_count > FRIIDUMP_REPORT_NOTE_MAX ||
1033 report->artifact_count > FRIIDUMP_REPORT_ARTIFACT_MAX) {
1034 report_error(error_text, error_text_size,
1035 "Report note or artifact count exceeds the implementation limit");
1040 for (i = 0; i < report->note_count; i++) {
1041 if (!report->notes[i][0] ||
1042 strlen(report->notes[i]) > FRIIDUMP_REPORT_TEXT_MAX) {
1043 report_error(error_text, error_text_size,
1044 "One or more report notes are invalid");
1049 if (report->seed_attempted) {
1050 if (strcmp(report->seed_result, "pass") != 0 &&
1051 strcmp(report->seed_result, "partial") != 0 &&
1052 strcmp(report->seed_result, "fail") != 0) {
1053 report_error(error_text, error_text_size,
1054 "An attempted seed stage requires pass, partial, or fail");
1058 if ((strcmp(report->seed_result, "not_attempted") != 0 &&
1059 strcmp(report->seed_result, "not_applicable") != 0) ||
1060 report->have_seed_duration) {
1061 report_error(error_text, error_text_size,
1062 "A non-attempted seed stage cannot claim a result or duration");
1066 if (report->dump_attempted) {
1067 if (strcmp(report->dump_result, "pass") != 0 &&
1068 strcmp(report->dump_result, "partial") != 0 &&
1069 strcmp(report->dump_result, "fail") != 0) {
1070 report_error(error_text, error_text_size,
1071 "An attempted dump requires pass, partial, or fail");
1075 if ((strcmp(report->dump_result, "not_attempted") != 0 &&
1076 strcmp(report->dump_result, "not_applicable") != 0) ||
1077 report->have_sector_count || report->have_byte_count ||
1078 report->have_dump_duration || report->have_failure_stage ||
1079 report->have_failure_sector || report->have_output_path) {
1080 report_error(error_text, error_text_size,
1081 "A non-attempted dump cannot claim output, progress, failure, or duration");
1085 if (strcmp(report->dump_result, "pass") == 0 &&
1086 (strcmp(report->test_type, "full_dump") != 0 ||
1087 strcmp(report->run_result, "pass") != 0)) {
1088 report_error(error_text, error_text_size,
1089 "A successful dump requires a full_dump/pass run outcome");
1092 if (strcmp(report->dump_result, "partial") == 0 &&
1093 (strcmp(report->test_type, "partial_dump") != 0 ||
1094 strcmp(report->run_result, "partial") != 0)) {
1095 report_error(error_text, error_text_size,
1096 "A partial dump requires a partial_dump/partial run outcome");
1099 if (strcmp(report->dump_result, "fail") == 0 &&
1100 strcmp(report->run_result, "fail") != 0) {
1101 report_error(error_text, error_text_size,
1102 "A failed dump requires a failed run outcome");
1105 if (strcmp(report->test_type, "seed_only") == 0 &&
1106 (!report->seed_attempted || report->dump_attempted)) {
1107 report_error(error_text, error_text_size,
1108 "A seed_only run requires an attempted seed stage and no dump");
1111 if ((strcmp(report->test_type, "diagnostic_no_media") == 0 ||
1112 strcmp(report->test_type, "diagnostic_wrong_media") == 0 ||
1113 strcmp(report->test_type, "media_transition") == 0) &&
1114 (strcmp(report->run_result, "not_applicable") != 0 ||
1115 report->dump_attempted)) {
1116 report_error(error_text, error_text_size,
1117 "Diagnostic and media-transition runs must be not_applicable and must not dump");
1120 if (strcmp(report->dump_result, "pass") == 0 &&
1121 (!report->have_sector_count || !report->have_byte_count)) {
1122 report_error(error_text, error_text_size,
1123 "A successful dump requires sector and byte counts");
1126 if (strcmp(report->dump_result, "fail") == 0 && !report->have_failure_stage) {
1127 report_error(error_text, error_text_size,
1128 "A failed dump requires a failure stage");
1131 if (strcmp(report->reference_result, "match") == 0 &&
1132 !report->have_reference_provider) {
1133 report_error(error_text, error_text_size,
1134 "A reference match requires a provider");
1139 size_t dump_output_count = 0;
1140 const friidump_native_artifact *artifact;
1141 for (i = 0; i < report->artifact_count; i++) {
1142 artifact = &report->artifacts[i];
1143 if (!artifact->type[0] || strlen(artifact->type) > 80 ||
1144 (artifact->have_path && strlen(artifact->path) > FRIIDUMP_REPORT_PATH_MAX) ||
1145 (artifact->have_sha256 && !report_valid_hex(artifact->sha256, 64))) {
1146 report_error(error_text, error_text_size,
1147 "One or more artifact records are invalid");
1150 if (strcmp(artifact->type, "dump_output") == 0) {
1151 dump_output_count++;
1152 if (!artifact->have_path || !report->have_output_path ||
1153 strcmp(artifact->path, report->output_path) != 0 ||
1154 (report->have_byte_count &&
1155 (!artifact->have_bytes || artifact->bytes != report->byte_count)) ||
1156 (report_valid_hex(report->sha256, 64) &&
1157 (!artifact->have_sha256 ||
1158 strcmp(artifact->sha256, report->sha256) != 0))) {
1159 report_error(error_text, error_text_size,
1160 "The dump_output artifact does not match the reported output");
1165 if (report->dump_attempted && report->have_output_path &&
1166 dump_output_count != 1) {
1167 report_error(error_text, error_text_size,
1168 "A dump with an output path requires exactly one dump_output artifact");
1175 bool friidump_native_report_write(friidump_native_report *report,
1177 size_t written_path_size,
1179 size_t error_text_size) {
1181 char temporary_path[FRIIDUMP_REPORT_PATH_MAX + 64];
1184 if (written_path && written_path_size > 0)
1185 written_path[0] = '\0';
1186 if (error_text && error_text_size > 0)
1187 error_text[0] = '\0';
1189 if (!report || !report->enabled) {
1190 report_error(error_text, error_text_size, "Native report is not enabled");
1193 report_utc_now(report->completed_utc, sizeof(report->completed_utc));
1194 report_utc_now(report->generated_utc, sizeof(report->generated_utc));
1196 if (!report_validate_for_write(report, error_text, error_text_size))
1198 if (!report_resolve_path(report, error_text, error_text_size))
1200 if (FRIIDUMP_ACCESS(report->final_path) == 0) {
1201 if (report->explicit_path) {
1202 report_error(error_text, error_text_size,
1203 "Refusing to overwrite an existing native report");
1206 if (!report_collision_path(report->final_path,
1209 sizeof(report->final_path))) {
1210 report_error(error_text, error_text_size,
1211 "Could not generate a collision-safe native report path");
1214 if (FRIIDUMP_ACCESS(report->final_path) == 0) {
1215 report_error(error_text, error_text_size,
1216 "Collision-safe native report path already exists");
1221 if (snprintf(temporary_path, sizeof(temporary_path), "%s.tmp-%s",
1222 report->final_path, report->run_id) >= (int) sizeof(temporary_path)) {
1223 report_error(error_text, error_text_size, "Temporary report path is too long");
1227 if (FRIIDUMP_ACCESS(temporary_path) == 0 && remove(temporary_path) != 0) {
1228 report_error(error_text, error_text_size, "Could not remove a stale temporary report");
1232 f = fopen(temporary_path, "wb");
1234 report_error(error_text, error_text_size, "Could not open temporary native report");
1238 if (!report_write_document(f, report) || fflush(f) != 0 || FRIIDUMP_FSYNC(FRIIDUMP_FILENO(f)) != 0) {
1240 remove(temporary_path);
1241 report_error(error_text, error_text_size, "Could not write or flush native report");
1245 close_status = fclose(f);
1246 if (close_status != 0) {
1247 remove(temporary_path);
1248 report_error(error_text, error_text_size, "Could not close temporary native report");
1253 if (!MoveFileExA(temporary_path, report->final_path, MOVEFILE_WRITE_THROUGH)) {
1254 remove(temporary_path);
1255 report_error(error_text, error_text_size, "Could not atomically publish native report");
1259 if (link(temporary_path, report->final_path) != 0) {
1260 remove(temporary_path);
1261 if (errno == EEXIST)
1262 report_error(error_text, error_text_size, "Refusing to overwrite an existing native report");
1264 report_error(error_text, error_text_size, "Could not atomically publish native report");
1267 if (unlink(temporary_path) != 0) {
1268 remove(report->final_path);
1269 report_error(error_text, error_text_size, "Could not finalize the atomic native report publication");
1274 report_copy(written_path, written_path_size, report->final_path);