Training courses

Kernel and Embedded Linux

Bootlin training courses

Embedded Linux, kernel,
Yocto Project, Buildroot, real-time,
graphics, boot time, debugging...

Bootlin logo

Elixir Cross Referencer

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
/*
 * Copyright (c) 2015, Juniper Networks, Inc.
 * All rights reserved.
 * This SOFTWARE is licensed under the LICENSE provided in the
 * ../Copyright file. By downloading, installing, copying, or otherwise
 * using the SOFTWARE, you agree to be bound by the terms of that
 * LICENSE.
 * Phil Shafer, August 2015
 */

/*
 * CSV encoder generates comma-separated value files for specific
 * subsets of data.  This is not (and cannot be) a generalized
 * facility, but for specific subsets of data, CSV data can be
 * reasonably generated.  For example, the df XML content:
 *     <filesystem>
 *      <name>procfs</name>
 *      <total-blocks>4</total-blocks>
 *      <used-blocks>4</used-blocks>
 *      <available-blocks>0</available-blocks>
 *      <used-percent>100</used-percent>
 *      <mounted-on>/proc</mounted-on>
 *    </filesystem>
 *
 * could be represented as:
 *
 *  #+name,total-blocks,used-blocks,available-blocks,used-percent,mounted-on
 *  procfs,4,4,0,100,/proc
 *
 * Data is then constrained to be sibling leaf values.  In addition,
 * singular leafs can also be matched.  The costs include recording
 * the specific leaf names (to ensure consistency) and some
 * buffering.
 *
 * Some escaping is needed for CSV files, following the rules of RFC4180:
 *
 * - Fields containing a line-break, double-quote or commas should be
 *   quoted. (If they are not, the file will likely be impossible to
 *   process correctly).
 * - A (double) quote character in a field must be represented by two
 *   (double) quote characters.
 * - Leading and trialing whitespace require fields be quoted.
 *
 * Cheesy, but simple.  The RFC also requires MS-DOS end-of-line,
 * which we only do with the "dos" option.  Strange that we still live
 * in a DOS-friendly world, but then again, we make spaceships based
 * on the horse butts (http://www.astrodigital.org/space/stshorse.html
 * though the "built by English expatriates” bit is rubbish; better to
 * say the first engines used in America were built by Englishmen.)
 */

#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdint.h>
#include <ctype.h>
#include <stdlib.h>
#include <limits.h>

#include "xo.h"
#include "xo_encoder.h"
#include "xo_buf.h"

#ifndef UNUSED
#define UNUSED __attribute__ ((__unused__))
#endif /* UNUSED */

/*
 * The CSV encoder has three moving parts:
 *
 * - The path holds the path we are matching against
 *   - This is given as input via "options" and does not change
 *
 * - The stack holds the current names of the open elements
 *   - The "open" operations push, while the "close" pop
 *   - Turns out, at this point, the stack is unused, but I've
 *     left "drippings" in the code because I see this as useful
 *     for future features (under CSV_STACK_IS_NEEDED).
 *
 * - The leafs record the current set of leaf
 *   - A key from the parent list counts as a leaf (unless CF_NO_KEYS)
 *   - Once the path is matched, all other leafs at that level are leafs
 *   - Leafs are recorded to get the header comment accurately recorded
 *   - Once the first line is emited, the set of leafs _cannot_ change
 *
 * We use offsets into the buffers, since we know they can be
 * realloc'd out from under us, as the size increases.  The 'path'
 * is fixed, we allocate it once, so it doesn't need offsets.
 */
typedef struct path_frame_s {
    char *pf_name;	       /* Path member name; points into c_path_buf */
    uint32_t pf_flags;	       /* Flags for this path element (PFF_*) */
} path_frame_t;

typedef struct stack_frame_s {
    ssize_t sf_off;		/* Element name; offset in c_stack_buf */
    uint32_t sf_flags;		/* Flags for this frame (SFF_*) */
} stack_frame_t;

/* Flags for sf_flags */

