Files
textmate/Frameworks/OakSystem/src/application.cc
Allan Odgaard 9894969e67 Initial commit
2012-08-09 16:25:56 +02:00

265 lines
7.7 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "application.h"
#include "process.h"
#include <io/io.h>
#include <cf/cf.h>
#include <text/format.h>
#include <oak/compat.h>
#include <oak/debug.h>
OAK_DEBUG_VAR(Application);
namespace oak
{
static std::string _app_name = NULL_STR;
static std::string _app_path = NULL_STR;
static std::string _full_app_path = NULL_STR;
static std::string _pid_path = NULL_STR;
static std::string _support_path = NULL_STR;
static std::string process_name (pid_t pid)
{
struct kinfo_proc procInfo;
size_t length = sizeof(procInfo);
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid };
if(sysctl(mib, 4, &procInfo, &length, NULL, 0) == 0 && length > 0)
return procInfo.kp_proc.p_comm;
return NULL_STR;
}
static void disable_interactive_input ()
{
if(int fd = strtol(getenv("TM_INTERACTIVE_INPUT_ECHO_FD") ?: "0", NULL, 10))
{
close(fd);
unsetenv("TM_INTERACTIVE_INPUT_ECHO_FD");
}
if(int fd = strtol(getenv("TM_ERROR_FD") ?: "0", NULL, 10))
{
close(fd);
unsetenv("TM_ERROR_FD");
}
}
application_t::application_t (int argc, char const* argv[])
{
disable_interactive_input(); // this is only relevant when we launch TM from a TM command that uses interactive input (like make) — each relaunch will allocate two new file descriptors, so eventually well run out of them
// When upgrading from r9110 or earlier.
static char const* LegacyVariables[] = { "RELAUNCH", "ACTIVATE", "DISABLE_UNTITLED_FILE" };
iterate(variable, LegacyVariables)
{
if(getenv(*variable))
{
setenv((std::string("OAK_") + *variable).c_str(), getenv(*variable), 1);
unsetenv(*variable);
}
}
_app_name = getenv("OAK_APP_NAME") ?: path::name(argv[0]);
_full_app_path = path::join(path::cwd(), argv[0]);
_app_path = _full_app_path;
_pid_path = support(((CFBundleGetMainBundle() && CFBundleGetIdentifier(CFBundleGetMainBundle())) ? cf::to_s(CFBundleGetIdentifier(CFBundleGetMainBundle())) : _app_name) + ".pid");
std::string const appBinary = path::join("Contents/MacOS", _app_name);
if(_app_path.size() > appBinary.size() && _app_path.find(appBinary) == _app_path.size() - appBinary.size())
_app_path.erase(_app_path.end() - appBinary.size(), _app_path.end());
std::string content = path::content(_pid_path);
if(content == NULL_STR && strcmp(getenv("OAK_RELAUNCH") ?: "0", "1") == 0)
content = path::content(support(_app_name + ".pid"));
long pid = content != NULL_STR ? strtol(content.c_str(), NULL, 10) : 0;
if(pid != 0 && process_name(pid) == _app_name)
{
int event_queue = kqueue();
struct kevent changeList = { (uintptr_t)pid, EVFILT_PROC, EV_ADD | EV_ENABLE | EV_ONESHOT, NOTE_EXIT, 0, NULL };
int res = kevent(event_queue, &changeList, 1, NULL /* event list */, 0 /* number of events */, NULL);
if(res == -1 && errno == ESRCH)
{
D(DBF_Application, bug("old instance no longer running\n"););
}
else if(res == 0)
{
if(char const* relaunch = getenv("OAK_RELAUNCH"))
{
D(DBF_Application, bug("%s is already running (pid %ld), OAK_RELAUNCH = %s\n", _app_name.c_str(), pid, relaunch););
kill(pid, strcmp(relaunch, "QUICK") == 0 ? SIGTERM : SIGINT);
struct kevent changed;
struct timespec timeout = { 30, 0 };
if(kevent(event_queue, NULL /* change list */, 0 /* number of changes */, &changed /* event list */, 1 /* number of events */, &timeout) == 1)
{
D(DBF_Application, bug("other instance terminated\n"););
ASSERTF((pid_t)changed.ident == pid, "pid %d, changed %d", pid, (pid_t)changed.ident);
CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication);
}
else
{
D(DBF_Application, bug("timeout expired, terminate\n"););
exit(1);
}
}
else
{
D(DBF_Application, bug("%s is already running (pid %ld), OAK_RELAUNCH = NO\n", name().c_str(), pid););
ProcessSerialNumber psn;
if(noErr == GetProcessForPID(pid, &psn))
{
SetFrontProcess(&psn);
exit(0);
}
// procNotFound
}
}
close(event_queue);
}
create_pid_file();
if(getenv("OAK_ACTIVATE") || getenv("OAK_RELAUNCH"))
{
D(DBF_Application, bug("bring new instance to front\n"););
SetFrontProcess(&(ProcessSerialNumber){ 0, kCurrentProcess });
unsetenv("OAK_ACTIVATE");
unsetenv("OAK_RELAUNCH");
}
}
void application_t::create_pid_file ()
{
if(path::set_content(_pid_path, text::format("%d", getpid())))
{
atexit(&remove_pid_file);
}
else
{
D(DBF_Application, bug("open(\"%s\", O_WRONLY): %s\n", _pid_path.c_str(), strerror(errno)););
}
}
void application_t::remove_pid_file ()
{
D(DBF_Application, bug("unlink %s\n", _pid_path.c_str()););
unlink(_pid_path.c_str());
}
std::string application_t::name ()
{
if(_app_name == NULL_STR)
{
if(CFBundleRef mainBundle = CFBundleGetMainBundle())
{
if(CFTypeRef appName = CFBundleGetValueForInfoDictionaryKey(mainBundle, CFSTR("CFBundleName")))
{
if(CFGetTypeID(appName) == CFStringGetTypeID())
_app_name = cf::to_s((CFStringRef)appName);
}
}
}
return _app_name;
}
std::string application_t::path (std::string const& relativePath)
{
if(_app_path == NULL_STR)
{
if(CFBundleRef mainBundle = CFBundleGetMainBundle())
{
if(CFURLRef bundleURL = CFBundleCopyBundleURL(mainBundle))
{
if(CFStringRef bundlePath = CFURLCopyFileSystemPath(bundleURL, kCFURLPOSIXPathStyle))
{
_app_path = cf::to_s(bundlePath);
CFRelease(bundlePath);
}
CFRelease(bundleURL);
}
}
}
return path::join(_app_path, relativePath);
}
void application_t::set_name (std::string const& newName)
{
_app_name = newName;
}
void application_t::set_path (std::string const& newPath)
{
_app_path = newPath;
}
void application_t::set_support (std::string const& newPath)
{
_support_path = newPath;
}
std::string application_t::support (std::string const& relativePath)
{
if(_support_path == NULL_STR)
_support_path = path::join(path::join(path::home(), "Library/Application Support"), name());
return path::join(_support_path, relativePath);
}
std::string application_t::revision ()
{
if(CFBundleRef mainBundle = CFBundleGetMainBundle())
{
if(CFTypeRef bundleVersion = CFBundleGetValueForInfoDictionaryKey(mainBundle, CFSTR("CFBundleVersion")))
{
if(CFGetTypeID(bundleVersion) == CFStringGetTypeID())
return cf::to_s((CFStringRef)bundleVersion);
}
}
return "???";
}
static void* relaunch_thread (void* userdata)
{
oak::set_thread_name("application_t::relaunch");
pid_t pid = (pid_t)userdata;
int status = 0;
if(waitpid(pid, &status, 0) != pid)
fprintf(stderr, "*** relaunch failed: no process for pid %d.\n", pid);
else if(WIFSIGNALED(status))
fprintf(stderr, "*** relaunch failed: process terminated, %s.\n", strsignal(WTERMSIG(status)));
else if(!WIFEXITED(status))
fprintf(stderr, "*** relaunch failed: process terminated abnormally %d.\n", status);
else
fprintf(stderr, "*** relaunch failed: process terminated with status %d.\n", status);
return NULL;
}
void application_t::relaunch ()
{
ASSERT(_full_app_path != NULL_STR);
D(DBF_Application, bug("%s\n", _full_app_path.c_str()););
create_pid_file(); // we create this during startup, but incase there was no support folder it would have failed
std::map<std::string, std::string> envMap = oak::basic_environment();
envMap["OAK_RELAUNCH"] = "1";
oak::c_array env(envMap);
pid_t pid = vfork();
if(pid == 0)
{
char const* argv[] = { _full_app_path.c_str(), NULL };
execve(argv[0], (char* const*)argv, env);
perror("relaunch");
_exit(-1);
}
else
{
pthread_t thread;
if(pthread_create(&thread, NULL, &relaunch_thread, (void*)pid) == 0)
pthread_detach(thread);
else perror("pthread_create()");
}
}
} /* oak */