1/*
2 * Copyright (c) 2015, Juniper Networks, Inc.
3 * All rights reserved.
4 * This SOFTWARE is licensed under the LICENSE provided in the
5 * ../Copyright file. By downloading, installing, copying, or otherwise
6 * using the SOFTWARE, you agree to be bound by the terms of that
7 * LICENSE.
8 * Phil Shafer, August 2015
9 */
10
11/**
12 * libxo includes a number of fixed encoding styles.  But other
13 * external encoders are need to deal with new encoders.  Rather
14 * than expose a swarm of libxo internals, we create a distinct
15 * API, with a simpler API than we use internally.
16 */
17
18#include <stdio.h>
19#include <unistd.h>
20#include <string.h>
21#include <sys/queue.h>
22#include <sys/param.h>
23#include <dlfcn.h>
24
25#include "xo_config.h"
26#include "xo.h"
27#include "xo_encoder.h"
28
29#ifdef HAVE_DLFCN_H
30#include <dlfcn.h>
31#if !defined(HAVE_DLFUNC)
32#define dlfunc(_p, _n)		dlsym(_p, _n)
33#endif
34#else /* HAVE_DLFCN_H */
35#define dlopen(_n, _f)		NULL /* Fail */
36#define dlsym(_p, _n)		NULL /* Fail */
37#define dlfunc(_p, _n)		NULL /* Fail */
38#endif /* HAVE_DLFCN_H */
39
40static void xo_encoder_setup (void); /* Forward decl */
41
42/*
43 * Need a simple string collection
44 */
45typedef struct xo_string_node_s {
46    TAILQ_ENTRY(xo_string_node_s) xs_link; /* Next string */
47    char xs_data[0];		      /* String data */
48} xo_string_node_t;
49
50typedef TAILQ_HEAD(xo_string_list_s, xo_string_node_s) xo_string_list_t;
51
52static inline void
53xo_string_list_init (xo_string_list_t *listp)
54{
55    if (listp->tqh_last == NULL)
56	TAILQ_INIT(listp);
57}
58
59static inline xo_string_node_t *
60xo_string_add (xo_string_list_t *listp, const char *str)
61{
62    if (listp == NULL || str == NULL)
63	return NULL;
64
65    xo_string_list_init(listp);
66    size_t len = strlen(str);
67    xo_string_node_t *xsp;
68
69    xsp = xo_realloc(NULL, sizeof(*xsp) + len + 1);
70    if (xsp) {
71	memcpy(xsp->xs_data, str, len);
72	xsp->xs_data[len] = '\0';
73	TAILQ_INSERT_TAIL(listp, xsp, xs_link);
74    }
75
76    return xsp;
77}
78
79#define XO_STRING_LIST_FOREACH(_xsp, _listp) \
80    xo_string_list_init(_listp); \
81    TAILQ_FOREACH(_xsp, _listp, xs_link)
82
83static inline void
84xo_string_list_clean (xo_string_list_t *listp)
85{
86    xo_string_node_t *xsp;
87
88    xo_string_list_init(listp);
89
90    for (;;) {
91	xsp = TAILQ_FIRST(listp);
92        if (xsp == NULL)
93            break;
94        TAILQ_REMOVE(listp, xsp, xs_link);
95	xo_free(xsp);
96    }
97}
98
99static xo_string_list_t xo_encoder_path;
100
101void
102xo_encoder_path_add (const char *path)
103{
104    xo_encoder_setup();
105
106    if (path)
107	xo_string_add(&xo_encoder_path, path);
108}
109
110/* ---------------------------------------------------------------------- */
111
112typedef struct xo_encoder_node_s {
113    TAILQ_ENTRY(xo_encoder_node_s) xe_link; /* Next session */
114    char *xe_name;			/* Name for this encoder */
115    xo_encoder_func_t xe_handler;	/* Callback function */
116    void *xe_dlhandle;			/* dlopen handle */
117} xo_encoder_node_t;
118
119typedef TAILQ_HEAD(xo_encoder_list_s, xo_encoder_node_s) xo_encoder_list_t;
120
121#define XO_ENCODER_LIST_FOREACH(_xep, _listp) \
122    xo_encoder_list_init(_listp); \
123    TAILQ_FOREACH(_xep, _listp, xe_link)
124
125static xo_encoder_list_t xo_encoders;
126
127static void
128xo_encoder_list_init (xo_encoder_list_t *listp)
129{
130    if (listp->tqh_last == NULL)
131	TAILQ_INIT(listp);
132}
133
134static xo_encoder_node_t *
135xo_encoder_list_add (const char *name)
136{
137    if (name == NULL)
138	return NULL;
139
140    xo_encoder_node_t *xep = xo_realloc(NULL, sizeof(*xep));
141    if (xep) {
142	ssize_t len = strlen(name) + 1;
143	xep->xe_name = xo_realloc(NULL, len);
144	if (xep->xe_name == NULL) {
145	    xo_free(xep);
146	    return NULL;
147	}
148
149	memcpy(xep->xe_name, name, len);
150
151	TAILQ_INSERT_TAIL(&xo_encoders, xep, xe_link);
152    }
153
154    return xep;
155}
156
157void
158xo_encoders_clean (void)
159{
160    xo_encoder_node_t *xep;
161
162    xo_encoder_setup();
163
164    for (;;) {
165	xep = TAILQ_FIRST(&xo_encoders);
166        if (xep == NULL)
167            break;
168
169        TAILQ_REMOVE(&xo_encoders, xep, xe_link);
170
171	if (xep->xe_dlhandle)
172	    dlclose(xep->xe_dlhandle);
173
174	xo_free(xep);
175    }
176
177    xo_string_list_clean(&xo_encoder_path);
178}
179
180static void
181xo_encoder_setup (void)
182{
183    static int initted;
184    if (!initted) {
185	initted = 1;
186
187	xo_string_list_init(&xo_encoder_path);
188	xo_encoder_list_init(&xo_encoders);
189
190	xo_encoder_path_add(XO_ENCODERDIR);
191    }
192}
193
194static xo_encoder_node_t *
195xo_encoder_find (const char *name)
196{
197    xo_encoder_node_t *xep;
198
199    xo_encoder_list_init(&xo_encoders);
200
201    XO_ENCODER_LIST_FOREACH(xep, &xo_encoders) {
202	if (xo_streq(xep->xe_name, name))
203	    return xep;
204    }
205
206    return NULL;
207}
208
209/*
210 * Return the encoder function for a specific shared library.  This is
211 * really just a means of keeping the annoying gcc verbiage out of the
212 * main code.  And that's only need because gcc breaks dlfunc's
213 * promise that I can cast it's return value to a function: "The
214 * precise return type of dlfunc() is unspecified; applications must
215 * cast it to an appropriate function pointer type."
216 */
217static xo_encoder_init_func_t
218xo_encoder_func (void *dlp)
219{
220    xo_encoder_init_func_t func;
221
222#if defined(HAVE_GCC) && __GNUC__ > 8
223#pragma GCC diagnostic push
224#pragma GCC diagnostic ignored "-Wcast-function-type"
225#endif /* HAVE_GCC */
226
227    func = (xo_encoder_init_func_t) dlfunc(dlp, XO_ENCODER_INIT_NAME);
228
229#if defined(HAVE_GCC) && __GNUC__ > 8
230#pragma GCC diagnostic pop	/* Restore previous setting */
231#endif /* HAVE_GCC */
232
233    return func;
234}
235
236static xo_encoder_node_t *
237xo_encoder_discover (const char *name)
238{
239    void *dlp = NULL;
240    char buf[MAXPATHLEN];
241    xo_string_node_t *xsp;
242    xo_encoder_node_t *xep = NULL;
243
244    XO_STRING_LIST_FOREACH(xsp, &xo_encoder_path) {
245	static const char fmt[] = "%s/%s.enc";
246	char *dir = xsp->xs_data;
247	size_t len = snprintf(buf, sizeof(buf), fmt, dir, name);
248
249	if (len > sizeof(buf))	/* Should not occur */
250	    continue;
251
252	dlp = dlopen((const char *) buf, RTLD_NOW);
253	if (dlp)
254	    break;
255    }
256
257    if (dlp) {
258	/*
259	 * If the library exists, find the initializer function and
260	 * call it.
261	 */
262	xo_encoder_init_func_t func;
263
264	func = xo_encoder_func(dlp);
265	if (func) {
266	    xo_encoder_init_args_t xei;
267
268	    bzero(&xei, sizeof(xei));
269
270	    xei.xei_version = XO_ENCODER_VERSION;
271	    ssize_t rc = func(&xei);
272	    if (rc == 0 && xei.xei_handler) {
273		xep = xo_encoder_list_add(name);
274		if (xep) {
275		    xep->xe_handler = xei.xei_handler;
276		    xep->xe_dlhandle = dlp;
277		}
278	    }
279	}
280
281	if (xep == NULL)
282	    dlclose(dlp);
283    }
284
285    return xep;
286}
287
288void
289xo_encoder_register (const char *name, xo_encoder_func_t func)
290{
291    xo_encoder_setup();
292
293    xo_encoder_node_t *xep = xo_encoder_find(name);
294
295    if (xep)			/* "We alla-ready got one" */
296	return;
297
298    xep = xo_encoder_list_add(name);
299    if (xep)
300	xep->xe_handler = func;
301}
302
303void
304xo_encoder_unregister (const char *name)
305{
306    xo_encoder_setup();
307
308    xo_encoder_node_t *xep = xo_encoder_find(name);
309    if (xep) {
310	TAILQ_REMOVE(&xo_encoders, xep, xe_link);
311	xo_free(xep);
312    }
313}
314
315int
316xo_encoder_init (xo_handle_t *xop, const char *name)
317{
318    xo_encoder_setup();
319
320    char opts_char = '\0';
321    const char *col_opts = strchr(name, ':');
322    const char *plus_opts = strchr(name, '+');
323
324    /*
325     * Find the option-separating character (plus or colon) which
326     * appears first in the options string.
327     */
328    const char *opts = (col_opts == NULL) ? plus_opts
329	: (plus_opts == NULL) ? col_opts
330	: (plus_opts < col_opts) ? plus_opts : col_opts;
331
332    if (opts) {
333	opts_char = *opts;
334
335	/* Make a writable copy of the name */
336	size_t len = strlen(name);
337	char *copy = alloca(len + 1);
338	memcpy(copy, name, len);
339	copy[len] = '\0';
340
341	char *opts_copy = copy + (opts - name); /* Move to ':' */
342	*opts_copy++ = '\0';			/* Trim it off */
343
344	opts = opts_copy;	/* Use copy as options */
345	name = copy;		/* Use trimmed copy as name */
346    }
347
348    /* Can't have names containing '/' or ':' */
349    if (strchr(name, '/') != NULL || strchr(name, ':') != NULL) {
350	xo_failure(xop, "invalid encoder name: %s", name);
351	return -1;
352    }
353
354   /*
355     * First we look on the list of known (registered) encoders.
356     * If we don't find it, we follow the set of paths to find
357     * the encoding library.
358     */
359    xo_encoder_node_t *xep = xo_encoder_find(name);
360    if (xep == NULL) {
361	xep = xo_encoder_discover(name);
362	if (xep == NULL) {
363	    xo_failure(xop, "encoder not founde: %s", name);
364	    return -1;
365	}
366    }
367
368    xo_set_encoder(xop, xep->xe_handler);
369
370    int rc = xo_encoder_handle(xop, XO_OP_CREATE, name, NULL, 0);
371    if (rc == 0 && opts != NULL) {
372	xo_encoder_op_t op;
373
374	/* Encoder API is limited, so we're stuck with two different options */
375	op = (opts_char == '+') ? XO_OP_OPTIONS_PLUS : XO_OP_OPTIONS;
376	rc = xo_encoder_handle(xop, op, name, opts, 0);
377    }
378
379    return rc;
380}
381
382/*
383 * A couple of function varieties here, to allow for multiple
384 * use cases.  This variant is for when the main program knows
385 * its own encoder needs.
386 */
387xo_handle_t *
388xo_encoder_create (const char *name, xo_xof_flags_t flags)
389{
390    xo_handle_t *xop;
391
392    xop = xo_create(XO_STYLE_ENCODER, flags);
393    if (xop) {
394	if (xo_encoder_init(xop, name)) {
395	    xo_destroy(xop);
396	    xop = NULL;
397	}
398    }
399
400    return xop;
401}
402
403int
404xo_encoder_handle (xo_handle_t *xop, xo_encoder_op_t op,
405		   const char *name, const char *value, xo_xff_flags_t flags)
406{
407    void *private = xo_get_private(xop);
408    xo_encoder_func_t func = xo_get_encoder(xop);
409
410    if (func == NULL)
411	return -1;
412
413    return func(xop, op, name, value, private, flags);
414}
415
416const char *
417xo_encoder_op_name (xo_encoder_op_t op)
418{
419    static const char *names[] = {
420	/*  0 */ "unknown",
421	/*  1 */ "create",
422	/*  2 */ "open_container",
423	/*  3 */ "close_container",
424	/*  4 */ "open_list",
425	/*  5 */ "close_list",
426	/*  6 */ "open_leaf_list",
427	/*  7 */ "close_leaf_list",
428	/*  8 */ "open_instance",
429	/*  9 */ "close_instance",
430	/* 10 */ "string",
431	/* 11 */ "content",
432	/* 12 */ "finish",
433	/* 13 */ "flush",
434	/* 14 */ "destroy",
435	/* 15 */ "attr",
436	/* 16 */ "version",
437	/* 17 */ "options",
438    };
439
440    if (op > sizeof(names) / sizeof(names[0]))
441	return "unknown";
442
443    return names[op];
444}
445