Commit | Line | Data |
---|---|---|
a09e091a JB |
1 | /* Copyright (c) 2008-2012 Apple Inc. |
2 | * | |
3 | * Permission is hereby granted, free of charge, to any person | |
4 | * obtaining a copy of this software and associated documentation files | |
5 | * (the "Software"), to deal in the Software without restriction, | |
6 | * including without limitation the rights to use, copy, modify, merge, | |
7 | * publish, distribute, sublicense, and/or sell copies of the Software, | |
8 | * and to permit persons to whom the Software is furnished to do so, | |
9 | * subject to the following conditions: | |
10 | * | |
11 | * The above copyright notice and this permission notice shall be | |
12 | * included in all copies or substantial portions of the Software. | |
13 | * | |
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
15 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
16 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
17 | * NONINFRINGEMENT. IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT | |
18 | * HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
19 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
21 | * DEALINGS IN THE SOFTWARE. | |
22 | * | |
23 | * Except as contained in this notice, the name(s) of the above | |
24 | * copyright holders shall not be used in advertising or otherwise to | |
25 | * promote the sale, use or other dealings in this Software without | |
26 | * prior written authorization. | |
27 | */ | |
28 | ||
29 | #include <CoreServices/CoreServices.h> | |
30 | ||
31 | #ifdef HAVE_DIX_CONFIG_H | |
32 | #include <dix-config.h> | |
33 | #endif | |
34 | ||
35 | #include <string.h> | |
36 | #include <unistd.h> | |
37 | #include <errno.h> | |
38 | #include <asl.h> | |
39 | ||
40 | #include <sys/socket.h> | |
41 | #include <sys/un.h> | |
42 | ||
43 | #define kX11AppBundleId BUNDLE_ID_PREFIX ".X11" | |
44 | #define kX11AppBundlePath "/Contents/MacOS/X11" | |
45 | ||
46 | #include <mach/mach.h> | |
47 | #include <mach/mach_error.h> | |
48 | #include <servers/bootstrap.h> | |
49 | #include "mach_startup.h" | |
50 | ||
51 | #include <signal.h> | |
52 | ||
53 | #include <AvailabilityMacros.h> | |
54 | ||
55 | #include "launchd_fd.h" | |
56 | ||
57 | static char x11_path[PATH_MAX + 1]; | |
58 | static pid_t x11app_pid = 0; | |
59 | aslclient aslc; | |
60 | ||
61 | static void | |
62 | set_x11_path(void) | |
63 | { | |
64 | #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 | |
65 | ||
66 | CFURLRef appURL = NULL; | |
67 | OSStatus osstatus = | |
68 | LSFindApplicationForInfo(kLSUnknownCreator, CFSTR( | |
69 | kX11AppBundleId), nil, nil, &appURL); | |
70 | ||
71 | switch (osstatus) { | |
72 | case noErr: | |
73 | if (appURL == NULL) { | |
74 | asl_log( | |
75 | aslc, NULL, ASL_LEVEL_ERR, | |
76 | "Xquartz: Invalid response from LSFindApplicationForInfo(%s)", | |
77 | kX11AppBundleId); | |
78 | exit(1); | |
79 | } | |
80 | ||
81 | if (!CFURLGetFileSystemRepresentation(appURL, true, | |
82 | (unsigned char *)x11_path, | |
83 | sizeof(x11_path))) { | |
84 | asl_log(aslc, NULL, ASL_LEVEL_ERR, | |
85 | "Xquartz: Error resolving URL for %s", | |
86 | kX11AppBundleId); | |
87 | exit(3); | |
88 | } | |
89 | ||
90 | strlcat(x11_path, kX11AppBundlePath, sizeof(x11_path)); | |
91 | asl_log(aslc, NULL, ASL_LEVEL_INFO, "Xquartz: X11.app = %s", x11_path); | |
92 | break; | |
93 | ||
94 | case kLSApplicationNotFoundErr: | |
95 | asl_log(aslc, NULL, ASL_LEVEL_ERR, | |
96 | "Xquartz: Unable to find application for %s", | |
97 | kX11AppBundleId); | |
98 | exit(10); | |
99 | ||
100 | default: | |
101 | asl_log(aslc, NULL, ASL_LEVEL_ERR, | |
102 | "Xquartz: Unable to find application for %s, error code = %d", | |
103 | kX11AppBundleId, | |
104 | (int)osstatus); | |
105 | exit(11); | |
106 | } | |
107 | #else | |
108 | /* TODO: Make Tiger smarter... but TBH, this should never get called on Tiger... */ | |
109 | strlcpy(x11_path, "/Applications/Utilities/X11.app/Contents/MacOS/X11", | |
110 | sizeof(x11_path)); | |
111 | #endif | |
112 | } | |
113 | ||
114 | static int | |
115 | connect_to_socket(const char *filename) | |
116 | { | |
117 | struct sockaddr_un servaddr_un; | |
118 | struct sockaddr *servaddr; | |
119 | socklen_t servaddr_len; | |
120 | int ret_fd; | |
121 | ||
122 | /* Setup servaddr_un */ | |
123 | memset(&servaddr_un, 0, sizeof(struct sockaddr_un)); | |
124 | servaddr_un.sun_family = AF_UNIX; | |
125 | strlcpy(servaddr_un.sun_path, filename, sizeof(servaddr_un.sun_path)); | |
126 | ||
127 | servaddr = (struct sockaddr *)&servaddr_un; | |
128 | servaddr_len = sizeof(struct sockaddr_un) - | |
129 | sizeof(servaddr_un.sun_path) + strlen(filename); | |
130 | ||
131 | ret_fd = socket(PF_UNIX, SOCK_STREAM, 0); | |
132 | if (ret_fd == -1) { | |
133 | asl_log(aslc, NULL, ASL_LEVEL_ERR, | |
134 | "Xquartz: Failed to create socket: %s - %s", filename, | |
135 | strerror( | |
136 | errno)); | |
137 | return -1; | |
138 | } | |
139 | ||
140 | if (connect(ret_fd, servaddr, servaddr_len) < 0) { | |
141 | asl_log(aslc, NULL, ASL_LEVEL_ERR, | |
142 | "Xquartz: Failed to connect to socket: %s - %d - %s", | |
143 | filename, errno, | |
144 | strerror( | |
145 | errno)); | |
146 | close(ret_fd); | |
147 | return -1; | |
148 | } | |
149 | ||
150 | return ret_fd; | |
151 | } | |
152 | ||
153 | static void | |
154 | send_fd_handoff(int connected_fd, int launchd_fd) | |
155 | { | |
156 | char databuf[] = "display"; | |
157 | struct iovec iov[1]; | |
158 | ||
159 | union { | |
160 | struct cmsghdr hdr; | |
161 | char bytes[CMSG_SPACE(sizeof(int))]; | |
162 | } buf; | |
163 | ||
164 | struct msghdr msg; | |
165 | struct cmsghdr *cmsg; | |
166 | ||
167 | iov[0].iov_base = databuf; | |
168 | iov[0].iov_len = sizeof(databuf); | |
169 | ||
170 | msg.msg_iov = iov; | |
171 | msg.msg_iovlen = 1; | |
172 | msg.msg_control = buf.bytes; | |
173 | msg.msg_controllen = sizeof(buf); | |
174 | msg.msg_name = 0; | |
175 | msg.msg_namelen = 0; | |
176 | msg.msg_flags = 0; | |
177 | ||
178 | cmsg = CMSG_FIRSTHDR(&msg); | |
179 | cmsg->cmsg_level = SOL_SOCKET; | |
180 | cmsg->cmsg_type = SCM_RIGHTS; | |
181 | cmsg->cmsg_len = CMSG_LEN(sizeof(int)); | |
182 | ||
183 | msg.msg_controllen = cmsg->cmsg_len; | |
184 | ||
185 | *((int *)CMSG_DATA(cmsg)) = launchd_fd; | |
186 | ||
187 | if (sendmsg(connected_fd, &msg, 0) < 0) { | |
188 | asl_log( | |
189 | aslc, NULL, ASL_LEVEL_ERR, | |
190 | "Xquartz: Error sending $DISPLAY file descriptor over fd %d: %d -- %s", | |
191 | connected_fd, errno, strerror(errno)); | |
192 | return; | |
193 | } | |
194 | ||
195 | asl_log(aslc, NULL, ASL_LEVEL_DEBUG, | |
196 | "Xquartz: Message sent. Closing handoff fd."); | |
197 | close(connected_fd); | |
198 | } | |
199 | ||
200 | __attribute__((__noreturn__)) | |
201 | static void | |
202 | signal_handler(int sig) | |
203 | { | |
204 | if (x11app_pid) | |
205 | kill(x11app_pid, sig); | |
206 | _exit(0); | |
207 | } | |
208 | ||
209 | int | |
210 | main(int argc, char **argv, char **envp) | |
211 | { | |
212 | int envpc; | |
213 | kern_return_t kr; | |
214 | mach_port_t mp; | |
215 | string_array_t newenvp; | |
216 | string_array_t newargv; | |
217 | size_t i; | |
218 | int launchd_fd; | |
219 | string_t handoff_socket_filename; | |
220 | sig_t handler; | |
221 | char *asl_sender; | |
222 | char *asl_facility; | |
223 | char *server_bootstrap_name = kX11AppBundleId; | |
224 | ||
225 | if (getenv("X11_PREFS_DOMAIN")) | |
226 | server_bootstrap_name = getenv("X11_PREFS_DOMAIN"); | |
227 | ||
228 | asprintf(&asl_sender, "%s.stub", server_bootstrap_name); | |
229 | assert(asl_sender); | |
230 | ||
231 | asl_facility = strdup(server_bootstrap_name); | |
232 | assert(asl_facility); | |
233 | if (strcmp(asl_facility + strlen(asl_facility) - 4, ".X11") == 0) | |
234 | asl_facility[strlen(asl_facility) - 4] = '\0'; | |
235 | ||
236 | assert(aslc = asl_open(asl_sender, asl_facility, ASL_OPT_NO_DELAY)); | |
237 | free(asl_sender); | |
238 | free(asl_facility); | |
239 | ||
240 | /* We don't have a mechanism in place to handle this interrupt driven | |
241 | * server-start notification, so just send the signal now, so xinit doesn't | |
242 | * time out waiting for it and will just poll for the server. | |
243 | */ | |
244 | handler = signal(SIGUSR1, SIG_IGN); | |
245 | if (handler == SIG_IGN) | |
246 | kill(getppid(), SIGUSR1); | |
247 | signal(SIGUSR1, handler); | |
248 | ||
249 | /* Pass on SIGs to X11.app */ | |
250 | signal(SIGINT, signal_handler); | |
251 | signal(SIGTERM, signal_handler); | |
252 | ||
253 | /* Get the $DISPLAY FD */ | |
254 | launchd_fd = launchd_display_fd(); | |
255 | ||
256 | kr = bootstrap_look_up(bootstrap_port, server_bootstrap_name, &mp); | |
257 | if (kr != KERN_SUCCESS) { | |
258 | pid_t child; | |
259 | ||
260 | asl_log(aslc, NULL, ASL_LEVEL_WARNING, | |
261 | "Xquartz: Unable to locate waiting server: %s", | |
262 | server_bootstrap_name); | |
263 | set_x11_path(); | |
264 | ||
265 | /* This forking is ugly and will be cleaned up later */ | |
266 | child = fork(); | |
267 | if (child == -1) { | |
268 | asl_log(aslc, NULL, ASL_LEVEL_ERR, "Xquartz: Could not fork: %s", | |
269 | strerror( | |
270 | errno)); | |
271 | return EXIT_FAILURE; | |
272 | } | |
273 | ||
274 | if (child == 0) { | |
275 | char *_argv[3]; | |
276 | _argv[0] = x11_path; | |
277 | _argv[1] = "--listenonly"; | |
278 | _argv[2] = NULL; | |
279 | asl_log(aslc, NULL, ASL_LEVEL_NOTICE, | |
280 | "Xquartz: Starting X server: %s --listenonly", | |
281 | x11_path); | |
282 | return execvp(x11_path, _argv); | |
283 | } | |
284 | ||
285 | /* Try connecting for 10 seconds */ | |
286 | for (i = 0; i < 80; i++) { | |
287 | usleep(250000); | |
288 | kr = bootstrap_look_up(bootstrap_port, server_bootstrap_name, &mp); | |
289 | if (kr == KERN_SUCCESS) | |
290 | break; | |
291 | } | |
292 | ||
293 | if (kr != KERN_SUCCESS) { | |
294 | #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 | |
295 | asl_log(aslc, NULL, ASL_LEVEL_ERR, | |
296 | "Xquartz: bootstrap_look_up(): %s", bootstrap_strerror( | |
297 | kr)); | |
298 | #else | |
299 | asl_log(aslc, NULL, ASL_LEVEL_ERR, | |
300 | "Xquartz: bootstrap_look_up(): %ul", | |
301 | (unsigned long)kr); | |
302 | #endif | |
303 | return EXIT_FAILURE; | |
304 | } | |
305 | } | |
306 | ||
307 | /* Get X11.app's pid */ | |
308 | request_pid(mp, &x11app_pid); | |
309 | ||
310 | /* Handoff the $DISPLAY FD */ | |
311 | if (launchd_fd != -1) { | |
312 | size_t try, try_max; | |
313 | int handoff_fd = -1; | |
314 | ||
315 | for (try = 0, try_max = 5; try < try_max; try++) { | |
316 | if (request_fd_handoff_socket(mp, | |
317 | handoff_socket_filename) != | |
318 | KERN_SUCCESS) { | |
319 | asl_log( | |
320 | aslc, NULL, ASL_LEVEL_INFO, | |
321 | "Xquartz: Failed to request a socket from the server to send the $DISPLAY fd over (try %d of %d)", | |
322 | (int)try + 1, (int)try_max); | |
323 | continue; | |
324 | } | |
325 | ||
326 | handoff_fd = connect_to_socket(handoff_socket_filename); | |
327 | if (handoff_fd == -1) { | |
328 | asl_log(aslc, NULL, ASL_LEVEL_ERR, | |
329 | "Xquartz: Failed to connect to socket (try %d of %d)", | |
330 | (int)try + 1, | |
331 | (int)try_max); | |
332 | continue; | |
333 | } | |
334 | ||
335 | asl_log( | |
336 | aslc, NULL, ASL_LEVEL_INFO, | |
337 | "Xquartz: Handoff connection established (try %d of %d) on fd %d, \"%s\". Sending message.", | |
338 | (int)try + 1, (int)try_max, handoff_fd, | |
339 | handoff_socket_filename); | |
340 | send_fd_handoff(handoff_fd, launchd_fd); | |
341 | close(handoff_fd); | |
342 | break; | |
343 | } | |
344 | } | |
345 | ||
346 | /* Count envp */ | |
347 | for (envpc = 0; envp[envpc]; envpc++) ; | |
348 | ||
349 | /* We have fixed-size string lengths due to limitations in IPC, | |
350 | * so we need to copy our argv and envp. | |
351 | */ | |
352 | newargv = (string_array_t)calloc((1 + argc), sizeof(string_t)); | |
353 | newenvp = (string_array_t)calloc((1 + envpc), sizeof(string_t)); | |
354 | ||
355 | if (!newargv || !newenvp) { | |
356 | /* Silence the clang static analyzer */ | |
357 | free(newargv); | |
358 | free(newenvp); | |
359 | ||
360 | asl_log(aslc, NULL, ASL_LEVEL_ERR, | |
361 | "Xquartz: Memory allocation failure"); | |
362 | return EXIT_FAILURE; | |
363 | } | |
364 | ||
365 | for (i = 0; i < argc; i++) { | |
366 | strlcpy(newargv[i], argv[i], STRING_T_SIZE); | |
367 | } | |
368 | for (i = 0; i < envpc; i++) { | |
369 | strlcpy(newenvp[i], envp[i], STRING_T_SIZE); | |
370 | } | |
371 | ||
372 | kr = start_x11_server(mp, newargv, argc, newenvp, envpc); | |
373 | ||
374 | free(newargv); | |
375 | free(newenvp); | |
376 | ||
377 | if (kr != KERN_SUCCESS) { | |
378 | asl_log(aslc, NULL, ASL_LEVEL_ERR, "Xquartz: start_x11_server: %s", | |
379 | mach_error_string( | |
380 | kr)); | |
381 | return EXIT_FAILURE; | |
382 | } | |
383 | return EXIT_SUCCESS; | |
384 | } |