From 22a0f396e6da224b4435e262048d6d27f0e8ae2b Mon Sep 17 00:00:00 2001 From: meeh Date: Wed, 26 Sep 2018 20:42:58 +0000 Subject: [PATCH] Mac OS X Launcher: * Enabled Apple's "Hardened Runtime", however unsecure memory had to be allowed to spawn java etc. * Updated docs about Event Manager code * Make the launcher handle cases where extract is incomplete or invalid * Bugfixes as always --- .../I2PLauncher/SwiftMainDelegate.swift | 16 +++- .../I2PLauncher/routermgmt/DetectJava.swift | 1 + .../routermgmt/RouterManager.swift | 45 +++++++++--- launchers/macosx/README.md | 19 +++++ launchers/macosx/RouterTask.mm | 4 +- launchers/macosx/SBridge.mm | 11 +-- launchers/macosx/main.mm | 73 ++++++++++++------- launchers/macosx/osx_create_dmg.sh | 4 + 8 files changed, 123 insertions(+), 50 deletions(-) diff --git a/launchers/macosx/I2PLauncher/SwiftMainDelegate.swift b/launchers/macosx/I2PLauncher/SwiftMainDelegate.swift index e0de9e5d3..f11695c8d 100644 --- a/launchers/macosx/I2PLauncher/SwiftMainDelegate.swift +++ b/launchers/macosx/I2PLauncher/SwiftMainDelegate.swift @@ -59,10 +59,18 @@ import Cocoa let sub:Subprocess = Subprocess.init(executablePath: "/bin/sh", arguments: cmdArgs) let results:ExecutionResult = sub.execute(captureOutput: true)! if (results.didCaptureOutput) { - let i2pVersion = results.outputLines.first?.replace(target: "I2P Core version: ", withString: "") - NSLog("I2P version detected: %@",i2pVersion ?? "Unknown") - RouterProcessStatus.routerVersion = i2pVersion - RouterManager.shared().eventManager.trigger(eventName: "router_version", information: i2pVersion) + if (results.status == 0) { + let i2pVersion = results.outputLines.first?.replace(target: "I2P Core version: ", withString: "") + NSLog("I2P version detected: %@",i2pVersion ?? "Unknown") + RouterProcessStatus.routerVersion = i2pVersion + RouterManager.shared().eventManager.trigger(eventName: "router_version", information: i2pVersion) + RouterManager.shared().eventManager.trigger(eventName: "router_can_start", information: i2pVersion) + } else { + NSLog("Non zero exit code from subprocess while trying to detect version number!") + for line in results.errorsLines { + NSLog(line) + } + } } else { print("Warning: Version Detection did NOT captured output") } diff --git a/launchers/macosx/I2PLauncher/routermgmt/DetectJava.swift b/launchers/macosx/I2PLauncher/routermgmt/DetectJava.swift index 186318e09..43a60c6c5 100644 --- a/launchers/macosx/I2PLauncher/routermgmt/DetectJava.swift +++ b/launchers/macosx/I2PLauncher/routermgmt/DetectJava.swift @@ -32,6 +32,7 @@ import Foundation DetectJava.hasJRE = true self.javaHome = self.javaHome.replace(target: "\n", withString: "").replace(target: "Internet Plug-Ins", withString: "Internet\\ Plug-Ins") print("DetectJava.javaHome did change to "+self.javaHome) + RouterManager.shared().eventManager.trigger(eventName: "java_found", information: self.javaHome) } }; private var testedEnv : Bool = false diff --git a/launchers/macosx/I2PLauncher/routermgmt/RouterManager.swift b/launchers/macosx/I2PLauncher/routermgmt/RouterManager.swift index 695b402c3..8715542e3 100644 --- a/launchers/macosx/I2PLauncher/routermgmt/RouterManager.swift +++ b/launchers/macosx/I2PLauncher/routermgmt/RouterManager.swift @@ -8,6 +8,13 @@ import Foundation + +enum ErrorsInRouterMgmr: Swift.Error { + case NoJavaFound + case InvalidVersion + case NotFound +} + class RouterManager : NSObject { // MARK: - Properties @@ -18,6 +25,10 @@ class RouterManager : NSObject { var logViewStorage: NSTextStorage? + private static func handleRouterException(information:Any?) { + NSLog("event! - handle router exception") + NSLog(information as! String) + } private static func handleRouterStart(information:Any?) { NSLog("event! - handle router start") RouterProcessStatus.routerStartedAt = Date() @@ -31,17 +42,32 @@ class RouterManager : NSObject { RouterProcessStatus.isRouterRunning = false } private static func handleRouterPid(information:Any?) { - Swift.print("event! - handle router pid: %s", information ?? "") + Swift.print("event! - handle router pid: ", information ?? "") } private static func handleRouterVersion(information:Any?) { - Swift.print("event! - handle router version: %s", information ?? "") - let currentVersion : String = information as! String - if (packedVersion.compare(currentVersion, options: .numeric) == .orderedDescending) { - Swift.print("event! - router version: Packed version is newer, gonna re-deploy") - RouterManager.shared().eventManager.trigger(eventName: "router_must_upgrade", information: "got new version") - } else { - Swift.print("event! - router version: No update needed") - RouterManager.shared().eventManager.trigger(eventName: "router_can_start", information: "all ok") + do { + Swift.print("event! - handle router version: ", information ?? "") + guard let currentVersion : String = information as? String else { + throw ErrorsInRouterMgmr.InvalidVersion + } + if (currentVersion == "Unknown") { + throw ErrorsInRouterMgmr.InvalidVersion + } + if (packedVersion.compare(currentVersion, options: .numeric) == .orderedDescending) { + Swift.print("event! - router version: Packed version is newer, gonna re-deploy") + RouterManager.shared().eventManager.trigger(eventName: "router_must_upgrade", information: "got new version") + } else { + Swift.print("event! - router version: No update needed") + RouterManager.shared().eventManager.trigger(eventName: "router_can_start", information: "all ok") + } + } catch ErrorsInRouterMgmr.InvalidVersion { + // This is most likely due to an earlier extract got killed halfway or something + // Trigger an update + RouterManager.shared().eventManager.trigger(eventName: "router_must_upgrade", information: "invalid version found") + } catch { + // WTF + NSLog("Fatal error in RouterManager"); + print(error) } } @@ -57,6 +83,7 @@ class RouterManager : NSObject { routerManager.eventManager.listenTo(eventName: "router_stop", action: handleRouterStop) routerManager.eventManager.listenTo(eventName: "router_pid", action: handleRouterPid) routerManager.eventManager.listenTo(eventName: "router_version", action: handleRouterVersion) + routerManager.eventManager.listenTo(eventName: "router_exception", action: handleRouterException) return routerManager }() diff --git a/launchers/macosx/README.md b/launchers/macosx/README.md index e8aad2993..327d58654 100644 --- a/launchers/macosx/README.md +++ b/launchers/macosx/README.md @@ -1,5 +1,24 @@ # The Mac OS X Launcher +## The Event Manager + +This is some Swift code which makes the application use events to signal the different compoents of the application. This seems to be the cleanest way since we're doing java subprocesses and so on. This can also be named a publish-subscribe system as well. If you're familiar with Javascript it would be a handy skill. + +### Event Overview + +| Event name | Details sent as arguments | Description | +| ------------- | ------------- | ------------- | +| router_start | nothing | This event get triggered when the router starts | +| router_exception | exception message | This will be triggered in case something within the functions that start the java (router) subprocess throws an exception | +| java_found | the location | This will be triggered once the DetectJava swift class has found java | +| router_must_upgrade | nothing | This will be triggered if no I2P is found, or if it's failing to get the version from the jar file | +| extract_completed | nothing | This is triggered when the deployment process is done extracting I2P to it's directory | +| router_can_start | nothing | This event will be triggered when I2P is found and a version number has been found, router won't start before this event | +| router_stop | error if any | Triggered when the router subprocess exits | +| router_pid | the pid number as string | Triggered when we know the pid of the router subprocess | +| router_version | the version string | Triggered when we have successfully extracted current I2P version | +| extract_errored | the error message | Triggered if the process didn't exit correctly | + ## Misc **Note** this project is WIP, cause Meeh has yet to merge in Obj-C/Swift code for GUI stuff in OSX. diff --git a/launchers/macosx/RouterTask.mm b/launchers/macosx/RouterTask.mm index 2f269869b..a845f1e1d 100644 --- a/launchers/macosx/RouterTask.mm +++ b/launchers/macosx/RouterTask.mm @@ -85,9 +85,7 @@ NSLog(@"Expection occurred %@", [e reason]); auto swiftRouterStatus = [[RouterProcessStatus alloc] init]; self.isRouterRunning = NO; - [swiftRouterStatus setRouterStatus: false]; - [swiftRouterStatus setRouterRanByUs: false]; - [swiftRouterStatus triggerEventWithEn:@"router_stop" details:@"error shutdown"]; + [swiftRouterStatus triggerEventWithEn:@"router_exception" details:[e reason]]; [[SBridge sharedInstance] setCurrentRouterInstance:nil]; sendUserNotification(@"An error occured, can't start the I2P Router", [e reason]); return 0; diff --git a/launchers/macosx/SBridge.mm b/launchers/macosx/SBridge.mm index c8ba2c142..6778631e7 100644 --- a/launchers/macosx/SBridge.mm +++ b/launchers/macosx/SBridge.mm @@ -36,16 +36,15 @@ std::future startupRouter(NSString* javaBin, NSArray* arguments, [[SBridge sharedInstance] setCurrentRouterInstance:instance]; [instance execute]; + sendUserNotification(APP_IDSTR, @"The I2P router is starting up."); + auto pid = [instance getPID]; + NSLog(@"Got pid: %d", pid); if (routerStatus != nil) { [routerStatus setRouterStatus: true]; [routerStatus setRouterRanByUs: true]; [routerStatus triggerEventWithEn:@"router_start" details:@"normal start"]; + [routerStatus triggerEventWithEn:@"router_pid" details:[NSString stringWithFormat:@"%d", pid]]; } - sendUserNotification(APP_IDSTR, @"The I2P router is starting up."); - auto pid = [instance getPID]; - NSLog(@"Got pid: %d", pid); - - if (routerStatus != nil) [routerStatus triggerEventWithEn:@"router_pid" details:[NSString stringWithFormat:@"%d", pid]]; return std::async(std::launch::async, [&pid]{ return pid; @@ -59,8 +58,6 @@ std::future startupRouter(NSString* javaBin, NSArray* arguments, [[SBridge sharedInstance] setCurrentRouterInstance:nil]; if (routerStatus != nil) { - [routerStatus setRouterStatus: false]; - [routerStatus setRouterRanByUs: false]; [routerStatus triggerEventWithEn:@"router_exception" details:errStr]; } diff --git a/launchers/macosx/main.mm b/launchers/macosx/main.mm index 6b6fd9062..2d1f27425 100644 --- a/launchers/macosx/main.mm +++ b/launchers/macosx/main.mm @@ -107,23 +107,16 @@ using namespace subprocess; for_each(cli, [&execStr](std::string str){ execStr += std::string(" ") + str; }); NSLog(@"Trying cmd: %@", [NSString stringWithUTF8String:execStr.c_str()]); - try { - sendUserNotification(APP_IDSTR, @"Please hold on while we extract I2P. You'll get a new message once done!"); - int extractStatus = Popen(execStr.c_str(), environment{{ - {"ZIPPATH", zippath.c_str()}, - {"I2PBASE", basePath.c_str()} - }}).wait(); - NSLog(@"Extraction exit code %@",[NSString stringWithUTF8String:(std::to_string(extractStatus)).c_str()]); - if (extractStatus == 0) - { - NSLog(@"Extraction complete!"); - } - - } catch (subprocess::OSError &err) { - auto errMsg = [NSString stringWithUTF8String:err.what()]; - //success = NO; - NSLog(@"Exception: %@", errMsg); - sendUserNotification(APP_IDSTR, [NSString stringWithFormat:@"Error: %@", errMsg]); + sendUserNotification(APP_IDSTR, @"Please hold on while we extract I2P. You'll get a new message once done!"); + int extractStatus = Popen(execStr.c_str(), environment{{ + {"ZIPPATH", zippath.c_str()}, + {"I2PBASE", basePath.c_str()} + }}).wait(); + NSLog(@"Extraction exit code %@",[NSString stringWithUTF8String:(std::to_string(extractStatus)).c_str()]); + if (extractStatus == 0) { + NSLog(@"Extraction process done"); + } else { + NSLog(@"Something went wrong"); } // All done. Assume success and error are already set. @@ -137,6 +130,7 @@ using namespace subprocess; } catch (OSError &err) { auto errMsg = [NSString stringWithUTF8String:err.what()]; NSLog(@"Exception: %@", errMsg); + sendUserNotification(APP_IDSTR, [NSString stringWithFormat:@"Error: %@", errMsg]); } }); } @@ -224,27 +218,52 @@ using namespace subprocess; // This will trigger the router start after an upgrade. [routerStatus listenForEventWithEventName:@"router_must_upgrade" callbackActionFn:^(NSString* information) { - NSLog(@"Got signal, router must be upgraded"); + NSLog(@"Got signal, router must be deployed from base.zip"); [self extractI2PBaseDir:^(BOOL success, NSError *error) { - sendUserNotification(@"I2P is done extracting", @"I2P is now installed and ready to run!"); - NSLog(@"Done extracting I2P"); - [routerStatus triggerEventWithEn:@"router_can_start" details:@"upgrade complete"]; + if (success && error != nil) { + sendUserNotification(@"I2P is done extracting", @"I2P is now installed and ready to run!"); + NSLog(@"Done extracting I2P"); + [routerStatus triggerEventWithEn:@"extract_completed" details:@"upgrade complete"]; + } else { + NSLog(@"Error while extracting I2P"); + [routerStatus triggerEventWithEn:@"extract_errored" details:[NSString stringWithFormat:@"%@", error]]; + } }]; }]; // Initialize the Swift environment (the UI components) [self.swiftRuntime applicationDidFinishLaunching]; + + NSString *nsI2PBaseStr = [NSString stringWithUTF8String:i2pBaseDir.c_str()]; - struct stat sb; - if ( !(stat(i2pBaseDir.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)) ) + + //struct stat sb; + //if ( !(stat(i2pBaseDir.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)) ) + BOOL shouldBeTrueOnReturnDir = YES; + if (! [NSFileManager.defaultManager fileExistsAtPath: nsI2PBaseStr isDirectory: &shouldBeTrueOnReturnDir]) { // I2P is not extracted. - if (self.enableVerboseLogging) NSLog(@"I2P Directory don't exists!"); - [routerStatus triggerEventWithEn:@"router_must_upgrade" details:@"deploy needed"]; + if (shouldBeTrueOnReturnDir) { + if (self.enableVerboseLogging) NSLog(@"I2P Directory don't exists!"); + [routerStatus triggerEventWithEn:@"router_must_upgrade" details:@"deploy needed"]; + } else { + // TODO: handle if i2p path exists but it's not a dir. + } } else { // I2P was already found extracted - NSLog(@"Time to detect I2P version in install directory"); - [self.swiftRuntime findInstalledI2PVersion]; + NSString *nsI2pJar = [NSString stringWithFormat:@"%@/lib/i2p.jar", nsI2PBaseStr]; + + // But does I2PBASE/lib/i2p.jar exists? + if ([NSFileManager.defaultManager fileExistsAtPath:nsI2pJar]) { + NSLog(@"Time to detect I2P version in install directory"); + [self.swiftRuntime findInstalledI2PVersion]; + } else { + // The directory exists, but not i2p.jar - most likely we're in mid-extraction state. + [routerStatus listenForEventWithEventName:@"extract_completed" callbackActionFn:^(NSString* information) { + NSLog(@"Time to detect I2P version in install directory"); + [self.swiftRuntime findInstalledI2PVersion]; + }]; + } } #endif diff --git a/launchers/macosx/osx_create_dmg.sh b/launchers/macosx/osx_create_dmg.sh index 26c21b1d5..97e8b5331 100755 --- a/launchers/macosx/osx_create_dmg.sh +++ b/launchers/macosx/osx_create_dmg.sh @@ -1,5 +1,7 @@ #!/bin/bash +. .sign-secrets + APP_NAME="I2PLauncher" VERSION="0.9.36" DMG_BACKGROUND_IMG="Background.png" @@ -98,6 +100,8 @@ hdiutil detach "${DEVICE}" echo "Creating compressed image" hdiutil convert "${DMG_TMP}" -format UDZO -imagekey zlib-level=9 -o "${DMG_FINAL}" +codesign --force --sign "${APPLE_CODE_SIGNER_ID}" "${DMG_FINAL}" + # clean up rm -rf "${DMG_TMP}" rm -rf "${STAGING_DIR}"