blob: b59cca1b44dbf97e1822bd740b40e26574d4b123 [file] [log] [blame]
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "subprocess.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <launchpad/launchpad.h>
#include <poll.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <zircon/errors.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
extern char** environ;
#include "util.h"
Subprocess::Subprocess(bool use_console) : fd_(-1), handle_(ZX_HANDLE_INVALID),
use_console_(use_console) {
}
Subprocess::~Subprocess() {
if (fd_ >= 0)
close(fd_);
// Reap child if forgotten.
if (handle_ != ZX_HANDLE_INVALID)
Finish();
}
bool Subprocess::Start(SubprocessSet* set, const string& command) {
printf("executing %s\n", command.c_str());
int output_pipe[2];
if (pipe(output_pipe) < 0)
Fatal("pipe: %s", strerror(errno));
fd_ = output_pipe[0];
launchpad_t* lp = NULL;
launchpad_create(ZX_HANDLE_INVALID, "sh", &lp);
const char* args[] = { "/boot/bin/sh", "-c", command.c_str() };
launchpad_load_from_file(lp, args[0]);
launchpad_set_args(lp, 3, args);
launchpad_clone(lp, LP_CLONE_ALL & (~LP_CLONE_FDIO_STDIO));
if (!use_console_) {
launchpad_clone_fd(lp, output_pipe[1], 1);
launchpad_clone_fd(lp, output_pipe[1], 2);
}
const char *errmsg = NULL;
if (launchpad_go(lp, &handle_, &errmsg) != ZX_OK)
Fatal(errmsg);
launchpad_destroy(lp);
close(output_pipe[1]);
return true;
}
void Subprocess::OnPipeReady() {
char buf[4 << 10];
ssize_t len = read(fd_, buf, sizeof(buf));
if (len > 0) {
buf_.append(buf, len);
} else {
if (len < 0)
Fatal("read: %s", strerror(errno));
close(fd_);
fd_ = -1;
}
}
ExitStatus Subprocess::Finish() {
assert(handle_ != ZX_HANDLE_INVALID);
zx_status_t status = zx_object_wait_one(handle_, ZX_PROCESS_TERMINATED, ZX_TIME_INFINITE, nullptr);
if (status != ZX_OK)
Fatal("zx_object_wait_one failed");
zx_info_process_t info;
status = zx_object_get_info(handle_, ZX_INFO_PROCESS, &info, sizeof(info), nullptr, nullptr);
if (status != ZX_OK)
Fatal("zx_object_get_info failed to get process status");
zx_handle_close(handle_);
handle_ = ZX_HANDLE_INVALID;
if (info.exited) {
if (info.return_code == 0)
return ExitSuccess;
}
return ExitFailure;
}
bool Subprocess::Done() const {
return fd_ == -1;
}
const string& Subprocess::GetOutput() const {
return buf_;
}
SubprocessSet::SubprocessSet() {
}
SubprocessSet::~SubprocessSet() {
Clear();
}
Subprocess *SubprocessSet::Add(const string& command, bool use_console) {
Subprocess *subprocess = new Subprocess(use_console);
if (!subprocess->Start(this, command)) {
delete subprocess;
return 0;
}
running_.push_back(subprocess);
return subprocess;
}
bool SubprocessSet::DoWork() {
vector<pollfd> fds;
nfds_t nfds = 0;
for (vector<Subprocess*>::iterator i = running_.begin();
i != running_.end(); ++i) {
int fd = (*i)->fd_;
if (fd < 0)
continue;
pollfd pfd = { fd, POLLIN | POLLPRI, 0 };
fds.push_back(pfd);
++nfds;
}
int ret = ppoll(&fds.front(), nfds, NULL, NULL);
if (ret == -1) {
perror("ninja: ppoll");
return false;
}
nfds_t cur_nfd = 0;
for (vector<Subprocess*>::iterator i = running_.begin();
i != running_.end(); ) {
int fd = (*i)->fd_;
if (fd < 0)
continue;
assert(fd == fds[cur_nfd].fd);
if (fds[cur_nfd++].revents) {
(*i)->OnPipeReady();
if ((*i)->Done()) {
finished_.push(*i);
i = running_.erase(i);
continue;
}
}
++i;
}
return false;
}
Subprocess* SubprocessSet::NextFinished() {
if (finished_.empty())
return NULL;
Subprocess* subproc = finished_.front();
finished_.pop();
return subproc;
}
void SubprocessSet::Clear() {
for (vector<Subprocess*>::iterator i = running_.begin();
i != running_.end(); ++i)
// Since the foreground process is in our process group, it will receive
// the interruption signal (i.e. SIGINT or SIGTERM) at the same time as us.
if (!(*i)->use_console_)
zx_task_kill((*i)->handle_);
for (vector<Subprocess*>::iterator i = running_.begin();
i != running_.end(); ++i)
delete *i;
running_.clear();
}