ish/app/AppDelegate.m
2019-05-11 15:59:19 -07:00

255 lines
9.6 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 "AppDelegate.h"
#import "TerminalViewController.h"
#import "UserPreferences.h"
#include "kernel/init.h"
#include "kernel/calls.h"
@interface AppDelegate ()
@property int sessionPid;
@property BOOL exiting;
@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;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:ProcessExitedNotification
object:nil
userInfo:@{@"pid": @(task->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)startThings {
NSFileManager *manager = [NSFileManager defaultManager];
NSURL *container = [manager containerURLForSecurityApplicationGroupIdentifier:PRODUCT_APP_GROUP_IDENTIFIER];
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;
// 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_mknod("/dev/console", S_IFCHR|0666, dev_make(5, 1));
generic_mknod("/dev/tty1", S_IFCHR|0666, dev_make(4, 1));
generic_mknod("/dev/tty2", S_IFCHR|0666, dev_make(4, 2));
generic_mknod("/dev/tty3", S_IFCHR|0666, dev_make(4, 3));
generic_mknod("/dev/tty4", S_IFCHR|0666, dev_make(4, 4));
generic_mknod("/dev/tty5", S_IFCHR|0666, dev_make(4, 5));
generic_mknod("/dev/tty6", S_IFCHR|0666, dev_make(4, 6));
generic_mknod("/dev/tty7", S_IFCHR|0666, dev_make(4, 7));
generic_mknod("/dev/tty", S_IFCHR|0666, dev_make(5, 0));
generic_mknod("/dev/ptmx", S_IFCHR|0666, dev_make(5, 2));
generic_mknod("/dev/random", S_IFCHR|0666, dev_make(1, 8));
generic_mknod("/dev/urandom", S_IFCHR|0666, dev_make(1, 9));
do_mount(&procfs, "proc", "/proc", 0);
do_mount(&devptsfs, "devpts", "/dev/pts", 0);
// configure dns
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];
}
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);
}
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_tty_driver;
set_console_device(TTY_CONSOLE_MAJOR, 1);
err = create_stdio("/dev/console");
if (err < 0)
return err;
NSArray<NSString *> *command;
if (UserPreferences.shared.bootEnabled) {
command = UserPreferences.shared.bootCommand;
} else {
command = UserPreferences.shared.launchCommand;
}
NSLog(@"%@", command);
char argv[4096];
[self convertCommand:command toArgs:argv limitSize:sizeof(argv)];
const char *envp = "TERM=xterm-256color\0";
err = sys_execve(argv, argv, envp);
if (err < 0)
return err;
task_start(current);
if (UserPreferences.shared.bootEnabled) {
err = [self startSession];
if (err < 0)
return err;
}
return 0;
}
- (int)startSession {
int err = become_new_init_child();
if (err < 0)
return err;
err = create_stdio("/dev/tty7");
if (err < 0)
return err;
char argv[4096];
[self convertCommand:UserPreferences.shared.launchCommand toArgs:argv limitSize:sizeof(argv)];
const char *envp = "TERM=xterm-256color\0";
err = sys_execve(argv, argv, envp);
if (err < 0)
return err;
self.sessionPid = current->pid;
task_start(current);
return 0;
}
- (void)convertCommand:(NSArray<NSString *> *)command toArgs:(char *)argv limitSize:(size_t)maxSize {
char *p = argv;
for (NSString *cmd in command) {
const char *c = cmd.UTF8String;
// Save space for the final NUL byte in argv
while (p < argv + maxSize - 1 && (*p++ = *c++));
// If we reach the end of the buffer, the last string still needs to be
// NUL terminated
*p = '\0';
}
// Add the final NUL byte to argv
*++p = '\0';
}
- (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];
[UserPreferences.shared addObserver:self forKeyPath:@"shouldDisableDimming" options:NSKeyValueObservingOptionInitial context:nil];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(processExited:)
name:ProcessExitedNotification
object:nil];
int err = [self startThings];
if (err < 0) {
NSString *message = [NSString stringWithFormat:@"could not initialize"];
NSString *subtitle = [NSString stringWithFormat:@"error code %d", err];
if (err == _EINVAL)
subtitle = [subtitle stringByAppendingString:@"\n(try reinstalling the app, see release notes for details)"];
[self showMessage:message subtitle:subtitle fatal:NO];
NSLog(@"failed with code %d", err);
}
return YES;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
UIApplication.sharedApplication.idleTimerDisabled = UserPreferences.shared.shouldDisableDimming;
}
- (void)processExited:(NSNotification *)notif {
int pid = [notif.userInfo[@"pid"] intValue];
if (pid == self.sessionPid) {
[self startSession];
}
}
- (void)showMessage:(NSString *)message subtitle:(NSString *)subtitle fatal:(BOOL)fatal {
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:message message:subtitle preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"k"
style:UIAlertActionStyleDefault
handler:nil]];
[self.window.rootViewController presentViewController:alert animated:YES completion:nil];
});
}
- (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";