1 /*
2 btrace.c
3
4 Copyright (c) J.J. Green 2014
5 */
6
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10
11 #include <stdarg.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <time.h>
15
16 #ifdef HAVE_JANSSON_H
17 #include <jansson.h>
18 #endif
19
20 #include <libxml/encoding.h>
21 #include <libxml/xmlwriter.h>
22
23 #include "btrace.h"
24
25 #define BUFSZ 512
26
27 typedef struct btrace_line_t btrace_line_t;
28
29 struct btrace_line_t
30 {
31 int line;
32 const char *file;
33 char *message;
34 btrace_line_t *next;
35 };
36
37 typedef struct
38 {
39 const char *program;
40 btrace_line_t *lines;
41 } btrace_t;
42
43 static btrace_t btrace_global =
44 {
45 .program = NULL,
46 .lines = NULL
47 };
48
date_string(void)49 static const char* date_string(void)
50 {
51 time_t t = time(NULL);
52 struct tm* bdt = gmtime(&t);
53 static char buffer[32];
54
55 if (strftime(buffer, 32, "%FT%T", bdt) >= 32)
56 fprintf(stderr, "overflow formatting date\n");
57
58 return buffer;
59 }
60
61 /* string to format */
62
btrace_format(const char * name)63 extern int btrace_format(const char* name)
64 {
65 if (name == NULL)
66 return BTRACE_NONE;
67 else if (strcmp(name, "plain") == 0)
68 return BTRACE_PLAIN;
69 else if (strcmp(name, "xml") == 0)
70 return BTRACE_XML;
71 else if (strcmp(name, "json") == 0)
72 return BTRACE_JSON;
73
74 return BTRACE_ERROR;
75 }
76
77 /* enable/disable */
78
enable(const char * program,btrace_t * bt)79 static void enable(const char *program, btrace_t *bt)
80 {
81 bt->program = program;
82 }
83
btrace_enable(const char * program)84 extern void btrace_enable(const char *program)
85 {
86 enable(program, &btrace_global);
87 }
88
disable(btrace_t * bt)89 static void disable(btrace_t *bt)
90 {
91 bt->program = NULL;
92 }
93
btrace_disable(void)94 extern void btrace_disable(void)
95 {
96 disable(&btrace_global);
97 }
98
is_enabled(btrace_t * bt)99 static bool is_enabled(btrace_t *bt)
100 {
101 return bt->program != NULL;
102 }
103
btrace_is_enabled(void)104 extern bool btrace_is_enabled(void)
105 {
106 return is_enabled(&btrace_global);
107 }
108
109 /* testing nonempty */
110
is_empty(btrace_t * bt)111 static bool is_empty(btrace_t* bt)
112 {
113 return bt->lines == NULL;
114 }
115
btrace_is_empty(void)116 extern bool btrace_is_empty(void)
117 {
118 return is_empty(&btrace_global);
119 }
120
121 /* free lines */
122
line_free(btrace_line_t * btl)123 static void line_free(btrace_line_t *btl)
124 {
125 free(btl->message);
126 free(btl);
127 }
128
lines_free(btrace_line_t * btl)129 static void lines_free(btrace_line_t *btl)
130 {
131 if (btl)
132 {
133 lines_free(btl->next);
134 line_free(btl);
135 }
136 }
137
138 /* reset the btrace */
139
reset(btrace_t * bt)140 static void reset(btrace_t *bt)
141 {
142 lines_free(bt->lines);
143 bt->lines = NULL;
144 }
145
btrace_reset(void)146 extern void btrace_reset(void)
147 {
148 reset(&btrace_global);
149 }
150
151 /* count lines */
152
count_lines(btrace_line_t * btl)153 static size_t count_lines(btrace_line_t *btl)
154 {
155 return btl == NULL ? 0 : count_lines(btl->next) + 1;
156 }
157
count(btrace_t * bt)158 static size_t count(btrace_t *bt)
159 {
160 return count_lines(bt->lines);
161 }
162
btrace_count(void)163 extern size_t btrace_count(void)
164 {
165 return count(&(btrace_global));
166 }
167
168 /* adding */
169
line_new(const char * file,int line,char * message)170 static btrace_line_t* line_new(const char* file, int line, char *message)
171 {
172 btrace_line_t *btl;
173
174 if ((btl = malloc(sizeof(btrace_line_t))) != NULL)
175 {
176 if ((btl->message = strdup(message)) != NULL)
177 {
178 btl->file = file;
179 btl->line = line;
180
181 return btl;
182 }
183 free(btl);
184 }
185
186 return NULL;
187 }
188
append(btrace_t * bt,const char * file,int line,char * message)189 static void append(btrace_t *bt, const char* file, int line, char *message)
190 {
191 btrace_line_t *btl;
192
193 if ((btl = line_new(file, line, message)) == NULL)
194 return;
195
196 btl->next = bt->lines;
197 bt->lines = btl;
198 }
199
btrace_add(const char * file,int line,const char * format,...)200 extern void btrace_add(const char* file, int line, const char* format, ...)
201 {
202 char buffer[512];
203 va_list args;
204
205 va_start(args, format);
206 vsnprintf(buffer, BUFSZ, format, args);
207 va_end(args);
208
209 append(&btrace_global, file, line, buffer);
210 }
211
212 /* print plain format */
213
line_print_plain(FILE * stream,btrace_line_t * btl)214 static int line_print_plain(FILE *stream, btrace_line_t *btl)
215 {
216 fprintf(stream, "%s (%s, %i)\n", btl->message, btl->file, btl->line);
217
218 return 0;
219 }
220
lines_print_plain(FILE * stream,btrace_line_t * btl)221 static int lines_print_plain(FILE *stream, btrace_line_t *btl)
222 {
223 if (btl)
224 {
225 return
226 lines_print_plain(stream, btl->next) +
227 line_print_plain(stream, btl);
228 }
229
230 return 0;
231 }
232
print_plain(FILE * stream,btrace_t * bt)233 static int print_plain(FILE *stream, btrace_t *bt)
234 {
235 return lines_print_plain(stream, bt->lines);
236 }
237
238 /* print XML format */
239
line_print_xml(xmlTextWriter * writer,btrace_line_t * btl)240 static int line_print_xml(xmlTextWriter* writer, btrace_line_t *btl)
241 {
242 if (xmlTextWriterStartElement(writer, BAD_CAST "message") < 0)
243 {
244 fprintf(stderr, "error from open message\n");
245 return 1;
246 }
247
248 if (xmlTextWriterWriteAttribute(writer,
249 BAD_CAST "file",
250 BAD_CAST btl->file) < 0)
251 {
252 fprintf(stderr, "error setting file attribute\n");
253 return 1;
254 }
255
256 char linestring[32];
257
258 if (snprintf(linestring, 32, "%d", btl->line) >= 32)
259 {
260 fprintf(stderr, "buffer overflow formatting line number\n");
261 return 1;
262 }
263
264 if (xmlTextWriterWriteAttribute(writer,
265 BAD_CAST "line",
266 BAD_CAST linestring) < 0)
267 {
268 fprintf(stderr, "error setting file attribute\n");
269 return 1;
270 }
271
272 if (xmlTextWriterWriteString(writer, BAD_CAST btl->message) < 0)
273 {
274 fprintf(stderr, "error writing message body\n");
275 return 1;
276 }
277
278 if (xmlTextWriterEndElement(writer) < 0)
279 {
280 fprintf(stderr, "error from close message\n");
281 return 1;
282 }
283
284 return 0;
285 }
286
lines_print_xml(xmlTextWriter * writer,btrace_line_t * btl)287 static int lines_print_xml(xmlTextWriter* writer, btrace_line_t *btl)
288 {
289 if (btl)
290 {
291 return
292 lines_print_xml(writer, btl->next) +
293 line_print_xml(writer, btl);
294 }
295
296 return 0;
297 }
298
print_xml_doc(xmlTextWriter * writer,btrace_t * bt)299 static int print_xml_doc(xmlTextWriter* writer, btrace_t *bt)
300 {
301 if (xmlTextWriterStartDocument(writer, NULL, "UTF-8", NULL) < 0)
302 {
303 fprintf(stderr, "error from start document\n");
304 return 1;
305 }
306
307 if (xmlTextWriterStartElement(writer, BAD_CAST "backtrace") < 0)
308 {
309 fprintf(stderr, "error from open backtrace\n");
310 return 1;
311 }
312
313 if (xmlTextWriterWriteAttribute(writer,
314 BAD_CAST "program",
315 BAD_CAST bt->program) < 0)
316 {
317 fprintf(stderr, "error setting program attribute\n");
318 return 1;
319 }
320
321 if (xmlTextWriterWriteAttribute(writer,
322 BAD_CAST "version",
323 BAD_CAST VERSION) < 0)
324 {
325 fprintf(stderr, "error setting file attribute\n");
326 return 1;
327 }
328
329 if (xmlTextWriterWriteAttribute(writer,
330 BAD_CAST "created",
331 BAD_CAST date_string()) < 0)
332 {
333 fprintf(stderr, "error setting created attribute\n");
334 return 1;
335 }
336
337 if (lines_print_xml(writer, bt->lines) != 0)
338 {
339 fprintf(stderr, "error writing lines\n");
340 return 1;
341 }
342
343 if (xmlTextWriterEndElement(writer) < 0)
344 {
345 fprintf(stderr, "error from close backtrace\n");
346 return 1;
347 }
348
349 if (xmlTextWriterEndDocument(writer) < 0)
350 {
351 fprintf(stderr, "error from end document\n");
352 return 1;
353 }
354
355 return 0;
356 }
357
print_xml(FILE * stream,btrace_t * bt)358 static int print_xml(FILE *stream, btrace_t *bt)
359 {
360 if (is_empty(bt))
361 return 0;
362
363 xmlBuffer* buffer;
364 int err = 0;
365
366 if ((buffer = xmlBufferCreate()) != NULL)
367 {
368 xmlTextWriter* writer;
369
370 if ((writer = xmlNewTextWriterMemory(buffer, 0)) != NULL)
371 {
372 if (print_xml_doc(writer, bt) == 0)
373 {
374 fprintf(stream, "%s", buffer->content);
375 }
376 else
377 {
378 fprintf(stderr, "failed to print XML\n");
379 err++;
380 }
381 xmlFreeTextWriter(writer);
382 }
383 else
384 {
385 fprintf(stderr, "failed to create XML writer\n");
386 err++;
387 }
388 xmlBufferFree(buffer);
389 }
390 else
391 {
392 fprintf(stderr, "failed to create XML buffer\n");
393 err++;
394 }
395
396 return err;
397 }
398
399 #ifdef HAVE_JANSSON_H
400
401 /*
402 print JSON format
403 */
404
line_print_json(json_t * msgs,btrace_line_t * btl)405 static int line_print_json(json_t *msgs, btrace_line_t *btl)
406 {
407 json_t *msg = json_object();
408
409 json_object_set_new(msg, "file", json_string(btl->file));
410 json_object_set_new(msg, "line", json_integer(btl->line));
411 json_object_set_new(msg, "message", json_string(btl->message));
412
413 json_array_append(msgs, msg);
414
415 return 0;
416 }
417
lines_print_json(json_t * msgs,btrace_line_t * btl)418 static int lines_print_json(json_t* msgs, btrace_line_t *btl)
419 {
420 if (btl)
421 {
422 return
423 lines_print_json(msgs, btl->next) +
424 line_print_json(msgs, btl);
425 }
426
427 return 0;
428 }
429
print_json(FILE * stream,btrace_t * bt)430 static int print_json(FILE *stream, btrace_t *bt)
431 {
432 int err = 0;
433
434 if (is_empty(bt))
435 return 0;
436
437 json_t *messages;
438
439 if ((messages = json_array()) != NULL)
440 {
441 if (lines_print_json(messages, bt->lines) == 0)
442 {
443 json_t *root;
444
445 if ((root = json_object()) != NULL)
446 {
447 if (
448 (json_object_set_new(root, "program", json_string(bt->program)) == 0) &&
449 (json_object_set_new(root, "version", json_string(VERSION)) == 0) &&
450 (json_object_set_new(root, "created", json_string(date_string())) == 0) &&
451 (json_object_set(root, "messages", messages) == 0)
452 )
453 {
454 if (json_dumpf(root, stream, JSON_INDENT(2)) == 0)
455 {
456 /* success */
457 }
458 else
459 {
460 fprintf(stderr, "failed to dump to stream\n");
461 err++;
462 }
463 }
464 else
465 {
466 fprintf(stderr, "error creating JSON message object\n");
467 err++;
468 }
469
470 json_decref(root);
471 }
472 else
473 {
474 fprintf(stderr, "failed to create JSON root object\n");
475 err++;
476 }
477 }
478 else
479 {
480 fprintf(stderr, "failed to convert lines to JSON\n");
481 err++;
482 }
483 json_decref(messages);
484 }
485 else
486 {
487 fprintf(stderr, "failed create JSOM message array\n");
488 err++;
489 }
490
491 return err;
492 }
493
494 #else
495
print_json(FILE * stream,btrace_t * bt)496 static int print_json(FILE *stream, btrace_t *bt)
497 {
498 fprintf(stderr, "compiled without jansson library support\n");
499 return 1;
500 }
501
502 #endif
503
504 typedef int (*printer_t)(FILE*, btrace_t*);
505
btrace_print_stream(FILE * stream,int type)506 extern int btrace_print_stream(FILE* stream, int type)
507 {
508 printer_t printer = NULL;
509
510 switch (type)
511 {
512 case BTRACE_PLAIN:
513 printer = print_plain;
514 break;
515 case BTRACE_XML:
516 printer = print_xml;
517 break;
518 case BTRACE_JSON:
519 printer = print_json;
520 break;
521 default:
522 fprintf(stderr, "no such trace format\n");
523 return 1;
524 }
525
526 return printer(stream, &btrace_global);
527 }
528
btrace_print(const char * path,int type)529 extern int btrace_print(const char* path, int type)
530 {
531 if (! btrace_is_empty() )
532 {
533 FILE* stream;
534
535 if ((stream = fopen(path, "w")) == NULL)
536 return 1;
537
538 btrace_print_stream(stream, type);
539
540 if (fclose(stream) != 0)
541 return 1;
542 }
543
544 return 0;
545 }
546