in lib/Basic/Subprocess.cpp [642:1108]
void llbuild::basic::spawnProcess(
ProcessDelegate& delegate,
ProcessContext* ctx,
ProcessGroup& pgrp,
ProcessHandle handle,
ArrayRef<StringRef> commandLine,
POSIXEnvironment environment,
ProcessAttributes attr,
ProcessReleaseFn&& releaseFn,
ProcessCompletionFn&& completionFn
) {
llbuild_pid_t pid = (llbuild_pid_t)-1;
#if !defined(_WIN32) && !defined(HAVE_POSIX_SPAWN)
auto result = ProcessResult::makeFailed();
delegate.processStarted(ctx, handle, pid);
delegate.processHadError(ctx, handle, Twine("process spawning is unavailable"));
delegate.processFinished(ctx, handle, result);
completionFn(result);
return;
#else
// Don't use lane release feature for console workloads.
if (attr.connectToConsole) {
attr.controlEnabled = false;
}
#if defined(_WIN32)
// Control channel support is broken (thread-unsafe) on Windows.
attr.controlEnabled = false;
#endif
if (commandLine.size() == 0) {
auto result = ProcessResult::makeFailed();
delegate.processStarted(ctx, handle, pid);
delegate.processHadError(ctx, handle, Twine("no arguments for command"));
delegate.processFinished(ctx, handle, result);
completionFn(result);
return;
}
// Form the complete C string command line.
std::vector<std::string> argsStorage(commandLine.begin(), commandLine.end());
#if defined(_WIN32)
std::string args = llbuild::basic::formatWindowsCommandString(argsStorage);
// Convert the command line string to utf16
llvm::SmallVector<llvm::UTF16, 20> u16Executable;
llvm::SmallVector<llvm::UTF16, 20> u16CmdLine;
llvm::convertUTF8ToUTF16String(argsStorage[0], u16Executable);
llvm::convertUTF8ToUTF16String(args, u16CmdLine);
#else
std::vector<const char*> args(argsStorage.size() + 1);
for (size_t i = 0; i != argsStorage.size(); ++i) {
args[i] = argsStorage[i].c_str();
}
args[argsStorage.size()] = nullptr;
#endif
#if defined(_WIN32)
DWORD creationFlags = NORMAL_PRIORITY_CLASS |
CREATE_UNICODE_ENVIRONMENT;
PROCESS_INFORMATION processInfo = {0};
#else
// Initialize the spawn attributes.
posix_spawnattr_t attributes;
posix_spawnattr_init(&attributes);
// Unmask all signals.
sigset_t noSignals;
sigemptyset(&noSignals);
posix_spawnattr_setsigmask(&attributes, &noSignals);
// Reset all signals to default behavior.
//
// On Linux, this can only be used to reset signals that are legal to
// modify, so we have to take care about the set we use.
#if defined(__linux__)
sigset_t mostSignals;
sigemptyset(&mostSignals);
for (int i = 1; i < SIGSYS; ++i) {
if (i == SIGKILL || i == SIGSTOP) continue;
sigaddset(&mostSignals, i);
}
posix_spawnattr_setsigdefault(&attributes, &mostSignals);
#else
sigset_t mostSignals;
sigfillset(&mostSignals);
sigdelset(&mostSignals, SIGKILL);
sigdelset(&mostSignals, SIGSTOP);
posix_spawnattr_setsigdefault(&attributes, &mostSignals);
#endif // else !defined(_WIN32)
// Establish a separate process group.
posix_spawnattr_setpgroup(&attributes, 0);
// Set the attribute flags.
unsigned flags = POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF;
if (!attr.connectToConsole) {
flags |= POSIX_SPAWN_SETPGROUP;
}
// Close all other files by default.
//
// FIXME: Note that this is an Apple-specific extension, and we will have to
// do something else on other platforms (and unfortunately, there isn't
// really an easy answer other than using a stub executable).
#ifdef __APPLE__
flags |= POSIX_SPAWN_CLOEXEC_DEFAULT;
#endif
// On Darwin, set the QoS of launched processes to one of the current thread.
#ifdef __APPLE__
posix_spawnattr_set_qos_class_np(&attributes, qos_class_self());
#endif
posix_spawnattr_setflags(&attributes, flags);
// Setup the file actions.
posix_spawn_file_actions_t fileActions;
posix_spawn_file_actions_init(&fileActions);
bool usePosixSpawnChdirFallback = true;
const auto workingDir = attr.workingDir.str();
if (!workingDir.empty() &&
posix_spawn_file_actions_addchdir(&fileActions, workingDir.c_str()) != ENOSYS) {
usePosixSpawnChdirFallback = false;
}
#endif
#if defined(_WIN32)
/// Process startup information for Windows.
STARTUPINFOW startupInfo = {0};
PlatformSpecificPipesConfig& pipesConfig = startupInfo;
#else
PlatformSpecificPipesConfig& pipesConfig = fileActions;
#endif
// Automatically managed (released) descriptors for output and control pipes.
// The child ends are forwarded to the child (and quickly released in the
// parent). The parent ends are retained and read/written by the parent.
ManagedDescriptor outputPipeParentEnd{__FILE__, __LINE__};
ManagedDescriptor controlPipeParentEnd{__FILE__, __LINE__};
#if defined(_WIN32)
llvm::SmallVector<llvm::UTF16, 20> u16Cwd;
std::string workingDir = attr.workingDir.str();
if (!workingDir.empty()) {
llvm::convertUTF8ToUTF16String(workingDir, u16Cwd);
}
#endif
// Export a task ID to subprocesses.
auto taskID = Twine::utohexstr(handle.id);
environment.setIfMissing("LLBUILD_TASK_ID", taskID.str());
// Resolve the executable path, if necessary.
//
// FIXME: This should be cached.
if (!llvm::sys::path::is_absolute(argsStorage[0])) {
auto res = llvm::sys::findProgramByName(argsStorage[0]);
if (!res.getError()) {
argsStorage[0] = *res;
#if defined(_WIN32)
u16Executable.clear();
llvm::convertUTF8ToUTF16String(argsStorage[0], u16Executable);
#else
args[0] = argsStorage[0].c_str();
#endif
}
}
// Spawn the command.
bool wasCancelled;
do {
// We need to hold the spawn processes lock when we spawn, to ensure that
// we don't create a process in between when we are cancelled.
std::lock_guard<std::mutex> guard(pgrp.mutex);
wasCancelled = pgrp.isClosed();
// If we have been cancelled since we started, skip startup.
if (wasCancelled) { break; }
// The partf of the control pipes that are inherited by the child.
ManagedDescriptor outputPipeChildEnd{__FILE__, __LINE__};
ManagedDescriptor controlPipeChildEnd{__FILE__, __LINE__};
// Open the communication channel under the mutex to avoid
// leaking the wrong channel into other children started concurrently.
auto errorPair = createCommunicationPipes(attr, pipesConfig, outputPipeParentEnd, outputPipeChildEnd, controlPipeParentEnd, controlPipeChildEnd);
if (errorPair.first != CommunicationPipesCreationError::ERROR_NONE) {
std::string whatPipe = errorPair.first == CommunicationPipesCreationError::OUTPUT_PIPE_FAILED ? "output pipe" : "control pipe";
#if !defined(_WIN32)
posix_spawn_file_actions_destroy(&fileActions);
posix_spawnattr_destroy(&attributes);
#endif
delegate.processStarted(ctx, handle, pid);
delegate.processHadError(ctx, handle,
Twine("unable to open " + whatPipe + " (") + strerror(errorPair.second) + ")");
delegate.processFinished(ctx, handle, ProcessResult::makeFailed());
completionFn(ProcessResult(ProcessStatus::Failed));
return;
}
if (controlPipeChildEnd.isValid()) {
long long controlFd = (long long)controlPipeChildEnd.unsafeDescriptor();
environment.setIfMissing("LLBUILD_CONTROL_FD", Twine(controlFd).str());
}
int result = 0;
bool workingDirectoryUnsupported = false;
#if !defined(_WIN32)
if (usePosixSpawnChdirFallback) {
#if defined(__APPLE__)
thread_local std::string threadWorkingDir;
if (workingDir.empty()) {
if (!threadWorkingDir.empty()) {
pthread_fchdir_np(-1);
threadWorkingDir.clear();
}
} else {
if (threadWorkingDir != workingDir) {
if (pthread_chdir_np(workingDir.c_str()) == -1) {
result = errno;
} else {
threadWorkingDir = workingDir;
}
}
}
#else
if (!workingDir.empty()) {
workingDirectoryUnsupported = true;
result = -1;
}
#endif // if defined(__APPLE__)
}
#endif // else !defined(_WIN32)
if (result == 0) {
#if defined(_WIN32)
auto unicodeEnv = environment.getWindowsEnvp();
result = !CreateProcessW(
/*lpApplicationName=*/(LPWSTR)u16Executable.data(),
(LPWSTR)u16CmdLine.data(),
/*lpProcessAttributes=*/NULL,
/*lpThreadAttributes=*/NULL,
/*bInheritHandles=*/TRUE, creationFlags,
/*lpEnvironment=*/unicodeEnv.get(),
/*lpCurrentDirectory=*/u16Cwd.empty() ? NULL
: (LPWSTR)u16Cwd.data(),
&startupInfo, &processInfo);
#else
result =
posix_spawn(&pid, args[0], /*file_actions=*/&fileActions,
/*attrp=*/&attributes, const_cast<char**>(args.data()),
const_cast<char* const*>(environment.getEnvp()));
#endif
}
delegate.processStarted(ctx, handle, pid);
if (result != 0) {
auto processResult = ProcessResult::makeFailed();
#if defined(_WIN32)
result = GetLastError();
#endif
delegate.processHadError(
ctx, handle,
workingDirectoryUnsupported
? Twine("working-directory unsupported on this platform")
: Twine("unable to spawn process '") + argsStorage[0] + "' (" + sys::strerror(result) +
")");
delegate.processFinished(ctx, handle, processResult);
pid = (llbuild_pid_t)-1;
} else {
#if defined(_WIN32)
pid = processInfo.hProcess;
#endif
ProcessInfo info{ attr.canSafelyInterrupt };
pgrp.add(std::move(guard), pid, info);
}
// Close the child ends of the forwarded output and control pipes.
controlPipeChildEnd.close();
outputPipeChildEnd.close();
} while(false);
#if !defined(_WIN32)
posix_spawn_file_actions_destroy(&fileActions);
posix_spawnattr_destroy(&attributes);
#endif
// If we failed to launch a process, clean up and abort.
if (pid == (llbuild_pid_t)-1) {
// Manually close to avoid triggering debug-time leak check.
outputPipeParentEnd.close();
controlPipeParentEnd.close();
auto result = wasCancelled ? ProcessResult::makeCancelled() : ProcessResult::makeFailed();
completionFn(result);
return;
}
#if !defined(_WIN32)
// Set up our poll() structures. We use assert() to ensure
// the file descriptors are alive.
pollfd readfds[] = {
{ outputPipeParentEnd.unsafeDescriptor(), 0, 0 },
{ controlPipeParentEnd.unsafeDescriptor(), 0, 0 }
};
bool activeEvents = false;
#endif
const int nfds = 2;
ControlProtocolState control(taskID.str());
std::function<bool (StringRef)> readCbs[] = {
// output capture callback
[&delegate, ctx, handle](StringRef buf) -> bool {
// Notify the client of the output.
delegate.processHadOutput(ctx, handle, buf);
return true;
},
// control callback handle
[&delegate, &control, ctx, handle](StringRef buf) mutable -> bool {
std::string errstr;
int ret = control.read(buf, &errstr);
if (ret < 0) {
delegate.processHadError(ctx, handle,
Twine("control protocol error" + errstr));
}
return (ret == 0);
}
};
#if defined(_WIN32)
struct threadData {
std::function<void(void*)> reader;
const ManagedDescriptor& handle;
std::function<bool(StringRef)> cb;
};
HANDLE readers[2] = {NULL, NULL};
auto reader = [&delegate, handle, ctx](void* lpArgs) {
threadData* args = (threadData*)lpArgs;
for (;;) {
char buf[4096];
DWORD numBytes;
bool result = ReadFile(args->handle.unsafeDescriptor(), buf, sizeof(buf), &numBytes, NULL);
if (!result || numBytes == 0) {
if (GetLastError() == ERROR_BROKEN_PIPE) {
// Pipe done, exit
return;
} else {
delegate.processHadError(ctx, handle,
Twine("unable to read process output (") +
sys::strerror(GetLastError()) + ")");
}
}
if (numBytes <= 0 || !args->cb(StringRef(buf, numBytes))) {
continue;
}
}
};
struct threadData outputThreadParams = {reader, outputPipeParentEnd, readCbs[0]};
struct threadData controlThreadParams = {reader, controlPipeParentEnd, readCbs[1]};
HANDLE threads[2] = {NULL, NULL};
#endif // defined(_WIN32)
int threadCount = 0;
// Read the command output, if capturing instead of pass-throughing.
if (!attr.connectToConsole) {
#if defined(_WIN32)
threads[threadCount++] = (HANDLE)_beginthread(
[](LPVOID lpParams) { ((threadData*)lpParams)->reader(lpParams); }, 0,
&outputThreadParams);
#else
readfds[0].events = POLLIN;
activeEvents = true;
(void)threadCount;
#endif
}
// Process the control channel input.
if (attr.controlEnabled) {
#if defined(_WIN32)
threads[threadCount++] = (HANDLE)_beginthread(
[](LPVOID lpParams) { ((threadData*)lpParams)->reader(lpParams); }, 0,
&controlThreadParams);
#else
readfds[1].events = POLLIN;
activeEvents = true;
#endif
}
#if defined(_WIN32)
DWORD waitResult = WaitForMultipleObjects(threadCount, threads,
/*bWaitAll=*/false,
/*dwMilliseconds=*/INFINITE);
if (WAIT_FAILED == waitResult || WAIT_TIMEOUT == waitResult) {
int err = GetLastError();
delegate.processHadError(
ctx, handle, Twine("failed to poll (") + sys::strerror(err) + ")");
}
#else // !defined(_WIN32)
while (activeEvents) {
char buf[4096];
activeEvents = false;
// Ensure we haven't autoclosed the file descriptors,
// or move()d somewhere they could be autoclosed in.
assert(readfds[0].fd == outputPipeParentEnd.unsafeDescriptor());
assert(readfds[1].fd == controlPipeParentEnd.unsafeDescriptor());
if (poll(readfds, nfds, -1) == -1) {
int err = errno;
delegate.processHadError(ctx, handle,
Twine("failed to poll (") + strerror(err) + ")");
break;
}
for (int i = 0; i < nfds; i++) {
if (readfds[i].revents & (POLLIN | POLLERR | POLLHUP)) {
ssize_t numBytes = read(readfds[i].fd, buf, sizeof(buf));
if (numBytes < 0) {
int err = errno;
delegate.processHadError(ctx, handle,
Twine("unable to read process output (") + strerror(err) + ")");
}
if (numBytes <= 0 || !readCbs[i](StringRef(buf, numBytes))) {
readfds[i].events = 0;
continue;
}
}
activeEvents |= readfds[i].events != 0;
}
if (control.shouldRelease()) {
std::shared_ptr<ManagedDescriptor> outputFdShared
= std::make_shared<ManagedDescriptor>(std::move(outputPipeParentEnd));
std::shared_ptr<ManagedDescriptor> controlFdShared
= std::make_shared<ManagedDescriptor>(std::move(controlPipeParentEnd));
releaseFn([&delegate, &pgrp, pid, handle, ctx,
outputFdShared, controlFdShared,
completionFn=std::move(completionFn)]() mutable {
if (outputFdShared->isValid()) {
captureExecutedProcessOutput(delegate, *outputFdShared, handle, ctx);
}
cleanUpExecutedProcess(delegate, pgrp, pid, handle, ctx,
std::move(completionFn), *controlFdShared);
});
return;
}
}
#endif // else !defined(_WIN32)
// If we have reached here, both the control and read pipes have given us
// the requisite EOF/hang-up events. Safe to close the read end of the
// output pipe.
outputPipeParentEnd.close();
cleanUpExecutedProcess(delegate, pgrp, pid, handle, ctx,
std::move(completionFn), controlPipeParentEnd);
#endif
}