Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/MatthewMerrill/ish
Browse files Browse the repository at this point in the history
  • Loading branch information
MatthewMerrill committed May 14, 2019
2 parents 60fa13f + e4c6e55 commit 596dbf2
Show file tree
Hide file tree
Showing 77 changed files with 1,896 additions and 635 deletions.
2 changes: 1 addition & 1 deletion ISSUE_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!--
If you're reporting a crash, please include a crash dump. You can find them in Settings > Privacy > Diagnostics and Usage.
If you're reporting a crash, please include a crash dump. You can find them in Settings -> Privacy -> Analytics -> Analytics Data on iOS 12, or Settings -> Privacy -> Diagnostics and Usage on iOS 11.
If this is a "Bad system call", "Illegal instruction", or "Segmentation fault", run `dmesg` to get a dump of the log messages and include the relevant output.
-->
25 changes: 6 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,17 @@ You'll need these things to build the project:
- Clang and LLD (on mac, `brew install llvm`, on linux, `sudo apt install clang lld` or `sudo pacman -S clang lld` or whatever)
- sqlite3 (this is so common it may already be installed on linux and is definitely already installed on mac. if not, do something like `sudo apt install libsqlite3-dev`)

To set up your environment, cd to the project and run `meson build` to create a build directory in `build`. Then cd to the build directory and run `ninja`.

To set up a self-contained Alpine linux filesystem, download the Alpine minirootfs tarball for i386 from the [Alpine website](https://alpinelinux.org/downloads/) and run the `tools/fakefsify.py` script. Specify the minirootfs tarball as the first argument and the name of the output directory as the second argument. Then you can run things inside the Alpine filesystem with `./ish -f alpine /bin/login -f root`, assuming the output directory is called `alpine`.

You can replace `ish` with `tools/ptraceomatic` to run the program in a real process and single step and compare the registers at each step. I use it for debugging. Requires 64-bit Linux 4.11 or later.
## Build for iOS

To compile the iOS app, just open the Xcode project and click run. There are scripts that should download and set up the alpine filesystem and create build directories for cross compilation and so on automatically.
Open the project in Xcode and click Run. If you're not me, first open the project build settings and change the Product Bundle Identifier to something unique. There are scripts that should do everything else automatically. If you run into any problems, open an issue and I'll try to help.

## Further setup guide
## Build command line tool for testing

To enable local development there are a few more steps that needs to be done.

- Go to the project settings in Xcode find the "iSH" target
- Under "General" change the bundle identifier to a specific identifier for you
- Under "Capabilities" change the name of the "App Group" and remove the old app group

- Go to the "iSHFileProvider" target
- Under "General" use the same bundle identifier you created before and add `.FileProvider` to it
- Under "Capabilities" use the same name of the "App Group" as for the "iSH" target
To set up your environment, cd to the project and run `meson build` to create a build directory in `build`. Then cd to the build directory and run `ninja`.

- Go to the file `app/AppDelegate.m`
- Change the string in the function `manager containerURLForSecurityApplicationGroupIdentifier:` to your App Group name that you entered in the step before.
To set up a self-contained Alpine linux filesystem, download the Alpine minirootfs tarball for i386 from the [Alpine website](https://alpinelinux.org/downloads/) and run the `tools/fakefsify.py` script. Specify the minirootfs tarball as the first argument and the name of the output directory as the second argument. Then you can run things inside the Alpine filesystem with `./ish -f alpine /bin/login -f root`, assuming the output directory is called `alpine`.

Congratulations! You should now have the app running!
You can replace `ish` with `tools/ptraceomatic` to run the program in a real process and single step and compare the registers at each step. I use it for debugging. Requires 64-bit Linux 4.11 or later.

# A note on the JIT

Expand Down
25 changes: 20 additions & 5 deletions app/AboutViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ @interface AboutViewController ()
@property (weak, nonatomic) IBOutlet UITableViewCell *themeCell;
@property (weak, nonatomic) IBOutlet UISwitch *disableDimmingSwitch;
@property (weak, nonatomic) IBOutlet UITextField *launchCommandField;
@property (weak, nonatomic) IBOutlet UITextField *bootCommandField;
@property (weak, nonatomic) IBOutlet UISwitch *bootEnabledSwitch;

@property (weak, nonatomic) IBOutlet UITableViewCell *sendFeedback;
@property (weak, nonatomic) IBOutlet UITableViewCell *openGithub;
Expand Down Expand Up @@ -43,6 +45,8 @@ - (void)_addObservers {
[prefs addObserver:self forKeyPath:@"capsLockMapping" options:opts context:nil];
[prefs addObserver:self forKeyPath:@"fontSize" options:opts context:nil];
[prefs addObserver:self forKeyPath:@"launchCommand" options:opts context:nil];
[prefs addObserver:self forKeyPath:@"bootCommand" options:opts context:nil];
[prefs addObserver:self forKeyPath:@"bootEnabled" options:opts context:nil];
}

- (void)_removeObservers {
Expand All @@ -51,6 +55,8 @@ - (void)_removeObservers {
[prefs removeObserver:self forKeyPath:@"capsLockMapping"];
[prefs removeObserver:self forKeyPath:@"fontSize"];
[prefs removeObserver:self forKeyPath:@"launchCommand"];
[prefs removeObserver:self forKeyPath:@"bootCommand"];
[prefs removeObserver:self forKeyPath:@"bootEnabled"];
} @catch (NSException * __unused exception) {}
}

Expand All @@ -77,6 +83,8 @@ - (void)_updatePreferenceUI {
self.capsLockMappingCell.detailTextLabel.text = capsLockMappingDescr;
self.disableDimmingSwitch.on = UserPreferences.shared.shouldDisableDimming;
self.launchCommandField.text = [UserPreferences.shared.launchCommand componentsJoinedByString:@" "];
self.bootCommandField.text = [UserPreferences.shared.bootCommand componentsJoinedByString:@" "];
self.bootEnabledSwitch.on = UserPreferences.shared.bootEnabled;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Expand All @@ -91,7 +99,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath
[UIApplication openURL:@"https://discord.gg/SndDh5y"];
} else if (cell == self.exportContainerCell) {
// copy the files to the app container so they can be extracted from iTunes file sharing
NSURL *container = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:@"group.app.ish.iSH"];
NSURL *container = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:PRODUCT_APP_GROUP_IDENTIFIER];
NSURL *documents = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask][0];
[NSFileManager.defaultManager removeItemAtURL:[documents URLByAppendingPathComponent:@"roots copy"] error:nil];
[NSFileManager.defaultManager copyItemAtURL:[container URLByAppendingPathComponent:@"roots"]
Expand All @@ -108,17 +116,24 @@ - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return sections;
}