typedef struct leaf_s {
    ssize_t f_name;		/* Name of leaf; offset in c_name_buf */
    ssize_t f_value;		/* Value of leaf; offset in c_value_buf */
    uint32_t f_flags;		/* Flags for this value (FF_*)  */
#ifdef CSV_STACK_IS_NEEDED
    ssize_t f_depth;		/* Depth of stack when leaf was recorded */
#endif /* CSV_STACK_IS_NEEDED */
} leaf_t;

/* Flags for f_flags */
#define LF_KEY		(1<<0)	/* Leaf is a key */
#define LF_HAS_VALUE	(1<<1)	/* Value has been set */

typedef struct csv_private_s {
    uint32_t c_flags;		/* Flags for this encoder */

    /* The path for which we select leafs */
    char *c_path_buf;	    	/* Buffer containing path members */
    path_frame_t *c_path;	/* Array of path members */
    ssize_t c_path_max;		/* Depth of c_path[] */
    ssize_t c_path_cur;		/* Current depth in c_path[] */

    /* A stack of open elements (xo_op_list, xo_op_container) */
#if CSV_STACK_IS_NEEDED
    xo_buffer_t c_stack_buf;	/* Buffer used for stack content */
    stack_frame_t *c_stack;	/* Stack of open tags */
    ssize_t c_stack_max;	/* Maximum stack depth */
#endif /* CSV_STACK_IS_NEEDED */
    ssize_t c_stack_depth;	/* Current stack depth */

    /* List of leafs we are emitting (to ensure consistency) */
    xo_buffer_t c_name_buf;	/* String buffer for leaf names */
    xo_buffer_t c_value_buf;	/* String buffer for leaf values */
    leaf_t *c_leaf;		/* List of leafs */
    ssize_t c_leaf_depth;	/* Current depth of c_leaf[] (next free) */
    ssize_t c_leaf_max;		/* Max depth of c_leaf[] */

    xo_buffer_t c_data;		/* Buffer for creating data */
} csv_private_t;

#define C_STACK_MAX	32	/* default c_stack_max */
#define C_LEAF_MAX	32	/* default c_leaf_max */

/* Flags for this structure */
#define CF_HEADER_DONE	(1<<0)	/* Have already written the header */
#define CF_NO_HEADER	(1<<1)	/* Do not generate header */
#define CF_NO_KEYS	(1<<2)	/* Do not generate excess keys */
#define CF_VALUE_ONLY	(1<<3)	/* Only generate the value */

#define CF_DOS_NEWLINE	(1<<4)	/* Generate CR-NL, just like MS-DOS */
#define CF_LEAFS_DONE	(1<<5)	/* Leafs are already been recorded */
#define CF_NO_QUOTES	(1<<6)	/* Do not generate quotes */
#define CF_RECORD_DATA	(1<<7)	/* Record all sibling leafs */

#define CF_DEBUG	(1<<8)	/* Make debug output */
#define CF_HAS_PATH	(1<<9)	/* A "path" option was provided */

/*
 * A simple debugging print function, similar to psu_dbg.  Controlled by
 * the undocumented "debug" option.
 */
static void
csv_dbg (xo_handle_t *xop UNUSED, csv_private_t *csv UNUSED,
	 const char *fmt, ...)
{
    if (csv == NULL || !(csv->c_flags & CF_DEBUG))
	return;

    va_list vap;

    va_start(vap, fmt);
    vfprintf(stderr, fmt, vap);
    va_end(vap);
}

/*
 * Create the private data for this handle, initialize it, and record
 * the pointer in the handle.
 */
static int
csv_create (xo_handle_t *xop)
{
    csv_private_t *csv = xo_realloc(NULL, sizeof(*csv));
    if (csv == NULL)
	return -1;

    bzero(csv, sizeof(*csv));
    xo_buf_init(&csv->c_data);
    xo_buf_init(&csv->c_name_buf);
    xo_buf_init(&csv->c_value_buf);
#ifdef CSV_STACK_IS_NEEDED
    xo_buf_init(&csv->c_stack_buf);
#endif /* CSV_STACK_IS_NEEDED */

    xo_set_private(xop, csv);

    return 0;
}

