mirror of
https://github.com/ish-app/ish.git
synced 2026-01-25 14:06:40 +00:00
285 lines
11 KiB
Objective-C
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";
|