Tizen 2.0 Release
[platform/core/messaging/email-service.git] / email-common-use / tpl.c
1 /*
2 Copyright (c) 2005-2010, Troy D. Hanson     http://tpl.sourceforge.net
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
7
8     * Redistributions of source code must retain the above copyright
9       notice, this list of conditions and the following disclaimer.
10
11 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
12 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
13 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
14 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
15 OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
16 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
17 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
18 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
19 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
20 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22 */
23
24 #define TPL_VERSION 1.5
25
26 static const char id[]="$Id: tpl.c 192 2009-04-24 10:35:30Z thanson $";
27
28
29 #include <stdlib.h>  /* malloc */
30 #include <stdarg.h>  /* va_list */
31 #include <string.h>  /* memcpy, memset, strchr */
32 #include <stdio.h>   /* printf (tpl_hook.oops default function) */
33
34 #ifndef _WIN32
35 #include <unistd.h>     /* for ftruncate */
36 #else
37 #include <io.h>
38 #define ftruncate(x,y) _chsize(x,y)
39 #endif
40 #include <sys/types.h>  /* for 'open' */
41 #include <sys/stat.h>   /* for 'open' */
42 #include <fcntl.h>      /* for 'open' */
43 #include <errno.h>
44 #ifndef _WIN32
45 #include <inttypes.h>   /* uint32_t, uint64_t, etc */
46 #else
47 typedef unsigned short ushort;
48 typedef __int16 int16_t;
49 typedef __int32 int32_t;
50 typedef __int64 int64_t;
51 typedef unsigned __int16 uint16_t;
52 typedef unsigned __int32 uint32_t;
53 typedef unsigned __int64 uint64_t;
54 #endif
55
56
57 #if ( defined __CYGWIN__ || defined __MINGW32__ || defined _WIN32 )
58 #include "win/mman.h"   /* mmap */
59 #else
60 #include <sys/mman.h>   /* mmap */
61 #endif
62
63 #include "tpl.h"
64 #include "email-debug-log.h"
65
66
67 #define TPL_GATHER_BUFLEN 8192
68 #define TPL_MAGIC "tpl"
69
70 /* macro to add a structure to a doubly-linked list */
71 #define DL_ADD(head,add)                                        \
72     do {                                                        \
73         if (head) {                                             \
74             (add)->prev = (head)->prev;                         \
75             (head)->prev->next = (add);                         \
76             (head)->prev = (add);                               \
77             (add)->next = NULL;                                 \
78         } else {                                                \
79             (head)=(add);                                       \
80             (head)->prev = (head);                              \
81             (head)->next = NULL;                                \
82         }                                                       \
83     } while (0);
84
85 #define fatal_oom() tpl_hook.fatal("out of memory\n")
86
87 /* bit flags (internal). preceded by the external flags in tpl.h */
88 #define TPL_WRONLY         (1 << 9)  /* app has initiated tpl packing  */
89 #define TPL_RDONLY         (1 << 10)  /* tpl was loaded (for unpacking) */
90 #define TPL_XENDIAN        (1 << 11)  /* swap endianness when unpacking */
91 #define TPL_OLD_STRING_FMT (1 << 12) /* tpl has strings in 1.2 format */
92
93 /* values for the flags byte that appears after the magic prefix */
94 #define TPL_SUPPORTED_BITFLAGS 3
95 #define TPL_FL_BIGENDIAN   (1 << 0)
96 #define TPL_FL_NULLSTRINGS (1 << 1)
97
98 /* char values for node type */
99 #define TPL_TYPE_ROOT   0
100 #define TPL_TYPE_INT32  1
101 #define TPL_TYPE_UINT32 2
102 #define TPL_TYPE_BYTE   3
103 #define TPL_TYPE_STR    4
104 #define TPL_TYPE_ARY    5
105 #define TPL_TYPE_BIN    6
106 #define TPL_TYPE_DOUBLE 7
107 #define TPL_TYPE_INT64  8
108 #define TPL_TYPE_UINT64 9
109 #define TPL_TYPE_INT16  10
110 #define TPL_TYPE_UINT16 11
111 #define TPL_TYPE_POUND  12
112
113 /* error codes */
114 #define ERR_NOT_MINSIZE        (-1)
115 #define ERR_MAGIC_MISMATCH     (-2)
116 #define ERR_INCONSISTENT_SZ    (-3)
117 #define ERR_FMT_INVALID        (-4)
118 #define ERR_FMT_MISSING_NUL    (-5)
119 #define ERR_FMT_MISMATCH       (-6)
120 #define ERR_FLEN_MISMATCH      (-7)
121 #define ERR_INCONSISTENT_SZ2   (-8)
122 #define ERR_INCONSISTENT_SZ3   (-9)
123 #define ERR_INCONSISTENT_SZ4   (-10)
124 #define ERR_UNSUPPORTED_FLAGS  (-11)
125
126 /* access to A(...) nodes by index */
127 typedef struct tpl_pidx {
128     struct tpl_node *node;
129     struct tpl_pidx *next,*prev;
130 } tpl_pidx;
131
132 /* A(...) node datum */
133 typedef struct tpl_atyp {
134     uint32_t num;    /* num elements */
135     size_t sz;       /* size of each backbone's datum */
136     struct tpl_backbone *bb,*bbtail;
137     void *cur;
138 } tpl_atyp;
139
140 /* backbone to extend A(...) lists dynamically */
141 typedef struct tpl_backbone {
142     struct tpl_backbone *next;
143     /* when this structure is malloc'd, extra space is alloc'd at the
144      * end to store the backbone "datum", and data points to it. */
145 #if __STDC_VERSION__ < 199901
146     char *data;
147 #else
148     char data[];
149 #endif
150 } tpl_backbone;
151
152 /* mmap record */
153 typedef struct tpl_mmap_rec {
154     int fd;
155     void *text;
156     size_t text_sz;
157 } tpl_mmap_rec;
158
159 /* root node datum */
160 typedef struct tpl_root_data {
161     int flags;
162     tpl_pidx *pidx;
163     tpl_mmap_rec mmap;
164     char *fmt;
165     int *fxlens, num_fxlens;
166 } tpl_root_data;
167
168 /* node type to size mapping */
169 struct tpl_type_t {
170     char c;
171     int sz;
172 };
173
174
175 /* Internal prototypes */
176 static tpl_node *tpl_node_new(tpl_node *parent);
177 static tpl_node *tpl_find_i(tpl_node *n, int i);
178 static void *tpl_cpv(void *datav, void *data, size_t sz);
179 static void *tpl_extend_backbone(tpl_node *n);
180 static char *tpl_fmt(tpl_node *r);
181 static void *tpl_dump_atyp(tpl_node *n, tpl_atyp* at, void *dv);
182 static size_t tpl_ser_osz(tpl_node *n);
183 static void tpl_free_atyp(tpl_node *n,tpl_atyp *atyp);
184 static int tpl_dump_to_mem(tpl_node *r, void *addr, size_t sz);
185 static int tpl_mmap_file(char *filename, tpl_mmap_rec *map_rec);
186 static int tpl_mmap_output_file(char *filename, size_t sz, void **text_out);
187 static int tpl_cpu_bigendian(void);
188 static int tpl_needs_endian_swap(void *);
189 static void tpl_byteswap(void *word, int len);
190 static void tpl_fatal(char *fmt, ...);
191 static int tpl_serlen(tpl_node *r, tpl_node *n, void *dv, size_t *serlen);
192 static int tpl_unpackA0(tpl_node *r);
193 static int tpl_oops(const char *fmt, ...);
194 static int tpl_gather_mem( char *buf, size_t len, tpl_gather_t **gs, tpl_gather_cb *cb, void *data);
195 static int tpl_gather_nonblocking( int fd, tpl_gather_t **gs, tpl_gather_cb *cb, void *data);
196 static int tpl_gather_blocking(int fd, void **img, size_t *sz);
197 static tpl_node *tpl_map_va(char *fmt, va_list ap);
198
199 /* This is used internally to help calculate padding when a 'double'
200  * follows a smaller datatype in a structure. Normally under gcc
201  * on x86, d will be aligned at +4, however use of -malign-double
202  * causes d to be aligned at +8 (this is actually faster on x86).
203  * Also SPARC and x86_64 seem to align always on +8.
204  */
205 struct tpl_double_alignment_detector {
206     char a;
207     double d;  /* some platforms align this on +4, others on +8 */
208 };
209
210 /* this is another case where alignment varies. mac os x/gcc was observed
211  * to align the int64_t at +4 under -m32 and at +8 under -m64 */
212 struct tpl_int64_alignment_detector {
213     int i;
214     int64_t j;  /* some platforms align this on +4, others on +8 */
215 };
216
217 typedef struct {
218   size_t inter_elt_len; /* padded inter-element len; i.e. &a[1].field - &a[0].field */
219   tpl_node *iter_start_node; /* node to jump back to, as we start each new iteration */
220   size_t iternum; /* current iteration number (total req'd. iter's in n->num) */
221 } tpl_pound_data;
222
223 /* Hooks for customizing tpl mem alloc, error handling, etc. Set defaults. */
224 tpl_hook_t tpl_hook = {
225     /* .oops =       */ tpl_oops,
226     /* .malloc =     */ malloc,
227     /* .realloc =    */ realloc,
228     /* .free =       */ free,
229     /* .fatal =      */ tpl_fatal,
230     /* .gather_max = */ 0 /* max tpl size (bytes) for tpl_gather */
231 };
232
233 static const char tpl_fmt_chars[] = "AS($)BiucsfIUjv#"; /* valid format chars */
234 static const char tpl_S_fmt_chars[] = "iucsfIUjv#$()"; /* valid within S(...) */
235 static const char tpl_datapeek_ok_chars[] = "iucsfIUjv"; /* valid in datapeek */
236 static const struct tpl_type_t tpl_types[] = {
237     /* [TPL_TYPE_ROOT] =   */  {'r', 0},
238     /* [TPL_TYPE_INT32] =  */  {'i', sizeof(int32_t)},
239     /* [TPL_TYPE_UINT32] = */  {'u', sizeof(uint32_t)},
240     /* [TPL_TYPE_BYTE] =   */  {'c', sizeof(char)},
241     /* [TPL_TYPE_STR] =    */  {'s', sizeof(char*)},
242     /* [TPL_TYPE_ARY] =    */  {'A', 0},
243     /* [TPL_TYPE_BIN] =    */  {'B', 0},
244     /* [TPL_TYPE_DOUBLE] = */  {'f', 8}, /* not sizeof(double) as that varies */
245     /* [TPL_TYPE_INT64] =  */  {'I', sizeof(int64_t)},
246     /* [TPL_TYPE_UINT64] = */  {'U', sizeof(uint64_t)},
247     /* [TPL_TYPE_INT16] =  */  {'j', sizeof(int16_t)},
248     /* [TPL_TYPE_UINT16] = */  {'v', sizeof(uint16_t)},
249     /* [TPL_TYPE_POUND] =  */  {'#', 0},
250 };
251
252 /* default error-reporting function. Just writes to stderr. */
253 static int tpl_oops(const char *fmt, ...) {
254         char buf[256];
255     va_list ap;
256     va_start(ap,fmt);
257 /*    vfprintf(stderr,fmt,ap); */
258         snprintf(buf, sizeof(buf)-1, fmt, ap);
259         EM_DEBUG_EXCEPTION("%s", buf);
260     va_end(ap);
261     return 0;
262 }
263
264
265 static tpl_node *tpl_node_new(tpl_node *parent) {
266     tpl_node *n;
267     if ((n=tpl_hook.malloc(sizeof(tpl_node))) == NULL) {
268         fatal_oom();
269     }
270     n->addr=NULL;
271     n->data=NULL;
272     n->num=1;
273     n->ser_osz=0;
274     n->children=NULL;
275     n->next=NULL;
276     n->parent=parent;
277     return n;
278 }
279
280 /* Used in S(..) formats to pack several fields from a structure based on
281  * only the structure address. We need to calculate field addresses
282  * manually taking into account the size of the fields and intervening padding.
283  * The wrinkle is that double is not normally aligned on x86-32 but the
284  * -malign-double compiler option causes it to be. Double are aligned
285  * on Sparc, and apparently on 64 bit x86. We use a helper structure
286  * to detect whether double is aligned in this compilation environment.
287  */
288 char *calc_field_addr(tpl_node *parent, int type,char *struct_addr, int ordinal) {
289     tpl_node *prev;
290     int offset;
291     int align_sz;
292
293     if (ordinal == 1) return struct_addr;  /* first field starts on structure address */
294
295     /* generate enough padding so field addr is divisible by it's align_sz. 4, 8, etc */
296     prev = parent->children->prev;
297     switch(type) {
298       case TPL_TYPE_DOUBLE:
299         align_sz = sizeof(struct tpl_double_alignment_detector) > 12 ? 8 : 4;
300         break;
301       case TPL_TYPE_INT64:
302       case TPL_TYPE_UINT64:
303         align_sz = sizeof(struct tpl_int64_alignment_detector) > 12 ? 8 : 4;
304         break;
305       default:
306         align_sz = tpl_types[type].sz;
307         break;
308     }
309     offset = ((uintptr_t)prev->addr - (uintptr_t)struct_addr)
310             + (tpl_types[prev->type].sz * prev->num);
311     offset = (offset + align_sz - 1) / align_sz * align_sz;
312     return struct_addr + offset;
313 }
314
315 TPL_API tpl_node *tpl_map(char *fmt,...) {
316   va_list ap;
317   tpl_node *tn;
318
319   va_start(ap,fmt);
320   tn = tpl_map_va(fmt, ap);
321   va_end(ap);
322   return tn;
323 }
324
325 static tpl_node *tpl_map_va(char *fmt, va_list ap) {
326     int lparen_level=0,expect_lparen=0,t=0,in_structure=0,ordinal=0;
327     int in_nested_structure=0;
328     char *c, *peek, *struct_addr=NULL, *struct_next;
329     tpl_node *root,*parent,*n=NULL,*preceding,*iter_start_node=NULL,
330              *struct_widest_node=NULL, *np; tpl_pidx *pidx;
331     tpl_pound_data *pd;
332     int *fxlens, num_fxlens, pound_num, pound_prod, applies_to_struct;
333     int contig_fxlens[10]; /* temp space for contiguous fxlens */
334     int num_contig_fxlens, i, j;
335     ptrdiff_t inter_elt_len=0; /* padded element length of contiguous structs in array */
336
337
338     root = tpl_node_new(NULL);
339     root->type = TPL_TYPE_ROOT;
340     root->data = (tpl_root_data*)tpl_hook.malloc(sizeof(tpl_root_data));
341     if (!root->data) fatal_oom();
342     memset((tpl_root_data*)root->data,0,sizeof(tpl_root_data));
343
344     /* set up root nodes special ser_osz to reflect overhead of preamble */
345     root->ser_osz =  sizeof(uint32_t); /* tpl leading length */
346     root->ser_osz += EM_SAFE_STRLEN(fmt) + 1;  /* fmt + NUL-terminator */
347     root->ser_osz += 4;                /* 'tpl' magic prefix + flags byte */
348
349     parent=root;
350
351     c=fmt;
352     while (*c != '\0') {
353         switch (*c) {
354             case 'c':
355             case 'i':
356             case 'u':
357             case 'j':
358             case 'v':
359             case 'I':
360             case 'U':
361             case 'f':
362                 if      (*c=='c') t=TPL_TYPE_BYTE;
363                 else if (*c=='i') t=TPL_TYPE_INT32;
364                 else if (*c=='u') t=TPL_TYPE_UINT32;
365                 else if (*c=='j') t=TPL_TYPE_INT16;
366                 else if (*c=='v') t=TPL_TYPE_UINT16;
367                 else if (*c=='I') t=TPL_TYPE_INT64;
368                 else if (*c=='U') t=TPL_TYPE_UINT64;
369                 else if (*c=='f') t=TPL_TYPE_DOUBLE;
370
371                 if (expect_lparen) goto fail;
372                 n = tpl_node_new(parent);
373                 n->type = t;
374                 if (in_structure) {
375                     if (ordinal == 1) {
376                       /* for S(...)# iteration. Apply any changes to case 's' too!!! */
377                       iter_start_node = n;
378                       struct_widest_node = n;
379                     }
380                     if (tpl_types[n->type].sz > tpl_types[struct_widest_node->type].sz) {
381                       struct_widest_node = n;
382                     }
383                     n->addr = calc_field_addr(parent,n->type,struct_addr,ordinal++);
384                 } else n->addr = (void*)va_arg(ap,void*);
385                 n->data = tpl_hook.malloc(tpl_types[t].sz);
386                 if (!n->data) fatal_oom();
387                 if (n->parent->type == TPL_TYPE_ARY)
388                     ((tpl_atyp*)(n->parent->data))->sz += tpl_types[t].sz;
389                 DL_ADD(parent->children,n);
390                 break;
391             case 's':
392                 if (expect_lparen) goto fail;
393                 n = tpl_node_new(parent);
394                 n->type = TPL_TYPE_STR;
395                 if (in_structure) {
396                     if (ordinal == 1) {
397                       iter_start_node = n; /* for S(...)# iteration */
398                       struct_widest_node = n;
399                     }
400                     if (tpl_types[n->type].sz > tpl_types[struct_widest_node->type].sz) {
401                       struct_widest_node = n;
402                     }
403                     n->addr = calc_field_addr(parent,n->type,struct_addr,ordinal++);
404                 } else n->addr = (void*)va_arg(ap,void*);
405                 n->data = tpl_hook.malloc(sizeof(char*));
406                 if (!n->data) fatal_oom();
407                 *(char**)(n->data) = NULL;
408                 if (n->parent->type == TPL_TYPE_ARY)
409                     ((tpl_atyp*)(n->parent->data))->sz += sizeof(void*);
410                 DL_ADD(parent->children,n);
411                 break;
412             case '#':
413                 /* apply a 'num' to preceding atom */
414                 if (!parent->children) goto fail;
415                 preceding = parent->children->prev; /* first child's prev is 'last child'*/
416                 t = preceding->type;
417                 applies_to_struct = (*(c-1) == ')') ? 1 : 0;
418                 if (!applies_to_struct) {
419                   if (!(t == TPL_TYPE_BYTE   || t == TPL_TYPE_INT32 ||
420                         t == TPL_TYPE_UINT32 || t == TPL_TYPE_DOUBLE ||
421                         t == TPL_TYPE_UINT64 || t == TPL_TYPE_INT64 ||
422                         t == TPL_TYPE_UINT16 || t == TPL_TYPE_INT16 ||
423                         t == TPL_TYPE_STR )) goto fail;
424                 }
425                 /* count up how many contiguous # and form their product */
426                 pound_prod=1;
427                 num_contig_fxlens=0;
428                 for(peek=c; *peek == '#'; peek++) {
429                   pound_num = va_arg(ap, int);
430                   if (pound_num < 1) {
431                     tpl_hook.fatal("non-positive iteration count %d\n", pound_num);
432                   }
433                   if (num_contig_fxlens >= (sizeof(contig_fxlens)/sizeof(contig_fxlens[0]))) {
434                     tpl_hook.fatal("contiguous # exceeds hardcoded limit\n");
435                   }
436                   contig_fxlens[num_contig_fxlens++] = pound_num;
437                   pound_prod *= pound_num;
438                 }
439                 /* increment c to skip contiguous # so its points to last one */
440                 c = peek-1;
441                 /* differentiate atom-# from struct-# by noting preceding rparen */
442                 if (applies_to_struct) { /* insert # node to induce looping */
443                   n = tpl_node_new(parent);
444                   n->type = TPL_TYPE_POUND;
445                   n->num = pound_prod;
446                   n->data = tpl_hook.malloc(sizeof(tpl_pound_data));
447                   if (!n->data) fatal_oom();
448                   pd = (tpl_pound_data*)n->data;
449                   pd->inter_elt_len = inter_elt_len;
450                   pd->iter_start_node = iter_start_node;
451                   pd->iternum = 0;
452                   DL_ADD(parent->children,n);
453                   /* multiply the 'num' and data space on each atom in the structure */
454                   for(np = iter_start_node; np != n; np = np->next) {
455                     if (n->parent->type == TPL_TYPE_ARY) {
456                       ((tpl_atyp*)(n->parent->data))->sz +=
457                          tpl_types[np->type].sz * (np->num * (n->num - 1));
458                     }
459                     np->data = tpl_hook.realloc(np->data, tpl_types[np->type].sz *
460                                                           np->num * n->num);
461                     if (!np->data) fatal_oom();
462                     memset(np->data, 0, tpl_types[np->type].sz * np->num * n->num);
463                   }
464                 } else { /* simple atom-# form does not require a loop */
465                   preceding->num = pound_prod;
466                   preceding->data = tpl_hook.realloc(preceding->data,
467                       tpl_types[t].sz * preceding->num);
468                   if (!preceding->data) fatal_oom();
469                   memset(preceding->data,0,tpl_types[t].sz * preceding->num);
470                   if (n->parent->type == TPL_TYPE_ARY) {
471                       ((tpl_atyp*)(n->parent->data))->sz += tpl_types[t].sz *
472                                                             (preceding->num-1);
473                   }
474                 }
475                 root->ser_osz += (sizeof(uint32_t) * num_contig_fxlens);
476
477                 j = ((tpl_root_data*)root->data)->num_fxlens; /* before incrementing */
478                 (((tpl_root_data*)root->data)->num_fxlens) += num_contig_fxlens;
479                 num_fxlens = ((tpl_root_data*)root->data)->num_fxlens; /* new value */
480                 fxlens = ((tpl_root_data*)root->data)->fxlens;
481                 fxlens = tpl_hook.realloc(fxlens, sizeof(int) * num_fxlens);
482                 if (!fxlens) fatal_oom();
483                 ((tpl_root_data*)root->data)->fxlens = fxlens;
484                 for(i=0; i < num_contig_fxlens; i++) fxlens[j++] = contig_fxlens[i];
485
486                 break;
487             case 'B':
488                 if (expect_lparen) goto fail;
489                 if (in_structure) goto fail;
490                 n = tpl_node_new(parent);
491                 n->type = TPL_TYPE_BIN;
492                 n->addr = (tpl_bin*)va_arg(ap,void*);
493                 n->data = tpl_hook.malloc(sizeof(tpl_bin*));
494                 if (!n->data) fatal_oom();
495                 *((tpl_bin**)n->data) = NULL;
496                 if (n->parent->type == TPL_TYPE_ARY)
497                     ((tpl_atyp*)(n->parent->data))->sz += sizeof(tpl_bin);
498                 DL_ADD(parent->children,n);
499                 break;
500             case 'A':
501                 if (in_structure) goto fail;
502                 n = tpl_node_new(parent);
503                 n->type = TPL_TYPE_ARY;
504                 DL_ADD(parent->children,n);
505                 parent = n;
506                 expect_lparen=1;
507                 pidx = (tpl_pidx*)tpl_hook.malloc(sizeof(tpl_pidx));
508                 if (!pidx) fatal_oom();
509                 pidx->node = n;
510                 pidx->next = NULL;
511                 DL_ADD(((tpl_root_data*)(root->data))->pidx,pidx);
512                 /* set up the A's tpl_atyp */
513                 n->data = (tpl_atyp*)tpl_hook.malloc(sizeof(tpl_atyp));
514                 if (!n->data) fatal_oom();
515                 ((tpl_atyp*)(n->data))->num = 0;
516                 ((tpl_atyp*)(n->data))->sz = 0;
517                 ((tpl_atyp*)(n->data))->bb = NULL;
518                 ((tpl_atyp*)(n->data))->bbtail = NULL;
519                 ((tpl_atyp*)(n->data))->cur = NULL;
520                 if (n->parent->type == TPL_TYPE_ARY)
521                     ((tpl_atyp*)(n->parent->data))->sz += sizeof(void*);
522                 break;
523             case 'S':
524                 if (in_structure) goto fail;
525                 expect_lparen=1;
526                 ordinal=1;  /* index upcoming atoms in S(..) */
527                 in_structure=1+lparen_level; /* so we can tell where S fmt ends */
528                 struct_addr = (char*)va_arg(ap,void*);
529                 break;
530             case '$': /* nested structure */
531                 if (!in_structure) goto fail;
532                 expect_lparen=1;
533                 in_nested_structure++;
534                 break;
535             case ')':
536                 lparen_level--;
537                 if (lparen_level < 0) goto fail;
538                 if (*(c-1) == '(') goto fail;
539                 if (in_nested_structure) in_nested_structure--;
540                 else if (in_structure && (in_structure-1 == lparen_level)) {
541                   /* calculate delta between contiguous structures in array */
542                   struct_next = calc_field_addr(parent, struct_widest_node->type,
543                                                 struct_addr, ordinal++);
544                   inter_elt_len = struct_next - struct_addr;
545                   in_structure=0;
546                 }
547                 else parent = parent->parent; /* rparen ends A() type, not S() type */
548                 break;
549             case '(':
550                 if (!expect_lparen) goto fail;
551                 expect_lparen=0;
552                 lparen_level++;
553                 break;
554             default:
555                 tpl_hook.oops("unsupported option %c\n", *c);
556                 goto fail;
557         }
558         c++;
559     }
560     if (lparen_level != 0) goto fail;
561
562     /* copy the format string, save for convenience */
563     ((tpl_root_data*)(root->data))->fmt = tpl_hook.malloc(EM_SAFE_STRLEN(fmt)+1);
564     if (((tpl_root_data*)(root->data))->fmt == NULL)
565         fatal_oom();
566     memcpy(((tpl_root_data*)(root->data))->fmt,fmt,EM_SAFE_STRLEN(fmt)+1);
567
568     return root;
569
570 fail:
571     tpl_hook.oops("failed to parse %s\n", fmt);
572     tpl_free(root);
573     return NULL;
574 }
575
576 static int tpl_unmap_file( tpl_mmap_rec *mr) {
577
578     if ( munmap( mr->text, mr->text_sz ) == -1 ) {
579         tpl_hook.oops("Failed to munmap: %s\n", strerror(errno));
580     }
581     close(mr->fd);
582     mr->text = NULL;
583     mr->text_sz = 0;
584     return 0;
585 }
586
587 static void tpl_free_keep_map(tpl_node *r) {
588     int mmap_bits = (TPL_RDONLY|TPL_FILE);
589     int ufree_bits = (TPL_MEM|TPL_UFREE);
590     tpl_node *nxtc,*c;
591     int find_next_node=0,looking,i;
592     size_t sz;
593
594     /* For mmap'd files, or for 'ufree' memory images , do appropriate release */
595     if ((((tpl_root_data*)(r->data))->flags & mmap_bits) == mmap_bits) {
596         tpl_unmap_file( &((tpl_root_data*)(r->data))->mmap);
597     } else if ((((tpl_root_data*)(r->data))->flags & ufree_bits) == ufree_bits) {
598         tpl_hook.free( ((tpl_root_data*)(r->data))->mmap.text );
599     }
600
601     c = r->children;
602     if (c) {
603         while(c->type != TPL_TYPE_ROOT) {    /* loop until we come back to root node */
604             switch (c->type) {
605                 case TPL_TYPE_BIN:
606                     /* free any binary buffer hanging from tpl_bin */
607                     if ( *((tpl_bin**)(c->data)) ) {
608                         if ( (*((tpl_bin**)(c->data)))->addr ) {
609                             tpl_hook.free( (*((tpl_bin**)(c->data)))->addr );
610                         }
611                         *((tpl_bin**)c->data) = NULL; /* reset tpl_bin */
612                     }
613                     find_next_node=1;
614                     break;
615                 case TPL_TYPE_STR:
616                     /* free any packed (copied) string */
617                     for(i=0; i < c->num; i++) {
618                       char *str = ((char**)c->data)[i];
619                       if (str) {
620                         tpl_hook.free(str);
621                         ((char**)c->data)[i] = NULL;
622                       }
623                     }
624                     find_next_node=1;
625                     break;
626                 case TPL_TYPE_INT32:
627                 case TPL_TYPE_UINT32:
628                 case TPL_TYPE_INT64:
629                 case TPL_TYPE_UINT64:
630                 case TPL_TYPE_BYTE:
631                 case TPL_TYPE_DOUBLE:
632                 case TPL_TYPE_INT16:
633                 case TPL_TYPE_UINT16:
634                 case TPL_TYPE_POUND:
635                     find_next_node=1;
636                     break;
637                 case TPL_TYPE_ARY:
638                     c->ser_osz = 0; /* zero out the serialization output size */
639
640                     sz = ((tpl_atyp*)(c->data))->sz;  /* save sz to use below */
641                     tpl_free_atyp(c,c->data);
642
643                     /* make new atyp */
644                     c->data = (tpl_atyp*)tpl_hook.malloc(sizeof(tpl_atyp));
645                     if (!c->data) fatal_oom();
646                     ((tpl_atyp*)(c->data))->num = 0;
647                     ((tpl_atyp*)(c->data))->sz = sz;  /* restore bb datum sz */
648                     ((tpl_atyp*)(c->data))->bb = NULL;
649                     ((tpl_atyp*)(c->data))->bbtail = NULL;
650                     ((tpl_atyp*)(c->data))->cur = NULL;
651
652                     c = c->children;
653                     break;
654                 default:
655                     tpl_hook.fatal("unsupported format character\n");
656                     break;
657             }
658
659             if (find_next_node) {
660                 find_next_node=0;
661                 looking=1;
662                 while(looking) {
663                     if (c->next) {
664                         nxtc=c->next;
665                         c=nxtc;
666                         looking=0;
667                     } else {
668                         if (c->type == TPL_TYPE_ROOT) break; /* root node */
669                         else {
670                             nxtc=c->parent;
671                             c=nxtc;
672                         }
673                     }
674                 }
675             }
676         }
677     }
678
679     ((tpl_root_data*)(r->data))->flags = 0;  /* reset flags */
680 }
681
682 TPL_API void tpl_free(tpl_node *r) {
683     int mmap_bits = (TPL_RDONLY|TPL_FILE);
684     int ufree_bits = (TPL_MEM|TPL_UFREE);
685     tpl_node *nxtc,*c;
686     int find_next_node=0,looking,i;
687     tpl_pidx *pidx,*pidx_nxt;
688
689     /* For mmap'd files, or for 'ufree' memory images , do appropriate release */
690     if ((((tpl_root_data*)(r->data))->flags & mmap_bits) == mmap_bits) {
691         tpl_unmap_file( &((tpl_root_data*)(r->data))->mmap);
692     } else if ((((tpl_root_data*)(r->data))->flags & ufree_bits) == ufree_bits) {
693         tpl_hook.free( ((tpl_root_data*)(r->data))->mmap.text );
694     }
695
696     c = r->children;
697     if (c) {
698         while(c->type != TPL_TYPE_ROOT) {    /* loop until we come back to root node */
699             switch (c->type) {
700                 case TPL_TYPE_BIN:
701                     /* free any binary buffer hanging from tpl_bin */
702                     if ( *((tpl_bin**)(c->data)) ) {
703                         if ( (*((tpl_bin**)(c->data)))->sz != 0 ) {
704                             tpl_hook.free( (*((tpl_bin**)(c->data)))->addr );
705                         }
706                         tpl_hook.free(*((tpl_bin**)c->data)); /* free tpl_bin */
707                     }
708                     tpl_hook.free(c->data);  /* free tpl_bin* */
709                     find_next_node=1;
710                     break;
711                 case TPL_TYPE_STR:
712                     /* free any packed (copied) string */
713                     for(i=0; i < c->num; i++) {
714                       char *str = ((char**)c->data)[i];
715                       if (str) {
716                         tpl_hook.free(str);
717                         ((char**)c->data)[i] = NULL;
718                       }
719                     }
720                     tpl_hook.free(c->data);
721                     find_next_node=1;
722                     break;
723                 case TPL_TYPE_INT32:
724                 case TPL_TYPE_UINT32:
725                 case TPL_TYPE_INT64:
726                 case TPL_TYPE_UINT64:
727                 case TPL_TYPE_BYTE:
728                 case TPL_TYPE_DOUBLE:
729                 case TPL_TYPE_INT16:
730                 case TPL_TYPE_UINT16:
731                 case TPL_TYPE_POUND:
732                     tpl_hook.free(c->data);
733                     find_next_node=1;
734                     break;
735                 case TPL_TYPE_ARY:
736                     tpl_free_atyp(c,c->data);
737                     if (c->children) c = c->children; /* normal case */
738                     else find_next_node=1; /* edge case, handle bad format A() */
739                     break;
740                 default:
741                     tpl_hook.fatal("unsupported format character\n");
742                     break;
743             }
744
745             if (find_next_node) {
746                 find_next_node=0;
747                 looking=1;
748                 while(looking) {
749                     if (c->next) {
750                         nxtc=c->next;
751                         tpl_hook.free(c);
752                         c=nxtc;
753                         looking=0;
754                     } else {
755                         if (c->type == TPL_TYPE_ROOT) break; /* root node */
756                         else {
757                             nxtc=c->parent;
758                             tpl_hook.free(c);
759                             c=nxtc;
760                         }
761                     }
762                 }
763             }
764         }
765     }
766
767     /* free root */
768     for(pidx=((tpl_root_data*)(r->data))->pidx; pidx; pidx=pidx_nxt) {
769         pidx_nxt = pidx->next;
770         tpl_hook.free(pidx);
771     }
772     tpl_hook.free(((tpl_root_data*)(r->data))->fmt);
773     if (((tpl_root_data*)(r->data))->num_fxlens > 0) {
774         tpl_hook.free(((tpl_root_data*)(r->data))->fxlens);
775     }
776     tpl_hook.free(r->data);  /* tpl_root_data */
777     tpl_hook.free(r);
778 }
779
780
781 /* Find the i'th packable ('A' node) */
782 static tpl_node *tpl_find_i(tpl_node *n, int i) {
783     int j=0;
784     tpl_pidx *pidx;
785     if (n->type != TPL_TYPE_ROOT) return NULL;
786     if (i == 0) return n;  /* packable 0 is root */
787     for(pidx=((tpl_root_data*)(n->data))->pidx; pidx; pidx=pidx->next) {
788         if (++j == i) return pidx->node;
789     }
790     return NULL;
791 }
792
793 static void *tpl_cpv(void *datav, void *data, size_t sz) {
794     if (sz>0) memcpy(datav,data,sz);
795     return (void*)((uintptr_t)datav + sz);
796 }
797
798 static void *tpl_extend_backbone(tpl_node *n) {
799     tpl_backbone *bb;
800     bb = (tpl_backbone*)tpl_hook.malloc(sizeof(tpl_backbone) +
801       ((tpl_atyp*)(n->data))->sz );  /* datum hangs on coattails of bb */
802     if (!bb) fatal_oom();
803 #if __STDC_VERSION__ < 199901
804     bb->data = (char*)((uintptr_t)bb + sizeof(tpl_backbone));
805 #endif
806     memset(bb->data,0,((tpl_atyp*)(n->data))->sz);
807     bb->next = NULL;
808     /* Add the new backbone to the tail, also setting head if necessary  */
809     if (((tpl_atyp*)(n->data))->bb == NULL) {
810         ((tpl_atyp*)(n->data))->bb = bb;
811         ((tpl_atyp*)(n->data))->bbtail = bb;
812     } else {
813         ((tpl_atyp*)(n->data))->bbtail->next = bb;
814         ((tpl_atyp*)(n->data))->bbtail = bb;
815     }
816
817     ((tpl_atyp*)(n->data))->num++;
818     return bb->data;
819 }
820
821 /* Get the format string corresponding to a given tpl (root node) */
822 static char *tpl_fmt(tpl_node *r) {
823     return ((tpl_root_data*)(r->data))->fmt;
824 }
825
826 /* Get the fmt # lengths as a contiguous buffer of ints (length num_fxlens) */
827 static int *tpl_fxlens(tpl_node *r, int *num_fxlens) {
828     *num_fxlens = ((tpl_root_data*)(r->data))->num_fxlens;
829     return ((tpl_root_data*)(r->data))->fxlens;
830 }
831
832 /* called when serializing an 'A' type node into a buffer which has
833  * already been set up with the proper space. The backbone is walked
834  * which was obtained from the tpl_atyp header passed in.
835  */
836 static void *tpl_dump_atyp(tpl_node *n, tpl_atyp* at, void *dv) {
837     tpl_backbone *bb;
838     tpl_node *c;
839     void *datav;
840     uint32_t slen;
841     tpl_bin *binp;
842     char *strp;
843     tpl_atyp *atypp;
844     tpl_pound_data *pd;
845     int i;
846     size_t itermax;
847
848     /* handle 'A' nodes */
849     dv = tpl_cpv(dv,&at->num,sizeof(uint32_t));  /* array len */
850     for(bb=at->bb; bb; bb=bb->next) {
851         datav = bb->data;
852         c=n->children;
853         while(c) {
854             switch (c->type) {
855                 case TPL_TYPE_BYTE:
856                 case TPL_TYPE_DOUBLE:
857                 case TPL_TYPE_INT32:
858                 case TPL_TYPE_UINT32:
859                 case TPL_TYPE_INT64:
860                 case TPL_TYPE_UINT64:
861                 case TPL_TYPE_INT16:
862                 case TPL_TYPE_UINT16:
863                     dv = tpl_cpv(dv,datav,tpl_types[c->type].sz * c->num);
864                     datav = (void*)((uintptr_t)datav + tpl_types[c->type].sz * c->num);
865                     break;
866                 case TPL_TYPE_BIN:
867                     /* dump the buffer length followed by the buffer */
868                     memcpy(&binp,datav,sizeof(tpl_bin*)); /* cp to aligned */
869                     slen = binp->sz;
870                     dv = tpl_cpv(dv,&slen,sizeof(uint32_t));
871                     dv = tpl_cpv(dv,binp->addr,slen);
872                     datav = (void*)((uintptr_t)datav + sizeof(tpl_bin*));
873                     break;
874                 case TPL_TYPE_STR:
875                     /* dump the string length followed by the string */
876                     for(i=0; i < c->num; i++) {
877                       memcpy(&strp,datav,sizeof(char*)); /* cp to aligned */
878                       slen = strp ? (EM_SAFE_STRLEN(strp)+1) : 0;
879                       dv = tpl_cpv(dv,&slen,sizeof(uint32_t));
880                       if (slen > 1) dv = tpl_cpv(dv,strp,slen-1);
881                       datav = (void*)((uintptr_t)datav + sizeof(char*));
882                     }
883                     break;
884                 case TPL_TYPE_ARY:
885                     memcpy(&atypp,datav,sizeof(tpl_atyp*)); /* cp to aligned */
886                     dv = tpl_dump_atyp(c,atypp,dv);
887                     datav = (void*)((uintptr_t)datav + sizeof(void*));
888                     break;
889                 case TPL_TYPE_POUND:
890                     /* iterate over the preceding nodes */
891                     pd = (tpl_pound_data*)c->data;
892                     itermax = c->num;
893                     if (++(pd->iternum) < itermax) {
894                       c = pd->iter_start_node;
895                       continue;
896                     } else { /* loop complete. */
897                       pd->iternum = 0;
898                     }
899                     break;
900                 default:
901                     tpl_hook.fatal("unsupported format character\n");
902                     break;
903             }
904             c=c->next;
905         }
906     }
907     return dv;
908 }
909
910 /* figure the serialization output size needed for tpl whose root is n*/
911 static size_t tpl_ser_osz(tpl_node *n) {
912     tpl_node *c, *np;
913     size_t sz, itermax;
914     tpl_bin *binp;
915     char *strp;
916     tpl_pound_data *pd;
917     int i;
918
919     /* handle the root node ONLY (subtree's ser_osz have been bubbled-up) */
920     if (n->type != TPL_TYPE_ROOT) {
921         tpl_hook.fatal("internal error: tpl_ser_osz on non-root node\n");
922     }
923
924     sz = n->ser_osz;    /* start with fixed overhead, already stored */
925     c=n->children;
926     while (c) {
927         switch (c->type) {
928             case TPL_TYPE_BYTE:
929             case TPL_TYPE_DOUBLE:
930             case TPL_TYPE_INT32:
931             case TPL_TYPE_UINT32:
932             case TPL_TYPE_INT64:
933             case TPL_TYPE_UINT64:
934             case TPL_TYPE_INT16:
935             case TPL_TYPE_UINT16:
936                 sz += tpl_types[c->type].sz * c->num;
937                 break;
938             case TPL_TYPE_BIN:
939                 sz += sizeof(uint32_t);  /* binary buf len */
940                 memcpy(&binp,c->data,sizeof(tpl_bin*)); /* cp to aligned */
941                 sz += binp->sz;
942                 break;
943             case TPL_TYPE_STR:
944                 for(i=0; i < c->num; i++) {
945                   sz += sizeof(uint32_t);  /* string len */
946                   memcpy(&strp,&((char**)c->data)[i],sizeof(char*)); /* cp to aligned */
947                   sz += strp ? EM_SAFE_STRLEN(strp) : 0;
948                 }
949                 break;
950             case TPL_TYPE_ARY:
951                 sz += sizeof(uint32_t);  /* array len */
952                 sz += c->ser_osz;        /* bubbled-up child array ser_osz */
953                 break;
954             case TPL_TYPE_POUND:
955                 /* iterate over the preceding nodes */
956                 itermax = c->num;
957                 pd = (tpl_pound_data*)c->data;
958                 if (++(pd->iternum) < itermax) {
959                   for(np=pd->iter_start_node; np != c; np = np->next) {
960                      np->data = (char*)(np->data) +
961                                 (tpl_types[np->type].sz * np->num);
962                   }
963                   c = pd->iter_start_node;
964                   continue;
965                 } else { /* loop complete. */
966                   pd->iternum = 0;
967                   for(np=pd->iter_start_node; np != c; np = np->next) {
968                      np->data = (char*)(np->data) - ((itermax-1) *
969                                                      tpl_types[np->type].sz *
970                                                      np->num);
971                   }
972                 }
973                 break;
974             default:
975                 tpl_hook.fatal("unsupported format character\n");
976                 break;
977         }
978         c=c->next;
979     }
980     return sz;
981 }
982
983
984 TPL_API int tpl_dump(tpl_node *r, int mode, ...) {
985     va_list ap;
986     char *filename, *bufv;
987     void **addr_out,*buf, *pa_addr;
988     int fd,rc=0;
989     size_t sz,*sz_out, pa_sz;
990
991     if (((tpl_root_data*)(r->data))->flags & TPL_RDONLY) {  /* unusual */
992         tpl_hook.oops("error: tpl_dump called for a loaded tpl\n");
993         return -1;
994     }
995
996     sz = tpl_ser_osz(r); /* compute the size needed to serialize  */
997
998     va_start(ap,mode);
999     if (mode & TPL_FILE) {
1000         filename = va_arg(ap,char*);
1001         fd = tpl_mmap_output_file(filename, sz, &buf);
1002         if (fd == -1) rc = -1;
1003         else {
1004             rc = tpl_dump_to_mem(r,buf,sz);
1005             if (msync(buf,sz,MS_SYNC) == -1) {
1006                 tpl_hook.oops("msync failed on fd %d: %s\n", fd, strerror(errno));
1007             }
1008             if (munmap(buf, sz) == -1) {
1009                 tpl_hook.oops("munmap failed on fd %d: %s\n", fd, strerror(errno));
1010             }
1011             close(fd);
1012         }
1013     } else if (mode & TPL_FD) {
1014         fd = va_arg(ap, int);
1015         if ( (buf = tpl_hook.malloc(sz)) == NULL) fatal_oom();
1016         tpl_dump_to_mem(r,buf,sz);
1017         bufv = buf;
1018         do {
1019             rc = write(fd,bufv,sz);
1020             if (rc > 0) {
1021                 sz -= rc;
1022                 bufv += rc;
1023             } else if (rc == -1) {
1024                 if (errno == EINTR || errno == EAGAIN) continue;
1025                 tpl_hook.oops("error writing to fd %d: %s\n", fd, strerror(errno));
1026                 free(buf);
1027                 return -1;
1028             }
1029         } while (sz > 0);
1030         free(buf);
1031         rc = 0;
1032     } else if (mode & TPL_MEM) {
1033         if (mode & TPL_PREALLOCD) { /* caller allocated */
1034           pa_addr = (void*)va_arg(ap, void*);
1035           pa_sz = va_arg(ap, size_t);
1036           if (pa_sz < sz) {
1037               tpl_hook.oops("tpl_dump: buffer too small, need %d bytes\n", sz);
1038               return -1;
1039           }
1040           rc=tpl_dump_to_mem(r,pa_addr,sz);
1041         } else { /* we allocate */
1042           addr_out = (void**)va_arg(ap, void*);
1043           sz_out = va_arg(ap, size_t*);
1044           if ( (buf = tpl_hook.malloc(sz)) == NULL) fatal_oom();
1045           *sz_out = sz;
1046           *addr_out = buf;
1047           rc=tpl_dump_to_mem(r,buf,sz);
1048         }
1049     } else if (mode & TPL_GETSIZE) {
1050         sz_out = va_arg(ap, size_t*);
1051         *sz_out = sz;
1052     } else {
1053         tpl_hook.oops("unsupported tpl_dump mode %d\n", mode);
1054         rc=-1;
1055     }
1056     va_end(ap);
1057     return rc;
1058 }
1059
1060 /* This function expects the caller to have set up a memory buffer of
1061  * adequate size to hold the serialized tpl. The sz parameter must be
1062  * the result of tpl_ser_osz(r).
1063  */
1064 static int tpl_dump_to_mem(tpl_node *r,void *addr,size_t sz) {
1065     uint32_t slen, sz32;
1066     int *fxlens, num_fxlens, i;
1067     void *dv;
1068     char *fmt,flags;
1069     tpl_node *c, *np;
1070     tpl_pound_data *pd;
1071     size_t itermax;
1072
1073     fmt = tpl_fmt(r);
1074     flags = 0;
1075     if (tpl_cpu_bigendian()) flags |= TPL_FL_BIGENDIAN;
1076     if (strchr(fmt,'s')) flags |= TPL_FL_NULLSTRINGS;
1077     sz32 = sz;
1078
1079     dv = addr;
1080     dv = tpl_cpv(dv,TPL_MAGIC,3);         /* copy tpl magic prefix */
1081     dv = tpl_cpv(dv,&flags,1);            /* copy flags byte */
1082     dv = tpl_cpv(dv,&sz32,sizeof(uint32_t));/* overall length (inclusive) */
1083     dv = tpl_cpv(dv,fmt,EM_SAFE_STRLEN(fmt)+1);   /* copy format with NUL-term */
1084     fxlens = tpl_fxlens(r,&num_fxlens);
1085     dv = tpl_cpv(dv,fxlens,num_fxlens*sizeof(uint32_t));/* fmt # lengths */
1086
1087     /* serialize the tpl content, iterating over direct children of root */
1088     c = r->children;
1089     while (c) {
1090         switch (c->type) {
1091             case TPL_TYPE_BYTE:
1092             case TPL_TYPE_DOUBLE:
1093             case TPL_TYPE_INT32:
1094             case TPL_TYPE_UINT32:
1095             case TPL_TYPE_INT64:
1096             case TPL_TYPE_UINT64:
1097             case TPL_TYPE_INT16:
1098             case TPL_TYPE_UINT16:
1099                 dv = tpl_cpv(dv,c->data,tpl_types[c->type].sz * c->num);
1100                 break;
1101             case TPL_TYPE_BIN:
1102                 slen = (*(tpl_bin**)(c->data))->sz;
1103                 dv = tpl_cpv(dv,&slen,sizeof(uint32_t));  /* buffer len */
1104                 dv = tpl_cpv(dv,(*(tpl_bin**)(c->data))->addr,slen); /* buf */
1105                 break;
1106             case TPL_TYPE_STR:
1107                 for(i=0; i < c->num; i++) {
1108                   char *str = ((char**)c->data)[i];
1109                   slen = str ? EM_SAFE_STRLEN(str)+1 : 0;
1110                   dv = tpl_cpv(dv,&slen,sizeof(uint32_t));  /* string len */
1111                   if (slen>1) dv = tpl_cpv(dv,str,slen-1); /*string*/
1112                 }
1113                 break;
1114             case TPL_TYPE_ARY:
1115                 dv = tpl_dump_atyp(c,(tpl_atyp*)c->data,dv);
1116                 break;
1117             case TPL_TYPE_POUND:
1118                  pd = (tpl_pound_data*)c->data;
1119                  itermax = c->num;
1120                  if (++(pd->iternum) < itermax) {
1121
1122                    /* in start or midst of loop. advance data pointers. */
1123                    for(np=pd->iter_start_node; np != c; np = np->next) {
1124                      np->data = (char*)(np->data) +
1125                                 (tpl_types[np->type].sz * np->num);
1126                    }
1127                    /* do next iteration */
1128                    c = pd->iter_start_node;
1129                    continue;
1130
1131                  } else { /* loop complete. */
1132
1133                    /* reset iteration index and addr/data pointers. */
1134                    pd->iternum = 0;
1135                    for(np=pd->iter_start_node; np != c; np = np->next) {
1136                      np->data = (char*)(np->data) - ((itermax-1) *
1137                                                      tpl_types[np->type].sz *
1138                                                      np->num);
1139                    }
1140
1141                  }
1142                  break;
1143             default:
1144                 tpl_hook.fatal("unsupported format character\n");
1145                 break;
1146         }
1147         c = c->next;
1148     }
1149
1150     return 0;
1151 }
1152
1153 static int tpl_cpu_bigendian() {
1154    unsigned i = 1;
1155    char *c;
1156    c = (char*)&i;
1157    return (c[0] == 1 ? 0 : 1);
1158 }
1159
1160
1161 /*
1162  * algorithm for sanity-checking a tpl image:
1163  * scan the tpl whilst not exceeding the buffer size (bufsz) ,
1164  * formulating a calculated (expected) size of the tpl based
1165  * on walking its data. When calcsize has been calculated it
1166  * should exactly match the buffer size (bufsz) and the internal
1167  * recorded size (intlsz)
1168  */
1169 static int tpl_sanity(tpl_node *r, int excess_ok) {
1170     uint32_t intlsz;
1171     int found_nul=0,rc, octothorpes=0, num_fxlens, *fxlens, flen;
1172     void *d, *dv;
1173     char intlflags, *fmt, c, *mapfmt;
1174     size_t bufsz, serlen;
1175
1176     d = ((tpl_root_data*)(r->data))->mmap.text;
1177     bufsz = ((tpl_root_data*)(r->data))->mmap.text_sz;
1178
1179     dv = d;
1180     if (bufsz < (4 + sizeof(uint32_t) + 1)) return ERR_NOT_MINSIZE; /* min sz: magic+flags+len+nul */
1181     if (memcmp(dv,TPL_MAGIC, 3) != 0) return ERR_MAGIC_MISMATCH; /* missing tpl magic prefix */
1182     if (tpl_needs_endian_swap(dv)) ((tpl_root_data*)(r->data))->flags |= TPL_XENDIAN;
1183     dv = (void*)((uintptr_t)dv + 3);
1184     memcpy(&intlflags,dv,sizeof(char));  /* extract flags */
1185     if (intlflags & ~TPL_SUPPORTED_BITFLAGS) return ERR_UNSUPPORTED_FLAGS;
1186     /* TPL1.3 stores strings with a "length+1" prefix to discern NULL strings from
1187        empty strings from non-empty strings; TPL1.2 only handled the latter two.
1188        So we need to be mindful of which string format we're reading from. */
1189     if (!(intlflags & TPL_FL_NULLSTRINGS)) {
1190       ((tpl_root_data*)(r->data))->flags |= TPL_OLD_STRING_FMT;
1191     }
1192     dv = (void*)((uintptr_t)dv + 1);
1193     memcpy(&intlsz,dv,sizeof(uint32_t));  /* extract internal size */
1194     if (((tpl_root_data*)(r->data))->flags & TPL_XENDIAN) tpl_byteswap(&intlsz, sizeof(uint32_t));
1195     if (!excess_ok && (intlsz != bufsz)) return ERR_INCONSISTENT_SZ;  /* inconsisent buffer/internal size */
1196     dv = (void*)((uintptr_t)dv + sizeof(uint32_t));
1197
1198     /* dv points to the start of the format string. Look for nul w/in buf sz */
1199     fmt = (char*)dv;
1200     while ((uintptr_t)dv-(uintptr_t)d < bufsz && !found_nul) {
1201         if ( (c = *(char*)dv) != '\0') {
1202             if (strchr(tpl_fmt_chars,c) == NULL)
1203                return ERR_FMT_INVALID;  /* invalid char in format string */
1204             if ( (c = *(char*)dv) == '#') octothorpes++;
1205             dv = (void*)((uintptr_t)dv + 1);
1206         }
1207         else found_nul = 1;
1208     }
1209     if (!found_nul) return ERR_FMT_MISSING_NUL;  /* runaway format string */
1210     dv = (void*)((uintptr_t)dv + 1);   /* advance to octothorpe lengths buffer */
1211
1212     /* compare the map format to the format of this tpl image */
1213     mapfmt = tpl_fmt(r);
1214     rc = strcmp(mapfmt,fmt);
1215     if (rc != 0) return ERR_FMT_MISMATCH;
1216
1217     /* compare octothorpe lengths in image to the mapped values */
1218     if ((((uintptr_t)dv + (octothorpes * 4)) - (uintptr_t)d) > bufsz) return ERR_INCONSISTENT_SZ4;
1219     fxlens = tpl_fxlens(r,&num_fxlens);  /* mapped fxlens */
1220     while(num_fxlens--) {
1221         memcpy(&flen,dv,sizeof(uint32_t)); /* stored flen */
1222         if (((tpl_root_data*)(r->data))->flags & TPL_XENDIAN) tpl_byteswap(&flen, sizeof(uint32_t));
1223         if (flen != *fxlens) return ERR_FLEN_MISMATCH;
1224         dv = (void*)((uintptr_t)dv + sizeof(uint32_t));
1225         fxlens++;
1226     }
1227
1228     /* dv now points to beginning of data */
1229     rc = tpl_serlen(r,r,dv,&serlen);  /* get computed serlen of data part */
1230     if (rc == -1) return ERR_INCONSISTENT_SZ2; /* internal inconsistency in tpl image */
1231     serlen += ((uintptr_t)dv - (uintptr_t)d);   /* add back serlen of preamble part */
1232     if (excess_ok && (bufsz < serlen)) return ERR_INCONSISTENT_SZ3;
1233     if (!excess_ok && (serlen != bufsz)) return ERR_INCONSISTENT_SZ3;  /* buffer/internal sz exceeds serlen */
1234     return 0;
1235 }
1236
1237 static void *tpl_find_data_start(void *d) {
1238     int octothorpes=0;
1239     d = (void*)((uintptr_t)d + 4); /* skip TPL_MAGIC and flags byte */
1240     d = (void*)((uintptr_t)d + 4); /* skip int32 overall len */
1241     while(*(char*)d != '\0') {
1242         if (*(char*)d == '#') octothorpes++;
1243         d = (void*)((uintptr_t)d + 1);
1244     }
1245     d = (void*)((uintptr_t)d +  1);  /* skip NUL */
1246     d = (void*)((uintptr_t)d +  (octothorpes * sizeof(uint32_t)));  /* skip # array lens */
1247     return d;
1248 }
1249
1250 static int tpl_needs_endian_swap(void *d) {
1251     char *c;
1252     int cpu_is_bigendian;
1253     c = (char*)d;
1254     cpu_is_bigendian = tpl_cpu_bigendian();
1255     return ((c[3] & TPL_FL_BIGENDIAN) == cpu_is_bigendian) ? 0 : 1;
1256 }
1257
1258 static size_t tpl_size_for(char c) {
1259   int i;
1260   for(i=0; i < sizeof(tpl_types)/sizeof(tpl_types[0]); i++) {
1261     if (tpl_types[i].c == c) return tpl_types[i].sz;
1262   }
1263   return 0;
1264 }
1265
1266 TPL_API char* tpl_peek(int mode, ...) {
1267     va_list ap;
1268     int xendian=0,found_nul=0,old_string_format=0;
1269     char *filename=NULL, *datapeek_f=NULL, *datapeek_c, *datapeek_s;
1270     void *addr=NULL, *dv, *datapeek_p=NULL;
1271     size_t sz=0, fmt_len, first_atom, num_fxlens=0;
1272     uint32_t datapeek_ssz, datapeek_csz, datapeek_flen;
1273     tpl_mmap_rec mr = {0,NULL,0};
1274     char *fmt,*fmt_cpy=NULL,c;
1275     uint32_t intlsz, **fxlens=NULL, *num_fxlens_out=NULL, *fxlensv;
1276
1277     va_start(ap,mode);
1278     if ((mode & TPL_FXLENS) && (mode & TPL_DATAPEEK)) {
1279         tpl_hook.oops("TPL_FXLENS and TPL_DATAPEEK mutually exclusive\n");
1280         goto fail;
1281     }
1282     if (mode & TPL_FILE) filename = va_arg(ap,char *);
1283     else if (mode & TPL_MEM) {
1284         addr = va_arg(ap,void *);
1285         sz = va_arg(ap,size_t);
1286     } else {
1287         tpl_hook.oops("unsupported tpl_peek mode %d\n", mode);
1288         goto fail;
1289     }
1290     if (mode & TPL_DATAPEEK) {
1291         datapeek_f = va_arg(ap, char*);
1292     }
1293     if (mode & TPL_FXLENS) {
1294         num_fxlens_out = va_arg(ap,uint32_t *);
1295         fxlens = va_arg(ap,uint32_t **);
1296         *num_fxlens_out = 0;
1297         *fxlens = NULL;
1298     }
1299
1300     if (mode & TPL_FILE) {
1301         if (tpl_mmap_file(filename, &mr) != 0) {
1302             tpl_hook.oops("tpl_peek failed for file %s\n", filename);
1303             goto fail;
1304         }
1305         addr = mr.text;
1306         sz = mr.text_sz;
1307     }
1308
1309     dv = addr;
1310     if (sz < (4 + sizeof(uint32_t) + 1)) goto fail; /* min sz */
1311     if (memcmp(dv,TPL_MAGIC, 3) != 0) goto fail; /* missing tpl magic prefix */
1312     if (tpl_needs_endian_swap(dv)) xendian=1;
1313     if ((((char*)dv)[3] & TPL_FL_NULLSTRINGS)==0) old_string_format=1;
1314     dv = (void*)((uintptr_t)dv + 4);
1315     memcpy(&intlsz,dv,sizeof(uint32_t));  /* extract internal size */
1316     if (xendian) tpl_byteswap(&intlsz, sizeof(uint32_t));
1317     if (intlsz != sz) goto fail;  /* inconsisent buffer/internal size */
1318     dv = (void*)((uintptr_t)dv + sizeof(uint32_t));
1319
1320     /* dv points to the start of the format string. Look for nul w/in buf sz */
1321     fmt = (char*)dv;
1322     while ((uintptr_t)dv-(uintptr_t)addr < sz && !found_nul) {
1323         if ( (c = *(char*)dv) == '\0') {
1324             found_nul = 1;
1325         } else if (c == '#') {
1326           num_fxlens++;
1327         }
1328         dv = (void*)((uintptr_t)dv + 1);
1329     }
1330     if (!found_nul) goto fail;  /* runaway format string */
1331     fmt_len = (char*)dv - fmt;  /* include space for \0 */
1332     fmt_cpy = tpl_hook.malloc(fmt_len);
1333     if (fmt_cpy == NULL) {
1334         fatal_oom();
1335     }
1336     memcpy(fmt_cpy, fmt, fmt_len);
1337
1338     /* retrieve the octothorpic lengths if requested */
1339     if (num_fxlens > 0) {
1340       if (sz < ((uintptr_t)dv + (num_fxlens * sizeof(uint32_t)) - (uintptr_t)addr)) {
1341         goto fail;
1342       }
1343     }
1344     if ((mode & TPL_FXLENS) && (num_fxlens > 0)) {
1345       *fxlens = tpl_hook.malloc(num_fxlens * sizeof(uint32_t));
1346       if (*fxlens == NULL) tpl_hook.fatal("out of memory");
1347       *num_fxlens_out = num_fxlens;
1348       fxlensv = *fxlens;
1349       while(num_fxlens--) {
1350           memcpy(fxlensv,dv,sizeof(uint32_t));
1351           if (xendian) tpl_byteswap(fxlensv, sizeof(uint32_t));
1352           dv = (void*)((uintptr_t)dv + sizeof(uint32_t));
1353           fxlensv++;
1354       }
1355     }
1356     /* if caller requested, peek into the specified data elements */
1357     if (mode & TPL_DATAPEEK) {
1358
1359        first_atom = strspn(fmt, "S()"); /* skip any leading S() */
1360
1361        datapeek_flen = EM_SAFE_STRLEN(datapeek_f);
1362        if (strspn(datapeek_f, tpl_datapeek_ok_chars) < datapeek_flen) {
1363          tpl_hook.oops("invalid TPL_DATAPEEK format: %s\n", datapeek_f);
1364          tpl_hook.free(fmt_cpy); fmt_cpy = NULL; /* fail */
1365          goto fail;
1366        }
1367
1368        if (strncmp( &fmt[first_atom], datapeek_f, datapeek_flen) != 0) {
1369          tpl_hook.oops("TPL_DATAPEEK format mismatches tpl iamge\n");
1370          tpl_hook.free(fmt_cpy); fmt_cpy = NULL; /* fail */
1371          goto fail;
1372        }
1373
1374        /* advance to data start, then copy out requested elements */
1375        dv = (void*)((uintptr_t)dv +  (num_fxlens * sizeof(uint32_t)));
1376        for(datapeek_c = datapeek_f; *datapeek_c != '\0'; datapeek_c++) {
1377          datapeek_p = va_arg(ap, void*);
1378          if (*datapeek_c == 's') {  /* special handling for strings */
1379            if ((uintptr_t)dv-(uintptr_t)addr + sizeof(uint32_t) > sz) {
1380              tpl_hook.oops("tpl_peek: tpl has insufficient length\n");
1381              tpl_hook.free(fmt_cpy); fmt_cpy = NULL; /* fail */
1382              goto fail;
1383            }
1384            memcpy(&datapeek_ssz,dv,sizeof(uint32_t)); /* get slen */
1385            if (xendian) tpl_byteswap(&datapeek_ssz, sizeof(uint32_t));
1386            if (old_string_format) datapeek_ssz++;
1387            dv = (void*)((uintptr_t)dv + sizeof(uint32_t)); /* adv. to str */
1388            if (datapeek_ssz == 0) datapeek_s = NULL;
1389            else {
1390              if ((uintptr_t)dv-(uintptr_t)addr + datapeek_ssz-1 > sz) {
1391                tpl_hook.oops("tpl_peek: tpl has insufficient length\n");
1392                tpl_hook.free(fmt_cpy); fmt_cpy = NULL; /* fail */
1393                goto fail;
1394              }
1395              datapeek_s = tpl_hook.malloc(datapeek_ssz);
1396              if (datapeek_s == NULL) fatal_oom();
1397              memcpy(datapeek_s, dv, datapeek_ssz-1);
1398              datapeek_s[datapeek_ssz-1] = '\0';
1399              dv = (void*)((uintptr_t)dv + datapeek_ssz-1);
1400            }
1401            *(char**)datapeek_p = datapeek_s;
1402          } else {
1403            datapeek_csz = tpl_size_for(*datapeek_c);
1404            if ((uintptr_t)dv-(uintptr_t)addr + datapeek_csz > sz) {
1405              tpl_hook.oops("tpl_peek: tpl has insufficient length\n");
1406              tpl_hook.free(fmt_cpy); fmt_cpy = NULL; /* fail */
1407              goto fail;
1408            }
1409            memcpy(datapeek_p, dv, datapeek_csz);
1410            if (xendian) tpl_byteswap(datapeek_p, datapeek_csz);
1411            dv = (void*)((uintptr_t)dv + datapeek_csz);
1412          }
1413        }
1414     }
1415
1416 fail:
1417     va_end(ap);
1418     if ((mode & TPL_FILE) && mr.text != NULL) tpl_unmap_file( &mr );
1419     return fmt_cpy;
1420 }
1421
1422 /* tpl_jot(TPL_FILE, "file.tpl", "si", &s, &i); */
1423 /* tpl_jot(TPL_MEM, &buf, &sz, "si", &s, &i); */
1424 /* tpl_jot(TPL_FD, fd, "si", &s, &i); */
1425 TPL_API int tpl_jot(int mode, ...) {
1426     va_list ap;
1427     char *filename, *fmt;
1428     size_t *sz;
1429     int fd, rc=0;
1430     void **buf;
1431     tpl_node *tn;
1432
1433     va_start(ap,mode);
1434     if (mode & TPL_FILE) {
1435       filename = va_arg(ap,char*);
1436       fmt = va_arg(ap,char*);
1437       tn = tpl_map_va(fmt, ap);
1438       if (tn == NULL) { rc=-1; goto fail;}
1439       tpl_pack(tn, 0);
1440       rc = tpl_dump(tn, TPL_FILE, filename);
1441       tpl_free(tn);
1442     } else if (mode & TPL_MEM) {
1443       buf = va_arg(ap,void*);
1444       sz = va_arg(ap,size_t*);
1445       fmt = va_arg(ap,char*);
1446       tn = tpl_map_va(fmt,ap);
1447       if (tn == NULL) { rc=-1; goto fail;}
1448       tpl_pack(tn,0);
1449       rc = tpl_dump(tn, TPL_MEM, buf, sz);
1450       tpl_free(tn);
1451     } else if (mode & TPL_FD) {
1452       fd = va_arg(ap,int);
1453       fmt = va_arg(ap,char*);
1454       tn = tpl_map_va(fmt,ap);
1455       if (tn == NULL) { rc=-1; goto fail;}
1456       tpl_pack(tn,0);
1457       rc = tpl_dump(tn, TPL_FD, fd);
1458       tpl_free(tn);
1459     } else {
1460       tpl_hook.fatal("invalid tpl_jot mode\n");
1461     }
1462
1463 fail:
1464     va_end(ap);
1465     return rc;
1466 }
1467
1468 TPL_API int tpl_load(tpl_node *r, int mode, ...) {
1469     va_list ap;
1470     int rc=0,fd=0;
1471     char *filename=NULL;
1472     void *addr;
1473     size_t sz;
1474
1475     va_start(ap,mode);
1476     if (mode & TPL_FILE) filename = va_arg(ap,char *);
1477     else if (mode & TPL_MEM) {
1478         addr = va_arg(ap,void *);
1479         sz = va_arg(ap,size_t);
1480     } else if (mode & TPL_FD) {
1481         fd = va_arg(ap,int);
1482     } else {
1483         tpl_hook.oops("unsupported tpl_load mode %d\n", mode);
1484         return -1;
1485     }
1486     va_end(ap);
1487
1488     if (r->type != TPL_TYPE_ROOT) {
1489         tpl_hook.oops("error: tpl_load to non-root node\n");
1490         return -1;
1491     }
1492     if (((tpl_root_data*)(r->data))->flags & (TPL_WRONLY|TPL_RDONLY)) {
1493         /* already packed or loaded, so reset it as if newly mapped */
1494         tpl_free_keep_map(r);
1495     }
1496     if (mode & TPL_FILE) {
1497         if (tpl_mmap_file(filename, &((tpl_root_data*)(r->data))->mmap) != 0) {
1498             tpl_hook.oops("tpl_load failed for file %s\n", filename);
1499             return -1;
1500         }
1501         if ( (rc = tpl_sanity(r, (mode & TPL_EXCESS_OK))) != 0) {
1502             if (rc == ERR_FMT_MISMATCH) {
1503                 tpl_hook.oops("%s: format signature mismatch\n", filename);
1504             } else if (rc == ERR_FLEN_MISMATCH) {
1505                 tpl_hook.oops("%s: array lengths mismatch\n", filename);
1506             } else {
1507                 tpl_hook.oops("%s: not a valid tpl file\n", filename);
1508             }
1509             tpl_unmap_file( &((tpl_root_data*)(r->data))->mmap );
1510             return -1;
1511         }
1512         ((tpl_root_data*)(r->data))->flags = (TPL_FILE | TPL_RDONLY);
1513     } else if (mode & TPL_MEM) {
1514         ((tpl_root_data*)(r->data))->mmap.text = addr;
1515         ((tpl_root_data*)(r->data))->mmap.text_sz = sz;
1516         if ( (rc = tpl_sanity(r, (mode & TPL_EXCESS_OK))) != 0) {
1517             if (rc == ERR_FMT_MISMATCH) {
1518                 tpl_hook.oops("format signature mismatch\n");
1519             } else {
1520                 tpl_hook.oops("not a valid tpl file\n");
1521             }
1522             return -1;
1523         }
1524         ((tpl_root_data*)(r->data))->flags = (TPL_MEM | TPL_RDONLY);
1525         if (mode & TPL_UFREE) ((tpl_root_data*)(r->data))->flags |= TPL_UFREE;
1526     } else if (mode & TPL_FD) {
1527         /* if fd read succeeds, resulting mem img is used for load */
1528         if (tpl_gather(TPL_GATHER_BLOCKING,fd,&addr,&sz) > 0) {
1529             return tpl_load(r, TPL_MEM|TPL_UFREE, addr, sz);
1530         } else return -1;
1531     } else {
1532         tpl_hook.oops("invalid tpl_load mode %d\n", mode);
1533         return -1;
1534     }
1535     /* this applies to TPL_MEM or TPL_FILE */
1536     if (tpl_needs_endian_swap(((tpl_root_data*)(r->data))->mmap.text))
1537         ((tpl_root_data*)(r->data))->flags |= TPL_XENDIAN;
1538     tpl_unpackA0(r);   /* prepare root A nodes for use */
1539     return 0;
1540 }
1541
1542 TPL_API int tpl_Alen(tpl_node *r, int i) {
1543     tpl_node *n;
1544
1545     n = tpl_find_i(r,i);
1546     if (n == NULL) {
1547         tpl_hook.oops("invalid index %d to tpl_unpack\n", i);
1548         return -1;
1549     }
1550     if (n->type != TPL_TYPE_ARY) return -1;
1551     return ((tpl_atyp*)(n->data))->num;
1552 }
1553
1554 static void tpl_free_atyp(tpl_node *n, tpl_atyp *atyp) {
1555     tpl_backbone *bb,*bbnxt;
1556     tpl_node *c;
1557     void *dv;
1558     tpl_bin *binp;
1559     tpl_atyp *atypp;
1560     char *strp;
1561     size_t itermax;
1562     tpl_pound_data *pd;
1563     int i;
1564
1565     bb = atyp->bb;
1566     while (bb) {
1567         bbnxt = bb->next;
1568         dv = bb->data;
1569         c=n->children;
1570         while (c) {
1571             switch (c->type) {
1572                 case TPL_TYPE_BYTE:
1573                 case TPL_TYPE_DOUBLE:
1574                 case TPL_TYPE_INT32:
1575                 case TPL_TYPE_UINT32:
1576                 case TPL_TYPE_INT64:
1577                 case TPL_TYPE_UINT64:
1578                 case TPL_TYPE_INT16:
1579                 case TPL_TYPE_UINT16:
1580                     dv = (void*)((uintptr_t)dv + tpl_types[c->type].sz*c->num);
1581                     break;
1582                 case TPL_TYPE_BIN:
1583                     memcpy(&binp,dv,sizeof(tpl_bin*)); /* cp to aligned */
1584                     if (binp->addr) tpl_hook.free( binp->addr ); /* free buf */
1585                     tpl_hook.free(binp);  /* free tpl_bin */
1586                     dv = (void*)((uintptr_t)dv + sizeof(tpl_bin*));
1587                     break;
1588                 case TPL_TYPE_STR:
1589                     for(i=0; i < c->num; i++) {
1590                       memcpy(&strp,dv,sizeof(char*)); /* cp to aligned */
1591                       if (strp) tpl_hook.free(strp); /* free string */
1592                       dv = (void*)((uintptr_t)dv + sizeof(char*));
1593                     }
1594                     break;
1595                 case TPL_TYPE_POUND:
1596                     /* iterate over the preceding nodes */
1597                     itermax = c->num;
1598                     pd = (tpl_pound_data*)c->data;
1599                     if (++(pd->iternum) < itermax) {
1600                       c = pd->iter_start_node;
1601                       continue;
1602                     } else { /* loop complete. */
1603                       pd->iternum = 0;
1604                     }
1605                     break;
1606                 case TPL_TYPE_ARY:
1607                     memcpy(&atypp,dv,sizeof(tpl_atyp*)); /* cp to aligned */
1608                     tpl_free_atyp(c,atypp);  /* free atyp */
1609                     dv = (void*)((uintptr_t)dv + sizeof(void*));
1610                     break;
1611                 default:
1612                     tpl_hook.fatal("unsupported format character\n");
1613                     break;
1614             }
1615             c=c->next;
1616         }
1617         tpl_hook.free(bb);
1618         bb = bbnxt;
1619     }
1620     tpl_hook.free(atyp);
1621 }
1622
1623 /* determine (by walking) byte length of serialized r/A node at address dv
1624  * returns 0 on success, or -1 if the tpl isn't trustworthy (fails consistency)
1625  */
1626 static int tpl_serlen(tpl_node *r, tpl_node *n, void *dv, size_t *serlen) {
1627     uint32_t slen;
1628     int num,fidx;
1629     tpl_node *c;
1630     size_t len=0, alen, buf_past, itermax;
1631     tpl_pound_data *pd;
1632
1633     buf_past = ((uintptr_t)((tpl_root_data*)(r->data))->mmap.text +
1634                       ((tpl_root_data*)(r->data))->mmap.text_sz);
1635
1636     if (n->type == TPL_TYPE_ROOT) num = 1;
1637     else if (n->type == TPL_TYPE_ARY) {
1638         if ((uintptr_t)dv + sizeof(uint32_t) > buf_past) return -1;
1639         memcpy(&num,dv,sizeof(uint32_t));
1640         if (((tpl_root_data*)(r->data))->flags & TPL_XENDIAN)
1641              tpl_byteswap(&num, sizeof(uint32_t));
1642         dv = (void*)((uintptr_t)dv + sizeof(uint32_t));
1643         len += sizeof(uint32_t);
1644     } else tpl_hook.fatal("internal error in tpl_serlen\n");
1645
1646     while (num-- > 0) {
1647         c=n->children;
1648         while (c) {
1649             switch (c->type) {
1650                 case TPL_TYPE_BYTE:
1651                 case TPL_TYPE_DOUBLE:
1652                 case TPL_TYPE_INT32:
1653                 case TPL_TYPE_UINT32:
1654                 case TPL_TYPE_INT64:
1655                 case TPL_TYPE_UINT64:
1656                 case TPL_TYPE_INT16:
1657                 case TPL_TYPE_UINT16:
1658                     for(fidx=0; fidx < c->num; fidx++) {  /* octothorpe support */
1659                         if ((uintptr_t)dv + tpl_types[c->type].sz > buf_past) return -1;
1660                         dv = (void*)((uintptr_t)dv + tpl_types[c->type].sz);
1661                         len += tpl_types[c->type].sz;
1662                     }
1663                     break;
1664                 case TPL_TYPE_BIN:
1665                     len += sizeof(uint32_t);
1666                     if ((uintptr_t)dv + sizeof(uint32_t) > buf_past) return -1;
1667                     memcpy(&slen,dv,sizeof(uint32_t));
1668                     if (((tpl_root_data*)(r->data))->flags & TPL_XENDIAN)
1669                         tpl_byteswap(&slen, sizeof(uint32_t));
1670                     len += slen;
1671                     dv = (void*)((uintptr_t)dv + sizeof(uint32_t));
1672                     if ((uintptr_t)dv + slen > buf_past) return -1;
1673                     dv = (void*)((uintptr_t)dv + slen);
1674                     break;
1675                 case TPL_TYPE_STR:
1676                     for(fidx=0; fidx < c->num; fidx++) {  /* octothorpe support */
1677                       len += sizeof(uint32_t);
1678                       if ((uintptr_t)dv + sizeof(uint32_t) > buf_past) return -1;
1679                       memcpy(&slen,dv,sizeof(uint32_t));
1680                       if (((tpl_root_data*)(r->data))->flags & TPL_XENDIAN)
1681                           tpl_byteswap(&slen, sizeof(uint32_t));
1682                       if (!(((tpl_root_data*)(r->data))->flags & TPL_OLD_STRING_FMT))
1683                          slen = (slen>1) ? (slen-1) : 0;
1684                       len += slen;
1685                       dv = (void*)((uintptr_t)dv + sizeof(uint32_t));
1686                       if ((uintptr_t)dv + slen > buf_past) return -1;
1687                       dv = (void*)((uintptr_t)dv + slen);
1688                     }
1689                     break;
1690                 case TPL_TYPE_ARY:
1691                     if ( tpl_serlen(r,c,dv, &alen) == -1) return -1;
1692                     dv = (void*)((uintptr_t)dv + alen);
1693                     len += alen;
1694                     break;
1695                 case TPL_TYPE_POUND:
1696                     /* iterate over the preceding nodes */
1697                     itermax = c->num;
1698                     pd = (tpl_pound_data*)c->data;
1699                     if (++(pd->iternum) < itermax) {
1700                       c = pd->iter_start_node;
1701                       continue;
1702                     } else { /* loop complete. */
1703                       pd->iternum = 0;
1704                     }
1705                     break;
1706                 default:
1707                     tpl_hook.fatal("unsupported format character\n");
1708                     break;
1709             }
1710             c=c->next;
1711         }
1712     }
1713     *serlen = len;
1714     return 0;
1715 }
1716
1717 static int tpl_mmap_output_file(char *filename, size_t sz, void **text_out) {
1718     void *text;
1719     int fd,perms;
1720
1721 #ifndef _WIN32
1722     perms = S_IRUSR|S_IWUSR|S_IWGRP|S_IRGRP|S_IROTH;  /* ug+w o+r */
1723     fd=open(filename,O_CREAT|O_TRUNC|O_RDWR,perms);
1724 #else
1725         perms = _S_IWRITE;
1726     fd=_open(filename,_O_CREAT|_O_TRUNC|_O_RDWR,perms);
1727 #endif
1728
1729     if ( fd == -1 ) {
1730         tpl_hook.oops("Couldn't open file %s: %s\n", filename, strerror(errno));
1731         return -1;
1732     }
1733
1734     text = mmap(0, sz, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
1735     if (text == MAP_FAILED) {
1736         tpl_hook.oops("Failed to mmap %s: %s\n", filename, strerror(errno));
1737         close(fd);
1738         return -1;
1739     }
1740     if (ftruncate(fd,sz) == -1) {
1741         tpl_hook.oops("ftruncate failed: %s\n", strerror(errno));
1742         munmap( text, sz );
1743         close(fd);
1744         return -1;
1745     }
1746     *text_out = text;
1747     return fd;
1748 }
1749
1750 static int tpl_mmap_file(char *filename, tpl_mmap_rec *mr) {
1751     struct stat stat_buf;
1752
1753     if ( (mr->fd = open(filename, O_RDONLY)) == -1 ) {
1754         tpl_hook.oops("Couldn't open file %s: %s\n", filename, strerror(errno));
1755         return -1;
1756     }
1757
1758     if ( fstat(mr->fd, &stat_buf) == -1) {
1759         close(mr->fd);
1760         tpl_hook.oops("Couldn't stat file %s: %s\n", filename, strerror(errno));
1761         return -1;
1762     }
1763
1764     mr->text_sz = (size_t)stat_buf.st_size;
1765     mr->text = mmap(0, stat_buf.st_size, PROT_READ, MAP_PRIVATE, mr->fd, 0);
1766     if (mr->text == MAP_FAILED) {
1767         close(mr->fd);
1768         tpl_hook.oops("Failed to mmap %s: %s\n", filename, strerror(errno));
1769         return -1;
1770     }
1771
1772     return 0;
1773 }
1774
1775 TPL_API int tpl_pack(tpl_node *r, int i) {
1776     tpl_node *n, *child, *np;
1777     void *datav=NULL;
1778     size_t sz, itermax;
1779     uint32_t slen;
1780     char *str;
1781     tpl_bin *bin;
1782     tpl_pound_data *pd;
1783     int fidx;
1784
1785     n = tpl_find_i(r,i);
1786     if (n == NULL) {
1787         tpl_hook.oops("invalid index %d to tpl_pack\n", i);
1788         return -1;
1789     }
1790
1791     if (((tpl_root_data*)(r->data))->flags & TPL_RDONLY) {
1792         /* convert to an writeable tpl, initially empty */
1793         tpl_free_keep_map(r);
1794     }
1795
1796     ((tpl_root_data*)(r->data))->flags |= TPL_WRONLY;
1797
1798     if (n->type == TPL_TYPE_ARY) datav = tpl_extend_backbone(n);
1799     child = n->children;
1800     while(child) {
1801         switch(child->type) {
1802             case TPL_TYPE_BYTE:
1803             case TPL_TYPE_DOUBLE:
1804             case TPL_TYPE_INT32:
1805             case TPL_TYPE_UINT32:
1806             case TPL_TYPE_INT64:
1807             case TPL_TYPE_UINT64:
1808             case TPL_TYPE_INT16:
1809             case TPL_TYPE_UINT16:
1810                 /* no need to use fidx iteration here; we can copy multiple values in one memcpy */
1811                 memcpy(child->data,child->addr,tpl_types[child->type].sz * child->num);
1812                 if (datav) datav = tpl_cpv(datav,child->data,tpl_types[child->type].sz * child->num);
1813                 if (n->type == TPL_TYPE_ARY) n->ser_osz += tpl_types[child->type].sz * child->num;
1814                 break;
1815             case TPL_TYPE_BIN:
1816                 /* copy the buffer to be packed */
1817                 slen = ((tpl_bin*)child->addr)->sz;
1818                 if (slen >0) {
1819                     str = tpl_hook.malloc(slen);
1820                     if (!str) fatal_oom();
1821                     memcpy(str,((tpl_bin*)child->addr)->addr,slen);
1822                 } else str = NULL;
1823                 /* and make a tpl_bin to point to it */
1824                 bin = tpl_hook.malloc(sizeof(tpl_bin));
1825                 if (!bin) fatal_oom();
1826                 bin->addr = str;
1827                 bin->sz = slen;
1828                 /* now pack its pointer, first deep freeing any pre-existing bin */
1829                 if (*(tpl_bin**)(child->data) != NULL) {
1830                     if ((*(tpl_bin**)(child->data))->sz != 0) {
1831                             tpl_hook.free( (*(tpl_bin**)(child->data))->addr );
1832                     }
1833                     tpl_hook.free(*(tpl_bin**)(child->data));
1834                 }
1835                 memcpy(child->data,&bin,sizeof(tpl_bin*));
1836                 if (datav) {
1837                     datav = tpl_cpv(datav, &bin, sizeof(tpl_bin*));
1838                     *(tpl_bin**)(child->data) = NULL;
1839                 }
1840                 if (n->type == TPL_TYPE_ARY) {
1841                     n->ser_osz += sizeof(uint32_t); /* binary buf len word */
1842                     n->ser_osz += bin->sz;          /* binary buf */
1843                 }
1844                 break;
1845             case TPL_TYPE_STR:
1846                 for(fidx=0; fidx < child->num; fidx++) {
1847                   /* copy the string to be packed. slen includes \0. this
1848                      block also works if the string pointer is NULL. */
1849                   char *caddr = ((char**)child->addr)[fidx];
1850                   char **cdata = &((char**)child->data)[fidx];
1851                   slen = caddr ?  (EM_SAFE_STRLEN(caddr) + 1) : 0;
1852                   if (slen) {
1853                     str = tpl_hook.malloc(slen);
1854                     if (!str) fatal_oom();
1855                     memcpy(str,caddr,slen); /* include \0 */
1856                   } else {
1857                     str = NULL;
1858                   }
1859                   /* now pack its pointer, first freeing any pre-existing string */
1860                   if (*cdata != NULL) {
1861                       tpl_hook.free(*cdata);
1862                   }
1863                   memcpy(cdata,&str,sizeof(char*));
1864                   if (datav) {
1865                       datav = tpl_cpv(datav, &str, sizeof(char*));
1866                       *cdata = NULL;
1867                   }
1868                   if (n->type == TPL_TYPE_ARY) {
1869                       n->ser_osz += sizeof(uint32_t); /* string len word */
1870                       if (slen>1) n->ser_osz += slen-1;/* string (without nul) */
1871                   }
1872                 }
1873                 break;
1874             case TPL_TYPE_ARY:
1875                 /* copy the child's tpl_atype* and reset it to empty */
1876                 if (datav) {
1877                     sz = ((tpl_atyp*)(child->data))->sz;
1878                     datav = tpl_cpv(datav, &child->data, sizeof(void*));
1879                     child->data = tpl_hook.malloc(sizeof(tpl_atyp));
1880                     if (!child->data) fatal_oom();
1881                     ((tpl_atyp*)(child->data))->num = 0;
1882                     ((tpl_atyp*)(child->data))->sz = sz;
1883                     ((tpl_atyp*)(child->data))->bb = NULL;
1884                     ((tpl_atyp*)(child->data))->bbtail = NULL;
1885                 }
1886                 /* parent is array? then bubble up child array's ser_osz */
1887                 if (n->type == TPL_TYPE_ARY) {
1888                     n->ser_osz += sizeof(uint32_t); /* array len word */
1889                     n->ser_osz += child->ser_osz;   /* child array ser_osz */
1890                     child->ser_osz = 0;             /* reset child array ser_osz */
1891                 }
1892                 break;
1893
1894             case TPL_TYPE_POUND:
1895                 /* we need to iterate n times over preceding nodes in S(...).
1896                  * we may be in the midst of an iteration each time or starting. */
1897                  pd = (tpl_pound_data*)child->data;
1898                  itermax = child->num;
1899
1900                  /* itermax is total num of iterations needed  */
1901                  /* pd->iternum is current iteration index  */
1902                  /* pd->inter_elt_len is element-to-element len of contiguous structs */
1903                  /* pd->iter_start_node is where we jump to at each iteration. */
1904
1905                  if (++(pd->iternum) < itermax) {
1906
1907                    /* in start or midst of loop. advance addr/data pointers. */
1908                    for(np=pd->iter_start_node; np != child; np = np->next) {
1909                      np->data = (char*)(np->data) +
1910                           (tpl_types[np->type].sz * np->num);
1911                      np->addr = (char*)(np->addr) + pd->inter_elt_len;
1912                    }
1913                    /* do next iteration */
1914                    child = pd->iter_start_node;
1915                    continue;
1916
1917                  } else { /* loop complete. */
1918
1919                    /* reset iteration index and addr/data pointers. */
1920                    pd->iternum = 0;
1921                    for(np=pd->iter_start_node; np != child; np = np->next) {
1922                      np->data = (char*)(np->data) - ((itermax-1) *
1923                                                       tpl_types[np->type].sz *
1924                                                       np->num);
1925                      np->addr = (char*)(np->addr) - ((itermax-1) * pd->inter_elt_len);
1926                    }
1927
1928                  }
1929                 break;
1930             default:
1931                 tpl_hook.fatal("unsupported format character\n");
1932                 break;
1933         }
1934         child=child->next;
1935     }
1936     return 0;
1937 }
1938
1939 TPL_API int tpl_unpack(tpl_node *r, int i) {
1940     tpl_node *n, *c, *np;
1941     uint32_t slen;
1942     int rc=1, fidx;
1943     char *str;
1944     void *dv=NULL, *caddr;
1945     size_t A_bytes, itermax;
1946     tpl_pound_data *pd;
1947     void *img;
1948     size_t sz;
1949
1950
1951     /* handle unusual case of tpl_pack,tpl_unpack without an
1952      * intervening tpl_dump. do a dump/load implicitly. */
1953     if (((tpl_root_data*)(r->data))->flags & TPL_WRONLY) {
1954         if (tpl_dump(r,TPL_MEM,&img,&sz) != 0) return -1;
1955         if (tpl_load(r,TPL_MEM|TPL_UFREE,img,sz) != 0) {
1956             tpl_hook.free(img);
1957             return -1;
1958         };
1959     }
1960
1961     n = tpl_find_i(r,i);
1962     if (n == NULL) {
1963         tpl_hook.oops("invalid index %d to tpl_unpack\n", i);
1964         return -1;
1965     }
1966
1967     /* either root node or an A node */
1968     if (n->type == TPL_TYPE_ROOT) {
1969         dv = tpl_find_data_start( ((tpl_root_data*)(n->data))->mmap.text );
1970     } else if (n->type == TPL_TYPE_ARY) {
1971         if (((tpl_atyp*)(n->data))->num <= 0) return 0; /* array consumed */
1972         else rc = ((tpl_atyp*)(n->data))->num--;
1973         dv = ((tpl_atyp*)(n->data))->cur;
1974         if (!dv) tpl_hook.fatal("must unpack parent of node before node itself\n");
1975     }
1976
1977     c = n->children;
1978     while (c) {
1979         switch (c->type) {
1980             case TPL_TYPE_BYTE:
1981             case TPL_TYPE_DOUBLE:
1982             case TPL_TYPE_INT32:
1983             case TPL_TYPE_UINT32:
1984             case TPL_TYPE_INT64:
1985             case TPL_TYPE_UINT64:
1986             case TPL_TYPE_INT16:
1987             case TPL_TYPE_UINT16:
1988                 /* unpack elements of cross-endian octothorpic array individually */
1989                 if (((tpl_root_data*)(r->data))->flags & TPL_XENDIAN) {
1990                     for(fidx=0; fidx < c->num; fidx++) {
1991                         caddr = (void*)((uintptr_t)c->addr + (fidx * tpl_types[c->type].sz));
1992                         memcpy(caddr,dv,tpl_types[c->type].sz);
1993                         tpl_byteswap(caddr, tpl_types[c->type].sz);
1994                         dv = (void*)((uintptr_t)dv + tpl_types[c->type].sz);
1995                     }
1996                 } else {
1997                     /* bulk unpack ok if not cross-endian */
1998                     memcpy(c->addr, dv, tpl_types[c->type].sz * c->num);
1999                     dv = (void*)((uintptr_t)dv + tpl_types[c->type].sz * c->num);
2000                 }
2001                 break;
2002             case TPL_TYPE_BIN:
2003                 memcpy(&slen,dv,sizeof(uint32_t));
2004                 if (((tpl_root_data*)(r->data))->flags & TPL_XENDIAN)
2005                     tpl_byteswap(&slen, sizeof(uint32_t));
2006                 if (slen > 0) {
2007                     str = (char*)tpl_hook.malloc(slen);
2008                     if (!str) fatal_oom();
2009                 } else str=NULL;
2010                 dv = (void*)((uintptr_t)dv + sizeof(uint32_t));
2011                 if (slen>0) memcpy(str,dv,slen);
2012                 memcpy(&(((tpl_bin*)c->addr)->addr),&str,sizeof(void*));
2013                 memcpy(&(((tpl_bin*)c->addr)->sz),&slen,sizeof(uint32_t));
2014                 dv = (void*)((uintptr_t)dv + slen);
2015                 break;
2016             case TPL_TYPE_STR:
2017                 for(fidx=0; fidx < c->num; fidx++) {
2018                   memcpy(&slen,dv,sizeof(uint32_t));
2019                   if (((tpl_root_data*)(r->data))->flags & TPL_XENDIAN)
2020                       tpl_byteswap(&slen, sizeof(uint32_t));
2021                   if (((tpl_root_data*)(r->data))->flags & TPL_OLD_STRING_FMT)
2022                     slen += 1;
2023                   dv = (void*)((uintptr_t)dv + sizeof(uint32_t));
2024                   if (slen) {  /* slen includes \0 */
2025                     str = (char*)tpl_hook.malloc(slen);
2026                     if (!str) fatal_oom();
2027                     if (slen>1) memcpy(str,dv,slen-1);
2028                     str[slen-1] = '\0'; /* nul terminate */
2029                     dv = (void*)((uintptr_t)dv + slen-1);
2030                   } else str=NULL;
2031                   memcpy(&((char**)c->addr)[fidx],&str,sizeof(char*));
2032                 }
2033                 break;
2034             case TPL_TYPE_POUND:
2035                 /* iterate over preceding nodes */
2036                 pd = (tpl_pound_data*)c->data;
2037                 itermax = c->num;
2038                 if (++(pd->iternum) < itermax) {
2039                   /* in start or midst of loop. advance addr/data pointers. */
2040                   for(np=pd->iter_start_node; np != c; np = np->next) {
2041                     np->addr = (char*)(np->addr) + pd->inter_elt_len;
2042                   }
2043                   /* do next iteration */
2044                   c = pd->iter_start_node;
2045                   continue;
2046
2047                 } else { /* loop complete. */
2048
2049                   /* reset iteration index and addr/data pointers. */
2050                   pd->iternum = 0;
2051                   for(np=pd->iter_start_node; np != c; np = np->next) {
2052                     np->addr = (char*)(np->addr) - ((itermax-1) * pd->inter_elt_len);
2053                   }
2054
2055                 }
2056                 break;
2057             case TPL_TYPE_ARY:
2058                 if (tpl_serlen(r,c,dv, &A_bytes) == -1)
2059                     tpl_hook.fatal("internal error in unpack\n");
2060                 memcpy( &((tpl_atyp*)(c->data))->num, dv, sizeof(uint32_t));
2061                 if (((tpl_root_data*)(r->data))->flags & TPL_XENDIAN)
2062                     tpl_byteswap(&((tpl_atyp*)(c->data))->num, sizeof(uint32_t));
2063                 ((tpl_atyp*)(c->data))->cur = (void*)((uintptr_t)dv+sizeof(uint32_t));
2064                 dv = (void*)((uintptr_t)dv + A_bytes);
2065                 break;
2066             default:
2067                 tpl_hook.fatal("unsupported format character\n");
2068                 break;
2069         }
2070
2071         c = c->next;
2072     }
2073     if (n->type == TPL_TYPE_ARY) ((tpl_atyp*)(n->data))->cur = dv; /* next element */
2074     return rc;
2075 }
2076
2077 /* Specialized function that unpacks only the root's A nodes, after tpl_load  */
2078 static int tpl_unpackA0(tpl_node *r) {
2079     tpl_node *n, *c;
2080     uint32_t slen;
2081     int rc=1,fidx,i;
2082     void *dv;
2083     size_t A_bytes, itermax;
2084     tpl_pound_data *pd;
2085
2086     n = r;
2087     dv = tpl_find_data_start( ((tpl_root_data*)(r->data))->mmap.text);
2088
2089     c=n->children;
2090     while (c)  {
2091         switch (c->type) {
2092             case TPL_TYPE_BYTE:
2093             case TPL_TYPE_DOUBLE:
2094             case TPL_TYPE_INT32:
2095             case TPL_TYPE_UINT32:
2096             case TPL_TYPE_INT64:
2097             case TPL_TYPE_UINT64:
2098             case TPL_TYPE_INT16:
2099             case TPL_TYPE_UINT16:
2100                 for(fidx=0;fidx < c->num; fidx++) {
2101                     dv = (void*)((uintptr_t)dv + tpl_types[c->type].sz);
2102                 }
2103                 break;
2104             case TPL_TYPE_BIN:
2105                 memcpy(&slen,dv,sizeof(uint32_t));
2106                 if (((tpl_root_data*)(r->data))->flags & TPL_XENDIAN)
2107                     tpl_byteswap(&slen, sizeof(uint32_t));
2108                 dv = (void*)((uintptr_t)dv + sizeof(uint32_t));
2109                 dv = (void*)((uintptr_t)dv + slen);
2110                 break;
2111             case TPL_TYPE_STR:
2112                 for(i=0; i<c->num; i++) {
2113                   memcpy(&slen,dv,sizeof(uint32_t));
2114                   if (((tpl_root_data*)(r->data))->flags & TPL_XENDIAN)
2115                       tpl_byteswap(&slen, sizeof(uint32_t));
2116                   if (((tpl_root_data*)(r->data))->flags & TPL_OLD_STRING_FMT)
2117                     slen += 1;
2118                   dv = (void*)((uintptr_t)dv + sizeof(uint32_t));
2119                   if (slen>1) dv = (void*)((uintptr_t)dv + slen-1);
2120                 }
2121                 break;
2122             case TPL_TYPE_POUND:
2123                 /* iterate over the preceding nodes */
2124                 itermax = c->num;
2125                 pd = (tpl_pound_data*)c->data;
2126                 if (++(pd->iternum) < itermax) {
2127                   c = pd->iter_start_node;
2128                   continue;
2129                 } else { /* loop complete. */
2130                   pd->iternum = 0;
2131                 }
2132                 break;
2133             case TPL_TYPE_ARY:
2134                 if ( tpl_serlen(r,c,dv, &A_bytes) == -1)
2135                     tpl_hook.fatal("internal error in unpackA0\n");
2136                 memcpy( &((tpl_atyp*)(c->data))->num, dv, sizeof(uint32_t));
2137                 if (((tpl_root_data*)(r->data))->flags & TPL_XENDIAN)
2138                     tpl_byteswap(&((tpl_atyp*)(c->data))->num, sizeof(uint32_t));
2139                 ((tpl_atyp*)(c->data))->cur = (void*)((uintptr_t)dv+sizeof(uint32_t));
2140                 dv = (void*)((uintptr_t)dv + A_bytes);
2141                 break;
2142             default:
2143                 tpl_hook.fatal("unsupported format character\n");
2144                 break;
2145         }
2146         c=c->next;
2147     }
2148     return rc;
2149 }
2150
2151 /* In-place byte order swapping of a word of length "len" bytes */
2152 static void tpl_byteswap(void *word, int len) {
2153     int i;
2154     char c, *w;
2155     w = (char*)word;
2156     for(i=0; i<len/2; i++) {
2157         c = w[i];
2158         w[i] = w[len-1-i];
2159         w[len-1-i] = c;
2160     }
2161 }
2162
2163 static void tpl_fatal(char *fmt, ...) {
2164     va_list ap;
2165     char exit_msg[100];
2166
2167     va_start(ap,fmt);
2168     vsnprintf(exit_msg, 100, fmt, ap);
2169     va_end(ap);
2170
2171     tpl_hook.oops("%s", exit_msg);
2172     exit(-1);
2173 }
2174
2175 TPL_API int tpl_gather(int mode, ...) {
2176     va_list ap;
2177     int fd,rc=0;
2178     size_t *szp,sz;
2179     void **img,*addr,*data;
2180     tpl_gather_t **gs;
2181     tpl_gather_cb *cb;
2182
2183     va_start(ap,mode);
2184     switch (mode) {
2185         case TPL_GATHER_BLOCKING:
2186             fd = va_arg(ap,int);
2187             img = va_arg(ap,void*);
2188             szp = va_arg(ap,size_t*);
2189             rc = tpl_gather_blocking(fd,img,szp);
2190             break;
2191         case TPL_GATHER_NONBLOCKING:
2192             fd = va_arg(ap,int);
2193             gs = (tpl_gather_t**)va_arg(ap,void*);
2194             cb = (tpl_gather_cb*)va_arg(ap,tpl_gather_cb*);
2195             data = va_arg(ap,void*);
2196             rc = tpl_gather_nonblocking(fd,gs,cb,data);
2197             break;
2198         case TPL_GATHER_MEM:
2199             addr = va_arg(ap,void*);
2200             sz = va_arg(ap,size_t);
2201             gs = (tpl_gather_t**)va_arg(ap,void*);
2202             cb = (tpl_gather_cb*)va_arg(ap,tpl_gather_cb*);
2203             data = va_arg(ap,void*);
2204             rc = tpl_gather_mem(addr,sz,gs,cb,data);
2205             break;
2206         default:
2207             tpl_hook.fatal("unsupported tpl_gather mode %d\n",mode);
2208             break;
2209     }
2210     va_end(ap);
2211     return rc;
2212 }
2213
2214 /* dequeue a tpl by reading until one full tpl image is obtained.
2215  * We take care not to read past the end of the tpl.
2216  * This is intended as a blocking call i.e. for use with a blocking fd.
2217  * It can be given a non-blocking fd, but the read spins if we have to wait.
2218  */
2219 static int tpl_gather_blocking(int fd, void **img, size_t *sz) {
2220     char preamble[8];
2221     int i=0, rc;
2222     uint32_t tpllen;
2223
2224     do {
2225         rc = read(fd,&preamble[i],8-i);
2226         i += (rc>0) ? rc : 0;
2227     } while ((rc==-1 && (errno==EINTR||errno==EAGAIN)) || (rc>0 && i<8));
2228
2229     if (rc<0) {
2230         tpl_hook.oops("tpl_gather_fd_blocking failed: %s\n", strerror(errno));
2231         return -1;
2232     } else if (rc == 0) {
2233         /* tpl_hook.oops("tpl_gather_fd_blocking: eof\n"); */
2234         return 0;
2235     } else if (i != 8) {
2236         tpl_hook.oops("internal error\n");
2237         return -1;
2238     }
2239
2240     if (preamble[0] == 't' && preamble[1] == 'p' && preamble[2] == 'l') {
2241         memcpy(&tpllen,&preamble[4],4);
2242         if (tpl_needs_endian_swap(preamble)) tpl_byteswap(&tpllen,4);
2243     } else {
2244         tpl_hook.oops("tpl_gather_fd_blocking: non-tpl input\n");
2245         return -1;
2246     }
2247
2248     /* malloc space for remainder of tpl image (overall length tpllen)
2249      * and read it in
2250      */
2251     if (tpl_hook.gather_max > 0 &&
2252         tpllen > tpl_hook.gather_max) {
2253         tpl_hook.oops("tpl exceeds max length %d\n",
2254             tpl_hook.gather_max);
2255         return -2;
2256     }
2257     *sz = tpllen;
2258     if ( (*img = tpl_hook.malloc(tpllen)) == NULL) {
2259         fatal_oom();
2260     }
2261
2262     memcpy(*img,preamble,8);  /* copy preamble to output buffer */
2263     i=8;
2264     do {
2265         rc = read(fd,&((*(char**)img)[i]),tpllen-i);
2266         i += (rc>0) ? rc : 0;
2267     } while ((rc==-1 && (errno==EINTR||errno==EAGAIN)) || (rc>0 && i<tpllen));
2268
2269     if (rc<0) {
2270         tpl_hook.oops("tpl_gather_fd_blocking failed: %s\n", strerror(errno));
2271         tpl_hook.free(*img);
2272         return -1;
2273     } else if (rc == 0) {
2274         /* tpl_hook.oops("tpl_gather_fd_blocking: eof\n"); */
2275         tpl_hook.free(*img);
2276         return 0;
2277     } else if (i != tpllen) {
2278         tpl_hook.oops("internal error\n");
2279         tpl_hook.free(*img);
2280         return -1;
2281     }
2282
2283     return 1;
2284 }
2285
2286 /* Used by select()-driven apps which want to gather tpl images piecemeal */
2287 /* the file descriptor must be non-blocking for this functino to work. */
2288 static int tpl_gather_nonblocking( int fd, tpl_gather_t **gs, tpl_gather_cb *cb, void *data) {
2289     char buf[TPL_GATHER_BUFLEN], *img, *tpl;
2290     int rc, keep_looping, cbrc=0;
2291     size_t catlen;
2292     uint32_t tpllen;
2293
2294     while (1) {
2295         rc = read(fd,buf,TPL_GATHER_BUFLEN);
2296         if (rc == -1) {
2297             if (errno == EINTR) continue;  /* got signal during read, ignore */
2298             if (errno == EAGAIN) return 1; /* nothing to read right now */
2299             else {
2300                 tpl_hook.oops("tpl_gather failed: %s\n", strerror(errno));
2301                 if (*gs) {
2302                     tpl_hook.free((*gs)->img);
2303                     tpl_hook.free(*gs);
2304                     *gs = NULL;
2305                 }
2306                 return -1;                 /* error, caller should close fd  */
2307             }
2308         } else if (rc == 0) {
2309             if (*gs) {
2310                 tpl_hook.oops("tpl_gather: partial tpl image precedes EOF\n");
2311                 tpl_hook.free((*gs)->img);
2312                 tpl_hook.free(*gs);
2313                 *gs = NULL;
2314             }
2315             return 0;                      /* EOF, caller should close fd */
2316         } else {
2317             /* concatenate any partial tpl from last read with new buffer */
2318             if (*gs) {
2319                 catlen = (*gs)->len + rc;
2320                 if (tpl_hook.gather_max > 0 &&
2321                     catlen > tpl_hook.gather_max) {
2322                     tpl_hook.free( (*gs)->img );
2323                     tpl_hook.free( (*gs) );
2324                     *gs = NULL;
2325                     tpl_hook.oops("tpl exceeds max length %d\n",
2326                         tpl_hook.gather_max);
2327                     return -2;              /* error, caller should close fd */
2328                 }
2329                 if ( (img = tpl_hook.realloc((*gs)->img, catlen)) == NULL) {
2330                     fatal_oom();
2331                 }
2332                 memcpy(img + (*gs)->len, buf, rc);
2333                 tpl_hook.free(*gs);
2334                 *gs = NULL;
2335             } else {
2336                 img = buf;
2337                 catlen = rc;
2338             }
2339             /* isolate any full tpl(s) in img and invoke cb for each */
2340             tpl = img;
2341             keep_looping = (tpl+8 < img+catlen) ? 1 : 0;
2342             while (keep_looping) {
2343                 if (strncmp("tpl", tpl, 3) != 0) {
2344                     tpl_hook.oops("tpl prefix invalid\n");
2345                     if (img != buf) tpl_hook.free(img);
2346                     tpl_hook.free(*gs);
2347                     *gs = NULL;
2348                     return -3; /* error, caller should close fd */
2349                 }
2350                 memcpy(&tpllen,&tpl[4],4);
2351                 if (tpl_needs_endian_swap(tpl)) tpl_byteswap(&tpllen,4);
2352                 if (tpl+tpllen <= img+catlen) {
2353                     cbrc = (cb)(tpl,tpllen,data);  /* invoke cb for tpl image */
2354                     tpl += tpllen;                 /* point to next tpl image */
2355                     if (cbrc < 0) keep_looping = 0;
2356                     else keep_looping = (tpl+8 < img+catlen) ? 1 : 0;
2357                 } else keep_looping=0;
2358             }
2359             /* check if app callback requested closure of tpl source */
2360             if (cbrc < 0) {
2361                 tpl_hook.oops("tpl_fd_gather aborted by app callback\n");
2362                 if (img != buf) tpl_hook.free(img);
2363                 if (*gs) tpl_hook.free(*gs);
2364                 *gs = NULL;
2365                 return -4;
2366             }
2367             /* store any leftover, partial tpl fragment for next read */
2368             if (tpl == img && img != buf) {
2369                 /* consumed nothing from img!=buf */
2370                 if ( (*gs = tpl_hook.malloc(sizeof(tpl_gather_t))) == NULL ) {
2371                     fatal_oom();
2372                 }
2373                 (*gs)->img = tpl;
2374                 (*gs)->len = catlen;
2375             } else if (tpl < img+catlen) {
2376                 /* consumed 1+ tpl(s) from img!=buf or 0 from img==buf */
2377                 if ( (*gs = tpl_hook.malloc(sizeof(tpl_gather_t))) == NULL ) {
2378                     fatal_oom();
2379                 }
2380                 if ( ((*gs)->img = tpl_hook.malloc(img+catlen - tpl)) == NULL ) {
2381                     fatal_oom();
2382                 }
2383                 (*gs)->len = img+catlen - tpl;
2384                 memcpy( (*gs)->img, tpl, img+catlen - tpl);
2385                 /* free partially consumed concat buffer if used */
2386                 if (img != buf) tpl_hook.free(img);
2387             } else {                        /* tpl(s) fully consumed */
2388                 /* free consumed concat buffer if used */
2389                 if (img != buf) tpl_hook.free(img);
2390             }
2391         }
2392     }
2393 }
2394
2395 /* gather tpl piecemeal from memory buffer (not fd) e.g., from a lower-level api */
2396 static int tpl_gather_mem( char *buf, size_t len, tpl_gather_t **gs, tpl_gather_cb *cb, void *data) {
2397     char *img, *tpl;
2398     int keep_looping, cbrc=0;
2399     size_t catlen;
2400     uint32_t tpllen;
2401
2402     /* concatenate any partial tpl from last read with new buffer */
2403     if (*gs) {
2404         catlen = (*gs)->len + len;
2405         if (tpl_hook.gather_max > 0 &&
2406             catlen > tpl_hook.gather_max) {
2407             tpl_hook.free( (*gs)->img );
2408             tpl_hook.free( (*gs) );
2409             *gs = NULL;
2410             tpl_hook.oops("tpl exceeds max length %d\n",
2411                 tpl_hook.gather_max);
2412             return -2;              /* error, caller should stop accepting input from source*/
2413         }
2414         if ( (img = tpl_hook.realloc((*gs)->img, catlen)) == NULL) {
2415             fatal_oom();
2416         }
2417         memcpy(img + (*gs)->len, buf, len);
2418         tpl_hook.free(*gs);
2419         *gs = NULL;
2420     } else {
2421         img = buf;
2422         catlen = len;
2423     }
2424     /* isolate any full tpl(s) in img and invoke cb for each */
2425     tpl = img;
2426     keep_looping = (tpl+8 < img+catlen) ? 1 : 0;
2427     while (keep_looping) {
2428         if (strncmp("tpl", tpl, 3) != 0) {
2429             tpl_hook.oops("tpl prefix invalid\n");
2430             if (img != buf) tpl_hook.free(img);
2431             tpl_hook.free(*gs);
2432             *gs = NULL;
2433             return -3; /* error, caller should stop accepting input from source*/
2434         }
2435         memcpy(&tpllen,&tpl[4],4);
2436         if (tpl_needs_endian_swap(tpl)) tpl_byteswap(&tpllen,4);
2437         if (tpl+tpllen <= img+catlen) {
2438             cbrc = (cb)(tpl,tpllen,data);  /* invoke cb for tpl image */
2439             tpl += tpllen;               /* point to next tpl image */
2440             if (cbrc < 0) keep_looping = 0;
2441             else keep_looping = (tpl+8 < img+catlen) ? 1 : 0;
2442         } else keep_looping=0;
2443     }
2444     /* check if app callback requested closure of tpl source */
2445     if (cbrc < 0) {
2446         tpl_hook.oops("tpl_mem_gather aborted by app callback\n");
2447         if (img != buf) tpl_hook.free(img);
2448         if (*gs) tpl_hook.free(*gs);
2449         *gs = NULL;
2450         return -4;
2451     }
2452     /* store any leftover, partial tpl fragment for next read */
2453     if (tpl == img && img != buf) {
2454         /* consumed nothing from img!=buf */
2455         if ( (*gs = tpl_hook.malloc(sizeof(tpl_gather_t))) == NULL ) {
2456             fatal_oom();
2457         }
2458         (*gs)->img = tpl;
2459         (*gs)->len = catlen;
2460     } else if (tpl < img+catlen) {
2461         /* consumed 1+ tpl(s) from img!=buf or 0 from img==buf */
2462         if ( (*gs = tpl_hook.malloc(sizeof(tpl_gather_t))) == NULL ) {
2463             fatal_oom();
2464         }
2465         if ( ((*gs)->img = tpl_hook.malloc(img+catlen - tpl)) == NULL ) {
2466             fatal_oom();
2467         }
2468         (*gs)->len = img+catlen - tpl;
2469         memcpy( (*gs)->img, tpl, img+catlen - tpl);
2470         /* free partially consumed concat buffer if used */
2471         if (img != buf) tpl_hook.free(img);
2472     } else {                        /* tpl(s) fully consumed */
2473         /* free consumed concat buffer if used */
2474         if (img != buf) tpl_hook.free(img);
2475     }
2476     return 1;
2477 }