From 0ec7a91082280dba00bdd6d0482b05ce1a7e723b Mon Sep 17 00:00:00 2001 From: Ariel Abreu Date: Wed, 21 Apr 2021 17:30:34 -0400 Subject: [PATCH] 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. --- CMakeLists.txt | 1 + internal-include/xpc/objects/bundle.h | 3 + src/bundle.m | 15 ++- src/runtime.m | 134 +++++++++++++++++++++++++- test/CMakeLists.txt | 1 + test/bundled-service/CMakeLists.txt | 24 +++++ test/bundled-service/Info.plist | 27 ++++++ test/bundled-service/service.c | 19 ++++ 8 files changed, 217 insertions(+), 7 deletions(-) create mode 100644 test/bundled-service/CMakeLists.txt create mode 100644 test/bundled-service/Info.plist create mode 100644 test/bundled-service/service.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 88dc4eb..c860aa1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ add_compile_definitions( add_compile_options( -Wno-extern-initializer + -Wno-gnu-folding-constant ) add_compile_options( diff --git a/internal-include/xpc/objects/bundle.h b/internal-include/xpc/objects/bundle.h index 4ccbf29..c097cb5 100644 --- a/internal-include/xpc/objects/bundle.h +++ b/internal-include/xpc/objects/bundle.h @@ -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; diff --git a/src/bundle.m b/src/bundle.m index 7663e1b..198cfc0 100644 --- a/src/bundle.m +++ b/src/bundle.m @@ -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]; } }; diff --git a/src/runtime.m b/src/runtime.m index 81d5378..10f5ae3 100644 --- a/src/runtime.m +++ b/src/runtime.m @@ -1,6 +1,35 @@ #import #import +#import + +#import +#import +#import +#import + +#import + +#import +#import +#import +#import + +#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 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6f8229e..2d523f3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(launchd-service) +add_subdirectory(bundled-service) set(TEST_SOURCES array.m diff --git a/test/bundled-service/CMakeLists.txt b/test/bundled-service/CMakeLists.txt new file mode 100644 index 0000000..a59c63f --- /dev/null +++ b/test/bundled-service/CMakeLists.txt @@ -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 +) diff --git a/test/bundled-service/Info.plist b/test/bundled-service/Info.plist new file mode 100644 index 0000000..e7a0799 --- /dev/null +++ b/test/bundled-service/Info.plist @@ -0,0 +1,27 @@ + + + + + CFBundleDisplayName + XPC Bundled Service + CFBundleExecutable + XPCBundledService + CFBundleIdentifier + org.darlinghq.libxpc.test.bundled-service + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + XPCBundledService + CFBundlePackageType + XPC! + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + XPCService + + ServiceType + Application + + + diff --git a/test/bundled-service/service.c b/test/bundled-service/service.c new file mode 100644 index 0000000..f786aec --- /dev/null +++ b/test/bundled-service/service.c @@ -0,0 +1,19 @@ +#include +#include + +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; +};