1/*
2 * Copyright © 2016 Broadcom
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 */
23
24#ifdef HAVE_DIX_CONFIG_H
25#include <dix-config.h>
26#endif
27
28#include <errno.h>
29#include <signal.h>
30#include <stdbool.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <sys/wait.h>
35#include <unistd.h>
36
37static void
38kill_server(int server_pid)
39{
40    int ret = kill(server_pid, SIGTERM);
41    int wstatus;
42
43    if (ret) {
44        fprintf(stderr, "Failed to send kill to the server: %s\n",
45                strerror(errno));
46        exit(1);
47    }
48
49    ret = waitpid(server_pid, &wstatus, 0);
50    if (ret < 0) {
51        fprintf(stderr, "Failed to wait for X to die: %s\n", strerror(errno));
52        exit(1);
53    }
54}
55
56static void
57usage(int argc, char **argv)
58{
59    fprintf(stderr, "%s <client command> -- <server command>\n", argv[0]);
60    exit(1);
61}
62
63static int server_displayfd;
64static const char *server_dead = "server_dead";
65
66static void
67handle_sigchld(int sig)
68{
69    write(server_displayfd, server_dead, strlen(server_dead));
70}
71
72/* Starts the X server, returning its pid. */
73static int
74start_server(char *const *server_args)
75{
76    int server_pid = fork();
77
78    if (server_pid == -1) {
79        fprintf(stderr, "Fork failed: %s\n", strerror(errno));
80        exit(1);
81    } else if (server_pid != 0) {
82        /* Continue along the main process that will exec the client. */
83
84        struct sigaction sa;
85        sa.sa_handler = handle_sigchld;
86        sigemptyset(&sa.sa_mask);
87        sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
88        if (sigaction(SIGCHLD, &sa, 0) == -1) {
89            fprintf(stderr, "Failed to set up signal handler: %s\n",
90                    strerror(errno));
91            exit(1);
92        }
93
94        return server_pid;
95    }
96
97    /* Execute the server.  This only returns if an error occurred. */
98    execvp(server_args[0], server_args);
99    fprintf(stderr, "Error starting the server: %s\n", strerror(errno));
100    exit(1);
101}
102
103/* Reads the display number out of the started server's display socket. */
104static int
105get_display(int displayfd)
106{
107    char display_string[20];
108    ssize_t ret;
109
110    ret = read(displayfd, display_string, sizeof(display_string) - 1);
111    if (ret <= 0) {
112        fprintf(stderr, "Failed reading displayfd: %s\n", strerror(errno));
113        exit(1);
114    }
115
116    /* We've read in the display number as a string terminated by
117     * '\n', but not '\0'.  Cap it and parse the number.
118     */
119    display_string[ret] = '\0';
120
121    if (strncmp(display_string, server_dead, strlen(server_dead)) == 0) {
122        fprintf(stderr, "Server failed to start before setting up displayfd\n");
123        exit(1);
124    }
125
126    return atoi(display_string);
127}
128
129static int
130start_client(char *const *client_args, int display)
131{
132    char *display_string;
133    int ret;
134    int client_pid;
135
136    ret = asprintf(&display_string, ":%d", display);
137    if (ret < 0) {
138        fprintf(stderr, "asprintf fail\n");
139        exit(1);
140    }
141
142    ret = setenv("DISPLAY", display_string, true);
143    if (ret) {
144        fprintf(stderr, "Failed to set DISPLAY\n");
145        exit(1);
146    }
147
148    client_pid = fork();
149    if (client_pid == -1) {
150        fprintf(stderr, "Fork failed: %s\n", strerror(errno));
151        exit(1);
152    } else if (client_pid) {
153        int wstatus;
154
155        ret = waitpid(client_pid, &wstatus, 0);
156        if (ret < 0) {
157            fprintf(stderr, "Error waiting for client to start: %s\n",
158                    strerror(errno));
159            return 1;
160        }
161
162        if (!WIFEXITED(wstatus))
163            return 1;
164
165        return WEXITSTATUS(wstatus);
166    } else {
167        execvp(client_args[0], client_args);
168        /* exec only returns if an error occurred. */
169        fprintf(stderr, "Error starting the client: %s\n", strerror(errno));
170        exit(1);
171    }
172}
173
174/* Splits the incoming argc/argv into a pair of NULL-terminated arrays
175 * of args.
176 */
177static void
178parse_args(int argc, char **argv,
179           char * const **out_client_args,
180           char * const **out_server_args,
181           int displayfd)
182{
183    /* We're stripping the -- and the program name, inserting two
184     * NULLs, and also the -displayfd and fd number.
185     */
186    char **args_storage = calloc(argc + 2, sizeof(char *));
187    char *const *client_args;
188    char *const *server_args = NULL;
189    char **next_arg = args_storage;
190    bool parsing_client = true;
191    int i, ret;
192    char *displayfd_string;
193
194    if (!args_storage)
195        exit(1);
196
197    client_args = args_storage;
198    for (i = 1; i < argc; i++) {
199        if (strcmp(argv[i], "--") == 0) {
200            if (!parsing_client)
201                usage(argc, argv);
202
203            /* Cap the client list */
204            *next_arg = NULL;
205            next_arg++;
206
207            /* Move to adding into server_args. */
208            server_args = next_arg;
209            parsing_client = false;
210            continue;
211        }
212
213        /* A sort of escaped "--" argument so we can nest server
214         * invocations for testing.
215         */
216        if (strcmp(argv[i], "----") == 0)
217            *next_arg = (char *)"--";
218        else
219            *next_arg = argv[i];
220        next_arg++;
221    }
222
223    if (client_args[0] == NULL || !server_args || server_args[0] == NULL)
224        usage(argc, argv);
225
226    /* Give the server -displayfd X */
227    *next_arg = (char *)"-displayfd";
228    next_arg++;
229
230    ret = asprintf(&displayfd_string, "%d", displayfd);
231    if (ret < 0) {
232        fprintf(stderr, "asprintf fail\n");
233        exit(1);
234    }
235    *next_arg = displayfd_string;
236    next_arg++;
237
238    *out_client_args = client_args;
239    *out_server_args = server_args;
240}
241
242int
243main(int argc, char **argv)
244{
245    char * const *client_args;
246    char * const *server_args;
247    int displayfd_pipe[2];
248    int display, server_pid;
249    int ret;
250
251    ret = pipe(displayfd_pipe);
252    if (ret) {
253        fprintf(stderr, "Pipe creation failure: %s", strerror(errno));
254        exit(1);
255    }
256
257    server_displayfd = displayfd_pipe[1];
258    parse_args(argc, argv, &client_args, &server_args, server_displayfd);
259    server_pid = start_server(server_args);
260    display = get_display(displayfd_pipe[0]);
261    ret = start_client(client_args, display);
262    kill_server(server_pid);
263
264    exit(ret);
265}
266