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