]> FriiDump Source - friidump.git/blob - src/native_report.c
FriiDump 0.5.3.16: finalize release identity and documentation
[friidump.git] / src / native_report.c
1 #include "native_report.h"
2
3 #include <ctype.h>
4 #include <errno.h>
5 #include <math.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <time.h>
10
11 #ifdef WIN32
12 #include <direct.h>
13 #include <io.h>
14 #include <objbase.h>
15 #include <sys/stat.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)
20 #else
21 #include <fcntl.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 #include <unistd.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)
29 #endif
30
31 static void report_copy(char *dst, size_t dst_size, const char *src) {
32     if (!dst || dst_size == 0)
33         return;
34     if (!src)
35         src = "";
36     snprintf(dst, dst_size, "%s", src);
37 }
38
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");
41 }
42
43 static bool report_valid_hex(const char *value, size_t length) {
44     size_t i;
45     if (!value || strlen(value) != length)
46         return false;
47     for (i = 0; i < length; i++) {
48         if (!isxdigit((unsigned char) value[i]))
49             return false;
50     }
51     return true;
52 }
53
54
55 static bool report_string_in_set(const char *value,
56                                  const char *const *values,
57                                  size_t value_count) {
58     size_t i;
59
60     if (!value)
61         return false;
62     for (i = 0; i < value_count; i++) {
63         if (strcmp(value, values[i]) == 0)
64             return true;
65     }
66     return false;
67 }
68
69 static bool report_valid_uuid(const char *value) {
70     static const size_t hyphens[] = {8, 13, 18, 23};
71     size_t i;
72     size_t h;
73     bool all_zero;
74
75     if (!value || strlen(value) != 36)
76         return false;
77
78     h = 0;
79     all_zero = true;
80     for (i = 0; i < 36; i++) {
81         if (h < sizeof(hyphens) / sizeof(hyphens[0]) && i == hyphens[h]) {
82             if (value[i] != '-')
83                 return false;
84             h++;
85             continue;
86         }
87         if (!isxdigit((unsigned char) value[i]))
88             return false;
89         if (value[i] != '0')
90             all_zero = false;
91     }
92
93     if (all_zero)
94         return false;
95     if (value[14] < '1' || value[14] > '5')
96         return false;
97     if (value[19] != '8' && value[19] != '9' &&
98         value[19] != 'a' && value[19] != 'A' &&
99         value[19] != 'b' && value[19] != 'B')
100         return false;
101     return true;
102 }
103
104 static bool report_valid_utc_timestamp(const char *value) {
105     size_t i;
106
107     if (!value || strlen(value) != 20)
108         return false;
109     for (i = 0; i < 20; i++) {
110         if (i == 4 || i == 7) {
111             if (value[i] != '-')
112                 return false;
113         } else if (i == 10) {
114             if (value[i] != 'T')
115                 return false;
116         } else if (i == 13 || i == 16) {
117             if (value[i] != ':')
118                 return false;
119         } else if (i == 19) {
120             if (value[i] != 'Z')
121                 return false;
122         } else if (!isdigit((unsigned char) value[i])) {
123             return false;
124         }
125     }
126     return true;
127 }
128
129 static void report_utc_now(char *out, size_t out_size) {
130     time_t now;
131     struct tm utc_tm;
132
133     if (!out || out_size == 0)
134         return;
135
136     now = time(NULL);
137 #ifdef WIN32
138     if (gmtime_s(&utc_tm, &now) != 0) {
139         out[0] = '\0';
140         return;
141     }
142 #else
143     if (!gmtime_r(&now, &utc_tm)) {
144         out[0] = '\0';
145         return;
146     }
147 #endif
148     strftime(out, out_size, "%Y-%m-%dT%H:%M:%SZ", &utc_tm);
149 }
150
151 #ifndef WIN32
152 static bool report_random_bytes(unsigned char *bytes, size_t length) {
153     int fd;
154     size_t total;
155     ssize_t got;
156
157     fd = open("/dev/urandom", O_RDONLY);
158     if (fd < 0)
159         return false;
160
161     total = 0;
162     while (total < length) {
163         got = read(fd, bytes + total, length - total);
164         if (got <= 0) {
165             close(fd);
166             return false;
167         }
168         total += (size_t) got;
169     }
170     close(fd);
171     return true;
172 }
173 #endif
174
175 static bool report_uuid(char out[37]) {
176 #ifdef WIN32
177     GUID guid;
178     HRESULT status;
179
180     status = CoCreateGuid(&guid);
181     if (FAILED(status))
182         return false;
183
184     snprintf(out, 37,
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]);
197     return true;
198 #else
199     unsigned char b[16];
200
201     if (!report_random_bytes(b, sizeof(b)))
202         return false;
203
204     b[6] = (unsigned char) ((b[6] & 0x0fU) | 0x40U);
205     b[8] = (unsigned char) ((b[8] & 0x3fU) | 0x80U);
206
207     snprintf(out, 37,
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]);
211     return true;
212 #endif
213 }
214
215 static bool report_directory_exists(const char *path) {
216     struct stat st;
217     if (!path || !path[0])
218         return false;
219     if (stat(path, &st) != 0)
220         return false;
221 #ifdef WIN32
222     return (st.st_mode & _S_IFDIR) != 0;
223 #else
224     return S_ISDIR(st.st_mode);
225 #endif
226 }
227
228 static bool report_make_directory(const char *path) {
229     char tmp[FRIIDUMP_REPORT_PATH_MAX];
230     size_t i;
231
232     if (!path || !path[0])
233         return false;
234     if (report_directory_exists(path))
235         return true;
236
237     report_copy(tmp, sizeof(tmp), path);
238     for (i = 1; tmp[i]; i++) {
239         if (tmp[i] != '/' && tmp[i] != '\\')
240             continue;
241 #ifdef WIN32
242         if (i == 2 && tmp[1] == ':')
243             continue;
244 #endif
245         {
246             char saved = tmp[i];
247             tmp[i] = '\0';
248             if (tmp[0] && !report_directory_exists(tmp)) {
249                 if (FRIIDUMP_MKDIR(tmp) != 0 && errno != EEXIST)
250                     return false;
251             }
252             tmp[i] = saved;
253         }
254     }
255
256     if (!report_directory_exists(tmp)) {
257         if (FRIIDUMP_MKDIR(tmp) != 0 && errno != EEXIST)
258             return false;
259     }
260     return report_directory_exists(tmp);
261 }
262
263 static char report_path_separator(const char *directory) {
264 #ifdef WIN32
265     (void) directory;
266     return '\\';
267 #else
268     (void) directory;
269     return '/';
270 #endif
271 }
272
273 static const char *report_path_leaf(const char *path) {
274     const char *slash;
275     const char *backslash;
276     const char *leaf;
277
278     if (!path)
279         return "";
280     slash = strrchr(path, '/');
281     backslash = strrchr(path, '\\');
282     leaf = path;
283     if (slash && slash + 1 > leaf)
284         leaf = slash + 1;
285     if (backslash && backslash + 1 > leaf)
286         leaf = backslash + 1;
287     return leaf;
288 }
289
290 static bool report_path_parent(const char *path,
291                                char *output,
292                                size_t output_size) {
293     const char *leaf;
294     size_t length;
295
296     if (!path || !path[0] || !output || output_size == 0)
297         return false;
298
299     leaf = report_path_leaf(path);
300     if (leaf == path)
301         return false;
302
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)
309         return false;
310
311     memcpy(output, path, length);
312     output[length] = '\0';
313     return true;
314 }
315
316 static bool report_ends_with_case_insensitive(const char *value,
317                                                const char *suffix) {
318     size_t value_length;
319     size_t suffix_length;
320     size_t i;
321
322     if (!value || !suffix)
323         return false;
324     value_length = strlen(value);
325     suffix_length = strlen(suffix);
326     if (suffix_length > value_length)
327         return false;
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]))
332             return false;
333     }
334     return true;
335 }
336
337 static bool report_join_path(const char *directory,
338                              const char *leaf,
339                              char *output,
340                              size_t output_size) {
341     size_t directory_length;
342     char separator;
343
344     if (!directory || !directory[0] || !leaf || !leaf[0] ||
345         !output || output_size == 0)
346         return false;
347
348     directory_length = strlen(directory);
349     separator = report_path_separator(directory);
350     return snprintf(output, output_size,
351                     "%s%s%s",
352                     directory,
353                     (directory_length > 0 &&
354                      directory[directory_length - 1] != '/' &&
355                      directory[directory_length - 1] != '\\')
356                         ? (separator == '\\' ? "\\" : "/")
357                         : "",
358                     leaf) < (int) output_size;
359 }
360
361 static void report_sanitize_leaf(const char *input, char *output, size_t output_size) {
362     size_t used;
363     bool previous_separator;
364     unsigned char c;
365
366     if (!output || output_size == 0)
367         return;
368     output[0] = '\0';
369     if (!input)
370         return;
371
372     used = 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;
383             }
384         }
385     }
386     while (used > 0 && output[used - 1] == '_')
387         used--;
388     output[used] = '\0';
389 }
390
391 static bool report_derived_leaf(const friidump_native_report *report,
392                                 char *leaf,
393                                 size_t leaf_size,
394                                 char *error_text,
395                                 size_t error_text_size) {
396     const char *basis;
397     char fallback[128];
398     size_t length;
399
400     if (!report || !leaf || leaf_size == 0)
401         return false;
402     leaf[0] = '\0';
403
404     if (report->explicit_path) {
405         basis = report_path_leaf(report->requested_path);
406         if (!basis[0]) {
407             report_error(error_text, error_text_size,
408                          "Native report pathname has no filename");
409             return false;
410         }
411         if (strlen(basis) >= leaf_size) {
412             report_error(error_text, error_text_size,
413                          "Native report filename is too long");
414             return false;
415         }
416         report_copy(leaf, leaf_size, basis);
417         return true;
418     }
419
420     basis = report_path_leaf(report->name_basis);
421     if (basis[0]) {
422         if (strlen(basis) >= leaf_size) {
423             report_error(error_text, error_text_size,
424                          "Derived native report filename is too long");
425             return false;
426         }
427         report_copy(leaf, leaf_size, basis);
428         if (report_ends_with_case_insensitive(leaf, ".log"))
429             leaf[strlen(leaf) - 4] = '\0';
430         if (!leaf[0])
431             report_copy(leaf, leaf_size, "friidump");
432     } else {
433         basis = report->have_title
434                     ? report->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");
438     }
439
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");
444         return false;
445     }
446     strcat(leaf, ".friidump.json");
447     return true;
448 }
449
450 static bool report_collision_path(const char *path,
451                                   const char *run_id,
452                                   char *output,
453                                   size_t output_size) {
454     static const char suffix[] = ".friidump.json";
455     size_t path_length;
456     size_t stem_length;
457
458     if (!path || !path[0] || !run_id || !run_id[0] ||
459         !output || output_size == 0)
460         return false;
461
462     path_length = strlen(path);
463     stem_length = path_length;
464     if (report_ends_with_case_insensitive(path, suffix))
465         stem_length -= strlen(suffix);
466
467     if (stem_length + 1 + strlen(run_id) + strlen(suffix) + 1 > output_size)
468         return false;
469
470     memcpy(output, path, stem_length);
471     output[stem_length] = '\0';
472     strcat(output, "-");
473     strcat(output, run_id);
474     strcat(output, suffix);
475     return true;
476 }
477
478 static bool report_resolve_path(friidump_native_report *report,
479                                 char *error_text,
480                                 size_t error_text_size) {
481     char leaf[FRIIDUMP_REPORT_PATH_MAX];
482     char basis_directory[FRIIDUMP_REPORT_PATH_MAX];
483
484     if (!report_derived_leaf(report, leaf, sizeof(leaf),
485                              error_text, error_text_size))
486         return false;
487
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';
492     }
493
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");
498             return false;
499         }
500         if (!report_join_path(report->report_dir, leaf,
501                               report->final_path,
502                               sizeof(report->final_path))) {
503             report_error(error_text, error_text_size,
504                          "Generated report path is too long");
505             return false;
506         }
507         return true;
508     }
509
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,
514                            basis_directory,
515                            sizeof(basis_directory))) {
516         if (!report_join_path(basis_directory, leaf,
517                               report->final_path,
518                               sizeof(report->final_path))) {
519             report_error(error_text, error_text_size,
520                          "Generated report path is too long");
521             return false;
522         }
523         return true;
524     }
525
526     report_copy(report->final_path, sizeof(report->final_path), leaf);
527     return report->final_path[0] != '\0';
528 }
529
530 static size_t report_utf8_sequence_length(const unsigned char *p) {
531     if (!p || p[0] == 0)
532         return 0;
533     if (p[0] < 0x80)
534         return 1;
535     if (p[0] >= 0xc2 && p[0] <= 0xdf &&
536         p[1] >= 0x80 && p[1] <= 0xbf)
537         return 2;
538     if (p[0] == 0xe0 &&
539         p[1] >= 0xa0 && p[1] <= 0xbf &&
540         p[2] >= 0x80 && p[2] <= 0xbf)
541         return 3;
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)
546         return 3;
547     if (p[0] == 0xed &&
548         p[1] >= 0x80 && p[1] <= 0x9f &&
549         p[2] >= 0x80 && p[2] <= 0xbf)
550         return 3;
551     if (p[0] == 0xf0 &&
552         p[1] >= 0x90 && p[1] <= 0xbf &&
553         p[2] >= 0x80 && p[2] <= 0xbf &&
554         p[3] >= 0x80 && p[3] <= 0xbf)
555         return 4;
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)
560         return 4;
561     if (p[0] == 0xf4 &&
562         p[1] >= 0x80 && p[1] <= 0x8f &&
563         p[2] >= 0x80 && p[2] <= 0xbf &&
564         p[3] >= 0x80 && p[3] <= 0xbf)
565         return 4;
566     return 0;
567 }
568
569 static void report_json_string(FILE *f, const char *value) {
570     const unsigned char *p;
571     size_t utf8_length;
572
573     fputc('"', f);
574     if (value) {
575         for (p = (const unsigned char *) value; *p; p++) {
576             if (*p >= 0x80) {
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;
581                 } else {
582                     fprintf(f, "\\u%04x", (unsigned int) *p);
583                 }
584                 continue;
585             }
586             switch (*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;
594                 default:
595                     if (*p < 0x20)
596                         fprintf(f, "\\u%04x", (unsigned int) *p);
597                     else
598                         fputc(*p, f);
599                     break;
600             }
601         }
602     }
603     fputc('"', f);
604 }
605
606 static void report_json_nullable_string(FILE *f, const char *value, bool present) {
607     if (present)
608         report_json_string(f, value);
609     else
610         fputs("null", f);
611 }
612
613 static void report_json_nullable_u64(FILE *f, uint64_t value, bool present) {
614     if (present)
615         fprintf(f, "%llu", (unsigned long long) value);
616     else
617         fputs("null", f);
618 }
619
620 static void report_json_nullable_double(FILE *f, double value, bool present) {
621     if (present)
622         fprintf(f, "%.3f", value < 0.0 ? 0.0 : value);
623     else
624         fputs("null", f);
625 }
626
627 static bool report_write_document(FILE *f, const friidump_native_report *r) {
628     size_t i;
629     const friidump_native_artifact *artifact;
630
631     fputs("{\n", f);
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);
637     fputs("  },\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);
643     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);
650     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);
659     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);
665     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);
670     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);
680     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);
686     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);
691     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);
696     fputs("  },\n", f);
697     fputs("  \"notes\": [", f);
698     if (r->note_count > 0)
699         fputc('\n', f);
700     for (i = 0; i < r->note_count; i++) {
701         fputs("    ", f);
702         report_json_string(f, r->notes[i]);
703         fputs((i + 1 < r->note_count) ? ",\n" : "\n", f);
704     }
705     fputs(r->note_count > 0 ? "  ],\n" : "],\n", f);
706     fputs("  \"artifacts\": [", f);
707     if (r->artifact_count > 0)
708         fputc('\n', f);
709     for (i = 0; i < r->artifact_count; i++) {
710         artifact = &r->artifacts[i];
711         fputs("    {\n", f);
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);
717     }
718     fputs(r->artifact_count > 0 ? "  ]\n" : "]\n", f);
719     fputs("}\n", f);
720
721     return ferror(f) == 0;
722 }
723
724 void friidump_native_report_init(friidump_native_report *report, const char *generator_version) {
725     if (!report)
726         return;
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';
738 }
739
740 bool friidump_native_report_enable_default(friidump_native_report *report) {
741     if (!report)
742         return false;
743     report->enabled = true;
744     return true;
745 }
746
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))
749         return false;
750     report->enabled = true;
751     report->explicit_path = true;
752     report_copy(report->requested_path, sizeof(report->requested_path), path);
753     return true;
754 }
755
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))
758         return false;
759     report->enabled = true;
760     report->directory_override = true;
761     report_copy(report->report_dir, sizeof(report->report_dir), directory);
762     return true;
763 }
764
765 bool friidump_native_report_set_name_basis(friidump_native_report *report,
766                                            const char *path) {
767     if (!report || !path || !path[0] || strlen(path) >= sizeof(report->name_basis))
768         return false;
769     report_copy(report->name_basis, sizeof(report->name_basis), path);
770     return true;
771 }
772
773 bool friidump_native_report_is_enabled(const friidump_native_report *report) {
774     return report && report->enabled;
775 }
776
777 void friidump_native_report_set_build_commit(friidump_native_report *report, const char *commit) {
778     if (!report)
779         return;
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 : "");
782 }
783
784 void friidump_native_report_set_drive(friidump_native_report *report,
785                                       const char *vendor,
786                                       const char *model,
787                                       const char *firmware_revision,
788                                       const char *interface_name,
789                                       const char *device_path) {
790     if (!report)
791         return;
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';
799 }
800
801 void friidump_native_report_set_firmware_modified(friidump_native_report *report,
802                                                    bool modified,
803                                                    const char *note) {
804     if (!report)
805         return;
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 : "");
809 }
810
811 void friidump_native_report_set_media(friidump_native_report *report,
812                                       const char *platform,
813                                       const char *title,
814                                       const char *region,
815                                       const char *disc_id) {
816     if (!report)
817         return;
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 : "");
825 }
826
827 void friidump_native_report_set_profile(friidump_native_report *report,
828                                         const char *support_tier,
829                                         uint32_t cdb_offset,
830                                         uint32_t gate_address) {
831     if (!report)
832         return;
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);
841 }
842
843 void friidump_native_report_set_seed(friidump_native_report *report,
844                                      bool attempted,
845                                      const char *result,
846                                      bool have_duration,
847                                      double duration_seconds) {
848     if (!report)
849         return;
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;
854 }
855
856 void friidump_native_report_set_dump(friidump_native_report *report,
857                                      bool attempted,
858                                      const char *result,
859                                      bool have_sector_count,
860                                      uint64_t sector_count,
861                                      bool have_byte_count,
862                                      uint64_t byte_count,
863                                      bool have_duration,
864                                      double duration_seconds,
865                                      const char *failure_stage,
866                                      bool have_failure_sector,
867                                      uint64_t failure_sector,
868                                      const char *output_path) {
869     if (!report)
870         return;
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 : "");
885 }
886
887 void friidump_native_report_set_hashes(friidump_native_report *report,
888                                        const char *crc32,
889                                        const char *md5,
890                                        const char *sha1,
891                                        const char *sha256) {
892     if (!report)
893         return;
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 : "");
898 }
899
900 void friidump_native_report_set_reference(friidump_native_report *report,
901                                           const char *provider,
902                                           const char *result,
903                                           const char *reference_id) {
904     if (!report)
905         return;
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 : "");
911 }
912
913 void friidump_native_report_set_outcome(friidump_native_report *report,
914                                         const char *test_type,
915                                         const char *run_result) {
916     if (!report)
917         return;
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");
920 }
921
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)
926         return false;
927     report_copy(report->notes[report->note_count], sizeof(report->notes[report->note_count]), note);
928     report->note_count++;
929     return true;
930 }
931
932 bool friidump_native_report_add_artifact(friidump_native_report *report,
933                                          const char *type,
934                                          const char *path,
935                                          bool have_bytes,
936                                          uint64_t bytes,
937                                          const char *sha256) {
938     friidump_native_artifact *artifact;
939
940     if (!report || !type || !type[0] || strlen(type) > 80 ||
941         report->artifact_count >= FRIIDUMP_REPORT_ARTIFACT_MAX)
942         return false;
943     if (path && strlen(path) > FRIIDUMP_REPORT_PATH_MAX)
944         return false;
945     if (sha256 && sha256[0] && !report_valid_hex(sha256, 64))
946         return false;
947
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++;
958     return true;
959 }
960
961
962 static bool report_validate_for_write(const friidump_native_report *report,
963                                       char *error_text,
964                                       size_t error_text_size) {
965     static const char *const platforms[] = {
966         "gamecube", "wii", "xbox", "dvd", "unknown"
967     };
968     static const char *const test_types[] = {
969         "full_dump", "seed_only", "partial_dump", "diagnostic_no_media",
970         "diagnostic_wrong_media", "media_transition", "other"
971     };
972     static const char *const run_results[] = {
973         "pass", "partial", "fail", "not_applicable"
974     };
975     static const char *const step_results[] = {
976         "pass", "partial", "fail", "not_attempted", "not_applicable"
977     };
978     static const char *const reference_results[] = {
979         "match", "mismatch", "not_checked", "not_applicable"
980     };
981
982     if (!report->generator_version[0]) {
983         report_error(error_text, error_text_size, "Generator version is empty");
984         return false;
985     }
986     if (!report_valid_uuid(report->run_id)) {
987         report_error(error_text, error_text_size, "Run UUID is missing or invalid");
988         return false;
989     }
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");
994         return false;
995     }
996     if (strcmp(report->completed_utc, report->started_utc) < 0) {
997         report_error(error_text, error_text_size, "Completion timestamp precedes start timestamp");
998         return false;
999     }
1000     if (!report->drive_known) {
1001         report_error(error_text, error_text_size,
1002                      "Drive identity is incomplete; no database-compatible report was written");
1003         return false;
1004     }
1005     if (report->firmware_modified && !report->have_modification_note) {
1006         report_error(error_text, error_text_size,
1007                      "Modified firmware requires a modification note");
1008         return false;
1009     }
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");
1023         return false;
1024     }
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");
1030         return false;
1031     }
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");
1036         return false;
1037     }
1038     {
1039         size_t i;
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");
1045                 return false;
1046             }
1047         }
1048     }
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");
1055             return false;
1056         }
1057     } else {
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");
1063             return false;
1064         }
1065     }
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");
1072             return false;
1073         }
1074     } else {
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");
1082             return false;
1083         }
1084     }
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");
1090         return false;
1091     }
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");
1097         return false;
1098     }
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");
1103         return false;
1104     }
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");
1109         return false;
1110     }
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");
1118         return false;
1119     }
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");
1124         return false;
1125     }
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");
1129         return false;
1130     }
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");
1135         return false;
1136     }
1137     {
1138         size_t i;
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");
1148                 return false;
1149             }
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");
1161                     return false;
1162                 }
1163             }
1164         }
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");
1169             return false;
1170         }
1171     }
1172     return true;
1173 }
1174
1175 bool friidump_native_report_write(friidump_native_report *report,
1176                                   char *written_path,
1177                                   size_t written_path_size,
1178                                   char *error_text,
1179                                   size_t error_text_size) {
1180     FILE *f;
1181     char temporary_path[FRIIDUMP_REPORT_PATH_MAX + 64];
1182     int close_status;
1183
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';
1188
1189     if (!report || !report->enabled) {
1190         report_error(error_text, error_text_size, "Native report is not enabled");
1191         return false;
1192     }
1193     report_utc_now(report->completed_utc, sizeof(report->completed_utc));
1194     report_utc_now(report->generated_utc, sizeof(report->generated_utc));
1195
1196     if (!report_validate_for_write(report, error_text, error_text_size))
1197         return false;
1198     if (!report_resolve_path(report, error_text, error_text_size))
1199         return false;
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");
1204             return false;
1205         }
1206         if (!report_collision_path(report->final_path,
1207                                    report->run_id,
1208                                    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");
1212             return false;
1213         }
1214         if (FRIIDUMP_ACCESS(report->final_path) == 0) {
1215             report_error(error_text, error_text_size,
1216                          "Collision-safe native report path already exists");
1217             return false;
1218         }
1219     }
1220
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");
1224         return false;
1225     }
1226
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");
1229         return false;
1230     }
1231
1232     f = fopen(temporary_path, "wb");
1233     if (!f) {
1234         report_error(error_text, error_text_size, "Could not open temporary native report");
1235         return false;
1236     }
1237
1238     if (!report_write_document(f, report) || fflush(f) != 0 || FRIIDUMP_FSYNC(FRIIDUMP_FILENO(f)) != 0) {
1239         fclose(f);
1240         remove(temporary_path);
1241         report_error(error_text, error_text_size, "Could not write or flush native report");
1242         return false;
1243     }
1244
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");
1249         return false;
1250     }
1251
1252 #ifdef WIN32
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");
1256         return false;
1257     }
1258 #else
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");
1263         else
1264             report_error(error_text, error_text_size, "Could not atomically publish native report");
1265         return false;
1266     }
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");
1270         return false;
1271     }
1272 #endif
1273
1274     report_copy(written_path, written_path_size, report->final_path);
1275     return true;
1276 }