- (IBAction)launchCommandSubmit:(id)sender {
- (IBAction)disableDimmingChanged:(id)sender {
UserPreferences.shared.shouldDisableDimming = self.disableDimmingSwitch.on;
}

- (IBAction)textBoxSubmit:(id)sender {
[sender resignFirstResponder];
}

- (IBAction)launchCommandChanged:(id)sender {
UserPreferences.shared.launchCommand = [self.launchCommandField.text componentsSeparatedByString:@" "];
NSLog(@"asdf");
}

- (IBAction)disableDimmingChanged:(id)sender {
UserPreferences.shared.shouldDisableDimming = self.disableDimmingSwitch.on;
- (IBAction)bootCommandChanged:(id)sender {
UserPreferences.shared.bootCommand = [self.bootCommandField.text componentsSeparatedByString:@" "];
}

- (IBAction)bootEnabledChanged:(id)sender {
UserPreferences.shared.bootEnabled = self.bootEnabledSwitch.on;
}

@end
2 changes: 1 addition & 1 deletion app/AppDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@

@end

extern NSString *const ISHExitedNotification;
extern NSString *const ProcessExitedNotification;
149 changes: 114 additions & 35 deletions app/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,27 @@
#import "UserPreferences.h"
#include "kernel/init.h"
#include "kernel/calls.h"
#include "fs/path.h"

@interface AppDelegate ()

@property int sessionPid;

@property BOOL exiting;

@end

static void ios_handle_exit(int code) {
[[NSNotificationCenter defaultCenter] postNotificationName:ISHExitedNotification object:nil];
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
Expand All @@ -36,7 +48,7 @@ @implementation AppDelegate

- (int)startThings {
NSFileManager *manager = [NSFileManager defaultManager];
NSURL *container = [manager containerURLForSecurityApplicationGroupIdentifier:@"group.app.ish.iSH"];
NSURL *container = [manager containerURLForSecurityApplicationGroupIdentifier:PRODUCT_APP_GROUP_IDENTIFIER];
NSURL *alpineRoot = [container URLByAppendingPathComponent:@"roots/alpine"];
[manager createDirectoryAtURL:[container URLByAppendingPathComponent:@"roots"]
withIntermediateDirectories:YES
Expand Down Expand Up @@ -66,29 +78,30 @@ - (int)startThings {
if (err < 0)
return err;

create_first_process();
NSArray<NSString *> *command = UserPreferences.shared.launchCommand;
char argv[4096];
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 + sizeof(argv) - 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';
const char *envp = "TERM=xterm-256color\0";
err = sys_execve(argv, argv, envp);
if (err < 0)
return err;
err = create_stdio(&ios_tty_driver);
// 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;
exit_hook = ios_handle_exit;
die_handler = ios_handle_die;

// 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/null", S_IFCHR|0666, dev_make(1, 3));
generic_mknod("/dev/random", S_IFCHR|0666, dev_make(1, 8));
generic_mknod("/dev/urandom", S_IFCHR|0666, dev_make(1, 9));
generic_mkdirat(AT_PWD, "/dev/pts", 0755);

do_mount(&procfs, "proc", "/proc", 0);
do_mount(&devptsfs, "devpts", "/dev/pts", 0);

// configure dns
struct __res_state res;
Expand All @@ -105,7 +118,7 @@ - (int)startThings {
for (int i = 0; i < serversFound; i ++) {
union res_sockaddr_union s = servers[i];
if (s.sin.sin_len == 0)
continue;
continue;
getnameinfo((struct sockaddr *) &s.sin, s.sin.sin_len,
address, sizeof(address),
NULL, 0, NI_NUMERICHOST);
Expand All @@ -117,25 +130,84 @@ - (int)startThings {
fd_close(fd);
}

// create some device nodes
// this will do nothing if they already exist
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");
do_mount(&devptsfs, "devpts", "/dev/pts");
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"];
Expand All @@ -152,6 +224,13 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
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];
Expand All @@ -175,4 +254,4 @@ - (void)applicationDidEnterBackground:(UIApplication *)application {

@end

NSString *const ISHExitedNotification = @"ISHExitedNotification";
NSString *const ProcessExitedNotification = @"ProcessExitedNotification";
Loading

0 comments on commit 596dbf2

Please sign in to comment.