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