[email protected] | 991bd8a | 2013-12-12 18:45:45 | [diff] [blame] | 1 | // Copyright 2013 The Chromium Authors. All rights reserved. |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | // This file contains functions for launching subprocesses. |
| 6 | |
| 7 | #ifndef BASE_PROCESS_LAUNCH_H_ |
| 8 | #define BASE_PROCESS_LAUNCH_H_ |
| 9 | |
avi | beced7c | 2015-12-24 06:47:59 | [diff] [blame] | 10 | #include <stddef.h> |
| 11 | |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 12 | #include <string> |
| 13 | #include <utility> |
| 14 | #include <vector> |
| 15 | |
| 16 | #include "base/base_export.h" |
[email protected] | b345c48 | 2013-08-30 18:00:39 | [diff] [blame] | 17 | #include "base/environment.h" |
avi | beced7c | 2015-12-24 06:47:59 | [diff] [blame] | 18 | #include "base/macros.h" |
rvargas | 6293e5b | 2014-12-01 22:53:09 | [diff] [blame] | 19 | #include "base/process/process.h" |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 20 | #include "base/process/process_handle.h" |
[email protected] | 7eb6bec6 | 2013-12-05 22:41:04 | [diff] [blame] | 21 | #include "base/strings/string_piece.h" |
avi | beced7c | 2015-12-24 06:47:59 | [diff] [blame] | 22 | #include "build/build_config.h" |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 23 | |
| 24 | #if defined(OS_POSIX) |
| 25 | #include "base/posix/file_descriptor_shuffle.h" |
| 26 | #elif defined(OS_WIN) |
| 27 | #include <windows.h> |
| 28 | #endif |
| 29 | |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 30 | namespace base { |
| 31 | |
[email protected] | 2f3b1cc | 2014-03-17 23:07:15 | [diff] [blame] | 32 | class CommandLine; |
| 33 | |
[email protected] | 991bd8a | 2013-12-12 18:45:45 | [diff] [blame] | 34 | #if defined(OS_WIN) |
| 35 | typedef std::vector<HANDLE> HandlesToInheritVector; |
| 36 | #endif |
| 37 | // TODO(viettrungluu): Only define this on POSIX? |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 38 | typedef std::vector<std::pair<int, int> > FileHandleMappingVector; |
| 39 | |
| 40 | // Options for launching a subprocess that are passed to LaunchProcess(). |
| 41 | // The default constructor constructs the object with default options. |
[email protected] | b345c48 | 2013-08-30 18:00:39 | [diff] [blame] | 42 | struct BASE_EXPORT LaunchOptions { |
rickyz | a0b860b | 2015-01-16 18:19:34 | [diff] [blame] | 43 | #if defined(OS_POSIX) |
| 44 | // Delegate to be run in between fork and exec in the subprocess (see |
| 45 | // pre_exec_delegate below) |
| 46 | class BASE_EXPORT PreExecDelegate { |
| 47 | public: |
| 48 | PreExecDelegate() {} |
| 49 | virtual ~PreExecDelegate() {} |
| 50 | |
| 51 | // Since this is to be run between fork and exec, and fork may have happened |
| 52 | // while multiple threads were running, this function needs to be async |
| 53 | // safe. |
| 54 | virtual void RunAsyncSafe() = 0; |
| 55 | |
| 56 | private: |
| 57 | DISALLOW_COPY_AND_ASSIGN(PreExecDelegate); |
| 58 | }; |
| 59 | #endif // defined(OS_POSIX) |
| 60 | |
[email protected] | b345c48 | 2013-08-30 18:00:39 | [diff] [blame] | 61 | LaunchOptions(); |
vmpstr | 7c787706 | 2016-02-18 22:12:24 | [diff] [blame] | 62 | LaunchOptions(const LaunchOptions&); |
[email protected] | b345c48 | 2013-08-30 18:00:39 | [diff] [blame] | 63 | ~LaunchOptions(); |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 64 | |
| 65 | // If true, wait for the process to complete. |
gab | 21691da | 2016-08-02 20:19:58 | [diff] [blame] | 66 | bool wait = false; |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 67 | |
sergeyu | 78205516 | 2016-04-06 08:57:59 | [diff] [blame] | 68 | // If not empty, change to this directory before executing the new process. |
| 69 | base::FilePath current_directory; |
| 70 | |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 71 | #if defined(OS_WIN) |
gab | 21691da | 2016-08-02 20:19:58 | [diff] [blame] | 72 | bool start_hidden = false; |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 73 | |
[email protected] | 991bd8a | 2013-12-12 18:45:45 | [diff] [blame] | 74 | // If non-null, inherit exactly the list of handles in this vector (these |
ananta | f265187 | 2016-06-16 22:21:02 | [diff] [blame] | 75 | // handles must be inheritable). |
gab | 21691da | 2016-08-02 20:19:58 | [diff] [blame] | 76 | HandlesToInheritVector* handles_to_inherit = nullptr; |
[email protected] | 991bd8a | 2013-12-12 18:45:45 | [diff] [blame] | 77 | |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 78 | // If true, the new process inherits handles from the parent. In production |
| 79 | // code this flag should be used only when running short-lived, trusted |
| 80 | // binaries, because open handles from other libraries and subsystems will |
| 81 | // leak to the child process, causing errors such as open socket hangs. |
[email protected] | 991bd8a | 2013-12-12 18:45:45 | [diff] [blame] | 82 | // Note: If |handles_to_inherit| is non-null, this flag is ignored and only |
ananta | f265187 | 2016-06-16 22:21:02 | [diff] [blame] | 83 | // those handles will be inherited. |
gab | 21691da | 2016-08-02 20:19:58 | [diff] [blame] | 84 | bool inherit_handles = false; |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 85 | |
[email protected] | 991bd8a | 2013-12-12 18:45:45 | [diff] [blame] | 86 | // If non-null, runs as if the user represented by the token had launched it. |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 87 | // Whether the application is visible on the interactive desktop depends on |
| 88 | // the token belonging to an interactive logon session. |
| 89 | // |
| 90 | // To avoid hard to diagnose problems, when specified this loads the |
| 91 | // environment variables associated with the user and if this operation fails |
| 92 | // the entire call fails as well. |
gab | 21691da | 2016-08-02 20:19:58 | [diff] [blame] | 93 | UserTokenHandle as_user = nullptr; |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 94 | |
| 95 | // If true, use an empty string for the desktop name. |
gab | 21691da | 2016-08-02 20:19:58 | [diff] [blame] | 96 | bool empty_desktop_name = false; |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 97 | |
[email protected] | 991bd8a | 2013-12-12 18:45:45 | [diff] [blame] | 98 | // If non-null, launches the application in that job object. The process will |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 99 | // be terminated immediately and LaunchProcess() will fail if assignment to |
| 100 | // the job object fails. |
gab | 21691da | 2016-08-02 20:19:58 | [diff] [blame] | 101 | HANDLE job_handle = nullptr; |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 102 | |
| 103 | // Handles for the redirection of stdin, stdout and stderr. The handles must |
| 104 | // be inheritable. Caller should either set all three of them or none (i.e. |
| 105 | // there is no way to redirect stderr without redirecting stdin). The |
| 106 | // |inherit_handles| flag must be set to true when redirecting stdio stream. |
gab | 21691da | 2016-08-02 20:19:58 | [diff] [blame] | 107 | HANDLE stdin_handle = nullptr; |
| 108 | HANDLE stdout_handle = nullptr; |
| 109 | HANDLE stderr_handle = nullptr; |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 110 | |
| 111 | // If set to true, ensures that the child process is launched with the |
| 112 | // CREATE_BREAKAWAY_FROM_JOB flag which allows it to breakout of the parent |
| 113 | // job if any. |
gab | 21691da | 2016-08-02 20:19:58 | [diff] [blame] | 114 | bool force_breakaway_from_job_ = false; |
| 115 | #else // !defined(OS_WIN) |
[email protected] | 6f38c134 | 2014-06-05 22:16:13 | [diff] [blame] | 116 | // Set/unset environment variables. These are applied on top of the parent |
| 117 | // process environment. Empty (the default) means to inherit the same |
| 118 | // environment. See AlterEnvironment(). |
[email protected] | b345c48 | 2013-08-30 18:00:39 | [diff] [blame] | 119 | EnvironmentMap environ; |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 120 | |
[email protected] | 6f38c134 | 2014-06-05 22:16:13 | [diff] [blame] | 121 | // Clear the environment for the new process before processing changes from |
| 122 | // |environ|. |
gab | 21691da | 2016-08-02 20:19:58 | [diff] [blame] | 123 | bool clear_environ = false; |
[email protected] | 6f38c134 | 2014-06-05 22:16:13 | [diff] [blame] | 124 | |
[email protected] | 991bd8a | 2013-12-12 18:45:45 | [diff] [blame] | 125 | // If non-null, remap file descriptors according to the mapping of |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 126 | // src fd->dest fd to propagate FDs into the child process. |
| 127 | // This pointer is owned by the caller and must live through the |
| 128 | // call to LaunchProcess(). |
gab | 21691da | 2016-08-02 20:19:58 | [diff] [blame] | 129 | const FileHandleMappingVector* fds_to_remap = nullptr; |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 130 | |
| 131 | // Each element is an RLIMIT_* constant that should be raised to its |
| 132 | // rlim_max. This pointer is owned by the caller and must live through |
| 133 | // the call to LaunchProcess(). |
gab | 21691da | 2016-08-02 20:19:58 | [diff] [blame] | 134 | const std::vector<int>* maximize_rlimits = nullptr; |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 135 | |
| 136 | // If true, start the process in a new process group, instead of |
| 137 | // inheriting the parent's process group. The pgid of the child process |
| 138 | // will be the same as its pid. |
gab | 21691da | 2016-08-02 20:19:58 | [diff] [blame] | 139 | bool new_process_group = false; |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 140 | |
| 141 | #if defined(OS_LINUX) |
| 142 | // If non-zero, start the process using clone(), using flags as provided. |
rickyz | f1eb9cc | 2015-01-13 22:59:48 | [diff] [blame] | 143 | // Unlike in clone, clone_flags may not contain a custom termination signal |
| 144 | // that is sent to the parent when the child dies. The termination signal will |
| 145 | // always be set to SIGCHLD. |
gab | 21691da | 2016-08-02 20:19:58 | [diff] [blame] | 146 | int clone_flags = 0; |
[email protected] | d078691 | 2014-04-09 20:06:26 | [diff] [blame] | 147 | |
| 148 | // By default, child processes will have the PR_SET_NO_NEW_PRIVS bit set. If |
| 149 | // true, then this bit will not be set in the new child process. |
gab | 21691da | 2016-08-02 20:19:58 | [diff] [blame] | 150 | bool allow_new_privs = false; |
phajdan.jr | ed5ed8f4 | 2015-03-13 21:40:13 | [diff] [blame] | 151 | |
| 152 | // Sets parent process death signal to SIGKILL. |
gab | 21691da | 2016-08-02 20:19:58 | [diff] [blame] | 153 | bool kill_on_parent_death = false; |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 154 | #endif // defined(OS_LINUX) |
| 155 | |
rickyz | a0b860b | 2015-01-16 18:19:34 | [diff] [blame] | 156 | #if defined(OS_POSIX) |
rkjnsn | 732f03d | 2016-10-03 17:59:54 | [diff] [blame^] | 157 | // If not empty, launch the specified executable instead of |
| 158 | // cmdline.GetProgram(). This is useful when it is necessary to pass a custom |
| 159 | // argv[0]. |
| 160 | base::FilePath real_path; |
| 161 | |
rickyz | a0b860b | 2015-01-16 18:19:34 | [diff] [blame] | 162 | // If non-null, a delegate to be run immediately prior to executing the new |
| 163 | // program in the child process. |
| 164 | // |
| 165 | // WARNING: If LaunchProcess is called in the presence of multiple threads, |
| 166 | // code running in this delegate essentially needs to be async-signal safe |
| 167 | // (see man 7 signal for a list of allowed functions). |
gab | 21691da | 2016-08-02 20:19:58 | [diff] [blame] | 168 | PreExecDelegate* pre_exec_delegate = nullptr; |
rickyz | a0b860b | 2015-01-16 18:19:34 | [diff] [blame] | 169 | #endif // defined(OS_POSIX) |
| 170 | |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 171 | #if defined(OS_CHROMEOS) |
| 172 | // If non-negative, the specified file descriptor will be set as the launched |
| 173 | // process' controlling terminal. |
gab | 21691da | 2016-08-02 20:19:58 | [diff] [blame] | 174 | int ctrl_terminal_fd = -1; |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 175 | #endif // defined(OS_CHROMEOS) |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 176 | #endif // !defined(OS_WIN) |
| 177 | }; |
| 178 | |
| 179 | // Launch a process via the command line |cmdline|. |
| 180 | // See the documentation of LaunchOptions for details on |options|. |
| 181 | // |
rvargas | c40cfc6 | 2014-12-02 02:46:36 | [diff] [blame] | 182 | // Returns a valid Process upon success. |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 183 | // |
| 184 | // Unix-specific notes: |
| 185 | // - All file descriptors open in the parent process will be closed in the |
| 186 | // child process except for any preserved by options::fds_to_remap, and |
| 187 | // stdin, stdout, and stderr. If not remapped by options::fds_to_remap, |
| 188 | // stdin is reopened as /dev/null, and the child is allowed to inherit its |
| 189 | // parent's stdout and stderr. |
| 190 | // - If the first argument on the command line does not contain a slash, |
| 191 | // PATH will be searched. (See man execvp.) |
rvargas | c40cfc6 | 2014-12-02 02:46:36 | [diff] [blame] | 192 | BASE_EXPORT Process LaunchProcess(const CommandLine& cmdline, |
| 193 | const LaunchOptions& options); |
| 194 | |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 195 | #if defined(OS_WIN) |
| 196 | // Windows-specific LaunchProcess that takes the command line as a |
| 197 | // string. Useful for situations where you need to control the |
| 198 | // command line arguments directly, but prefer the CommandLine version |
| 199 | // if launching Chrome itself. |
| 200 | // |
| 201 | // The first command line argument should be the path to the process, |
| 202 | // and don't forget to quote it. |
| 203 | // |
| 204 | // Example (including literal quotes) |
| 205 | // cmdline = "c:\windows\explorer.exe" -foo "c:\bar\" |
rvargas | 6181277 | 2014-12-05 03:14:54 | [diff] [blame] | 206 | BASE_EXPORT Process LaunchProcess(const string16& cmdline, |
| 207 | const LaunchOptions& options); |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 208 | |
[email protected] | fa01e47 | 2014-02-11 14:45:35 | [diff] [blame] | 209 | // Launches a process with elevated privileges. This does not behave exactly |
| 210 | // like LaunchProcess as it uses ShellExecuteEx instead of CreateProcess to |
| 211 | // create the process. This means the process will have elevated privileges |
rvargas | 6293e5b | 2014-12-01 22:53:09 | [diff] [blame] | 212 | // and thus some common operations like OpenProcess will fail. Currently the |
| 213 | // only supported LaunchOptions are |start_hidden| and |wait|. |
| 214 | BASE_EXPORT Process LaunchElevatedProcess(const CommandLine& cmdline, |
| 215 | const LaunchOptions& options); |
[email protected] | fa01e47 | 2014-02-11 14:45:35 | [diff] [blame] | 216 | |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 217 | #elif defined(OS_POSIX) |
| 218 | // A POSIX-specific version of LaunchProcess that takes an argv array |
| 219 | // instead of a CommandLine. Useful for situations where you need to |
| 220 | // control the command line arguments directly, but prefer the |
| 221 | // CommandLine version if launching Chrome itself. |
rvargas | 02a9986 | 2015-01-10 00:46:12 | [diff] [blame] | 222 | BASE_EXPORT Process LaunchProcess(const std::vector<std::string>& argv, |
| 223 | const LaunchOptions& options); |
| 224 | |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 225 | // Close all file descriptors, except those which are a destination in the |
| 226 | // given multimap. Only call this function in a child process where you know |
| 227 | // that there aren't any other threads. |
| 228 | BASE_EXPORT void CloseSuperfluousFds(const InjectiveMultimap& saved_map); |
| 229 | #endif // defined(OS_POSIX) |
| 230 | |
| 231 | #if defined(OS_WIN) |
[email protected] | 15db082 | 2013-09-13 21:24:47 | [diff] [blame] | 232 | // Set |job_object|'s JOBOBJECT_EXTENDED_LIMIT_INFORMATION |
| 233 | // BasicLimitInformation.LimitFlags to |limit_flags|. |
| 234 | BASE_EXPORT bool SetJobObjectLimitFlags(HANDLE job_object, DWORD limit_flags); |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 235 | |
| 236 | // Output multi-process printf, cout, cerr, etc to the cmd.exe console that ran |
| 237 | // chrome. This is not thread-safe: only call from main thread. |
jam | 79dc59a | 2015-08-17 03:38:16 | [diff] [blame] | 238 | BASE_EXPORT void RouteStdioToConsole(bool create_console_if_not_found); |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 239 | #endif // defined(OS_WIN) |
| 240 | |
| 241 | // Executes the application specified by |cl| and wait for it to exit. Stores |
| 242 | // the output (stdout) in |output|. Redirects stderr to /dev/null. Returns true |
| 243 | // on success (application launched and exited cleanly, with exit code |
| 244 | // indicating success). |
| 245 | BASE_EXPORT bool GetAppOutput(const CommandLine& cl, std::string* output); |
| 246 | |
jam | 79dc59a | 2015-08-17 03:38:16 | [diff] [blame] | 247 | // Like GetAppOutput, but also includes stderr. |
| 248 | BASE_EXPORT bool GetAppOutputAndError(const CommandLine& cl, |
| 249 | std::string* output); |
| 250 | |
[email protected] | 7eb6bec6 | 2013-12-05 22:41:04 | [diff] [blame] | 251 | #if defined(OS_WIN) |
| 252 | // A Windows-specific version of GetAppOutput that takes a command line string |
| 253 | // instead of a CommandLine object. Useful for situations where you need to |
| 254 | // control the command line arguments directly. |
| 255 | BASE_EXPORT bool GetAppOutput(const StringPiece16& cl, std::string* output); |
| 256 | #endif |
| 257 | |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 258 | #if defined(OS_POSIX) |
| 259 | // A POSIX-specific version of GetAppOutput that takes an argv array |
| 260 | // instead of a CommandLine. Useful for situations where you need to |
| 261 | // control the command line arguments directly. |
| 262 | BASE_EXPORT bool GetAppOutput(const std::vector<std::string>& argv, |
| 263 | std::string* output); |
| 264 | |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 265 | // A version of |GetAppOutput()| which also returns the exit code of the |
| 266 | // executed command. Returns true if the application runs and exits cleanly. If |
| 267 | // this is the case the exit code of the application is available in |
| 268 | // |*exit_code|. |
| 269 | BASE_EXPORT bool GetAppOutputWithExitCode(const CommandLine& cl, |
| 270 | std::string* output, int* exit_code); |
| 271 | #endif // defined(OS_POSIX) |
| 272 | |
| 273 | // If supported on the platform, and the user has sufficent rights, increase |
| 274 | // the current process's scheduling priority to a high priority. |
| 275 | BASE_EXPORT void RaiseProcessToHighPriority(); |
| 276 | |
| 277 | #if defined(OS_MACOSX) |
| 278 | // Restore the default exception handler, setting it to Apple Crash Reporter |
| 279 | // (ReportCrash). When forking and execing a new process, the child will |
| 280 | // inherit the parent's exception ports, which may be set to the Breakpad |
| 281 | // instance running inside the parent. The parent's Breakpad instance should |
| 282 | // not handle the child's exceptions. Calling RestoreDefaultExceptionHandler |
| 283 | // in the child after forking will restore the standard exception handler. |
| 284 | // See http://crbug.com/20371/ for more details. |
| 285 | void RestoreDefaultExceptionHandler(); |
| 286 | #endif // defined(OS_MACOSX) |
| 287 | |
[email protected] | d078691 | 2014-04-09 20:06:26 | [diff] [blame] | 288 | // Creates a LaunchOptions object suitable for launching processes in a test |
| 289 | // binary. This should not be called in production/released code. |
| 290 | BASE_EXPORT LaunchOptions LaunchOptionsForTest(); |
| 291 | |
rickyz | 179aeb7b | 2015-06-19 00:18:46 | [diff] [blame] | 292 | #if defined(OS_LINUX) || defined(OS_NACL_NONSFI) |
rickyz | a2f6d74 | 2015-01-21 21:57:34 | [diff] [blame] | 293 | // A wrapper for clone with fork-like behavior, meaning that it returns the |
| 294 | // child's pid in the parent and 0 in the child. |flags|, |ptid|, and |ctid| are |
| 295 | // as in the clone system call (the CLONE_VM flag is not supported). |
| 296 | // |
| 297 | // This function uses the libc clone wrapper (which updates libc's pid cache) |
| 298 | // internally, so callers may expect things like getpid() to work correctly |
| 299 | // after in both the child and parent. An exception is when this code is run |
| 300 | // under Valgrind. Valgrind does not support the libc clone wrapper, so the libc |
| 301 | // pid cache may be incorrect after this function is called under Valgrind. |
| 302 | // |
| 303 | // As with fork(), callers should be extremely careful when calling this while |
| 304 | // multiple threads are running, since at the time the fork happened, the |
| 305 | // threads could have been in any state (potentially holding locks, etc.). |
| 306 | // Callers should most likely call execve() in the child soon after calling |
| 307 | // this. |
| 308 | BASE_EXPORT pid_t ForkWithFlags(unsigned long flags, pid_t* ptid, pid_t* ctid); |
| 309 | #endif |
| 310 | |
[email protected] | 300c386 | 2013-07-17 18:12:40 | [diff] [blame] | 311 | } // namespace base |
| 312 | |
| 313 | #endif // BASE_PROCESS_LAUNCH_H_ |