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