Blind fix macOS build
[geeqie.git] / src / debug.cc
1 /*
2  * Copyright (C) 2008 - 2016 The Geeqie Team
3  *
4  * Authors: Vladimir Nadvornik, Laurent Monin
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program 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
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20
21 #include "debug.h"
22
23 #include <regex.h>
24 #include <sys/time.h>
25
26 #include <cstdarg>
27 #include <cstdio>
28 #include <cstdlib>
29 #include <cstring>
30
31 #include <config.h>
32
33 #if HAVE_EXECINFO_H
34 #  include <execinfo.h>
35 #endif
36
37 #include "filedata.h"
38 #include "intl.h"
39 #include "logwindow.h"
40 #include "main-defines.h"
41 #include "main.h"
42 #include "misc.h"
43 #include "options.h"
44 #include "ui-fileops.h"
45
46 /*
47  * Logging functions
48  */
49 static gchar *regexp = nullptr;
50
51 static gboolean log_msg_cb(gpointer data)
52 {
53         auto buf = static_cast<gchar *>(data);
54         log_window_append(buf, LOG_MSG);
55         g_free(buf);
56         return FALSE;
57 }
58
59 /**
60  * @brief Appends a user information message to the log window queue
61  * @param data The message
62  * @returns FALSE
63  *
64  * If the first word of the message is either "error" or "warning"
65  * (case insensitive) the message is color-coded appropriately
66  */
67 static gboolean log_normal_cb(gpointer data)
68 {
69         auto buf = static_cast<gchar *>(data);
70         gchar *buf_casefold = g_utf8_casefold(buf, -1);
71         gchar *error_casefold = g_utf8_casefold(_("error"), -1);
72         gchar *warning_casefold = g_utf8_casefold(_("warning"), -1);
73
74         if (buf_casefold == g_strstr_len(buf_casefold, -1, error_casefold))
75                 {
76                 log_window_append(buf, LOG_ERROR);
77                 }
78         else if (buf_casefold == g_strstr_len(buf_casefold, -1, warning_casefold))
79                 {
80                 log_window_append(buf, LOG_WARN);
81                 }
82         else
83                 {
84                 log_window_append(buf, LOG_NORMAL);
85                 }
86
87         g_free(buf);
88         g_free(buf_casefold);
89         g_free(error_casefold);
90         g_free(warning_casefold);
91         return FALSE;
92 }
93
94 void log_domain_print_message(const gchar *domain, gchar *buf)
95 {
96         gchar *buf_nl;
97         regex_t regex;
98         gint ret_comp;
99         gint ret_exec;
100
101         buf_nl = g_strconcat(buf, "\n", NULL);
102
103         if (regexp && command_line)
104                 {
105                         ret_comp = regcomp(&regex, regexp, 0);
106                         if (!ret_comp)
107                                 {
108                                 ret_exec = regexec(&regex, buf_nl, 0, nullptr, 0);
109
110                                 if (!ret_exec)
111                                         {
112                                         print_term(FALSE, buf_nl);
113                                         if (strcmp(domain, DOMAIN_INFO) == 0)
114                                                 g_idle_add(log_normal_cb, buf_nl);
115                                         else
116                                                 g_idle_add(log_msg_cb, buf_nl);
117                                         }
118                                 regfree(&regex);
119                                 }
120                 }
121         else
122                 {
123                 print_term(FALSE, buf_nl);
124                 if (strcmp(domain, DOMAIN_INFO) == 0)
125                         g_idle_add(log_normal_cb, buf_nl);
126                 else
127                         g_idle_add(log_msg_cb, buf_nl);
128                 }
129         g_free(buf);
130 }
131
132 void log_domain_print_debug(const gchar *domain, const gchar *file_name, int line_number, const gchar *function_name, const gchar *format, ...)
133 {
134         va_list ap;
135         gchar *message;
136         gchar *location;
137         gchar *buf;
138
139         va_start(ap, format);
140         message = g_strdup_vprintf(format, ap);
141         va_end(ap);
142
143         if (options && options->log_window.timer_data)
144                 {
145                 location = g_strdup_printf("%s:%s:%d:%s:", get_exec_time(), file_name, line_number, function_name);
146                 }
147         else
148                 {
149                 location = g_strdup_printf("%s:%d:%s:", file_name, line_number, function_name);
150                 }
151
152         buf = g_strconcat(location, message, NULL);
153         log_domain_print_message(domain,buf);
154         g_free(location);
155         g_free(message);
156 }
157
158 void log_domain_printf(const gchar *domain, const gchar *format, ...)
159 {
160         va_list ap;
161         gchar *buf;
162
163         va_start(ap, format);
164         buf = g_strdup_vprintf(format, ap);
165         va_end(ap);
166
167         log_domain_print_message(domain, buf);
168 }
169
170 /*
171  * Debugging only functions
172  */
173
174 #ifdef DEBUG
175
176 static gint debug_level = DEBUG_LEVEL_MIN;
177
178
179 gint get_debug_level()
180 {
181         return debug_level;
182 }
183
184 void set_debug_level(gint new_level)
185 {
186         debug_level = CLAMP(new_level, DEBUG_LEVEL_MIN, DEBUG_LEVEL_MAX);
187 }
188
189 void debug_level_add(gint delta)
190 {
191         set_debug_level(debug_level + delta);
192 }
193
194 gint required_debug_level(gint level)
195 {
196         return (debug_level >= level);
197 }
198
199 static gint timeval_delta(struct timeval *result, struct timeval *x, struct timeval *y)
200 {
201         if (x->tv_usec < y->tv_usec)
202                 {
203                 gint nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
204                 y->tv_usec -= 1000000 * nsec;
205                 y->tv_sec += nsec;
206                 }
207
208         if (x->tv_usec - y->tv_usec > 1000000)
209                 {
210                 gint nsec = (x->tv_usec - y->tv_usec) / 1000000;
211                 y->tv_usec += 1000000 * nsec;
212                 y->tv_sec -= nsec;
213         }
214
215         result->tv_sec = x->tv_sec - y->tv_sec;
216         result->tv_usec = x->tv_usec - y->tv_usec;
217
218         return x->tv_sec < y->tv_sec;
219 }
220
221 const gchar *get_exec_time()
222 {
223         static gchar timestr[30];
224         static struct timeval start_tv = {0, 0};
225         static struct timeval previous = {0, 0};
226         static gint started = 0;
227
228         struct timeval tv = {0, 0};
229         static struct timeval delta = {0, 0};
230
231         gettimeofday(&tv, nullptr);
232
233         if (start_tv.tv_sec == 0) start_tv = tv;
234
235         tv.tv_sec -= start_tv.tv_sec;
236         if (tv.tv_usec >= start_tv.tv_usec)
237                 tv.tv_usec -= start_tv.tv_usec;
238         else
239                 {
240                 tv.tv_usec += 1000000 - start_tv.tv_usec;
241                 tv.tv_sec -= 1;
242                 }
243
244         if (started) timeval_delta(&delta, &tv, &previous);
245
246         previous = tv;
247         started = 1;
248
249         g_snprintf(timestr, sizeof(timestr), "%5d.%06d (+%05d.%06d)", static_cast<gint>(tv.tv_sec), static_cast<gint>(tv.tv_usec), static_cast<gint>(delta.tv_sec), static_cast<gint>(delta.tv_usec));
250
251         return timestr;
252 }
253
254 void init_exec_time()
255 {
256         get_exec_time();
257 }
258
259 void set_regexp(const gchar *cmd_regexp)
260 {
261         regexp = g_strdup(cmd_regexp);
262 }
263
264 gchar *get_regexp()
265 {
266         return g_strdup(regexp);
267 }
268
269 #if HAVE_EXECINFO_H
270 /**
271  * @brief Backtrace of geeqie files
272  * @param file
273  * @param function
274  * @param line
275  *
276  * Requires command line program addr2line \n
277  * Prints the contents of the backtrace buffer for Geeqie files. \n
278  * Format printed is: \n
279  * <full path to source file>:<line number>
280  *
281  * The log window F1 command and Edit/Preferences/Behavior/Log Window F1
282  * Command may be used to open an editor at a backtrace location.
283  */
284 void log_print_backtrace(const gchar *file, gint line, const gchar *function)
285 {
286         FILE *fp;
287         char **bt_syms;
288         char path[2048];
289         gchar *address_offset;
290         gchar *cmd_line;
291         gchar *exe_path;
292         gchar *function_name = nullptr;
293         gchar *paren_end;
294         gchar *paren_start;
295         gint bt_size;
296         gint i;
297         void *bt[1024];
298
299         if (runcmd(reinterpret_cast<const gchar *>("which addr2line >/dev/null 2>&1")) == 0)
300                 {
301                 exe_path = g_path_get_dirname(gq_executable_path);
302                 bt_size = backtrace(bt, 1024);
303                 bt_syms = backtrace_symbols(bt, bt_size);
304
305                 log_printf("Backtrace start");
306                 log_printf("%s/../%s:%d %s\n", exe_path, file, line, function);
307
308                 /* Last item is always "??:?", so ignore it */
309                 for (i = 1; i < bt_size - 1; i++)
310                         {
311                         if (strstr(bt_syms[i], GQ_APPNAME_LC))
312                                 {
313                                 paren_start = g_strstr_len(bt_syms[i], -1, "(");
314                                 paren_end = g_strstr_len(bt_syms[i], -1, ")");
315                                 address_offset = g_strndup(paren_start + 1, paren_end - paren_start - 1);
316
317                                 cmd_line = g_strconcat("addr2line -p -f -C -e ", gq_executable_path, " ", address_offset, NULL);
318
319                                 fp = popen(cmd_line, "r");
320                                 if (fp == nullptr)
321                                         {
322                                         log_printf("Failed to run command: %s", cmd_line);
323                                         }
324                                 else
325                                         {
326                                         while (fgets(path, sizeof(path), fp) != nullptr)
327                                                 {
328                                                 /* Remove redundant newline */
329                                                 path[strlen(path) - 1] = '\0';
330
331                                                 if (g_strstr_len(path, strlen(path), "(") != nullptr)
332                                                         {
333                                                         function_name = g_strndup(path, g_strstr_len(path, strlen(path), "(") - path);
334                                                         }
335                                                 else
336                                                         {
337                                                         function_name = g_strdup("");
338                                                         }
339                                                 log_printf("%s %s", g_strstr_len(path, -1, "at ") + 3, function_name);
340
341                                                 g_free(function_name);
342                                                 }
343                                         }
344
345                                 pclose(fp);
346
347                                 g_free(address_offset);
348                                 g_free(cmd_line);
349                                 }
350                         }
351                 log_printf("Backtrace end");
352
353                 free(bt_syms);
354                 g_free(exe_path);
355                 }
356 }
357 #else
358 void log_print_backtrace(const gchar *, gint, const gchar *)
359 {
360 }
361 #endif
362
363 /**
364  * @brief Print ref. count and image name
365  * @param file
366  * @param function
367  * @param line
368  *
369  * Print image ref. count and full path name of all images in
370  * the file_data_pool.
371  */
372 void log_print_file_data_dump(const gchar *file, const gchar *function, gint line)
373 {
374         gchar *exe_path;
375
376         exe_path = g_path_get_dirname(gq_executable_path);
377
378         log_printf("FileData dump start");
379         log_printf("%s/../%s:%d %s\n", exe_path, file, line, function);
380
381         file_data_dump();
382
383         log_printf("FileData dump end");
384
385         g_free(exe_path);
386 }
387
388 #endif /* DEBUG */
389 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */