Merge branch 'logbuf' into next

This commit is contained in:
Michael Stapelberg 2011-12-10 11:35:37 +00:00
commit 6610215aaa
16 changed files with 570 additions and 50 deletions

View File

@ -18,7 +18,7 @@ else
UNUSED:=$(shell $(MAKE) loglevels.h) UNUSED:=$(shell $(MAKE) loglevels.h)
endif endif
SUBDIRS:=i3-msg i3-input i3-nagbar i3-config-wizard i3bar SUBDIRS:=i3-msg i3-input i3-nagbar i3-config-wizard i3bar i3-dump-log
# Depend on the specific file (.c for each .o) and on all headers # Depend on the specific file (.c for each .o) and on all headers
src/%.o: src/%.c ${HEADERS} src/%.o: src/%.c ${HEADERS}
@ -100,7 +100,7 @@ dist: distclean
[ ! -e i3-${VERSION}.tar.bz2 ] || rm i3-${VERSION}.tar.bz2 [ ! -e i3-${VERSION}.tar.bz2 ] || rm i3-${VERSION}.tar.bz2
mkdir i3-${VERSION} mkdir i3-${VERSION}
cp i3-migrate-config-to-v4 i3-sensible-* i3.config.keycodes DEPENDS GOALS LICENSE PACKAGE-MAINTAINER RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome pseudo-doc.doxygen i3-wsbar Makefile i3-${VERSION} cp i3-migrate-config-to-v4 i3-sensible-* i3.config.keycodes DEPENDS GOALS LICENSE PACKAGE-MAINTAINER RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome pseudo-doc.doxygen i3-wsbar Makefile i3-${VERSION}
cp -r src libi3 i3-msg i3-nagbar i3-config-wizard i3bar yajl-fallback include man i3-${VERSION} cp -r src libi3 i3-msg i3-nagbar i3-config-wizard i3bar i3-dump-log yajl-fallback include man i3-${VERSION}
# Only copy toplevel documentation (important stuff) # Only copy toplevel documentation (important stuff)
mkdir i3-${VERSION}/docs mkdir i3-${VERSION}/docs
# Pre-generate documentation # Pre-generate documentation

View File

@ -68,6 +68,7 @@ CPPFLAGS += -DPCRE_HAS_UCP=1
endif endif
LIBS += -lm LIBS += -lm
LIBS += -lrt
LIBS += -L $(TOPDIR)/libi3 -li3 LIBS += -L $(TOPDIR)/libi3 -li3
LIBS += $(call ldflags_for_lib, xcb-event,xcb-event) LIBS += $(call ldflags_for_lib, xcb-event,xcb-event)
LIBS += $(call ldflags_for_lib, xcb-keysyms,xcb-keysyms) LIBS += $(call ldflags_for_lib, xcb-keysyms,xcb-keysyms)

View File

