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