1 /* main.c -- X application launcher
2 * Copyright (c) 2007 Jeremy Huddleston
3 * Copyright (c) 2007-2012 Apple Inc. All rights reserved.
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:
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
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.
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.
31 #include <CoreFoundation/CoreFoundation.h>
32 #include <AvailabilityMacros.h>
34 #ifdef HAVE_DIX_CONFIG_H
35 #include <dix-config.h>
47 #ifdef HAVE_LIBDISPATCH
48 #include <dispatch/dispatch.h>
53 #include <sys/socket.h>
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"
64 #include "console_redirect.h"
66 /* From darwinEvents.c ... but don't want to pull in all the server cruft */
68 DarwinListenOnOpenFD(int fd
);
70 extern aslclient aslc
;
72 /* Ditto, from os/log.c */
74 ErrorF(const char *f
, ...) _X_ATTRIBUTE_PRINTF(1, 2);
76 FatalError(const char *f
, ...) _X_ATTRIBUTE_PRINTF(1, 2) _X_NORETURN
;
78 extern int noPanoramiXExtension
;
80 #define DEFAULT_CLIENT X11BINDIR "/xterm"
81 #define DEFAULT_STARTX X11BINDIR "/startx -- " X11BINDIR "/Xquartz"
82 #define DEFAULT_SHELL "/bin/sh"
87 #ifndef XSERVER_VERSION
88 #define XSERVER_VERSION "?"
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");
101 static const char *__crashreporter_info__base
=
102 "X.Org X Server " XSERVER_VERSION
" Build Date: " BUILD_DATE
;
104 char *bundle_id_prefix
= NULL
;
105 static char *server_bootstrap_name
= NULL
;
109 /* This is in quartzStartup.c */
111 server_main(int argc
, char **argv
, char **envp
);
114 execute(const char *command
);
116 command_from_prefs(const char *key
, const char *default_value
);
118 static char *pref_app_to_run
;
119 static char *pref_login_shell
;
120 static char *pref_startx_script
;
122 #ifndef HAVE_LIBDISPATCH
123 /*** Pthread Magics ***/
125 create_thread(void *(*func
)(void *), void *arg
)
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
);
140 /*** Mach-O IPC Stuffs ***/
143 union __RequestUnion__mach_startup_subsystem req
;
144 union __ReplyUnion__mach_startup_subsystem rep
;
148 checkin_or_register(char *bname
)
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
)
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
));
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
));
173 #pragma clang diagnostic push
174 #pragma clang diagnostic ignored "-Wdeprecated-declarations" // bootstrap_register
176 kr
= bootstrap_register(bootstrap_port
, bname
, mp
);
178 #pragma clang diagnostic pop
181 if (kr
!= KERN_SUCCESS
) {
182 ErrorF("bootstrap_register(): %s\n", mach_error_string(kr
));
189 /*** $DISPLAY handoff ***/
191 accept_fd_handoff(int connected_fd
)
195 char databuf
[] = "display";
200 char bytes
[CMSG_SPACE(sizeof(int))];
204 struct cmsghdr
*cmsg
;
206 iov
[0].iov_base
= databuf
;
207 iov
[0].iov_len
= sizeof(databuf
);
211 msg
.msg_control
= buf
.bytes
;
212 msg
.msg_controllen
= sizeof(buf
);
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));
222 msg
.msg_controllen
= cmsg
->cmsg_len
;
224 *((int *)CMSG_DATA(cmsg
)) = -1;
226 if (recvmsg(connected_fd
, &msg
, 0) < 0) {
228 "X11.app: Error receiving $DISPLAY file descriptor. recvmsg() error: %s\n",
233 launchd_fd
= *((int *)CMSG_DATA(cmsg
));
243 /* This thread accepts an incoming connection and hands off the file
244 * descriptor for the new connection to accept_fd_handoff()
246 #ifdef HAVE_LIBDISPATCH
248 socket_handoff(socket_handoff_t
*handoff_data
)
252 socket_handoff_thread(void *arg
)
254 socket_handoff_t
*handoff_data
= (socket_handoff_t
*)arg
;
260 /* Now actually get the passed file descriptor from this connection
261 * If we encounter an error, keep listening.
263 while (launchd_fd
== -1) {
264 connected_fd
= accept(handoff_data
->fd
, NULL
, NULL
);
265 if (connected_fd
== -1) {
267 "X11.app: Failed to accept incoming connection on socket (fd=%d): %s\n",
268 handoff_data
->fd
, strerror(errno
));
273 launchd_fd
= accept_fd_handoff(connected_fd
);
274 if (launchd_fd
== -1)
276 "X11.app: Error receiving $DISPLAY file descriptor, no descriptor received? Waiting for another connection.\n");
281 close(handoff_data
->fd
);
282 unlink(handoff_data
->filename
);
286 "X11.app Handing off fd to server thread via DarwinListenOnOpenFD(%d)\n",
288 DarwinListenOnOpenFD(launchd_fd
);
290 #ifndef HAVE_LIBDISPATCH
296 create_socket(char *filename_out
)
298 struct sockaddr_un servaddr_un
;
299 struct sockaddr
*servaddr
;
300 socklen_t servaddr_len
;
304 for (try = 0, try_max
= 5; try < try_max
; try++) {
305 tmpnam(filename_out
);
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
));
313 servaddr
= (struct sockaddr
*)&servaddr_un
;
314 servaddr_len
= sizeof(struct sockaddr_un
) -
315 sizeof(servaddr_un
.sun_path
) + strlen(filename_out
);
317 ret_fd
= socket(PF_UNIX
, SOCK_STREAM
, 0);
320 "X11.app: Failed to create socket (try %d / %d): %s - %s\n",
321 (int)try + 1, (int)try_max
, filename_out
, strerror(errno
));
325 if (bind(ret_fd
, servaddr
, servaddr_len
) != 0) {
326 ErrorF("X11.app: Failed to bind socket: %d - %s\n", errno
,
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(
342 ErrorF("X11.app: Listening on socket for fd handoff: (%d) %s\n",
353 static int launchd_socket_handed_off
= 0;
356 do_request_fd_handoff_socket(mach_port_t port
, string_t filename
)
358 socket_handoff_t
*handoff_data
;
360 launchd_socket_handed_off
= 1;
362 handoff_data
= (socket_handoff_t
*)calloc(1, sizeof(socket_handoff_t
));
364 ErrorF("X11.app: Error allocating memory for handoff_data\n");
368 handoff_data
->fd
= create_socket(handoff_data
->filename
);
369 if (!handoff_data
->fd
) {
374 strlcpy(filename
, handoff_data
->filename
, STRING_T_SIZE
);
376 #ifdef HAVE_LIBDISPATCH
377 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
,
379 socket_handoff(handoff_data
);
382 create_thread(socket_handoff_thread
, handoff_data
);
387 "X11.app: Thread created for handoff. Returning success to tell caller to connect and push the fd.\n");
394 do_request_pid(mach_port_t port
, int *my_pid
)
400 /*** Server Startup ***/
402 do_start_x11_server(mach_port_t port
, string_array_t argv
,
403 mach_msg_type_number_t argvCnt
,
405 mach_msg_type_number_t envpCnt
)
407 /* And now back to char ** */
408 char **_argv
= alloca((argvCnt
+ 1) * sizeof(char *));
409 char **_envp
= alloca((envpCnt
+ 1) * sizeof(char *));
412 /* If we didn't get handed a launchd DISPLAY socket, we should
413 * unset DISPLAY or we can run into problems with pbproxy
415 if (!launchd_socket_handed_off
) {
416 ErrorF("X11.app: No launchd socket handed off, unsetting DISPLAY\n");
420 if (!_argv
|| !_envp
) {
424 ErrorF("X11.app: do_start_x11_server(): argc=%d\n", argvCnt
);
425 for (i
= 0; i
< argvCnt
; i
++) {
427 ErrorF("\targv[%u] = %s\n", (unsigned)i
, argv
[i
]);
429 _argv
[argvCnt
] = NULL
;
431 for (i
= 0; i
< envpCnt
; i
++) {
434 _envp
[envpCnt
] = NULL
;
436 if (server_main(argvCnt
, _argv
, _envp
) == 0)
443 startup_trigger(int argc
, char **argv
, char **envp
)
448 /* Take care of the case where we're called like a normal DDX */
449 if (argc
> 1 && argv
[1][0] == ':') {
453 string_array_t newenvp
;
454 string_array_t newargv
;
456 /* We need to count envp */
458 for (envpc
= 0; envp
[envpc
]; envpc
++) ;
460 /* We have fixed-size string lengths due to limitations in IPC,
461 * so we need to copy our argv and envp.
463 newargv
= (string_array_t
)alloca(argc
* sizeof(string_t
));
464 newenvp
= (string_array_t
)alloca(envpc
* sizeof(string_t
));
466 if (!newargv
|| !newenvp
) {
467 ErrorF("Memory allocation failure\n");
471 for (i
= 0; i
< argc
; i
++) {
472 strlcpy(newargv
[i
], argv
[i
], STRING_T_SIZE
);
474 for (i
= 0; i
< envpc
; i
++) {
475 strlcpy(newenvp
[i
], envp
[i
], STRING_T_SIZE
);
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
,
485 ErrorF("bootstrap_look_up(%s): %ul\n", server_bootstrap_name
,
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
));
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
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
);
506 /* Could open the display, start the launcher */
507 XCloseDisplay(display
);
509 return execute(pref_app_to_run
);
513 /* Start the server */
514 if ((s
= getenv("DISPLAY"))) {
516 "X11.app: Could not connect to server (DISPLAY=\"%s\", unsetting). Starting X server.\n",
522 "X11.app: Could not connect to server (DISPLAY is not set). Starting X server.\n");
524 return execute(pref_startx_script
);
527 /** Setup the environment we want our child processes to inherit */
529 ensure_path(const char *dir
)
531 char buf
[1024], *temp
;
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",
539 setenv("PATH", buf
, TRUE
);
541 else if (strnstr(temp
, X11BINDIR
, sizeof(temp
)) == NULL
) {
542 snprintf(buf
, sizeof(buf
), "%s:%s", temp
, dir
);
543 setenv("PATH", buf
, TRUE
);
548 setup_console_redirect(const char *bundle_id
)
553 asprintf(&asl_sender
, "%s.server", bundle_id
);
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';
561 assert(aslc
= asl_open(asl_sender
, asl_facility
, ASL_OPT_NO_DELAY
));
565 asl_set_filter(aslc
, ASL_FILTER_MASK_UPTO(ASL_LEVEL_WARNING
));
567 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1080
568 # if MAC_OS_X_VERSION_MIN_REQUIRED < 1080
569 if (asl_log_descriptor
)
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
);
575 # if MAC_OS_X_VERSION_MIN_REQUIRED < 1080
577 xq_asl_capture_fd(aslc
, NULL
, ASL_LEVEL_INFO
, STDOUT_FILENO
);
578 xq_asl_capture_fd(aslc
, NULL
, ASL_LEVEL_NOTICE
, STDERR_FILENO
);
582 xq_asl_capture_fd(aslc
, NULL
, ASL_LEVEL_INFO
, STDOUT_FILENO
);
583 xq_asl_capture_fd(aslc
, NULL
, ASL_LEVEL_NOTICE
, STDERR_FILENO
);
591 const char *pds
= NULL
;
592 const char *disp
= getenv("DISPLAY");
595 /* Pass on our prefs domain to startx and its inheritors (mainly for
596 * quartz-wm and the Xquartz stub's MachIPC)
598 CFBundleRef bundle
= CFBundleGetMainBundle();
600 CFStringRef pd
= CFBundleGetIdentifier(bundle
);
602 pds
= CFStringGetCStringPtr(pd
, 0);
606 /* fallback to hardcoded value if we can't discover it */
608 pds
= BUNDLE_ID_PREFIX
".X11";
611 setup_console_redirect(pds
);
613 server_bootstrap_name
= strdup(pds
);
614 if (!server_bootstrap_name
) {
615 ErrorF("X11.app: Memory allocation error.\n");
618 setenv("X11_PREFS_DOMAIN", server_bootstrap_name
, 1);
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");
626 strlcpy(bundle_id_prefix
, server_bootstrap_name
, len
- 3);
628 /* We need to unset DISPLAY if it is not our socket */
630 /* s = basename(disp) */
632 for (s
= NULL
, d
= disp
; *d
; d
++) {
638 if (strcmp(bundle_id_prefix
,
639 "org.x") == 0 && strcmp(s
, ":0") == 0) {
641 "X11.app: Detected old style launchd DISPLAY, please update xinit.\n");
644 temp
= (char *)malloc(sizeof(char) * len
);
647 "X11.app: Memory allocation error creating space for socket name test.\n");
650 strlcpy(temp
, bundle_id_prefix
, len
);
651 strlcat(temp
, ":0", len
);
653 if (strcmp(temp
, s
) != 0) {
654 /* If we don't have a match, unset it. */
656 "X11.app: DISPLAY (\"%s\") does not match our id (\"%s\"), unsetting.\n",
657 disp
, bundle_id_prefix
);
664 /* The DISPLAY environment variable is not formatted like a launchd socket, so reset. */
666 "X11.app: DISPLAY does not look like a launchd set variable, unsetting.\n");
671 /* Make sure PATH is right */
672 ensure_path(X11BINDIR
);
675 temp
= getenv("HOME");
676 if (temp
!= NULL
&& temp
[0] != '\0')
682 main(int argc
, char **argv
, char **envp
)
684 Bool listenOnly
= FALSE
;
686 mach_msg_size_t mxmsgsz
= sizeof(union MaxMsgSize
) + MAX_TRAILER_SIZE
;
690 /* Setup our environment for our children */
693 /* The server must not run the PanoramiX operations. */
694 noPanoramiXExtension
= TRUE
;
696 /* Setup the initial crasherporter info */
697 strlcpy(__crashreporter_info_buff__
, __crashreporter_info__base
,
698 sizeof(__crashreporter_info_buff__
));
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")) {
708 mp
= checkin_or_register(server_bootstrap_name
);
709 if (mp
== MACH_PORT_NULL
) {
710 ErrorF("NULL mach service: %s", server_bootstrap_name
);
714 /* Check if we need to do something other than listen, and make another
718 pid_t child1
, child2
;
721 pref_app_to_run
= command_from_prefs("app_to_run", DEFAULT_CLIENT
);
722 assert(pref_app_to_run
);
724 pref_login_shell
= command_from_prefs("login_shell", DEFAULT_SHELL
);
725 assert(pref_login_shell
);
727 pref_startx_script
= command_from_prefs("startx_script",
729 assert(pref_startx_script
);
731 /* Do the fork-twice trick to avoid having to reap zombies */
735 FatalError("fork() failed: %s\n", strerror(errno
));
744 FatalError("fork() failed: %s\n", strerror(errno
));
747 /* close all open files except for standard streams */
748 max_files
= sysconf(_SC_OPEN_MAX
);
749 for (i
= 3; i
< max_files
; i
++)
752 /* ensure stdin is on /dev/null */
754 open("/dev/null", O_RDONLY
);
756 return startup_trigger(argc
, argv
, envp
);
758 default: /* parent (child1) */
763 default: /* parent */
764 waitpid(child1
, &status
, 0);
767 free(pref_app_to_run
);
768 free(pref_login_shell
);
769 free(pref_startx_script
);
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
));
784 execute(const char *command
)
786 const char *newargv
[4];
789 newargv
[0] = pref_login_shell
;
791 newargv
[2] = command
;
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
);
799 execvp(newargv
[0], (char *const *)newargv
);
800 perror("X11.app: Couldn't exec.");
805 command_from_prefs(const char *key
, const char *default_value
)
807 char *command
= NULL
;
810 CFPropertyListRef PlistRef
;
815 cfKey
= CFStringCreateWithCString(NULL
, key
, kCFStringEncodingASCII
);
820 PlistRef
= CFPreferencesCopyAppValue(cfKey
,
821 kCFPreferencesCurrentApplication
);
823 if ((PlistRef
== NULL
) ||
824 (CFGetTypeID(PlistRef
) != CFStringGetTypeID())) {
825 CFStringRef cfDefaultValue
= CFStringCreateWithCString(
826 NULL
, default_value
, kCFStringEncodingASCII
);
827 int len
= strlen(default_value
) + 1;
830 goto command_from_prefs_out
;
832 CFPreferencesSetAppValue(cfKey
, cfDefaultValue
,
833 kCFPreferencesCurrentApplication
);
834 CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication
);
835 CFRelease(cfDefaultValue
);
837 command
= (char *)malloc(len
* sizeof(char));
839 goto command_from_prefs_out
;
840 strcpy(command
, default_value
);
843 int len
= CFStringGetLength((CFStringRef
)PlistRef
) + 1;
844 command
= (char *)malloc(len
* sizeof(char));
846 goto command_from_prefs_out
;
847 CFStringGetCString((CFStringRef
)PlistRef
, command
, len
,
848 kCFStringEncodingASCII
);
851 command_from_prefs_out
: