forked from I2P_Developers/i2p.i2p
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:
@@ -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/
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user