/*
 * Clean up and release any data in use by this handle
 */
static void
csv_destroy (xo_handle_t *xop UNUSED, csv_private_t *csv)
{
    /* Clean up */
    xo_buf_cleanup(&csv->c_data);
    xo_buf_cleanup(&csv->c_name_buf);
    xo_buf_cleanup(&csv->c_value_buf);
#ifdef CSV_STACK_IS_NEEDED
    xo_buf_cleanup(&csv->c_stack_buf);
#endif /* CSV_STACK_IS_NEEDED */

    if (csv->c_leaf)
	xo_free(csv->c_leaf);
    if (csv->c_path_buf)
	xo_free(csv->c_path_buf);
}

/*
 * Return the element name at the top of the path stack.  This is the
 * item that we are currently trying to match on.
 */
static const char *
csv_path_top (csv_private_t *csv, ssize_t delta)
{
    if (!(csv->c_flags & CF_HAS_PATH) || csv->c_path == NULL)
	return NULL;

    ssize_t cur = csv->c_path_cur + delta;

    if (cur < 0)
	return NULL;

    return csv->c_path[cur].pf_name;
}

/*
 * Underimplemented stack functionality
 */
static inline void
csv_stack_push (csv_private_t *csv UNUSED, const char *name UNUSED)
{
#ifdef CSV_STACK_IS_NEEDED
    csv->c_stack_depth += 1;
#endif /* CSV_STACK_IS_NEEDED */
}

/*
 * Underimplemented stack functionality
 */
static inline void
csv_stack_pop (csv_private_t *csv UNUSED, const char *name UNUSED)
{
#ifdef CSV_STACK_IS_NEEDED
    csv->c_stack_depth -= 1;
#endif /* CSV_STACK_IS_NEEDED */
}

/* Flags for csv_quote_flags */
#define QF_NEEDS_QUOTES	(1<<0)		/* Needs to be quoted */
#define QF_NEEDS_ESCAPE	(1<<1)		/* Needs to be escaped */

/*
 * Determine how much quote processing is needed.  The details of the
 * quoting rules are given at the top of this file.  We return a set
 * of flags, indicating what's needed.
 */
static uint32_t
csv_quote_flags (xo_handle_t *xop UNUSED, csv_private_t *csv UNUSED,
		  const char *value)
{
    static const char quoted[] = "\n\r\",";
    static const char escaped[] = "\"";

    if (csv->c_flags & CF_NO_QUOTES)	/* User doesn't want quotes */
	return 0;

    size_t len = strlen(value);
    uint32_t rc = 0;

    if (strcspn(value, quoted) != len)
	rc |= QF_NEEDS_QUOTES;
    else if (isspace((int) value[0]))	/* Leading whitespace */
	rc |= QF_NEEDS_QUOTES;
    else if (isspace((int) value[len - 1])) /* Trailing whitespace */
	rc |= QF_NEEDS_QUOTES;

    if (strcspn(value, escaped) != len)
	rc |= QF_NEEDS_ESCAPE;

    csv_dbg(xop, csv, "csv: quote flags [%s] -> %x (%zu/%zu)\n",
	    value, rc, len, strcspn(value, quoted));

    return rc;
}

/*
 * Escape the string, following the rules in RFC4180
 */
static void
csv_escape (xo_buffer_t *xbp, const char *value, size_t len)
{
    const char *cp, *ep, *np;

    for (cp = value, ep = value + len; cp && cp < ep; cp = np) {
	np = strchr(cp, '"');
	if (np) {
	    np += 1;
	    xo_buf_append(xbp, cp, np - cp);
	    xo_buf_append(xbp, "\"", 1);
	} else
	    xo_buf_append(xbp, cp, ep - cp);
    }
}

/*
 * Append a newline to the buffer, following the settings of the "dos"
 * flag.
 */
static void
csv_append_newline (xo_buffer_t *xbp, csv_private_t *csv)
{
    if (csv->c_flags & CF_DOS_NEWLINE)
	xo_buf_append(xbp, "\r\n", 2);
    else 
	xo_buf_append(xbp, "\n", 1);
}

