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