Enable building tests for windows
[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 GPGRT_LOCK_DEFINE (log_lock);
33
34 /* Acquire the mutex for logging.  Returns 0 on success. */
35 static int
36 lock_log (void)
37 {
38   gpgrt_lock_lock (&log_lock);
39   return 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   gpgrt_lock_unlock (&log_lock);
49 }
50
51 const char *
52 get_log_file (void)
53 {
54   return logfile? logfile : "";
55 }
56
57 void
58 set_log_file (const char *name)
59 {
60 #ifdef HAVE_W32_SYSTEM
61   if (!lock_log ())
62     {
63 #endif
64       if (logfp)
65         {
66           fclose (logfp);
67           logfp = NULL;
68         }
69       free (logfile);
70       if (!name || *name == '\"' || !*name)
71         logfile = NULL;
72       else
73         logfile = strdup (name);
74
75 #ifdef HAVE_W32_SYSTEM
76       unlock_log ();
77     }
78 #endif
79 }
80
81 static void
82 do_log (const char *fmt, va_list a, int w32err, int err,
83         const void *buf, size_t buflen)
84 {
85   if (!logfile)
86     return;
87
88 #ifdef HAVE_W32_SYSTEM
89   if (!opt.enable_debug)
90     return;
91
92   if (lock_log ())
93     {
94       OutputDebugStringA ("GpgOL: Failed to log.");
95       return;
96     }
97 #endif
98
99   if (!strcmp (logfile, "stdout"))
100     {
101       logfp = stdout;
102     }
103   else if (!strcmp (logfile, "stderr"))
104     {
105       logfp = stderr;
106     }
107   if (!logfp)
108     logfp = fopen (logfile, "a+");
109 #ifdef HAVE_W32_SYSTEM
110   if (!logfp)
111     {
112       unlock_log ();
113       return;
114     }
115
116   char time_str[9];
117   SYSTEMTIME utc_time;
118   GetSystemTime (&utc_time);
119   if (GetTimeFormatA (LOCALE_INVARIANT,
120                       TIME_FORCE24HOURFORMAT | LOCALE_USE_CP_ACP,
121                       &utc_time,
122                       "HH:mm:ss",
123                       time_str,
124                       9))
125     {
126       fprintf (logfp, "%s/%lu/",
127                time_str,
128                (unsigned long)GetCurrentThreadId ());
129     }
130   else
131     {
132       fprintf (logfp, "unknown/%lu/",
133                (unsigned long)GetCurrentThreadId ());
134     }
135 #endif
136
137   if (err == 1)
138     fputs ("ERROR/", logfp);
139   vfprintf (logfp, fmt, a);
140 #ifdef HAVE_W32_SYSTEM
141   if (w32err)
142     {
143       char tmpbuf[256];
144
145       FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, w32err,
146                      MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
147                      tmpbuf, sizeof (tmpbuf)-1, NULL);
148       fputs (": ", logfp);
149       if (*tmpbuf && tmpbuf[strlen (tmpbuf)-1] == '\n')
150         tmpbuf[strlen (tmpbuf)-1] = 0;
151       if (*tmpbuf && tmpbuf[strlen (tmpbuf)-1] == '\r')
152         tmpbuf[strlen (tmpbuf)-1] = 0;
153       fprintf (logfp, "%s (%d)", tmpbuf, w32err);
154     }
155 #endif
156   if (buf)
157     {
158       const unsigned char *p = (const unsigned char*)buf;
159
160       for ( ; buflen; buflen--, p++)
161         fprintf (logfp, "%02X", *p);
162       putc ('\n', logfp);
163     }
164   else if ( *fmt && fmt[strlen (fmt) - 1] != '\n')
165     putc ('\n', logfp);
166
167   fflush (logfp);
168 #ifdef HAVE_W32_SYSTEM
169   unlock_log ();
170 #endif
171 }
172
173 const char *
174 log_srcname (const char *file)
175 {
176   const char *s = strrchr (file, '/');
177   return s? s+1:file;
178 }
179
180 void
181 log_debug (const char *fmt, ...)
182 {
183   va_list a;
184
185   va_start (a, fmt);
186   do_log (fmt, a, 0, 0, NULL, 0);
187   va_end (a);
188 }
189
190 void
191 log_error (const char *fmt, ...)
192 {
193   va_list a;
194
195   va_start (a, fmt);
196   do_log (fmt, a, 0, 1, NULL, 0);
197   va_end (a);
198 }
199
200 void
201 log_vdebug (const char *fmt, va_list a)
202 {
203   do_log (fmt, a, 0, 0, NULL, 0);
204 }
205
206 void
207 log_hexdump (const void *buf, size_t buflen, const char *fmt, ...)
208 {
209   va_list a;
210
211   va_start (a, fmt);
212   do_log (fmt, a, 0, 2, buf, buflen);
213   va_end (a);
214 }
215
216 #ifdef HAVE_W32_SYSTEM
217 void
218 log_debug_w32 (int w32err, const char *fmt, ...)
219 {
220   va_list a;
221
222   if (w32err == -1)
223     w32err = GetLastError ();
224
225   va_start (a, fmt);
226   do_log (fmt, a, w32err, 0, NULL, 0);
227   va_end (a);
228 }
229
230 void
231 log_error_w32 (int w32err, const char *fmt, ...)
232 {
233   va_list a;
234
235   if (w32err == -1)
236     w32err = GetLastError ();
237
238   va_start (a, fmt);
239   do_log (fmt, a, w32err, 1, NULL, 0);
240   va_end (a);
241 }
242
243 static void
244 do_log_window_info (HWND window, int level)
245 {
246   char buf[1024+1];
247   char name[200];
248   int nname;
249   char *pname;
250   DWORD pid;
251
252   if (!window)
253     return;
254
255   GetWindowThreadProcessId (window, &pid);
256   if (pid != GetCurrentProcessId ())
257     return;
258
259   memset (buf, 0, sizeof (buf));
260   GetWindowText (window, buf, sizeof (buf)-1);
261   nname = GetClassName (window, name, sizeof (name)-1);
262   if (nname)
263     pname = name;
264   else
265     pname = NULL;
266
267   if (level == -1)
268     log_debug ("  parent=%p/%lu (%s) `%s'", window, (unsigned long)pid,
269                pname? pname:"", buf);
270   else
271     log_debug ("    %*shwnd=%p/%lu (%s) `%s'", level*2, "", window,
272                (unsigned long)pid, pname? pname:"", buf);
273 }
274
275
276 /* Helper to log_window_hierarchy.  */
277 static HWND
278 do_log_window_hierarchy (HWND parent, int level)
279 {
280   HWND child;
281
282   child = GetWindow (parent, GW_CHILD);
283   while (child)
284     {
285       do_log_window_info (child, level);
286       do_log_window_hierarchy (child, level+1);
287       child = GetNextWindow (child, GW_HWNDNEXT);
288     }
289
290   return NULL;
291 }
292
293
294 /* Print a debug message using the format string FMT followed by the
295    window hierarchy of WINDOW.  */
296 void
297 log_window_hierarchy (HWND window, const char *fmt, ...)
298 {
299   va_list a;
300
301   va_start (a, fmt);
302   do_log (fmt, a, 0, 0, NULL, 0);
303   va_end (a);
304   if (window)
305     {
306       do_log_window_info (window, -1);
307       do_log_window_hierarchy (window, 0);
308     }
309 }
310 #endif
311
312 GPGRT_LOCK_DEFINE (anon_str_lock);
313
314 /* Weel ok this survives unload but we don't want races
315    and it makes a bit of sense to keep the strings constant. */
316 static std::unordered_map<std::string, std::string> str_map;
317
318 const char *anonstr (const char *data)
319 {
320   static int64_t cnt;
321   if (opt.enable_debug & DBG_DATA)
322     {
323       return data;
324     }
325   if (!data)
326     {
327       return "gpgol_str_null";
328     }
329   if (!strlen (data))
330     {
331       return "gpgol_str_empty";
332     }
333
334   gpgol_lock (&anon_str_lock);
335   const std::string strData (data);
336   auto it = str_map.find (strData);
337
338   if (it == str_map.end ())
339     {
340       const auto anon = std::string ("gpgol_string_") + std::to_string (++cnt);
341       str_map.insert (std::make_pair (strData, anon));
342       it = str_map.find (strData);
343     }
344
345   // As the data is saved in our map we can return
346   // the c_str as it won't be touched as const.
347
348   gpgol_unlock (&anon_str_lock);
349
350   return it->second.c_str();
351 }