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