Implement xpc_main

However, bundled XPC services still don't work properly.

The problem is that bundled XPC service support needs to be implemented in launchd.
The open-source launchd sources are severely outdated, much like CoreFoundation.
The new launchd is based on libxpc and is closed-source.
We probably don't want to completely rewrite launchd, but at the very least, we do need to add support for new XPC-based features.
This commit is contained in:
Ariel Abreu 2021-04-21 17:30:34 -04:00
parent 96434fd2f1
commit 0ec7a91082
No known key found for this signature in database
GPG Key ID: BB20848279B910AC
8 changed files with 217 additions and 7 deletions

View File

@ -18,6 +18,7 @@ add_compile_definitions(
add_compile_options(
-Wno-extern-initializer
-Wno-gnu-folding-constant
)
add_compile_options(

View File

@ -32,6 +32,9 @@ struct xpc_bundle_s {
// this API is modeled after NSBundle
// (but we've added lots of non-NSBundle extensions)
// NOTE: differs from NSBundle by returning a new bundle every time it's called
@property(class, readonly, copy) XPC_CLASS(bundle)* mainBundle;
@property(strong) XPC_CLASS(string)* bundlePath;
@property(strong) XPC_CLASS(string)* executablePath;
@property(strong) XPC_CLASS(dictionary)* infoDictionary;

View File

@ -20,6 +20,15 @@ OS_OBJECT_NONLAZY_CLASS
XPC_CLASS_HEADER(bundle);
+ (XPC_CLASS(bundle)*)mainBundle
{
char* exec_path = xpc_copy_main_executable_path();
if (!exec_path) {
return NULL;
}
return [[[[self class] alloc] initWithPath: [XPC_CLASS(string) stringWithUTF8StringNoCopy: exec_path freeWhenDone: YES]] autorelease];
}
+ (BOOL)pathIsBundleRoot: (XPC_CLASS(string)*)path
{
// frameworks are special
@ -361,11 +370,7 @@ xpc_object_t xpc_bundle_create_from_origin(unsigned int origin, const char* path
XPC_EXPORT
xpc_object_t xpc_bundle_create_main(void) {
@autoreleasepool {
char* exec_path = xpc_copy_main_executable_path();
if (!exec_path) {
return NULL;
}
return [[XPC_CLASS(bundle) alloc] initWithPath: [XPC_CLASS(string) stringWithUTF8StringNoCopy: exec_path freeWhenDone: YES]];
return [[XPC_CLASS(bundle) mainBundle] retain];
}
};

View File

@ -1,6 +1,35 @@
#import <xpc/xpc.h>
#import <xpc/util.h>
#import <xpc/private/bundle.h>
#import <xpc/objects/bundle.h>
#import <xpc/objects/dictionary.h>
#import <xpc/objects/string.h>
#import <xpc/objects/connection.h>
#import <objc/runtime.h>
#import <Foundation/NSRunLoop.h>
#import <dlfcn.h>
#import <AppKit/NSApplication.h>
#import <crt_externs.h>
#define INFO_DICT_IDENTIFIER_KEY "CFBundleIdentifier"
#define INFO_DICT_PACKAGE_TYPE_KEY "CFBundlePackageType"
#define XPC_SERVICE_DICT_RUNLOOP_TYPE_KEY "RunLoopType"
#define XPC_PACKAGE_TYPE "XPC!"
OS_ENUM(xpc_service_runloop_type, uint8_t,
xpc_service_runloop_type_invalid,
xpc_service_runloop_type_dispatch,
xpc_service_runloop_type_nsrunloop,
xpc_service_runloop_type_nsapplicationmain,
xpc_service_runloop_type_uiapplicationmain,
);
XPC_EXPORT
xpc_object_t _xpc_runtime_get_entitlements_data(void) {
// returns a data object
@ -21,10 +50,111 @@ bool _xpc_runtime_is_app_sandboxed(void) {
return false;
};
static xpc_service_runloop_type_t runloop_name_to_type(const char* name) {
if (!name) {
return xpc_service_runloop_type_dispatch;
} else if (strcmp(name, "_UIApplicationMain") == 0) {
return xpc_service_runloop_type_uiapplicationmain;
} else if (strcmp(name, "_NSApplicationMain") == 0) {
return xpc_service_runloop_type_nsapplicationmain;
} else if (strcmp(name, "NSRunLoop") == 0 || strcmp(name, "_WebKit") == 0) {
return xpc_service_runloop_type_nsrunloop;
} else {
return xpc_service_runloop_type_dispatch;
}
};
XPC_EXPORT
void xpc_main(xpc_connection_handler_t handler) {
xpc_stub();
abort();
@autoreleasepool {
XPC_CLASS(bundle)* mainBundle = [XPC_CLASS(bundle) mainBundle];
XPC_CLASS(string)* identifier = nil;
xpc_service_runloop_type_t runloopType = xpc_service_runloop_type_invalid;
XPC_CLASS(connection)* server = nil;
if (!mainBundle) {
xpc_abort("failed to retrieve main bundle information");
}
[mainBundle resolve];
if (mainBundle.error != 0) {
xpc_abort("failed to resolve main bundle information");
}
if (![[mainBundle.infoDictionary stringForKey: INFO_DICT_PACKAGE_TYPE_KEY] isEqualToString: XPC_PACKAGE_TYPE]) {
xpc_abort("main bundle was not an XPC service bundle");
}
identifier = [mainBundle.infoDictionary stringForKey: INFO_DICT_IDENTIFIER_KEY];
if (!identifier) {
xpc_abort("failed to determine main bundle identifier");
}
server = [[XPC_CLASS(connection) alloc] initAsServerForService: identifier.UTF8String queue: NULL];
if (!server) {
xpc_abort("failed to create server connection");
}
server.eventHandler = ^(xpc_object_t object) {
xpc_type_t type = xpc_get_type(object);
if (type == (xpc_type_t)XPC_TYPE_CONNECTION) {
handler(XPC_CAST(connection, object));
} else if (type == (xpc_type_t)XPC_TYPE_ERROR) {
if (object == XPC_ERROR_TERMINATION_IMMINENT) {
xpc_log(XPC_LOG_WARNING, "someone wants us to terminate");
} else {
xpc_abort("unexpected error receive in managed server event handler: %s", xpc_copy_description(object));
}
} else {
xpc_abort("invalid object received in managed server event handler: %s", xpc_copy_description(object));
}
};
// schedule the connection to be activated once the runloop is kicked off
dispatch_async(dispatch_get_main_queue(), ^{
[server activate];
});
runloopType = runloop_name_to_type([XPC_CAST(dictionary, xpc_bundle_get_xpcservice_dictionary(mainBundle)) stringForKey: XPC_SERVICE_DICT_RUNLOOP_TYPE_KEY].UTF8String);
switch (runloopType) {
case xpc_service_runloop_type_dispatch: {
dispatch_main();
} break;
case xpc_service_runloop_type_nsrunloop: {
Class NSRunLoopClass = objc_getClass("NSRunLoop");
if (!NSRunLoopClass) {
xpc_abort("failed to load NSRunLoop class");
}
[[NSRunLoopClass currentRunLoop] run];
} break;
case xpc_service_runloop_type_nsapplicationmain: {
void* appkit = dlopen("/System/Library/Frameworks/AppKit.framework/AppKit", RTLD_LAZY);
if (!appkit) {
xpc_abort("failed to load AppKit");
}
__typeof__(NSApplicationMain)* NSApplicationMain_ptr = dlsym(appkit, "NSApplicationMain");
if (!NSApplicationMain_ptr) {
xpc_abort("failed to load NSApplicationMain from AppKit");
}
NSApplicationMain_ptr(*_NSGetArgc(), (const char**)*_NSGetArgv());
} break;
case xpc_service_runloop_type_uiapplicationmain: {
xpc_abort("UIApplicationMain runloop not implemented");
} break;
default: {
xpc_abort("failed to determine runloop type");
} break;
}
}
xpc_abort("runloop returned");
};
XPC_EXPORT

View File

@ -1,4 +1,5 @@
add_subdirectory(launchd-service)
add_subdirectory(bundled-service)
set(TEST_SOURCES
array.m

View File

@ -0,0 +1,24 @@
add_darling_executable(libxpc_test_bundled_service service.c)
set_target_properties(libxpc_test_bundled_service PROPERTIES
OUTPUT_NAME "XPCBundledService"
)
target_link_libraries(libxpc_test_bundled_service
xpc_static
objc
)
install(
TARGETS
libxpc_test_bundled_service
DESTINATION
libexec/darling/usr/libexec/test/xpc/XPCBundledService.xpc/Contents/MacOS
)
install(
FILES
Info.plist
DESTINATION
libexec/darling/usr/libexec/test/xpc/XPCBundledService.xpc/Contents
)

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>XPC Bundled Service</string>
<key>CFBundleExecutable</key>
<string>XPCBundledService</string>
<key>CFBundleIdentifier</key>
<string>org.darlinghq.libxpc.test.bundled-service</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>XPCBundledService</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>XPCService</key>
<dict>
<key>ServiceType</key>
<string>Application</string>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,19 @@
#include <xpc/xpc.h>
#include <xpc/connection.h>
static void connection_handler(xpc_connection_t connection) {
printf("Got a new connection: %p\n", connection);
xpc_connection_set_event_handler(connection, ^(xpc_object_t object) {
char* desc = xpc_copy_description(object);
xpc_type_t type = xpc_get_type(object);
printf("Recevied %s from connection %p: %s\n", (type == (xpc_type_t)XPC_TYPE_DICTIONARY) ? "message" : ((type == (xpc_type_t)XPC_TYPE_ERROR) ? "error" : "unexpected object"), connection, desc);
free(desc);
});
xpc_connection_resume(connection);
};
int main(int argc, char** argv) {
xpc_main(connection_handler);
return 0;
};