diff options
author | rsesek@chromium.org <rsesek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-07-17 18:12:40 +0000 |
---|---|---|
committer | rsesek@chromium.org <rsesek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-07-17 18:12:40 +0000 |
commit | 300c386b2aa297d9fe887f4c63f840d9b60db3ca (patch) | |
tree | 03f16ab42b2560f15603dbf3818bcddb99df0c5a /base/process | |
parent | 1f523dc9b34f3f6e2ca73d52e1c060368a268c2a (diff) | |
download | chromium_src-300c386b2aa297d9fe887f4c63f840d9b60db3ca.zip chromium_src-300c386b2aa297d9fe887f4c63f840d9b60db3ca.tar.gz chromium_src-300c386b2aa297d9fe887f4c63f840d9b60db3ca.tar.bz2 |
Finish splitting base/process_util.h by moving the remaining routines to base/process/launch.h
This leaves a forwarding header at base/process_util.h so that include paths
can be cleaned up.
BUG=242290
R=brettw@chromium.org
Review URL: https://codereview.chromium.org/19240006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@212091 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base/process')
-rw-r--r-- | base/process/launch.h | 258 | ||||
-rw-r--r-- | base/process/launch_ios.cc | 13 | ||||
-rw-r--r-- | base/process/launch_mac.cc | 28 | ||||
-rw-r--r-- | base/process/launch_posix.cc | 728 | ||||
-rw-r--r-- | base/process/launch_win.cc | 283 |
5 files changed, 1310 insertions, 0 deletions
diff --git a/base/process/launch.h b/base/process/launch.h new file mode 100644 index 0000000..45b1053 --- /dev/null +++ b/base/process/launch.h @@ -0,0 +1,258 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains functions for launching subprocesses. + +#ifndef BASE_PROCESS_LAUNCH_H_ +#define BASE_PROCESS_LAUNCH_H_ + +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/process/process_handle.h" + +#if defined(OS_POSIX) +#include "base/posix/file_descriptor_shuffle.h" +#elif defined(OS_WIN) +#include <windows.h> +#endif + +class CommandLine; + +namespace base { + +typedef std::vector<std::pair<std::string, std::string> > EnvironmentVector; +typedef std::vector<std::pair<int, int> > FileHandleMappingVector; + +// Options for launching a subprocess that are passed to LaunchProcess(). +// The default constructor constructs the object with default options. +struct LaunchOptions { + LaunchOptions() + : wait(false), +#if defined(OS_WIN) + start_hidden(false), + inherit_handles(false), + as_user(NULL), + empty_desktop_name(false), + job_handle(NULL), + stdin_handle(NULL), + stdout_handle(NULL), + stderr_handle(NULL), + force_breakaway_from_job_(false) +#else + environ(NULL), + fds_to_remap(NULL), + maximize_rlimits(NULL), + new_process_group(false) +#if defined(OS_LINUX) + , clone_flags(0) +#endif // OS_LINUX +#if defined(OS_CHROMEOS) + , ctrl_terminal_fd(-1) +#endif // OS_CHROMEOS +#endif // !defined(OS_WIN) + {} + + // If true, wait for the process to complete. + bool wait; + +#if defined(OS_WIN) + bool start_hidden; + + // If true, the new process inherits handles from the parent. In production + // code this flag should be used only when running short-lived, trusted + // binaries, because open handles from other libraries and subsystems will + // leak to the child process, causing errors such as open socket hangs. + bool inherit_handles; + + // If non-NULL, runs as if the user represented by the token had launched it. + // Whether the application is visible on the interactive desktop depends on + // the token belonging to an interactive logon session. + // + // To avoid hard to diagnose problems, when specified this loads the + // environment variables associated with the user and if this operation fails + // the entire call fails as well. + UserTokenHandle as_user; + + // If true, use an empty string for the desktop name. + bool empty_desktop_name; + + // If non-NULL, launches the application in that job object. The process will + // be terminated immediately and LaunchProcess() will fail if assignment to + // the job object fails. + HANDLE job_handle; + + // Handles for the redirection of stdin, stdout and stderr. The handles must + // be inheritable. Caller should either set all three of them or none (i.e. + // there is no way to redirect stderr without redirecting stdin). The + // |inherit_handles| flag must be set to true when redirecting stdio stream. + HANDLE stdin_handle; + HANDLE stdout_handle; + HANDLE stderr_handle; + + // If set to true, ensures that the child process is launched with the + // CREATE_BREAKAWAY_FROM_JOB flag which allows it to breakout of the parent + // job if any. + bool force_breakaway_from_job_; +#else + // If non-NULL, set/unset environment variables. + // See documentation of AlterEnvironment(). + // This pointer is owned by the caller and must live through the + // call to LaunchProcess(). + const EnvironmentVector* environ; + + // If non-NULL, remap file descriptors according to the mapping of + // src fd->dest fd to propagate FDs into the child process. + // This pointer is owned by the caller and must live through the + // call to LaunchProcess(). + const FileHandleMappingVector* fds_to_remap; + + // Each element is an RLIMIT_* constant that should be raised to its + // rlim_max. This pointer is owned by the caller and must live through + // the call to LaunchProcess(). + const std::set<int>* maximize_rlimits; + + // If true, start the process in a new process group, instead of + // inheriting the parent's process group. The pgid of the child process + // will be the same as its pid. + bool new_process_group; + +#if defined(OS_LINUX) + // If non-zero, start the process using clone(), using flags as provided. + int clone_flags; +#endif // defined(OS_LINUX) + +#if defined(OS_CHROMEOS) + // If non-negative, the specified file descriptor will be set as the launched + // process' controlling terminal. + int ctrl_terminal_fd; +#endif // defined(OS_CHROMEOS) + +#endif // !defined(OS_WIN) +}; + +// Launch a process via the command line |cmdline|. +// See the documentation of LaunchOptions for details on |options|. +// +// Returns true upon success. +// +// Upon success, if |process_handle| is non-NULL, it will be filled in with the +// handle of the launched process. NOTE: In this case, the caller is +// responsible for closing the handle so that it doesn't leak! +// Otherwise, the process handle will be implicitly closed. +// +// Unix-specific notes: +// - All file descriptors open in the parent process will be closed in the +// child process except for any preserved by options::fds_to_remap, and +// stdin, stdout, and stderr. If not remapped by options::fds_to_remap, +// stdin is reopened as /dev/null, and the child is allowed to inherit its +// parent's stdout and stderr. +// - If the first argument on the command line does not contain a slash, +// PATH will be searched. (See man execvp.) +BASE_EXPORT bool LaunchProcess(const CommandLine& cmdline, + const LaunchOptions& options, + ProcessHandle* process_handle); + +#if defined(OS_WIN) +// Windows-specific LaunchProcess that takes the command line as a +// string. Useful for situations where you need to control the +// command line arguments directly, but prefer the CommandLine version +// if launching Chrome itself. +// +// The first command line argument should be the path to the process, +// and don't forget to quote it. +// +// Example (including literal quotes) +// cmdline = "c:\windows\explorer.exe" -foo "c:\bar\" +BASE_EXPORT bool LaunchProcess(const string16& cmdline, + const LaunchOptions& options, + ProcessHandle* process_handle); + +#elif defined(OS_POSIX) +// A POSIX-specific version of LaunchProcess that takes an argv array +// instead of a CommandLine. Useful for situations where you need to +// control the command line arguments directly, but prefer the +// CommandLine version if launching Chrome itself. +BASE_EXPORT bool LaunchProcess(const std::vector<std::string>& argv, + const LaunchOptions& options, + ProcessHandle* process_handle); + +// AlterEnvironment returns a modified environment vector, constructed from the +// given environment and the list of changes given in |changes|. Each key in +// the environment is matched against the first element of the pairs. In the +// event of a match, the value is replaced by the second of the pair, unless +// the second is empty, in which case the key-value is removed. +// +// The returned array is allocated using new[] and must be freed by the caller. +BASE_EXPORT char** AlterEnvironment(const EnvironmentVector& changes, + const char* const* const env); + +// Close all file descriptors, except those which are a destination in the +// given multimap. Only call this function in a child process where you know +// that there aren't any other threads. +BASE_EXPORT void CloseSuperfluousFds(const InjectiveMultimap& saved_map); +#endif // defined(OS_POSIX) + +#if defined(OS_WIN) +// Set JOBOBJECT_EXTENDED_LIMIT_INFORMATION to JobObject |job_object|. +// As its limit_info.BasicLimitInformation.LimitFlags has +// JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. +// When the provide JobObject |job_object| is closed, the binded process will +// be terminated. +BASE_EXPORT bool SetJobObjectAsKillOnJobClose(HANDLE job_object); + +// Output multi-process printf, cout, cerr, etc to the cmd.exe console that ran +// chrome. This is not thread-safe: only call from main thread. +BASE_EXPORT void RouteStdioToConsole(); +#endif // defined(OS_WIN) + +// Executes the application specified by |cl| and wait for it to exit. Stores +// the output (stdout) in |output|. Redirects stderr to /dev/null. Returns true +// on success (application launched and exited cleanly, with exit code +// indicating success). +BASE_EXPORT bool GetAppOutput(const CommandLine& cl, std::string* output); + +#if defined(OS_POSIX) +// A POSIX-specific version of GetAppOutput that takes an argv array +// instead of a CommandLine. Useful for situations where you need to +// control the command line arguments directly. +BASE_EXPORT bool GetAppOutput(const std::vector<std::string>& argv, + std::string* output); + +// A restricted version of |GetAppOutput()| which (a) clears the environment, +// and (b) stores at most |max_output| bytes; also, it doesn't search the path +// for the command. +BASE_EXPORT bool GetAppOutputRestricted(const CommandLine& cl, + std::string* output, size_t max_output); + +// A version of |GetAppOutput()| which also returns the exit code of the +// executed command. Returns true if the application runs and exits cleanly. If +// this is the case the exit code of the application is available in +// |*exit_code|. +BASE_EXPORT bool GetAppOutputWithExitCode(const CommandLine& cl, + std::string* output, int* exit_code); +#endif // defined(OS_POSIX) + +// If supported on the platform, and the user has sufficent rights, increase +// the current process's scheduling priority to a high priority. +BASE_EXPORT void RaiseProcessToHighPriority(); + +#if defined(OS_MACOSX) +// Restore the default exception handler, setting it to Apple Crash Reporter +// (ReportCrash). When forking and execing a new process, the child will +// inherit the parent's exception ports, which may be set to the Breakpad +// instance running inside the parent. The parent's Breakpad instance should +// not handle the child's exceptions. Calling RestoreDefaultExceptionHandler +// in the child after forking will restore the standard exception handler. +// See http://crbug.com/20371/ for more details. +void RestoreDefaultExceptionHandler(); +#endif // defined(OS_MACOSX) + +} // namespace base + +#endif // BASE_PROCESS_LAUNCH_H_ diff --git a/base/process/launch_ios.cc b/base/process/launch_ios.cc new file mode 100644 index 0000000..3c700f8 --- /dev/null +++ b/base/process/launch_ios.cc @@ -0,0 +1,13 @@ +// Copyright (c) 2012 The Chromium 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 "base/process/launch.h" + +namespace base { + +void RaiseProcessToHighPriority() { + // Impossible on iOS. Do nothing. +} + +} // namespace base diff --git a/base/process/launch_mac.cc b/base/process/launch_mac.cc new file mode 100644 index 0000000..176edca --- /dev/null +++ b/base/process/launch_mac.cc @@ -0,0 +1,28 @@ +// Copyright (c) 2012 The Chromium 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 "base/process/launch.h" + +#include <mach/mach.h> + +namespace base { + +void RestoreDefaultExceptionHandler() { + // This function is tailored to remove the Breakpad exception handler. + // exception_mask matches s_exception_mask in + // breakpad/src/client/mac/handler/exception_handler.cc + const exception_mask_t exception_mask = EXC_MASK_BAD_ACCESS | + EXC_MASK_BAD_INSTRUCTION | + EXC_MASK_ARITHMETIC | + EXC_MASK_BREAKPOINT; + + // Setting the exception port to MACH_PORT_NULL may not be entirely + // kosher to restore the default exception handler, but in practice, + // it results in the exception port being set to Apple Crash Reporter, + // the desired behavior. + task_set_exception_ports(mach_task_self(), exception_mask, MACH_PORT_NULL, + EXCEPTION_DEFAULT, THREAD_STATE_NONE); +} + +} // namespace base diff --git a/base/process/launch_posix.cc b/base/process/launch_posix.cc new file mode 100644 index 0000000..336633c --- /dev/null +++ b/base/process/launch_posix.cc @@ -0,0 +1,728 @@ +// Copyright (c) 2012 The Chromium 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 "base/process/launch.h" + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdlib.h> +#include <sys/resource.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <iterator> +#include <limits> +#include <set> + +#include "base/allocator/type_profiler_control.h" +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/debug/debugger.h" +#include "base/debug/stack_trace.h" +#include "base/file_util.h" +#include "base/files/dir_reader_posix.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process_util.h" +#include "base/strings/stringprintf.h" +#include "base/synchronization/waitable_event.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread_restrictions.h" + +#if defined(OS_CHROMEOS) +#include <sys/ioctl.h> +#endif + +#if defined(OS_FREEBSD) +#include <sys/event.h> +#include <sys/ucontext.h> +#endif + +#if defined(OS_MACOSX) +#include <crt_externs.h> +#include <sys/event.h> +#else +extern char** environ; +#endif + +namespace base { + +namespace { + +// Get the process's "environment" (i.e. the thing that setenv/getenv +// work with). +char** GetEnvironment() { +#if defined(OS_MACOSX) + return *_NSGetEnviron(); +#else + return environ; +#endif +} + +// Set the process's "environment" (i.e. the thing that setenv/getenv +// work with). +void SetEnvironment(char** env) { +#if defined(OS_MACOSX) + *_NSGetEnviron() = env; +#else + environ = env; +#endif +} + +#if !defined(OS_LINUX) || \ + (!defined(__i386__) && !defined(__x86_64__) && !defined(__arm__)) +void ResetChildSignalHandlersToDefaults() { + // The previous signal handlers are likely to be meaningless in the child's + // context so we reset them to the defaults for now. http://crbug.com/44953 + // These signal handlers are set up at least in browser_main_posix.cc: + // BrowserMainPartsPosix::PreEarlyInitialization and stack_trace_posix.cc: + // EnableInProcessStackDumping. + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGILL, SIG_DFL); + signal(SIGABRT, SIG_DFL); + signal(SIGFPE, SIG_DFL); + signal(SIGBUS, SIG_DFL); + signal(SIGSEGV, SIG_DFL); + signal(SIGSYS, SIG_DFL); + signal(SIGTERM, SIG_DFL); +} + +#else + +// TODO(jln): remove the Linux special case once kernels are fixed. + +// Internally the kernel makes sigset_t an array of long large enough to have +// one bit per signal. +typedef uint64_t kernel_sigset_t; + +// This is what struct sigaction looks like to the kernel at least on X86 and +// ARM. MIPS, for instance, is very different. +struct kernel_sigaction { + void* k_sa_handler; // For this usage it only needs to be a generic pointer. + unsigned long k_sa_flags; + void* k_sa_restorer; // For this usage it only needs to be a generic pointer. + kernel_sigset_t k_sa_mask; +}; + +// glibc's sigaction() will prevent access to sa_restorer, so we need to roll +// our own. +int sys_rt_sigaction(int sig, const struct kernel_sigaction* act, + struct kernel_sigaction* oact) { + return syscall(SYS_rt_sigaction, sig, act, oact, sizeof(kernel_sigset_t)); +} + +// This function is intended to be used in between fork() and execve() and will +// reset all signal handlers to the default. +// The motivation for going through all of them is that sa_restorer can leak +// from parents and help defeat ASLR on buggy kernels. We reset it to NULL. +// See crbug.com/177956. +void ResetChildSignalHandlersToDefaults(void) { + for (int signum = 1; ; ++signum) { + struct kernel_sigaction act = {0}; + int sigaction_get_ret = sys_rt_sigaction(signum, NULL, &act); + if (sigaction_get_ret && errno == EINVAL) { +#if !defined(NDEBUG) + // Linux supports 32 real-time signals from 33 to 64. + // If the number of signals in the Linux kernel changes, someone should + // look at this code. + const int kNumberOfSignals = 64; + RAW_CHECK(signum == kNumberOfSignals + 1); +#endif // !defined(NDEBUG) + break; + } + // All other failures are fatal. + if (sigaction_get_ret) { + RAW_LOG(FATAL, "sigaction (get) failed."); + } + + // The kernel won't allow to re-set SIGKILL or SIGSTOP. + if (signum != SIGSTOP && signum != SIGKILL) { + act.k_sa_handler = reinterpret_cast<void*>(SIG_DFL); + act.k_sa_restorer = NULL; + if (sys_rt_sigaction(signum, &act, NULL)) { + RAW_LOG(FATAL, "sigaction (set) failed."); + } + } +#if !defined(NDEBUG) + // Now ask the kernel again and check that no restorer will leak. + if (sys_rt_sigaction(signum, NULL, &act) || act.k_sa_restorer) { + RAW_LOG(FATAL, "Cound not fix sa_restorer."); + } +#endif // !defined(NDEBUG) + } +} +#endif // !defined(OS_LINUX) || + // (!defined(__i386__) && !defined(__x86_64__) && !defined(__arm__)) + +} // anonymous namespace + +// A class to handle auto-closing of DIR*'s. +class ScopedDIRClose { + public: + inline void operator()(DIR* x) const { + if (x) { + closedir(x); + } + } +}; +typedef scoped_ptr_malloc<DIR, ScopedDIRClose> ScopedDIR; + +#if defined(OS_LINUX) +static const char kFDDir[] = "/proc/self/fd"; +#elif defined(OS_MACOSX) +static const char kFDDir[] = "/dev/fd"; +#elif defined(OS_SOLARIS) +static const char kFDDir[] = "/dev/fd"; +#elif defined(OS_FREEBSD) +static const char kFDDir[] = "/dev/fd"; +#elif defined(OS_OPENBSD) +static const char kFDDir[] = "/dev/fd"; +#elif defined(OS_ANDROID) +static const char kFDDir[] = "/proc/self/fd"; +#endif + +void CloseSuperfluousFds(const base::InjectiveMultimap& saved_mapping) { + // DANGER: no calls to malloc are allowed from now on: + // http://crbug.com/36678 + + // Get the maximum number of FDs possible. + size_t max_fds = GetMaxFds(); + + DirReaderPosix fd_dir(kFDDir); + if (!fd_dir.IsValid()) { + // Fallback case: Try every possible fd. + for (size_t i = 0; i < max_fds; ++i) { + const int fd = static_cast<int>(i); + if (fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO) + continue; + InjectiveMultimap::const_iterator j; + for (j = saved_mapping.begin(); j != saved_mapping.end(); j++) { + if (fd == j->dest) + break; + } + if (j != saved_mapping.end()) + continue; + + // Since we're just trying to close anything we can find, + // ignore any error return values of close(). + ignore_result(HANDLE_EINTR(close(fd))); + } + return; + } + + const int dir_fd = fd_dir.fd(); + + for ( ; fd_dir.Next(); ) { + // Skip . and .. entries. + if (fd_dir.name()[0] == '.') + continue; + + char *endptr; + errno = 0; + const long int fd = strtol(fd_dir.name(), &endptr, 10); + if (fd_dir.name()[0] == 0 || *endptr || fd < 0 || errno) + continue; + if (fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO) + continue; + InjectiveMultimap::const_iterator i; + for (i = saved_mapping.begin(); i != saved_mapping.end(); i++) { + if (fd == i->dest) + break; + } + if (i != saved_mapping.end()) + continue; + if (fd == dir_fd) + continue; + + // When running under Valgrind, Valgrind opens several FDs for its + // own use and will complain if we try to close them. All of + // these FDs are >= |max_fds|, so we can check against that here + // before closing. See https://bugs.kde.org/show_bug.cgi?id=191758 + if (fd < static_cast<int>(max_fds)) { + int ret = HANDLE_EINTR(close(fd)); + DPCHECK(ret == 0); + } + } +} + +char** AlterEnvironment(const EnvironmentVector& changes, + const char* const* const env) { + unsigned count = 0; + unsigned size = 0; + + // First assume that all of the current environment will be included. + for (unsigned i = 0; env[i]; i++) { + const char *const pair = env[i]; + count++; + size += strlen(pair) + 1 /* terminating NUL */; + } + + for (EnvironmentVector::const_iterator j = changes.begin(); + j != changes.end(); + ++j) { + bool found = false; + const char *pair; + + for (unsigned i = 0; env[i]; i++) { + pair = env[i]; + const char *const equals = strchr(pair, '='); + if (!equals) + continue; + const unsigned keylen = equals - pair; + if (keylen == j->first.size() && + memcmp(pair, j->first.data(), keylen) == 0) { + found = true; + break; + } + } + + // if found, we'll either be deleting or replacing this element. + if (found) { + count--; + size -= strlen(pair) + 1; + if (j->second.size()) + found = false; + } + + // if !found, then we have a new element to add. + if (!found && !j->second.empty()) { + count++; + size += j->first.size() + 1 /* '=' */ + j->second.size() + 1 /* NUL */; + } + } + + count++; // for the final NULL + uint8_t *buffer = new uint8_t[sizeof(char*) * count + size]; + char **const ret = reinterpret_cast<char**>(buffer); + unsigned k = 0; + char *scratch = reinterpret_cast<char*>(buffer + sizeof(char*) * count); + + for (unsigned i = 0; env[i]; i++) { + const char *const pair = env[i]; + const char *const equals = strchr(pair, '='); + if (!equals) { + const unsigned len = strlen(pair); + ret[k++] = scratch; + memcpy(scratch, pair, len + 1); + scratch += len + 1; + continue; + } + const unsigned keylen = equals - pair; + bool handled = false; + for (EnvironmentVector::const_iterator + j = changes.begin(); j != changes.end(); j++) { + if (j->first.size() == keylen && + memcmp(j->first.data(), pair, keylen) == 0) { + if (!j->second.empty()) { + ret[k++] = scratch; + memcpy(scratch, pair, keylen + 1); + scratch += keylen + 1; + memcpy(scratch, j->second.c_str(), j->second.size() + 1); + scratch += j->second.size() + 1; + } + handled = true; + break; + } + } + + if (!handled) { + const unsigned len = strlen(pair); + ret[k++] = scratch; + memcpy(scratch, pair, len + 1); + scratch += len + 1; + } + } + + // Now handle new elements + for (EnvironmentVector::const_iterator + j = changes.begin(); j != changes.end(); j++) { + if (j->second.empty()) + continue; + + bool found = false; + for (unsigned i = 0; env[i]; i++) { + const char *const pair = env[i]; + const char *const equals = strchr(pair, '='); + if (!equals) + continue; + const unsigned keylen = equals - pair; + if (keylen == j->first.size() && + memcmp(pair, j->first.data(), keylen) == 0) { + found = true; + break; + } + } + + if (!found) { + ret[k++] = scratch; + memcpy(scratch, j->first.data(), j->first.size()); + scratch += j->first.size(); + *scratch++ = '='; + memcpy(scratch, j->second.c_str(), j->second.size() + 1); + scratch += j->second.size() + 1; + } + } + + ret[k] = NULL; + return ret; +} + +bool LaunchProcess(const std::vector<std::string>& argv, + const LaunchOptions& options, + ProcessHandle* process_handle) { + size_t fd_shuffle_size = 0; + if (options.fds_to_remap) { + fd_shuffle_size = options.fds_to_remap->size(); + } + + InjectiveMultimap fd_shuffle1; + InjectiveMultimap fd_shuffle2; + fd_shuffle1.reserve(fd_shuffle_size); + fd_shuffle2.reserve(fd_shuffle_size); + + scoped_ptr<char*[]> argv_cstr(new char*[argv.size() + 1]); + scoped_ptr<char*[]> new_environ; + if (options.environ) + new_environ.reset(AlterEnvironment(*options.environ, GetEnvironment())); + + pid_t pid; +#if defined(OS_LINUX) + if (options.clone_flags) { + pid = syscall(__NR_clone, options.clone_flags, 0, 0, 0); + } else +#endif + { + pid = fork(); + } + + if (pid < 0) { + DPLOG(ERROR) << "fork"; + return false; + } else if (pid == 0) { + // Child process + + // DANGER: fork() rule: in the child, if you don't end up doing exec*(), + // you call _exit() instead of exit(). This is because _exit() does not + // call any previously-registered (in the parent) exit handlers, which + // might do things like block waiting for threads that don't even exist + // in the child. + + // If a child process uses the readline library, the process block forever. + // In BSD like OSes including OS X it is safe to assign /dev/null as stdin. + // See http://crbug.com/56596. + int null_fd = HANDLE_EINTR(open("/dev/null", O_RDONLY)); + if (null_fd < 0) { + RAW_LOG(ERROR, "Failed to open /dev/null"); + _exit(127); + } + + file_util::ScopedFD null_fd_closer(&null_fd); + int new_fd = HANDLE_EINTR(dup2(null_fd, STDIN_FILENO)); + if (new_fd != STDIN_FILENO) { + RAW_LOG(ERROR, "Failed to dup /dev/null for stdin"); + _exit(127); + } + + if (options.new_process_group) { + // Instead of inheriting the process group ID of the parent, the child + // starts off a new process group with pgid equal to its process ID. + if (setpgid(0, 0) < 0) { + RAW_LOG(ERROR, "setpgid failed"); + _exit(127); + } + } + + // Stop type-profiler. + // The profiler should be stopped between fork and exec since it inserts + // locks at new/delete expressions. See http://crbug.com/36678. + base::type_profiler::Controller::Stop(); + + if (options.maximize_rlimits) { + // Some resource limits need to be maximal in this child. + std::set<int>::const_iterator resource; + for (resource = options.maximize_rlimits->begin(); + resource != options.maximize_rlimits->end(); + ++resource) { + struct rlimit limit; + if (getrlimit(*resource, &limit) < 0) { + RAW_LOG(WARNING, "getrlimit failed"); + } else if (limit.rlim_cur < limit.rlim_max) { + limit.rlim_cur = limit.rlim_max; + if (setrlimit(*resource, &limit) < 0) { + RAW_LOG(WARNING, "setrlimit failed"); + } + } + } + } + +#if defined(OS_MACOSX) + RestoreDefaultExceptionHandler(); +#endif // defined(OS_MACOSX) + + ResetChildSignalHandlersToDefaults(); + +#if 0 + // When debugging it can be helpful to check that we really aren't making + // any hidden calls to malloc. + void *malloc_thunk = + reinterpret_cast<void*>(reinterpret_cast<intptr_t>(malloc) & ~4095); + mprotect(malloc_thunk, 4096, PROT_READ | PROT_WRITE | PROT_EXEC); + memset(reinterpret_cast<void*>(malloc), 0xff, 8); +#endif // 0 + + // DANGER: no calls to malloc are allowed from now on: + // http://crbug.com/36678 + +#if defined(OS_CHROMEOS) + if (options.ctrl_terminal_fd >= 0) { + // Set process' controlling terminal. + if (HANDLE_EINTR(setsid()) != -1) { + if (HANDLE_EINTR( + ioctl(options.ctrl_terminal_fd, TIOCSCTTY, NULL)) == -1) { + RAW_LOG(WARNING, "ioctl(TIOCSCTTY), ctrl terminal not set"); + } + } else { + RAW_LOG(WARNING, "setsid failed, ctrl terminal not set"); + } + } +#endif // defined(OS_CHROMEOS) + + if (options.fds_to_remap) { + for (FileHandleMappingVector::const_iterator + it = options.fds_to_remap->begin(); + it != options.fds_to_remap->end(); ++it) { + fd_shuffle1.push_back(InjectionArc(it->first, it->second, false)); + fd_shuffle2.push_back(InjectionArc(it->first, it->second, false)); + } + } + + if (options.environ) + SetEnvironment(new_environ.get()); + + // fd_shuffle1 is mutated by this call because it cannot malloc. + if (!ShuffleFileDescriptors(&fd_shuffle1)) + _exit(127); + + CloseSuperfluousFds(fd_shuffle2); + + for (size_t i = 0; i < argv.size(); i++) + argv_cstr[i] = const_cast<char*>(argv[i].c_str()); + argv_cstr[argv.size()] = NULL; + execvp(argv_cstr[0], argv_cstr.get()); + + RAW_LOG(ERROR, "LaunchProcess: failed to execvp:"); + RAW_LOG(ERROR, argv_cstr[0]); + _exit(127); + } else { + // Parent process + if (options.wait) { + // While this isn't strictly disk IO, waiting for another process to + // finish is the sort of thing ThreadRestrictions is trying to prevent. + base::ThreadRestrictions::AssertIOAllowed(); + pid_t ret = HANDLE_EINTR(waitpid(pid, 0, 0)); + DPCHECK(ret > 0); + } + + if (process_handle) + *process_handle = pid; + } + + return true; +} + + +bool LaunchProcess(const CommandLine& cmdline, + const LaunchOptions& options, + ProcessHandle* process_handle) { + return LaunchProcess(cmdline.argv(), options, process_handle); +} + +void RaiseProcessToHighPriority() { + // On POSIX, we don't actually do anything here. We could try to nice() or + // setpriority() or sched_getscheduler, but these all require extra rights. +} + +// Return value used by GetAppOutputInternal to encapsulate the various exit +// scenarios from the function. +enum GetAppOutputInternalResult { + EXECUTE_FAILURE, + EXECUTE_SUCCESS, + GOT_MAX_OUTPUT, +}; + +// Executes the application specified by |argv| and wait for it to exit. Stores +// the output (stdout) in |output|. If |do_search_path| is set, it searches the +// path for the application; in that case, |envp| must be null, and it will use +// the current environment. If |do_search_path| is false, |argv[0]| should fully +// specify the path of the application, and |envp| will be used as the +// environment. Redirects stderr to /dev/null. +// If we successfully start the application and get all requested output, we +// return GOT_MAX_OUTPUT, or if there is a problem starting or exiting +// the application we return RUN_FAILURE. Otherwise we return EXECUTE_SUCCESS. +// The GOT_MAX_OUTPUT return value exists so a caller that asks for limited +// output can treat this as a success, despite having an exit code of SIG_PIPE +// due to us closing the output pipe. +// In the case of EXECUTE_SUCCESS, the application exit code will be returned +// in |*exit_code|, which should be checked to determine if the application +// ran successfully. +static GetAppOutputInternalResult GetAppOutputInternal( + const std::vector<std::string>& argv, + char* const envp[], + std::string* output, + size_t max_output, + bool do_search_path, + int* exit_code) { + // Doing a blocking wait for another command to finish counts as IO. + base::ThreadRestrictions::AssertIOAllowed(); + // exit_code must be supplied so calling function can determine success. + DCHECK(exit_code); + *exit_code = EXIT_FAILURE; + + int pipe_fd[2]; + pid_t pid; + InjectiveMultimap fd_shuffle1, fd_shuffle2; + scoped_ptr<char*[]> argv_cstr(new char*[argv.size() + 1]); + + fd_shuffle1.reserve(3); + fd_shuffle2.reserve(3); + + // Either |do_search_path| should be false or |envp| should be null, but not + // both. + DCHECK(!do_search_path ^ !envp); + + if (pipe(pipe_fd) < 0) + return EXECUTE_FAILURE; + + switch (pid = fork()) { + case -1: // error + close(pipe_fd[0]); + close(pipe_fd[1]); + return EXECUTE_FAILURE; + case 0: // child + { +#if defined(OS_MACOSX) + RestoreDefaultExceptionHandler(); +#endif + // DANGER: no calls to malloc are allowed from now on: + // http://crbug.com/36678 + + // Obscure fork() rule: in the child, if you don't end up doing exec*(), + // you call _exit() instead of exit(). This is because _exit() does not + // call any previously-registered (in the parent) exit handlers, which + // might do things like block waiting for threads that don't even exist + // in the child. + int dev_null = open("/dev/null", O_WRONLY); + if (dev_null < 0) + _exit(127); + + // Stop type-profiler. + // The profiler should be stopped between fork and exec since it inserts + // locks at new/delete expressions. See http://crbug.com/36678. + base::type_profiler::Controller::Stop(); + + fd_shuffle1.push_back(InjectionArc(pipe_fd[1], STDOUT_FILENO, true)); + fd_shuffle1.push_back(InjectionArc(dev_null, STDERR_FILENO, true)); + fd_shuffle1.push_back(InjectionArc(dev_null, STDIN_FILENO, true)); + // Adding another element here? Remeber to increase the argument to + // reserve(), above. + + std::copy(fd_shuffle1.begin(), fd_shuffle1.end(), + std::back_inserter(fd_shuffle2)); + + if (!ShuffleFileDescriptors(&fd_shuffle1)) + _exit(127); + + CloseSuperfluousFds(fd_shuffle2); + + for (size_t i = 0; i < argv.size(); i++) + argv_cstr[i] = const_cast<char*>(argv[i].c_str()); + argv_cstr[argv.size()] = NULL; + if (do_search_path) + execvp(argv_cstr[0], argv_cstr.get()); + else + execve(argv_cstr[0], argv_cstr.get(), envp); + _exit(127); + } + default: // parent + { + // Close our writing end of pipe now. Otherwise later read would not + // be able to detect end of child's output (in theory we could still + // write to the pipe). + close(pipe_fd[1]); + + output->clear(); + char buffer[256]; + size_t output_buf_left = max_output; + ssize_t bytes_read = 1; // A lie to properly handle |max_output == 0| + // case in the logic below. + + while (output_buf_left > 0) { + bytes_read = HANDLE_EINTR(read(pipe_fd[0], buffer, + std::min(output_buf_left, sizeof(buffer)))); + if (bytes_read <= 0) + break; + output->append(buffer, bytes_read); + output_buf_left -= static_cast<size_t>(bytes_read); + } + close(pipe_fd[0]); + + // Always wait for exit code (even if we know we'll declare + // GOT_MAX_OUTPUT). + bool success = WaitForExitCode(pid, exit_code); + + // If we stopped because we read as much as we wanted, we return + // GOT_MAX_OUTPUT (because the child may exit due to |SIGPIPE|). + if (!output_buf_left && bytes_read > 0) + return GOT_MAX_OUTPUT; + else if (success) + return EXECUTE_SUCCESS; + return EXECUTE_FAILURE; + } + } +} + +bool GetAppOutput(const CommandLine& cl, std::string* output) { + return GetAppOutput(cl.argv(), output); +} + +bool GetAppOutput(const std::vector<std::string>& argv, std::string* output) { + // Run |execve()| with the current environment and store "unlimited" data. + int exit_code; + GetAppOutputInternalResult result = GetAppOutputInternal( + argv, NULL, output, std::numeric_limits<std::size_t>::max(), true, + &exit_code); + return result == EXECUTE_SUCCESS && exit_code == EXIT_SUCCESS; +} + +// TODO(viettrungluu): Conceivably, we should have a timeout as well, so we +// don't hang if what we're calling hangs. +bool GetAppOutputRestricted(const CommandLine& cl, + std::string* output, size_t max_output) { + // Run |execve()| with the empty environment. + char* const empty_environ = NULL; + int exit_code; + GetAppOutputInternalResult result = GetAppOutputInternal( + cl.argv(), &empty_environ, output, max_output, false, &exit_code); + return result == GOT_MAX_OUTPUT || (result == EXECUTE_SUCCESS && + exit_code == EXIT_SUCCESS); +} + +bool GetAppOutputWithExitCode(const CommandLine& cl, + std::string* output, + int* exit_code) { + // Run |execve()| with the current environment and store "unlimited" data. + GetAppOutputInternalResult result = GetAppOutputInternal( + cl.argv(), NULL, output, std::numeric_limits<std::size_t>::max(), true, + exit_code); + return result == EXECUTE_SUCCESS; +} + +} // namespace base diff --git a/base/process/launch_win.cc b/base/process/launch_win.cc new file mode 100644 index 0000000..41477f7 --- /dev/null +++ b/base/process/launch_win.cc @@ -0,0 +1,283 @@ +// Copyright (c) 2012 The Chromium 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 "base/process/launch.h" + +#include <fcntl.h> +#include <io.h> +#include <windows.h> +#include <userenv.h> +#include <psapi.h> + +#include <ios> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/command_line.h" +#include "base/debug/stack_trace.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/metrics/histogram.h" +#include "base/process/kill.h" +#include "base/sys_info.h" +#include "base/win/object_watcher.h" +#include "base/win/scoped_handle.h" +#include "base/win/scoped_process_information.h" +#include "base/win/windows_version.h" + +// userenv.dll is required for CreateEnvironmentBlock(). +#pragma comment(lib, "userenv.lib") + +namespace base { + +namespace { + +// This exit code is used by the Windows task manager when it kills a +// process. It's value is obviously not that unique, and it's +// surprising to me that the task manager uses this value, but it +// seems to be common practice on Windows to test for it as an +// indication that the task manager has killed something if the +// process goes away. +const DWORD kProcessKilledExitCode = 1; + +} // namespace + +void RouteStdioToConsole() { + // Don't change anything if stdout or stderr already point to a + // valid stream. + // + // If we are running under Buildbot or under Cygwin's default + // terminal (mintty), stderr and stderr will be pipe handles. In + // that case, we don't want to open CONOUT$, because its output + // likely does not go anywhere. + // + // We don't use GetStdHandle() to check stdout/stderr here because + // it can return dangling IDs of handles that were never inherited + // by this process. These IDs could have been reused by the time + // this function is called. The CRT checks the validity of + // stdout/stderr on startup (before the handle IDs can be reused). + // _fileno(stdout) will return -2 (_NO_CONSOLE_FILENO) if stdout was + // invalid. + if (_fileno(stdout) >= 0 || _fileno(stderr) >= 0) + return; + + if (!AttachConsole(ATTACH_PARENT_PROCESS)) { + unsigned int result = GetLastError(); + // Was probably already attached. + if (result == ERROR_ACCESS_DENIED) + return; + // Don't bother creating a new console for each child process if the + // parent process is invalid (eg: crashed). + if (result == ERROR_GEN_FAILURE) + return; + // Make a new console if attaching to parent fails with any other error. + // It should be ERROR_INVALID_HANDLE at this point, which means the browser + // was likely not started from a console. + AllocConsole(); + } + + // Arbitrary byte count to use when buffering output lines. More + // means potential waste, less means more risk of interleaved + // log-lines in output. + enum { kOutputBufferSize = 64 * 1024 }; + + if (freopen("CONOUT$", "w", stdout)) { + setvbuf(stdout, NULL, _IOLBF, kOutputBufferSize); + // Overwrite FD 1 for the benefit of any code that uses this FD + // directly. This is safe because the CRT allocates FDs 0, 1 and + // 2 at startup even if they don't have valid underlying Windows + // handles. This means we won't be overwriting an FD created by + // _open() after startup. + _dup2(_fileno(stdout), 1); + } + if (freopen("CONOUT$", "w", stderr)) { + setvbuf(stderr, NULL, _IOLBF, kOutputBufferSize); + _dup2(_fileno(stderr), 2); + } + + // Fix all cout, wcout, cin, wcin, cerr, wcerr, clog and wclog. + std::ios::sync_with_stdio(); +} + +bool LaunchProcess(const string16& cmdline, + const LaunchOptions& options, + ProcessHandle* process_handle) { + STARTUPINFO startup_info = {}; + startup_info.cb = sizeof(startup_info); + if (options.empty_desktop_name) + startup_info.lpDesktop = L""; + startup_info.dwFlags = STARTF_USESHOWWINDOW; + startup_info.wShowWindow = options.start_hidden ? SW_HIDE : SW_SHOW; + + if (options.stdin_handle || options.stdout_handle || options.stderr_handle) { + DCHECK(options.inherit_handles); + DCHECK(options.stdin_handle); + DCHECK(options.stdout_handle); + DCHECK(options.stderr_handle); + startup_info.dwFlags |= STARTF_USESTDHANDLES; + startup_info.hStdInput = options.stdin_handle; + startup_info.hStdOutput = options.stdout_handle; + startup_info.hStdError = options.stderr_handle; + } + + DWORD flags = 0; + + if (options.job_handle) { + flags |= CREATE_SUSPENDED; + + // If this code is run under a debugger, the launched process is + // automatically associated with a job object created by the debugger. + // The CREATE_BREAKAWAY_FROM_JOB flag is used to prevent this. + flags |= CREATE_BREAKAWAY_FROM_JOB; + } + + if (options.force_breakaway_from_job_) + flags |= CREATE_BREAKAWAY_FROM_JOB; + + base::win::ScopedProcessInformation process_info; + + if (options.as_user) { + flags |= CREATE_UNICODE_ENVIRONMENT; + void* enviroment_block = NULL; + + if (!CreateEnvironmentBlock(&enviroment_block, options.as_user, FALSE)) { + DPLOG(ERROR); + return false; + } + + BOOL launched = + CreateProcessAsUser(options.as_user, NULL, + const_cast<wchar_t*>(cmdline.c_str()), + NULL, NULL, options.inherit_handles, flags, + enviroment_block, NULL, &startup_info, + process_info.Receive()); + DestroyEnvironmentBlock(enviroment_block); + if (!launched) { + DPLOG(ERROR); + return false; + } + } else { + if (!CreateProcess(NULL, + const_cast<wchar_t*>(cmdline.c_str()), NULL, NULL, + options.inherit_handles, flags, NULL, NULL, + &startup_info, process_info.Receive())) { + DPLOG(ERROR); + return false; + } + } + + if (options.job_handle) { + if (0 == AssignProcessToJobObject(options.job_handle, + process_info.process_handle())) { + DLOG(ERROR) << "Could not AssignProcessToObject."; + KillProcess(process_info.process_handle(), kProcessKilledExitCode, true); + return false; + } + + ResumeThread(process_info.thread_handle()); + } + + if (options.wait) + WaitForSingleObject(process_info.process_handle(), INFINITE); + + // If the caller wants the process handle, we won't close it. + if (process_handle) + *process_handle = process_info.TakeProcessHandle(); + + return true; +} + +bool LaunchProcess(const CommandLine& cmdline, + const LaunchOptions& options, + ProcessHandle* process_handle) { + return LaunchProcess(cmdline.GetCommandLineString(), options, process_handle); +} + +bool SetJobObjectAsKillOnJobClose(HANDLE job_object) { + JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit_info = {0}; + limit_info.BasicLimitInformation.LimitFlags = + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + return 0 != SetInformationJobObject( + job_object, + JobObjectExtendedLimitInformation, + &limit_info, + sizeof(limit_info)); +} + +bool GetAppOutput(const CommandLine& cl, std::string* output) { + HANDLE out_read = NULL; + HANDLE out_write = NULL; + + SECURITY_ATTRIBUTES sa_attr; + // Set the bInheritHandle flag so pipe handles are inherited. + sa_attr.nLength = sizeof(SECURITY_ATTRIBUTES); + sa_attr.bInheritHandle = TRUE; + sa_attr.lpSecurityDescriptor = NULL; + + // Create the pipe for the child process's STDOUT. + if (!CreatePipe(&out_read, &out_write, &sa_attr, 0)) { + NOTREACHED() << "Failed to create pipe"; + return false; + } + + // Ensure we don't leak the handles. + win::ScopedHandle scoped_out_read(out_read); + win::ScopedHandle scoped_out_write(out_write); + + // Ensure the read handle to the pipe for STDOUT is not inherited. + if (!SetHandleInformation(out_read, HANDLE_FLAG_INHERIT, 0)) { + NOTREACHED() << "Failed to disabled pipe inheritance"; + return false; + } + + FilePath::StringType writable_command_line_string(cl.GetCommandLineString()); + + base::win::ScopedProcessInformation proc_info; + STARTUPINFO start_info = { 0 }; + + start_info.cb = sizeof(STARTUPINFO); + start_info.hStdOutput = out_write; + // Keep the normal stdin and stderr. + start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + start_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); + start_info.dwFlags |= STARTF_USESTDHANDLES; + + // Create the child process. + if (!CreateProcess(NULL, + &writable_command_line_string[0], + NULL, NULL, + TRUE, // Handles are inherited. + 0, NULL, NULL, &start_info, proc_info.Receive())) { + NOTREACHED() << "Failed to start process"; + return false; + } + + // Close our writing end of pipe now. Otherwise later read would not be able + // to detect end of child's output. + scoped_out_write.Close(); + + // Read output from the child process's pipe for STDOUT + const int kBufferSize = 1024; + char buffer[kBufferSize]; + + for (;;) { + DWORD bytes_read = 0; + BOOL success = ReadFile(out_read, buffer, kBufferSize, &bytes_read, NULL); + if (!success || bytes_read == 0) + break; + output->append(buffer, bytes_read); + } + + // Let's wait for the process to finish. + WaitForSingleObject(proc_info.process_handle(), INFINITE); + + return true; +} + +void RaiseProcessToHighPriority() { + SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); +} + +} // namespace base |