1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chromeos/process_proxy/process_proxy.h"
10 #include "base/bind.h"
11 #include "base/command_line.h"
12 #include "base/files/file_util.h"
13 #include "base/location.h"
14 #include "base/logging.h"
15 #include "base/posix/eintr_wrapper.h"
16 #include "base/process/launch.h"
17 #include "base/process/process.h"
18 #include "base/single_thread_task_runner.h"
19 #include "base/thread_task_runner_handle.h"
20 #include "base/threading/thread.h"
21 #include "third_party/cros_system_api/switches/chrome_switches.h"
30 enum PseudoTerminalFd
{
35 const int kInvalidFd
= -1;
41 ProcessProxy::ProcessProxy(): process_launched_(false),
43 watcher_started_(false) {
44 // Set pipes to initial, invalid value so we can easily know if a pipe was
49 bool ProcessProxy::Open(const std::string
& command
, pid_t
* pid
) {
50 if (process_launched_
)
53 if (!CreatePseudoTerminalPair(pt_pair_
)) {
57 process_launched_
= LaunchProcess(command
, pt_pair_
[PT_SLAVE_FD
], &pid_
);
59 if (process_launched_
) {
60 // We won't need these anymore. These will be used by the launched process.
61 CloseFd(&pt_pair_
[PT_SLAVE_FD
]);
63 LOG(WARNING
) << "Process launched: " << pid_
;
65 CloseFdPair(pt_pair_
);
67 return process_launched_
;
70 bool ProcessProxy::StartWatchingOnThread(
71 base::Thread
* watch_thread
,
72 const ProcessOutputCallback
& callback
) {
73 DCHECK(process_launched_
);
76 if (pipe(shutdown_pipe_
) ||
77 !ProcessOutputWatcher::VerifyFileDescriptor(
78 shutdown_pipe_
[PIPE_END_READ
])) {
82 // We give ProcessOutputWatcher a copy of master to make life easier during
84 // TODO(tbarzic): improve fd managment.
85 int master_copy
= HANDLE_EINTR(dup(pt_pair_
[PT_MASTER_FD
]));
86 if (!ProcessOutputWatcher::VerifyFileDescriptor(master_copy
))
91 callback_runner_
= base::ThreadTaskRunnerHandle::Get();
93 // This object will delete itself once watching is stopped.
94 // It also takes ownership of the passed fds.
95 ProcessOutputWatcher
* output_watcher
=
96 new ProcessOutputWatcher(master_copy
,
97 shutdown_pipe_
[PIPE_END_READ
],
98 base::Bind(&ProcessProxy::OnProcessOutput
,
101 // Output watcher took ownership of the read end of shutdown pipe.
102 shutdown_pipe_
[PIPE_END_READ
] = -1;
104 // |watch| thread is blocked by |output_watcher| from now on.
105 watch_thread
->task_runner()->PostTask(
106 FROM_HERE
, base::Bind(&ProcessOutputWatcher::Start
,
107 base::Unretained(output_watcher
)));
108 watcher_started_
= true;
112 void ProcessProxy::OnProcessOutput(ProcessOutputType type
,
113 const std::string
& output
) {
114 if (!callback_runner_
.get())
117 callback_runner_
->PostTask(
119 base::Bind(&ProcessProxy::CallOnProcessOutputCallback
,
120 this, type
, output
));
123 void ProcessProxy::CallOnProcessOutputCallback(ProcessOutputType type
,
124 const std::string
& output
) {
125 // We may receive some output even after Close was called (crosh process does
126 // not have to quit instantly, or there may be some trailing data left in
127 // output stream fds). In that case owner of the callback may be gone so we
128 // don't want to send it anything. |callback_set_| is reset when this gets
131 callback_
.Run(type
, output
);
134 bool ProcessProxy::StopWatching() {
135 if (!watcher_started_
)
137 // Signal Watcher that we are done. We use self-pipe trick to unblock watcher.
138 // Anything may be written to the pipe.
139 const char message
[] = "q";
140 return base::WriteFileDescriptor(shutdown_pipe_
[PIPE_END_WRITE
],
141 message
, sizeof(message
));
144 void ProcessProxy::Close() {
145 if (!process_launched_
)
148 process_launched_
= false;
149 callback_set_
= false;
150 callback_
= ProcessOutputCallback();
151 callback_runner_
= NULL
;
153 base::Process process
= base::Process::DeprecatedGetProcessFromHandle(pid_
);
154 process
.Terminate(0, true /* wait */);
156 // TODO(tbarzic): What if this fails?
162 bool ProcessProxy::Write(const std::string
& text
) {
163 if (!process_launched_
)
166 // We don't want to write '\0' to the pipe.
167 size_t data_size
= text
.length() * sizeof(*text
.c_str());
168 return base::WriteFileDescriptor(
169 pt_pair_
[PT_MASTER_FD
], text
.c_str(), data_size
);
172 bool ProcessProxy::OnTerminalResize(int width
, int height
) {
173 if (width
< 0 || height
< 0)
179 // Number of columns.
182 return (HANDLE_EINTR(ioctl(pt_pair_
[PT_MASTER_FD
], TIOCSWINSZ
, &ws
)) != -1);
185 ProcessProxy::~ProcessProxy() {
186 // In case watcher did not started, we may get deleted without calling Close.
187 // In that case we have to clean up created pipes. If watcher had been
188 // started, there will be a callback with our reference owned by
189 // process_output_watcher until Close is called, so we know Close has been
190 // called by now (and pipes have been cleaned).
191 if (!watcher_started_
)
195 bool ProcessProxy::CreatePseudoTerminalPair(int *pt_pair
) {
196 ClearFdPair(pt_pair
);
199 pt_pair
[PT_MASTER_FD
] = HANDLE_EINTR(posix_openpt(O_RDWR
| O_NOCTTY
));
200 if (pt_pair
[PT_MASTER_FD
] == -1)
203 if (grantpt(pt_pair_
[PT_MASTER_FD
]) != 0 ||
204 unlockpt(pt_pair_
[PT_MASTER_FD
]) != 0) {
205 CloseFd(&pt_pair
[PT_MASTER_FD
]);
208 char* slave_name
= NULL
;
209 // Per man page, slave_name must not be freed.
210 slave_name
= ptsname(pt_pair_
[PT_MASTER_FD
]);
212 pt_pair_
[PT_SLAVE_FD
] = HANDLE_EINTR(open(slave_name
, O_RDWR
| O_NOCTTY
));
214 if (pt_pair_
[PT_SLAVE_FD
] == -1) {
215 CloseFdPair(pt_pair
);
222 bool ProcessProxy::LaunchProcess(const std::string
& command
, int slave_fd
,
224 // Redirect crosh process' output and input so we can read it.
225 base::FileHandleMappingVector fds_mapping
;
226 fds_mapping
.push_back(std::make_pair(slave_fd
, STDIN_FILENO
));
227 fds_mapping
.push_back(std::make_pair(slave_fd
, STDOUT_FILENO
));
228 fds_mapping
.push_back(std::make_pair(slave_fd
, STDERR_FILENO
));
229 base::LaunchOptions options
;
230 // Do not set NO_NEW_PRIVS on processes if the system is in dev-mode. This
231 // permits sudo in the crosh shell when in developer mode.
232 options
.allow_new_privs
= base::CommandLine::ForCurrentProcess()->
233 HasSwitch(chromeos::switches::kSystemInDevMode
);
234 options
.fds_to_remap
= &fds_mapping
;
235 options
.ctrl_terminal_fd
= slave_fd
;
236 options
.environ
["TERM"] = "xterm";
238 // Launch the process.
239 base::Process process
=
240 base::LaunchProcess(base::CommandLine(base::FilePath(command
)), options
);
242 // TODO(rvargas) crbug/417532: This is somewhat wrong but the interface of
243 // Open vends pid_t* so ownership is quite vague anyway, and Process::Close
244 // doesn't do much in POSIX.
245 *pid
= process
.Pid();
246 return process
.IsValid();
249 void ProcessProxy::CloseAllFdPairs() {
250 CloseFdPair(pt_pair_
);
251 CloseFdPair(shutdown_pipe_
);
254 void ProcessProxy::CloseFdPair(int* pipe
) {
255 CloseFd(&(pipe
[PIPE_END_READ
]));
256 CloseFd(&(pipe
[PIPE_END_WRITE
]));
259 void ProcessProxy::CloseFd(int* fd
) {
260 if (*fd
!= kInvalidFd
) {
261 if (IGNORE_EINTR(close(*fd
)) != 0)
262 DPLOG(WARNING
) << "close fd failed.";
267 void ProcessProxy::ClearAllFdPairs() {
268 ClearFdPair(pt_pair_
);
269 ClearFdPair(shutdown_pipe_
);
272 void ProcessProxy::ClearFdPair(int* pipe
) {
273 pipe
[PIPE_END_READ
] = kInvalidFd
;
274 pipe
[PIPE_END_WRITE
] = kInvalidFd
;
277 } // namespace chromeos