@ -1,7 +1,7 @@
IPC interface (interprocess communication) IPC interface (interprocess communication)
========================================== ==========================================
Michael Stapelberg <michael+i3@stapelberg.de> Michael Stapelberg <michael+i3@stapelberg.de>
October 2011 December 2011
This document describes how to interface with i3 from a separate process. This This document describes how to interface with i3 from a separate process. This
is useful for example to remote-control i3 (to write test cases for example) or is useful for example to remote-control i3 (to write test cases for example) or
@ -68,6 +68,10 @@ GET_BAR_CONFIG (6)::
Gets the configuration (as JSON map) of the workspace bar with the Gets the configuration (as JSON map) of the workspace bar with the
given ID. If no ID is provided, an array with all configured bar IDs is given ID. If no ID is provided, an array with all configured bar IDs is
returned instead. returned instead.
GET_LOG_MARKERS (7)::
Gets the SHM log markers for the current position, the last wrap, the
SHM segment name and segment size. This is necessary for tools like
i3-dump-log which want to display the SHM log.
So, a typical message could look like this: So, a typical message could look like this:
-------------------------------------------------- --------------------------------------------------
@ -111,18 +115,20 @@ The following reply types are implemented:
COMMAND (0):: COMMAND (0)::
Confirmation/Error code for the COMMAND message. Confirmation/Error code for the COMMAND message.
GET_WORKSPACES (1):: WORKSPACES (1)::
Reply to the GET_WORKSPACES message. Reply to the GET_WORKSPACES message.
SUBSCRIBE (2):: SUBSCRIBE (2)::
Confirmation/Error code for the SUBSCRIBE message. Confirmation/Error code for the SUBSCRIBE message.
GET_OUTPUTS (3):: OUTPUTS (3)::
Reply to the GET_OUTPUTS message. Reply to the GET_OUTPUTS message.
GET_TREE (4):: TREE (4)::
Reply to the GET_TREE message. Reply to the GET_TREE message.
GET_MARKS (5):: MARKS (5)::
Reply to the GET_MARKS message. Reply to the GET_MARKS message.
GET_BAR_CONFIG (6):: BAR_CONFIG (6)::
Reply to the GET_BAR_CONFIG message. Reply to the GET_BAR_CONFIG message.
LOG_MARKERS (7)::
Reply to the GET_LOG_MARKERS message.
=== COMMAND reply === COMMAND reply
@ -134,7 +140,7 @@ property is +success (bool)+, but this will be expanded in future versions.
{ "success": true } { "success": true }
------------------- -------------------
=== GET_WORKSPACES reply === WORKSPACES reply
The reply consists of a serialized list of workspaces. Each workspace has the The reply consists of a serialized list of workspaces. Each workspace has the
following properties: following properties:
@ -248,7 +254,7 @@ rect (map)::
] ]
------------------- -------------------
=== GET_TREE reply === TREE reply
The reply consists of a serialized tree. Each node in the tree (representing The reply consists of a serialized tree. Each node in the tree (representing
one container) has at least the properties listed below. While the nodes might one container) has at least the properties listed below. While the nodes might
@ -431,7 +437,7 @@ JSON dump:
} }
------------------------ ------------------------
=== GET_MARKS reply === MARKS reply
The reply consists of a single array of strings for each container that has a The reply consists of a single array of strings for each container that has a
mark. The order of that array is undefined. If more than one container has the mark. The order of that array is undefined. If more than one container has the
@ -440,7 +446,7 @@ contents are not unique).
If no window has a mark the response will be the empty array []. If no window has a mark the response will be the empty array [].
=== GET_BAR_CONFIG reply === BAR_CONFIG reply
This can be used by third-party workspace bars (especially i3bar, but others This can be used by third-party workspace bars (especially i3bar, but others
are free to implement compatible alternatives) to get the +bar+ block are free to implement compatible alternatives) to get the +bar+ block
@ -524,6 +530,40 @@ urgent_workspace_text/urgent_workspace_bar::
} }
-------------- --------------
=== LOG_MARKERS reply
Gets the SHM log markers for the current position, the last wrap, the
SHM segment name and segment size. This is necessary for tools like
i3-dump-log which want to display the SHM log.
The reply is a JSON map with the following entries:
shmname (string)::
The name of the SHM segment, will be of the format +/i3-log-<pid>+.
size (integer)::
The size (in bytes) of the SHM segment. If this is 0, SHM logging is
disabled.
offset_next_write (integer)::
The offset in the SHM segment at which the next write will happen.
Tools should start printing lines from here, since the bytes following
this offset are the oldest log lines. However, the first line might be
garbled, so it makes sense to skip all bytes until the first \0.
offset_last_wrap (integer)::
The offset in the SHM segment at which the last wrap occured. i3 only
stores entire messages in the SHM log, so it might waste a few bytes at
the end to be more efficient. Tools should not print content after the
offset_last_wrap.
*Example*:
-----------------------------
{
"offset_next_write":132839,
"offset_last_wrap":26214400,
"shmname":"/i3-log-3392",
"size":26214400
}
-----------------------------
== Events == Events
[[events]] [[events]]

32
i3-dump-log/Makefile Normal file
View File

@ -0,0 +1,32 @@
# Default value so one can compile i3-dump-log standalone
TOPDIR=..
include $(TOPDIR)/common.mk
CFLAGS += -I$(TOPDIR)/include
# Depend on the object files of all source-files in src/*.c and on all header files
FILES=$(patsubst %.c,%.o,$(wildcard *.c))
HEADERS=$(wildcard *.h)
# Depend on the specific file (.c for each .o) and on all headers
%.o: %.c ${HEADERS}
echo "[i3-dump-log] CC $<"
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
all: i3-dump-log
i3-dump-log: ${FILES}
echo "[i3-dump-log] LINK i3-dump-log"
$(CC) $(LDFLAGS) -o i3-dump-log ${FILES} $(LIBS)
install: all
echo "[i3-dump-log] INSTALL"
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
$(INSTALL) -m 0755 i3-dump-log $(DESTDIR)$(PREFIX)/bin/
clean:
rm -f *.o
distclean: clean
rm -f i3-dump-log

160
i3-dump-log/main.c Normal file
View File

