Files
ish/app/AppDelegate.m
T
2026-05-03 08:52:40 -07:00

343 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 "ExceptionExfiltrator.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 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)}];
});
}
static void ios_handle_die(const char *msg) {
NSString *message = [NSString stringWithFormat:@"%s: %s", __func__, msg];
iSHExceptionHandler([[NSException alloc] initWithName:NSGenericException reason:message userInfo:nil]);
}
#elif ISH_LINUX
void ReportPanic(const char *message) {
[NSNotificationCenter.defaultCenter postNotificationName:KernelPanicNotification object:nil userInfo:@{@"message":@(message)}];
}
#endif
static int bootError;
static NSString *const kSkipStartupMessage = @"Skip Startup Message";
@implementation AppDelegate
- (int)boot {
#if !ISH_LINUX
NSURL *root = [Roots.instance rootUrl:Roots.instance.defaultRoot];
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();
// 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
// On first launch, this will trigger the import of the default root. Make sure to do this before entering the kernel, because it needs to run something on the main thread, and that would deadlock.
[Roots instance];
NSArray<NSString *> *args = @[];
actuate_kernel([args componentsJoinedByString:@" "].UTF8String);
#endif
return 0;
}
#if ISH_LINUX
const char *DefaultRootPath() {
return [Roots.instance rootUrl:Roots.instance.defaultRoot].fileSystemRepresentation;
}
void SyncHostname(void) {
async_do_in_workqueue(^{
char hostname[256];
if (gethostname(hostname, sizeof(hostname)) < 0)
return;
linux_sethostname(hostname);
});
}
#endif
- (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];
#if ISH_LINUX
[NSNotificationCenter.defaultCenter addObserverForName:UIApplicationWillEnterForegroundNotification object:UIApplication.sharedApplication queue:nil usingBlock:^(NSNotification * _Nonnull note) {
SyncHostname();
}];
SyncHostname();
#endif
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
NSString *ishVersion = [NSString stringWithFormat:@"iSH %@ (%@)",
[NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"],
[NSBundle.mainBundle objectForInfoDictionaryKey:(NSString *) kCFBundleVersionKey]];
extern const char *proc_ish_version;
proc_ish_version = strdup(ishVersion.UTF8String);
// this defaults key is set when taking app store screenshots
extern const char *uname_hostname_override;
NSString *hostnameOverride = UserPreferences.shared._hostnameOverride;
if (@available(iOS 16.0, *)) { // Hostname obfuscation is in effect
hostnameOverride = hostnameOverride ? hostnameOverride : UserPreferences.shared.hostnameOverride;
}
if (hostnameOverride) {
uname_hostname_override = strdup(hostnameOverride.UTF8String);
}
#endif
[UserPreferences.shared observe:@[@"shouldDisableDimming"] options:NSKeyValueObservingOptionInitial
owner:self usingBlock:^(typeof(self) self) {
dispatch_async(dispatch_get_main_queue(), ^{
UIApplication.sharedApplication.idleTimerDisabled = UserPreferences.shared.shouldDisableDimming;
});
}];
// This code is IPv4 and IPv6 aware: see https://developer.apple.com/library/archive/samplecode/Reachability/Listings/ReadMe_md.html
struct sockaddr_in address = {
.sin_len = sizeof(address),
.sin_family = AF_INET,
};
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