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