in watchman/main.cpp [411:557]
static SpawnResult spawn_via_launchd() {
char watchman_path[WATCHMAN_NAME_MAX];
uint32_t size = sizeof(watchman_path);
char plist_path[WATCHMAN_NAME_MAX];
FILE* fp;
struct passwd* pw;
uid_t uid;
close_random_fds();
if (_NSGetExecutablePath(watchman_path, &size) == -1) {
log(FATAL, "_NSGetExecutablePath: path too long; size ", size, "\n");
}
uid = getuid();
pw = getpwuid(uid);
if (!pw) {
log(FATAL,
"getpwuid(",
uid,
") failed: ",
folly::errnoStr(errno),
". I don't know who you are\n");
}
snprintf(
plist_path, sizeof(plist_path), "%s/Library/LaunchAgents", pw->pw_dir);
// Best effort attempt to ensure that the agents dir exists. We'll detect
// and report the failure in the fopen call below.
mkdir(plist_path, 0755);
snprintf(
plist_path,
sizeof(plist_path),
"%s/Library/LaunchAgents/com.github.facebook.watchman.plist",
pw->pw_dir);
if (access(plist_path, R_OK) == 0) {
// Unload any that may already exist, as it is likely wrong
ChildProcess unload_proc(
{"/bin/launchctl", "unload", "-F", plist_path},
ChildProcess::Options());
unload_proc.wait();
// Forcibly remove the plist. In some cases it may have some attributes
// set that prevent launchd from loading it. This can happen where
// the system was re-imaged or restored from a backup
unlink(plist_path);
}
fp = fopen(plist_path, "w");
if (!fp) {
log(FATAL,
"Failed to open ",
plist_path,
" for write: ",
folly::errnoStr(errno),
"\n");
}
compute_file_name(flags.pid_file, computeUserName(), "pid", "pidfile");
const char* path_env =
Configuration().getString("subprocess_path_env", getenv("PATH"));
// If subprocess_path_env is not set and PATH is not in the environment,
// set the path to the empty string.
if (path_env == nullptr) {
path_env = "";
}
auto plist_content = folly::to<std::string>(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" "
"\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
"<plist version=\"1.0\">\n"
"<dict>\n"
" <key>Label</key>\n"
" <string>com.github.facebook.watchman</string>\n"
" <key>Disabled</key>\n"
" <false/>\n"
" <key>ProgramArguments</key>\n"
" <array>\n"
" <string>",
watchman_path,
"</string>\n"
" <string>--foreground</string>\n"
" <string>--logfile=",
logging::log_name,
"</string>\n"
" <string>--log-level=",
logging::log_level,
"</string>\n"
// TODO: switch from `--sockname` to `--unix-listener-path`
// after a grace period to allow for sane results if we
// roll back to an earlier version
" <string>--sockname=",
get_unix_sock_name(),
"</string>\n"
" <string>--statefile=",
flags.watchman_state_file,
"</string>\n"
" <string>--pidfile=",
flags.pid_file,
"</string>\n"
" </array>\n"
" <key>KeepAlive</key>\n"
" <dict>\n"
" <key>Crashed</key>\n"
" <true/>\n"
" </dict>\n"
" <key>RunAtLoad</key>\n"
" <true/>\n"
" <key>EnvironmentVariables</key>\n"
" <dict>\n"
" <key>PATH</key>\n"
" <string>",
path_env,
"</string>\n"
" </dict>\n"
" <key>ProcessType</key>\n"
" <string>Interactive</string>\n"
" <key>Nice</key>\n"
" <integer>-5</integer>\n"
"</dict>\n"
"</plist>\n");
fwrite(plist_content.data(), 1, plist_content.size(), fp);
fclose(fp);
// Don't rely on umask, ensure we have the correct perms
chmod(plist_path, 0644);
ChildProcess load_proc(
{"/bin/launchctl", "load", "-F", plist_path}, ChildProcess::Options());
auto res = load_proc.wait();
if (WIFEXITED(res) && WEXITSTATUS(res) == 0) {
return SpawnResult::Spawned;
}
// Most likely cause is "headless" operation with no GUI context
if (WIFEXITED(res)) {
logf(ERR, "launchctl: exited with status {}\n", WEXITSTATUS(res));
} else if (WIFSIGNALED(res)) {
logf(ERR, "launchctl: signaled with {}\n", WTERMSIG(res));
}
logf(ERR, "Falling back to daemonize\n");
return run_service_as_daemon();
}