| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <assert.h> |
| #include <lib/fdio/io.h> |
| #include <lib/fdio/spawn.h> |
| #include <lib/fdio/util.h> |
| #include <memory.h> |
| #include <memory> |
| #include <string> |
| #include <unistd.h> |
| #include <vector> |
| #include <zircon/process.h> |
| #include <zircon/processargs.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/object.h> |
| |
| #include "deProcess.h" |
| |
| #if (DE_OS != DE_OS_FUCHSIA) |
| #error DE_OS_FUCHSIA expected |
| #endif |
| |
| #include "deCommandLine.h" |
| |
| struct deProcess_s { |
| }; |
| |
| class ModifiedEnviron { |
| public: |
| explicit ModifiedEnviron(char** env) |
| { |
| for (size_t i = 0; env[i]; i++) { |
| envs_.push_back(env[i]); |
| } |
| } |
| |
| void ReplacePwd(const char* working_directory) |
| { |
| assert(working_directory); |
| char pwd_string[strlen(working_directory) + 4 + 1]; |
| |
| sprintf(pwd_string, "PWD=%s", working_directory); |
| for (size_t i = 0; i < envs_.size(); i++) { |
| if (strstr(envs_[i].c_str(), "PWD=") == 0) { |
| envs_[i] = pwd_string; |
| return; |
| } |
| } |
| envs_.push_back(pwd_string); |
| } |
| |
| // Return value must not outlive ModifiedEnviron. Invalidated if ReplacePwd |
| // is called. |
| std::vector<const char*> GetEnviron() |
| { |
| std::vector<const char*> env; |
| for (size_t i = 0; i < envs_.size(); i++) { |
| env.push_back(envs_[i].c_str()); |
| } |
| env.push_back(nullptr); |
| return env; |
| } |
| |
| private: |
| std::vector<std::string> envs_; |
| }; |
| |
| class DeProcess : public deProcess_s { |
| public: |
| static std::unique_ptr<DeProcess> Create() |
| { |
| return std::unique_ptr<DeProcess>(new DeProcess()); |
| } |
| |
| DeProcess() : proc_(ZX_HANDLE_INVALID), fstdin_(nullptr), fstdout_(nullptr), fstderr_(nullptr) |
| { |
| } |
| |
| ~DeProcess() |
| { |
| if (proc_ != ZX_HANDLE_INVALID) |
| zx_handle_close(proc_); |
| closeStdIn(); |
| closeStdOut(); |
| closeStdErr(); |
| } |
| |
| bool start(const char* commandLine, const char* workingDirectory) |
| { |
| deCommandLine* cmdLine = deCommandLine_parse(commandLine); |
| assert(cmdLine->args[cmdLine->numArgs] == 0); |
| fdio_spawn_action_t spawn_actions[3]; |
| int result; |
| int stdin_pipes[2]; |
| result = pipe(stdin_pipes); |
| assert(result == 0); |
| int stdout_pipes[2]; |
| result = pipe(stdout_pipes); |
| assert(result == 0); |
| int stderr_pipes[2]; |
| result = pipe(stderr_pipes); |
| assert(result == 0); |
| |
| spawn_actions[0].action = FDIO_SPAWN_ACTION_TRANSFER_FD; |
| spawn_actions[0].fd.local_fd = stdin_pipes[0]; |
| spawn_actions[0].fd.target_fd = STDIN_FILENO; |
| fstdin_ = deFile_createFromHandle(stdin_pipes[1]); |
| |
| spawn_actions[1].action = FDIO_SPAWN_ACTION_TRANSFER_FD; |
| spawn_actions[1].fd.local_fd = stdout_pipes[1]; |
| spawn_actions[1].fd.target_fd = STDOUT_FILENO; |
| fstdout_ = deFile_createFromHandle(stdout_pipes[0]); |
| |
| #ifndef RAW_STDERR |
| spawn_actions[2].action = FDIO_SPAWN_ACTION_TRANSFER_FD; |
| spawn_actions[2].fd.local_fd = stderr_pipes[1]; |
| spawn_actions[2].fd.target_fd = STDERR_FILENO; |
| fstderr_ = deFile_createFromHandle(stderr_pipes[0]); |
| #else |
| // Printing to our stderr sometimes works better. |
| spawn_actions[2].action = FDIO_SPAWN_ACTION_CLONE_FD; |
| spawn_actions[2].fd.local_fd = STDERR_FILENO; |
| spawn_actions[2].fd.target_fd = STDERR_FILENO; |
| fstderr_ = deFile_createFromHandle(stderr_pipes[0]); |
| #endif |
| |
| ModifiedEnviron env(environ); |
| if (workingDirectory) |
| env.ReplacePwd(workingDirectory); |
| |
| char err_msg_out[FDIO_SPAWN_ERR_MSG_MAX_LENGTH]; |
| |
| uint32_t spawn_flags = FDIO_SPAWN_CLONE_NAMESPACE | FDIO_SPAWN_CLONE_ENVIRON | |
| FDIO_SPAWN_CLONE_LDSVC | FDIO_SPAWN_CLONE_JOB; |
| zx_status_t status = fdio_spawn_etc( |
| ZX_HANDLE_INVALID, spawn_flags, cmdLine->args[0], (const char* const*)&cmdLine->args[0], |
| env.GetEnviron().data(), 3, spawn_actions, &proc_, err_msg_out); |
| |
| if (status != ZX_OK) { |
| fprintf(stderr, "fdio_spawn error: %d %s\n", status, err_msg_out); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool IsRunning() |
| { |
| zx_signals_t pending = 0; |
| zx_status_t status = zx_object_wait_one(proc_, ZX_TASK_TERMINATED, 0, &pending); |
| if (pending & ZX_TASK_TERMINATED) |
| return false; |
| assert(status == ZX_ERR_TIMED_OUT); |
| return true; |
| } |
| |
| bool Wait() |
| { |
| zx_status_t status = zx_object_wait_one(proc_, ZX_TASK_TERMINATED, ZX_TIME_INFINITE, NULL); |
| return status == ZX_OK; |
| } |
| |
| int GetExitCode() |
| { |
| zx_info_process_t info; |
| zx_status_t status = |
| zx_object_get_info(proc_, ZX_INFO_PROCESS, &info, sizeof(info), NULL, NULL); |
| assert(status == ZX_OK); |
| return info.return_code; |
| } |
| |
| static DeProcess* cast(deProcess* process) { return static_cast<DeProcess*>(process); } |
| |
| deFile* fstdin() { return fstdin_; } |
| deFile* fstdout() { return fstdout_; } |
| deFile* fstderr() { return fstderr_; } |
| |
| void closeStdIn() |
| { |
| if (fstdin_) { |
| deFile_destroy(fstdin_); |
| fstdin_ = nullptr; |
| } |
| } |
| |
| void closeStdOut() |
| { |
| if (fstdout_) { |
| deFile_destroy(fstdout_); |
| fstdout_ = nullptr; |
| } |
| } |
| |
| void closeStdErr() |
| { |
| if (fstderr_) { |
| deFile_destroy(fstderr_); |
| fstderr_ = nullptr; |
| } |
| } |
| |
| private: |
| zx_handle_t proc_; |
| deFile* fstdin_; |
| deFile* fstdout_; |
| deFile* fstderr_; |
| }; |
| |
| deProcess* deProcess_create() |
| { |
| std::unique_ptr<DeProcess> process = DeProcess::Create(); |
| if (!process) |
| return nullptr; |
| |
| return process.release(); |
| } |
| |
| void deProcess_destroy(deProcess* process) { delete DeProcess::cast(process); } |
| |
| deBool deProcess_start(deProcess* de_process, const char* commandLine, const char* workingDirectory) |
| { |
| (void)workingDirectory; |
| |
| DeProcess* process = DeProcess::cast(de_process); |
| |
| if (!process->start(commandLine, workingDirectory)) |
| return false; |
| |
| return true; |
| } |
| |
| deBool deProcess_isRunning(deProcess* process) { return DeProcess::cast(process)->IsRunning(); } |
| |
| deBool deProcess_waitForFinish(deProcess* process) |
| { |
| return DeProcess::cast(process)->Wait() ? DE_TRUE : DE_FALSE; |
| } |
| |
| const char* deProcess_getLastError(const deProcess*) { return nullptr; } |
| |
| int deProcess_getExitCode(const deProcess* process) |
| { |
| return DeProcess::cast((deProcess*)process)->GetExitCode(); |
| } |
| |
| deBool deProcess_terminate(deProcess*) |
| { |
| printf("%s:%d Not implemented\n", __FILE__, __LINE__); |
| return DE_FALSE; |
| } |
| |
| deBool deProcess_kill(deProcess*) |
| { |
| printf("%s:%d Not implemented\n", __FILE__, __LINE__); |
| return DE_FALSE; |
| } |
| |
| deFile* deProcess_getStdIn(deProcess* process) { return DeProcess::cast(process)->fstdin(); } |
| |
| deFile* deProcess_getStdOut(deProcess* process) { return DeProcess::cast(process)->fstdout(); } |
| |
| deFile* deProcess_getStdErr(deProcess* process) { return DeProcess::cast(process)->fstderr(); } |
| |
| deBool deProcess_closeStdIn(deProcess* process) |
| { |
| DeProcess::cast(process)->closeStdIn(); |
| return DE_TRUE; |
| } |
| |
| deBool deProcess_closeStdOut(deProcess* process) |
| { |
| DeProcess::cast(process)->closeStdOut(); |
| return DE_TRUE; |
| } |
| |
| deBool deProcess_closeStdErr(deProcess* process) |
| { |
| DeProcess::cast(process)->closeStdErr(); |
| return DE_TRUE; |
| } |