88e2923041bb996a7b307da3f05e5cd6553727ba
[gpgol.git] / src / debug.cpp
1 /* debug.cpp - Debugging / Log helpers for GpgOL
2  * Copyright (C) 2018 by by Intevation GmbH
3  *
4  * This file is part of GpgOL.
5  *
6  * GpgOL is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public License
8  * as published by the Free Software Foundation; either version 2.1
9  * of the License, or (at your option) any later version.
10  *
11  * GpgOL is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include "common_indep.h"
21
22 #include <gpg-error.h>
23
24 #include <string>
25 #include <unordered_map>
26
27 /* The malloced name of the logfile and the logging stream.  If
28    LOGFILE is NULL, no logging is done. */
29 static char *logfile;
30 static FILE *logfp;
31
32 #ifdef HAVE_W32_SYSTEM
33
34 /* Acquire the mutex for logging.  Returns 0 on success. */
35 static int
36 lock_log (void)
37 {
38   int code = WaitForSingleObject (log_mutex, 10000);
39   return code != WAIT_OBJECT_0;
40 }
41
42 /* Release the mutex for logging. No error return is done because this
43    is a fatal error anyway and we have no means for proper
44    notification. */
45 static void
46 unlock_log (void)
47 {
48   ReleaseMutex (log_mutex);
49 }
50 #endif
51
52 const char *
53 get_log_file (void)
54 {
55   return logfile? logfile : "";
56 }
57
58 void
59 set_log_file (const char *name)
60 {
61 #ifdef HAVE_W32_SYSTEM
62   if (!lock_log ())
63     {
64 #endif
65       if (logfp)
66         {
67           fclose (logfp);
68           logfp = NULL;
69         }
70       free (logfile);
71       if (!name || *name == '\"' || !*name)
72         logfile = NULL;
73       else
74         logfile = strdup (name);
75
76 #ifdef HAVE_W32_SYSTEM
77       unlock_log ();
78     }
79 #endif
80 }
81
82 static void
83 do_log (const char *fmt, va_list a, int w32err, int err,
84         const void *buf, size_t buflen)
85 {
86   if (!logfile)
87     return;
88
89 #ifdef HAVE_W32_SYSTEM
90   if (!opt.enable_debug)
91     return;
92
93   if (lock_log ())
94     {
95       OutputDebugStringA ("GpgOL: Failed to log.");
96       return;
97     }
98 #endif
99
100   if (!strcmp (logfile, "stdout"))
101     {
102       logfp = stdout;
103     }
104   else if (!strcmp (logfile, "stderr"))
105     {
106       logfp = stderr;
107     }
108   if (!logfp)
109     logfp = fopen (logfile, "a+");
110 #ifdef HAVE_W32_SYSTEM
111   if (!logfp)
112     {
113       unlock_log ();
114       return;
115     }
116
117   char time_str[9];
118   SYSTEMTIME utc_time;
119   GetSystemTime (&utc_time);
120   if (GetTimeFormatA (LOCALE_INVARIANT,
121                       TIME_FORCE24HOURFORMAT | LOCALE_USE_CP_ACP,
122                       &utc_time,
123                       "HH:mm:ss",
124                       time_str,
125                       9))
126     {
127       fprintf (logfp, "%s/%lu/",
128                time_str,
129                (unsigned long)GetCurrentThreadId ());
130     }
131   else
132     {
133       fprintf (logfp, "unknown/%lu/",
134                (unsigned long)GetCurrentThreadId ());
135     }
136 #endif
137
138   if (err == 1)
139     fputs ("ERROR/", logfp);
140   vfprintf (logfp, fmt, a);
141 #ifdef HAVE_W32_SYSTEM
142   if (w32err)
143     {
144       char tmpbuf[256];
145
146       FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, w32err,
147                      MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
148                      tmpbuf, sizeof (tmpbuf)-1, NULL);
149       fputs (": ", logfp);
150       if (*tmpbuf && tmpbuf[strlen (tmpbuf)-1] == '\n')
151         tmpbuf[strlen (tmpbuf)-1] = 0;
152       if (*tmpbuf && tmpbuf[strlen (tmpbuf)-1] == '\r')
153         tmpbuf[strlen (tmpbuf)-1] = 0;
154       fprintf (logfp, "%s (%d)", tmpbuf, w32err);
155     }
156 #endif
157   if (buf)
158     {
159       const unsigned char *p = (const unsigned char*)buf;
160
161       for ( ; buflen; buflen--, p++)
162         fprintf (logfp, "%02X", *p);
163       putc ('\n', logfp);
164     }
165   else if ( *fmt && fmt[strlen (fmt) - 1] != '\n')
166     putc ('\n', logfp);
167
168   fflush (logfp);
169 #ifdef HAVE_W32_SYSTEM
170   unlock_log ();
171 #endif
172 }
173
174 const char *
175 log_srcname (const char *file)
176 {
177   const char *s = strrchr (file, '/');
178   return s? s+1:file;
179 }
180
181 void
182 log_debug (const char *fmt, ...)
183 {
184   va_list a;
185
186   va_start (a, fmt);
187   do_log (fmt, a, 0, 0, NULL, 0);
188   va_end (a);
189 }
190
191 void
192 log_error (const char *fmt, ...)
193 {
194   va_list a;
195
196   va_start (a, fmt);
197   do_log (fmt, a, 0, 1, NULL, 0);
198   va_end (a);
199 }
200
201 void
202 log_vdebug (const char *fmt, va_list a)
203 {
204   do_log (fmt, a, 0, 0, NULL, 0);
205 }
206
207 void
208 log_hexdump (const void *buf, size_t buflen, const char *fmt, ...)
209 {
210   va_list a;
211
212   va_start (a, fmt);
213   do_log (fmt, a, 0, 2, buf, buflen);
214   va_end (a);
215 }
216
217 #ifdef HAVE_W32_SYSTEM
218 void
219 log_debug_w32 (int w32err, const char *fmt, ...)
220 {
221   va_list a;
222
223   if (w32err == -1)
224     w32err = GetLastError ();
225
226   va_start (a, fmt);
227   do_log (fmt, a, w32err, 0, NULL, 0);
228   va_end (a);
229 }
230
231 void
232 log_error_w32 (int w32err, const char *fmt, ...)
233 {
234   va_list a;
235
236   if (w32err == -1)
237     w32err = GetLastError ();
238
239   va_start (a, fmt);
240   do_log (fmt, a, w32err, 1, NULL, 0);
241   va_end (a);
242 }
243
244 static void
245 do_log_window_info (HWND window, int level)
246 {
247   char buf[1024+1];
248   char name[200];
249   int nname;
250   char *pname;
251   DWORD pid;
252
253   if (!window)
254     return;
255
256   GetWindowThreadProcessId (window, &pid);
257   if (pid != GetCurrentProcessId ())
258     return;
259
260   memset (buf, 0, sizeof (buf));
261   GetWindowText (window, buf, sizeof (buf)-1);
262   nname = GetClassName (window, name, sizeof (name)-1);
263   if (nname)
264     pname = name;
265   else
266     pname = NULL;
267
268   if (level == -1)
269     log_debug ("  parent=%p/%lu (%s) `%s'", window, (unsigned long)pid,
270                pname? pname:"", buf);
271   else
272     log_debug ("    %*shwnd=%p/%lu (%s) `%s'", level*2, "", window,
273                (unsigned long)pid, pname? pname:"", buf);
274 }
275
276
277 /* Helper to log_window_hierarchy.  */
278 static HWND
279 do_log_window_hierarchy (HWND parent, int level)
280 {
281   HWND child;
282
283   child = GetWindow (parent, GW_CHILD);
284   while (child)
285     {
286       do_log_window_info (child, level);
287       do_log_window_hierarchy (child, level+1);
288       child = GetNextWindow (child, GW_HWNDNEXT);
289     }
290
291   return NULL;
292 }
293
294
295 /* Print a debug message using the format string FMT followed by the
296    window hierarchy of WINDOW.  */
297 void
298 log_window_hierarchy (HWND window, const char *fmt, ...)
299 {
300   va_list a;
301
302   va_start (a, fmt);
303   do_log (fmt, a, 0, 0, NULL, 0);
304   va_end (a);
305   if (window)
306     {
307       do_log_window_info (window, -1);
308       do_log_window_hierarchy (window, 0);
309     }
310 }
311 #endif
312
313 GPGRT_LOCK_DEFINE (anon_str_lock);
314
315 /* Weel ok this survives unload but we don't want races
316    and it makes a bit of sense to keep the strings constant. */
317 static std::unordered_map<std::string, std::string> str_map;
318
319 const char *anonstr (const char *data)
320 {
321   static int64_t cnt;
322   if (opt.enable_debug & DBG_DATA)
323     {
324       return data;
325     }
326   if (!data)
327     {
328       return "gpgol_str_null";
329     }
330   if (!strlen (data))
331     {
332       return "gpgol_str_empty";
333     }
334
335   gpgol_lock (&anon_str_lock);
336   const std::string strData (data);
337   auto it = str_map.find (strData);
338
339   if (it == str_map.end ())
340     {
341       const auto anon = std::string ("gpgol_string_") + std::to_string (++cnt);
342       str_map.insert (std::make_pair (strData, anon));
343       it = str_map.find (strData);
344     }
345
346   // As the data is saved in our map we can return
347   // the c_str as it won't be touched as const.
348
349   gpgol_unlock (&anon_str_lock);
350
351   return it->second.c_str();
352 }