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