Files
ish/app/AppDelegate.m
T
2021-12-13 20:00:45 -08:00

342 lines
13 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// 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 "AboutViewController.h"
#import "AppDelegate.h"
#import "AppGroup.h"
#import "CurrentRoot.h"
#import "iOSFS.h"
#import "SceneDelegate.h"
#import "PasteboardDevice.h"
#import "LocationDevice.h"
#import "NSObject+SaneKVO.h"
#import "Roots.h"
#import "TerminalViewController.h"
#import "UserPreferences.h"
#import "UIApplication+OpenURL.h"
#include "kernel/init.h"
#include "kernel/calls.h"
#include "fs/dyndev.h"
#include "fs/devices.h"
#include "fs/path.h"
#if ISH_LINUX
#import "LinuxInterop.h"
#endif
@interface AppDelegate ()
@property BOOL exiting;
@property NSString *unameVersion;
@property NSString *unameHostname;
@property SCNetworkReachabilityRef reachability;
@end
#if !ISH_LINUX
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);
}
#elif ISH_LINUX
void ReportPanic(const char *message, void (^completion)(void)) {
[NSNotificationCenter.defaultCenter postNotificationName:KernelPanicNotification object:nil userInfo:@{@"message":@(message)}];
}
#endif
static int bootError;
static NSString *const kSkipStartupMessage = @"Skip Startup Message";
@implementation AppDelegate
- (int)boot {
NSURL *root = [Roots.instance rootUrl:Roots.instance.defaultRoot];
#if !ISH_LINUX
int err = mount_root(&fakefs, [root URLByAppendingPathComponent:@"data"].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;
FsInitialize();
// 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;
#if !TARGET_OS_SIMULATOR
NSString *sockTmp = [NSTemporaryDirectory() stringByAppendingString:@"ishsock"];
sock_tmp_prefix = strdup(sockTmp.UTF8String);
#endif
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);
#else
if (strchr(root.fileSystemRepresentation, '"') != NULL) {
NSLog(@"can't deal with double quote in rootfs path");
return _EINVAL;
}
NSArray<NSString *> *args = @[
@"rootfstype=fakefs",
[NSString stringWithFormat:@"root=\"%s\"", root.fileSystemRepresentation],
@"rw",
];
actuate_kernel([args componentsJoinedByString:@" "].UTF8String);
#endif
return 0;
}
- (void)configureDns {
#if !ISH_LINUX
struct __res_state res;
if (EXIT_SUCCESS != res_ninit(&res)) {
exit(2);
}
NSMutableString *resolvConf = [NSMutableString new];
if (res.dnsrch[0] != NULL) {
[resolvConf appendString:@"search"];
for (int i = 0; res.dnsrch[i] != NULL; i++) {
[resolvConf appendFormat:@" %s", res.dnsrch[i]];
}
[resolvConf appendString:@"\n"];
}
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);
}
#endif
}
+ (int)bootError {
return bootError;
}
+ (void)maybePresentStartupMessageOnViewController:(UIViewController *)vc {
if ([NSUserDefaults.standardUserDefaults integerForKey:kSkipStartupMessage] >= 1)
return;
if (!FsIsManaged()) {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Install iSHs built-in APK?"
message:@"iSH now includes the APK package manager, but it must be manually activated."
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Show me how"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
[UIApplication openURL:@"https://go.ish.app/get-apk"];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Don't show again"
style:UIAlertActionStyleDefault
handler:nil]];
[vc presentViewController:alert animated:YES completion:nil];
}
[NSUserDefaults.standardUserDefaults setInteger:1 forKey:kSkipStartupMessage];
}
- (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"];
}
if ([NSUserDefaults.standardUserDefaults boolForKey:@"recovery"])
return YES;
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];
if ([NSUserDefaults.standardUserDefaults boolForKey:@"FASTLANE_SNAPSHOT"])
[UIView setAnimationsEnabled:NO];
#if !ISH_LINUX
self.unameVersion = [NSString stringWithFormat:@"iSH %@ (%@)",
[NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"],
[NSBundle.mainBundle objectForInfoDictionaryKey:(NSString *) kCFBundleVersionKey]];
extern const char *uname_version;
uname_version = self.unameVersion.UTF8String;
// this defaults key is set when taking app store screenshots
self.unameHostname = [NSUserDefaults.standardUserDefaults stringForKey:@"hostnameOverride"];
extern const char *uname_hostname_override;
uname_hostname_override = self.unameHostname.UTF8String;
#endif
[UserPreferences.shared observe:@[@"shouldDisableDimming"] options:NSKeyValueObservingOptionInitial
owner:self usingBlock:^(typeof(self) self) {
UIApplication.sharedApplication.idleTimerDisabled = UserPreferences.shared.shouldDisableDimming;
}];
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
if ([NSUserDefaults.standardUserDefaults boolForKey:@"recovery"]) {
UINavigationController *vc = [[UIStoryboard storyboardWithName:@"About" bundle:nil] instantiateInitialViewController];
AboutViewController *avc = (AboutViewController *) vc.topViewController;
avc.recoveryMode = YES;
self.window.rootViewController = vc;
return YES;
}
TerminalViewController *vc = (TerminalViewController *) self.window.rootViewController;
currentTerminalViewController = vc;
[vc startNewSession];
}
return YES;
}
- (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
#if !ISH_LINUX
NSString *const ProcessExitedNotification = @"ProcessExitedNotification";
#else
NSString *const KernelPanicNotification = @"KernelPanicNotification";
#endif