]> git.sur5r.net Git - i3/i3/blob - src/log.c
clean up zero-byte logfile on immediate exit
[i3/i3] / src / log.c
1 #line 2 "log.c"
2 /*
3  * vim:ts=4:sw=4:expandtab
4  *
5  * i3 - an improved dynamic tiling window manager
6  * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
7  *
8  * log.c: Logging functions.
9  *
10  */
11 #include <stdarg.h>
12 #include <stdio.h>
13 #include <string.h>
14 #include <stdbool.h>
15 #include <stdlib.h>
16 #include <sys/time.h>
17 #include <unistd.h>
18 #include <fcntl.h>
19 #include <sys/mman.h>
20 #include <sys/stat.h>
21 #include <errno.h>
22 #if defined(__APPLE__)
23 #include <sys/types.h>
24 #include <sys/sysctl.h>
25 #endif
26
27 #include "util.h"
28 #include "log.h"
29 #include "i3.h"
30 #include "libi3.h"
31 #include "shmlog.h"
32
33 static bool debug_logging = false;
34 static bool verbose = false;
35 static FILE *errorfile;
36 char *errorfilename;
37
38 /* SHM logging variables */
39
40 /* The name for the SHM (/i3-log-%pid). Will end up on /dev/shm on most
41  * systems. Global so that we can clean up at exit. */
42 char *shmlogname = "";
43 /* Size limit for the SHM log, by default 25 MiB. Can be overwritten using the
44  * flag --shmlog-size. */
45 int shmlog_size = 0;
46 /* If enabled, logbuffer will point to a memory mapping of the i3 SHM log. */
47 static char *logbuffer;
48 /* A pointer (within logbuffer) where data will be written to next. */
49 static char *logwalk;
50 /* A pointer to the byte where we last wrapped. Necessary to not print the
51  * left-overs at the end of the ringbuffer. */
52 static char *loglastwrap;
53 /* Size (in bytes) of the i3 SHM log. */
54 static int logbuffer_size;
55 /* File descriptor for shm_open. */
56 static int logbuffer_shm;
57
58 /*
59  * Writes the offsets for the next write and for the last wrap to the
60  * shmlog_header.
61  * Necessary to print the i3 SHM log in the correct order.
62  *
63  */
64 static void store_log_markers(void) {
65     i3_shmlog_header *header = (i3_shmlog_header*)logbuffer;
66
67     header->offset_next_write = (logwalk - logbuffer);
68     header->offset_last_wrap = (loglastwrap - logbuffer);
69     header->size = logbuffer_size;
70 }
71
72 /*
73  * Initializes logging by creating an error logfile in /tmp (or
74  * XDG_RUNTIME_DIR, see get_process_filename()).
75  *
76  * Will be called twice if --shmlog-size is specified.
77  *
78  */
79 void init_logging(void) {
80     if (!errorfilename) {
81         if (!(errorfilename = get_process_filename("errorlog")))
82             ELOG("Could not initialize errorlog\n");
83         else {
84             errorfile = fopen(errorfilename, "w");
85             if (fcntl(fileno(errorfile), F_SETFD, FD_CLOEXEC)) {
86                 ELOG("Could not set close-on-exec flag\n");
87             }
88         }
89     }
90
91     /* If this is a debug build (not a release version), we will enable SHM
92      * logging by default, unless the user turned it off explicitly. */
93     if (logbuffer == NULL && shmlog_size > 0) {
94         /* Reserve 1% of the RAM for the logfile, but at max 25 MiB.
95          * For 512 MiB of RAM this will lead to a 5 MiB log buffer.
96          * At the moment (2011-12-10), no testcase leads to an i3 log
97          * of more than ~ 600 KiB. */
98         long long physical_mem_bytes;
99 #if defined(__APPLE__)
100         int mib[2] = { CTL_HW, HW_MEMSIZE };
101         size_t length = sizeof(long long);
102         sysctl(mib, 2, &physical_mem_bytes, &length, NULL, 0);
103 #else
104         physical_mem_bytes = (long long)sysconf(_SC_PHYS_PAGES) *
105                                         sysconf(_SC_PAGESIZE);
106 #endif
107         logbuffer_size = min(physical_mem_bytes * 0.01, shmlog_size);
108         sasprintf(&shmlogname, "/i3-log-%d", getpid());
109         logbuffer_shm = shm_open(shmlogname, O_RDWR | O_CREAT | O_TRUNC, S_IREAD | S_IWRITE);
110         if (logbuffer_shm == -1) {
111             ELOG("Could not shm_open SHM segment for the i3 log: %s\n", strerror(errno));
112             return;
113         }
114
115         if (ftruncate(logbuffer_shm, logbuffer_size) == -1) {
116             close(logbuffer_shm);
117             shm_unlink("/i3-log-");
118             ELOG("Could not ftruncate SHM segment for the i3 log: %s\n", strerror(errno));
119             return;
120         }
121
122         logbuffer = mmap(NULL, logbuffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, logbuffer_shm, 0);
123         if (logbuffer == MAP_FAILED) {
124             close(logbuffer_shm);
125             shm_unlink("/i3-log-");
126             ELOG("Could not mmap SHM segment for the i3 log: %s\n", strerror(errno));
127             logbuffer = NULL;
128             return;
129         }
130         logwalk = logbuffer + sizeof(i3_shmlog_header);
131         loglastwrap = logbuffer + logbuffer_size;
132         store_log_markers();
133     }
134     atexit(purge_zerobyte_logfile);
135 }
136
137 /*
138  * Set verbosity of i3. If verbose is set to true, informative messages will
139  * be printed to stdout. If verbose is set to false, only errors will be
140  * printed.
141  *
142  */
143 void set_verbosity(bool _verbose) {
144     verbose = _verbose;
145 }
146
147 /*
148  * Set debug logging.
149  *
150  */
151 void set_debug_logging(const bool _debug_logging) {
152     debug_logging = _debug_logging;
153 }
154
155 /*
156  * Logs the given message to stdout (if print is true) while prefixing the
157  * current time to it. Additionally, the message will be saved in the i3 SHM
158  * log if enabled.
159  * This is to be called by *LOG() which includes filename/linenumber/function.
160  *
161  */
162 static void vlog(const bool print, const char *fmt, va_list args) {
163     /* Precisely one page to not consume too much memory but to hold enough
164      * data to be useful. */
165     static char message[4096];
166     static struct tm result;
167     static time_t t;
168     static struct tm *tmp;
169     static size_t len;
170
171     /* Get current time */
172     t = time(NULL);
173     /* Convert time to local time (determined by the locale) */
174     tmp = localtime_r(&t, &result);
175     /* Generate time prefix */
176     len = strftime(message, sizeof(message), "%x %X - ", tmp);
177
178     /*
179      * logbuffer  print
180      * ----------------
181      *  true      true   format message, save, print
182      *  true      false  format message, save
183      *  false     true   print message only
184      *  false     false  INVALID, never called
185      */
186     if (!logbuffer) {
187 #ifdef DEBUG_TIMING
188         struct timeval tv;
189         gettimeofday(&tv, NULL);
190         printf("%s%d.%d - ", message, tv.tv_sec, tv.tv_usec);
191 #else
192         printf("%s", message);
193 #endif
194         vprintf(fmt, args);
195     } else {
196         len += vsnprintf(message + len, sizeof(message) - len, fmt, args);
197         if (len >= sizeof(message)) {
198             fprintf(stderr, "BUG: single log message > 4k\n");
199         }
200
201         /* If there is no space for the current message (plus trailing
202          * nullbyte) in the ringbuffer, we need to wrap and write to the
203          * beginning again. */
204         if ((len+1) >= (logbuffer_size - (logwalk - logbuffer))) {
205             loglastwrap = logwalk;
206             logwalk = logbuffer + sizeof(i3_shmlog_header);
207         }
208
209         /* Copy the buffer, terminate it, move the write pointer to the byte after
210          * our current message. */
211         strncpy(logwalk, message, len);
212         logwalk[len] = '\0';
213         logwalk += len + 1;
214
215         store_log_markers();
216
217         if (print)
218             fwrite(message, len, 1, stdout);
219     }
220 }
221
222 /*
223  * Logs the given message to stdout while prefixing the current time to it,
224  * but only if verbose mode is activated.
225  *
226  */
227 void verboselog(char *fmt, ...) {
228     va_list args;
229
230     if (!logbuffer && !verbose)
231         return;
232
233     va_start(args, fmt);
234     vlog(verbose, fmt, args);
235     va_end(args);
236 }
237
238 /*
239  * Logs the given message to stdout while prefixing the current time to it.
240  *
241  */
242 void errorlog(char *fmt, ...) {
243     va_list args;
244
245     va_start(args, fmt);
246     vlog(true, fmt, args);
247     va_end(args);
248
249     /* also log to the error logfile, if opened */
250     va_start(args, fmt);
251     vfprintf(errorfile, fmt, args);
252     fflush(errorfile);
253     va_end(args);
254 }
255
256 /*
257  * Logs the given message to stdout while prefixing the current time to it,
258  * but only if debug logging was activated.
259  * This is to be called by DLOG() which includes filename/linenumber
260  *
261  */
262 void debuglog(char *fmt, ...) {
263     va_list args;
264
265     if (!logbuffer && !(debug_logging))
266         return;
267
268     va_start(args, fmt);
269     vlog(debug_logging, fmt, args);
270     va_end(args);
271 }
272
273 /*
274  * Deletes the unused log files. Useful if i3 exits immediately, eg.
275  * because --get-socketpath was called. We don't care for syscall
276  * failures. This function is invoked automatically when exiting.
277  */
278 void purge_zerobyte_logfile(void) {
279     struct stat st;
280     char *slash;
281
282     if (!errorfilename)
283         return;
284
285     /* don't delete the log file if it contains something */
286     if ((stat(errorfilename, &st)) == -1 || st.st_size > 0)
287         return;
288
289     if (unlink(errorfilename) == -1)
290         return;
291
292     if ((slash = strrchr(errorfilename, '/')) != NULL) {
293         *slash = '\0';
294         /* possibly fails with ENOTEMPTY if there are files (or
295          * sockets) left. */
296         rmdir(errorfilename);
297     }
298 }