/* UIKit Tools - command-line utilities for UIKit
* Copyright (C) 2018-2019 Sam Bingner
* Copyright (C) 2008-2012 Jay Freeman (saurik)
* Portions Copyright (C) 2019 Sam Bingner
*/
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
/* Modified BSD License {{{ */
/*
* Redistribution and use in source and binary
* forms, with or without modification, are permitted
* provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the
* above copyright notice, this list of conditions
* and the following disclaimer.
* 2. Redistributions in binary form must reproduce the
* above copyright notice, this list of conditions
* and the following disclaimer in the documentation
* and/or other materials provided with the
* distribution.
* 3. The name of the author may not be used to endorse
* or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* }}} */
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#include "csstore.hpp"
#if TARGET_OS_IPHONE
#define FRONTBOARD "SpringBoard"
#define FRONTBOARD_ID "com.apple.SpringBoard"
#elif TARGET_OS_TV
#define FRONTBOARD "PineBoard"
#define FRONTBOARD_ID "com.apple.PineBoard"
@interface PBSSystemService : NSObject
+(id)sharedInstance;
-(void)relaunchBackboardd;
@end
@interface PBSSystemServiceConnection : NSObject
+(id)sharedConnection;
-(id)systemServiceProxy;
@end
#else
#error "Unsupported target OS"
#endif
@interface NSMutableArray (Cydia)
- (void) addInfoDictionary:(NSDictionary *)info;
@end
@implementation NSMutableArray (Cydia)
- (void) addInfoDictionary:(NSDictionary *)info {
[self addObject:info];
}
- (NSArray *) allInfoDictionaries {
return self;
}
@end
@interface NSMutableDictionary (Cydia)
- (void) addInfoDictionary:(NSDictionary *)info;
@end
@implementation NSMutableDictionary (Cydia)
- (void) addInfoDictionary:(NSDictionary *)info {
NSString *bundle = [info objectForKey:@"CFBundleIdentifier"];
[self setObject:info forKey:bundle];
}
- (NSArray *) allInfoDictionaries {
return [self allValues];
}
@end
@interface LSApplicationProxy : NSObject
- (NSString*) applicationIdentifier;
- (NSURL*) bundleURL;
- (NSDate*) registeredDate;
@end
@interface LSApplicationWorkspace : NSObject
+ (id) defaultWorkspace;
- (BOOL) registerApplication:(id)application;
- (BOOL) unregisterApplication:(id)application;
- (BOOL) invalidateIconCache:(id)bundle;
- (BOOL) registerApplicationDictionary:(id)application;
- (BOOL) installApplication:(id)application withOptions:(id)options;
- (BOOL) _LSPrivateRebuildApplicationDatabasesForSystemApps:(BOOL)system internal:(BOOL)internal user:(BOOL)user;
- (NSArray*) allApplications;
@end
@interface MCMAppDataContainer
+(id)containerWithIdentifier:(NSString*)identifier createIfNecessary:(bool)create existed:(bool*)existed error:(NSError*)error;
-(NSURL*)url;
@end
@interface FBSSystemService
+(id)sharedService;
-(void)sendActions:(NSSet*)actions withResult:(id)result;
@end
typedef enum {
None = 0,
RestartRenderServer = (1 << 0), // also relaunch backboardd
SnapshotTransition = (1 << 1),
FadeToBlackTransition = (1 << 2),
} SBSRelaunchActionStyle;
@interface SBSRelaunchAction
+(id)actionWithReason:(id)reason options:(int64_t)options targetURL:(NSURL*)url;
@end
static int verbose=0;
static int standard_uicache(void);
static Class $MCMPluginKitPluginDataContainer;
static Class $MCMAppDataContainer;
static Class $LSApplicationWorkspace;
LSApplicationWorkspace *workspace=nil;
NSString *getAppPath(NSString *path)
{
path = [path stringByResolvingSymlinksInPath];
if (![path hasPrefix:@"/Applications/"]) {
fprintf(stderr, "Error: Path must be within /Applications/\n");
return nil;
}
return [NSString pathWithComponents:[[path pathComponents] subarrayWithRange:NSMakeRange(0, 3)]];
}
bool appIsRegistered(NSString *path)
{
@autoreleasepool {
path = getAppPath(path);
if (!path) return false;
for (LSApplicationProxy *app in [workspace allApplications]) {
if ([path isEqualToString:[[app bundleURL] path]]) return true;
}
return false;
}
}
bool unregisterPath(NSString *path)
{
@autoreleasepool {
if (verbose) fprintf(stderr, "Unregistering %s\n", path.lastPathComponent.UTF8String);
path = getAppPath(path);
if (!path) return false;
if (appIsRegistered(path) && ![workspace unregisterApplication:[NSURL fileURLWithPath:path]]) {
fprintf(stderr, "Error: unregisterApplication failed for %s\n", path.lastPathComponent.UTF8String);
return false;
}
}
return true;
}
// Credit to coolstar for finding how to do this and not sharing with the community thereby forcing me to figure out how to do the same thing.
bool registerPath(NSString *path)
{
if (!path) {
if (verbose) fprintf(stderr, "registerPath called with no path\n");
return false;
}
NSString *realPath = getAppPath(path);
if (!realPath) {
if (verbose) fprintf(stderr, "unable to determine path for %s\n", path.UTF8String);
return false;
}
NSDictionary *infoDictionary = [NSDictionary dictionaryWithContentsOfFile:
[realPath stringByAppendingPathComponent:@"Info.plist"]];
NSString *bundleID = [infoDictionary objectForKey:@"CFBundleIdentifier"];
if (bundleID) {
NSFileManager *fm = [NSFileManager defaultManager];
if ([infoDictionary objectForKey:@"CFBundleExecutable"]) {
NSString *executable = [realPath stringByAppendingPathComponent:[infoDictionary objectForKey:@"CFBundleExecutable"]];
if (![fm fileExistsAtPath:executable]) {
if ([bundleID isEqual:@"com.apple.TrustMe"]) {
return true;
}
fprintf(stderr, "Error: CFBundleExecutable defined but missing for %s - this is a fatal error. Aborting.\n", realPath.lastPathComponent.UTF8String);
return false;
}
}
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
@"System", @"ApplicationType",
@YES, @"BundleNameIsLocalized",
bundleID, @"CFBundleIdentifier",
@NO, @"CompatibilityState",
@NO, @"IsDeletable",
realPath, @"Path",
[NSMutableDictionary dictionary], @"_LSBundlePlugins",
nil];
id appContainer = [$MCMAppDataContainer containerWithIdentifier:bundleID
createIfNecessary:YES existed:NULL error:nil];
NSString *appContainerPath = [[appContainer url] path];
if (appContainerPath) {
dict[@"Container"] = appContainerPath;
}
NSString *pluginsPath = [realPath stringByAppendingPathComponent:@"PlugIns"];
for (NSString *plugin in [fm contentsOfDirectoryAtPath:pluginsPath error:nil]) {
NSString *pluginPath = [pluginsPath stringByAppendingPathComponent:plugin];
NSString *pluginInfoPlistPath = [pluginPath stringByAppendingPathComponent:@"Info.plist"];
NSString *pluginBundleIdentifier = [[NSDictionary dictionaryWithContentsOfFile:pluginInfoPlistPath] objectForKey:@"CFBundleIdentifier"];
if (pluginBundleIdentifier) {
id pluginContainer = [$MCMPluginKitPluginDataContainer containerWithIdentifier:pluginBundleIdentifier
createIfNecessary:YES existed:NULL error:nil];
NSURL *pluginContainerURL = [pluginContainer url];
NSString *pluginContainerPath = [pluginContainerURL path];
dict[@"_LSBundlePlugins"][pluginBundleIdentifier] = @{
@"ApplicationType": @"PluginKitPlugin",
@"BundleNameIsLocalized": @YES,
@"CFBundleIdentifier": pluginBundleIdentifier,
@"CompatibilityState": @NO,
@"Container": pluginContainerPath,
@"Path": pluginPath,
@"PluginOwnerBundleID": bundleID
};
}
}
if (![[$LSApplicationWorkspace defaultWorkspace] registerApplicationDictionary:dict]) {
fprintf(stderr, "Error: registerApplicationDictionary failed for %s\n", path.lastPathComponent.UTF8String);
return false;
}
} else {
return unregisterPath(realPath);
}
return true;
}
void usage(void)
{
fprintf(stderr, "Usage: %s [-hrv] [[-p | -u] /Applications/App.app]]\n", getprogname());
exit(EXIT_FAILURE);
}
pid_t launch_get_job_pid(const char * job)
{
launch_data_t resp;
launch_data_t msg;
msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
if (msg == NULL) {
return -1;
}
launch_data_dict_insert(msg, launch_data_new_string(job), LAUNCH_KEY_GETJOB);
resp = launch_msg(msg);
launch_data_free(msg);
if (resp == NULL) {
return -1;
}
if (launch_data_get_type(resp) != LAUNCH_DATA_DICTIONARY) return -1;
launch_data_t pid_data = launch_data_dict_lookup(resp, "PID");
if (launch_data_get_type(pid_data) != LAUNCH_DATA_INTEGER) return -1;
pid_t pid = (pid_t)launch_data_get_integer(pid_data);
launch_data_free(resp);
return pid;
}
int standard_uicache(void)
{
@autoreleasepool {
if (kCFCoreFoundationVersionNumber > 1000 && // this API is on iOS 7 but invaliding the icon cache is harder there
[workspace respondsToSelector:@selector(_LSPrivateRebuildApplicationDatabasesForSystemApps:internal:user:)]) {
if (![workspace _LSPrivateRebuildApplicationDatabasesForSystemApps:YES internal:YES user:NO])
fprintf(stderr, "failed to rebuild application databases");
return 0;
}
bool respring(false);
NSString *home(NSHomeDirectory());
NSString *path([NSString stringWithFormat:@"%@/Library/Caches/com.apple.mobile.installation.plist", home]);
system("killall -SIGSTOP " FRONTBOARD);
sleep(1);
@try {
DeleteCSStores([home UTF8String]);
system("killall lsd");
if ([workspace respondsToSelector:@selector(invalidateIconCache:)])
while (![workspace invalidateIconCache:nil])
sleep(1);
if (NSMutableDictionary *cache = [NSMutableDictionary dictionaryWithContentsOfFile:path]) {
NSFileManager *manager = [NSFileManager defaultManager];
NSError *error = nil;
NSMutableDictionary *bundles([NSMutableDictionary dictionaryWithCapacity:16]);
id after = [cache objectForKey:@"System"];
if (after == nil) { error:
fprintf(stderr, "%s\n", error == nil ? strerror(errno) : [[error localizedDescription] UTF8String]);
goto cached;
}
id before([[after copy] autorelease]);
[after removeAllObjects];
NSArray *cached([cache objectForKey:@"InfoPlistCachedKeys"]);
NSMutableSet *removed([NSMutableSet set]);
for (NSDictionary *info in [before allInfoDictionaries])
if (NSString *path = [info objectForKey:@"Path"])
[removed addObject:path];
if (NSArray *apps = [manager contentsOfDirectoryAtPath:@"/Applications" error:&error]) {
for (NSString *app in apps)
if ([app hasSuffix:@".app"]) {
NSString *path = [@"/Applications" stringByAppendingPathComponent:app];
NSString *plist = [path stringByAppendingPathComponent:@"Info.plist"];
if (NSMutableDictionary *info = [NSMutableDictionary dictionaryWithContentsOfFile:plist]) {
if (NSString *identifier = [info objectForKey:@"CFBundleIdentifier"]) {
[bundles setObject:path forKey:identifier];
[removed removeObject:path];
if (cached != nil) {
NSMutableDictionary *merged([before objectForKey:identifier]);
if (merged == nil)
merged = [NSMutableDictionary dictionary];
else
merged = [[merged mutableCopy] autorelease];
for (NSString *key in cached)
if (NSObject *value = [info objectForKey:key])
[merged setObject:value forKey:key];
else
[merged removeObjectForKey:key];
info = merged;
}
[info setObject:path forKey:@"Path"];
[info setObject:@"System" forKey:@"ApplicationType"];
[after addInfoDictionary:info];
} else
fprintf(stderr, "%s missing CFBundleIdentifier", [app UTF8String]);
}
}
} else goto error;
[cache writeToFile:path atomically:YES];
if (workspace != nil) {
if ([workspace respondsToSelector:@selector(invalidateIconCache:)]) {
for (NSString *identifier in bundles)
[workspace invalidateIconCache:identifier];
} else {
for (NSString *identifier in bundles) {
NSString *path([bundles objectForKey:identifier]);
[workspace unregisterApplication:[NSURL fileURLWithPath:path]];
}
}
for (NSString *identifier in bundles) {
NSString *path([bundles objectForKey:identifier]);
if (kCFCoreFoundationVersionNumber >= 800)
[workspace registerApplicationDictionary:[after objectForKey:identifier]];
else
[workspace registerApplication:[NSURL fileURLWithPath:path]];
}
for (NSString *path in removed)
[workspace unregisterApplication:[NSURL fileURLWithPath:path]];
}
} else fprintf(stderr, "cannot open cache file. incorrect user?\n");
cached:
if (respring || kCFCoreFoundationVersionNumber >= 550.32) {
unlink([[NSString stringWithFormat:@"%@/Library/Caches/com.apple.springboard-imagecache-icons", home] UTF8String]);
unlink([[NSString stringWithFormat:@"%@/Library/Caches/com.apple.springboard-imagecache-icons.plist", home] UTF8String]);
unlink([[NSString stringWithFormat:@"%@/Library/Caches/com.apple.springboard-imagecache-smallicons", home] UTF8String]);
unlink([[NSString stringWithFormat:@"%@/Library/Caches/com.apple.springboard-imagecache-smallicons.plist", home] UTF8String]);
system([[NSString stringWithFormat:@"rm -rf %@/Library/Caches/SpringBoardIconCache", home] UTF8String]);
system([[NSString stringWithFormat:@"rm -rf %@/Library/Caches/SpringBoardIconCache-small", home] UTF8String]);
system([[NSString stringWithFormat:@"rm -rf %@/Library/Caches/com.apple.IconsCache", home] UTF8String]);
}
system("killall installd");
} @finally {
system("killall -SIGCONT " FRONTBOARD);
}
notify_post("com.apple.mobile.application_installed");
return 0;
}
}
pid_t pidOfCydia(void) {
launch_data_t request = launch_data_new_string(LAUNCH_KEY_GETJOBS);
launch_data_t response = launch_msg(request);
launch_data_free(request);
__block pid_t pid=-1;
if (response == NULL || launch_data_get_type(response) != LAUNCH_DATA_DICTIONARY) return -1;
xpc_dictionary_apply((xpc_object_t)response, ^bool(const char *key, xpc_object_t value) {
if (xpc_get_type(value) == XPC_TYPE_DICTIONARY) {
const char *program = xpc_dictionary_get_string(value, "Program");
if (program && strcmp(program, "/Applications/Cydia.app/Cydia") == 0) {
pid = (pid_t)xpc_dictionary_get_int64(value, "PID");
if (verbose) fprintf(stderr, "Found Cydia running with PID: %d\n", pid);
return false;
}
}
return true;
});
if (pid>0) {
return pid;
}
return -1;
}
int optimized_uicache(void) {
__block int rv=0;
NSMutableDictionary *registered = [NSMutableDictionary new];
static void (^readHandler)(NSFileHandle*) = ^(NSFileHandle *fh) {
NSData *output = [fh readDataToEndOfFile];
if (output.length==0) return;
const char *found_path = (const char *)[output bytes];
NSArray *found = [@(found_path) pathComponents];
if (found.count >= 3) {
NSString *appPath = [@"/Applications" stringByAppendingPathComponent:found[2]];
@synchronized (registered) {
if (registered[appPath]) return;
if (verbose) fprintf(stderr, "Updating %s\n", appPath.lastPathComponent.UTF8String);
registered[appPath] = @YES;
}
pid_t cydia_pid;
if ([found[2] isEqualToString:@"Cydia.app"] &&
(cydia_pid = pidOfCydia()) > 0) {
// We are in cydia and trying to refresh it - this will kill it. Let's schedule it for later.
if (verbose) fprintf(stderr, "Waiting to refresh Cydia...\n");
pid_t pid = fork();
if (pid == 0) {
setpgrp();
signal(SIGHUP, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
fclose(stdin);
freopen("/dev/null", "a", stderr);
freopen("/dev/null", "a", stdout);
pid = fork();
if (pid == 0) {
while (kill(cydia_pid, 0)==0) {
sleep(1);
}
const char *uicache = (*_NSGetArgv())[0];
execl(uicache, uicache, "-vvvvvvv", NULL);
fprintf(stderr, "Unable to exec\n");
fflush(stderr);
exit(-1);
}
exit(0);
} else if (pid > 0) {
int stat;
waitpid(pid, &stat, 0);
return;
} else {
fprintf(stderr, "Unable to fork\n");
}
}
if (!registerPath(appPath)) rv++;
}
};
NSFileManager *fm = [NSFileManager defaultManager];
NSMutableDictionary *apps = [NSMutableDictionary new];
NSMutableArray *cleanup = [NSMutableArray new];
NSMutableArray *finds = [NSMutableArray new];
if (verbose>1) fprintf(stderr, "Enumerating apps\n");
for (LSApplicationProxy *app in [workspace allApplications]) {
NSString *path = [[app bundleURL] path];
if (![path hasPrefix:@"/Applications/"]) continue;
if (verbose>1) fprintf(stderr, "Checking %s\n", path.lastPathComponent.UTF8String);
NSDate *lastRegistered = [app registeredDate];
if ([fm fileExistsAtPath:path]) {
// Check for updated components
NSTask *find = [NSTask new];
[find setLaunchPath:@"/usr/bin/find"];
[find setStandardOutput:[NSPipe pipe]];
NSString *stampPath = [NSString stringWithFormat:@"/var/tmp/uicache.stamp.%@", app.applicationIdentifier];
[fm createFileAtPath:stampPath contents:nil attributes:@{NSFileModificationDate: lastRegistered}];
[cleanup addObject:stampPath];
[find setArguments:@[ path, @"-newer", stampPath, @"-print0", @"-quit"]];
[finds addObject:find];
[find launch];
apps[path.lastPathComponent] = app;
} else {
if (verbose) fprintf(stderr, "De-registering removed app: %s\n", path.lastPathComponent.UTF8String);
@synchronized (registered) {
if (registered[path]) continue;
registered[path] = @YES;
}
if (!unregisterPath(path)) rv++;
}
}
for (NSString* existing in [[NSFileManager defaultManager] contentsOfDirectoryAtPath:@"/Applications" error:nil]) {
NSString *path = [@"/Applications" stringByAppendingPathComponent:existing];
if (apps[existing] || registered[path] || ![existing hasSuffix:@".app"]) continue;
if (verbose) fprintf(stderr, "Registering new app: %s\n", existing.UTF8String);
@synchronized (registered) {
registered[path] = @YES;
}
if (!registerPath(path)) rv++;
}
for (NSTask *find in finds) {
if (verbose>2) fprintf(stderr, "waiting for find %s\n", [find.arguments componentsJoinedByString:@" "].UTF8String);
readHandler([find.standardOutput fileHandleForReading]);
[find waitUntilExit];
}
for (NSString *path in cleanup) {
[fm removeItemAtPath:path error:nil];
}
return rv;
}
int main(int argc, const char *argv[])
{
if (getuid() == 0) {
// Be mobile
if (setuid(501)) {
fprintf(stderr, "Error: unable to become mobile");
return -1;
}
}
dlopen("/System/Library/PrivateFrameworks/MobileContainerManager.framework/MobileContainerManager", RTLD_LAZY);
dlopen("/System/Library/PrivateFrameworks/FrontBoardServices.framework/FrontBoardServices", RTLD_LAZY);
dlopen("/System/Library/PrivateFrameworks/SpringBoardServices.framework/SpringBoardServices", RTLD_LAZY);
Class $SBSRelaunchAction = objc_getClass("SBSRelaunchAction");
Class $FBSSystemService = objc_getClass("FBSSystemService");
$MCMPluginKitPluginDataContainer = objc_getClass("MCMPluginKitPluginDataContainer");
$MCMAppDataContainer = objc_getClass("MCMAppDataContainer");
$LSApplicationWorkspace = objc_getClass("LSApplicationWorkspace");
workspace = [$LSApplicationWorkspace defaultWorkspace];
static int rv=0;
@autoreleasepool {
bool respring=false, do_all=false;
void (*jb_oneshot_entitle_now)(pid_t a, uint64_t b);
void *libjb = dlopen("/usr/lib/libjailbreak.dylib", RTLD_LAZY);
if (libjb) {
dlerror();
jb_oneshot_entitle_now = (void (*)(pid_t, uint64_t))dlsym(libjb, "jb_oneshot_entitle_now");
if (!dlerror()) jb_oneshot_entitle_now(getpid(), 2);
}
NSMutableDictionary *paths = [NSMutableDictionary new];
NSMutableDictionary *unregister_paths = [NSMutableDictionary new];
static struct option long_options[] =
{
{"all", no_argument, 0, 'a'},
{"help", no_argument, 0, 'h'},
{"path", required_argument, 0, 'p'},
{"unregister", required_argument, 0, 'u'},
{"respring", no_argument, 0, 'r'},
{"verbose", no_argument, 0, 'v'},
{0, 0, 0, 0}
};
int option_index = 0;
char ch;
bool have_path = false;
while ((ch = getopt_long(argc, (char *const *)argv, "ap:ru:vh?", long_options, &option_index)) != -1) {
switch (ch)
{
case 'a':
do_all = true;
break;
case 'h':
usage();
break;
case 'p':
paths[@(optarg)] = @YES;
have_path = true;
break;
case 'r':
respring = true;
break;
case 'u':
unregister_paths[@(optarg)] = @YES;
have_path = true;
break;
case 'v':
verbose++;
break;
default:
break;
}
}
if (do_all || !$MCMPluginKitPluginDataContainer || !$MCMAppDataContainer || !$LSApplicationWorkspace) {
rv = standard_uicache();
} else if (have_path) {
for (NSString *path in [paths allKeys]) {
if (verbose) fprintf(stderr, "Refreshing %s\n", path.UTF8String);
if (!registerPath(path)) rv++;
}
for (NSString *path in [unregister_paths allKeys]) {
if (!unregisterPath(path)) rv++;
}
} else {
rv += optimized_uicache();
}
if ( respring )
{
#if TARGET_OS_IPHONE
pid_t sb_pid = launch_get_job_pid("com.apple.SpringBoard");
if ($SBSRelaunchAction && $FBSSystemService) {
id action = [$SBSRelaunchAction actionWithReason:@"respring" options:RestartRenderServer targetURL:nil];
id sharedService = [$FBSSystemService sharedService];
[sharedService sendActions:[NSSet setWithObject:action] withResult:nil];
for (int i=0; i<100; i++) {
if (kill(sb_pid, 0)) {
break;
}
usleep(1000);
}
} else {
system("launchctl stop " FRONTBOARD_ID);
system("launchctl stop com.apple.backboardd");
}
#elif TARGET_OS_TV
[[[PBSSystemServiceConnection sharedConnection] systemServiceProxy] relaunchBackboardd];
#else
#error "Unsupported target OS"
#endif
}
} // @autoreleasepool
return rv;
}