Apache Mesos
killtree.hpp
Go to the documentation of this file.
1 // Licensed under the Apache License, Version 2.0 (the "License");
2 // you may not use this file except in compliance with the License.
3 // You may obtain a copy of the License at
4 //
5 // http://www.apache.org/licenses/LICENSE-2.0
6 //
7 // Unless required by applicable law or agreed to in writing, software
8 // distributed under the License is distributed on an "AS IS" BASIS,
9 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 // See the License for the specific language governing permissions and
11 // limitations under the License.
12 
13 #ifndef __STOUT_OS_POSIX_KILLTREE_HPP__
14 #define __STOUT_OS_POSIX_KILLTREE_HPP__
15 
16 #include <stdlib.h>
17 #include <unistd.h>
18 
19 #include <list>
20 #include <queue>
21 #include <set>
22 
23 #include <stout/check.hpp>
24 #include <stout/os.hpp>
25 
26 #include <stout/os/pstree.hpp>
27 
28 
29 namespace os {
30 
31 // Forward declarations from os.hpp.
32 inline std::set<pid_t> children(pid_t, const std::list<Process>&, bool);
34 inline Option<Process> process(pid_t, const std::list<Process>&);
37  const std::set<pid_t>&,
38  const std::list<Process>&);
39 
40 
41 // Sends a signal to a process tree rooted at the specified pid.
42 // If groups is true, this also sends the signal to all encountered
43 // process groups.
44 // If sessions is true, this also sends the signal to all encountered
45 // process sessions.
46 // Note that processes of the group and session of the parent of the
47 // root process is not included unless they are part of the root
48 // process tree.
49 // Note that if the process 'pid' has exited we'll signal the process
50 // tree(s) rooted at pids in the group or session led by the process
51 // if groups = true or sessions = true, respectively.
52 // Returns the process trees that were successfully or unsuccessfully
53 // signaled. Note that the process trees can be stringified.
54 // TODO(benh): Allow excluding the root pid from stopping, killing,
55 // and continuing so as to provide a means for expressing "kill all of
56 // my children". This is non-trivial because of the current
57 // implementation.
59  pid_t pid,
60  int signal,
61  bool groups = false,
62  bool sessions = false)
63 {
65 
66  if (processes.isError()) {
67  return Error(processes.error());
68  }
69 
70  Result<Process> process = os::process(pid, processes.get());
71 
72  std::queue<pid_t> queue;
73 
74  // If the root process has already terminated we'll add in any pids
75  // that are in the process group originally led by pid or in the
76  // session originally led by pid, if instructed.
77  if (process.isNone()) {
78  foreach (const Process& _process, processes.get()) {
79  if (groups && _process.group == pid) {
80  queue.push(_process.pid);
81  } else if (sessions &&
82  _process.session.isSome() &&
83  _process.session.get() == pid) {
84  queue.push(_process.pid);
85  }
86  }
87 
88  // Root process is not running and no processes found in the
89  // process group or session so nothing we can do.
90  if (queue.empty()) {
91  return std::list<ProcessTree>();
92  }
93  } else {
94  // Start the traversal from pid as the root.
95  queue.push(pid);
96  }
97 
98  struct {
99  std::set<pid_t> pids;
100  std::set<pid_t> groups;
101  std::set<pid_t> sessions;
102  std::list<Process> processes;
103  } visited;
104 
105  // If we are following groups and/or sessions then we try and make
106  // the group and session of the parent process "already visited" so
107  // that we don't kill "up the tree". This can only be done if the
108  // process is present.
109  if (process.isSome() && (groups || sessions)) {
110  Option<Process> parent =
111  os::process(process.get().parent, processes.get());
112 
113  if (parent.isSome()) {
114  if (groups) {
115  visited.groups.insert(parent.get().group);
116  }
117  if (sessions && parent.get().session.isSome()) {
118  visited.sessions.insert(parent.get().session.get());
119  }
120  }
121  }
122 
123  while (!queue.empty()) {
124  pid_t pid = queue.front();
125  queue.pop();
126 
127  if (visited.pids.count(pid) != 0) {
128  continue;
129  }
130 
131  // Make sure this process still exists.
132  process = os::process(pid);
133 
134  if (process.isError()) {
135  return Error(process.error());
136  } else if (process.isNone()) {
137  continue;
138  }
139 
140  // Stop the process to keep it from forking while we are killing
141  // it since a forked child might get re-parented by init and
142  // become impossible to find.
143  kill(pid, SIGSTOP);
144 
145  visited.pids.insert(pid);
146  visited.processes.push_back(process.get());
147 
148  // Now refresh the process list knowing that the current process
149  // can't fork any more children (since it's stopped).
150  processes = os::processes();
151 
152  if (processes.isError()) {
153  return Error(processes.error());
154  }
155 
156  // Enqueue the children for visiting.
157  foreach (pid_t child, os::children(pid, processes.get(), false)) {
158  queue.push(child);
159  }
160 
161  // Now "visit" the group and/or session of the current process.
162  if (groups) {
163  pid_t group = process.get().group;
164  if (visited.groups.count(group) == 0) {
165  foreach (const Process& process, processes.get()) {
166  if (process.group == group) {
167  queue.push(process.pid);
168  }
169  }
170  visited.groups.insert(group);
171  }
172  }
173 
174  // If we do not have a session for the process, it's likely
175  // because the process is a zombie on OS X. This implies it has
176  // not been reaped and thus is located somewhere in the tree we
177  // are trying to kill. Therefore, we should discover it from our
178  // tree traversal, or through its group (which is always present).
179  if (sessions && process.get().session.isSome()) {
180  pid_t session = process.get().session.get();
181  if (visited.sessions.count(session) == 0) {
182  foreach (const Process& process, processes.get()) {
183  if (process.session.isSome() && process.session.get() == session) {
184  queue.push(process.pid);
185  }
186  }
187  visited.sessions.insert(session);
188  }
189  }
190  }
191 
192  // Now that all processes are stopped, we send the signal.
193  foreach (pid_t pid, visited.pids) {
194  kill(pid, signal);
195  }
196 
197  // There is a concern that even though some process is stopped,
198  // sending a signal to any of its children may cause a SIGCLD to
199  // be delivered to it which wakes it up (or any other signal maybe
200  // delivered). However, from the Open Group standards on "Signal
201  // Concepts":
202  //
203  // "While a process is stopped, any additional signals that are
204  // sent to the process shall not be delivered until the process
205  // is continued, except SIGKILL which always terminates the
206  // receiving process."
207  //
208  // In practice, this is not what has been witnessed. Rather, a
209  // process that has been stopped will respond to SIGTERM, SIGINT,
210  // etc. That being said, we still continue the process below in the
211  // event that it doesn't terminate from the sending signal but it
212  // also doesn't get continued (as per the specifications above).
213 
214  // Try and continue the processes in case the signal is
215  // non-terminating but doesn't continue the process.
216  foreach (pid_t pid, visited.pids) {
217  kill(pid, SIGCONT);
218  }
219 
220  // Return the process trees representing the visited pids.
221  return pstrees(visited.pids, visited.processes);
222 }
223 
224 } // namespace os {
225 
226 #endif // __STOUT_OS_POSIX_KILLTREE_HPP__
bool isNone() const
Definition: result.hpp:112
Definition: errorbase.hpp:35
Definition: option.hpp:28
Definition: try.hpp:34
static Result< T > error(const std::string &message)
Definition: result.hpp:53
const pid_t group
Definition: process.hpp:55
Try< std::list< ProcessTree > > pstrees(const std::set< pid_t > &, const std::list< Process > &)
Definition: pstree.hpp:84
Try< std::list< Process > > processes()
Definition: os.hpp:182
const Option< pid_t > session
Definition: process.hpp:56
Definition: result.hpp:40
bool isSome() const
Definition: option.hpp:115
DWORD pid_t
Definition: windows.hpp:187
Definition: process.hpp:32
const pid_t pid
Definition: process.hpp:53
Try< std::list< ProcessTree > > killtree(pid_t pid, int signal, bool groups=false, bool sessions=false)
Definition: killtree.hpp:58
const T & get() const &
Definition: option.hpp:118
const T & get() const
Definition: result.hpp:115
static Try error(const E &e)
Definition: try.hpp:42
int kill(pid_t pid, int sig)
Definition: kill.hpp:21
const pid_t parent
Definition: process.hpp:54
Result< Process > process(pid_t pid)
Definition: freebsd.hpp:30
Definition: grp.hpp:26
bool isError() const
Definition: try.hpp:71
std::set< pid_t > children(pid_t, const std::list< Process > &, bool)
Definition: os.hpp:215
bool isSome() const
Definition: result.hpp:111
bool isError() const
Definition: result.hpp:113
Try< std::set< pid_t > > pids()
Definition: freebsd.hpp:62
const T & get() const
Definition: try.hpp:73