Mac OSX Launcher:

* Update readme about event manager
  * RouterTask can now detect a running router by scanning processes&arguments for i2p.jar
  * The logger will log to OSX's default: ~/Library/Logs/I2P/[whatever].log
This commit is contained in:
meeh
2018-09-30 09:40:43 +00:00
parent 36b758f2c0
commit 2233f7f47b
5 changed files with 384 additions and 35 deletions

View File

@@ -18,6 +18,7 @@ This is some Swift code which makes the application use events to signal the dif
| 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 |
| router_already_running | an error message | Triggered if any processes containing i2p.jar in name/arguments already exists upon router launch |
## Misc
@@ -35,3 +36,12 @@ An example build command:
`xcodebuild -target I2PLauncher -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk`
## Objective-C / Swift Links
* https://nshipster.com/at-compiler-directives/
* https://developer.apple.com/documentation/swift/cocoa_design_patterns/handling_cocoa_errors_in_swift
* https://content.pivotal.io/blog/rails-to-ios-what-the-are-these-symbols-in-my-code
* https://mackuba.eu/2008/10/05/learn-objective-c-in-30-minutes/
* https://en.wikipedia.org/wiki/Objective-C
* http://cocoadevcentral.com/d/learn_objectivec/

View File

@@ -2,6 +2,10 @@
#include <dispatch/dispatch.h>
#include <memory.h>
#include <sys/sysctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Cocoa/Cocoa.h>
#import <AppKit/AppKit.h>
@@ -10,6 +14,8 @@
#include <vector>
#include <string>
const std::vector<NSString*> defaultStartupFlags {
@"-Xmx512M",
@"-Xms128m",
@@ -52,7 +58,162 @@ const std::vector<std::string> defaultFlagsForExtractorJob {
@end
@interface IIProcessInfo : NSObject {
@private
int numberOfProcesses;
NSMutableArray *processList;
}
- (id) init;
- (int)numberOfProcesses;
- (void)obtainFreshProcessList;
- (BOOL)findProcessWithStringInNameOrArguments:(NSString *)procNameToSearch;
@end
#ifdef __cplusplus
// Inspired by the "ps" util.
inline std::string getArgvOfPid(int pid) {
int mib[3], argmax, nargs, c = 0;
size_t size;
char *procargs, *sp, *np, *cp;
int show_args = 1;
mib[0] = CTL_KERN;
mib[1] = KERN_ARGMAX;
size = sizeof(argmax);
if (sysctl(mib, 2, &argmax, &size, NULL, 0) == -1) {
return std::string("sorry");
}
/* Allocate space for the arguments. */
procargs = (char *)malloc(argmax);
if (procargs == NULL) {
return std::string("sorry");
}
/*
* Make a sysctl() call to get the raw argument space of the process.
* The layout is documented in start.s, which is part of the Csu
* project. In summary, it looks like:
*
* /---------------\ 0x00000000
* : :
* : :
* |---------------|
* | argc |
* |---------------|
* | arg[0] |
* |---------------|
* : :
* : :
* |---------------|
* | arg[argc - 1] |
* |---------------|
* | 0 |
* |---------------|
* | env[0] |
* |---------------|
* : :
* : :
* |---------------|
* | env[n] |
* |---------------|
* | 0 |
* |---------------| <-- Beginning of data returned by sysctl() is here.
* | argc |
* |---------------|
* | exec_path |
* |:::::::::::::::|
* | |
* | String area. |
* | |
* |---------------| <-- Top of stack.
* : :
* : :
* \---------------/ 0xffffffff
*/
mib[0] = CTL_KERN;
mib[1] = KERN_PROCARGS2;
mib[2] = pid;
size = (size_t)argmax;
if (sysctl(mib, 3, procargs, &size, NULL, 0) == -1) {
free(procargs);
return std::string("sorry");
}
memcpy(&nargs, procargs, sizeof(nargs));
cp = procargs + sizeof(nargs);
/* Skip the saved exec_path. */
for (; cp < &procargs[size]; cp++) {
if (*cp == '\0') {
/* End of exec_path reached. */
break;
}
}
if (cp == &procargs[size]) {
free(procargs);
return std::string("sorry");
}
/* Skip trailing '\0' characters. */
for (; cp < &procargs[size]; cp++) {
if (*cp != '\0') {
/* Beginning of first argument reached. */
break;
}
}
if (cp == &procargs[size]) {
free(procargs);
return std::string("sorry");
}
/* Save where the argv[0] string starts. */
sp = cp;
/*
* Iterate through the '\0'-terminated strings and convert '\0' to ' '
* until a string is found that has a '=' character in it (or there are
* no more strings in procargs). There is no way to deterministically
* know where the command arguments end and the environment strings
* start, which is why the '=' character is searched for as a heuristic.
*/
for (np = NULL; c < nargs && cp < &procargs[size]; cp++) {
if (*cp == '\0') {
c++;
if (np != NULL) {
/* Convert previous '\0'. */
*np = ' ';
} else {
/* *argv0len = cp - sp; */
}
/* Note location of current '\0'. */
np = cp;
if (!show_args) {
/*
* Don't convert '\0' characters to ' '.
* However, we needed to know that the
* command name was terminated, which we
* now know.
*/
break;
}
}
}
/*
* sp points to the beginning of the arguments/environment string, and
* np should point to the '\0' terminator for the string.
*/
if (np == NULL || np == sp) {
/* Empty or unterminated string. */
free(procargs);
return std::string("sorry");
}
return std::string(sp);
}
#endif

View File

@@ -8,6 +8,11 @@
#include "include/subprocess.hpp"
#import "I2PLauncher-Swift.h"
#include "AppDelegate.h"
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <sys/sysctl.h>
#endif
#include "include/PidWatcher.h"
@@ -20,7 +25,6 @@
@implementation I2PRouterTask
- (void)routerStdoutData:(NSNotification *)notification
{
NSLog(@"%@", [[NSString alloc] initWithData:[notification.object availableData] encoding:NSUTF8StringEncoding]);
@@ -31,7 +35,6 @@
{
self.userRequestedRestart = NO;
self.isRouterRunning = NO;
//self.input = [NSFileHandle fileHandleWithStandardInput];
self.routerTask = [NSTask new];
self.processPipe = [NSPipe new];
[self.routerTask setLaunchPath:options.binPath];
@@ -47,14 +50,11 @@
[self.routerTask setTerminationHandler:^(NSTask* task) {
// Cleanup
NSLog(@"termHandler triggered!");
auto swiftRouterStatus = [[RouterProcessStatus alloc] init];
[swiftRouterStatus setRouterStatus: false];
[swiftRouterStatus setRouterRanByUs: false];
[swiftRouterStatus triggerEventWithEn:@"router_stop" details:@"normal shutdown"];
[[[RouterProcessStatus alloc] init] triggerEventWithEn:@"router_stop" details:@"normal shutdown"];
[[SBridge sharedInstance] setCurrentRouterInstance:nil];
sendUserNotification(APP_IDSTR, @"I2P Router has stopped");
}];
return self;
return self;
}
- (void) requestShutdown
@@ -83,9 +83,8 @@
@catch (NSException *e)
{
NSLog(@"Expection occurred %@", [e reason]);
auto swiftRouterStatus = [[RouterProcessStatus alloc] init];
self.isRouterRunning = NO;
[swiftRouterStatus triggerEventWithEn:@"router_exception" details:[e reason]];
[[[RouterProcessStatus alloc] init] triggerEventWithEn:@"router_exception" details:[e reason]];
[[SBridge sharedInstance] setCurrentRouterInstance:nil];
sendUserNotification(@"An error occured, can't start the I2P Router", [e reason]);
return 0;
@@ -98,3 +97,142 @@
}
@end
typedef struct kinfo_proc kinfo_proc;
@implementation IIProcessInfo
- (id) init
{
self = [super init];
if (self != nil)
{
numberOfProcesses = -1; // means "not initialized"
processList = NULL;
}
return self;
}
- (int)numberOfProcesses
{
return numberOfProcesses;
}
- (void)setNumberOfProcesses:(int)num
{
numberOfProcesses = num;
}
- (int)getBSDProcessList:(kinfo_proc **)procList
withNumberOfProcesses:(size_t *)procCount
{
#ifdef __cplusplus
int err;
kinfo_proc * result;
bool done;
static const int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
size_t length;
// a valid pointer procList holder should be passed
assert( procList != NULL );
// But it should not be pre-allocated
assert( *procList == NULL );
// a valid pointer to procCount should be passed
assert( procCount != NULL );
*procCount = 0;
result = NULL;
done = false;
do
{
assert( result == NULL );
// Call sysctl with a NULL buffer to get proper length
length = 0;
err = sysctl((int *)name,(sizeof(name)/sizeof(*name))-1,NULL,&length,NULL,0);
if( err == -1 )
err = errno;
// Now, proper length is optained
if( err == 0 )
{
result = (kinfo_proc *)malloc(length);
if( result == NULL )
err = ENOMEM; // not allocated
}
if( err == 0 )
{
err = sysctl( (int *)name, (sizeof(name)/sizeof(*name))-1, result, &length, NULL, 0);
if( err == -1 )
err = errno;
if( err == 0 )
done = true;
else if( err == ENOMEM )
{
assert( result != NULL );
free( result );
result = NULL;
err = 0;
}
}
}while ( err == 0 && !done );
// Clean up and establish post condition
if( err != 0 && result != NULL )
{
free(result);
result = NULL;
}
*procList = result; // will return the result as procList
if( err == 0 )
*procCount = length / sizeof( kinfo_proc );
assert( (err == 0) == (*procList != NULL ) );
return err;
}
- (void)obtainFreshProcessList
{
int i;
kinfo_proc *allProcs = 0;
size_t numProcs;
NSString *procName;
int err = [self getBSDProcessList:&allProcs withNumberOfProcesses:&numProcs];
if( err )
{
numberOfProcesses = -1;
processList = NULL;
return;
}
// Construct an array for ( process name, pid, arguments concat'ed )
processList = [NSMutableArray arrayWithCapacity:numProcs];
for( i = 0; i < numProcs; i++ )
{
int pid = (int)allProcs[i].kp_proc.p_pid;
procName = [NSString stringWithFormat:@"%s, pid %d, args: %s", allProcs[i].kp_proc.p_comm, pid, getArgvOfPid(pid).c_str()];
[processList addObject:procName];
}
[self setNumberOfProcesses:(int)numProcs];
free( allProcs );
#endif
}
- (BOOL)findProcessWithStringInNameOrArguments:(NSString *)procNameToSearch
{
BOOL seenProcessThatMatch = NO;
for (NSString* processInfoStr in processList) {
if ([processInfoStr containsString:procNameToSearch]) {
seenProcessThatMatch = YES;
break;
}
}
return seenProcessThatMatch;
}
@end

View File

@@ -20,6 +20,9 @@
#import <AppKit/AppKit.h>
#import "I2PLauncher-Swift.h"
#include "LoggerWorker.hpp"
#include "Logger.h"
#include "logger_c.h"
#include "AppDelegate.h"
#include "include/fn.h"
@@ -28,27 +31,39 @@
std::future<int> startupRouter(NSString* javaBin, NSArray<NSString*>* arguments, NSString* i2pBaseDir, RouterProcessStatus* routerStatus) {
@try {
RTaskOptions* options = [RTaskOptions alloc];
options.binPath = javaBin;
options.arguments = arguments;
options.i2pBaseDir = i2pBaseDir;
auto instance = [[I2PRouterTask alloc] initWithOptions: options];
[[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]];
IIProcessInfo* processInfoObj = [[IIProcessInfo alloc] init];
[processInfoObj obtainFreshProcessList];
auto anyRouterLookingProcs = [processInfoObj findProcessWithStringInNameOrArguments:@"i2p.jar"];
if (anyRouterLookingProcs) {
auto errMessage = @"Seems i2p is already running - I've detected another process with i2p.jar in it's arguments.";
NSLog(@"%@", errMessage);
sendUserNotification(APP_IDSTR, errMessage);
[routerStatus triggerEventWithEn:@"router_already_running" details:@"won't start - another router is running"];
return std::async(std::launch::async, []{
return -1;
});
} else {
RTaskOptions* options = [RTaskOptions alloc];
options.binPath = javaBin;
options.arguments = arguments;
options.i2pBaseDir = i2pBaseDir;
auto instance = [[I2PRouterTask alloc] initWithOptions: options];
[[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) {
// TODO: Merge events router_start and router_pid ?
[routerStatus triggerEventWithEn:@"router_start" details:@"normal start"];
[routerStatus triggerEventWithEn:@"router_pid" details:[NSString stringWithFormat:@"%d", pid]];
}
return std::async(std::launch::async, [&pid]{
return pid;
});
}
return std::async(std::launch::async, [&pid]{
return pid;
});
}
@catch (NSException *e)
{
@@ -92,7 +107,7 @@ std::future<int> startupRouter(NSString* javaBin, NSArray<NSString*>* arguments,
const char * basePath = [i2pPath UTF8String];
auto jarList = buildClassPathForObjC(basePath);
const char * classpath = jarList.c_str();
NSLog(@"Classpath from ObjC = %s", classpath);
MLog(0, @"Classpath from ObjC = %s", classpath);
return [[NSString alloc] initWithUTF8String:classpath];
}
@@ -105,14 +120,17 @@ std::future<int> startupRouter(NSString* javaBin, NSArray<NSString*>* arguments,
auto classPathStr = buildClassPathForObjC(basePath);
RouterProcessStatus* routerStatus = [[RouterProcessStatus alloc] init];
NSString *confDir = [NSString stringWithFormat:@"%@/Application\\ Support/i2p", NSHomeDirectory()];
try {
std::vector<NSString*> argList = {
@"-Xmx512M",
@"-Xms128m",
@"-Djava.awt.headless=true",
@"-Dwrapper.logfile=/tmp/router.log",
[NSString stringWithFormat:@"-Dwrapper.logfile=%@/router.log", [NSString stringWithUTF8String:getDefaultLogDir().c_str()]],
@"-Dwrapper.logfile.loglevel=DEBUG",
@"-Dwrapper.java.pidfile=/tmp/routerjvm.pid",
[NSString stringWithFormat:@"-Dwrapper.java.pidfile=%@/router.pid", confDir],
@"-Dwrapper.console.loglevel=DEBUG"
};

View File

@@ -32,12 +32,16 @@
#include "include/subprocess.hpp"
#include "include/strutil.hpp"
using namespace subprocess;
#include "Logger.h"
#include "LoggerWorker.hpp"
using namespace subprocess;
#endif
#define debug(format, ...) CFShow([NSString stringWithFormat:format, ## __VA_ARGS__]);
@interface AppDelegate () <NSUserNotificationCenterDelegate, NSApplicationDelegate>
@end
@@ -68,6 +72,7 @@ using namespace subprocess;
std::string basePath(homeDir);
basePath.append("/Library/I2P");
auto jarResPath = [launcherBundle pathForResource:@"launcher" ofType:@"jar"];
NSLog(@"Trying to load launcher.jar from url = %@", jarResPath);
self.metaInfo.jarFile = jarResPath;
@@ -196,6 +201,7 @@ using namespace subprocess;
NSBundle *launcherBundle = [NSBundle mainBundle];
// Helper object to hold statefull path information
self.metaInfo = [[ExtractMetaInfo alloc] init];
self.metaInfo.i2pBase = [NSString stringWithUTF8String:i2pBaseDir.c_str()];
@@ -288,12 +294,25 @@ using namespace subprocess;
}
@end
#ifdef __cplusplus
namespace {
const std::string logDirectory = getDefaultLogDir();
}
#endif
int main(int argc, const char **argv)
{
NSApplication *app = [NSApplication sharedApplication];
#ifdef __cplusplus
mkdir(logDirectory.c_str(), S_IRUSR | S_IWUSR | S_IXUSR);
SharedLogWorker logger("I2PLauncher", logDirectory);
MeehLog::initializeLogging(&logger);
MLOG(INFO) << "Application is starting up";
#endif
AppDelegate *appDelegate = [[AppDelegate alloc] initWithArgc:argc argv:argv];
app.delegate = appDelegate;
auto mainBundle = [NSBundle mainBundle];
@@ -304,8 +323,11 @@ int main(int argc, const char **argv)
[NSApp terminate:nil];
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
[NSBundle loadNibNamed:@"I2Launcher" owner:NSApp];
#pragma GCC diagnostic pop
[NSApp run];
return 0;
}