1 // Copyright 2010-2018, Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 // * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 #include "ipc/process_watch_dog.h"
31
32 #ifdef OS_WIN
33 #include <windows.h>
34 #else
35 #include <signal.h>
36 #include <errno.h>
37 #endif
38
39 #include "base/logging.h"
40 #include "base/mutex.h"
41 #include "base/port.h"
42 #include "base/scoped_handle.h"
43 #include "base/util.h"
44
45 namespace mozc {
46
47 #ifdef OS_WIN
ProcessWatchDog()48 ProcessWatchDog::ProcessWatchDog()
49 : event_(::CreateEventW(NULL, TRUE, FALSE, NULL)),
50 process_id_(UnknownProcessID),
51 thread_id_(UnknownThreadID),
52 timeout_(-1),
53 is_finished_(false),
54 mutex_(new Mutex) {
55 if (event_.get() == NULL) {
56 LOG(ERROR) << "::CreateEvent() failed.";
57 return;
58 }
59 Thread::Start("WatchDog"); // start
60 }
61
~ProcessWatchDog()62 ProcessWatchDog::~ProcessWatchDog() {
63 is_finished_ = true; // set the flag to terminate the thread
64
65 if (event_.get() != NULL) {
66 ::SetEvent(event_.get()); // wake up WaitForMultipleObjects
67 }
68
69 Join(); // wait for the thread
70 }
71
SetID(ProcessID process_id,ThreadID thread_id,int timeout)72 bool ProcessWatchDog::SetID(ProcessID process_id, ThreadID thread_id,
73 int timeout) {
74 if (event_.get() == NULL) {
75 LOG(ERROR) << "event is NULL";
76 return false;
77 }
78
79 if (process_id_ == process_id && thread_id_ == thread_id &&
80 timeout_ == timeout) {
81 // don't repeat if we are checking the same thread/process
82 return true;
83 }
84
85 // rewrite the valeus
86 {
87 scoped_lock l(mutex_.get());
88 process_id_ = process_id;
89 thread_id_ = thread_id;
90 timeout_ = timeout;
91 }
92
93 // wake up WaitForMultipleObjects
94 ::SetEvent(event_.get());
95
96 return true;
97 }
98
Run()99 void ProcessWatchDog::Run() {
100 while (!is_finished_) {
101 ScopedHandle process_handle;
102 ScopedHandle thread_handle;
103 int timeout = -1;
104
105 // read the current ids/timeout
106 {
107 scoped_lock l(mutex_.get());
108
109 if (process_id_ != UnknownProcessID) {
110 const HANDLE handle = ::OpenProcess(SYNCHRONIZE, FALSE, process_id_);
111 const DWORD error = ::GetLastError();
112 process_handle.reset(handle);
113 if (process_handle.get() == NULL) {
114 LOG(ERROR) << "OpenProcess failed: " << process_id_ << " " << error;
115 switch (error) {
116 case ERROR_ACCESS_DENIED:
117 Signaled(ProcessWatchDog::PROCESS_ACCESS_DENIED_SIGNALED);
118 break;
119 case ERROR_INVALID_PARAMETER:
120 Signaled(ProcessWatchDog::PROCESS_NOT_FOUND_SIGNALED);
121 break;
122 default:
123 Signaled(ProcessWatchDog::PROCESS_ERROR_SIGNALED);
124 break;
125 }
126 }
127 }
128
129 if (thread_id_ != UnknownThreadID) {
130 const HANDLE handle = ::OpenThread(SYNCHRONIZE, FALSE, thread_id_);
131 const DWORD error = ::GetLastError();
132 thread_handle.reset(handle);
133 if (thread_handle.get() == NULL) {
134 LOG(ERROR) << "OpenThread failed: " << thread_id_ << " " << error;
135 switch (error) {
136 case ERROR_ACCESS_DENIED:
137 Signaled(ProcessWatchDog::THREAD_ACCESS_DENIED_SIGNALED);
138 break;
139 case ERROR_INVALID_PARAMETER:
140 Signaled(ProcessWatchDog::THREAD_NOT_FOUND_SIGNALED);
141 break;
142 default:
143 Signaled(ProcessWatchDog::THREAD_ERROR_SIGNALED);
144 break;
145 }
146 }
147 }
148
149 timeout = timeout_;
150 if (timeout_ < 0) {
151 timeout = INFINITE;
152 }
153
154 process_id_ = UnknownProcessID;
155 thread_id_ = UnknownThreadID;
156 timeout_ = -1;
157 }
158
159 SignalType types[3];
160 HANDLE handles[3] = {};
161
162 // set event
163 handles[0] = event_.get();
164
165 // set handles
166 DWORD size = 1;
167 if (process_handle.get() != NULL) {
168 VLOG(2) << "Inserting process handle";
169 handles[size] = process_handle.get();
170 types[size] = ProcessWatchDog::PROCESS_SIGNALED;
171 ++size;
172 }
173
174 if (thread_handle.get() != NULL) {
175 VLOG(2) << "Inserting thread handle";
176 handles[size] = thread_handle.get();
177 types[size] = ProcessWatchDog::THREAD_SIGNALED;
178 ++size;
179 }
180
181 const DWORD result = ::WaitForMultipleObjects(
182 size, handles, FALSE, timeout);
183 SignalType result_type = ProcessWatchDog::UNKNOWN_SIGNALED;
184 switch (result) {
185 case WAIT_OBJECT_0:
186 case WAIT_ABANDONED_0:
187 VLOG(2) << "event is signaled";
188 ::ResetEvent(event_.get()); // reset event to wait for the new request
189 break;
190 case WAIT_OBJECT_0 + 1:
191 case WAIT_ABANDONED_0 + 1:
192 VLOG(2) << "handle 1 is signaled";
193 result_type = types[1];
194 break;
195 case WAIT_OBJECT_0 + 2:
196 case WAIT_ABANDONED_0 + 2:
197 VLOG(2) << "handle 2 is signaled";
198 result_type = types[2];
199 break;
200 case WAIT_TIMEOUT:
201 VLOG(2) << "timeout is signaled";
202 result_type = ProcessWatchDog::TIMEOUT_SIGNALED;
203 break;
204 default:
205 LOG(ERROR) << "WaitForMultipleObjects() failed: " << GetLastError();
206 break;
207 }
208
209 if (result_type != ProcessWatchDog::UNKNOWN_SIGNALED) {
210 VLOG(1) << "Sending signal: " << static_cast<int>(result_type);
211 Signaled(result_type); // call signal handler
212 }
213 }
214 }
215
216 #else // OS_WIN
217
218 ProcessWatchDog::ProcessWatchDog()
219 : process_id_(UnknownProcessID),
220 thread_id_(UnknownProcessID),
221 is_finished_(false),
222 mutex_(new Mutex) {
223 Thread::Start("WatchDog");
224 }
225
226 ProcessWatchDog::~ProcessWatchDog() {
227 is_finished_ = true;
228 Join();
229 }
230
231 bool ProcessWatchDog::SetID(ProcessWatchDog::ProcessID process_id,
232 ProcessWatchDog::ThreadID thread_id,
233 int timeout) {
234 if (process_id_ == process_id && thread_id_ == thread_id &&
235 timeout_ == timeout) {
236 // don't repeat if we are checking the same thread/process
237 return true;
238 }
239
240 LOG_IF(ERROR, thread_id != UnknownThreadID)
241 << "Linux/Mac don't allow to capture ThreadID";
242 LOG_IF(ERROR, timeout > 0) << "timeout is not supported";
243
244 if (::kill(process_id, 0) != 0) {
245 if (errno == ESRCH) {
246 // emit PROCESS_NOT_FOUND_SIGNALED immediately,
247 // if the process id is not found NOW.
248 Signaled(ProcessWatchDog::PROCESS_NOT_FOUND_SIGNALED);
249 process_id = UnknownProcessID;
250 }
251 }
252
253 {
254 scoped_lock l(mutex_.get());
255 process_id_ = process_id;
256 thread_id_ = thread_id;
257 timeout_ = -1;
258 }
259
260 return true;
261 }
262
263 void ProcessWatchDog::Run() {
264 // Polling-based watch-dog.
265 // Unlike WaitForMultipleObjects on Windows, no event-driven API seems to be
266 // available on Linux.
267 // NOTE In theory, there may possibility that some other process
268 // reuse same process id in 250ms or write to is_finished_ stays
269 // forever in another CPU's local cache.
270 // TODO(team): use kqueue with EVFILT_PROC/NOTE_EXIT for Mac.
271 while (!is_finished_) {
272 Util::Sleep(250);
273 if (process_id_ == UnknownProcessID) {
274 continue;
275 }
276 if (::kill(process_id_, 0) != 0) {
277 if (errno == EPERM) {
278 Signaled(ProcessWatchDog::PROCESS_ACCESS_DENIED_SIGNALED);
279 } else if (errno == ESRCH) {
280 // Since we are polling the process by NULL signal,
281 // it is essentially impossible to tell the process is not found
282 // or terminated.
283 Signaled(ProcessWatchDog::PROCESS_SIGNALED);
284 } else {
285 Signaled(ProcessWatchDog::PROCESS_ERROR_SIGNALED);
286 }
287 scoped_lock l(mutex_.get());
288 process_id_ = UnknownProcessID;
289 }
290 }
291 }
292 #endif // OS_WIN
293
294 } // namespace mozc
295