/*
* Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017
* Jonathan Schleifer <js@heap.zone>
*
* All rights reserved.
*
* This file is part of ObjFW. It may be distributed under the terms of the
* Q Public License 1.0, which can be found in the file LICENSE.QPL included in
* the packaging of this file.
*
* Alternatively, it may be distributed under the terms of the GNU General
* Public License, either version 2 or 3, which can be found in the file
* LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
* file.
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include "unistd_wrapper.h"
#import "OFApplication.h"
#import "OFString.h"
#import "OFArray.h"
#import "OFDictionary.h"
#import "OFLocalization.h"
#import "OFRunLoop.h"
#import "OFRunLoop+Private.h"
#import "OFThread.h"
#import "OFThread+Private.h"
#import "OFSandbox.h"
#ifdef OF_MORPHOS
# import "OFFile.h"
# import "OFFileManager.h"
#endif
#import "OFOutOfMemoryException.h"
#import "OFOutOfRangeException.h"
#import "OFSandboxActivationFailedException.h"
#if defined(OF_MACOS)
# include <crt_externs.h>
#elif defined(OF_WINDOWS)
# include <windows.h>
extern int _CRT_glob;
extern void __wgetmainargs(int *, wchar_t ***, wchar_t ***, int, int *);
#elif defined(OF_MORPHOS)
# define BOOL EXEC_BOOL
# include <proto/exec.h>
# include <proto/dos.h>
# undef BOOL
#elif !defined(OF_IOS)
extern char **environ;
#endif
#ifdef OF_PSP
# include <pspkerneltypes.h>
# include <psploadexec.h>
#endif
#ifdef OF_NINTENDO_DS
# define asm __asm__
# include <nds.h>
# undef asm
#endif
@interface OFApplication ()
- (instancetype)of_init OF_METHOD_FAMILY(init);
- (void)of_setArgumentCount: (int *)argc
andArgumentValues: (char **[])argv;
#ifdef OF_WINDOWS
- (void)of_setArgumentCount: (int)argc
andWideArgumentValues: (wchar_t *[])argv;
#endif
- (void)of_run;
@end
static OFApplication *app = nil;
static void
atexitHandler(void)
{
id <OFApplicationDelegate> delegate = [app delegate];
if ([delegate respondsToSelector: @selector(applicationWillTerminate)])
[delegate applicationWillTerminate];
[delegate release];
}
#define SIGNAL_HANDLER(signal) \
static void \
handle##signal(int sig) \
{ \
app->_##signal##Handler(app->_delegate, \
@selector(applicationDidReceive##signal)); \
}
SIGNAL_HANDLER(SIGINT)
#ifdef SIGHUP
SIGNAL_HANDLER(SIGHUP)
#endif
#ifdef SIGUSR1
SIGNAL_HANDLER(SIGUSR1)
#endif
#ifdef SIGUSR2
SIGNAL_HANDLER(SIGUSR2)
#endif
#undef SIGNAL_HANDLER
int
of_application_main(int *argc, char **argv[], Class cls)
{
id <OFApplicationDelegate> delegate;
#ifdef OF_WINDOWS
wchar_t **wargv, **wenvp;
int wargc, si = 0;
#endif
[[OFLocalization alloc] init];
if (![cls conformsToProtocol: @protocol(OFApplicationDelegate)]) {
fprintf(stderr, "FATAL ERROR:\n Class %s does not conform to "
"protocol OFApplicationDelegate,\n but was specified via "
"OF_APPLICATION_DELEGATE()!\n", class_getName(cls));
exit(1);
}
app = [[OFApplication alloc] of_init];
[app of_setArgumentCount: argc
andArgumentValues: argv];
#ifdef OF_WINDOWS
__wgetmainargs(&wargc, &wargv, &wenvp, _CRT_glob, &si);
[app of_setArgumentCount: wargc
andWideArgumentValues: wargv];
#endif
delegate = [[cls alloc] init];
[app setDelegate: delegate];
[app of_run];
[delegate release];
return 0;
}
@implementation OFApplication
@synthesize programName = _programName, arguments = _arguments;
@synthesize environment = _environment;
+ (OFApplication *)sharedApplication
{
return app;
}
+ (OFString *)programName
{
return [app programName];
}
+ (OFArray *)arguments
{
return [app arguments];
}
+ (OFDictionary *)environment
{
return [app environment];
}
+ (void)terminate
{
[self terminateWithStatus: EXIT_SUCCESS];
OF_UNREACHABLE
}
+ (void)terminateWithStatus: (int)status
{
#ifndef OF_PSP
exit(status);
#else
sceKernelExitGame();
OF_UNREACHABLE
#endif
}
#ifdef OF_HAVE_SANDBOX
+ (void)activateSandbox: (OFSandbox *)sandbox
{
[app activateSandbox: sandbox];
}
#endif
- (instancetype)init
{
OF_INVALID_INIT_METHOD
}
- (instancetype)of_init
{
self = [super init];
@try {
_environment = [[OFMutableDictionary alloc] init];
atexit(atexitHandler);
#if defined(OF_WINDOWS)
char16_t *env, *env0;
env = env0 = GetEnvironmentStringsW();
while (*env != 0) {
void *pool = objc_autoreleasePoolPush();
OFString *tmp, *key, *value;
size_t length, pos;
length = of_string_utf16_length(env);
tmp = [OFString stringWithUTF16String: env
length: length];
env += length + 1;
/*
* cmd.exe seems to add some special variables which
* start with a "=", even though variable names are not
* allowed to contain a "=".
*/
if ([tmp hasPrefix: @"="]) {
objc_autoreleasePoolPop(pool);
continue;
}
pos = [tmp rangeOfString: @"="].location;
if (pos == OF_NOT_FOUND) {
fprintf(stderr, "Warning: Invalid environment "
"variable: %s\n", [tmp UTF8String]);
continue;
}
key = [tmp substringWithRange: of_range(0, pos)];
value = [tmp substringWithRange:
of_range(pos + 1, [tmp length] - pos - 1)];
[_environment setObject: value
forKey: key];
objc_autoreleasePoolPop(pool);
}
FreeEnvironmentStringsW(env0);
#elif defined(OF_MORPHOS)
void *pool = objc_autoreleasePoolPush();
OFFileManager *fileManager = [OFFileManager defaultManager];
OFArray *envContents =
[fileManager contentsOfDirectoryAtPath: @"ENV:"];
const of_string_encoding_t encoding = [OFLocalization encoding];
struct Process *proc;
struct LocalVar *firstLocalVar;
for (OFString *name in envContents) {
void *pool2 = objc_autoreleasePoolPush();
OFString *path, *value;
OFFile *file;
if ([name containsString: @"."])
continue;
path = [@"ENV:" stringByAppendingString: name];
if ([fileManager directoryExistsAtPath: path])
continue;
file = [OFFile fileWithPath: path
mode: @"r"];
value = [file readLineWithEncoding: encoding];
if (value != nil)
[_environment setObject: value
forKey: name];
objc_autoreleasePoolPop(pool2);
}
/* Local variables override global variables */
proc = (struct Process *)FindTask(NULL);
firstLocalVar = (struct LocalVar *)proc->pr_LocalVars.mlh_Head;
for (struct LocalVar *iter = firstLocalVar;
iter->lv_Node.ln_Succ != NULL;
iter = (struct LocalVar *)iter->lv_Node.ln_Succ) {
size_t length;
OFString *key, *value;
if (iter->lv_Node.ln_Type != LV_VAR ||
iter->lv_Flags & GVF_BINARY_VAR)
continue;
for (length = 0; length < iter->lv_Len; length++)
if (iter->lv_Value[length] == 0)
break;
key = [OFString stringWithCString: iter->lv_Node.ln_Name
encoding: encoding];
value = [OFString
stringWithCString: (const char *)iter->lv_Value
encoding: encoding
length: length];
[_environment setObject: value
forKey: key];
}
objc_autoreleasePoolPop(pool);
#elif !defined(OF_IOS)
# ifndef OF_MACOS
char **env = environ;
# else
char **env = *_NSGetEnviron();
# endif
if (env != NULL) {
const of_string_encoding_t encoding =
[OFLocalization encoding];
for (; *env != NULL; env++) {
void *pool = objc_autoreleasePoolPush();
OFString *key, *value;
char *sep;
if ((sep = strchr(*env, '=')) == NULL) {
fprintf(stderr, "Warning: Invalid "
"environment variable: %s\n", *env);
continue;
}
key = [OFString
stringWithCString: *env
encoding: encoding
length: sep - *env];
value = [OFString
stringWithCString: sep + 1
encoding: encoding];
[_environment setObject: value
forKey: key];
objc_autoreleasePoolPop(pool);
}
}
#else
/*
* iOS does not provide environ and Apple does not allow using
* _NSGetEnviron on iOS. Therefore, we just get a few common
* variables from the environment which applications might
* expect.
*/
void *pool = objc_autoreleasePoolPush();
char *env;
if ((env = getenv("HOME")) != NULL) {
OFString *home = [[[OFString alloc]
initWithUTF8StringNoCopy: env
freeWhenDone: false] autorelease];
[_environment setObject: home
forKey: @"HOME"];
}
if ((env = getenv("PATH")) != NULL) {
OFString *path = [[[OFString alloc]
initWithUTF8StringNoCopy: env
freeWhenDone: false] autorelease];
[_environment setObject: path
forKey: @"PATH"];
}
if ((env = getenv("SHELL")) != NULL) {
OFString *shell = [[[OFString alloc]
initWithUTF8StringNoCopy: env
freeWhenDone: false] autorelease];
[_environment setObject: shell
forKey: @"SHELL"];
}
if ((env = getenv("TMPDIR")) != NULL) {
OFString *tmpdir = [[[OFString alloc]
initWithUTF8StringNoCopy: env
freeWhenDone: false] autorelease];
[_environment setObject: tmpdir
forKey: @"TMPDIR"];
}
if ((env = getenv("USER")) != NULL) {
OFString *user = [[[OFString alloc]
initWithUTF8StringNoCopy: env
freeWhenDone: false] autorelease];
[_environment setObject: user
forKey: @"USER"];
}
objc_autoreleasePoolPop(pool);
#endif
[_environment makeImmutable];
} @catch (id e) {
[self release];
@throw e;
}
return self;
}
- (void)dealloc
{
[_arguments release];
[_environment release];
[super dealloc];
}
- (void)of_setArgumentCount: (int *)argc
andArgumentValues: (char ***)argv
{
#ifndef OF_WINDOWS
void *pool = objc_autoreleasePoolPush();
OFMutableArray *arguments;
of_string_encoding_t encoding;
_argc = argc;
_argv = argv;
encoding = [OFLocalization encoding];
# ifndef OF_NINTENDO_DS
if (*argc > 0) {
# else
if (__system_argv->argvMagic == ARGV_MAGIC &&
__system_argv->argc > 0) {
# endif
_programName = [[OFString alloc] initWithCString: (*argv)[0]
encoding: encoding];
arguments = [[OFMutableArray alloc] init];
_arguments = arguments;
for (int i = 1; i < *argc; i++)
[arguments addObject:
[OFString stringWithCString: (*argv)[i]
encoding: encoding]];
[arguments makeImmutable];
}
objc_autoreleasePoolPop(pool);
#else
_argc = argc;
_argv = argv;
#endif
}
#ifdef OF_WINDOWS
- (void)of_setArgumentCount: (int)argc
andWideArgumentValues: (wchar_t **)argv
{
void *pool = objc_autoreleasePoolPush();
OFMutableArray *arguments;
if (argc > 0) {
_programName = [[OFString alloc] initWithUTF16String: argv[0]];
arguments = [[OFMutableArray alloc] init];
for (int i = 1; i < argc; i++)
[arguments addObject:
[OFString stringWithUTF16String: argv[i]]];
[arguments makeImmutable];
_arguments = arguments;
}
objc_autoreleasePoolPop(pool);
}
#endif
- (void)getArgumentCount: (int **)argc
andArgumentValues: (char ****)argv
{
*argc = _argc;
*argv = _argv;
}
- (id <OFApplicationDelegate>)delegate
{
return _delegate;
}
- (void)setDelegate: (id <OFApplicationDelegate>)delegate
{
#define REGISTER_SIGNAL(sig) \
if ([delegate respondsToSelector: \
@selector(applicationDidReceive##sig)]) { \
_##sig##Handler = (void (*)(id, SEL))[(id)delegate \
methodForSelector: \
@selector(applicationDidReceive##sig)]; \
signal(sig, handle##sig); \
} else { \
_##sig##Handler = NULL; \
signal(sig, (void (*)(int))SIG_DFL); \
}
_delegate = delegate;
REGISTER_SIGNAL(SIGINT)
#ifdef SIGHUP
REGISTER_SIGNAL(SIGHUP)
#endif
#ifdef SIGUSR1
REGISTER_SIGNAL(SIGUSR1)
#endif
#ifdef SIGUSR2
REGISTER_SIGNAL(SIGUSR2)
#endif
#undef REGISTER_SIGNAL
}
- (void)of_run
{
void *pool = objc_autoreleasePoolPush();
OFRunLoop *runLoop;
#ifdef OF_HAVE_THREADS
[OFThread of_createMainThread];
runLoop = [OFRunLoop currentRunLoop];
#else
runLoop = [[[OFRunLoop alloc] init] autorelease];
#endif
[OFRunLoop of_setMainRunLoop: runLoop];
objc_autoreleasePoolPop(pool);
/*
* Note: runLoop is still valid after the release of the pool, as
* of_setMainRunLoop: retained it. However, we only have a weak
* reference to it now, whereas we had a strong reference before.
*/
pool = objc_autoreleasePoolPush();
[_delegate applicationDidFinishLaunching];
objc_autoreleasePoolPop(pool);
[runLoop run];
}
- (void)terminate
{
[[self class] terminate];
OF_UNREACHABLE
}
- (void)terminateWithStatus: (int)status
{
[[self class] terminateWithStatus: status];
OF_UNREACHABLE
}
#ifdef OF_HAVE_SANDBOX
- (void)activateSandbox: (OFSandbox *)sandbox
{
# ifdef OF_HAVE_PLEDGE
void *pool = objc_autoreleasePoolPush();
const char *promises = [[sandbox pledgeString]
cStringWithEncoding: [OFLocalization encoding]];
if (pledge(promises, NULL) != 0)
@throw [OFSandboxActivationFailedException
exceptionWithSandbox: sandbox
errNo: errno];
objc_autoreleasePoolPop(pool);
# endif
}
#endif
@end