]> git.sur5r.net Git - i3/i3/blob - testcases/inject_randr1.5.c
inject_randr1.5: Refactor reading and storing reply buffer to a struct
[i3/i3] / testcases / inject_randr1.5.c
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  * inject_randr1.5.c: An X11 proxy which interprets RandR 1.5 GetMonitors
8  * requests and overwrites their reply with a custom reply.
9  *
10  * This tool can be refactored as necessary in order to perform the same
11  * purpose for other request types. The RandR 1.5 specific portions of the code
12  * have been marked as such to make such a refactoring easier.
13  *
14  */
15 #include "all.h"
16
17 #include <ev.h>
18 #include <fcntl.h>
19 #include <sys/types.h>
20 #include <sys/socket.h>
21 #include <sys/un.h>
22 #include <sys/time.h>
23 #include <sys/resource.h>
24 #include <sys/mman.h>
25 #include <sys/stat.h>
26 #include <libgen.h>
27
28 static void uds_connection_cb(EV_P_ ev_io *w, int revents);
29 static void read_client_setup_request_cb(EV_P_ ev_io *w, int revents);
30 static void read_server_setup_reply_cb(EV_P_ ev_io *w, int revents);
31 static void read_client_x11_packet_cb(EV_P_ ev_io *w, int revents);
32 static void read_server_x11_packet_cb(EV_P_ ev_io *w, int revents);
33
34 static char *sun_path = NULL;
35
36 void cleanup_socket(void) {
37     if (sun_path != NULL) {
38         unlink(sun_path);
39         free(sun_path);
40         sun_path = NULL;
41     }
42 }
43
44 struct injected_reply {
45     void *buf;
46     off_t len;
47 };
48
49 /* BEGIN RandR 1.5 specific */
50 static struct injected_reply getmonitors_reply = {NULL, 0};
51 /* END RandR 1.5 specific */
52
53 #define XCB_PAD(i) (-(i)&3)
54
55 struct connstate {
56     /* clientw is a libev watcher for the connection which we accept()ed. */
57     ev_io *clientw;
58
59     /* serverw is a libev watcher for the connection to X11 which we initiated
60          * on behalf of the client. */
61     ev_io *serverw;
62
63     /* sequence is the client-side sequence number counter. In X11’s wire
64      * encoding, sequence counters are not included in requests, only in
65      * replies. */
66     int sequence;
67
68     /* BEGIN RandR 1.5 specific */
69     /* sequence number of the most recent GetExtension request for RANDR */
70     int getext_randr;
71     /* sequence number of the most recent RRGetMonitors request */
72     int getmonitors;
73
74     int randr_major_opcode;
75     /* END RandR 1.5 specific */
76 };
77
78 /*
79  * Returns 0 on EOF
80  * Returns -1 on error (with errno from read() untouched)
81  *
82  */
83 static size_t readall_into(void *buffer, const size_t len, int fd) {
84     size_t read_bytes = 0;
85     while (read_bytes < len) {
86         ssize_t n = read(fd, buffer + read_bytes, len - read_bytes);
87         if (n <= 0) {
88             return n;
89         }
90         read_bytes += (size_t)n;
91     }
92     return read_bytes;
93 }
94
95 /*
96  * Exits the program with an error if the read failed.
97  *
98  */
99 static void must_read(int n) {
100     if (n == -1) {
101         err(EXIT_FAILURE, "read()");
102     }
103     if (n == 0) {
104         errx(EXIT_FAILURE, "EOF");
105     }
106 }
107
108 /*
109  * Exits the program with an error if the write failed.
110  *
111  */
112 static void must_write(int n) {
113     if (n == -1) {
114         err(EXIT_FAILURE, "write()");
115     }
116 }
117
118 static void uds_connection_cb(EV_P_ ev_io *w, int revents) {
119     struct sockaddr_un addr;
120     socklen_t addrlen = sizeof(addr);
121     const int clientfd = accept(w->fd, (struct sockaddr *)&addr, &addrlen);
122     if (clientfd == -1) {
123         if (errno == EINTR) {
124             return;
125         }
126         err(EXIT_FAILURE, "accept()");
127     }
128
129     struct connstate *connstate = scalloc(1, sizeof(struct connstate));
130
131     ev_io *clientw = scalloc(1, sizeof(ev_io));
132     connstate->clientw = clientw;
133     clientw->data = connstate;
134     ev_io_init(clientw, read_client_setup_request_cb, clientfd, EV_READ);
135     ev_io_start(EV_A_ clientw);
136 }
137
138 // https://www.x.org/releases/current/doc/xproto/x11protocol.html#Encoding::Connection_Setup
139 static void read_client_setup_request_cb(EV_P_ ev_io *w, int revents) {
140     ev_io_stop(EV_A_ w);
141     struct connstate *connstate = (struct connstate *)w->data;
142
143     /* Read X11 setup request in its entirety. */
144     xcb_setup_request_t setup_request;
145     must_read(readall_into(&setup_request, sizeof(setup_request), w->fd));
146
147     /* Establish a connection to X11. */
148     int fd = socket(AF_LOCAL, SOCK_STREAM, 0);
149     if (fd == -1) {
150         err(EXIT_FAILURE, "socket()");
151     }
152
153     char *host;
154     int displayp;
155     if (xcb_parse_display(getenv("DISPLAY"), &host, &displayp, NULL) == 0) {
156         errx(EXIT_FAILURE, "Could not parse DISPLAY=%s", getenv("DISPLAY"));
157     }
158     free(host);
159
160     struct sockaddr_un addr;
161     memset(&addr, 0, sizeof(addr));
162     addr.sun_family = AF_LOCAL;
163     snprintf(addr.sun_path, sizeof(addr.sun_path), "/tmp/.X11-unix/X%d", displayp);
164     if (connect(fd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) {
165         err(EXIT_FAILURE, "connect(%s)", addr.sun_path);
166     }
167
168     /* Relay setup request. */
169     must_write(writeall(fd, &setup_request, sizeof(setup_request)));
170
171     if (setup_request.authorization_protocol_name_len > 0 ||
172         setup_request.authorization_protocol_data_len > 0) {
173         const size_t authlen = setup_request.authorization_protocol_name_len +
174                                XCB_PAD(setup_request.authorization_protocol_name_len) +
175                                setup_request.authorization_protocol_data_len +
176                                XCB_PAD(setup_request.authorization_protocol_data_len);
177         void *buf = smalloc(authlen);
178         must_read(readall_into(buf, authlen, w->fd));
179         must_write(writeall(fd, buf, authlen));
180         free(buf);
181     }
182
183     /* Wait for a response from the X11 server. */
184     ev_io *serverw = scalloc(1, sizeof(ev_io));
185     connstate->serverw = serverw;
186     serverw->data = connstate;
187     ev_io_init(serverw, read_server_setup_reply_cb, fd, EV_READ);
188     ev_io_start(EV_A_ serverw);
189 }
190
191 static void read_server_setup_reply_cb(EV_P_ ev_io *w, int revents) {
192     struct connstate *connstate = (struct connstate *)w->data;
193     xcb_setup_failed_t setup_failed;
194     must_read(readall_into(&setup_failed, sizeof(setup_failed), w->fd));
195
196     switch (setup_failed.status) {
197         case 0:
198             errx(EXIT_FAILURE, "error authenticating at the X11 server");
199
200         case 2:
201             errx(EXIT_FAILURE, "two-factor auth not implemented");
202
203         case 1:
204             must_write(writeall(connstate->clientw->fd, &setup_failed, sizeof(xcb_setup_failed_t)));
205             const size_t len = (setup_failed.length * 4);
206             void *buf = smalloc(len);
207             must_read(readall_into(buf, len, w->fd));
208             must_write(writeall(connstate->clientw->fd, buf, len));
209             free(buf);
210
211             ev_set_cb(connstate->clientw, read_client_x11_packet_cb);
212             ev_set_cb(connstate->serverw, read_server_x11_packet_cb);
213             ev_io_start(EV_A_ connstate->clientw);
214             break;
215
216         default:
217             errx(EXIT_FAILURE, "X11 protocol error: expected setup_failed.status in [0..2], got %d", setup_failed.status);
218     }
219 }
220
221 // https://www.x.org/releases/current/doc/xproto/x11protocol.html#request_format
222 typedef struct {
223     uint8_t opcode;
224     uint8_t pad0;
225     uint16_t length;
226 } generic_x11_request_t;
227
228 // https://www.x.org/releases/current/doc/xproto/x11protocol.html#reply_format
229 typedef struct {
230     uint8_t code; /* if 1, this is a reply. if 0, this is an error. else, an event */
231     uint8_t pad0;
232     uint16_t sequence;
233     uint32_t length;
234 } generic_x11_reply_t;
235
236 static void read_client_x11_packet_cb(EV_P_ ev_io *w, int revents) {
237     struct connstate *connstate = (struct connstate *)w->data;
238
239     void *request = smalloc(sizeof(generic_x11_request_t));
240     must_read(readall_into(request, sizeof(generic_x11_request_t), connstate->clientw->fd));
241     const size_t len = (((generic_x11_request_t *)request)->length * 4);
242     if (len > sizeof(generic_x11_request_t)) {
243         request = srealloc(request, len);
244         must_read(readall_into(request + sizeof(generic_x11_request_t),
245                                len - sizeof(generic_x11_request_t),
246                                connstate->clientw->fd));
247     }
248
249     // XXX: sequence counter wrapping is not implemented, but should not be
250     // necessary given that this tool is scoped for test cases.
251     connstate->sequence++;
252
253     /* BEGIN RandR 1.5 specific */
254     const uint8_t opcode = ((generic_x11_request_t *)request)->opcode;
255     if (opcode == XCB_QUERY_EXTENSION) {
256         xcb_query_extension_request_t *req = request;
257         const char *name = request + sizeof(xcb_query_extension_request_t);
258         if (req->name_len == strlen("RANDR") &&
259             strncmp(name, "RANDR", strlen("RANDR")) == 0) {
260             connstate->getext_randr = connstate->sequence;
261         }
262     } else if (opcode == connstate->randr_major_opcode) {
263         const uint8_t randr_opcode = ((generic_x11_request_t *)request)->pad0;
264         if (randr_opcode == XCB_RANDR_GET_MONITORS) {
265             connstate->getmonitors = connstate->sequence;
266         }
267     }
268     /* END RandR 1.5 specific */
269
270     must_write(writeall(connstate->serverw->fd, request, len));
271     free(request);
272 }
273
274 static void read_server_x11_packet_cb(EV_P_ ev_io *w, int revents) {
275     struct connstate *connstate = (struct connstate *)w->data;
276     // all packets from the server are at least 32 bytes in length
277     size_t len = 32;
278     void *packet = smalloc(len);
279     must_read(readall_into(packet, len, connstate->serverw->fd));
280     switch (((generic_x11_reply_t *)packet)->code) {
281         case 0:  // error
282             break;
283
284         case 1:  // reply
285             len += ((generic_x11_reply_t *)packet)->length * 4;
286             if (len > 32) {
287                 packet = srealloc(packet, len);
288                 must_read(readall_into(packet + 32, len - 32, connstate->serverw->fd));
289             }
290
291             /* BEGIN RandR 1.5 specific */
292             const uint16_t sequence = ((generic_x11_reply_t *)packet)->sequence;
293
294             if (sequence == connstate->getext_randr) {
295                 xcb_query_extension_reply_t *reply = packet;
296                 connstate->randr_major_opcode = reply->major_opcode;
297             }
298
299             if (sequence == connstate->getmonitors) {
300                 printf("RRGetMonitors reply!\n");
301                 if (getmonitors_reply.buf != NULL) {
302                     printf("injecting reply\n");
303                     ((generic_x11_reply_t *)getmonitors_reply.buf)->sequence = sequence;
304                     must_write(writeall(connstate->clientw->fd, getmonitors_reply.buf, getmonitors_reply.len));
305                     free(packet);
306                     return;
307                 }
308             }
309             /* END RandR 1.5 specific */
310
311             break;
312
313         default:  // event
314             break;
315     }
316     must_write(writeall(connstate->clientw->fd, packet, len));
317     free(packet);
318 }
319
320 static void child_cb(EV_P_ ev_child *w, int revents) {
321     ev_child_stop(EV_A_ w);
322     if (WIFEXITED(w->rstatus)) {
323         exit(WEXITSTATUS(w->rstatus));
324     } else {
325         exit(WTERMSIG(w->rstatus) + 128);
326     }
327 }
328
329 static void must_read_reply(const char *filename, struct injected_reply *reply) {
330     FILE *f;
331     if ((f = fopen(filename, "r")) == NULL) {
332         err(EXIT_FAILURE, "fopen(%s)", filename);
333     }
334     struct stat stbuf;
335     if (fstat(fileno(f), &stbuf) != 0) {
336         err(EXIT_FAILURE, "fstat(%s)", filename);
337     }
338     reply->len = stbuf.st_size;
339     reply->buf = smalloc(stbuf.st_size);
340     int n = fread(reply->buf, 1, stbuf.st_size, f);
341     if (n != stbuf.st_size) {
342         err(EXIT_FAILURE, "fread(%s)", filename);
343     }
344     fclose(f);
345 }
346
347 int main(int argc, char *argv[]) {
348     static struct option long_options[] = {
349         {"getmonitors_reply", required_argument, 0, 0},
350         {0, 0, 0, 0},
351     };
352     char *options_string = "";
353     int opt;
354     int option_index = 0;
355
356     while ((opt = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
357         switch (opt) {
358             case 0:
359                 if (strcmp(long_options[option_index].name, "getmonitors_reply") == 0) {
360                     must_read_reply(optarg, &getmonitors_reply);
361                 }
362                 break;
363             default:
364                 exit(EXIT_FAILURE);
365         }
366     }
367
368     if (optind >= argc) {
369         errx(EXIT_FAILURE, "syntax: %s [options] <command>\n", argv[0]);
370     }
371
372     int fd = socket(AF_LOCAL, SOCK_STREAM, 0);
373     if (fd == -1) {
374         err(EXIT_FAILURE, "socket(AF_UNIX)");
375     }
376
377     if (fcntl(fd, F_SETFD, FD_CLOEXEC)) {
378         warn("Could not set FD_CLOEXEC");
379     }
380
381     struct sockaddr_un addr;
382     memset(&addr, 0, sizeof(struct sockaddr_un));
383     addr.sun_family = AF_UNIX;
384     int i;
385     bool bound = false;
386     for (i = 0; i < 100; i++) {
387         /* XXX: The path to X11 sockets differs on some platforms (e.g. Trusted
388          * Solaris, HPUX), but since libxcb doesn’t provide a function to
389          * generate the path, we’ll just have to hard-code it for now. */
390         snprintf(addr.sun_path, sizeof(addr.sun_path), "/tmp/.X11-unix/X%d", i);
391
392         if (bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) {
393             warn("bind(%s)", addr.sun_path);
394         } else {
395             bound = true;
396             /* Let the user know bind() was successful, so that they know the
397              * error messages can be disregarded. */
398             fprintf(stderr, "Successfuly bound to %s\n", addr.sun_path);
399             sun_path = sstrdup(addr.sun_path);
400             break;
401         }
402     }
403
404     if (!bound) {
405         err(EXIT_FAILURE, "bind()");
406     }
407
408     atexit(cleanup_socket);
409
410     /* This program will be started for each testcase which requires it, so we
411      * expect precisely one connection. */
412     if (listen(fd, 1) == -1) {
413         err(EXIT_FAILURE, "listen()");
414     }
415
416     pid_t child = fork();
417     if (child == -1) {
418         err(EXIT_FAILURE, "fork()");
419     }
420     if (child == 0) {
421         char *display;
422         sasprintf(&display, ":%d", i);
423         setenv("DISPLAY", display, 1);
424         free(display);
425
426         char **child_args = argv + optind;
427         execvp(child_args[0], child_args);
428         err(EXIT_FAILURE, "exec()");
429     }
430
431     struct ev_loop *loop = ev_default_loop(0);
432
433     ev_child cw;
434     ev_child_init(&cw, child_cb, child, 0);
435     ev_child_start(loop, &cw);
436
437     ev_io watcher;
438     ev_io_init(&watcher, uds_connection_cb, fd, EV_READ);
439     ev_io_start(loop, &watcher);
440
441     ev_run(loop, 0);
442 }