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
40 static void xo_encoder_setup (void); /* Forward decl */
41
42 /*
43 * Need a simple string collection
44 */
45 typedef 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
50 typedef TAILQ_HEAD(xo_string_list_s, xo_string_node_s) xo_string_list_t;
51
52 static inline void
xo_string_list_init(xo_string_list_t * listp)53 xo_string_list_init (xo_string_list_t *listp)
54 {
55 if (listp->tqh_last == NULL)
56 TAILQ_INIT(listp);
57 }
58
59 static inline xo_string_node_t *
xo_string_add(xo_string_list_t * listp,const char * str)60 xo_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
83 static inline void
xo_string_list_clean(xo_string_list_t * listp)84 xo_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
99 static xo_string_list_t xo_encoder_path;
100
101 void
xo_encoder_path_add(const char * path)102 xo_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
112 typedef 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
119 typedef 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
125 static xo_encoder_list_t xo_encoders;
126
127 static void
xo_encoder_list_init(xo_encoder_list_t * listp)128 xo_encoder_list_init (xo_encoder_list_t *listp)
129 {
130 if (listp->tqh_last == NULL)
131 TAILQ_INIT(listp);
132 }
133
134 static xo_encoder_node_t *
xo_encoder_list_add(const char * name)135 xo_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
157 void
xo_encoders_clean(void)158 xo_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
180 static void
xo_encoder_setup(void)181 xo_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
194 static xo_encoder_node_t *
xo_encoder_find(const char * name)195 xo_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 */
217 static xo_encoder_init_func_t
xo_encoder_func(void * dlp)218 xo_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
236 static xo_encoder_node_t *
xo_encoder_discover(const char * name)237 xo_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
288 void
xo_encoder_register(const char * name,xo_encoder_func_t func)289 xo_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
303 void
xo_encoder_unregister(const char * name)304 xo_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
315 int
xo_encoder_init(xo_handle_t * xop,const char * name)316 xo_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 */
387 xo_handle_t *
xo_encoder_create(const char * name,xo_xof_flags_t flags)388 xo_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
403 int
xo_encoder_handle(xo_handle_t * xop,xo_encoder_op_t op,const char * name,const char * value,xo_xff_flags_t flags)404 xo_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
416 const char *
xo_encoder_op_name(xo_encoder_op_t op)417 xo_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