xref: /freebsd/contrib/libxo/libxo/xo_encoder.c (revision 2a58b312)
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
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 *
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
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
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
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 *
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
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
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 *
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
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 *
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
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
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
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 *
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
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 *
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