1 /*
2 * Copyright (c) 2015, Juniper Networks, Inc.
3 * All rights reserved.
4 * This SOFTWARE is licensed under the LICENSE provided in the
5 * ../Copyright file. By downloading, installing, copying, or otherwise
6 * using the SOFTWARE, you agree to be bound by the terms of that
7 * LICENSE.
8 * Phil Shafer, August 2015
9 */
10
11 /*
12 * CSV encoder generates comma-separated value files for specific
13 * subsets of data. This is not (and cannot be) a generalized
14 * facility, but for specific subsets of data, CSV data can be
15 * reasonably generated. For example, the df XML content:
16 * <filesystem>
17 * <name>procfs</name>
18 * <total-blocks>4</total-blocks>
19 * <used-blocks>4</used-blocks>
20 * <available-blocks>0</available-blocks>
21 * <used-percent>100</used-percent>
22 * <mounted-on>/proc</mounted-on>
23 * </filesystem>
24 *
25 * could be represented as:
26 *
27 * #+name,total-blocks,used-blocks,available-blocks,used-percent,mounted-on
28 * procfs,4,4,0,100,/proc
29 *
30 * Data is then constrained to be sibling leaf values. In addition,
31 * singular leafs can also be matched. The costs include recording
32 * the specific leaf names (to ensure consistency) and some
33 * buffering.
34 *
35 * Some escaping is needed for CSV files, following the rules of RFC4180:
36 *
37 * - Fields containing a line-break, double-quote or commas should be
38 * quoted. (If they are not, the file will likely be impossible to
39 * process correctly).
40 * - A (double) quote character in a field must be represented by two
41 * (double) quote characters.
42 * - Leading and trialing whitespace require fields be quoted.
43 *
44 * Cheesy, but simple. The RFC also requires MS-DOS end-of-line,
45 * which we only do with the "dos" option. Strange that we still live
46 * in a DOS-friendly world, but then again, we make spaceships based
47 * on the horse butts (http://www.astrodigital.org/space/stshorse.html
48 * though the "built by English expatriates” bit is rubbish; better to
49 * say the first engines used in America were built by Englishmen.)
50 */
51
52 #include <string.h>
53 #include <sys/types.h>
54 #include <unistd.h>
55 #include <stdint.h>
56 #include <ctype.h>
57 #include <stdlib.h>
58 #include <limits.h>
59
60 #include "xo.h"
61 #include "xo_encoder.h"
62 #include "xo_buf.h"
63
64 #ifndef UNUSED
65 #define UNUSED __attribute__ ((__unused__))
66 #endif /* UNUSED */
67
68 /*
69 * The CSV encoder has three moving parts:
70 *
71 * - The path holds the path we are matching against
72 * - This is given as input via "options" and does not change
73 *
74 * - The stack holds the current names of the open elements
75 * - The "open" operations push, while the "close" pop
76 * - Turns out, at this point, the stack is unused, but I've
77 * left "drippings" in the code because I see this as useful
78 * for future features (under CSV_STACK_IS_NEEDED).
79 *
80 * - The leafs record the current set of leaf
81 * - A key from the parent list counts as a leaf (unless CF_NO_KEYS)
82 * - Once the path is matched, all other leafs at that level are leafs
83 * - Leafs are recorded to get the header comment accurately recorded
84 * - Once the first line is emited, the set of leafs _cannot_ change
85 *
86 * We use offsets into the buffers, since we know they can be
87 * realloc'd out from under us, as the size increases. The 'path'
88 * is fixed, we allocate it once, so it doesn't need offsets.
89 */
90 typedef struct path_frame_s {
91 char *pf_name; /* Path member name; points into c_path_buf */
92 uint32_t pf_flags; /* Flags for this path element (PFF_*) */
93 } path_frame_t;
94
95 typedef struct stack_frame_s {
96 ssize_t sf_off; /* Element name; offset in c_stack_buf */
97 uint32_t sf_flags; /* Flags for this frame (SFF_*) */
98 } stack_frame_t;
99
100 /* Flags for sf_flags */
101
102 typedef struct leaf_s {
103 ssize_t f_name; /* Name of leaf; offset in c_name_buf */
104 ssize_t f_value; /* Value of leaf; offset in c_value_buf */
105 uint32_t f_flags; /* Flags for this value (FF_*) */
106 #ifdef CSV_STACK_IS_NEEDED
107 ssize_t f_depth; /* Depth of stack when leaf was recorded */
108 #endif /* CSV_STACK_IS_NEEDED */
109 } leaf_t;
110
111 /* Flags for f_flags */
112 #define LF_KEY (1<<0) /* Leaf is a key */
113 #define LF_HAS_VALUE (1<<1) /* Value has been set */
114
115 typedef struct csv_private_s {
116 uint32_t c_flags; /* Flags for this encoder */
117
118 /* The path for which we select leafs */
119 char *c_path_buf; /* Buffer containing path members */
120 path_frame_t *c_path; /* Array of path members */
121 ssize_t c_path_max; /* Depth of c_path[] */
122 ssize_t c_path_cur; /* Current depth in c_path[] */
123
124 /* A stack of open elements (xo_op_list, xo_op_container) */
125 #if CSV_STACK_IS_NEEDED
126 xo_buffer_t c_stack_buf; /* Buffer used for stack content */
127 stack_frame_t *c_stack; /* Stack of open tags */
128 ssize_t c_stack_max; /* Maximum stack depth */
129 #endif /* CSV_STACK_IS_NEEDED */
130 ssize_t c_stack_depth; /* Current stack depth */
131
132 /* List of leafs we are emitting (to ensure consistency) */
133 xo_buffer_t c_name_buf; /* String buffer for leaf names */
134 xo_buffer_t c_value_buf; /* String buffer for leaf values */
135 leaf_t *c_leaf; /* List of leafs */
136 ssize_t c_leaf_depth; /* Current depth of c_leaf[] (next free) */
137 ssize_t c_leaf_max; /* Max depth of c_leaf[] */
138
139 xo_buffer_t c_data; /* Buffer for creating data */
140 } csv_private_t;
141
142 #define C_STACK_MAX 32 /* default c_stack_max */
143 #define C_LEAF_MAX 32 /* default c_leaf_max */
144
145 /* Flags for this structure */
146 #define CF_HEADER_DONE (1<<0) /* Have already written the header */
147 #define CF_NO_HEADER (1<<1) /* Do not generate header */
148 #define CF_NO_KEYS (1<<2) /* Do not generate excess keys */
149 #define CF_VALUE_ONLY (1<<3) /* Only generate the value */
150
151 #define CF_DOS_NEWLINE (1<<4) /* Generate CR-NL, just like MS-DOS */
152 #define CF_LEAFS_DONE (1<<5) /* Leafs are already been recorded */
153 #define CF_NO_QUOTES (1<<6) /* Do not generate quotes */
154 #define CF_RECORD_DATA (1<<7) /* Record all sibling leafs */
155
156 #define CF_DEBUG (1<<8) /* Make debug output */
157 #define CF_HAS_PATH (1<<9) /* A "path" option was provided */
158
159 /*
160 * A simple debugging print function, similar to psu_dbg. Controlled by
161 * the undocumented "debug" option.
162 */
163 static void
csv_dbg(xo_handle_t * xop UNUSED,csv_private_t * csv UNUSED,const char * fmt,...)164 csv_dbg (xo_handle_t *xop UNUSED, csv_private_t *csv UNUSED,
165 const char *fmt, ...)
166 {
167 if (csv == NULL || !(csv->c_flags & CF_DEBUG))
168 return;
169
170 va_list vap;
171
172 va_start(vap, fmt);
173 vfprintf(stderr, fmt, vap);
174 va_end(vap);
175 }
176
177 /*
178 * Create the private data for this handle, initialize it, and record
179 * the pointer in the handle.
180 */
181 static int
csv_create(xo_handle_t * xop)182 csv_create (xo_handle_t *xop)
183 {
184 csv_private_t *csv = xo_realloc(NULL, sizeof(*csv));
185 if (csv == NULL)
186 return -1;
187
188 bzero(csv, sizeof(*csv));
189 xo_buf_init(&csv->c_data);
190 xo_buf_init(&csv->c_name_buf);
191 xo_buf_init(&csv->c_value_buf);
192 #ifdef CSV_STACK_IS_NEEDED
193 xo_buf_init(&csv->c_stack_buf);
194 #endif /* CSV_STACK_IS_NEEDED */
195
196 xo_set_private(xop, csv);
197
198 return 0;
199 }
200
201 /*
202 * Clean up and release any data in use by this handle
203 */
204 static void
csv_destroy(xo_handle_t * xop UNUSED,csv_private_t * csv)205 csv_destroy (xo_handle_t *xop UNUSED, csv_private_t *csv)
206 {
207 /* Clean up */
208 xo_buf_cleanup(&csv->c_data);
209 xo_buf_cleanup(&csv->c_name_buf);
210 xo_buf_cleanup(&csv->c_value_buf);
211 #ifdef CSV_STACK_IS_NEEDED
212 xo_buf_cleanup(&csv->c_stack_buf);
213 #endif /* CSV_STACK_IS_NEEDED */
214
215 if (csv->c_leaf)
216 xo_free(csv->c_leaf);
217 if (csv->c_path_buf)
218 xo_free(csv->c_path_buf);
219 }
220
221 /*
222 * Return the element name at the top of the path stack. This is the
223 * item that we are currently trying to match on.
224 */
225 static const char *
csv_path_top(csv_private_t * csv,ssize_t delta)226 csv_path_top (csv_private_t *csv, ssize_t delta)
227 {
228 if (!(csv->c_flags & CF_HAS_PATH) || csv->c_path == NULL)
229 return NULL;
230
231 ssize_t cur = csv->c_path_cur + delta;
232
233 if (cur < 0)
234 return NULL;
235
236 return csv->c_path[cur].pf_name;
237 }
238
239 /*
240 * Underimplemented stack functionality
241 */
242 static inline void
csv_stack_push(csv_private_t * csv UNUSED,const char * name UNUSED)243 csv_stack_push (csv_private_t *csv UNUSED, const char *name UNUSED)
244 {
245 #ifdef CSV_STACK_IS_NEEDED
246 csv->c_stack_depth += 1;
247 #endif /* CSV_STACK_IS_NEEDED */
248 }
249
250 /*
251 * Underimplemented stack functionality
252 */
253 static inline void
csv_stack_pop(csv_private_t * csv UNUSED,const char * name UNUSED)254 csv_stack_pop (csv_private_t *csv UNUSED, const char *name UNUSED)
255 {
256 #ifdef CSV_STACK_IS_NEEDED
257 csv->c_stack_depth -= 1;
258 #endif /* CSV_STACK_IS_NEEDED */
259 }
260
261 /* Flags for csv_quote_flags */
262 #define QF_NEEDS_QUOTES (1<<0) /* Needs to be quoted */
263 #define QF_NEEDS_ESCAPE (1<<1) /* Needs to be escaped */
264
265 /*
266 * Determine how much quote processing is needed. The details of the
267 * quoting rules are given at the top of this file. We return a set
268 * of flags, indicating what's needed.
269 */
270 static uint32_t
csv_quote_flags(xo_handle_t * xop UNUSED,csv_private_t * csv UNUSED,const char * value)271 csv_quote_flags (xo_handle_t *xop UNUSED, csv_private_t *csv UNUSED,
272 const char *value)
273 {
274 static const char quoted[] = "\n\r\",";
275 static const char escaped[] = "\"";
276
277 if (csv->c_flags & CF_NO_QUOTES) /* User doesn't want quotes */
278 return 0;
279
280 size_t len = strlen(value);
281 uint32_t rc = 0;
282
283 if (strcspn(value, quoted) != len)
284 rc |= QF_NEEDS_QUOTES;
285 else if (isspace((int) value[0])) /* Leading whitespace */
286 rc |= QF_NEEDS_QUOTES;
287 else if (isspace((int) value[len - 1])) /* Trailing whitespace */
288 rc |= QF_NEEDS_QUOTES;
289
290 if (strcspn(value, escaped) != len)
291 rc |= QF_NEEDS_ESCAPE;
292
293 csv_dbg(xop, csv, "csv: quote flags [%s] -> %x (%zu/%zu)\n",
294 value, rc, len, strcspn(value, quoted));
295
296 return rc;
297 }
298
299 /*
300 * Escape the string, following the rules in RFC4180
301 */
302 static void
csv_escape(xo_buffer_t * xbp,const char * value,size_t len)303 csv_escape (xo_buffer_t *xbp, const char *value, size_t len)
304 {
305 const char *cp, *ep, *np;
306
307 for (cp = value, ep = value + len; cp && cp < ep; cp = np) {
308 np = strchr(cp, '"');
309 if (np) {
310 np += 1;
311 xo_buf_append(xbp, cp, np - cp);
312 xo_buf_append(xbp, "\"", 1);
313 } else
314 xo_buf_append(xbp, cp, ep - cp);
315 }
316 }
317
318 /*
319 * Append a newline to the buffer, following the settings of the "dos"
320 * flag.
321 */
322 static void
csv_append_newline(xo_buffer_t * xbp,csv_private_t * csv)323 csv_append_newline (xo_buffer_t *xbp, csv_private_t *csv)
324 {
325 if (csv->c_flags & CF_DOS_NEWLINE)
326 xo_buf_append(xbp, "\r\n", 2);
327 else
328 xo_buf_append(xbp, "\n", 1);
329 }
330
331 /*
332 * Create a 'record' of 'fields' from our recorded leaf values. If
333 * this is the first line and "no-header" isn't given, make a record
334 * containing the leaf names.
335 */
336 static void
csv_emit_record(xo_handle_t * xop,csv_private_t * csv)337 csv_emit_record (xo_handle_t *xop, csv_private_t *csv)
338 {
339 csv_dbg(xop, csv, "csv: emit: ...\n");
340
341 ssize_t fnum;
342 uint32_t quote_flags;
343 leaf_t *lp;
344
345 /* If we have no data, then don't bother */
346 if (csv->c_leaf_depth == 0)
347 return;
348
349 if (!(csv->c_flags & (CF_HEADER_DONE | CF_NO_HEADER))) {
350 csv->c_flags |= CF_HEADER_DONE;
351
352 for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {
353 lp = &csv->c_leaf[fnum];
354 const char *name = xo_buf_data(&csv->c_name_buf, lp->f_name);
355
356 if (fnum != 0)
357 xo_buf_append(&csv->c_data, ",", 1);
358
359 xo_buf_append(&csv->c_data, name, strlen(name));
360 }
361
362 csv_append_newline(&csv->c_data, csv);
363 }
364
365 for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {
366 lp = &csv->c_leaf[fnum];
367 const char *value;
368
369 if (lp->f_flags & LF_HAS_VALUE) {
370 value = xo_buf_data(&csv->c_value_buf, lp->f_value);
371 } else {
372 value = "";
373 }
374
375 quote_flags = csv_quote_flags(xop, csv, value);
376
377 if (fnum != 0)
378 xo_buf_append(&csv->c_data, ",", 1);
379
380 if (quote_flags & QF_NEEDS_QUOTES)
381 xo_buf_append(&csv->c_data, "\"", 1);
382
383 if (quote_flags & QF_NEEDS_ESCAPE)
384 csv_escape(&csv->c_data, value, strlen(value));
385 else
386 xo_buf_append(&csv->c_data, value, strlen(value));
387
388 if (quote_flags & QF_NEEDS_QUOTES)
389 xo_buf_append(&csv->c_data, "\"", 1);
390 }
391
392 csv_append_newline(&csv->c_data, csv);
393
394 /* We flush if either flush flag is set */
395 if (xo_get_flags(xop) & (XOF_FLUSH | XOF_FLUSH_LINE))
396 xo_flush_h(xop);
397
398 /* Clean out values from leafs */
399 for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {
400 lp = &csv->c_leaf[fnum];
401
402 lp->f_flags &= ~LF_HAS_VALUE;
403 lp->f_value = 0;
404 }
405
406 xo_buf_reset(&csv->c_value_buf);
407
408 /*
409 * Once we emit the first line, our set of leafs is locked and
410 * cannot be changed.
411 */
412 csv->c_flags |= CF_LEAFS_DONE;
413 }
414
415 /*
416 * Open a "level" of hierarchy, either a container or an instance. Look
417 * for a match in the path=x/y/z hierarchy, and ignore if not a match.
418 * If we're at the end of the path, start recording leaf values.
419 */
420 static int
csv_open_level(xo_handle_t * xop UNUSED,csv_private_t * csv,const char * name,int instance)421 csv_open_level (xo_handle_t *xop UNUSED, csv_private_t *csv,
422 const char *name, int instance)
423 {
424 /* An new "open" event means we stop recording */
425 if (csv->c_flags & CF_RECORD_DATA) {
426 csv->c_flags &= ~CF_RECORD_DATA;
427 csv_emit_record(xop, csv);
428 return 0;
429 }
430
431 const char *path_top = csv_path_top(csv, 0);
432
433 /* If the top of the stack does not match the name, then ignore */
434 if (path_top == NULL) {
435 if (instance && !(csv->c_flags & CF_HAS_PATH)) {
436 csv_dbg(xop, csv, "csv: recording (no-path) ...\n");
437 csv->c_flags |= CF_RECORD_DATA;
438 }
439
440 } else if (xo_streq(path_top, name)) {
441 csv->c_path_cur += 1; /* Advance to next path member */
442
443 csv_dbg(xop, csv, "csv: match: [%s] (%zd/%zd)\n", name,
444 csv->c_path_cur, csv->c_path_max);
445
446 /* If we're all the way thru the path members, start recording */
447 if (csv->c_path_cur == csv->c_path_max) {
448 csv_dbg(xop, csv, "csv: recording ...\n");
449 csv->c_flags |= CF_RECORD_DATA;
450 }
451 }
452
453 /* Push the name on the stack */
454 csv_stack_push(csv, name);
455
456 return 0;
457 }
458
459 /*
460 * Close a "level", either a container or an instance.
461 */
462 static int
csv_close_level(xo_handle_t * xop UNUSED,csv_private_t * csv,const char * name)463 csv_close_level (xo_handle_t *xop UNUSED, csv_private_t *csv, const char *name)
464 {
465 /* If we're recording, a close triggers an emit */
466 if (csv->c_flags & CF_RECORD_DATA) {
467 csv->c_flags &= ~CF_RECORD_DATA;
468 csv_emit_record(xop, csv);
469 }
470
471 const char *path_top = csv_path_top(csv, -1);
472 csv_dbg(xop, csv, "csv: close: [%s] [%s] (%zd)\n", name,
473 path_top ?: "", csv->c_path_cur);
474
475 /* If the top of the stack does not match the name, then ignore */
476 if (path_top != NULL && xo_streq(path_top, name)) {
477 csv->c_path_cur -= 1;
478 return 0;
479 }
480
481 /* Pop the name off the stack */
482 csv_stack_pop(csv, name);
483
484 return 0;
485 }
486
487 /*
488 * Return the index of a given leaf in the c_leaf[] array, where we
489 * record leaf values. If the leaf is new and we haven't stopped recording
490 * leafs, then make a new slot for it and record the name.
491 */
492 static int
csv_leaf_num(xo_handle_t * xop UNUSED,csv_private_t * csv,const char * name,xo_xff_flags_t flags)493 csv_leaf_num (xo_handle_t *xop UNUSED, csv_private_t *csv,
494 const char *name, xo_xff_flags_t flags)
495 {
496 ssize_t fnum;
497 leaf_t *lp;
498 xo_buffer_t *xbp = &csv->c_name_buf;
499
500 for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {
501 lp = &csv->c_leaf[fnum];
502
503 const char *fname = xo_buf_data(xbp, lp->f_name);
504 if (xo_streq(fname, name))
505 return fnum;
506 }
507
508 /* If we're done with adding new leafs, then bail */
509 if (csv->c_flags & CF_LEAFS_DONE)
510 return -1;
511
512 /* This leaf does not exist yet, so we need to create it */
513 /* Start by checking if there's enough room */
514 if (csv->c_leaf_depth + 1 >= csv->c_leaf_max) {
515 /* Out of room; realloc it */
516 ssize_t new_max = csv->c_leaf_max * 2;
517 if (new_max == 0)
518 new_max = C_LEAF_MAX;
519
520 lp = xo_realloc(csv->c_leaf, new_max * sizeof(*lp));
521 if (lp == NULL)
522 return -1; /* No luck; bail */
523
524 /* Zero out the new portion */
525 bzero(&lp[csv->c_leaf_max], csv->c_leaf_max * sizeof(*lp));
526
527 /* Update csv data */
528 csv->c_leaf = lp;
529 csv->c_leaf_max = new_max;
530 }
531
532 lp = &csv->c_leaf[csv->c_leaf_depth++];
533 #ifdef CSV_STACK_IS_NEEDED
534 lp->f_depth = csv->c_stack_depth;
535 #endif /* CSV_STACK_IS_NEEDED */
536
537 lp->f_name = xo_buf_offset(xbp);
538
539 char *cp = xo_buf_cur(xbp);
540 xo_buf_append(xbp, name, strlen(name) + 1);
541
542 if (flags & XFF_KEY)
543 lp->f_flags |= LF_KEY;
544
545 csv_dbg(xop, csv, "csv: leaf: name: %zd [%s] [%s] %x\n",
546 fnum, name, cp, lp->f_flags);
547
548 return fnum;
549 }
550
551 /*
552 * Record a new value for a leaf
553 */
554 static void
csv_leaf_set(xo_handle_t * xop UNUSED,csv_private_t * csv,leaf_t * lp,const char * value)555 csv_leaf_set (xo_handle_t *xop UNUSED, csv_private_t *csv, leaf_t *lp,
556 const char *value)
557 {
558 xo_buffer_t *xbp = &csv->c_value_buf;
559
560 lp->f_value = xo_buf_offset(xbp);
561 lp->f_flags |= LF_HAS_VALUE;
562
563 char *cp = xo_buf_cur(xbp);
564 xo_buf_append(xbp, value, strlen(value) + 1);
565
566 csv_dbg(xop, csv, "csv: leaf: value: [%s] [%s] %x\n",
567 value, cp, lp->f_flags);
568 }
569
570 /*
571 * Record the requested set of leaf names. The input should be a set
572 * of leaf names, separated by periods.
573 */
574 static int
csv_record_leafs(xo_handle_t * xop,csv_private_t * csv,const char * leafs_raw)575 csv_record_leafs (xo_handle_t *xop, csv_private_t *csv, const char *leafs_raw)
576 {
577 char *cp, *ep, *np;
578 ssize_t len = strlen(leafs_raw);
579 char *leafs_buf = alloca(len + 1);
580
581 memcpy(leafs_buf, leafs_raw, len + 1); /* Make local copy */
582
583 for (cp = leafs_buf, ep = leafs_buf + len; cp && cp < ep; cp = np) {
584 np = strchr(cp, '.');
585 if (np)
586 *np++ = '\0';
587
588 if (*cp == '\0') /* Skip empty names */
589 continue;
590
591 csv_dbg(xop, csv, "adding leaf: [%s]\n", cp);
592 csv_leaf_num(xop, csv, cp, 0);
593 }
594
595 /*
596 * Since we've been told explicitly what leafs matter, ignore the rest
597 */
598 csv->c_flags |= CF_LEAFS_DONE;
599
600 return 0;
601 }
602
603 /*
604 * Record the requested path elements. The input should be a set of
605 * container or instances names, separated by slashes.
606 */
607 static int
csv_record_path(xo_handle_t * xop,csv_private_t * csv,const char * path_raw)608 csv_record_path (xo_handle_t *xop, csv_private_t *csv, const char *path_raw)
609 {
610 int count;
611 char *cp, *ep, *np;
612 ssize_t len = strlen(path_raw);
613 char *path_buf = xo_realloc(NULL, len + 1);
614
615 memcpy(path_buf, path_raw, len + 1);
616
617 for (cp = path_buf, ep = path_buf + len, count = 2;
618 cp && cp < ep; cp = np) {
619 np = strchr(cp, '/');
620 if (np) {
621 np += 1;
622 count += 1;
623 }
624 }
625
626 path_frame_t *path = xo_realloc(NULL, sizeof(path[0]) * count);
627 if (path == NULL) {
628 xo_failure(xop, "allocation failure for path '%s'", path_buf);
629 return -1;
630 }
631
632 bzero(path, sizeof(path[0]) * count);
633
634 for (count = 0, cp = path_buf; cp && cp < ep; cp = np) {
635 path[count++].pf_name = cp;
636
637 np = strchr(cp, '/');
638 if (np)
639 *np++ = '\0';
640 csv_dbg(xop, csv, "path: [%s]\n", cp);
641 }
642
643 path[count].pf_name = NULL;
644
645 if (csv->c_path) /* In case two paths are given */
646 xo_free(csv->c_path);
647 if (csv->c_path_buf) /* In case two paths are given */
648 xo_free(csv->c_path_buf);
649
650 csv->c_path_buf = path_buf;
651 csv->c_path = path;
652 csv->c_path_max = count;
653 csv->c_path_cur = 0;
654
655 return 0;
656 }
657
658 /*
659 * Extract the option values. The format is:
660 * -libxo encoder=csv:kw=val:kw=val:kw=val,pretty
661 * -libxo encoder=csv+kw=val+kw=val+kw=val,pretty
662 */
663 static int
csv_options(xo_handle_t * xop,csv_private_t * csv,const char * raw_opts,char opts_char)664 csv_options (xo_handle_t *xop, csv_private_t *csv,
665 const char *raw_opts, char opts_char)
666 {
667 ssize_t len = strlen(raw_opts);
668 char *options = alloca(len + 1);
669 memcpy(options, raw_opts, len);
670 options[len] = '\0';
671
672 char *cp, *ep, *np, *vp;
673 for (cp = options, ep = options + len + 1; cp && cp < ep; cp = np) {
674 np = strchr(cp, opts_char);
675 if (np)
676 *np++ = '\0';
677
678 vp = strchr(cp, '=');
679 if (vp)
680 *vp++ = '\0';
681
682 if (xo_streq(cp, "path")) {
683 /* Record the path */
684 if (vp != NULL && csv_record_path(xop, csv, vp))
685 return -1;
686
687 csv->c_flags |= CF_HAS_PATH; /* Yup, we have an explicit path now */
688
689 } else if (xo_streq(cp, "leafs")
690 || xo_streq(cp, "leaf")
691 || xo_streq(cp, "leaves")) {
692 /* Record the leafs */
693 if (vp != NULL && csv_record_leafs(xop, csv, vp))
694 return -1;
695
696 } else if (xo_streq(cp, "no-keys")) {
697 csv->c_flags |= CF_NO_KEYS;
698 } else if (xo_streq(cp, "no-header")) {
699 csv->c_flags |= CF_NO_HEADER;
700 } else if (xo_streq(cp, "value-only")) {
701 csv->c_flags |= CF_VALUE_ONLY;
702 } else if (xo_streq(cp, "dos")) {
703 csv->c_flags |= CF_DOS_NEWLINE;
704 } else if (xo_streq(cp, "no-quotes")) {
705 csv->c_flags |= CF_NO_QUOTES;
706 } else if (xo_streq(cp, "debug")) {
707 csv->c_flags |= CF_DEBUG;
708 } else {
709 xo_warn_hc(xop, -1,
710 "unknown encoder option value: '%s'", cp);
711 return -1;
712 }
713 }
714
715 return 0;
716 }
717
718 /*
719 * Handler for incoming data values. We just record each leaf name and
720 * value. The values are emittd when the instance is closed.
721 */
722 static int
csv_data(xo_handle_t * xop UNUSED,csv_private_t * csv UNUSED,const char * name,const char * value,xo_xof_flags_t flags)723 csv_data (xo_handle_t *xop UNUSED, csv_private_t *csv UNUSED,
724 const char *name, const char *value,
725 xo_xof_flags_t flags)
726 {
727 csv_dbg(xop, csv, "data: [%s]=[%s] %llx\n", name, value, (unsigned long long) flags);
728
729 if (!(csv->c_flags & CF_RECORD_DATA))
730 return 0;
731
732 /* Find the leaf number */
733 int fnum = csv_leaf_num(xop, csv, name, flags);
734 if (fnum < 0)
735 return 0; /* Don't bother recording */
736
737 leaf_t *lp = &csv->c_leaf[fnum];
738 csv_leaf_set(xop, csv, lp, value);
739
740 return 0;
741 }
742
743 /*
744 * The callback from libxo, passing us operations/events as they
745 * happen.
746 */
747 static int
csv_handler(XO_ENCODER_HANDLER_ARGS)748 csv_handler (XO_ENCODER_HANDLER_ARGS)
749 {
750 int rc = 0;
751 csv_private_t *csv = private;
752 xo_buffer_t *xbp = csv ? &csv->c_data : NULL;
753
754 csv_dbg(xop, csv, "op %s: [%s] [%s]\n", xo_encoder_op_name(op),
755 name ?: "", value ?: "");
756 fflush(stdout);
757
758 /* If we don't have private data, we're sunk */
759 if (csv == NULL && op != XO_OP_CREATE)
760 return -1;
761
762 switch (op) {
763 case XO_OP_CREATE: /* Called when the handle is init'd */
764 rc = csv_create(xop);
765 break;
766
767 case XO_OP_OPTIONS:
768 rc = csv_options(xop, csv, value, ':');
769 break;
770
771 case XO_OP_OPTIONS_PLUS:
772 rc = csv_options(xop, csv, value, '+');
773 break;
774
775 case XO_OP_OPEN_LIST:
776 case XO_OP_CLOSE_LIST:
777 break; /* Ignore these ops */
778
779 case XO_OP_OPEN_CONTAINER:
780 case XO_OP_OPEN_LEAF_LIST:
781 rc = csv_open_level(xop, csv, name, 0);
782 break;
783
784 case XO_OP_OPEN_INSTANCE:
785 rc = csv_open_level(xop, csv, name, 1);
786 break;
787
788 case XO_OP_CLOSE_CONTAINER:
789 case XO_OP_CLOSE_LEAF_LIST:
790 case XO_OP_CLOSE_INSTANCE:
791 rc = csv_close_level(xop, csv, name);
792 break;
793
794 case XO_OP_STRING: /* Quoted UTF-8 string */
795 case XO_OP_CONTENT: /* Other content */
796 rc = csv_data(xop, csv, name, value, flags);
797 break;
798
799 case XO_OP_FINISH: /* Clean up function */
800 break;
801
802 case XO_OP_FLUSH: /* Clean up function */
803 rc = write(1, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
804 if (rc > 0)
805 rc = 0;
806
807 xo_buf_reset(xbp);
808 break;
809
810 case XO_OP_DESTROY: /* Clean up function */
811 csv_destroy(xop, csv);
812 break;
813
814 case XO_OP_ATTRIBUTE: /* Attribute name/value */
815 break;
816
817 case XO_OP_VERSION: /* Version string */
818 break;
819 }
820
821 return rc;
822 }
823
824 /*
825 * Callback when our encoder is loaded.
826 */
827 int
xo_encoder_library_init(XO_ENCODER_INIT_ARGS)828 xo_encoder_library_init (XO_ENCODER_INIT_ARGS)
829 {
830 arg->xei_handler = csv_handler;
831 arg->xei_version = XO_ENCODER_VERSION;
832
833 return 0;
834 }
835