i3
sighandler.c
Go to the documentation of this file.
1 /*
2  * vim:ts=4:sw=4:expandtab
3  *
4  * i3 - an improved dynamic tiling window manager
5  * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
6  *
7  */
8 #include "all.h"
9 
10 #include <ev.h>
11 #include <iconv.h>
12 #include <signal.h>
13 #include <sys/wait.h>
14 
15 #include <xcb/xcb_event.h>
16 
17 #include <X11/keysym.h>
18 
19 typedef struct dialog_t {
20  xcb_window_t id;
21  xcb_colormap_t colormap;
24 
27 } dialog_t;
28 
30 static int raised_signal;
31 static int backtrace_done = 0;
32 
33 static int sighandler_backtrace(void);
34 static void sighandler_setup(void);
35 static void sighandler_create_dialogs(void);
36 static void sighandler_destroy_dialogs(void);
37 static void sighandler_handle_expose(void);
38 static void sighandler_draw_dialog(dialog_t *dialog);
39 static void sighandler_handle_key_press(xcb_key_press_event_t *event);
40 
41 static i3String *message_intro;
42 static i3String *message_intro2;
43 static i3String *message_option_backtrace;
44 static i3String *message_option_restart;
45 static i3String *message_option_forget;
46 static int dialog_width;
47 static int dialog_height;
48 
49 static int border_width = 2;
50 static int margin = 4;
51 
52 /*
53  * Attach gdb to pid_parent and dump a backtrace to i3-backtrace.$pid in the
54  * tmpdir
55  */
56 static int sighandler_backtrace(void) {
57  char *tmpdir = getenv("TMPDIR");
58  if (tmpdir == NULL)
59  tmpdir = "/tmp";
60 
61  pid_t pid_parent = getpid();
62 
63  char *filename = NULL;
64  int suffix = 0;
65  struct stat bt;
66  /* Find a unique filename for the backtrace (since the PID of i3 stays the
67  * same), so that we don’t overwrite earlier backtraces. */
68  do {
69  FREE(filename);
70  sasprintf(&filename, "%s/i3-backtrace.%d.%d.txt", tmpdir, pid_parent, suffix);
71  suffix++;
72  } while (stat(filename, &bt) == 0);
73 
74  pid_t pid_gdb = fork();
75  if (pid_gdb < 0) {
76  DLOG("Failed to fork for GDB\n");
77  return -1;
78  } else if (pid_gdb == 0) {
79  /* child */
80  int stdin_pipe[2],
81  stdout_pipe[2];
82 
83  if (pipe(stdin_pipe) == -1) {
84  ELOG("Failed to init stdin_pipe\n");
85  return -1;
86  }
87  if (pipe(stdout_pipe) == -1) {
88  ELOG("Failed to init stdout_pipe\n");
89  return -1;
90  }
91 
92  /* close standard streams in case i3 is started from a terminal; gdb
93  * needs to run without controlling terminal for it to work properly in
94  * this situation */
95  close(STDIN_FILENO);
96  close(STDOUT_FILENO);
97  close(STDERR_FILENO);
98 
99  /* We provide pipe file descriptors for stdin/stdout because gdb < 7.5
100  * crashes otherwise, see
101  * https://sourceware.org/bugzilla/show_bug.cgi?id=14114 */
102  dup2(stdin_pipe[0], STDIN_FILENO);
103  dup2(stdout_pipe[1], STDOUT_FILENO);
104 
105  char *pid_s, *gdb_log_cmd;
106  sasprintf(&pid_s, "%d", pid_parent);
107  sasprintf(&gdb_log_cmd, "set logging file %s", filename);
108 
109  char *args[] = {
110  "gdb",
111  start_argv[0],
112  "-p",
113  pid_s,
114  "-batch",
115  "-nx",
116  "-ex", gdb_log_cmd,
117  "-ex", "set logging on",
118  "-ex", "bt full",
119  "-ex", "quit",
120  NULL};
121  execvp(args[0], args);
122  DLOG("Failed to exec GDB\n");
123  exit(1);
124  }
125  int status = 0;
126 
127  waitpid(pid_gdb, &status, 0);
128 
129  /* see if the backtrace was successful or not */
130  if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
131  DLOG("GDB did not run properly\n");
132  return -1;
133  } else if (stat(filename, &bt) == -1) {
134  DLOG("GDB executed successfully, but no backtrace was generated\n");
135  return -1;
136  }
137  return 1;
138 }
139 
140 static void sighandler_setup(void) {
141  border_width = logical_px(border_width);
142  margin = logical_px(margin);
143 
144  int num_lines = 5;
145  message_intro = i3string_from_utf8("i3 has just crashed. Please report a bug for this.");
146  message_intro2 = i3string_from_utf8("To debug this problem, you can either attach gdb or choose from the following options:");
147  message_option_backtrace = i3string_from_utf8("- 'b' to save a backtrace (requires gdb)");
148  message_option_restart = i3string_from_utf8("- 'r' to restart i3 in-place");
149  message_option_forget = i3string_from_utf8("- 'f' to forget the previous layout and restart i3");
150 
151  int width_longest_message = predict_text_width(message_intro2);
152 
153  dialog_width = width_longest_message + 2 * border_width + 2 * margin;
154  dialog_height = num_lines * config.font.height + 2 * border_width + 2 * margin;
155 }
156 
157 static void sighandler_create_dialogs(void) {
158  Output *output;
159  TAILQ_FOREACH(output, &outputs, outputs) {
160  if (!output->active) {
161  continue;
162  }
163 
164  dialog_t *dialog = scalloc(1, sizeof(struct dialog_t));
165  TAILQ_INSERT_TAIL(&dialogs, dialog, dialogs);
166 
167  xcb_visualid_t visual = get_visualid_by_depth(root_depth);
168  dialog->colormap = xcb_generate_id(conn);
169  xcb_create_colormap(conn, XCB_COLORMAP_ALLOC_NONE, dialog->colormap, root, visual);
170 
171  uint32_t mask = 0;
172  uint32_t values[4];
173  int i = 0;
174 
175  /* Needs to be set in the case of a 32-bit root depth. */
176  mask |= XCB_CW_BACK_PIXEL;
177  values[i++] = root_screen->black_pixel;
178 
179  /* Needs to be set in the case of a 32-bit root depth. */
180  mask |= XCB_CW_BORDER_PIXEL;
181  values[i++] = root_screen->black_pixel;
182 
183  mask |= XCB_CW_OVERRIDE_REDIRECT;
184  values[i++] = 1;
185 
186  /* Needs to be set in the case of a 32-bit root depth. */
187  mask |= XCB_CW_COLORMAP;
188  values[i++] = dialog->colormap;
189 
190  dialog->dims.x = output->rect.x + (output->rect.width / 2);
191  dialog->dims.y = output->rect.y + (output->rect.height / 2);
192  dialog->dims.width = dialog_width;
193  dialog->dims.height = dialog_height;
194 
195  /* Make sure the dialog is centered. */
196  dialog->dims.x -= dialog->dims.width / 2;
197  dialog->dims.y -= dialog->dims.height / 2;
198 
199  dialog->id = create_window(conn, dialog->dims, root_depth, visual,
200  XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_POINTER,
201  true, mask, values);
202 
203  draw_util_surface_init(conn, &(dialog->surface), dialog->id, get_visualtype_by_id(visual),
204  dialog->dims.width, dialog->dims.height);
205 
206  xcb_grab_keyboard(conn, false, dialog->id, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
207 
208  /* Confine the pointer to the crash dialog. */
209  xcb_grab_pointer(conn, false, dialog->id, XCB_NONE, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, dialog->id,
210  XCB_NONE, XCB_CURRENT_TIME);
211  }
212 
214  xcb_flush(conn);
215 }
216 
217 static void sighandler_destroy_dialogs(void) {
218  while (!TAILQ_EMPTY(&dialogs)) {
219  dialog_t *dialog = TAILQ_FIRST(&dialogs);
220 
221  xcb_free_colormap(conn, dialog->colormap);
222  draw_util_surface_free(conn, &(dialog->surface));
223  xcb_destroy_window(conn, dialog->id);
224 
225  TAILQ_REMOVE(&dialogs, dialog, dialogs);
226  free(dialog);
227  }
228 
229  xcb_flush(conn);
230 }
231 
232 static void sighandler_handle_expose(void) {
233  dialog_t *current;
234  TAILQ_FOREACH(current, &dialogs, dialogs) {
235  sighandler_draw_dialog(current);
236  }
237 
238  xcb_flush(conn);
239 }
240 
241 static void sighandler_draw_dialog(dialog_t *dialog) {
242  const color_t black = draw_util_hex_to_color("#000000");
243  const color_t white = draw_util_hex_to_color("#FFFFFF");
244  const color_t red = draw_util_hex_to_color("#FF0000");
245 
246  /* Start with a clean slate and draw a red border. */
247  draw_util_clear_surface(&(dialog->surface), red);
248  draw_util_rectangle(&(dialog->surface), black, border_width, border_width,
249  dialog->dims.width - 2 * border_width, dialog->dims.height - 2 * border_width);
250 
251  int y = border_width + margin;
252  const int x = border_width + margin;
253  const int max_width = dialog->dims.width - 2 * x;
254 
255  draw_util_text(message_intro, &(dialog->surface), white, black, x, y, max_width);
256  y += config.font.height;
257 
258  draw_util_text(message_intro2, &(dialog->surface), white, black, x, y, max_width);
259  y += config.font.height;
260 
261  char *bt_color = "#FFFFFF";
262  if (backtrace_done < 0) {
263  bt_color = "#AA0000";
264  } else if (backtrace_done > 0) {
265  bt_color = "#00AA00";
266  }
267  draw_util_text(message_option_backtrace, &(dialog->surface), draw_util_hex_to_color(bt_color), black, x, y, max_width);
268  y += config.font.height;
269 
270  draw_util_text(message_option_restart, &(dialog->surface), white, black, x, y, max_width);
271  y += config.font.height;
272 
273  draw_util_text(message_option_forget, &(dialog->surface), white, black, x, y, max_width);
274  y += config.font.height;
275 }
276 
277 static void sighandler_handle_key_press(xcb_key_press_event_t *event) {
278  uint16_t state = event->state;
279 
280  /* Apparently, after activating numlock once, the numlock modifier
281  * stays turned on (use xev(1) to verify). So, to resolve useful
282  * keysyms, we remove the numlock flag from the event state */
283  state &= ~xcb_numlock_mask;
284 
285  xcb_keysym_t sym = xcb_key_press_lookup_keysym(keysyms, event, state);
286 
287  if (sym == 'b') {
288  DLOG("User issued core-dump command.\n");
289 
290  /* fork and exec/attach GDB to the parent to get a backtrace in the
291  * tmpdir */
292  backtrace_done = sighandler_backtrace();
294  } else if (sym == 'r') {
296  i3_restart(false);
297  } else if (sym == 'f') {
299  i3_restart(true);
300  }
301 }
302 
303 void handle_signal(int sig, siginfo_t *info, void *data) {
304  DLOG("i3 crashed. SIG: %d\n", sig);
305 
306  struct sigaction action;
307  action.sa_handler = SIG_DFL;
308  action.sa_flags = 0;
309  sigemptyset(&action.sa_mask);
310  sigaction(sig, &action, NULL);
311  raised_signal = sig;
312 
315 
316  xcb_generic_event_t *event;
317  /* Yay, more own eventhandlers… */
318  while ((event = xcb_wait_for_event(conn))) {
319  /* Strip off the highest bit (set if the event is generated) */
320  int type = (event->response_type & 0x7F);
321  switch (type) {
322  case XCB_KEY_PRESS:
323  sighandler_handle_key_press((xcb_key_press_event_t *)event);
324  break;
325  case XCB_EXPOSE:
326  if (((xcb_expose_event_t *)event)->count == 0) {
328  }
329 
330  break;
331  }
332 
333  free(event);
334  }
335 }
336 
337 /*
338  * Configured a signal handler to gracefully handle crashes and allow the user
339  * to generate a backtrace and rescue their session.
340  *
341  */
343  struct sigaction action;
344 
345  action.sa_sigaction = handle_signal;
346  action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
347  sigemptyset(&action.sa_mask);
348 
349  /* Catch all signals with default action "Core", see signal(7) */
350  if (sigaction(SIGQUIT, &action, NULL) == -1 ||
351  sigaction(SIGILL, &action, NULL) == -1 ||
352  sigaction(SIGABRT, &action, NULL) == -1 ||
353  sigaction(SIGFPE, &action, NULL) == -1 ||
354  sigaction(SIGSEGV, &action, NULL) == -1)
355  ELOG("Could not setup signal handler.\n");
356 }
struct _i3String i3String
Opaque data structure for storing strings.
Definition: libi3.h:48
xcb_window_t root
Definition: main.c:59
int sasprintf(char **strp, const char *fmt,...)
Safe-wrapper around asprintf which exits if it returns -1 (meaning that there is no more memory avail...
struct outputs_head outputs
Definition: randr.c:21
#define TAILQ_HEAD_INITIALIZER(head)
Definition: queue.h:324
color_t draw_util_hex_to_color(const char *color)
Parses the given color in hex format to an internal color representation.
static void sighandler_draw_dialog(dialog_t *dialog)
Definition: sighandler.c:241
#define TAILQ_EMPTY(head)
Definition: queue.h:344
#define ELOG(fmt,...)
Definition: libi3.h:99
i3String * i3string_from_utf8(const char *from_utf8)
Build an i3String from an UTF-8 encoded string.
xcb_connection_t * conn
XCB connection and root screen.
Definition: main.c:46
#define TAILQ_INSERT_TAIL(head, elm, field)
Definition: queue.h:376
Definition: libi3.h:402
void draw_util_clear_surface(surface_t *surface, color_t color)
Clears a surface with the given color.
xcb_screen_t * root_screen
Definition: main.c:58
xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t depth, xcb_visualid_t visual, uint16_t window_class, enum xcursor_cursor_t cursor, bool map, uint32_t mask, uint32_t *values)
Convenience wrapper around xcb_create_window which takes care of depth, generating an ID and checking...
Definition: xcb.c:19
int predict_text_width(i3String *text)
Predict the text width in pixels for the given text.
#define FREE(pointer)
Definition: util.h:50
int height
The height of the font, built from font_ascent + font_descent.
Definition: libi3.h:67
Rect dims
Definition: sighandler.c:22
xcb_visualid_t get_visualid_by_depth(uint16_t depth)
Get visualid with specified depth.
Definition: xcb.c:242
xcb_visualtype_t * get_visualtype_by_id(xcb_visualid_t visual_id)
Get visual type specified by visualid.
Definition: xcb.c:221
uint32_t x
Definition: data.h:149
static void sighandler_handle_key_press(xcb_key_press_event_t *event)
Definition: sighandler.c:277
uint32_t x
Definition: data.h:127
static void sighandler_destroy_dialogs(void)
Definition: sighandler.c:217
#define TAILQ_FOREACH(var, head, field)
Definition: queue.h:347
static cmdp_state state
#define TAILQ_FIRST(head)
Definition: queue.h:336
void handle_signal(int sig, siginfo_t *info, void *data)
Definition: sighandler.c:303
static TAILQ_HEAD(dialogs_head, dialog_t)
Definition: sighandler.c:29
#define TAILQ_REMOVE(head, elm, field)
Definition: queue.h:402
surface_t surface
Definition: sighandler.c:23
static void sighandler_setup(void)
Definition: sighandler.c:140
void setup_signal_handler(void)
Configured a signal handler to gracefully handle crashes and allow the user to generate a backtrace a...
Definition: sighandler.c:342
xcb_key_symbols_t * keysyms
Definition: main.c:70
void * scalloc(size_t num, size_t size)
Safe-wrapper around calloc which exits if malloc returns NULL (meaning that there is no more memory a...
uint32_t y
Definition: data.h:150
void draw_util_surface_free(xcb_connection_t *conn, surface_t *surface)
Destroys the surface.
An Output is a physical output on your graphics driver.
Definition: data.h:366
uint32_t height
Definition: data.h:152
int logical_px(const int logical)
Convert a logical amount of pixels (e.g.
uint32_t width
Definition: data.h:151
static void sighandler_create_dialogs(void)
Definition: sighandler.c:157
uint32_t y
Definition: data.h:128
void i3_restart(bool forget_layout)
Restart i3 in-place appends -a to argument list to disable autostart.
Definition: util.c:282
unsigned int xcb_numlock_mask
Definition: xcb.c:12
char ** start_argv
Definition: main.c:44
#define DLOG(fmt,...)
Definition: libi3.h:104
void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width)
Draw the given text using libi3.
xcb_colormap_t colormap
Definition: sighandler.c:21
static void sighandler_handle_expose(void)
Definition: sighandler.c:232
#define TAILQ_ENTRY(type)
Definition: queue.h:327
Config config
Definition: config.c:17
Rect rect
x, y, width, height
Definition: data.h:390
i3Font font
Definition: configuration.h:98
Stores a rectangle, for example the size of a window, the child window etc.
Definition: data.h:148
uint8_t root_depth
Definition: main.c:64
void draw_util_surface_init(xcb_connection_t *conn, surface_t *surface, xcb_drawable_t drawable, xcb_visualtype_t *visual, int width, int height)
Initialize the surface to represent the given drawable.
xcb_window_t id
Definition: sighandler.c:20
void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h)
Draws a filled rectangle.
bool active
Whether the output is currently active (has a CRTC attached with a valid mode)
Definition: data.h:372