/*
 * Create a 'record' of 'fields' from our recorded leaf values.  If
 * this is the first line and "no-header" isn't given, make a record
 * containing the leaf names.
 */
static void
csv_emit_record (xo_handle_t *xop, csv_private_t *csv)
{
    csv_dbg(xop, csv, "csv: emit: ...\n");

    ssize_t fnum;
    uint32_t quote_flags;
    leaf_t *lp;

    /* If we have no data, then don't bother */
    if (csv->c_leaf_depth == 0)
	return;

    if (!(csv->c_flags & (CF_HEADER_DONE | CF_NO_HEADER))) {
	csv->c_flags |= CF_HEADER_DONE;

	for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {
	    lp = &csv->c_leaf[fnum];
	    const char *name = xo_buf_data(&csv->c_name_buf, lp->f_name);

	    if (fnum != 0)
		xo_buf_append(&csv->c_data, ",", 1);

	    xo_buf_append(&csv->c_data, name, strlen(name));
	}

	csv_append_newline(&csv->c_data, csv);
    }

    for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {
	lp = &csv->c_leaf[fnum];
	const char *value;

	if (lp->f_flags & LF_HAS_VALUE) {
	    value = xo_buf_data(&csv->c_value_buf, lp->f_value);
	} else {
	    value = "";
	}

	quote_flags = csv_quote_flags(xop, csv, value);

	if (fnum != 0)
	    xo_buf_append(&csv->c_data, ",", 1);

	if (quote_flags & QF_NEEDS_QUOTES)
	    xo_buf_append(&csv->c_data, "\"", 1);

	if (quote_flags & QF_NEEDS_ESCAPE)
	    csv_escape(&csv->c_data, value, strlen(value));
	else
	    xo_buf_append(&csv->c_data, value, strlen(value));

	if (quote_flags & QF_NEEDS_QUOTES)
	    xo_buf_append(&csv->c_data, "\"", 1);
    }

    csv_append_newline(&csv->c_data, csv);

    /* We flush if either flush flag is set */
    if (xo_get_flags(xop) & (XOF_FLUSH | XOF_FLUSH_LINE))
	xo_flush_h(xop);

    /* Clean out values from leafs */
    for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {
	lp = &csv->c_leaf[fnum];

	lp->f_flags &= ~LF_HAS_VALUE;
	lp->f_value = 0;
    }

    xo_buf_reset(&csv->c_value_buf);

    /*
     * Once we emit the first line, our set of leafs is locked and
     * cannot be changed.
     */
    csv->c_flags |= CF_LEAFS_DONE;
}

/*
 * Open a "level" of hierarchy, either a container or an instance.  Look
 * for a match in the path=x/y/z hierarchy, and ignore if not a match.
 * If we're at the end of the path, start recording leaf values.
 */
static int
csv_open_level (xo_handle_t *xop UNUSED, csv_private_t *csv,
		const char *name, int instance)
{
    /* An new "open" event means we stop recording */
    if (csv->c_flags & CF_RECORD_DATA) {
	csv->c_flags &= ~CF_RECORD_DATA;
	csv_emit_record(xop, csv);
	return 0;
    }

    const char *path_top = csv_path_top(csv, 0);

    /* If the top of the stack does not match the name, then ignore */
    if (path_top == NULL) {
	if (instance && !(csv->c_flags & CF_HAS_PATH)) {
	    csv_dbg(xop, csv, "csv: recording (no-path) ...\n");
	    csv->c_flags |= CF_RECORD_DATA;
	}

    } else if (xo_streq(path_top, name)) {
	csv->c_path_cur += 1;		/* Advance to next path member */

	csv_dbg(xop, csv, "csv: match: [%s] (%zd/%zd)\n", name,
	       csv->c_path_cur, csv->c_path_max);

	/* If we're all the way thru the path members, start recording */
	if (csv->c_path_cur == csv->c_path_max) {
	    csv_dbg(xop, csv, "csv: recording ...\n");
	    csv->c_flags |= CF_RECORD_DATA;
	}
    }

    /* Push the name on the stack */
    csv_stack_push(csv, name);

    return 0;
}

