Merge branch 'logbuf' into next
This commit is contained in:
commit
6610215aaa
4
Makefile
4
Makefile
|
@ -18,7 +18,7 @@ else
|
|||
UNUSED:=$(shell $(MAKE) loglevels.h)
|
||||
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
|
||||
src/%.o: src/%.c ${HEADERS}
|
||||
|
@ -100,7 +100,7 @@ dist: distclean
|
|||
[ ! -e i3-${VERSION}.tar.bz2 ] || rm i3-${VERSION}.tar.bz2
|
||||
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 -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)
|
||||
mkdir i3-${VERSION}/docs
|
||||
# Pre-generate documentation
|
||||
|
|
|
@ -68,6 +68,7 @@ CPPFLAGS += -DPCRE_HAS_UCP=1
|
|||
endif
|
||||
|
||||
LIBS += -lm
|
||||
LIBS += -lrt
|
||||
LIBS += -L $(TOPDIR)/libi3 -li3
|
||||
LIBS += $(call ldflags_for_lib, xcb-event,xcb-event)
|
||||
LIBS += $(call ldflags_for_lib, xcb-keysyms,xcb-keysyms)
|
||||
|
|
60
docs/ipc
60
docs/ipc
|
@ -1,7 +1,7 @@
|
|||
IPC interface (interprocess communication)
|
||||
==========================================
|
||||
Michael Stapelberg <michael+i3@stapelberg.de>
|
||||
October 2011
|
||||
December 2011
|
||||
|
||||
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
|
||||
|
@ -68,6 +68,10 @@ GET_BAR_CONFIG (6)::
|
|||
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
|
||||
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:
|
||||
--------------------------------------------------
|
||||
|
@ -111,18 +115,20 @@ The following reply types are implemented:
|
|||
|
||||
COMMAND (0)::
|
||||
Confirmation/Error code for the COMMAND message.
|
||||
GET_WORKSPACES (1)::
|
||||
WORKSPACES (1)::
|
||||
Reply to the GET_WORKSPACES message.
|
||||
SUBSCRIBE (2)::
|
||||
Confirmation/Error code for the SUBSCRIBE message.
|
||||
GET_OUTPUTS (3)::
|
||||
OUTPUTS (3)::
|
||||
Reply to the GET_OUTPUTS message.
|
||||
GET_TREE (4)::
|
||||
TREE (4)::
|
||||
Reply to the GET_TREE message.
|
||||
GET_MARKS (5)::
|
||||
MARKS (5)::
|
||||
Reply to the GET_MARKS message.
|
||||
GET_BAR_CONFIG (6)::
|
||||
BAR_CONFIG (6)::
|
||||
Reply to the GET_BAR_CONFIG message.
|
||||
LOG_MARKERS (7)::
|
||||
Reply to the GET_LOG_MARKERS message.
|
||||
|
||||
=== COMMAND reply
|
||||
|
||||
|
@ -134,7 +140,7 @@ property is +success (bool)+, but this will be expanded in future versions.
|
|||
{ "success": true }
|
||||
-------------------
|
||||
|
||||
=== GET_WORKSPACES reply
|
||||
=== WORKSPACES reply
|
||||
|
||||
The reply consists of a serialized list of workspaces. Each workspace has the
|
||||
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
|
||||
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
|
||||
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 [].
|
||||
|
||||
=== GET_BAR_CONFIG reply
|
||||
=== BAR_CONFIG reply
|
||||
|
||||
This can be used by third-party workspace bars (especially i3bar, but others
|
||||
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]]
|
||||
|
|
|
@ -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
|
|
@ -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, it’s 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;
|
||||
}
|
|
@ -73,9 +73,11 @@ int main(int argc, char *argv[]) {
|
|||
message_type = I3_IPC_MESSAGE_TYPE_GET_MARKS;
|
||||
else if (strcasecmp(optarg, "get_bar_config") == 0)
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
} else if (o == 'q') {
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
* this before starting any other process, since we set RLIMIT_CORE to
|
||||
* RLIM_INFINITY for i3 debugging versions. */
|
||||
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 int conn_screen;
|
||||
/** The last timestamp we got from X11 (timestamps are included in some events
|
||||
|
|
|
@ -40,6 +40,9 @@
|
|||
/** Request the configuration for a specific 'bar' */
|
||||
#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
|
||||
*
|
||||
|
@ -66,6 +69,9 @@
|
|||
/** Bar config reply type */
|
||||
#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.
|
||||
*
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
|
||||
extern char *loglevels[];
|
||||
extern char *errorfilename;
|
||||
extern char *shmlogname;
|
||||
extern int shmlog_size;
|
||||
|
||||
/**
|
||||
* Initializes logging by creating an error logfile in /tmp (or
|
||||
|
@ -35,6 +37,13 @@ void init_logging();
|
|||
*/
|
||||
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
|
||||
* be printed to stdout. If verbose is set to false, only errors will be
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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
|
||||
${A2M} $<
|
||||
|
@ -9,7 +9,7 @@ i3-wsbar.1: ../i3-wsbar
|
|||
pod2man $^ > $@
|
||||
|
||||
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 \
|
||||
rm -f $${file}.1 $${file}.html $${file}.xml; \
|
||||
done
|
||||
|
|
|
@ -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
|
47
src/ipc.c
47
src/ipc.c
|
@ -612,6 +612,48 @@ IPC_HANDLER(get_bar_config) {
|
|||
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).
|
||||
*
|
||||
|
@ -697,14 +739,15 @@ IPC_HANDLER(subscribe) {
|
|||
|
||||
/* The index of each callback function corresponds to the numeric
|
||||
* value of the message type (see include/i3/ipc.h) */
|
||||
handler_t handlers[7] = {
|
||||
handler_t handlers[8] = {
|
||||
handle_command,
|
||||
handle_get_workspaces,
|
||||
handle_subscribe,
|
||||
handle_get_outputs,
|
||||
handle_tree,
|
||||
handle_get_marks,
|
||||
handle_get_bar_config
|
||||
handle_get_bar_config,
|
||||
handle_get_log_markers
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
159
src/log.c
159
src/log.c
|
@ -15,9 +15,14 @@
|
|||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "util.h"
|
||||
#include "log.h"
|
||||
#include "i3.h"
|
||||
#include "libi3.h"
|
||||
|
||||
/* loglevels.h is autogenerated at make time */
|
||||
#include "loglevels.h"
|
||||
|
@ -27,21 +32,79 @@ static bool verbose = false;
|
|||
static FILE *errorfile;
|
||||
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
|
||||
* XDG_RUNTIME_DIR, see get_process_filename()).
|
||||
*
|
||||
* Will be called twice if --shmlog-size is specified.
|
||||
*
|
||||
*/
|
||||
void init_logging() {
|
||||
errorfilename = get_process_filename("errorlog");
|
||||
if (errorfilename == NULL) {
|
||||
ELOG("Could not initialize errorlog\n");
|
||||
return;
|
||||
if (!errorfilename) {
|
||||
if (!(errorfilename = get_process_filename("errorlog")))
|
||||
ELOG("Could not initialize errorlog\n");
|
||||
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 (fcntl(fileno(errorfile), F_SETFD, FD_CLOEXEC)) {
|
||||
ELOG("Could not set close-on-exec flag\n");
|
||||
/* If this is a debug build (not a release version), we will enable SHM
|
||||
* logging by default, unless the user turned it off explicitly. */
|
||||
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.
|
||||
*
|
||||
*/
|
||||
void vlog(char *fmt, va_list args) {
|
||||
static char timebuf[64];
|
||||
static void vlog(const bool print, const char *fmt, va_list args) {
|
||||
/* 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 time_t t;
|
||||
static struct tm *tmp;
|
||||
static size_t len;
|
||||
|
||||
/* Get current time */
|
||||
time_t t = time(NULL);
|
||||
t = time(NULL);
|
||||
/* Convert time to local time (determined by the locale) */
|
||||
struct tm *tmp = localtime_r(&t, &result);
|
||||
tmp = localtime_r(&t, &result);
|
||||
/* 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
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
printf("%s%d.%d - ", timebuf, tv.tv_sec, tv.tv_usec);
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
printf("%s%d.%d - ", message, tv.tv_sec, tv.tv_usec);
|
||||
#else
|
||||
printf("%s", timebuf);
|
||||
printf("%s", message);
|
||||
#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, ...) {
|
||||
va_list args;
|
||||
|
||||
if (!verbose)
|
||||
if (!logbuffer && !verbose)
|
||||
return;
|
||||
|
||||
va_start(args, fmt);
|
||||
vlog(fmt, args);
|
||||
vlog(verbose, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
|
@ -127,7 +240,7 @@ void errorlog(char *fmt, ...) {
|
|||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
vlog(fmt, args);
|
||||
vlog(true, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
/* also log to the error logfile, if opened */
|
||||
|
@ -146,10 +259,10 @@ void errorlog(char *fmt, ...) {
|
|||
void debuglog(uint64_t lev, char *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
if ((loglevel & lev) == 0)
|
||||
if (!logbuffer && !(loglevel & lev))
|
||||
return;
|
||||
|
||||
va_start(args, fmt);
|
||||
vlog(fmt, args);
|
||||
vlog((loglevel & lev), fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
|
90
src/main.c
90
src/main.c
|
@ -14,6 +14,8 @@
|
|||
#include <sys/un.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include "all.h"
|
||||
|
||||
#include "sd-daemon.h"
|
||||
|
@ -23,6 +25,9 @@
|
|||
* RLIM_INFINITY for i3 debugging versions. */
|
||||
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;
|
||||
|
||||
int xkb_current_group;
|
||||
|
@ -205,6 +210,28 @@ static void i3_exit() {
|
|||
#if EV_VERSION_MAJOR >= 4
|
||||
ev_loop_destroy(main_loop);
|
||||
#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[]) {
|
||||
|
@ -224,6 +251,8 @@ int main(int argc, char *argv[]) {
|
|||
{"force-xinerama", no_argument, 0, 0},
|
||||
{"force_xinerama", 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},
|
||||
{0, 0, 0, 0}
|
||||
|
@ -242,8 +271,21 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
srand(time(NULL));
|
||||
|
||||
/* Init logging *before* initializing debug_build to guarantee early
|
||||
* (file) 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;
|
||||
|
||||
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;
|
||||
} 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) {
|
||||
FREE(layout_path);
|
||||
layout_path = sstrdup(optarg);
|
||||
|
@ -326,6 +376,11 @@ int main(int argc, char *argv[]) {
|
|||
fprintf(stderr, "\t--get-socketpath\n"
|
||||
"\tRetrieve the i3 IPC socket path from X11, print it, then exit.\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"
|
||||
"to send to a currently running i3 (like i3-msg). This allows you to\n"
|
||||
"use nice and logical commands, such as:\n"
|
||||
|
@ -395,13 +450,11 @@ int main(int argc, char *argv[]) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
if ((strchr(I3_VERSION, '(') - I3_VERSION) > 10) {
|
||||
/* Enable logging to handle the case when the user did not specify --shmlog-size */
|
||||
init_logging();
|
||||
|
||||
/* Try to enable core dumps by default when running a debug build */
|
||||
if (debug_build) {
|
||||
struct rlimit limit = { RLIM_INFINITY, RLIM_INFINITY };
|
||||
setrlimit(RLIMIT_CORE, &limit);
|
||||
|
||||
|
@ -662,8 +715,31 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
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)
|
||||
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
|
||||
* while we are sending him a message */
|
||||
|
|
|
@ -199,8 +199,11 @@ void setup_signal_handler() {
|
|||
action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
|
||||
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(SIGFPE, &action, NULL) == -1)
|
||||
sigaction(SIGFPE, &action, NULL) == -1 ||
|
||||
sigaction(SIGSEGV, &action, NULL) == -1)
|
||||
ELOG("Could not setup signal handler");
|
||||
}
|
||||
|
|
|
@ -85,7 +85,8 @@ sub activate_i3 {
|
|||
|
||||
# Construct the command to launch i3. Use maximum debug level, disable
|
||||
# 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:
|
||||
my $outdir = $args{outdir};
|
||||
|
|
Loading…
Reference in New Issue