| 1 | /* main.c -- X application launcher |
| 2 | * Copyright (c) 2007 Jeremy Huddleston |
| 3 | * Copyright (c) 2007-2012 Apple Inc. All rights reserved. |
| 4 | * |
| 5 | * Permission is hereby granted, free of charge, to any person |
| 6 | * obtaining a copy of this software and associated documentation files |
| 7 | * (the "Software"), to deal in the Software without restriction, |
| 8 | * including without limitation the rights to use, copy, modify, merge, |
| 9 | * publish, distribute, sublicense, and/or sell copies of the Software, |
| 10 | * and to permit persons to whom the Software is furnished to do so, |
| 11 | * subject to the following conditions: |
| 12 | * |
| 13 | * The above copyright notice and this permission notice shall be |
| 14 | * included in all copies or substantial portions of the Software. |
| 15 | * |
| 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT |
| 20 | * HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
| 21 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
| 23 | * DEALINGS IN THE SOFTWARE. |
| 24 | * |
| 25 | * Except as contained in this notice, the name(s) of the above |
| 26 | * copyright holders shall not be used in advertising or otherwise to |
| 27 | * promote the sale, use or other dealings in this Software without |
| 28 | * prior written authorization. |
| 29 | */ |
| 30 | |
| 31 | #include <CoreFoundation/CoreFoundation.h> |
| 32 | #include <AvailabilityMacros.h> |
| 33 | |
| 34 | #ifdef HAVE_DIX_CONFIG_H |
| 35 | #include <dix-config.h> |
| 36 | #endif |
| 37 | |
| 38 | #include <X11/Xlib.h> |
| 39 | #include <assert.h> |
| 40 | #include <unistd.h> |
| 41 | #include <stdio.h> |
| 42 | #include <string.h> |
| 43 | #include <stdlib.h> |
| 44 | #include <stdbool.h> |
| 45 | #include <signal.h> |
| 46 | |
| 47 | #ifdef HAVE_LIBDISPATCH |
| 48 | #include <dispatch/dispatch.h> |
| 49 | #else |
| 50 | #include <pthread.h> |
| 51 | #endif |
| 52 | |
| 53 | #include <sys/socket.h> |
| 54 | #include <sys/un.h> |
| 55 | |
| 56 | #include <fcntl.h> |
| 57 | |
| 58 | #include <mach/mach.h> |
| 59 | #include <mach/mach_error.h> |
| 60 | #include <servers/bootstrap.h> |
| 61 | #include "mach_startup.h" |
| 62 | #include "mach_startupServer.h" |
| 63 | |
| 64 | #include "console_redirect.h" |
| 65 | |
| 66 | /* From darwinEvents.c ... but don't want to pull in all the server cruft */ |
| 67 | void |
| 68 | DarwinListenOnOpenFD(int fd); |
| 69 | |
| 70 | extern aslclient aslc; |
| 71 | |
| 72 | /* Ditto, from os/log.c */ |
| 73 | extern void |
| 74 | ErrorF(const char *f, ...) _X_ATTRIBUTE_PRINTF(1, 2); |
| 75 | extern void |
| 76 | FatalError(const char *f, ...) _X_ATTRIBUTE_PRINTF(1, 2) _X_NORETURN; |
| 77 | |
| 78 | extern int noPanoramiXExtension; |
| 79 | |
| 80 | #define DEFAULT_CLIENT X11BINDIR "/xterm" |
| 81 | #define DEFAULT_STARTX X11BINDIR "/startx -- " X11BINDIR "/Xquartz" |
| 82 | #define DEFAULT_SHELL "/bin/sh" |
| 83 | |
| 84 | #ifndef BUILD_DATE |
| 85 | #define BUILD_DATE "" |
| 86 | #endif |
| 87 | #ifndef XSERVER_VERSION |
| 88 | #define XSERVER_VERSION "?" |
| 89 | #endif |
| 90 | |
| 91 | static char __crashreporter_info_buff__[4096] = { 0 }; |
| 92 | static const char *__crashreporter_info__ __attribute__((__used__)) = |
| 93 | &__crashreporter_info_buff__[0]; |
| 94 | #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 |
| 95 | // This is actually a toolchain requirement, but I'm not sure the correct check, |
| 96 | // but it should be fine to just only include it for Leopard and later. This line |
| 97 | // just tells the linker to never strip this symbol (such as for space optimization) |
| 98 | asm (".desc ___crashreporter_info__, 0x10"); |
| 99 | #endif |
| 100 | |
| 101 | static const char *__crashreporter_info__base = |
| 102 | "X.Org X Server " XSERVER_VERSION " Build Date: " BUILD_DATE; |
| 103 | |
| 104 | char *bundle_id_prefix = NULL; |
| 105 | static char *server_bootstrap_name = NULL; |
| 106 | |
| 107 | #define DEBUG 1 |
| 108 | |
| 109 | /* This is in quartzStartup.c */ |
| 110 | int |
| 111 | server_main(int argc, char **argv, char **envp); |
| 112 | |
| 113 | static int |
| 114 | execute(const char *command); |
| 115 | static char * |
| 116 | command_from_prefs(const char *key, const char *default_value); |
| 117 | |
| 118 | static char *pref_app_to_run; |
| 119 | static char *pref_login_shell; |
| 120 | static char *pref_startx_script; |
| 121 | |
| 122 | #ifndef HAVE_LIBDISPATCH |
| 123 | /*** Pthread Magics ***/ |
| 124 | static pthread_t |
| 125 | create_thread(void *(*func)(void *), void *arg) |
| 126 | { |
| 127 | pthread_attr_t attr; |
| 128 | pthread_t tid; |
| 129 | |
| 130 | pthread_attr_init(&attr); |
| 131 | pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); |
| 132 | pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); |
| 133 | pthread_create(&tid, &attr, func, arg); |
| 134 | pthread_attr_destroy(&attr); |
| 135 | |
| 136 | return tid; |
| 137 | } |
| 138 | #endif |
| 139 | |
| 140 | /*** Mach-O IPC Stuffs ***/ |
| 141 | |
| 142 | union MaxMsgSize { |
| 143 | union __RequestUnion__mach_startup_subsystem req; |
| 144 | union __ReplyUnion__mach_startup_subsystem rep; |
| 145 | }; |
| 146 | |
| 147 | static mach_port_t |
| 148 | checkin_or_register(char *bname) |
| 149 | { |
| 150 | kern_return_t kr; |
| 151 | mach_port_t mp; |
| 152 | |
| 153 | /* If we're started by launchd or the old mach_init */ |
| 154 | kr = bootstrap_check_in(bootstrap_port, bname, &mp); |
| 155 | if (kr == KERN_SUCCESS) |
| 156 | return mp; |
| 157 | |
| 158 | /* We probably were not started by launchd or the old mach_init */ |
| 159 | kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &mp); |
| 160 | if (kr != KERN_SUCCESS) { |
| 161 | ErrorF("mach_port_allocate(): %s\n", mach_error_string(kr)); |
| 162 | exit(EXIT_FAILURE); |
| 163 | } |
| 164 | |
| 165 | kr = mach_port_insert_right( |
| 166 | mach_task_self(), mp, mp, MACH_MSG_TYPE_MAKE_SEND); |
| 167 | if (kr != KERN_SUCCESS) { |
| 168 | ErrorF("mach_port_insert_right(): %s\n", mach_error_string(kr)); |
| 169 | exit(EXIT_FAILURE); |
| 170 | } |
| 171 | |
| 172 | #ifdef __clang__ |
| 173 | #pragma clang diagnostic push |
| 174 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" // bootstrap_register |
| 175 | #endif |
| 176 | kr = bootstrap_register(bootstrap_port, bname, mp); |
| 177 | #ifdef __clang__ |
| 178 | #pragma clang diagnostic pop |
| 179 | #endif |
| 180 | |
| 181 | if (kr != KERN_SUCCESS) { |
| 182 | ErrorF("bootstrap_register(): %s\n", mach_error_string(kr)); |
| 183 | exit(EXIT_FAILURE); |
| 184 | } |
| 185 | |
| 186 | return mp; |
| 187 | } |
| 188 | |
| 189 | /*** $DISPLAY handoff ***/ |
| 190 | static int |
| 191 | accept_fd_handoff(int connected_fd) |
| 192 | { |
| 193 | int launchd_fd; |
| 194 | |
| 195 | char databuf[] = "display"; |
| 196 | struct iovec iov[1]; |
| 197 | |
| 198 | union { |
| 199 | struct cmsghdr hdr; |
| 200 | char bytes[CMSG_SPACE(sizeof(int))]; |
| 201 | } buf; |
| 202 | |
| 203 | struct msghdr msg; |
| 204 | struct cmsghdr *cmsg; |
| 205 | |
| 206 | iov[0].iov_base = databuf; |
| 207 | iov[0].iov_len = sizeof(databuf); |
| 208 | |
| 209 | msg.msg_iov = iov; |
| 210 | msg.msg_iovlen = 1; |
| 211 | msg.msg_control = buf.bytes; |
| 212 | msg.msg_controllen = sizeof(buf); |
| 213 | msg.msg_name = 0; |
| 214 | msg.msg_namelen = 0; |
| 215 | msg.msg_flags = 0; |
| 216 | |
| 217 | cmsg = CMSG_FIRSTHDR(&msg); |
| 218 | cmsg->cmsg_level = SOL_SOCKET; |
| 219 | cmsg->cmsg_type = SCM_RIGHTS; |
| 220 | cmsg->cmsg_len = CMSG_LEN(sizeof(int)); |
| 221 | |
| 222 | msg.msg_controllen = cmsg->cmsg_len; |
| 223 | |
| 224 | *((int *)CMSG_DATA(cmsg)) = -1; |
| 225 | |
| 226 | if (recvmsg(connected_fd, &msg, 0) < 0) { |
| 227 | ErrorF( |
| 228 | "X11.app: Error receiving $DISPLAY file descriptor. recvmsg() error: %s\n", |
| 229 | strerror(errno)); |
| 230 | return -1; |
| 231 | } |
| 232 | |
| 233 | launchd_fd = *((int *)CMSG_DATA(cmsg)); |
| 234 | |
| 235 | return launchd_fd; |
| 236 | } |
| 237 | |
| 238 | typedef struct { |
| 239 | int fd; |
| 240 | string_t filename; |
| 241 | } socket_handoff_t; |
| 242 | |
| 243 | /* This thread accepts an incoming connection and hands off the file |
| 244 | * descriptor for the new connection to accept_fd_handoff() |
| 245 | */ |
| 246 | #ifdef HAVE_LIBDISPATCH |
| 247 | static void |
| 248 | socket_handoff(socket_handoff_t *handoff_data) |
| 249 | { |
| 250 | #else |
| 251 | static void * |
| 252 | socket_handoff_thread(void *arg) |
| 253 | { |
| 254 | socket_handoff_t *handoff_data = (socket_handoff_t *)arg; |
| 255 | #endif |
| 256 | |
| 257 | int launchd_fd = -1; |
| 258 | int connected_fd; |
| 259 | |
| 260 | /* Now actually get the passed file descriptor from this connection |
| 261 | * If we encounter an error, keep listening. |
| 262 | */ |
| 263 | while (launchd_fd == -1) { |
| 264 | connected_fd = accept(handoff_data->fd, NULL, NULL); |
| 265 | if (connected_fd == -1) { |
| 266 | ErrorF( |
| 267 | "X11.app: Failed to accept incoming connection on socket (fd=%d): %s\n", |
| 268 | handoff_data->fd, strerror(errno)); |
| 269 | sleep(2); |
| 270 | continue; |
| 271 | } |
| 272 | |
| 273 | launchd_fd = accept_fd_handoff(connected_fd); |
| 274 | if (launchd_fd == -1) |
| 275 | ErrorF( |
| 276 | "X11.app: Error receiving $DISPLAY file descriptor, no descriptor received? Waiting for another connection.\n"); |
| 277 | |
| 278 | close(connected_fd); |
| 279 | } |
| 280 | |
| 281 | close(handoff_data->fd); |
| 282 | unlink(handoff_data->filename); |
| 283 | free(handoff_data); |
| 284 | |
| 285 | ErrorF( |
| 286 | "X11.app Handing off fd to server thread via DarwinListenOnOpenFD(%d)\n", |
| 287 | launchd_fd); |
| 288 | DarwinListenOnOpenFD(launchd_fd); |
| 289 | |
| 290 | #ifndef HAVE_LIBDISPATCH |
| 291 | return NULL; |
| 292 | #endif |
| 293 | } |
| 294 | |
| 295 | static int |
| 296 | create_socket(char *filename_out) |
| 297 | { |
| 298 | struct sockaddr_un servaddr_un; |
| 299 | struct sockaddr *servaddr; |
| 300 | socklen_t servaddr_len; |
| 301 | int ret_fd; |
| 302 | size_t try, try_max; |
| 303 | |
| 304 | for (try = 0, try_max = 5; try < try_max; try++) { |
| 305 | tmpnam(filename_out); |
| 306 | |
| 307 | /* Setup servaddr_un */ |
| 308 | memset(&servaddr_un, 0, sizeof(struct sockaddr_un)); |
| 309 | servaddr_un.sun_family = AF_UNIX; |
| 310 | strlcpy(servaddr_un.sun_path, filename_out, |
| 311 | sizeof(servaddr_un.sun_path)); |
| 312 | |
| 313 | servaddr = (struct sockaddr *)&servaddr_un; |
| 314 | servaddr_len = sizeof(struct sockaddr_un) - |
| 315 | sizeof(servaddr_un.sun_path) + strlen(filename_out); |
| 316 | |
| 317 | ret_fd = socket(PF_UNIX, SOCK_STREAM, 0); |
| 318 | if (ret_fd == -1) { |
| 319 | ErrorF( |
| 320 | "X11.app: Failed to create socket (try %d / %d): %s - %s\n", |
| 321 | (int)try + 1, (int)try_max, filename_out, strerror(errno)); |
| 322 | continue; |
| 323 | } |
| 324 | |
| 325 | if (bind(ret_fd, servaddr, servaddr_len) != 0) { |
| 326 | ErrorF("X11.app: Failed to bind socket: %d - %s\n", errno, |
| 327 | strerror( |
| 328 | errno)); |
| 329 | close(ret_fd); |
| 330 | return 0; |
| 331 | } |
| 332 | |
| 333 | if (listen(ret_fd, 10) != 0) { |
| 334 | ErrorF("X11.app: Failed to listen to socket: %s - %d - %s\n", |
| 335 | filename_out, errno, strerror( |
| 336 | errno)); |
| 337 | close(ret_fd); |
| 338 | return 0; |
| 339 | } |
| 340 | |
| 341 | #ifdef DEBUG |
| 342 | ErrorF("X11.app: Listening on socket for fd handoff: (%d) %s\n", |
| 343 | ret_fd, |
| 344 | filename_out); |
| 345 | #endif |
| 346 | |
| 347 | return ret_fd; |
| 348 | } |
| 349 | |
| 350 | return 0; |
| 351 | } |
| 352 | |
| 353 | static int launchd_socket_handed_off = 0; |
| 354 | |
| 355 | kern_return_t |
| 356 | do_request_fd_handoff_socket(mach_port_t port, string_t filename) |
| 357 | { |
| 358 | socket_handoff_t *handoff_data; |
| 359 | |
| 360 | launchd_socket_handed_off = 1; |
| 361 | |
| 362 | handoff_data = (socket_handoff_t *)calloc(1, sizeof(socket_handoff_t)); |
| 363 | if (!handoff_data) { |
| 364 | ErrorF("X11.app: Error allocating memory for handoff_data\n"); |
| 365 | return KERN_FAILURE; |
| 366 | } |
| 367 | |
| 368 | handoff_data->fd = create_socket(handoff_data->filename); |
| 369 | if (!handoff_data->fd) { |
| 370 | free(handoff_data); |
| 371 | return KERN_FAILURE; |
| 372 | } |
| 373 | |
| 374 | strlcpy(filename, handoff_data->filename, STRING_T_SIZE); |
| 375 | |
| 376 | #ifdef HAVE_LIBDISPATCH |
| 377 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, |
| 378 | 0), ^ { |
| 379 | socket_handoff(handoff_data); |
| 380 | }); |
| 381 | #else |
| 382 | create_thread(socket_handoff_thread, handoff_data); |
| 383 | #endif |
| 384 | |
| 385 | #ifdef DEBUG |
| 386 | ErrorF( |
| 387 | "X11.app: Thread created for handoff. Returning success to tell caller to connect and push the fd.\n"); |
| 388 | #endif |
| 389 | |
| 390 | return KERN_SUCCESS; |
| 391 | } |
| 392 | |
| 393 | kern_return_t |
| 394 | do_request_pid(mach_port_t port, int *my_pid) |
| 395 | { |
| 396 | *my_pid = getpid(); |
| 397 | return KERN_SUCCESS; |
| 398 | } |
| 399 | |
| 400 | /*** Server Startup ***/ |
| 401 | kern_return_t |
| 402 | do_start_x11_server(mach_port_t port, string_array_t argv, |
| 403 | mach_msg_type_number_t argvCnt, |
| 404 | string_array_t envp, |
| 405 | mach_msg_type_number_t envpCnt) |
| 406 | { |
| 407 | /* And now back to char ** */ |
| 408 | char **_argv = alloca((argvCnt + 1) * sizeof(char *)); |
| 409 | char **_envp = alloca((envpCnt + 1) * sizeof(char *)); |
| 410 | size_t i; |
| 411 | |
| 412 | /* If we didn't get handed a launchd DISPLAY socket, we should |
| 413 | * unset DISPLAY or we can run into problems with pbproxy |
| 414 | */ |
| 415 | if (!launchd_socket_handed_off) { |
| 416 | ErrorF("X11.app: No launchd socket handed off, unsetting DISPLAY\n"); |
| 417 | unsetenv("DISPLAY"); |
| 418 | } |
| 419 | |
| 420 | if (!_argv || !_envp) { |
| 421 | return KERN_FAILURE; |
| 422 | } |
| 423 | |
| 424 | ErrorF("X11.app: do_start_x11_server(): argc=%d\n", argvCnt); |
| 425 | for (i = 0; i < argvCnt; i++) { |
| 426 | _argv[i] = argv[i]; |
| 427 | ErrorF("\targv[%u] = %s\n", (unsigned)i, argv[i]); |
| 428 | } |
| 429 | _argv[argvCnt] = NULL; |
| 430 | |
| 431 | for (i = 0; i < envpCnt; i++) { |
| 432 | _envp[i] = envp[i]; |
| 433 | } |
| 434 | _envp[envpCnt] = NULL; |
| 435 | |
| 436 | if (server_main(argvCnt, _argv, _envp) == 0) |
| 437 | return KERN_SUCCESS; |
| 438 | else |
| 439 | return KERN_FAILURE; |
| 440 | } |
| 441 | |
| 442 | static int |
| 443 | startup_trigger(int argc, char **argv, char **envp) |
| 444 | { |
| 445 | Display *display; |
| 446 | const char *s; |
| 447 | |
| 448 | /* Take care of the case where we're called like a normal DDX */ |
| 449 | if (argc > 1 && argv[1][0] == ':') { |
| 450 | size_t i; |
| 451 | kern_return_t kr; |
| 452 | mach_port_t mp; |
| 453 | string_array_t newenvp; |
| 454 | string_array_t newargv; |
| 455 | |
| 456 | /* We need to count envp */ |
| 457 | int envpc; |
| 458 | for (envpc = 0; envp[envpc]; envpc++) ; |
| 459 | |
| 460 | /* We have fixed-size string lengths due to limitations in IPC, |
| 461 | * so we need to copy our argv and envp. |
| 462 | */ |
| 463 | newargv = (string_array_t)alloca(argc * sizeof(string_t)); |
| 464 | newenvp = (string_array_t)alloca(envpc * sizeof(string_t)); |
| 465 | |
| 466 | if (!newargv || !newenvp) { |
| 467 | ErrorF("Memory allocation failure\n"); |
| 468 | exit(EXIT_FAILURE); |
| 469 | } |
| 470 | |
| 471 | for (i = 0; i < argc; i++) { |
| 472 | strlcpy(newargv[i], argv[i], STRING_T_SIZE); |
| 473 | } |
| 474 | for (i = 0; i < envpc; i++) { |
| 475 | strlcpy(newenvp[i], envp[i], STRING_T_SIZE); |
| 476 | } |
| 477 | |
| 478 | kr = bootstrap_look_up(bootstrap_port, server_bootstrap_name, &mp); |
| 479 | if (kr != KERN_SUCCESS) { |
| 480 | #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 |
| 481 | ErrorF("bootstrap_look_up(%s): %s\n", server_bootstrap_name, |
| 482 | bootstrap_strerror( |
| 483 | kr)); |
| 484 | #else |
| 485 | ErrorF("bootstrap_look_up(%s): %ul\n", server_bootstrap_name, |
| 486 | (unsigned long)kr); |
| 487 | #endif |
| 488 | exit(EXIT_FAILURE); |
| 489 | } |
| 490 | |
| 491 | kr = start_x11_server(mp, newargv, argc, newenvp, envpc); |
| 492 | if (kr != KERN_SUCCESS) { |
| 493 | ErrorF("start_x11_server: %s\n", mach_error_string(kr)); |
| 494 | exit(EXIT_FAILURE); |
| 495 | } |
| 496 | exit(EXIT_SUCCESS); |
| 497 | } |
| 498 | |
| 499 | /* If we have a process serial number and it's our only arg, act as if |
| 500 | * the user double clicked the app bundle: launch app_to_run if possible |
| 501 | */ |
| 502 | if (argc == 1 || (argc == 2 && !strncmp(argv[1], "-psn_", 5))) { |
| 503 | /* Now, try to open a display, if so, run the launcher */ |
| 504 | display = XOpenDisplay(NULL); |
| 505 | if (display) { |
| 506 | /* Could open the display, start the launcher */ |
| 507 | XCloseDisplay(display); |
| 508 | |
| 509 | return execute(pref_app_to_run); |
| 510 | } |
| 511 | } |
| 512 | |
| 513 | /* Start the server */ |
| 514 | if ((s = getenv("DISPLAY"))) { |
| 515 | ErrorF( |
| 516 | "X11.app: Could not connect to server (DISPLAY=\"%s\", unsetting). Starting X server.\n", |
| 517 | s); |
| 518 | unsetenv("DISPLAY"); |
| 519 | } |
| 520 | else { |
| 521 | ErrorF( |
| 522 | "X11.app: Could not connect to server (DISPLAY is not set). Starting X server.\n"); |
| 523 | } |
| 524 | return execute(pref_startx_script); |
| 525 | } |
| 526 | |
| 527 | /** Setup the environment we want our child processes to inherit */ |
| 528 | static void |
| 529 | ensure_path(const char *dir) |
| 530 | { |
| 531 | char buf[1024], *temp; |
| 532 | |
| 533 | /* Make sure /usr/X11/bin is in the $PATH */ |
| 534 | temp = getenv("PATH"); |
| 535 | if (temp == NULL || temp[0] == 0) { |
| 536 | snprintf(buf, sizeof(buf), |
| 537 | "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:%s", |
| 538 | dir); |
| 539 | setenv("PATH", buf, TRUE); |
| 540 | } |
| 541 | else if (strnstr(temp, X11BINDIR, sizeof(temp)) == NULL) { |
| 542 | snprintf(buf, sizeof(buf), "%s:%s", temp, dir); |
| 543 | setenv("PATH", buf, TRUE); |
| 544 | } |
| 545 | } |
| 546 | |
| 547 | static void |
| 548 | setup_console_redirect(const char *bundle_id) |
| 549 | { |
| 550 | char *asl_sender; |
| 551 | char *asl_facility; |
| 552 | |
| 553 | asprintf(&asl_sender, "%s.server", bundle_id); |
| 554 | assert(asl_sender); |
| 555 | |
| 556 | asl_facility = strdup(bundle_id); |
| 557 | assert(asl_facility); |
| 558 | if (strcmp(asl_facility + strlen(asl_facility) - 4, ".X11") == 0) |
| 559 | asl_facility[strlen(asl_facility) - 4] = '\0'; |
| 560 | |
| 561 | assert(aslc = asl_open(asl_sender, asl_facility, ASL_OPT_NO_DELAY)); |
| 562 | free(asl_sender); |
| 563 | free(asl_facility); |
| 564 | |
| 565 | asl_set_filter(aslc, ASL_FILTER_MASK_UPTO(ASL_LEVEL_WARNING)); |
| 566 | |
| 567 | #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1080 |
| 568 | # if MAC_OS_X_VERSION_MIN_REQUIRED < 1080 |
| 569 | if (asl_log_descriptor) |
| 570 | # endif |
| 571 | { |
| 572 | asl_log_descriptor(aslc, NULL, ASL_LEVEL_INFO, STDOUT_FILENO, ASL_LOG_DESCRIPTOR_WRITE); |
| 573 | asl_log_descriptor(aslc, NULL, ASL_LEVEL_NOTICE, STDERR_FILENO, ASL_LOG_DESCRIPTOR_WRITE); |
| 574 | } |
| 575 | # if MAC_OS_X_VERSION_MIN_REQUIRED < 1080 |
| 576 | else { |
| 577 | xq_asl_capture_fd(aslc, NULL, ASL_LEVEL_INFO, STDOUT_FILENO); |
| 578 | xq_asl_capture_fd(aslc, NULL, ASL_LEVEL_NOTICE, STDERR_FILENO); |
| 579 | } |
| 580 | # endif |
| 581 | #else |
| 582 | xq_asl_capture_fd(aslc, NULL, ASL_LEVEL_INFO, STDOUT_FILENO); |
| 583 | xq_asl_capture_fd(aslc, NULL, ASL_LEVEL_NOTICE, STDERR_FILENO); |
| 584 | #endif |
| 585 | } |
| 586 | |
| 587 | static void |
| 588 | setup_env(void) |
| 589 | { |
| 590 | char *temp; |
| 591 | const char *pds = NULL; |
| 592 | const char *disp = getenv("DISPLAY"); |
| 593 | size_t len; |
| 594 | |
| 595 | /* Pass on our prefs domain to startx and its inheritors (mainly for |
| 596 | * quartz-wm and the Xquartz stub's MachIPC) |
| 597 | */ |
| 598 | CFBundleRef bundle = CFBundleGetMainBundle(); |
| 599 | if (bundle) { |
| 600 | CFStringRef pd = CFBundleGetIdentifier(bundle); |
| 601 | if (pd) { |
| 602 | pds = CFStringGetCStringPtr(pd, 0); |
| 603 | } |
| 604 | } |
| 605 | |
| 606 | /* fallback to hardcoded value if we can't discover it */ |
| 607 | if (!pds) { |
| 608 | pds = BUNDLE_ID_PREFIX ".X11"; |
| 609 | } |
| 610 | |
| 611 | setup_console_redirect(pds); |
| 612 | |
| 613 | server_bootstrap_name = strdup(pds); |
| 614 | if (!server_bootstrap_name) { |
| 615 | ErrorF("X11.app: Memory allocation error.\n"); |
| 616 | exit(1); |
| 617 | } |
| 618 | setenv("X11_PREFS_DOMAIN", server_bootstrap_name, 1); |
| 619 | |
| 620 | len = strlen(server_bootstrap_name); |
| 621 | bundle_id_prefix = malloc(sizeof(char) * (len - 3)); |
| 622 | if (!bundle_id_prefix) { |
| 623 | ErrorF("X11.app: Memory allocation error.\n"); |
| 624 | exit(1); |
| 625 | } |
| 626 | strlcpy(bundle_id_prefix, server_bootstrap_name, len - 3); |
| 627 | |
| 628 | /* We need to unset DISPLAY if it is not our socket */ |
| 629 | if (disp) { |
| 630 | /* s = basename(disp) */ |
| 631 | const char *d, *s; |
| 632 | for (s = NULL, d = disp; *d; d++) { |
| 633 | if (*d == '/') |
| 634 | s = d + 1; |
| 635 | } |
| 636 | |
| 637 | if (s && *s) { |
| 638 | if (strcmp(bundle_id_prefix, |
| 639 | "org.x") == 0 && strcmp(s, ":0") == 0) { |
| 640 | ErrorF( |
| 641 | "X11.app: Detected old style launchd DISPLAY, please update xinit.\n"); |
| 642 | } |
| 643 | else { |
| 644 | temp = (char *)malloc(sizeof(char) * len); |
| 645 | if (!temp) { |
| 646 | ErrorF( |
| 647 | "X11.app: Memory allocation error creating space for socket name test.\n"); |
| 648 | exit(1); |
| 649 | } |
| 650 | strlcpy(temp, bundle_id_prefix, len); |
| 651 | strlcat(temp, ":0", len); |
| 652 | |
| 653 | if (strcmp(temp, s) != 0) { |
| 654 | /* If we don't have a match, unset it. */ |
| 655 | ErrorF( |
| 656 | "X11.app: DISPLAY (\"%s\") does not match our id (\"%s\"), unsetting.\n", |
| 657 | disp, bundle_id_prefix); |
| 658 | unsetenv("DISPLAY"); |
| 659 | } |
| 660 | free(temp); |
| 661 | } |
| 662 | } |
| 663 | else { |
| 664 | /* The DISPLAY environment variable is not formatted like a launchd socket, so reset. */ |
| 665 | ErrorF( |
| 666 | "X11.app: DISPLAY does not look like a launchd set variable, unsetting.\n"); |
| 667 | unsetenv("DISPLAY"); |
| 668 | } |
| 669 | } |
| 670 | |
| 671 | /* Make sure PATH is right */ |
| 672 | ensure_path(X11BINDIR); |
| 673 | |
| 674 | /* cd $HOME */ |
| 675 | temp = getenv("HOME"); |
| 676 | if (temp != NULL && temp[0] != '\0') |
| 677 | chdir(temp); |
| 678 | } |
| 679 | |
| 680 | /*** Main ***/ |
| 681 | int |
| 682 | main(int argc, char **argv, char **envp) |
| 683 | { |
| 684 | Bool listenOnly = FALSE; |
| 685 | int i; |
| 686 | mach_msg_size_t mxmsgsz = sizeof(union MaxMsgSize) + MAX_TRAILER_SIZE; |
| 687 | mach_port_t mp; |
| 688 | kern_return_t kr; |
| 689 | |
| 690 | /* Setup our environment for our children */ |
| 691 | setup_env(); |
| 692 | |
| 693 | /* The server must not run the PanoramiX operations. */ |
| 694 | noPanoramiXExtension = TRUE; |
| 695 | |
| 696 | /* Setup the initial crasherporter info */ |
| 697 | strlcpy(__crashreporter_info_buff__, __crashreporter_info__base, |
| 698 | sizeof(__crashreporter_info_buff__)); |
| 699 | |
| 700 | ErrorF("X11.app: main(): argc=%d\n", argc); |
| 701 | for (i = 0; i < argc; i++) { |
| 702 | ErrorF("\targv[%u] = %s\n", (unsigned)i, argv[i]); |
| 703 | if (!strcmp(argv[i], "--listenonly")) { |
| 704 | listenOnly = TRUE; |
| 705 | } |
| 706 | } |
| 707 | |
| 708 | mp = checkin_or_register(server_bootstrap_name); |
| 709 | if (mp == MACH_PORT_NULL) { |
| 710 | ErrorF("NULL mach service: %s", server_bootstrap_name); |
| 711 | return EXIT_FAILURE; |
| 712 | } |
| 713 | |
| 714 | /* Check if we need to do something other than listen, and make another |
| 715 | * thread handle it. |
| 716 | */ |
| 717 | if (!listenOnly) { |
| 718 | pid_t child1, child2; |
| 719 | int status; |
| 720 | |
| 721 | pref_app_to_run = command_from_prefs("app_to_run", DEFAULT_CLIENT); |
| 722 | assert(pref_app_to_run); |
| 723 | |
| 724 | pref_login_shell = command_from_prefs("login_shell", DEFAULT_SHELL); |
| 725 | assert(pref_login_shell); |
| 726 | |
| 727 | pref_startx_script = command_from_prefs("startx_script", |
| 728 | DEFAULT_STARTX); |
| 729 | assert(pref_startx_script); |
| 730 | |
| 731 | /* Do the fork-twice trick to avoid having to reap zombies */ |
| 732 | child1 = fork(); |
| 733 | switch (child1) { |
| 734 | case -1: /* error */ |
| 735 | FatalError("fork() failed: %s\n", strerror(errno)); |
| 736 | |
| 737 | case 0: /* child1 */ |
| 738 | child2 = fork(); |
| 739 | |
| 740 | switch (child2) { |
| 741 | int max_files; |
| 742 | |
| 743 | case -1: /* error */ |
| 744 | FatalError("fork() failed: %s\n", strerror(errno)); |
| 745 | |
| 746 | case 0: /* child2 */ |
| 747 | /* close all open files except for standard streams */ |
| 748 | max_files = sysconf(_SC_OPEN_MAX); |
| 749 | for (i = 3; i < max_files; i++) |
| 750 | close(i); |
| 751 | |
| 752 | /* ensure stdin is on /dev/null */ |
| 753 | close(0); |
| 754 | open("/dev/null", O_RDONLY); |
| 755 | |
| 756 | return startup_trigger(argc, argv, envp); |
| 757 | |
| 758 | default: /* parent (child1) */ |
| 759 | _exit(0); |
| 760 | } |
| 761 | break; |
| 762 | |
| 763 | default: /* parent */ |
| 764 | waitpid(child1, &status, 0); |
| 765 | } |
| 766 | |
| 767 | free(pref_app_to_run); |
| 768 | free(pref_login_shell); |
| 769 | free(pref_startx_script); |
| 770 | } |
| 771 | |
| 772 | /* Main event loop */ |
| 773 | ErrorF("Waiting for startup parameters via Mach IPC.\n"); |
| 774 | kr = mach_msg_server(mach_startup_server, mxmsgsz, mp, 0); |
| 775 | if (kr != KERN_SUCCESS) { |
| 776 | ErrorF("%s.X11(mp): %s\n", BUNDLE_ID_PREFIX, mach_error_string(kr)); |
| 777 | return EXIT_FAILURE; |
| 778 | } |
| 779 | |
| 780 | return EXIT_SUCCESS; |
| 781 | } |
| 782 | |
| 783 | static int |
| 784 | execute(const char *command) |
| 785 | { |
| 786 | const char *newargv[4]; |
| 787 | const char **p; |
| 788 | |
| 789 | newargv[0] = pref_login_shell; |
| 790 | newargv[1] = "-c"; |
| 791 | newargv[2] = command; |
| 792 | newargv[3] = NULL; |
| 793 | |
| 794 | ErrorF("X11.app: Launching %s:\n", command); |
| 795 | for (p = newargv; *p; p++) { |
| 796 | ErrorF("\targv[%ld] = %s\n", (long int)(p - newargv), *p); |
| 797 | } |
| 798 | |
| 799 | execvp(newargv[0], (char *const *)newargv); |
| 800 | perror("X11.app: Couldn't exec."); |
| 801 | return 1; |
| 802 | } |
| 803 | |
| 804 | static char * |
| 805 | command_from_prefs(const char *key, const char *default_value) |
| 806 | { |
| 807 | char *command = NULL; |
| 808 | |
| 809 | CFStringRef cfKey; |
| 810 | CFPropertyListRef PlistRef; |
| 811 | |
| 812 | if (!key) |
| 813 | return NULL; |
| 814 | |
| 815 | cfKey = CFStringCreateWithCString(NULL, key, kCFStringEncodingASCII); |
| 816 | |
| 817 | if (!cfKey) |
| 818 | return NULL; |
| 819 | |
| 820 | PlistRef = CFPreferencesCopyAppValue(cfKey, |
| 821 | kCFPreferencesCurrentApplication); |
| 822 | |
| 823 | if ((PlistRef == NULL) || |
| 824 | (CFGetTypeID(PlistRef) != CFStringGetTypeID())) { |
| 825 | CFStringRef cfDefaultValue = CFStringCreateWithCString( |
| 826 | NULL, default_value, kCFStringEncodingASCII); |
| 827 | int len = strlen(default_value) + 1; |
| 828 | |
| 829 | if (!cfDefaultValue) |
| 830 | goto command_from_prefs_out; |
| 831 | |
| 832 | CFPreferencesSetAppValue(cfKey, cfDefaultValue, |
| 833 | kCFPreferencesCurrentApplication); |
| 834 | CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication); |
| 835 | CFRelease(cfDefaultValue); |
| 836 | |
| 837 | command = (char *)malloc(len * sizeof(char)); |
| 838 | if (!command) |
| 839 | goto command_from_prefs_out; |
| 840 | strcpy(command, default_value); |
| 841 | } |
| 842 | else { |
| 843 | int len = CFStringGetLength((CFStringRef)PlistRef) + 1; |
| 844 | command = (char *)malloc(len * sizeof(char)); |
| 845 | if (!command) |
| 846 | goto command_from_prefs_out; |
| 847 | CFStringGetCString((CFStringRef)PlistRef, command, len, |
| 848 | kCFStringEncodingASCII); |
| 849 | } |
| 850 | |
| 851 | command_from_prefs_out: |
| 852 | if (PlistRef) |
| 853 | CFRelease(PlistRef); |
| 854 | if (cfKey) |
| 855 | CFRelease(cfKey); |
| 856 | return command; |
| 857 | } |