/*
 * Close a "level", either a container or an instance.
 */
static int
csv_close_level (xo_handle_t *xop UNUSED, csv_private_t *csv, const char *name)
{
    /* If we're recording, a close triggers an emit */
    if (csv->c_flags & CF_RECORD_DATA) {
	csv->c_flags &= ~CF_RECORD_DATA;
	csv_emit_record(xop, csv);
    }

    const char *path_top = csv_path_top(csv, -1);
    csv_dbg(xop, csv, "csv: close: [%s] [%s] (%zd)\n", name,
	   path_top ?: "", csv->c_path_cur);

    /* If the top of the stack does not match the name, then ignore */
    if (path_top != NULL && xo_streq(path_top, name)) {
	csv->c_path_cur -= 1;
	return 0;
    }

    /* Pop the name off the stack */
    csv_stack_pop(csv, name);

    return 0;
}

/*
 * Return the index of a given leaf in the c_leaf[] array, where we
 * record leaf values.  If the leaf is new and we haven't stopped recording
 * leafs, then make a new slot for it and record the name.
 */
static int
csv_leaf_num (xo_handle_t *xop UNUSED, csv_private_t *csv,
	       const char *name, xo_xff_flags_t flags)
{
    ssize_t fnum;
    leaf_t *lp;
    xo_buffer_t *xbp = &csv->c_name_buf;

    for (fnum = 0; fnum < csv->c_leaf_depth; fnum++) {
	lp = &csv->c_leaf[fnum];

	const char *fname = xo_buf_data(xbp, lp->f_name);
	if (xo_streq(fname, name))
	    return fnum;
    }

    /* If we're done with adding new leafs, then bail */
    if (csv->c_flags & CF_LEAFS_DONE)
	return -1;

    /* This leaf does not exist yet, so we need to create it */
    /* Start by checking if there's enough room */
    if (csv->c_leaf_depth + 1 >= csv->c_leaf_max) {
	/* Out of room; realloc it */
	ssize_t new_max = csv->c_leaf_max * 2;
	if (new_max == 0)
	    new_max = C_LEAF_MAX;

	lp = xo_realloc(csv->c_leaf, new_max * sizeof(*lp));
	if (lp == NULL)
	    return -1;			/* No luck; bail */

	/* Zero out the new portion */
	bzero(&lp[csv->c_leaf_max], csv->c_leaf_max * sizeof(*lp));

	/* Update csv data */
	csv->c_leaf = lp;
	csv->c_leaf_max = new_max;
    }

    lp = &csv->c_leaf[csv->c_leaf_depth++];
#ifdef CSV_STACK_IS_NEEDED
    lp->f_depth = csv->c_stack_depth;
#endif /* CSV_STACK_IS_NEEDED */

    lp->f_name = xo_buf_offset(xbp);

    char *cp = xo_buf_cur(xbp);
    xo_buf_append(xbp, name, strlen(name) + 1);

    if (flags & XFF_KEY)
	lp->f_flags |= LF_KEY;

    csv_dbg(xop, csv, "csv: leaf: name: %zd [%s] [%s] %x\n",
	    fnum, name, cp, lp->f_flags);

    return fnum;
}

/*
 * Record a new value for a leaf
 */
static void
csv_leaf_set (xo_handle_t *xop UNUSED, csv_private_t *csv, leaf_t *lp,
	       const char *value)
{
    xo_buffer_t *xbp = &csv->c_value_buf;

    lp->f_value = xo_buf_offset(xbp);
    lp->f_flags |= LF_HAS_VALUE;

    char *cp = xo_buf_cur(xbp);
    xo_buf_append(xbp, value, strlen(value) + 1);

    csv_dbg(xop, csv, "csv: leaf: value: [%s] [%s] %x\n",
	    value, cp, lp->f_flags);
}

