
Adapting to a dynamic world Damien Sorresso Daemon Spawn 2 Introduction • Mac OS X’s process lifecycle model • Managed by launchd • Ad-hoc bootstrapping model 3 What You’ll Learn WhatWhy Launch-on-Demand you’ll learn is better How to write a Launch-on-Demand job Leveraging GCD for asynchronous design • WhyHow to Launch-on-Demandwrite a privileged helper is better • How to write a Launch-on-Demand job • Leveraging GCD for asynchronous design • How to write a privileged helper 4 • Startup order was well-defined • Process ordained by Holy Priesthood of rc • Rigid and inflexible • Unsuited to modern, hot-plugging world 5 • Race-prone PID files • “Make me a daemon“ code • Makefile approach to bootstrapping • Services correlating to processes Learn to Let Go! 6 Launch-on-Demand Once you’ve let go Plug-in model Pay-as-you-go computing Boilerplate code Holy Priesthood 7 Basic concepts • Two job types ■ Daemons (system-wide) ■ Agents (per-user) • Daemons run in privileged environment • Agents run in user environment • Can both advertise services 8 Basic concepts • Services as virtual endpoints • Not necessarily backed by a process • No explicit dependency graph • Dependencies expressed through IPC 9 Advantages EfficiencyReliability Flexibility 10 Crash Course CrashNew synchronization course primitive: queues Work submitted to queues Runtime guarantees synchronous execution of work • NewIncredibly synchronization easy primitive: queues Incredibly fast • Work submitted to queues • Runtime guarantees synchronous execution of work • Incredibly easy • Incredibly fast 11 Crash course • Queues act as run loop primitive • Sources schedule event handlers on queue • Sources monitor for events ■ File descriptor readability/writabilityText ■ Process events (exit, fork(2), exec(3), etc.) ■ Signals (SIGTERM, SIGHUP, etc.) ■ Timers 12 Crash course • Inline/anonymous functions • Implicitly capture any stack state referenced • No more marshaling state into a struct • Acts as an archive for function call 13 Crash course #include <dispatch/dispatch.h> int foo(dispatch_queue_t q) { int a = 0; /* Captured as const. */ int b = 1; /* Captured as const. */ __block int c = 2; /* Mutable. */ /* Copies block to heap. */ dispatch_async(q, ^{ c = 1; /* Groovy. */ a = 1; /* Harshes the compiler’s buzz. */ }); } 14 Design goals • As close to stateless as possible • If needed, archive state • Crashes will happen • Asynchronous, HTTP-style request handling 15 com.apple.ssd.sock (1138) ssh (22) launchd client ssd 16 launchd.plist <?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>Label</key> <string>com.apple.ssd</string> <key>Program</key> <string>/usr/libexec/ssd</string> <key>Sockets</key> <dict> <key>com.apple.ssd.sock</key> <dict> <key>SockServiceName</key> <string>1138</string> </dict> </dict> </dict> </plist> 17 Checking in #include <launch.h> launch_data_t req = launch_data_new_string(LAUNCH_KEY_CHECKIN); launch_data_t resp = launch_msg(req); launch_data_t sockets = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_SOCKETS); launch_data_t sarr = launch_data_dict_lookup(sockets, "com.apple.ssd.sock"); launch_data_t sockv6 = launch_data_array_get_index(sarr, 0); /* IPv6 */ launch_data_t sockv4 = launch_data_array_get_index(sarr, 1); /* IPv4 */ int sockfd = launch_data_get_fd(sockv4); Don’t Use These APIs for Anything Other Than This 18 19 Message structure struct ss_msg_s { uint32_t _len; unsigned char _bytes[0]; }; 20 GCD for accept(3) (void)fcntl(sockfd, F_SETFL, O_NONBLOCK); /* REQUIRED! */ dispatch_source_t s = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, sockfd, 0, q); dispatch_source_set_event_handler(s, ^{ struct sockaddr saddr; socklen_t slen = 0; int afd = accept(sockfd, (struct sockaddr *)&saddr, &slen)); (void)fcntl(fd, F_SETFL, O_NONBLOCK); server_accept(afd, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)); }); /* REQUIRED! */ dispatch_source_set_cancel_handler(s, ^{ dispatch_release(s); (void)close(sockfd); }); dispatch_resume(s); 21 GCD to read(2) void server_accept(int fd, dispatch_queue_t q) { __block size_t total = 0; size_t buff_sz = 10 * 1024 * 1024; /* Big enough. */ unsigned char *buff = malloc(buff_sz); dispatch_source_t s = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, q); dispatch_source_set_event_handler(s, ^{ if (server_read(fd, buff, buff_sz, &total)) { struct ss_msg_s *msg = (struct ss_msg_s *)buff; server_handle_request(fd, msg->_bytes, msg->_len); dispatch_source_cancel(s); } }); dispatch_source_set_cancel_handler(s, ^{ dispatch_release(s); free(buff); }); dispatch_resume(s); } 22 Marshaling the request bool server_read(int fd, unsigned char *buff, size_t buff_sz, size_t *total) { bool result = false; struct ss_msg_s *msg = (struct ss_msg_s *)buff; unsigned char *track_buff = buff + *total; size_t track_sz = buff_sz - *total; ssize_t nbytes = read(fd, track_buff, track_sz); if (nbytes + *total >= sizeof(struct ss_msg_s)) { if (msg->_len == (*total - sizeof(struct ss_msg_s))) { result = true; } } *total += nbytes; return result; } Beware of Buffer Overflows! 23 Demuxing the request void server_handle_request(int fd, const void *buff, size_t total) { CFDataRef data = CFDataCreate(NULL, buff, total); CFPropertyListRef plist = CFPropertyListCreateWithData(NULL, data, kCFPropertyListImmutable, NULL, NULL); /* Handle request, create reply (of a property list type). */ CFDataRef replyData = CFPropertyListCreateData(NULL, (CFPropertyListRef)reply, kCFPropertyListBinaryFormat_v1_0, 0, NULL); CFRelease(data); CFRelease(plist); CFRelease(reply); server_send_reply(fd, replyData); } 24 GCD to send the reply void server_send_reply(int fd, dispatch_queue_t q, CFDataRef data) { size_t total = sizeof(struct ss_msg_s) + CFDataGetLength(data); unsigned char *buff = (unsigned char *)malloc(total); struct ss_msg_s *msg = (struct ss_msg_s *)buff; msg->_len = total - sizeof(struct ss_msg_s); (void)memcpy(msg->_bytes, CFDataGetBytePtr(data), CFDataGetLength(data)); __block unsigned char *track_buff = buff; __block size_t track_sz = total; dispatch_source_t s = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, fd, 0, q); /* Continues... */ } 25 GCD to send the reply void __attribute__((continued)) server_send_reply(int fd, dispatch_queue_t q, CFDataRef data) { dispatch_source_set_event_handler(s, ^{ ssize_t nbytes = write(fd, track_buff, track_sz); if (nbytes != -1) { track_buff += nbytes; track_sz -= nbytes; if (track_sz == 0) { dispatch_source_cancel(s); } } }); dispatch_source_set_cancel_handler(s, ^{ (void)close(fd); free(buff); dispatch_release(s); }); dispatch_resume(s); } 26 Idle exiting • Globals dispatch_source_t g_timer_source = NULL; uint32_t g_transaction_count = 0; • Initialization g_timer_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, q); dispatch_time_t t0 = dispatch_time(DISPATCH_TIME_NOW, 20llu * NSEC_PER_SEC); dispatch_source_set_timer(g_timer_source, t0, 0llu, 0llu); 27 Idle exiting • Add to server_read()… if (OSAtomicIncrement32(&g_transaction_count) - 1 == 0) { dispatch_source_set_timer(g_timer_source, DISPATCH_TIME_FOREVER, 0llu, 0llu); } • Add to server_send_reply()… if (OSAtomicDecrement32(&g_transaction_count) == 0) { dispatch_time_t t0 = dispatch_time(DISPATCH_TIME_NOW, 20llu * NSEC_PER_SEC); dispatch_source_set_timer(g_timer_source, t0, 0llu, 0llu); } 28 Instant Off • Add to launchd.plist <key>EnableTransactions</key> <true/> • Open new transaction upon receiving request • Close transaction after sending reply 29 Instant Off • Add to server_read()… vproc_transaction_t t = vproc_transaction_begin(NULL); • Add to server_send_reply()… vproc_transaction_end(NULL, t); 30 Instant Off • Add SIGTERM handler… (void)signal(SIGTERM, SIG_IGN); /* REQUIRED! */ dispatch_source_t s = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGTERM, 0, q); dispatch_source_set_event_handler(s, ^{ g_accepting_transactions = false; }); dispatch_resume(s) • Check g_accepting_transactions in server_handle_request() • Close accept(2)’s socket if not 31 Privileged helper tools • GUI apps need to elevate privilege • Preserve drag-n-drop install • Avoid setuid tricks • On-demand daemon 32 Secure handshake • ServiceManagement framework provides API • Securely tie application and helper together • Application specifies tool’s identity (and vice-versa) • Identity verifiable through code signing 33 Application side • Add to application’s Info.plist <key>SMPrivilegedExecutables</key> <dict> <key>com.apple.wwdc.HelperTool</key> <string>identifier com.apple.wwdc.HelperTool and certificate leaf[subject.CN] = "WWDC Developer"</string> </dict> 34 Tool side • Embed in tool’s Info.plist <key>SMAuthorizedClients</key> <array> <string>identifier com.apple.wwdc.SMApplication and certificate leaf[subject.CN] = "WWDC Developer"</string> </array> 35 Requirements • No Program or ProgramArguments in tool’s launchd.plist • Both launchd.plist and Info.plist part of tool’s code signature • Tools live in Contents/Library/LaunchServices • Must be named identically to launchd label 36 37 Magic API call #include <ServiceManagement/ServiceManagement.h> AuthorizationItem authItem= { kSMRightBlessPrivilegedHelper, 0, NULL, 0 }; AuthorizationRights authRights= { 1, &authItem }; AuthorizationFlags flags =kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed| kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights; AuthorizationRef authRef = NULL; OSStatus status = AuthorizationCreate(&authRights,
Details
-
File Typepdf
-
Upload Time-
-
Content LanguagesEnglish
-
Upload UserAnonymous/Not logged-in
-
File Pages46 Page
-
File Size-