2009-11-04 Marcus Brinkmann <marcus@g10code.de>
[gpgme.git] / src / debug.c
1 /* debug.c - helpful output in desperate situations
2    Copyright (C) 2000 Werner Koch (dd9jn)
3    Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2009 g10 Code GmbH
4  
5    This file is part of GPGME.
6
7    GPGME is free software; you can redistribute it and/or modify it
8    under the terms of the GNU Lesser General Public License as
9    published by the Free Software Foundation; either version 2.1 of
10    the License, or (at your option) any later version.
11    
12    GPGME is distributed in the hope that it will be useful, but
13    WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    Lesser General Public License for more details.
16    
17    You should have received a copy of the GNU Lesser General Public
18    License along with this program; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 
20    MA 02110-1301, USA.  */
21
22 #if HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <stdarg.h>
29 #include <unistd.h>
30 #include <ctype.h>
31 #include <errno.h>
32 #include <time.h>
33 #ifndef HAVE_DOSISH_SYSTEM
34 #  include <sys/types.h>
35 #  include <sys/stat.h>
36 #  include <fcntl.h>
37 #endif
38 #include <assert.h>
39
40 #include "util.h"
41 #include "ath.h"
42 #include "sema.h"
43 #include "debug.h"
44
45 \f
46 /* Lock to serialize initialization of the debug output subsystem and
47    output of actual debug messages.  */
48 DEFINE_STATIC_LOCK (debug_lock);
49
50 /* The amount of detail requested by the user, per environment
51    variable GPGME_DEBUG.  */
52 static int debug_level;
53
54 /* The output stream for the debug messages.  */
55 static FILE *errfp;
56
57 \f
58 #ifdef __GNUC__
59 #define FRAME_NR
60 static __thread int frame_nr = 0;
61 #endif
62
63 void
64 _gpgme_debug_frame_begin (void)
65 {
66 #ifdef FRAME_NR
67   frame_nr++;
68 #endif
69 }
70
71 void _gpgme_debug_frame_end (void)
72 {
73 #ifdef FRAME_NR
74   frame_nr--;
75 #endif
76 }
77
78
79 \f
80 /* Remove leading and trailing white spaces.  */
81 static char *
82 trim_spaces (char *str)
83 {
84   char *string, *p, *mark;
85
86   string = str;
87   /* Find first non space character.  */
88   for (p = string; *p && isspace (*(unsigned char *) p); p++)
89     ;
90   /* Move characters.  */
91   for (mark = NULL; (*string = *p); string++, p++)
92     if (isspace (*(unsigned char *) p))
93       {
94         if (!mark)
95           mark = string;
96       }
97     else
98       mark = NULL;
99   if (mark)
100     *mark = '\0';       /* Remove trailing spaces.  */
101
102   return str;
103 }
104
105
106 static void
107 debug_init (void)
108 {
109   static int initialized;
110
111   LOCK (debug_lock);
112   if (!initialized)
113     {
114       gpgme_error_t err;
115       char *e;
116       const char *s1, *s2;;
117
118       err = _gpgme_getenv ("GPGME_DEBUG", &e);
119       if (err)
120         {
121           UNLOCK (debug_lock);
122           return;
123         }
124
125       initialized = 1;
126       errfp = stderr;
127       if (e)
128         {
129           debug_level = atoi (e);
130           s1 = strchr (e, PATHSEP_C);
131           if (s1)
132             {
133 #ifndef HAVE_DOSISH_SYSTEM
134               if (getuid () == geteuid ())
135                 {
136 #endif
137                   char *p;
138                   FILE *fp;
139
140                   s1++;
141                   if (!(s2 = strchr (s1, PATHSEP_C)))
142                     s2 = s1 + strlen (s1);
143                   p = malloc (s2 - s1 + 1);
144                   if (p)
145                     {
146                       memcpy (p, s1, s2 - s1);
147                       p[s2-s1] = 0;
148                       trim_spaces (p);
149                       fp = fopen (p,"a");
150                       if (fp)
151                         {
152                           setvbuf (fp, NULL, _IOLBF, 0);
153                           errfp = fp;
154                         }
155                       free (p);
156                     }
157 #ifndef HAVE_DOSISH_SYSTEM
158                 }
159 #endif
160             }
161           free (e);
162         }
163     }
164   UNLOCK (debug_lock);
165
166   if (debug_level > 0)
167     _gpgme_debug (DEBUG_INIT, "gpgme_debug: level=%d\n", debug_level);
168 }
169
170
171
172 /* This should be called as soon as the locks are intialized.  It is
173    required so that the assuan logging gets conncted to the gpgme log
174    stream as early as possible.  */
175 void
176 _gpgme_debug_subsystem_init (void)
177 {
178   debug_init ();
179 }
180
181
182
183 \f
184 /* Log the formatted string FORMAT at debug level LEVEL or higher.  */
185 void
186 _gpgme_debug (int level, const char *format, ...)
187 {
188   va_list arg_ptr;
189   int saved_errno;
190
191   saved_errno = errno;
192   if (debug_level < level)
193     return;
194     
195   va_start (arg_ptr, format);
196   LOCK (debug_lock);
197   {
198     struct tm *tp;
199     time_t atime = time (NULL);
200     
201     tp = localtime (&atime);
202     fprintf (errfp, "GPGME %04d-%02d-%02d %02d:%02d:%02d <0x%04llx>  ",
203              1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday,
204              tp->tm_hour, tp->tm_min, tp->tm_sec,
205              (unsigned long long) ath_self ());
206   }
207 #ifdef FRAME_NR
208   {
209     char spaces[] = "                                        ";
210     int nr_spaces = sizeof (spaces) - 1;
211     int nr_columns;
212
213     nr_columns = 2 * (frame_nr - 1);
214     if (nr_columns > nr_spaces)
215       nr_columns = nr_spaces;
216     if (nr_columns < 0)
217       nr_columns = 0;
218     spaces[nr_columns] = '\0';
219     fprintf (errfp, "%s", spaces);
220   }
221 #endif
222
223   vfprintf (errfp, format, arg_ptr);
224   va_end (arg_ptr);
225   if(format && *format && format[strlen (format) - 1] != '\n')
226     putc ('\n', errfp);
227   UNLOCK (debug_lock);
228   fflush (errfp);
229
230   errno = saved_errno;
231 }
232
233
234 /* Start a new debug line in *LINE, logged at level LEVEL or higher,
235    and starting with the formatted string FORMAT.  */
236 void
237 _gpgme_debug_begin (void **line, int level, const char *format, ...)
238 {
239   va_list arg_ptr;
240   int res;
241
242   if (debug_level < level)
243     {
244       /* Disable logging of this line.  */
245       *line = NULL;
246       return;
247     }
248
249   va_start (arg_ptr, format);
250   res = vasprintf ((char **) line, format, arg_ptr);
251   va_end (arg_ptr);
252   if (res < 0)
253     *line = NULL;
254 }
255
256
257 /* Add the formatted string FORMAT to the debug line *LINE.  */
258 void
259 _gpgme_debug_add (void **line, const char *format, ...)
260 {
261   va_list arg_ptr;
262   char *toadd;
263   char *result;
264   int res;
265
266   if (!*line)
267     return;
268
269   va_start (arg_ptr, format);
270   res = vasprintf (&toadd, format, arg_ptr);
271   va_end (arg_ptr);
272   if (res < 0)
273     {
274       free (*line);
275       *line = NULL;
276     }
277   res = asprintf (&result, "%s%s", *(char **) line, toadd);
278   free (toadd);
279   free (*line);
280   if (res < 0)
281     *line = NULL;
282   else
283     *line = result;
284 }
285
286
287 /* Finish construction of *LINE and send it to the debug output
288    stream.  */
289 void
290 _gpgme_debug_end (void **line)
291 {
292   if (!*line)
293     return;
294
295   /* The smallest possible level is 1, so force logging here by
296      using that.  */
297   _gpgme_debug (1, "%s", *line);
298   free (*line);
299   *line = NULL;
300 }
301
302
303 #define TOHEX(val) (((val) < 10) ? ((val) + '0') : ((val) - 10 + 'a'))
304
305 void
306 _gpgme_debug_buffer (int lvl, const char *const fmt,
307                      const char *const func, const char *const buffer,
308                      size_t len)
309 {
310   int idx = 0;
311   int j;
312
313   if (!_gpgme_debug_trace ())
314     return;
315
316   while (idx < len)
317     {
318       char str[51];
319       char *strp = str;
320       char *strp2 = &str[34];
321       
322       for (j = 0; j < 16; j++)
323         {
324           unsigned char val;
325           if (idx < len)
326             {
327               val = buffer[idx++];
328               *(strp++) = TOHEX (val >> 4);
329               *(strp++) = TOHEX (val % 16);
330               *(strp2++) = isprint (val) ? val : '.';
331             }
332           else
333             {
334               *(strp++) = ' ';
335               *(strp++) = ' ';
336             }
337           if (j == 7)
338             *(strp++) = ' ';
339         }
340       *(strp++) = ' ';
341       *(strp2) = '\0';
342
343       _gpgme_debug (lvl, fmt, func, str);
344     }
345 }