master
c 707 lines 23.9 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 /*
4 * Netdata YAML Parser/Generator Module
5 *
6 * This module provides YAML parsing and generation using libyaml, with conversion
7 * to/from json-c objects. It supports the YAML subset that is 100% compatible with JSON.
8 *
9 * KNOWN LIMITATIONS (due to libyaml):
10 * 1. Octal escape sequences (\101) are not supported - use hex (\x41) or unicode (\u0041)
11 * 2. Single-quoted strings with literal newlines have them converted to spaces
12 * 3. Null bytes in strings may cause issues
13 * 4. Complex block scalar indentation may not be preserved exactly
14 * 5. Some invalid YAML syntax may be accepted without error
15 *
16 * The module handles these limitations gracefully and provides consistent behavior
17 * for round-trip conversion where possible.
18 */
19
20 #include "yaml.h"
21
22 #define YAML_MAX_NESTING_DEPTH 256
23
24 static struct json_object *yaml_node_to_json(yaml_document_t *document, yaml_node_t *node, BUFFER *error, YAML2JSON_FLAGS flags, int depth);
25
26 static struct json_object *yaml_sequence_to_json(yaml_document_t *document, yaml_node_t *node, BUFFER *error, YAML2JSON_FLAGS flags, int depth) {
27 struct json_object *array = json_object_new_array();
28 if (!array) {
29 buffer_strcat(error, "Failed to create JSON array");
30 return NULL;
31 }
32
33 yaml_node_item_t *item;
34 for (item = node->data.sequence.items.start; item < node->data.sequence.items.top; item++) {
35 yaml_node_t *child = yaml_document_get_node(document, *item);
36 if (!child) {
37 buffer_sprintf(error, "Invalid sequence item reference");
38 json_object_put(array);
39 return NULL;
40 }
41
42 size_t error_len = buffer_strlen(error);
43 struct json_object *child_obj = yaml_node_to_json(document, child, error, flags, depth);
44
45 // Check for error (error buffer grew)
46 if (buffer_strlen(error) > error_len) {
47 if (child_obj) json_object_put(child_obj);
48 json_object_put(array);
49 return NULL;
50 }
51
52 if (json_object_array_add(array, child_obj) != 0) {
53 buffer_strcat(error, "Failed to add item to JSON array");
54 json_object_put(child_obj);
55 json_object_put(array);
56 return NULL;
57 }
58 }
59
60 return array;
61 }
62
63 static struct json_object *yaml_mapping_to_json(yaml_document_t *document, yaml_node_t *node, BUFFER *error, YAML2JSON_FLAGS flags, int depth) {
64 struct json_object *object = json_object_new_object();
65 if (!object) {
66 buffer_strcat(error, "Failed to create JSON object");
67 return NULL;
68 }
69
70 yaml_node_pair_t *pair;
71 for (pair = node->data.mapping.pairs.start; pair < node->data.mapping.pairs.top; pair++) {
72 yaml_node_t *key_node = yaml_document_get_node(document, pair->key);
73 yaml_node_t *value_node = yaml_document_get_node(document, pair->value);
74
75 if (!key_node || !value_node) {
76 buffer_sprintf(error, "Invalid mapping pair reference");
77 json_object_put(object);
78 return NULL;
79 }
80
81 if (key_node->type != YAML_SCALAR_NODE) {
82 buffer_sprintf(error, "Mapping key must be a scalar");
83 json_object_put(object);
84 return NULL;
85 }
86
87 const char *key = (const char *)key_node->data.scalar.value;
88 size_t error_len = buffer_strlen(error);
89 struct json_object *value_obj = yaml_node_to_json(document, value_node, error, flags, depth);
90
91 // Check for error (error buffer grew)
92 if (buffer_strlen(error) > error_len) {
93 if (value_obj) json_object_put(value_obj);
94 json_object_put(object);
95 return NULL;
96 }
97
98 // json_object_object_add handles NULL values correctly
99 if (json_object_object_add(object, key, value_obj) != 0) {
100 buffer_sprintf(error, "Failed to add property to JSON object");
101 if (value_obj) json_object_put(value_obj);
102 json_object_put(object);
103 return NULL;
104 }
105 }
106
107 return object;
108 }
109
110 // Remove underscores from a numeric string. Returns cleaned length, or 0 if too long.
111 static size_t remove_underscores(const char *str, size_t len, char *cleaned, size_t cleaned_size) {
112 size_t j = 0;
113 for (size_t i = 0; i < len; i++) {
114 if (str[i] != '_') {
115 if (j >= cleaned_size - 1)
116 return 0; // too long to be a number
117 cleaned[j++] = str[i];
118 }
119 }
120 cleaned[j] = '\0';
121 return j;
122 }
123
124 // Helper function to parse numbers with underscores
125 static bool parse_number_with_underscores(const char *str, size_t len, long long *int_result, double *double_result, bool *is_double) {
126 char cleaned[256];
127 if (!remove_underscores(str, len, cleaned, sizeof(cleaned)))
128 return false; // too long, treat as string
129
130 char *endptr;
131 errno = 0;
132
133 // Try as integer first
134 *int_result = strtoll(cleaned, &endptr, 10);
135 if (errno == 0 && *endptr == '\0') {
136 *is_double = false;
137 return true;
138 }
139
140 // Try as double
141 errno = 0;
142 *double_result = strtod(cleaned, &endptr);
143 if (errno == 0 && *endptr == '\0') {
144 *is_double = true;
145 return true;
146 }
147
148 return false;
149 }
150
151 static struct json_object *yaml_scalar_to_json(yaml_node_t *node, YAML2JSON_FLAGS flags) {
152 const char *value = (const char *)node->data.scalar.value;
153 size_t length = node->data.scalar.length;
154
155 // If YAML2JSON_ALL_VALUES_AS_STRINGS flag is set, always return as string
156 if (flags & YAML2JSON_ALL_VALUES_AS_STRINGS) {
157 return json_object_new_string_len(value, (int)length);
158 }
159
160 // Only plain scalars get implicit type conversion (null/bool/number).
161 // Quoted, literal block (|), and folded block (>) scalars are always strings.
162 if (node->data.scalar.style != YAML_PLAIN_SCALAR_STYLE) {
163 return json_object_new_string_len(value, (int)length);
164 }
165
166 // Handle null and tilde (case-insensitive for null)
167 if ((length == 4 && strcasecmp(value, "null") == 0) ||
168 (length == 1 && strncmp(value, "~", 1) == 0)) {
169 // In json-c, NULL represents JSON null values
170 return NULL;
171 }
172
173 // Handle booleans (case-insensitive)
174 if ((length == 4 && strcasecmp(value, "true") == 0) ||
175 (length == 3 && strcasecmp(value, "yes") == 0) ||
176 (length == 2 && strcasecmp(value, "on") == 0)) {
177 return json_object_new_boolean(1);
178 }
179
180 if ((length == 5 && strcasecmp(value, "false") == 0) ||
181 (length == 2 && strcasecmp(value, "no") == 0) ||
182 (length == 3 && strcasecmp(value, "off") == 0)) {
183 return json_object_new_boolean(0);
184 }
185
186 // Try to parse as number
187 char *endptr;
188 errno = 0;
189
190 // Check for hex (0x or 0X), octal (0o or 0O), or binary (0b or 0B) prefix
191 if (length > 2 && value[0] == '0') {
192 int base = 0;
193 if (value[1] == 'x' || value[1] == 'X') base = 16;
194 else if (value[1] == 'o' || value[1] == 'O') base = 8;
195 else if (value[1] == 'b' || value[1] == 'B') base = 2;
196
197 if (base) {
198 // Check if it contains underscores
199 bool has_underscore = false;
200 for (size_t i = 2; i < length; i++) {
201 if (value[i] == '_') { has_underscore = true; break; }
202 }
203
204 if (has_underscore) {
205 char cleaned[256];
206 size_t cleaned_len = remove_underscores(value, length, cleaned, sizeof(cleaned));
207 // need at least one digit after the prefix (e.g., "0x_" → "0x" has no digits)
208 if (cleaned_len > 2) {
209 long long int_val = strtoll(cleaned + 2, &endptr, base);
210 if (errno == 0 && *endptr == '\0')
211 return json_object_new_int64(int_val);
212 }
213 } else {
214 long long int_val = strtoll(value + 2, &endptr, base);
215 if (errno == 0 && endptr == value + length)
216 return json_object_new_int64(int_val);
217 }
218 }
219 }
220
221 // Check if number contains underscores
222 bool has_underscore = false;
223 for (size_t i = 0; i < length; i++) {
224 if (value[i] == '_') {
225 has_underscore = true;
226 break;
227 }
228 }
229
230 if (has_underscore) {
231 long long int_result;
232 double double_result;
233 bool is_double;
234
235 if (parse_number_with_underscores(value, length, &int_result, &double_result, &is_double)) {
236 if (is_double) {
237 return json_object_new_double(double_result);
238 } else {
239 return json_object_new_int64(int_result);
240 }
241 }
242 }
243
244 // Try integer
245 long long int_val = strtoll(value, &endptr, 10);
246 if (errno == 0 && endptr == value + length && *value != '\0') {
247 return json_object_new_int64(int_val);
248 }
249
250 // Try double
251 errno = 0;
252 double double_val = strtod(value, &endptr);
253 if (errno == 0 && endptr == value + length && *value != '\0') {
254 return json_object_new_double(double_val);
255 }
256
257 // Default to string
258 return json_object_new_string_len(value, (int)length);
259 }
260
261 static struct json_object *yaml_node_to_json(yaml_document_t *document, yaml_node_t *node, BUFFER *error, YAML2JSON_FLAGS flags, int depth) {
262 if (!node) {
263 buffer_strcat(error, "NULL node");
264 return NULL;
265 }
266
267 if (depth > YAML_MAX_NESTING_DEPTH) {
268 buffer_sprintf(error, "YAML nesting too deep (max %d levels)", YAML_MAX_NESTING_DEPTH);
269 return NULL;
270 }
271
272 switch (node->type) {
273 case YAML_SCALAR_NODE:
274 return yaml_scalar_to_json(node, flags);
275
276 case YAML_SEQUENCE_NODE:
277 return yaml_sequence_to_json(document, node, error, flags, depth + 1);
278
279 case YAML_MAPPING_NODE:
280 return yaml_mapping_to_json(document, node, error, flags, depth + 1);
281
282 default:
283 buffer_sprintf(error, "Unsupported YAML node type: %u", (unsigned int)node->type);
284 return NULL;
285 }
286 }
287
288 static struct json_object *yaml_document_to_json_with_flags(yaml_document_t *document, BUFFER *error, YAML2JSON_FLAGS flags) {
289 yaml_node_t *root = yaml_document_get_root_node(document);
290 if (!root) {
291 // Empty document - this is valid YAML but we return NULL
292 return NULL;
293 }
294
295 return yaml_node_to_json(document, root, error, flags, 0);
296 }
297
298 static struct json_object *yaml_parse_common(yaml_parser_t *parser, BUFFER *error, YAML2JSON_FLAGS flags) {
299 yaml_document_t document;
300 struct json_object *result = NULL;
301
302 if (!yaml_parser_load(parser, &document)) {
303 if (parser->error == YAML_NO_ERROR) {
304 buffer_strcat(error, "No YAML document found (empty input)");
305 } else {
306 buffer_sprintf(error, "YAML parse error: %s at line %zu, column %zu",
307 parser->problem ? parser->problem : "unknown error",
308 parser->problem_mark.line + 1,
309 parser->problem_mark.column + 1);
310 }
311 goto cleanup;
312 }
313
314
315 result = yaml_document_to_json_with_flags(&document, error, flags);
316 yaml_document_delete(&document);
317
318 cleanup:
319 yaml_parser_delete(parser);
320 return result;
321 }
322
323 struct json_object *yaml_parse_string(const char *yaml_string, BUFFER *error, YAML2JSON_FLAGS flags) {
324 if (!yaml_string) {
325 buffer_strcat(error, "NULL YAML string");
326 return NULL;
327 }
328
329 yaml_parser_t parser;
330 if (!yaml_parser_initialize(&parser)) {
331 buffer_strcat(error, "Failed to initialize YAML parser");
332 return NULL;
333 }
334
335 yaml_parser_set_input_string(&parser, (const unsigned char *)yaml_string, strlen(yaml_string));
336 return yaml_parse_common(&parser, error, flags);
337 }
338
339 struct json_object *yaml_parse_filename(const char *filename, BUFFER *error, YAML2JSON_FLAGS flags) {
340 if (!filename) {
341 buffer_strcat(error, "NULL filename");
342 return NULL;
343 }
344
345 FILE *file = fopen(filename, "r");
346 if (!file) {
347 buffer_sprintf(error, "Failed to open file '%s': %s", filename, strerror(errno));
348 return NULL;
349 }
350
351 yaml_parser_t parser;
352 if (!yaml_parser_initialize(&parser)) {
353 buffer_strcat(error, "Failed to initialize YAML parser");
354 fclose(file);
355 return NULL;
356 }
357
358 yaml_parser_set_input_file(&parser, file);
359 struct json_object *result = yaml_parse_common(&parser, error, flags);
360 fclose(file);
361 return result;
362 }
363
364 struct json_object *yaml_parse_fd(int fd, BUFFER *error, YAML2JSON_FLAGS flags) {
365 if (fd < 0) {
366 buffer_strcat(error, "Invalid file descriptor");
367 return NULL;
368 }
369
370 int duped = dup(fd);
371 if (duped < 0) {
372 buffer_sprintf(error, "Failed to dup file descriptor: %s", strerror(errno));
373 return NULL;
374 }
375
376 FILE *file = fdopen(duped, "r");
377 if (!file) {
378 close(duped);
379 buffer_sprintf(error, "Failed to open file descriptor: %s", strerror(errno));
380 return NULL;
381 }
382
383 yaml_parser_t parser;
384 if (!yaml_parser_initialize(&parser)) {
385 buffer_strcat(error, "Failed to initialize YAML parser");
386 fclose(file);
387 return NULL;
388 }
389
390 yaml_parser_set_input_file(&parser, file);
391 struct json_object *result = yaml_parse_common(&parser, error, flags);
392 fclose(file);
393 return result;
394 }
395
396 // YAML generation functions
397
398 // Determine the scalar style needed to preserve round-trip fidelity for a string.
399 // Strings that would be misinterpreted as null/bool/number by YAML parsers get quoted.
400 static yaml_scalar_style_t yaml_string_scalar_style(const char *str, size_t len) {
401 if (len == 0)
402 return YAML_DOUBLE_QUOTED_SCALAR_STYLE;
403
404 if (strchr(str, '\n') || strchr(str, '\r') ||
405 str[0] == ' ' || str[len - 1] == ' ' || str[0] == '\t')
406 return YAML_DOUBLE_QUOTED_SCALAR_STYLE;
407
408 // YAML null/tilde and booleans (case-insensitive)
409 if ((len == 4 && strcasecmp(str, "null") == 0) ||
410 (len == 1 && str[0] == '~') ||
411 (len == 4 && strcasecmp(str, "true") == 0) ||
412 (len == 5 && strcasecmp(str, "false") == 0) ||
413 (len == 3 && strcasecmp(str, "yes") == 0) ||
414 (len == 2 && strcasecmp(str, "no") == 0) ||
415 (len == 2 && strcasecmp(str, "on") == 0) ||
416 (len == 3 && strcasecmp(str, "off") == 0))
417 return YAML_DOUBLE_QUOTED_SCALAR_STYLE;
418
419 // Numeric-looking strings
420 char c = str[0];
421 if (c == '+' || c == '-' || c == '.' || (c >= '0' && c <= '9'))
422 return YAML_DOUBLE_QUOTED_SCALAR_STYLE;
423
424 return YAML_PLAIN_SCALAR_STYLE;
425 }
426
427 static int yaml_add_json_to_document(yaml_document_t *document, struct json_object *json, BUFFER *error, int depth);
428
429 static int yaml_add_array_to_document(yaml_document_t *document, struct json_object *array, BUFFER *error, int depth) {
430 int sequence = yaml_document_add_sequence(document, NULL, YAML_BLOCK_SEQUENCE_STYLE);
431 if (!sequence) {
432 buffer_strcat(error, "Failed to add sequence to YAML document");
433 return 0;
434 }
435
436 size_t len = json_object_array_length(array);
437 for (size_t i = 0; i < len; i++) {
438 struct json_object *item = json_object_array_get_idx(array, i);
439 int item_node = yaml_add_json_to_document(document, item, error, depth);
440 if (!item_node) {
441 return 0;
442 }
443
444 if (!yaml_document_append_sequence_item(document, sequence, item_node)) {
445 buffer_strcat(error, "Failed to append item to YAML sequence");
446 return 0;
447 }
448 }
449
450 return sequence;
451 }
452
453 static int yaml_add_object_to_document(yaml_document_t *document, struct json_object *object, BUFFER *error, int depth) {
454 int mapping = yaml_document_add_mapping(document, NULL, YAML_BLOCK_MAPPING_STYLE);
455 if (!mapping) {
456 buffer_strcat(error, "Failed to add mapping to YAML document");
457 return 0;
458 }
459
460 json_object_object_foreach(object, key, value) {
461 size_t key_len = strlen(key);
462 yaml_scalar_style_t key_style = yaml_string_scalar_style(key, key_len);
463 int key_node = yaml_document_add_scalar(document, NULL,
464 (yaml_char_t *)key,
465 key_len,
466 key_style);
467 if (!key_node) {
468 buffer_sprintf(error, "Failed to add key '%s' to YAML document", key);
469 return 0;
470 }
471
472 int value_node = yaml_add_json_to_document(document, value, error, depth);
473 if (!value_node) {
474 return 0;
475 }
476
477 if (!yaml_document_append_mapping_pair(document, mapping, key_node, value_node)) {
478 buffer_sprintf(error, "Failed to add mapping pair for key '%s'", key);
479 return 0;
480 }
481 }
482
483 return mapping;
484 }
485
486 static int yaml_add_json_to_document(yaml_document_t *document, struct json_object *json, BUFFER *error, int depth) {
487 if (depth > YAML_MAX_NESTING_DEPTH) {
488 buffer_sprintf(error, "JSON nesting too deep for YAML generation (max %d levels)", YAML_MAX_NESTING_DEPTH);
489 return 0;
490 }
491
492 if (!json) {
493 return yaml_document_add_scalar(document, NULL,
494 (yaml_char_t *)"null",
495 4,
496 YAML_PLAIN_SCALAR_STYLE);
497 }
498
499 enum json_type type = json_object_get_type(json);
500
501 switch (type) {
502 case json_type_null:
503 return yaml_document_add_scalar(document, NULL,
504 (yaml_char_t *)"null",
505 4,
506 YAML_PLAIN_SCALAR_STYLE);
507
508 case json_type_boolean: {
509 const char *bool_str = json_object_get_boolean(json) ? "true" : "false";
510 return yaml_document_add_scalar(document, NULL,
511 (yaml_char_t *)bool_str,
512 strlen(bool_str),
513 YAML_PLAIN_SCALAR_STYLE);
514 }
515
516 case json_type_int: {
517 char buf[32];
518 snprintf(buf, sizeof(buf), "%" PRId64, json_object_get_int64(json));
519 return yaml_document_add_scalar(document, NULL,
520 (yaml_char_t *)buf,
521 strlen(buf),
522 YAML_PLAIN_SCALAR_STYLE);
523 }
524
525 case json_type_double: {
526 char buf[64];
527 double val = json_object_get_double(json);
528
529 // Handle special case of -0.0
530 if (val == 0.0 && signbit(val)) {
531 strcpy(buf, "0.0");
532 } else {
533 // Use json-c's formatting but preserve .0 for whole numbers
534 const char *json_str = json_object_to_json_string(json);
535 strncpy(buf, json_str, sizeof(buf) - 1);
536 buf[sizeof(buf) - 1] = '\0';
537
538 // Check if this is a whole number that needs .0 suffix
539 double int_part;
540 if (modf(val, &int_part) == 0.0 && !strchr(buf, '.') && !strchr(buf, 'e') && !strchr(buf, 'E')) {
541 size_t len = strlen(buf);
542 if (len < sizeof(buf) - 3) {
543 strcat(buf, ".0");
544 }
545 }
546 }
547
548 return yaml_document_add_scalar(document, NULL,
549 (yaml_char_t *)buf,
550 strlen(buf),
551 YAML_PLAIN_SCALAR_STYLE);
552 }
553
554 case json_type_string: {
555 const char *str = json_object_get_string(json);
556 size_t len = json_object_get_string_len(json);
557 yaml_scalar_style_t style = yaml_string_scalar_style(str, len);
558
559 return yaml_document_add_scalar(document, NULL,
560 (yaml_char_t *)str,
561 len,
562 style);
563 }
564
565 case json_type_array:
566 return yaml_add_array_to_document(document, json, error, depth + 1);
567
568 case json_type_object:
569 return yaml_add_object_to_document(document, json, error, depth + 1);
570
571 default:
572 buffer_sprintf(error, "Unknown JSON type: %u", (unsigned int)type);
573 return 0;
574 }
575 }
576
577 static bool json_to_yaml_document(struct json_object *json, yaml_document_t *document, BUFFER *error) {
578 // Initialize with implicit_start=1 and implicit_end=1 to avoid "---" and "..."
579 if (!yaml_document_initialize(document, NULL, NULL, NULL, 1, 1)) {
580 buffer_strcat(error, "Failed to initialize YAML document");
581 return false;
582 }
583
584 int root = yaml_add_json_to_document(document, json, error, 0);
585 if (!root) {
586 yaml_document_delete(document);
587 return false;
588 }
589
590 return true;
591 }
592
593 static bool yaml_generate_common(yaml_emitter_t *emitter, struct json_object *json, BUFFER *error) {
594 yaml_document_t document;
595 bool success = false;
596
597 if (!json_to_yaml_document(json, &document, error)) {
598 goto cleanup;
599 }
600
601 if (!yaml_emitter_open(emitter)) {
602 buffer_strcat(error, "Failed to open YAML emitter");
603 yaml_document_delete(&document);
604 goto cleanup;
605 }
606
607 // yaml_emitter_dump() takes ownership of the document and destroys it
608 // regardless of success or failure — do NOT call yaml_document_delete after this
609 if (!yaml_emitter_dump(emitter, &document)) {
610 buffer_sprintf(error, "YAML emit error: %s",
611 emitter->problem ? emitter->problem : "unknown error");
612 goto cleanup;
613 }
614
615 if (!yaml_emitter_close(emitter)) {
616 buffer_strcat(error, "Failed to close YAML emitter");
617 goto cleanup;
618 }
619
620 success = true;
621
622 cleanup:
623 yaml_emitter_delete(emitter);
624 return success;
625 }
626
627 static int yaml_write_handler_buffer(void *data, unsigned char *buffer, size_t size) {
628 BUFFER *buf = (BUFFER *)data;
629 buffer_memcat(buf, buffer, size);
630 return 1;
631 }
632
633 bool yaml_generate_to_buffer(BUFFER *dst, struct json_object *json, BUFFER *error) {
634 if (!dst) {
635 buffer_strcat(error, "NULL destination buffer");
636 return false;
637 }
638
639 yaml_emitter_t emitter;
640 if (!yaml_emitter_initialize(&emitter)) {
641 buffer_strcat(error, "Failed to initialize YAML emitter");
642 return false;
643 }
644
645 yaml_emitter_set_output(&emitter, yaml_write_handler_buffer, dst);
646 yaml_emitter_set_unicode(&emitter, 1);
647 return yaml_generate_common(&emitter, json, error);
648 }
649
650 bool yaml_generate_to_filename(const char *filename, struct json_object *json, BUFFER *error) {
651 if (!filename) {
652 buffer_strcat(error, "NULL filename");
653 return false;
654 }
655
656 FILE *file = fopen(filename, "w");
657 if (!file) {
658 buffer_sprintf(error, "Failed to open file '%s': %s", filename, strerror(errno));
659 return false;
660 }
661
662 yaml_emitter_t emitter;
663 if (!yaml_emitter_initialize(&emitter)) {
664 buffer_strcat(error, "Failed to initialize YAML emitter");
665 fclose(file);
666 return false;
667 }
668
669 yaml_emitter_set_output_file(&emitter, file);
670 yaml_emitter_set_unicode(&emitter, 1);
671 bool result = yaml_generate_common(&emitter, json, error);
672 fclose(file);
673 return result;
674 }
675
676 bool yaml_generate_to_fd(int fd, struct json_object *json, BUFFER *error) {
677 if (fd < 0) {
678 buffer_strcat(error, "Invalid file descriptor");
679 return false;
680 }
681
682 int duped = dup(fd);
683 if (duped < 0) {
684 buffer_sprintf(error, "Failed to dup file descriptor: %s", strerror(errno));
685 return false;
686 }
687
688 FILE *file = fdopen(duped, "w");
689 if (!file) {
690 close(duped);
691 buffer_sprintf(error, "Failed to open file descriptor: %s", strerror(errno));
692 return false;
693 }
694
695 yaml_emitter_t emitter;
696 if (!yaml_emitter_initialize(&emitter)) {
697 buffer_strcat(error, "Failed to initialize YAML emitter");
698 fclose(file);
699 return false;
700 }
701
702 yaml_emitter_set_output_file(&emitter, file);
703 yaml_emitter_set_unicode(&emitter, 1);
704 bool result = yaml_generate_common(&emitter, json, error);
705 fclose(file);
706 return result;
707 }