#include "application.h" #include "process.h" #include #include #include #include #include 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 we’ll 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 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 */