/*
 * Record the requested set of leaf names.  The input should be a set
 * of leaf names, separated by periods.
 */
static int
csv_record_leafs (xo_handle_t *xop, csv_private_t *csv, const char *leafs_raw)
{
    char *cp, *ep, *np;
    ssize_t len = strlen(leafs_raw);
    char *leafs_buf = alloca(len + 1);

    memcpy(leafs_buf, leafs_raw, len + 1); /* Make local copy */

    for (cp = leafs_buf, ep = leafs_buf + len; cp && cp < ep; cp = np) {
	np = strchr(cp, '.');
	if (np)
	    *np++ = '\0';

	if (*cp == '\0')		/* Skip empty names */
	    continue;

	csv_dbg(xop, csv, "adding leaf: [%s]\n", cp);
	csv_leaf_num(xop, csv, cp, 0);
    }

    /*
     * Since we've been told explicitly what leafs matter, ignore the rest
     */
    csv->c_flags |= CF_LEAFS_DONE;

    return 0;
}

/*
 * Record the requested path elements.  The input should be a set of
 * container or instances names, separated by slashes.
 */
static int
csv_record_path (xo_handle_t *xop, csv_private_t *csv, const char *path_raw)
{
    int count;
    char *cp, *ep, *np;
    ssize_t len = strlen(path_raw);
    char *path_buf = xo_realloc(NULL, len + 1);

    memcpy(path_buf, path_raw, len + 1);

    for (cp = path_buf, ep = path_buf + len, count = 2;
	 cp && cp < ep; cp = np) {
	np = strchr(cp, '/');
	if (np) {
	    np += 1;
	    count += 1;
	}
    }

    path_frame_t *path = xo_realloc(NULL, sizeof(path[0]) * count);
    if (path == NULL) {
	xo_failure(xop, "allocation failure for path '%s'", path_buf);
	return -1;
    }

    bzero(path, sizeof(path[0]) * count);

    for (count = 0, cp = path_buf; cp && cp < ep; cp = np) {
	path[count++].pf_name = cp;

	np = strchr(cp, '/');
	if (np)
	    *np++ = '\0';
	csv_dbg(xop, csv, "path: [%s]\n", cp);
    }

    path[count].pf_name = NULL;

    if (csv->c_path)		     /* In case two paths are given */
	xo_free(csv->c_path);
    if (csv->c_path_buf)	     /* In case two paths are given */
	xo_free(csv->c_path_buf);

    csv->c_path_buf = path_buf;
    csv->c_path = path;
    csv->c_path_max = count;
    csv->c_path_cur = 0;

    return 0;
}

/*
 * Extract the option values.  The format is:
 *    -libxo encoder=csv:kw=val:kw=val:kw=val,pretty
 *    -libxo encoder=csv+kw=val+kw=val+kw=val,pretty
 */
static int
csv_options (xo_handle_t *xop, csv_private_t *csv,
	     const char *raw_opts, char opts_char)
{
    ssize_t len = strlen(raw_opts);
    char *options = alloca(len + 1);
    memcpy(options, raw_opts, len);
    options[len] = '\0';

    char *cp, *ep, *np, *vp;
    for (cp = options, ep = options + len + 1; cp && cp < ep; cp = np) {
	np = strchr(cp, opts_char);
	if (np)
	    *np++ = '\0';

	vp = strchr(cp, '=');
	if (vp)
	    *vp++ = '\0';

	if (xo_streq(cp, "path")) {
	    /* Record the path */
	    if (vp != NULL && csv_record_path(xop, csv, vp))
  		return -1;

	    csv->c_flags |= CF_HAS_PATH; /* Yup, we have an explicit path now */

	} else if (xo_streq(cp, "leafs")
		   || xo_streq(cp, "leaf")
		   || xo_streq(cp, "leaves")) {
	    /* Record the leafs */
	    if (vp != NULL && csv_record_leafs(xop, csv, vp))
  		return -1;

	} else if (xo_streq(cp, "no-keys")) {
	    csv->c_flags |= CF_NO_KEYS;
	} else if (xo_streq(cp, "no-header")) {
	    csv->c_flags |= CF_NO_HEADER;
	} else if (xo_streq(cp, "value-only")) {
	    csv->c_flags |= CF_VALUE_ONLY;
	} else if (xo_streq(cp, "dos")) {
	    csv->c_flags |= CF_DOS_NEWLINE;
	} else if (xo_streq(cp, "no-quotes")) {
	    csv->c_flags |= CF_NO_QUOTES;
	} else if (xo_streq(cp, "debug")) {
	    csv->c_flags |= CF_DEBUG;
	} else {
	    xo_warn_hc(xop, -1,
		       "unknown encoder option value: '%s'", cp);
	    return -1;
	}
    }

    return 0;
}