@ -0,0 +1,160 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
*
* i3-dump-log/main.c: Dumps the i3 SHM log to stdout.
*
*/
#include <stdio.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <err.h>
#include <stdint.h>
#include <getopt.h>
#include <limits.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include "libi3.h"
#include <i3/ipc.h>
int main(int argc, char *argv[]) {
char *socket_path = getenv("I3SOCK");
int o, option_index = 0;
int message_type = I3_IPC_MESSAGE_TYPE_GET_LOG_MARKERS;
bool verbose = false;
static struct option long_options[] = {
{"socket", required_argument, 0, 's'},
{"version", no_argument, 0, 'v'},
{"verbose", no_argument, 0, 'V'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
char *options_string = "s:vVh";
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
if (o == 's') {
if (socket_path != NULL)
free(socket_path);
socket_path = sstrdup(optarg);
} else if (o == 'v') {
printf("i3-dump-log " I3_VERSION "\n");
return 0;
} else if (o == 'V') {
verbose = true;
} else if (o == 'h') {
printf("i3-dump-log " I3_VERSION "\n");
printf("i3-dump-log [-s <socket>]\n");
return 0;
}
}
if (socket_path == NULL)
socket_path = socket_path_from_x11();
/* Fall back to the default socket path */
if (socket_path == NULL)
socket_path = sstrdup("/tmp/i3-ipc.sock");
int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (sockfd == -1)
err(EXIT_FAILURE, "Could not create socket");
struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_LOCAL;
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
err(EXIT_FAILURE, "Could not connect to i3");
if (ipc_send_message(sockfd, 0, message_type, NULL) == -1)
err(EXIT_FAILURE, "IPC: write()");
uint32_t reply_length;
uint8_t *reply;
int ret;
if ((ret = ipc_recv_message(sockfd, message_type, &reply_length, &reply)) != 0) {
if (ret == -1)
err(EXIT_FAILURE, "IPC: read()");
exit(1);
}
char *buffer = NULL;
sasprintf(&buffer, "%.*s", reply_length, reply);
/* The reply will look like this:
* {"offset_next_write":1729,"offset_last_wrap":1996,"size":2048,"shmname":"/i3-log-399"}
* IMO, its not worth linking a JSON parser in just for this. If the
* structure changes in the future, this decision needs to be re-evaluated
* :). */
int offset_next_write, offset_last_wrap, logbuffer_size;
char *next_write_str = strstr(buffer, "offset_next_write"),
*last_wrap_str = strstr(buffer, "offset_last_wrap"),
*size_str = strstr(buffer, "size"),
*shmname = strstr(buffer, "shmname");
if (!next_write_str ||
!last_wrap_str ||
!size_str ||
!shmname ||
sscanf(next_write_str, "offset_next_write\":%d", &offset_next_write) != 1 ||
sscanf(last_wrap_str, "offset_last_wrap\":%d", &offset_last_wrap) != 1 ||
sscanf(size_str, "size\":%d", &logbuffer_size) != 1)
errx(EXIT_FAILURE, "invalid IPC reply: %s\n", buffer);
shmname += strlen("shmname\":\"");
char *quote = strchr(shmname, '"');
if (!quote)
errx(EXIT_FAILURE, "invalid IPC reply: %s\n", buffer);
*quote = '\0';
if (verbose)
printf("next_write = %d, last_wrap = %d, logbuffer_size = %d, shmname = %s\n",
offset_next_write, offset_last_wrap, logbuffer_size, shmname);
if (*shmname == '\0')
errx(EXIT_FAILURE, "Cannot dump log: SHM logging is disabled in i3.");
int logbuffer_shm = shm_open(shmname, O_RDONLY, 0);
if (logbuffer_shm == -1)
err(EXIT_FAILURE, "Could not shm_open SHM segment for the i3 log (%s)", shmname);
char *logbuffer = mmap(NULL, logbuffer_size, PROT_READ, MAP_SHARED, logbuffer_shm, 0);
if (logbuffer == MAP_FAILED)
err(EXIT_FAILURE, "Could not mmap SHM segment for the i3 log");
int chars;
char *walk = logbuffer + offset_next_write;
/* Skip the first line, it very likely is mangled. Not a problem, though,
* the log is chatty enough to have plenty lines left. */
while (*walk != '\0')
walk++;
/* Print the oldest log lines. We use printf("%s") to stop on \0. */
while (walk < (logbuffer + offset_last_wrap)) {
chars = printf("%s", walk);
/* Shortcut: If there are two consecutive \0 bytes, this part of the
* buffer was never touched. To not call printf() for every byte of the
* buffer, we directly exit the loop. */
if (*walk == '\0' && *(walk+1) == '\0')
break;
walk += (chars > 0 ? chars : 1);
}
/* Then start from the beginning and print the newer lines */
walk = logbuffer;
while (walk < (logbuffer + offset_next_write)) {
chars = printf("%s", walk);
walk += (chars > 0 ? chars : 1);
}
return 0;
}

View File

@ -73,9 +73,11 @@ int main(int argc, char *argv[]) {
message_type = I3_IPC_MESSAGE_TYPE_GET_MARKS; message_type = I3_IPC_MESSAGE_TYPE_GET_MARKS;
else if (strcasecmp(optarg, "get_bar_config") == 0) else if (strcasecmp(optarg, "get_bar_config") == 0)
message_type = I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG; message_type = I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG;
else if (strcasecmp(optarg, "get_log_markers") == 0)
message_type = I3_IPC_MESSAGE_TYPE_GET_LOG_MARKERS;
else { else {
printf("Unknown message type\n"); printf("Unknown message type\n");
printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config\n"); printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_log_markers\n");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
} else if (o == 'q') { } else if (o == 'q') {

View File

@ -28,6 +28,8 @@
* this before starting any other process, since we set RLIMIT_CORE to * this before starting any other process, since we set RLIMIT_CORE to
* RLIM_INFINITY for i3 debugging versions. */ * RLIM_INFINITY for i3 debugging versions. */
extern struct rlimit original_rlimit_core; extern struct rlimit original_rlimit_core;
/** Whether this version of i3 is a debug build or a release build. */
extern bool debug_build;
extern xcb_connection_t *conn; extern xcb_connection_t *conn;
extern int conn_screen; extern int conn_screen;
/** The last timestamp we got from X11 (timestamps are included in some events /** The last timestamp we got from X11 (timestamps are included in some events

View File

@ -40,6 +40,9 @@
/** Request the configuration for a specific 'bar' */ /** Request the configuration for a specific 'bar' */
#define I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG 6 #define I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG 6
/** Request the SHM debug log start/wrap markers */
#define I3_IPC_MESSAGE_TYPE_GET_LOG_MARKERS 7
/* /*
* Messages from i3 to clients * Messages from i3 to clients
* *
@ -66,6 +69,9 @@
/** Bar config reply type */ /** Bar config reply type */
#define I3_IPC_REPLY_TYPE_BAR_CONFIG 6 #define I3_IPC_REPLY_TYPE_BAR_CONFIG 6
/** Request the SHM debug log start/wrap markers */
#define I3_IPC_REPLY_TYPE_LOG_MARKERS 7
/* /*
* Events from i3 to clients. Events have the first bit set high. * Events from i3 to clients. Events have the first bit set high.
* *

View File

@ -21,6 +21,8 @@
extern char *loglevels[]; extern char *loglevels[];
extern char *errorfilename; extern char *errorfilename;
extern char *shmlogname;
extern int shmlog_size;
/** /**
* Initializes logging by creating an error logfile in /tmp (or * Initializes logging by creating an error logfile in /tmp (or
@ -35,6 +37,13 @@ void init_logging();
*/ */
void add_loglevel(const char *level); void add_loglevel(const char *level);
/**
* Returns the offsets for the next write and for the last wrap.
* Necessary to print the i3 SHM log in the correct order.
*
*/
void get_log_markers(int *offset_next_write, int *offset_last_wrap, int *size);
/** /**
* Set verbosity of i3. If verbose is set to true, informative messages will * Set verbosity of i3. If verbose is set to true, informative messages will
* be printed to stdout. If verbose is set to false, only errors will be * be printed to stdout. If verbose is set to false, only errors will be

View File

@ -1,6 +1,6 @@
A2M:=a2x -f manpage --asciidoc-opts="-f asciidoc.conf" A2M:=a2x -f manpage --asciidoc-opts="-f asciidoc.conf"
all: i3.1 i3-msg.1 i3-input.1 i3-nagbar.1 i3-wsbar.1 i3-config-wizard.1 i3-migrate-config-to-v4.1 i3-sensible-editor.1 i3-sensible-pager.1 i3-sensible-terminal.1 all: i3.1 i3-msg.1 i3-input.1 i3-nagbar.1 i3-wsbar.1 i3-config-wizard.1 i3-migrate-config-to-v4.1 i3-sensible-editor.1 i3-sensible-pager.1 i3-sensible-terminal.1 i3-dump-log.1
%.1: %.man asciidoc.conf %.1: %.man asciidoc.conf
${A2M} $< ${A2M} $<
@ -9,7 +9,7 @@ i3-wsbar.1: ../i3-wsbar
pod2man $^ > $@ pod2man $^ > $@
clean: clean:
for file in $$(echo i3 i3-msg i3-input i3-nagbar i3-wsbar i3-config-wizard i3-migrate-config-to-v4 i3-sensible-editor i3-sensible-pager i3-sensible-terminal); \ for file in $$(echo i3 i3-msg i3-input i3-nagbar i3-wsbar i3-config-wizard i3-migrate-config-to-v4 i3-sensible-editor i3-sensible-pager i3-sensible-terminal i3-dump-log); \
do \ do \
rm -f $${file}.1 $${file}.html $${file}.xml; \ rm -f $${file}.1 $${file}.html $${file}.xml; \
done done

32
man/i3-dump-log.man Normal file
View File

@ -0,0 +1,32 @@
i3-dump-log(1)
==============
Michael Stapelberg <michael+i3@stapelberg.de>
v4.1, December 2011
== NAME
i3-dump-log - dumps the i3 SHM log
== SYNOPSIS
i3-dump-log [-s <socketpath>]
== DESCRIPTION
Debug versions of i3 automatically use 1% of your RAM (but 25 MiB max) to store
full debug loglevel log output. This is extremely helpful for bugreports and
figuring out what is going on, without permanently logging to a file.
With i3-dump-log, you can dump the SHM log to stdout.
== EXAMPLE
i3-dump-log | gzip -9 > /tmp/i3-log.gz
== SEE ALSO
i3(1)
== AUTHOR
Michael Stapelberg and contributors

View File

@ -612,6 +612,48 @@ IPC_HANDLER(get_bar_config) {
y(free); y(free);
} }
/*
* Formats the reply message for a GET_LOG_MARKERS request and sends it to the
* client.
*
*/
IPC_HANDLER(get_log_markers) {
#if YAJL_MAJOR >= 2
yajl_gen gen = yajl_gen_alloc(NULL);
#else
yajl_gen gen = yajl_gen_alloc(NULL, NULL);
#endif
int offset_next_write, offset_last_wrap, logsize;
get_log_markers(&offset_next_write, &offset_last_wrap, &logsize);
y(map_open);
ystr("offset_next_write");
y(integer, offset_next_write);
ystr("offset_last_wrap");
y(integer, offset_last_wrap);
ystr("shmname");
ystr(shmlogname);
ystr("size");
y(integer, logsize);
y(map_close);
const unsigned char *payload;
#if YAJL_MAJOR >= 2
size_t length;
#else
unsigned int length;
#endif
y(get_buf, &payload, &length);
ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_LOG_MARKERS, payload);
y(free);
}
/* /*
* Callback for the YAJL parser (will be called when a string is parsed). * Callback for the YAJL parser (will be called when a string is parsed).
* *
@ -697,14 +739,15 @@ IPC_HANDLER(subscribe) {
/* The index of each callback function corresponds to the numeric /* The index of each callback function corresponds to the numeric
* value of the message type (see include/i3/ipc.h) */ * value of the message type (see include/i3/ipc.h) */
handler_t handlers[7] = { handler_t handlers[8] = {
handle_command, handle_command,
handle_get_workspaces, handle_get_workspaces,
handle_subscribe, handle_subscribe,
handle_get_outputs, handle_get_outputs,
handle_tree, handle_tree,
handle_get_marks, handle_get_marks,
handle_get_bar_config handle_get_bar_config,
handle_get_log_markers
}; };
/* /*

159
src/log.c
View File

@ -15,9 +15,14 @@
#include <sys/time.h> #include <sys/time.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>
#include "util.h" #include "util.h"
#include "log.h" #include "log.h"
#include "i3.h"
#include "libi3.h"
/* loglevels.h is autogenerated at make time */ /* loglevels.h is autogenerated at make time */
#include "loglevels.h" #include "loglevels.h"
@ -27,21 +32,79 @@ static bool verbose = false;
static FILE *errorfile; static FILE *errorfile;
char *errorfilename; char *errorfilename;
/* SHM logging variables */
/* The name for the SHM (/i3-log-%pid). Will end up on /dev/shm on most
* systems. Global so that we can clean up at exit. */
char *shmlogname = "";
/* Size limit for the SHM log, by default 25 MiB. Can be overwritten using the
* flag --shmlog-size. */
int shmlog_size = 0;
/* If enabled, logbuffer will point to a memory mapping of the i3 SHM log. */
static char *logbuffer;
/* A pointer (within logbuffer) where data will be written to next. */
static char *logwalk;
/* A pointer to the byte where we last wrapped. Necessary to not print the
* left-overs at the end of the ringbuffer. */
static char *loglastwrap;
/* Size (in bytes) of the i3 SHM log. */
static int logbuffer_size;
/* File descriptor for shm_open. */
static int logbuffer_shm;
/* /*
* Initializes logging by creating an error logfile in /tmp (or * Initializes logging by creating an error logfile in /tmp (or
* XDG_RUNTIME_DIR, see get_process_filename()). * XDG_RUNTIME_DIR, see get_process_filename()).
* *
* Will be called twice if --shmlog-size is specified.
*
*/ */
void init_logging() { void init_logging() {
errorfilename = get_process_filename("errorlog"); if (!errorfilename) {
if (errorfilename == NULL) { if (!(errorfilename = get_process_filename("errorlog")))
ELOG("Could not initialize errorlog\n"); ELOG("Could not initialize errorlog\n");
return; else {
errorfile = fopen(errorfilename, "w");
if (fcntl(fileno(errorfile), F_SETFD, FD_CLOEXEC)) {
ELOG("Could not set close-on-exec flag\n");
}
}
} }
errorfile = fopen(errorfilename, "w"); /* If this is a debug build (not a release version), we will enable SHM
if (fcntl(fileno(errorfile), F_SETFD, FD_CLOEXEC)) { * logging by default, unless the user turned it off explicitly. */
ELOG("Could not set close-on-exec flag\n"); if (logbuffer == NULL && shmlog_size > 0) {
/* Reserve 1% of the RAM for the logfile, but at max 25 MiB.
* For 512 MiB of RAM this will lead to a 5 MiB log buffer.
* At the moment (2011-12-10), no testcase leads to an i3 log
* of more than ~ 600 KiB. */
long long physical_mem_bytes = (long long)sysconf(_SC_PHYS_PAGES) *
sysconf(_SC_PAGESIZE);
logbuffer_size = min(physical_mem_bytes * 0.01, shmlog_size);
sasprintf(&shmlogname, "/i3-log-%d", getpid());
logbuffer_shm = shm_open(shmlogname, O_RDWR | O_CREAT | O_TRUNC, S_IREAD | S_IWRITE);
if (logbuffer_shm == -1) {
ELOG("Could not shm_open SHM segment for the i3 log: %s\n", strerror(errno));
return;
}
if (ftruncate(logbuffer_shm, logbuffer_size) == -1) {
close(logbuffer_shm);
shm_unlink("/i3-log-");
ELOG("Could not ftruncate SHM segment for the i3 log: %s\n", strerror(errno));
return;
}
logbuffer = mmap(NULL, logbuffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, logbuffer_shm, 0);
if (logbuffer == MAP_FAILED) {
close(logbuffer_shm);
shm_unlink("/i3-log-");
ELOG("Could not mmap SHM segment for the i3 log: %s\n", strerror(errno));
logbuffer = NULL;
return;
}
logwalk = logbuffer;
loglastwrap = logbuffer + logbuffer_size;
} }
} }
@ -79,28 +142,78 @@ void add_loglevel(const char *level) {
} }
/* /*
* Logs the given message to stdout while prefixing the current time to it. * Returns the offsets for the next write and for the last wrap.
* Necessary to print the i3 SHM log in the correct order.
*
*/
void get_log_markers(int *offset_next_write, int *offset_last_wrap, int *size) {
*offset_next_write = (logwalk - logbuffer);
*offset_last_wrap = (loglastwrap - logbuffer);
*size = logbuffer_size;
}
/*
* Logs the given message to stdout (if print is true) while prefixing the
* current time to it. Additionally, the message will be saved in the i3 SHM
* log if enabled.
* This is to be called by *LOG() which includes filename/linenumber/function. * This is to be called by *LOG() which includes filename/linenumber/function.
* *
*/ */
void vlog(char *fmt, va_list args) { static void vlog(const bool print, const char *fmt, va_list args) {
static char timebuf[64]; /* Precisely one page to not consume too much memory but to hold enough
* data to be useful. */
static char message[4096];
static struct tm result; static struct tm result;
static time_t t;
static struct tm *tmp;
static size_t len;
/* Get current time */ /* Get current time */
time_t t = time(NULL); t = time(NULL);
/* Convert time to local time (determined by the locale) */ /* Convert time to local time (determined by the locale) */
struct tm *tmp = localtime_r(&t, &result); tmp = localtime_r(&t, &result);
/* Generate time prefix */ /* Generate time prefix */
strftime(timebuf, sizeof(timebuf), "%x %X - ", tmp); len = strftime(message, sizeof(message), "%x %X - ", tmp);
/*
* logbuffer print
* ----------------
* true true format message, save, print
* true false format message, save
* false true print message only
* false false INVALID, never called
*/
if (!logbuffer) {
#ifdef DEBUG_TIMING #ifdef DEBUG_TIMING
struct timeval tv; struct timeval tv;
gettimeofday(&tv, NULL); gettimeofday(&tv, NULL);
printf("%s%d.%d - ", timebuf, tv.tv_sec, tv.tv_usec); printf("%s%d.%d - ", message, tv.tv_sec, tv.tv_usec);
#else #else
printf("%s", timebuf); printf("%s", message);
#endif #endif
vprintf(fmt, args); vprintf(fmt, args);
} else {
len += vsnprintf(message + len, sizeof(message) - len, fmt, args);
if (len == sizeof(message)) {
fprintf(stderr, "BUG: single log message > 4k\n");
}
/* If there is no space for the current message (plus trailing
* nullbyte) in the ringbuffer, we need to wrap and write to the
* beginning again. */
if ((len+1) >= (logbuffer_size - (logwalk - logbuffer))) {
loglastwrap = logwalk;
logwalk = logbuffer;
}
/* Copy the buffer, terminate it, move the write pointer to the byte after
* our current message. */
strncpy(logwalk, message, len);
logwalk[len] = '\0';
logwalk += len + 1;
if (print)
fwrite(message, len, 1, stdout);
}
} }
/* /*
@ -111,11 +224,11 @@ void vlog(char *fmt, va_list args) {
void verboselog(char *fmt, ...) { void verboselog(char *fmt, ...) {
va_list args; va_list args;
if (!verbose) if (!logbuffer && !verbose)
return; return;
va_start(args, fmt); va_start(args, fmt);
vlog(fmt, args); vlog(verbose, fmt, args);
va_end(args); va_end(args);
} }
@ -127,7 +240,7 @@ void errorlog(char *fmt, ...) {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
vlog(fmt, args); vlog(true, fmt, args);
va_end(args); va_end(args);
/* also log to the error logfile, if opened */ /* also log to the error logfile, if opened */
@ -146,10 +259,10 @@ void errorlog(char *fmt, ...) {
void debuglog(uint64_t lev, char *fmt, ...) { void debuglog(uint64_t lev, char *fmt, ...) {
va_list args; va_list args;
if ((loglevel & lev) == 0) if (!logbuffer && !(loglevel & lev))
return; return;
va_start(args, fmt); va_start(args, fmt);
vlog(fmt, args); vlog((loglevel & lev), fmt, args);
va_end(args); va_end(args);
} }

View File

@ -14,6 +14,8 @@
#include <sys/un.h> #include <sys/un.h>
#include <sys/time.h> #include <sys/time.h>
#include <sys/resource.h> #include <sys/resource.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include "all.h" #include "all.h"
#include "sd-daemon.h" #include "sd-daemon.h"
@ -23,6 +25,9 @@
* RLIM_INFINITY for i3 debugging versions. */ * RLIM_INFINITY for i3 debugging versions. */
struct rlimit original_rlimit_core; struct rlimit original_rlimit_core;
/* Whether this version of i3 is a debug build or a release build. */
bool debug_build = false;
static int xkb_event_base; static int xkb_event_base;
int xkb_current_group; int xkb_current_group;
@ -205,6 +210,28 @@ static void i3_exit() {
#if EV_VERSION_MAJOR >= 4 #if EV_VERSION_MAJOR >= 4
ev_loop_destroy(main_loop); ev_loop_destroy(main_loop);
#endif #endif
if (*shmlogname != '\0') {
fprintf(stderr, "Closing SHM log \"%s\"\n", shmlogname);
fflush(stderr);
shm_unlink(shmlogname);
}
}
/*
* (One-shot) Handler for all signals with default action "Term", see signal(7)
*
* Unlinks the SHM log and re-raises the signal.
*
*/
static void handle_signal(int sig, siginfo_t *info, void *data) {
fprintf(stderr, "Received signal %d, terminating\n", sig);
if (*shmlogname != '\0') {
fprintf(stderr, "Closing SHM log \"%s\"\n", shmlogname);
shm_unlink(shmlogname);
}
fflush(stderr);
raise(sig);
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
@ -224,6 +251,8 @@ int main(int argc, char *argv[]) {
{"force-xinerama", no_argument, 0, 0}, {"force-xinerama", no_argument, 0, 0},
{"force_xinerama", no_argument, 0, 0}, {"force_xinerama", no_argument, 0, 0},
{"disable-signalhandler", no_argument, 0, 0}, {"disable-signalhandler", no_argument, 0, 0},
{"shmlog-size", required_argument, 0, 0},
{"shmlog_size", required_argument, 0, 0},
{"get-socketpath", no_argument, 0, 0}, {"get-socketpath", no_argument, 0, 0},
{"get_socketpath", no_argument, 0, 0}, {"get_socketpath", no_argument, 0, 0},
{0, 0, 0, 0} {0, 0, 0, 0}
@ -242,8 +271,21 @@ int main(int argc, char *argv[]) {
srand(time(NULL)); srand(time(NULL));
/* Init logging *before* initializing debug_build to guarantee early
* (file) logging. */
init_logging(); init_logging();
/* I3_VERSION contains either something like this:
* "4.0.2 (2011-11-11, branch "release")".
* or: "4.0.2-123-gCOFFEEBABE (2011-11-11, branch "next")".
*
* So we check for the offset of the first opening round bracket to
* determine whether this is a git version or a release version. */
debug_build = ((strchr(I3_VERSION, '(') - I3_VERSION) > 10);
/* On non-release builds, disable SHM logging by default. */
shmlog_size = (debug_build ? 25 * 1024 * 1024 : 0);
start_argv = argv; start_argv = argv;
while ((opt = getopt_long(argc, argv, "c:CvaL:hld:V", long_options, &option_index)) != -1) { while ((opt = getopt_long(argc, argv, "c:CvaL:hld:V", long_options, &option_index)) != -1) {
@ -300,6 +342,14 @@ int main(int argc, char *argv[]) {
} }
return 1; return 1;
} else if (strcmp(long_options[option_index].name, "shmlog-size") == 0 ||
strcmp(long_options[option_index].name, "shmlog_size") == 0) {
shmlog_size = atoi(optarg);
/* Re-initialize logging immediately to get as many
* logmessages as possible into the SHM log. */
init_logging();
LOG("Limiting SHM log size to %d bytes\n", shmlog_size);
break;
} else if (strcmp(long_options[option_index].name, "restart") == 0) { } else if (strcmp(long_options[option_index].name, "restart") == 0) {
FREE(layout_path); FREE(layout_path);
layout_path = sstrdup(optarg); layout_path = sstrdup(optarg);
@ -326,6 +376,11 @@ int main(int argc, char *argv[]) {
fprintf(stderr, "\t--get-socketpath\n" fprintf(stderr, "\t--get-socketpath\n"
"\tRetrieve the i3 IPC socket path from X11, print it, then exit.\n"); "\tRetrieve the i3 IPC socket path from X11, print it, then exit.\n");
fprintf(stderr, "\n"); fprintf(stderr, "\n");
fprintf(stderr, "\t--shmlog-size <limit>\n"
"\tLimits the size of the i3 SHM log to <limit> bytes. Setting this\n"
"\tto 0 disables SHM logging entirely.\n"
"\tThe default is %d bytes.\n", shmlog_size);
fprintf(stderr, "\n");
fprintf(stderr, "If you pass plain text arguments, i3 will interpret them as a command\n" fprintf(stderr, "If you pass plain text arguments, i3 will interpret them as a command\n"
"to send to a currently running i3 (like i3-msg). This allows you to\n" "to send to a currently running i3 (like i3-msg). This allows you to\n"
"use nice and logical commands, such as:\n" "use nice and logical commands, such as:\n"
@ -395,13 +450,11 @@ int main(int argc, char *argv[]) {
return 0; return 0;
} }
/* I3_VERSION contains either something like this: /* Enable logging to handle the case when the user did not specify --shmlog-size */
* "4.0.2 (2011-11-11, branch "release")". init_logging();
* or: "4.0.2-123-gCOFFEEBABE (2011-11-11, branch "next")".
* /* Try to enable core dumps by default when running a debug build */
* So we check for the offset of the first opening round bracket to if (debug_build) {
* determine whether this is a git version or a release version. */
if ((strchr(I3_VERSION, '(') - I3_VERSION) > 10) {
struct rlimit limit = { RLIM_INFINITY, RLIM_INFINITY }; struct rlimit limit = { RLIM_INFINITY, RLIM_INFINITY };
setrlimit(RLIMIT_CORE, &limit); setrlimit(RLIMIT_CORE, &limit);
@ -662,8 +715,31 @@ int main(int argc, char *argv[]) {
manage_existing_windows(root); manage_existing_windows(root);
struct sigaction action;
action.sa_sigaction = handle_signal;
action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
sigemptyset(&action.sa_mask);
if (!disable_signalhandler) if (!disable_signalhandler)
setup_signal_handler(); setup_signal_handler();
else {
/* Catch all signals with default action "Core", see signal(7) */
if (sigaction(SIGQUIT, &action, NULL) == -1 ||
sigaction(SIGILL, &action, NULL) == -1 ||
sigaction(SIGABRT, &action, NULL) == -1 ||
sigaction(SIGFPE, &action, NULL) == -1 ||
sigaction(SIGSEGV, &action, NULL) == -1)
ELOG("Could not setup signal handler");
}
/* Catch all signals with default action "Term", see signal(7) */
if (sigaction(SIGHUP, &action, NULL) == -1 ||
sigaction(SIGINT, &action, NULL) == -1 ||
sigaction(SIGALRM, &action, NULL) == -1 ||
sigaction(SIGUSR1, &action, NULL) == -1 ||
sigaction(SIGUSR2, &action, NULL) == -1)
ELOG("Could not setup signal handler");
/* Ignore SIGPIPE to survive errors when an IPC client disconnects /* Ignore SIGPIPE to survive errors when an IPC client disconnects
* while we are sending him a message */ * while we are sending him a message */

View File

@ -199,8 +199,11 @@ void setup_signal_handler() {
action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO; action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
sigemptyset(&action.sa_mask); sigemptyset(&action.sa_mask);
if (sigaction(SIGSEGV, &action, NULL) == -1 || /* Catch all signals with default action "Core", see signal(7) */
if (sigaction(SIGQUIT, &action, NULL) == -1 ||
sigaction(SIGILL, &action, NULL) == -1 ||
sigaction(SIGABRT, &action, NULL) == -1 || sigaction(SIGABRT, &action, NULL) == -1 ||
sigaction(SIGFPE, &action, NULL) == -1) sigaction(SIGFPE, &action, NULL) == -1 ||
sigaction(SIGSEGV, &action, NULL) == -1)
ELOG("Could not setup signal handler"); ELOG("Could not setup signal handler");
} }

View File

@ -85,7 +85,8 @@ sub activate_i3 {
# Construct the command to launch i3. Use maximum debug level, disable # Construct the command to launch i3. Use maximum debug level, disable
# the interactive signalhandler to make it crash immediately instead. # the interactive signalhandler to make it crash immediately instead.
my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler"; # Also disable logging to SHM since we want to redirect the logs anyways.
my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler --shmlog-size=0";
# For convenience: # For convenience:
my $outdir = $args{outdir}; my $outdir = $args{outdir};