1 /* Lepton EDA library
2  * Copyright (C) 1998-2010 Ales Hvezda
3  * Copyright (C) 1998-2016 gEDA Contributors
4  * Copyright (C) 2017-2021 Lepton EDA Contributors
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
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20 #include <config.h>
21 
22 #include <stdio.h>
23 #include <sys/stat.h>
24 #ifdef HAVE_STDLIB_H
25 #include <stdlib.h>
26 #endif
27 #ifdef HAVE_UNISTD_H
28 #include <unistd.h>
29 #endif
30 #ifdef HAVE_FCNTL_H
31 #include <fcntl.h>
32 #endif
33 #ifdef HAVE_ERRNO_H
34 #include <errno.h>
35 #endif
36 #ifdef HAVE_STRING_H
37 #include <string.h>
38 #endif
39 
40 #include "liblepton_priv.h"
41 
42 #include <time.h>
43 
44 /*! Default setting for log update callback function. */
45 void (*x_log_update_func)(const gchar*, GLogLevelFlags, const gchar*) = NULL;
46 
47 /*! Default setting for log enable. */
48 int do_logging = TRUE;
49 
50 #define CATCH_LOG_LEVELS (G_LOG_LEVEL_MASK ^ \
51                           (G_LOG_LEVEL_DEBUG | G_LOG_LEVEL_INFO))
52 #define PRINT_LOG_LEVELS (CATCH_LOG_LEVELS ^ \
53                           (G_LOG_LEVEL_WARNING | G_LOG_LEVEL_MESSAGE))
54 
55 #define LOG_OPEN_ATTEMPTS 5
56 
57 static void s_log_handler (const gchar *log_domain,
58                            GLogLevelFlags log_level,
59                            const gchar *message,
60                            gpointer user_data);
61 
62 static int logfile_fd = -1;
63 
64 static guint log_handler_id;
65 
66 /*! \brief Initialize libgeda logging feature.
67  *  \par Function Description
68  *  This function opens the file <B>filename</B> to log to and registers the
69  *  handler to redirect log message to this file.
70  *
71  *  \param [in] prefix  Character string with file name prefix to log to.
72  */
s_log_init(const gchar * prefix)73 void s_log_init (const gchar *prefix)
74 {
75   /* FIXME we assume that the prefix is in the filesystem encoding. */
76 
77   time_t nowt;
78   struct tm *nowtm;
79   gchar *full_prefix = NULL;
80   size_t full_prefix_len = 0;
81   gchar *dir_path = NULL;
82   gchar *filename = NULL;
83   int s, i;
84   int last_exist_logn = 0;
85   GDir *logdir = NULL;
86 
87   if (logfile_fd != -1) {
88     g_critical ("s_log_init: Log already initialised.\n");
89     return;
90   }
91   if (do_logging == FALSE) {
92     return;
93   }
94 
95   time (&nowt);
96   nowtm = gmtime (&nowt);
97 
98   /* create "real" prefix -- this has the form "<prefix>-<date>-" */
99   full_prefix = g_strdup_printf ("%s-%04i%02i%02i-", prefix,
100                                  nowtm->tm_year + 1900, nowtm->tm_mon + 1,
101                                  nowtm->tm_mday);
102   full_prefix_len = strlen (full_prefix);
103 
104   /* Find/create the directory where we're going to put the logs.
105    * FIXME should this be configured somehow?
106    *
107    * Then run through it finding the "biggest" existing filename with
108    * a matching prefix & date. */
109   dir_path = g_build_filename (eda_get_user_cache_dir(), "logs", NULL);
110   /* Try to create the directory. */
111   s = g_mkdir_with_parents (dir_path, 0777/*octal*/);
112   if (s != 0) {
113     /* It's okay to use the logging functions from here, because
114      * there's already a default handler. */
115     g_warning ("Could not create log directory %1$s: %2$s\n",
116                dir_path, strerror (errno));
117     g_free (dir_path);
118     g_free (full_prefix);
119     return;
120   }
121 
122   logdir = g_dir_open (dir_path, 0, NULL);
123   while (TRUE) {
124     const gchar *file = g_dir_read_name (logdir);
125     int n;
126     if (file == NULL) break;
127     if (strncmp (full_prefix, file, full_prefix_len)) continue;
128 
129     s = sscanf (file + full_prefix_len, "%i", &n);
130     if (s != 1) continue;
131 
132     if (n > last_exist_logn) last_exist_logn = n;
133   }
134   g_dir_close (logdir);
135 
136   /* Now try and create a new file. When we fail, increment the number. */
137   i = 0;
138   while (logfile_fd == -1 && (LOG_OPEN_ATTEMPTS > i++)) {
139     filename = g_strdup_printf ("%s%s%s%i.log", dir_path,
140                                 G_DIR_SEPARATOR_S, full_prefix,
141                                 ++last_exist_logn);
142     logfile_fd = open (filename, O_RDWR|O_CREAT|O_EXCL, 0600);
143 
144     if (logfile_fd == -1 && (errno != EEXIST)) break;
145   }
146 
147   if (logfile_fd != -1) {
148 
149     /* install the log handler */
150     log_handler_id = g_log_set_handler (NULL,
151                                         (GLogLevelFlags) CATCH_LOG_LEVELS,
152                                         s_log_handler,
153                                         NULL);
154 
155   } else {
156     /* It's okay to use the logging functions from here, because
157      * there's already a default handler. */
158     if (errno == EEXIST) {
159       g_warning ("Could not create unique log filename in %1$s\n",
160                  dir_path);
161     } else {
162       g_warning ("Could not create log file in %1$s: %2$s\n",
163                  dir_path, strerror (errno));
164     }
165   }
166 
167   g_free (filename);
168   g_free (dir_path);
169   g_free (full_prefix);
170 }
171 
172 /*! \brief Terminates the logging of messages.
173  *  \par Function Description
174  *  This function deregisters the handler for redirection to the log file
175  *  and closes it.
176  */
s_log_close(void)177 void s_log_close (void)
178 {
179   do_logging = FALSE; /* subsequent messages are lost after the close */
180 
181   if (logfile_fd == -1)
182   {
183     return;
184   }
185 
186   /* remove the handler */
187   g_log_remove_handler (NULL, log_handler_id);
188 
189   /* close the file */
190   if (logfile_fd != -1) {
191     close (logfile_fd);
192     logfile_fd = -1;
193   }
194 
195 }
196 
197 /*! \brief  Reads the current log file and returns its contents.
198  *  \par Function Description
199  *  This function reads the current log file and returns its contents.
200  *
201  *  \return Character string with current log's contents.
202  *
203  */
s_log_read(void)204 gchar *s_log_read (void)
205 {
206   gboolean tmp;
207 #define BUFSIZE 200
208   gchar buf[BUFSIZE];
209   GString *contents;
210   gint len;
211 
212   if (logfile_fd == -1) {
213     return NULL;
214   }
215 
216   tmp = do_logging;
217   do_logging = FALSE;
218 
219   /* rewind the file */
220   lseek(logfile_fd, 0, SEEK_SET);
221 
222   /* read its contents and build a string */
223   contents = g_string_new ("");
224   while ((len = read (logfile_fd, &buf, BUFSIZE)) != 0) {
225     contents = g_string_append_len (contents, buf, len);
226   }
227 
228   do_logging = tmp;
229 
230   return g_string_free (contents, FALSE);
231 }
232 
233 /*! \brief Write a message to the current log file.
234  *  \par Function Description
235  *  Writes <B>message</B> to the current log file whose file descriptor
236  *  is <B>logfile_fd</B>.
237  *
238  *  It also sends <B>message</B> to the optional function <B>x_log_update</B>
239  *  for further use.
240  *
241  *  \param [in] log_domain  (unused).
242  *  \param [in] log_level   (unused).
243  *  \param [in] message     Character string containing message to
244  *                          write to log.
245  *  \param [in] user_data   (unused).
246  *
247  */
s_log_handler(const gchar * log_domain,GLogLevelFlags log_level,const gchar * message,gpointer user_data)248 static void s_log_handler (const gchar *log_domain,
249                            GLogLevelFlags log_level,
250                            const gchar *message,
251                            gpointer user_data)
252 {
253   if (do_logging == FALSE) {
254     return;
255   }
256   g_return_if_fail (logfile_fd != -1);
257 
258   size_t len = strlen (message);
259   int status = 0;
260   if (status >= 0)
261     status = write (logfile_fd, message, len);
262   if (status >= 0)
263     status = write (logfile_fd, "\n", 1);
264   if (status == -1) {
265     fprintf(stderr, "Could not write message to log file\n");
266   }
267   if ((status == -1) || (log_level & PRINT_LOG_LEVELS)) {
268     /* If messages are serious or writing to file failed, call the
269      * default handler to write to the console. */
270     g_log_default_handler (log_domain, log_level, message, NULL);
271   }
272 
273   if (x_log_update_func) {
274     (*x_log_update_func) (log_domain, log_level, message);
275   }
276 
277 }
278 
279 
280 /* Helper functions to construct GLogLevelFlags values in
281    Scheme.  We don't just list their current values in Scheme code
282    since GLogLevelFlag is an opaque Glib enum and the flag values
283    may change in future. */
284 
285 GLogLevelFlags
lepton_log_flag_fatal()286 lepton_log_flag_fatal ()
287 {
288   return G_LOG_FLAG_FATAL;
289 }
290 
291 GLogLevelFlags
lepton_log_level_error()292 lepton_log_level_error ()
293 {
294   return G_LOG_LEVEL_ERROR;
295 }
296 
297 GLogLevelFlags
lepton_log_level_critical()298 lepton_log_level_critical ()
299 {
300   return G_LOG_LEVEL_CRITICAL;
301 }
302 
303 GLogLevelFlags
lepton_log_level_warning()304 lepton_log_level_warning ()
305 {
306   return G_LOG_LEVEL_WARNING;
307 }
308 
309 GLogLevelFlags
lepton_log_level_message()310 lepton_log_level_message ()
311 {
312   return G_LOG_LEVEL_MESSAGE;
313 }
314 
315 GLogLevelFlags
lepton_log_level_info()316 lepton_log_level_info ()
317 {
318   return G_LOG_LEVEL_INFO;
319 }
320 
321 GLogLevelFlags
lepton_log_level_debug()322 lepton_log_level_debug ()
323 {
324   return G_LOG_LEVEL_DEBUG;
325 }
326