ish/app/AppDelegate.m

285 lines
11 KiB
Objective-C

//
// AppDelegate.m
// iSH
//
// Created by Theodore Dubois on 10/17/17.
//
#include <resolv.h>
#include <arpa/inet.h>
#include <netdb.h>
#import <SystemConfiguration/SystemConfiguration.h>
#import "AppDelegate.h"
#import "AppGroup.h"
#import "iOSFS.h"
#import "SceneDelegate.h"
#import "PasteboardDevice.h"
#import "LocationDevice.h"
#import "TerminalViewController.h"
#import "UserPreferences.h"
#include "kernel/init.h"
#include "kernel/calls.h"
#include "fs/dyndev.h"
#include "fs/devices.h"
#include "fs/path.h"
@interface AppDelegate ()
@property BOOL exiting;
@property NSString *unameVersion;
@property SCNetworkReachabilityRef reachability;
@end
static void ios_handle_exit(struct task *task, int code) {
// we are interested in init and in children of init
// this is called with pids_lock as an implementation side effect, please do not cite as an example of good API design
if (task->parent != NULL && task->parent->parent != NULL)
return;
// pid should be saved now since task would be freed
pid_t pid = task->pid;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:ProcessExitedNotification
object:nil
userInfo:@{@"pid": @(pid),
@"code": @(code)}];
});
}
// Put the abort message in the thread name so it gets included in the crash dump
static void ios_handle_die(const char *msg) {
char name[17];
pthread_getname_np(pthread_self(), name, sizeof(name));
NSString *newName = [NSString stringWithFormat:@"%s died: %s", name, msg];
pthread_setname_np(newName.UTF8String);
}
@implementation AppDelegate
- (int)boot {
NSFileManager *manager = [NSFileManager defaultManager];
NSURL *container = ContainerURL();
NSURL *alpineRoot = [container URLByAppendingPathComponent:@"roots/alpine"];
[manager createDirectoryAtURL:[container URLByAppendingPathComponent:@"roots"]
withIntermediateDirectories:YES
attributes:@{}
error:nil];
#if 0
// copy the files to the app container so I can more easily get them out
NSURL *documents = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask][0];
[NSFileManager.defaultManager removeItemAtURL:[documents URLByAppendingPathComponent:@"roots copy"] error:nil];
[NSFileManager.defaultManager copyItemAtURL:[container URLByAppendingPathComponent:@"roots"]
toURL:[documents URLByAppendingPathComponent:@"roots copy"]
error:nil];
#endif
if (![manager fileExistsAtPath:alpineRoot.path]) {
NSURL *alpineMaster = [NSBundle.mainBundle URLForResource:@"alpine" withExtension:nil];
NSError *error = nil;
[manager copyItemAtURL:alpineMaster toURL:alpineRoot error:&error];
if (error != nil) {
NSLog(@"%@", error);
exit(1);
}
}
alpineRoot = [alpineRoot URLByAppendingPathComponent:@"data"];
int err = mount_root(&fakefs, alpineRoot.fileSystemRepresentation);
if (err < 0)
return err;
fs_register(&iosfs);
fs_register(&iosfs_unsafe);
// need to do this first so that we can have a valid current for the generic_mknod calls
err = become_first_process();
if (err < 0)
return err;
// create some device nodes
// this will do nothing if they already exist
generic_mknodat(AT_PWD, "/dev/tty1", S_IFCHR|0666, dev_make(TTY_CONSOLE_MAJOR, 1));
generic_mknodat(AT_PWD, "/dev/tty2", S_IFCHR|0666, dev_make(TTY_CONSOLE_MAJOR, 2));
generic_mknodat(AT_PWD, "/dev/tty3", S_IFCHR|0666, dev_make(TTY_CONSOLE_MAJOR, 3));
generic_mknodat(AT_PWD, "/dev/tty4", S_IFCHR|0666, dev_make(TTY_CONSOLE_MAJOR, 4));
generic_mknodat(AT_PWD, "/dev/tty5", S_IFCHR|0666, dev_make(TTY_CONSOLE_MAJOR, 5));
generic_mknodat(AT_PWD, "/dev/tty6", S_IFCHR|0666, dev_make(TTY_CONSOLE_MAJOR, 6));
generic_mknodat(AT_PWD, "/dev/tty7", S_IFCHR|0666, dev_make(TTY_CONSOLE_MAJOR, 7));
generic_mknodat(AT_PWD, "/dev/tty", S_IFCHR|0666, dev_make(TTY_ALTERNATE_MAJOR, DEV_TTY_MINOR));
generic_mknodat(AT_PWD, "/dev/console", S_IFCHR|0666, dev_make(TTY_ALTERNATE_MAJOR, DEV_CONSOLE_MINOR));
generic_mknodat(AT_PWD, "/dev/ptmx", S_IFCHR|0666, dev_make(TTY_ALTERNATE_MAJOR, DEV_PTMX_MINOR));
generic_mknodat(AT_PWD, "/dev/null", S_IFCHR|0666, dev_make(MEM_MAJOR, DEV_NULL_MINOR));
generic_mknodat(AT_PWD, "/dev/zero", S_IFCHR|0666, dev_make(MEM_MAJOR, DEV_ZERO_MINOR));
generic_mknodat(AT_PWD, "/dev/full", S_IFCHR|0666, dev_make(MEM_MAJOR, DEV_FULL_MINOR));
generic_mknodat(AT_PWD, "/dev/random", S_IFCHR|0666, dev_make(MEM_MAJOR, DEV_RANDOM_MINOR));
generic_mknodat(AT_PWD, "/dev/urandom", S_IFCHR|0666, dev_make(MEM_MAJOR, DEV_URANDOM_MINOR));
generic_mkdirat(AT_PWD, "/dev/pts", 0755);
// Permissions on / have been broken for a while, let's fix them
generic_setattrat(AT_PWD, "/", (struct attr) {.type = attr_mode, .mode = 0755}, false);
// Register clipboard device driver and create device node for it
err = dyn_dev_register(&clipboard_dev, DEV_CHAR, DYN_DEV_MAJOR, DEV_CLIPBOARD_MINOR);
if (err != 0) {
return err;
}
generic_mknodat(AT_PWD, "/dev/clipboard", S_IFCHR|0666, dev_make(DYN_DEV_MAJOR, DEV_CLIPBOARD_MINOR));
err = dyn_dev_register(&location_dev, DEV_CHAR, DYN_DEV_MAJOR, DEV_LOCATION_MINOR);
if (err != 0)
return err;
generic_mknodat(AT_PWD, "/dev/location", S_IFCHR|0666, dev_make(DYN_DEV_MAJOR, DEV_LOCATION_MINOR));
do_mount(&procfs, "proc", "/proc", "", 0);
do_mount(&devptsfs, "devpts", "/dev/pts", "", 0);
iosfs_init(); // let it mount any filesystems from user defaults
[self configureDns];
exit_hook = ios_handle_exit;
die_handler = ios_handle_die;
NSString *sockTmp = [NSTemporaryDirectory() stringByAppendingString:@"ishsock"];
sock_tmp_prefix = strdup(sockTmp.UTF8String);
tty_drivers[TTY_CONSOLE_MAJOR] = &ios_console_driver;
set_console_device(TTY_CONSOLE_MAJOR, 1);
err = create_stdio("/dev/console", TTY_CONSOLE_MAJOR, 1);
if (err < 0)
return err;
NSArray<NSString *> *command;
command = UserPreferences.shared.bootCommand;
NSLog(@"%@", command);
char argv[4096];
[Terminal convertCommand:command toArgs:argv limitSize:sizeof(argv)];
const char *envp = "TERM=xterm-256color\0";
err = do_execve(command[0].UTF8String, command.count, argv, envp);
if (err < 0)
return err;
task_start(current);
return 0;
}
- (void)configureDns {
struct __res_state res;
if (EXIT_SUCCESS != res_ninit(&res)) {
exit(2);
}
NSMutableString *resolvConf = [NSMutableString new];
for (int i = 0; res.dnsrch[i] != NULL; i++) {
[resolvConf appendFormat:@"search %s\n", res.dnsrch[i]];
}
union res_sockaddr_union servers[NI_MAXSERV];
int serversFound = res_getservers(&res, servers, NI_MAXSERV);
char address[NI_MAXHOST];
for (int i = 0; i < serversFound; i ++) {
union res_sockaddr_union s = servers[i];
if (s.sin.sin_len == 0)
continue;
getnameinfo((struct sockaddr *) &s.sin, s.sin.sin_len,
address, sizeof(address),
NULL, 0, NI_NUMERICHOST);
[resolvConf appendFormat:@"nameserver %s\n", address];
}
current = pid_get_task(1);
struct fd *fd = generic_open("/etc/resolv.conf", O_WRONLY_ | O_CREAT_ | O_TRUNC_, 0666);
if (!IS_ERR(fd)) {
fd->ops->write(fd, resolvConf.UTF8String, [resolvConf lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
fd_close(fd);
}
}
static int bootError;
+ (int)bootError {
return bootError;
}
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey,id> *)launchOptions {
NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
if ([defaults boolForKey:@"hail mary"]) {
[defaults removeObjectForKey:kPreferenceBootCommandKey];
[defaults removeObjectForKey:kPreferenceLaunchCommandKey];
[defaults setBool:NO forKey:@"hail mary"];
}
bootError = [self boot];
return YES;
}
void NetworkReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info) {
AppDelegate *self = (__bridge AppDelegate *) info;
[self configureDns];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// get the network permissions popup to appear on chinese devices
[[NSURLSession.sharedSession dataTaskWithURL:[NSURL URLWithString:@"http://captive.apple.com"]] resume];
self.unameVersion = [NSString stringWithFormat:@"iSH %@ (%@)",
[NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"],
[NSBundle.mainBundle objectForInfoDictionaryKey:(NSString *) kCFBundleVersionKey]];
extern const char *uname_version;
uname_version = self.unameVersion.UTF8String;
[UserPreferences.shared addObserver:self forKeyPath:@"shouldDisableDimming" options:NSKeyValueObservingOptionInitial context:nil];
struct sockaddr_in6 address = {
.sin6_len = sizeof(address),
.sin6_family = AF_INET6,
};
self.reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (struct sockaddr *) &address);
SCNetworkReachabilityContext context = {
.info = (__bridge void *) self,
};
SCNetworkReachabilitySetCallback(self.reachability, NetworkReachabilityCallback, &context);
SCNetworkReachabilityScheduleWithRunLoop(self.reachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
if (self.window != nil) {
// For iOS <13, where the app delegate owns the window instead of the scene
TerminalViewController *vc = (TerminalViewController *) self.window.rootViewController;
currentTerminalViewController = vc;
[vc startNewSession];
}
return YES;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
UIApplication.sharedApplication.idleTimerDisabled = UserPreferences.shared.shouldDisableDimming;
}
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions API_AVAILABLE(ios(13.0)) {
for (UISceneSession *sceneSession in sceneSessions) {
NSString *terminalUUID = sceneSession.stateRestorationActivity.userInfo[@"TerminalUUID"];
[[Terminal terminalWithUUID:[[NSUUID alloc] initWithUUIDString:terminalUUID]] destroy];
}
}
- (void)dealloc {
if (self.reachability != NULL) {
SCNetworkReachabilityUnscheduleFromRunLoop(self.reachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
CFRelease(self.reachability);
}
}
- (void)exitApp {
self.exiting = YES;
id app = [UIApplication sharedApplication];
[app suspend];
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
if (self.exiting)
exit(0);
}
@end
NSString *const ProcessExitedNotification = @"ProcessExitedNotification";