1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
2
3 caja-debug-log.c: Ring buffer for logging debug messages
4
5 Copyright (C) 2006 Novell, Inc.
6
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the
10 License, or (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
16
17 You should have received a copy of the GNU General Public
18 License along with this program; if not, write to the
19 Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20 Boston, MA 02110-1301, USA.
21
22 Author: Federico Mena-Quintero <federico@novell.com>
23 */
24 #include <config.h>
25 #include <errno.h>
26 #include <stdio.h>
27 #include <string.h>
28 #include "caja-debug-log.h"
29 #include "caja-file.h"
30
31 #if !GLIB_CHECK_VERSION(2,65,2)
32 #include <time.h>
33 #include <sys/time.h>
34 #endif
35
36 #define DEFAULT_RING_BUFFER_NUM_LINES 1000
37
38 #define KEY_FILE_GROUP "debug log"
39 #define KEY_FILE_DOMAINS_KEY "enable domains"
40 #define KEY_FILE_MAX_LINES_KEY "max lines"
41
42 #define MAX_URI_COUNT 20
43
44 static GMutex log_mutex;
45
46 static GHashTable *domains_hash;
47 static char **ring_buffer;
48 static int ring_buffer_next_index;
49 static int ring_buffer_num_lines;
50 static int ring_buffer_max_lines = DEFAULT_RING_BUFFER_NUM_LINES;
51
52 static GSList *milestones_head;
53 static GSList *milestones_tail;
54
55 static void
lock(void)56 lock (void)
57 {
58 g_mutex_lock (&log_mutex);
59 }
60
61 static void
unlock(void)62 unlock (void)
63 {
64 g_mutex_unlock (&log_mutex);
65 }
66
67 void
caja_debug_log(gboolean is_milestone,const char * domain,const char * format,...)68 caja_debug_log (gboolean is_milestone, const char *domain, const char *format, ...)
69 {
70 va_list args;
71
72 va_start (args, format);
73 caja_debug_logv (is_milestone, domain, NULL, format, args);
74 va_end (args);
75 }
76
77 static gboolean
is_domain_enabled(const char * domain)78 is_domain_enabled (const char *domain)
79 {
80 /* User actions are always logged */
81 if (strcmp (domain, CAJA_DEBUG_LOG_DOMAIN_USER) == 0)
82 return TRUE;
83
84 if (!domains_hash)
85 return FALSE;
86
87 return (g_hash_table_lookup (domains_hash, domain) != NULL);
88 }
89
90 static void
ensure_ring(void)91 ensure_ring (void)
92 {
93 if (ring_buffer)
94 return;
95
96 ring_buffer = g_new0 (char *, ring_buffer_max_lines);
97 ring_buffer_next_index = 0;
98 ring_buffer_num_lines = 0;
99 }
100
101 static void
add_to_ring(char * str)102 add_to_ring (char *str)
103 {
104 ensure_ring ();
105
106 g_assert (str != NULL);
107
108 if (ring_buffer_num_lines == ring_buffer_max_lines)
109 {
110 /* We have an overlap, and the ring_buffer_next_index points to
111 * the "first" item. Free it to make room for the new item.
112 */
113
114 g_assert (ring_buffer[ring_buffer_next_index] != NULL);
115 g_free (ring_buffer[ring_buffer_next_index]);
116 }
117 else
118 ring_buffer_num_lines++;
119
120 g_assert (ring_buffer_num_lines <= ring_buffer_max_lines);
121
122 ring_buffer[ring_buffer_next_index] = str;
123
124 ring_buffer_next_index++;
125 if (ring_buffer_next_index == ring_buffer_max_lines)
126 {
127 ring_buffer_next_index = 0;
128 g_assert (ring_buffer_num_lines == ring_buffer_max_lines);
129 }
130 }
131
132 static void
add_to_milestones(const char * str)133 add_to_milestones (const char *str)
134 {
135 char *str_copy;
136
137 str_copy = g_strdup (str);
138
139 if (milestones_tail)
140 {
141 milestones_tail = g_slist_append (milestones_tail, str_copy);
142 milestones_tail = milestones_tail->next;
143 }
144 else
145 {
146 milestones_head = milestones_tail = g_slist_append (NULL, str_copy);
147 }
148
149 g_assert (milestones_head != NULL && milestones_tail != NULL);
150 }
151
152 void
caja_debug_logv(gboolean is_milestone,const char * domain,const GList * uris,const char * format,va_list args)153 caja_debug_logv (gboolean is_milestone, const char *domain, const GList *uris, const char *format, va_list args)
154 {
155 char *str;
156 char *debug_str;
157 #if GLIB_CHECK_VERSION(2,65,2)
158 char *date_str;
159 GDateTime* datetime;
160 #else
161 struct timeval tv;
162 struct tm tm;
163 #endif
164
165 lock ();
166
167 if (!(is_milestone || is_domain_enabled (domain)))
168 goto out;
169
170 str = g_strdup_vprintf (format, args);
171
172 #if GLIB_CHECK_VERSION(2,65,2)
173 datetime = g_date_time_new_now_local ();
174 date_str = g_date_time_format (datetime, "%Y/%m/%d %H:%M:%S.%f");
175 g_date_time_unref (datetime);
176 debug_str = g_strdup_printf ("%p %s (%s): %s",
177 g_thread_self (),
178 date_str,
179 domain,
180 str);
181 g_free (date_str);
182 #else
183 gettimeofday (&tv, NULL);
184 tm = *localtime (&tv.tv_sec);
185 debug_str = g_strdup_printf ("%p %04d/%02d/%02d %02d:%02d:%02d.%04d (%s): %s",
186 g_thread_self (),
187 tm.tm_year + 1900,
188 tm.tm_mon + 1,
189 tm.tm_mday,
190 tm.tm_hour,
191 tm.tm_min,
192 tm.tm_sec,
193 (int) (tv.tv_usec / 100),
194 domain,
195 str);
196 #endif
197 g_free (str);
198
199 if (uris)
200 {
201 int debug_str_len;
202 int uris_len;
203 const GList *l;
204 char *new_str;
205 char *p;
206 int count;
207
208 uris_len = 0;
209
210 count = 0;
211 for (l = uris; l; l = l->next)
212 {
213 const char *uri;
214
215 uri = l->data;
216 uris_len += strlen (uri) + 2; /* plus 2 for a tab and the newline */
217
218 if (count++ > MAX_URI_COUNT)
219 {
220 uris_len += 4; /* "...\n" */
221 break;
222 }
223
224 }
225
226 debug_str_len = strlen (debug_str);
227 new_str = g_new (char, debug_str_len + 1 + uris_len + 1); /* plus 1 for newline & zero */
228
229 p = g_stpcpy (new_str, debug_str);
230 *p++ = '\n';
231
232 count = 0;
233 for (l = uris; l; l = l->next)
234 {
235 const char *uri;
236
237 uri = l->data;
238
239 *p++ = '\t';
240
241 p = g_stpcpy (p, uri);
242
243 if (l->next)
244 *p++ = '\n';
245
246 if (count++ > MAX_URI_COUNT)
247 {
248 p = g_stpcpy (p, "...\n");
249 break;
250 }
251 }
252
253 g_free (debug_str);
254 debug_str = new_str;
255 }
256
257 add_to_ring (debug_str);
258 if (is_milestone)
259 add_to_milestones (debug_str);
260
261 out:
262 unlock ();
263 }
264
265 void
caja_debug_log_with_uri_list(gboolean is_milestone,const char * domain,const GList * uris,const char * format,...)266 caja_debug_log_with_uri_list (gboolean is_milestone, const char *domain, const GList *uris,
267 const char *format, ...)
268 {
269 va_list args;
270
271 va_start (args, format);
272 caja_debug_logv (is_milestone, domain, uris, format, args);
273 va_end (args);
274 }
275
276 void
caja_debug_log_with_file_list(gboolean is_milestone,const char * domain,GList * files,const char * format,...)277 caja_debug_log_with_file_list (gboolean is_milestone, const char *domain, GList *files,
278 const char *format, ...)
279 {
280 va_list args;
281 GList *uris;
282 GList *l;
283 int count;
284
285 /* Avoid conversion if debugging disabled */
286 if (!(is_milestone ||
287 caja_debug_log_is_domain_enabled (domain)))
288 {
289 return;
290 }
291
292 uris = NULL;
293
294 count = 0;
295 for (l = files; l; l = l->next)
296 {
297 CajaFile *file;
298 char *uri;
299
300 file = CAJA_FILE (l->data);
301 uri = caja_file_get_uri (file);
302
303 if (caja_file_is_gone (file))
304 {
305 char *new_uri;
306
307 /* Hack: this will create an invalid URI, but it's for
308 * display purposes only.
309 */
310 new_uri = g_strconcat (uri ? uri : "", " (gone)", NULL);
311 g_free (uri);
312 uri = new_uri;
313 }
314 uris = g_list_prepend (uris, uri);
315
316 /* Avoid doing to much work, more than MAX_URI_COUNT uris
317 won't be shown anyway */
318 if (count++ > MAX_URI_COUNT + 1)
319 {
320 break;
321 }
322 }
323
324 uris = g_list_reverse (uris);
325
326 va_start (args, format);
327 caja_debug_logv (is_milestone, domain, uris, format, args);
328 va_end (args);
329
330 g_list_free_full (uris, g_free);
331 }
332
333 gboolean
caja_debug_log_load_configuration(const char * filename,GError ** error)334 caja_debug_log_load_configuration (const char *filename, GError **error)
335 {
336 GKeyFile *key_file;
337 char **strings;
338 gsize num_strings;
339 int num;
340 GError *my_error;
341
342 g_assert (filename != NULL);
343 g_assert (error == NULL || *error == NULL);
344
345 key_file = g_key_file_new ();
346
347 if (!g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, error))
348 {
349 g_key_file_free (key_file);
350 return FALSE;
351 }
352
353 /* Domains */
354
355 my_error = NULL;
356 strings = g_key_file_get_string_list (key_file, KEY_FILE_GROUP, KEY_FILE_DOMAINS_KEY, &num_strings, &my_error);
357 if (my_error)
358 g_error_free (my_error);
359 else
360 {
361 int i;
362
363 for (i = 0; i < num_strings; i++)
364 strings[i] = g_strstrip (strings[i]);
365
366 caja_debug_log_enable_domains ((const char **) strings, num_strings);
367 g_strfreev (strings);
368 }
369
370 /* Number of lines */
371
372 my_error = NULL;
373 num = g_key_file_get_integer (key_file, KEY_FILE_GROUP, KEY_FILE_MAX_LINES_KEY, &my_error);
374 if (my_error)
375 g_error_free (my_error);
376 else
377 caja_debug_log_set_max_lines (num);
378
379 g_key_file_free (key_file);
380 return TRUE;
381 }
382
383 void
caja_debug_log_enable_domains(const char ** domains,int n_domains)384 caja_debug_log_enable_domains (const char **domains, int n_domains)
385 {
386 int i;
387
388 g_assert (domains != NULL);
389 g_assert (n_domains >= 0);
390
391 lock ();
392
393 if (!domains_hash)
394 domains_hash = g_hash_table_new (g_str_hash, g_str_equal);
395
396 for (i = 0; i < n_domains; i++)
397 {
398 g_assert (domains[i] != NULL);
399
400 if (strcmp (domains[i], CAJA_DEBUG_LOG_DOMAIN_USER) == 0)
401 continue; /* user actions are always enabled */
402
403 if (g_hash_table_lookup (domains_hash, domains[i]) == NULL)
404 {
405 char *domain;
406
407 domain = g_strdup (domains[i]);
408 g_hash_table_insert (domains_hash, domain, domain);
409 }
410 }
411
412 unlock ();
413 }
414
415 void
caja_debug_log_disable_domains(const char ** domains,int n_domains)416 caja_debug_log_disable_domains (const char **domains, int n_domains)
417 {
418 g_assert (domains != NULL);
419 g_assert (n_domains >= 0);
420
421 lock ();
422
423 if (domains_hash)
424 {
425 int i;
426
427 for (i = 0; i < n_domains; i++)
428 {
429 char *domain;
430
431 g_assert (domains[i] != NULL);
432
433 if (strcmp (domains[i], CAJA_DEBUG_LOG_DOMAIN_USER) == 0)
434 continue; /* user actions are always enabled */
435
436 domain = g_hash_table_lookup (domains_hash, domains[i]);
437 if (domain)
438 {
439 g_hash_table_remove (domains_hash, domain);
440 g_free (domain);
441 }
442 }
443 } /* else, there is nothing to disable */
444
445 unlock ();
446 }
447
448 gboolean
caja_debug_log_is_domain_enabled(const char * domain)449 caja_debug_log_is_domain_enabled (const char *domain)
450 {
451 gboolean retval;
452
453 g_assert (domain != NULL);
454
455 lock ();
456 retval = is_domain_enabled (domain);
457 unlock ();
458
459 return retval;
460 }
461
462 struct domains_dump_closure
463 {
464 char **domains;
465 int num_domains;
466 };
467
468 static void
domains_foreach_dump_cb(gpointer key,gpointer value,gpointer data)469 domains_foreach_dump_cb (gpointer key, gpointer value, gpointer data)
470 {
471 struct domains_dump_closure *closure;
472 char *domain;
473
474 closure = data;
475 domain = key;
476
477 closure->domains[closure->num_domains] = domain;
478 closure->num_domains++;
479 }
480
481 static GKeyFile *
make_key_file_from_configuration(void)482 make_key_file_from_configuration (void)
483 {
484 GKeyFile *key_file;
485 struct domains_dump_closure closure;
486
487 key_file = g_key_file_new ();
488
489 /* domains */
490
491 if (domains_hash)
492 {
493 int num_domains;
494
495 num_domains = g_hash_table_size (domains_hash);
496 if (num_domains != 0)
497 {
498 closure.domains = g_new (char *, num_domains);
499 closure.num_domains = 0;
500
501 g_hash_table_foreach (domains_hash, domains_foreach_dump_cb, &closure);
502 g_assert (num_domains == closure.num_domains);
503
504 g_key_file_set_string_list (key_file, KEY_FILE_GROUP, KEY_FILE_DOMAINS_KEY,
505 (const gchar * const *) closure.domains, closure.num_domains);
506 g_free (closure.domains);
507 }
508 }
509
510 /* max lines */
511
512 g_key_file_set_integer (key_file, KEY_FILE_GROUP, KEY_FILE_MAX_LINES_KEY, ring_buffer_max_lines);
513
514 return key_file;
515 }
516
517 static gboolean
write_string(const char * filename,FILE * file,const char * str,GError ** error)518 write_string (const char *filename, FILE *file, const char *str, GError **error)
519 {
520 if (fputs (str, file) == EOF)
521 {
522 int saved_errno;
523
524 saved_errno = errno;
525 g_set_error (error,
526 G_FILE_ERROR,
527 g_file_error_from_errno (saved_errno),
528 "error when writing to log file %s", filename);
529
530 return FALSE;
531 }
532
533 return TRUE;
534 }
535
536 static gboolean
dump_configuration(const char * filename,FILE * file,GError ** error)537 dump_configuration (const char *filename, FILE *file, GError **error)
538 {
539 GKeyFile *key_file;
540 char *data;
541 gsize length;
542 gboolean success;
543
544 if (!write_string (filename, file,
545 "\n\n"
546 "This configuration for the debug log can be re-created\n"
547 "by putting the following in ~/caja-debug-log.conf\n"
548 "(use ';' to separate domain names):\n\n",
549 error))
550 {
551 return FALSE;
552 }
553
554 success = FALSE;
555
556 key_file = make_key_file_from_configuration ();
557
558 data = g_key_file_to_data (key_file, &length, error);
559 if (!data)
560 goto out;
561
562 if (!write_string (filename, file, data, error))
563 {
564 goto out;
565 }
566
567 success = TRUE;
568 out:
569 g_key_file_free (key_file);
570 return success;
571 }
572
573 static gboolean
dump_milestones(const char * filename,FILE * file,GError ** error)574 dump_milestones (const char *filename, FILE *file, GError **error)
575 {
576 GSList *l;
577
578 if (!write_string (filename, file, "===== BEGIN MILESTONES =====\n", error))
579 return FALSE;
580
581 for (l = milestones_head; l; l = l->next)
582 {
583 const char *str;
584
585 str = l->data;
586 if (!(write_string (filename, file, str, error)
587 && write_string (filename, file, "\n", error)))
588 return FALSE;
589 }
590
591 if (!write_string (filename, file, "===== END MILESTONES =====\n", error))
592 return FALSE;
593
594 return TRUE;
595 }
596
597 static gboolean
dump_ring_buffer(const char * filename,FILE * file,GError ** error)598 dump_ring_buffer (const char *filename, FILE *file, GError **error)
599 {
600 int start_index;
601 int i;
602
603 if (!write_string (filename, file, "===== BEGIN RING BUFFER =====\n", error))
604 return FALSE;
605
606 if (ring_buffer_num_lines == ring_buffer_max_lines)
607 start_index = ring_buffer_next_index;
608 else
609 start_index = 0;
610
611 for (i = 0; i < ring_buffer_num_lines; i++)
612 {
613 int idx;
614
615 idx = (start_index + i) % ring_buffer_max_lines;
616
617 if (!(write_string (filename, file, ring_buffer[idx], error)
618 && write_string (filename, file, "\n", error)))
619 {
620 return FALSE;
621 }
622 }
623
624 if (!write_string (filename, file, "===== END RING BUFFER =====\n", error))
625 return FALSE;
626
627 return TRUE;
628 }
629
630 gboolean
caja_debug_log_dump(const char * filename,GError ** error)631 caja_debug_log_dump (const char *filename, GError **error)
632 {
633 FILE *file;
634 gboolean success;
635
636 g_assert (error == NULL || *error == NULL);
637
638 lock ();
639
640 success = FALSE;
641
642 file = fopen (filename, "w");
643 if (!file)
644 {
645 int saved_errno;
646
647 saved_errno = errno;
648 g_set_error (error,
649 G_FILE_ERROR,
650 g_file_error_from_errno (saved_errno),
651 "could not open log file %s", filename);
652 goto out;
653 }
654
655 if (!(dump_milestones (filename, file, error)
656 && dump_ring_buffer (filename, file, error)
657 && dump_configuration (filename, file, error)))
658 {
659 goto do_close;
660 }
661
662 success = TRUE;
663
664 do_close:
665
666 if (fclose (file) != 0)
667 {
668 int saved_errno;
669
670 saved_errno = errno;
671
672 if (error && *error)
673 {
674 g_error_free (*error);
675 *error = NULL;
676 }
677
678 g_set_error (error,
679 G_FILE_ERROR,
680 g_file_error_from_errno (saved_errno),
681 "error when closing log file %s", filename);
682 success = FALSE;
683 }
684
685 out:
686
687 unlock ();
688 return success;
689 }
690
691 void
caja_debug_log_set_max_lines(int num_lines)692 caja_debug_log_set_max_lines (int num_lines)
693 {
694 char **new_buffer;
695 int lines_to_copy;
696
697 g_assert (num_lines > 0);
698
699 lock ();
700
701 if (num_lines == ring_buffer_max_lines)
702 goto out;
703
704 new_buffer = g_new0 (char *, num_lines);
705
706 lines_to_copy = MIN (num_lines, ring_buffer_num_lines);
707
708 if (ring_buffer)
709 {
710 int start_index;
711 int i;
712
713 if (ring_buffer_num_lines == ring_buffer_max_lines)
714 start_index = (ring_buffer_next_index + ring_buffer_max_lines - lines_to_copy) % ring_buffer_max_lines;
715 else
716 start_index = ring_buffer_num_lines - lines_to_copy;
717
718 g_assert (start_index >= 0 && start_index < ring_buffer_max_lines);
719
720 for (i = 0; i < lines_to_copy; i++)
721 {
722 int idx;
723
724 idx = (start_index + i) % ring_buffer_max_lines;
725
726 new_buffer[i] = ring_buffer[idx];
727 ring_buffer[idx] = NULL;
728 }
729
730 for (i = 0; i < ring_buffer_max_lines; i++)
731 g_free (ring_buffer[i]);
732
733 g_free (ring_buffer);
734 }
735
736 ring_buffer = new_buffer;
737 ring_buffer_next_index = lines_to_copy;
738 ring_buffer_num_lines = lines_to_copy;
739 ring_buffer_max_lines = num_lines;
740
741 out:
742
743 unlock ();
744 }
745
746 int
caja_debug_log_get_max_lines(void)747 caja_debug_log_get_max_lines (void)
748 {
749 int retval;
750
751 lock ();
752 retval = ring_buffer_max_lines;
753 unlock ();
754
755 return retval;
756 }
757
758 void
caja_debug_log_clear(void)759 caja_debug_log_clear (void)
760 {
761 int i;
762
763 lock ();
764
765 if (!ring_buffer)
766 goto out;
767
768 for (i = 0; i < ring_buffer_max_lines; i++)
769 {
770 g_free (ring_buffer[i]);
771 ring_buffer[i] = NULL;
772 }
773
774 ring_buffer_next_index = 0;
775 ring_buffer_num_lines = 0;
776
777 out:
778 unlock ();
779 }
780