source: launchers/macosx/main.mm @ 22a0f39

Last change on this file since 22a0f39 was 22a0f39, checked in by meeh <meeh@…>, 23 months ago

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
  • Property mode set to 100644
File size: 10.4 KB
Line 
1#include <functional>
2#include <memory>
3#include <iostream>
4#include <algorithm>
5#include <string>
6#include <list>
7#include <sys/stat.h>
8#include <stdlib.h>
9#include <future>
10#include <vector>
11
12#import <Foundation/Foundation.h>
13#import <Foundation/NSFileManager.h>
14#include <CoreFoundation/CFPreferences.h>
15
16#import <objc/Object.h>
17#import <Cocoa/Cocoa.h>
18#import <AppKit/AppKit.h>
19#import <AppKit/NSApplication.h>
20
21#import "I2PLauncher-Swift.h"
22
23#include "AppDelegate.h"
24#include "RouterTask.h"
25#include "include/fn.h"
26#include "include/portcheck.h"
27#import "SBridge.h"
28
29#ifdef __cplusplus
30#include <string>
31
32#include "include/subprocess.hpp"
33#include "include/strutil.hpp"
34
35using namespace subprocess;
36
37#endif
38
39#define debug(format, ...) CFShow([NSString stringWithFormat:format, ## __VA_ARGS__]);
40
41@interface AppDelegate () <NSUserNotificationCenterDelegate, NSApplicationDelegate>
42@end
43
44
45@implementation ExtractMetaInfo : NSObject
46@end
47
48
49@implementation AppDelegate
50
51- (void) awakeFromNib {
52}
53
54
55- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center
56     shouldPresentNotification:(NSUserNotification *)notification {
57  return YES;
58}
59
60#ifdef __cplusplus
61
62- (void)extractI2PBaseDir:(void(^)(BOOL success, NSError *error))completion
63{
64 
65  NSBundle *launcherBundle = [NSBundle mainBundle];
66  auto homeDir = RealHomeDirectory();
67  NSLog(@"Home directory is %s", homeDir);
68 
69  std::string basePath(homeDir);
70  basePath.append("/Library/I2P");
71  auto jarResPath = [launcherBundle pathForResource:@"launcher" ofType:@"jar"];
72  NSLog(@"Trying to load launcher.jar from url = %@", jarResPath);
73  self.metaInfo.jarFile = jarResPath;
74  self.metaInfo.zipFile = [launcherBundle pathForResource:@"base" ofType:@"zip"];
75 
76  NSParameterAssert(basePath.c_str());
77  NSError *error = NULL;
78  BOOL success = NO;
79  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
80
81
82    try {
83      std::string basearg("-Di2p.dir.base=");
84      basearg += basePath;
85
86      std::string zippath("-Di2p.base.zip=");
87      zippath += [self.metaInfo.zipFile UTF8String];
88
89      std::string jarfile("-cp ");
90      jarfile += [self.metaInfo.jarFile UTF8String];
91
92      // Create directory
93      mkdir(basePath.c_str(), S_IRUSR | S_IWUSR | S_IXUSR);
94
95      auto cli = defaultFlagsForExtractorJob;
96      setenv("I2PBASE", basePath.c_str(), true);
97      setenv("ZIPPATH", zippath.c_str(), true);
98      //setenv("DYLD_LIBRARY_PATH",".:/usr/lib:/lib:/usr/local/lib", true);
99
100      cli.push_back(basearg);
101      cli.push_back(zippath);
102      cli.push_back(jarfile);
103      cli.push_back("net.i2p.launchers.BaseExtractor");
104      auto rs = [[RouterProcessStatus alloc] init];
105     
106      std::string execStr = std::string([rs.getJavaHome UTF8String]);
107      for_each(cli, [&execStr](std::string str){ execStr += std::string(" ") + str; });
108
109      NSLog(@"Trying cmd: %@", [NSString stringWithUTF8String:execStr.c_str()]);
110      sendUserNotification(APP_IDSTR, @"Please hold on while we extract I2P. You'll get a new message once done!");
111      int extractStatus = Popen(execStr.c_str(), environment{{
112        {"ZIPPATH", zippath.c_str()},
113        {"I2PBASE", basePath.c_str()}
114      }}).wait();
115      NSLog(@"Extraction exit code %@",[NSString stringWithUTF8String:(std::to_string(extractStatus)).c_str()]);
116      if (extractStatus == 0) {
117        NSLog(@"Extraction process done");
118      } else {
119        NSLog(@"Something went wrong");
120      }
121
122      // All done. Assume success and error are already set.
123      dispatch_async(dispatch_get_main_queue(), ^{
124        if (completion) {
125          completion(success, error);
126        }
127      });
128     
129     
130    } catch (OSError &err) {
131      auto errMsg = [NSString stringWithUTF8String:err.what()];
132      NSLog(@"Exception: %@", errMsg);
133      sendUserNotification(APP_IDSTR, [NSString stringWithFormat:@"Error: %@", errMsg]);
134    }
135  });
136}
137
138- (void)setApplicationDefaultPreferences {
139  [self.userPreferences registerDefaults:@{
140    @"enableLogging": @YES,
141    @"enableVerboseLogging": @YES,
142    @"autoStartRouter": @YES,
143    @"i2pBaseDirectory": (NSString *)CFStringCreateWithCString(NULL, const_cast<const char *>(getDefaultBaseDir().c_str()), kCFStringEncodingUTF8)
144  }];
145
146  auto dict = [self.userPreferences dictionaryRepresentation];
147  [self.userPreferences setPersistentDomain:dict forName:NSAPPDOMAIN];
148
149  CFPreferencesSetMultiple((CFDictionaryRef)dict, NULL, CFAPPDOMAIN, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
150  CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication);
151
152  if (self.enableVerboseLogging) NSLog(@"Default preferences stored!");
153}
154
155#endif
156
157
158- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
159  // Init application here
160 
161  self.swiftRuntime = [[SwiftMainDelegate alloc] init];
162 
163  // This setup allows the application to send notifications
164  [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self];
165 
166 
167  // Start with user preferences
168  self.userPreferences = [NSUserDefaults standardUserDefaults];
169  [self setApplicationDefaultPreferences];
170  self.enableLogging = [self.userPreferences boolForKey:@"enableLogging"];
171  self.enableVerboseLogging = [self.userPreferences boolForKey:@"enableVerboseLogging"];
172  // In case we are unbundled, make us a proper UI application
173  [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
174  [NSApp activateIgnoringOtherApps:YES];
175
176
177#ifdef __cplusplus
178
179  RouterProcessStatus* routerStatus = [[RouterProcessStatus alloc] init];
180  std::string i2pBaseDir(getDefaultBaseDir());
181  NSLog(@"i2pBaseDir = %s", i2pBaseDir.c_str());
182  bool shouldAutoStartRouter = false;
183 
184  // TODO: Make the port a setting which defaults to 7657
185  if (port_check(7657) != 0)
186  {
187    NSLog(@"Seems i2p is already running - I will not start the router (port 7657 is in use..)");
188    sendUserNotification(@"Found already running router", @"TCP port 7657 seem to be used by another i2p instance.");
189   
190    [routerStatus setRouterStatus: true];
191    [routerStatus setRouterRanByUs: false];
192    shouldAutoStartRouter = false;
193  } else {
194    shouldAutoStartRouter = true;
195  }
196
197  NSBundle *launcherBundle = [NSBundle mainBundle];
198 
199  // Helper object to hold statefull path information
200  self.metaInfo = [[ExtractMetaInfo alloc] init];
201  self.metaInfo.i2pBase = [NSString stringWithUTF8String:i2pBaseDir.c_str()];
202  self.metaInfo.javaBinary = [routerStatus getJavaHome];
203  self.metaInfo.jarFile = [launcherBundle pathForResource:@"launcher" ofType:@"jar"];
204  self.metaInfo.zipFile = [launcherBundle pathForResource:@"base" ofType:@"zip"];
205
206  std::string basearg("-Di2p.dir.base=");
207  basearg += i2pBaseDir;
208
209  std::string jarfile("-cp ");
210  jarfile += [self.metaInfo.zipFile UTF8String];
211 
212  // Might be hard to read if you're not used to Objective-C
213  // But this is a "function call" that contains a "callback function"
214  [routerStatus listenForEventWithEventName:@"router_can_start" callbackActionFn:^(NSString* information) {
215    NSLog(@"Got signal, router can be started");
216    [[SBridge sharedInstance] startupI2PRouter:self.metaInfo.i2pBase javaBinPath:self.metaInfo.javaBinary];
217  }];
218 
219  // This will trigger the router start after an upgrade.
220  [routerStatus listenForEventWithEventName:@"router_must_upgrade" callbackActionFn:^(NSString* information) {
221    NSLog(@"Got signal, router must be deployed from base.zip");
222    [self extractI2PBaseDir:^(BOOL success, NSError *error) {
223      if (success && error != nil) {
224        sendUserNotification(@"I2P is done extracting", @"I2P is now installed and ready to run!");
225        NSLog(@"Done extracting I2P");
226        [routerStatus triggerEventWithEn:@"extract_completed" details:@"upgrade complete"];
227      } else {
228        NSLog(@"Error while extracting I2P");
229        [routerStatus triggerEventWithEn:@"extract_errored" details:[NSString stringWithFormat:@"%@", error]];
230      }
231    }];
232  }];
233 
234  // Initialize the Swift environment (the UI components)
235  [self.swiftRuntime applicationDidFinishLaunching];
236 
237  NSString *nsI2PBaseStr = [NSString stringWithUTF8String:i2pBaseDir.c_str()];
238
239 
240  //struct stat sb;
241  //if ( !(stat(i2pBaseDir.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)) )
242  BOOL shouldBeTrueOnReturnDir = YES;
243  if (! [NSFileManager.defaultManager fileExistsAtPath: nsI2PBaseStr isDirectory: &shouldBeTrueOnReturnDir])
244  {
245    // I2P is not extracted.
246    if (shouldBeTrueOnReturnDir) {
247      if (self.enableVerboseLogging) NSLog(@"I2P Directory don't exists!");
248      [routerStatus triggerEventWithEn:@"router_must_upgrade" details:@"deploy needed"];
249    } else {
250      // TODO: handle if i2p path exists but it's not a dir.
251    }
252  } else {
253    // I2P was already found extracted
254    NSString *nsI2pJar = [NSString stringWithFormat:@"%@/lib/i2p.jar", nsI2PBaseStr];
255   
256    // But does I2PBASE/lib/i2p.jar exists?
257    if ([NSFileManager.defaultManager fileExistsAtPath:nsI2pJar]) {
258      NSLog(@"Time to detect I2P version in install directory");
259      [self.swiftRuntime findInstalledI2PVersion];
260    } else {
261      // The directory exists, but not i2p.jar - most likely we're in mid-extraction state.
262      [routerStatus listenForEventWithEventName:@"extract_completed" callbackActionFn:^(NSString* information) {
263        NSLog(@"Time to detect I2P version in install directory");
264        [self.swiftRuntime findInstalledI2PVersion];
265      }];
266    }
267  }
268 
269#endif
270}
271
272/**
273 *
274 * Exit sequence
275 *
276 **/
277- (void)applicationWillTerminate:(NSNotification *)aNotification {
278  // Tear down here
279  NSString *string = @"applicationWillTerminate executed";
280  NSLog(@"%@", string);
281  [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:nil];
282}
283
284
285/* wrapper for main */
286- (AppDelegate *)initWithArgc:(int)argc argv:(const char **)argv {
287  return self;
288}
289@end
290
291
292
293int main(int argc, const char **argv)
294{
295  NSApplication *app = [NSApplication sharedApplication];
296
297  AppDelegate *appDelegate = [[AppDelegate alloc] initWithArgc:argc argv:argv];
298  app.delegate = appDelegate;
299  auto mainBundle = [NSBundle mainBundle];
300  NSString* stringNameBundle = [mainBundle objectForInfoDictionaryKey:(NSString *)kCFBundleNameKey];
301  if ([[NSRunningApplication runningApplicationsWithBundleIdentifier:[mainBundle bundleIdentifier]] count] > 1) {
302    [[NSAlert alertWithMessageText:[NSString stringWithFormat:@"Another copy of %@ is already running.",stringNameBundle]
303                     defaultButton:nil alternateButton:nil otherButton:nil informativeTextWithFormat:@"This copy will now quit."] runModal];
304   
305    [NSApp terminate:nil];
306  }
307  [NSBundle loadNibNamed:@"I2Launcher" owner:NSApp];
308
309  [NSApp run];
310  return 0;
311}
312
313
314
Note: See TracBrowser for help on using the repository browser.