/*
 * Handler for incoming data values.  We just record each leaf name and
 * value.  The values are emittd when the instance is closed.
 */
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)
{
    csv_dbg(xop, csv, "data: [%s]=[%s] %llx\n", name, value, (unsigned long long) flags);

    if (!(csv->c_flags & CF_RECORD_DATA))
	return 0;

    /* Find the leaf number */
    int fnum = csv_leaf_num(xop, csv, name, flags);
    if (fnum < 0)
	return 0;			/* Don't bother recording */

    leaf_t *lp = &csv->c_leaf[fnum];
    csv_leaf_set(xop, csv, lp, value);

    return 0;
}

/*
 * The callback from libxo, passing us operations/events as they
 * happen.
 */
static int
csv_handler (XO_ENCODER_HANDLER_ARGS)
{
    int rc = 0;
    csv_private_t *csv = private;
    xo_buffer_t *xbp = csv ? &csv->c_data : NULL;

    csv_dbg(xop, csv, "op %s: [%s] [%s]\n",  xo_encoder_op_name(op),
	   name ?: "", value ?: "");
    fflush(stdout);

    /* If we don't have private data, we're sunk */
    if (csv == NULL && op != XO_OP_CREATE)
	return -1;

    switch (op) {
    case XO_OP_CREATE:		/* Called when the handle is init'd */
	rc = csv_create(xop);
	break;

    case XO_OP_OPTIONS:
	rc = csv_options(xop, csv, value, ':');
	break;

    case XO_OP_OPTIONS_PLUS:
	rc = csv_options(xop, csv, value, '+');
	break;

    case XO_OP_OPEN_LIST:
    case XO_OP_CLOSE_LIST:
	break;				/* Ignore these ops */

    case XO_OP_OPEN_CONTAINER:
    case XO_OP_OPEN_LEAF_LIST:
	rc = csv_open_level(xop, csv, name, 0);
	break;

    case XO_OP_OPEN_INSTANCE:
	rc = csv_open_level(xop, csv, name, 1);
	break;

    case XO_OP_CLOSE_CONTAINER:
    case XO_OP_CLOSE_LEAF_LIST:
    case XO_OP_CLOSE_INSTANCE:
	rc = csv_close_level(xop, csv, name);
	break;

    case XO_OP_STRING:		   /* Quoted UTF-8 string */
    case XO_OP_CONTENT:		   /* Other content */
	rc = csv_data(xop, csv, name, value, flags);
	break;

    case XO_OP_FINISH:		   /* Clean up function */
	break;

    case XO_OP_FLUSH:		   /* Clean up function */
	rc = write(1, xbp->xb_bufp, xbp->xb_curp - xbp->xb_bufp);
	if (rc > 0)
	    rc = 0;

	xo_buf_reset(xbp);
	break;

    case XO_OP_DESTROY:		   /* Clean up function */
	csv_destroy(xop, csv);
	break;

    case XO_OP_ATTRIBUTE:	   /* Attribute name/value */
	break;

    case XO_OP_VERSION:		/* Version string */
	break;
    }

    return rc;
}

/*
 * Callback when our encoder is loaded.
 */
int
xo_encoder_library_init (XO_ENCODER_INIT_ARGS)
{
    arg->xei_handler = csv_handler;
    arg->xei_version = XO_ENCODER_VERSION;

    return 0;
}