1 /* BEGIN_COMMON_COPYRIGHT_HEADER
2  * (c)LGPL2+
3  *
4  * LXQt - a lightweight, Qt based, desktop toolset
5  * https://lxqt.org
6  *
7  * Copyright: 2021~ LXQt team
8  * Authors:
9  *  Palo Kisa <palo.kisa@gmail.com>
10  *
11  * This program or library is free software; you can redistribute it
12  * and/or modify it under the terms of the GNU Lesser General Public
13  * License as published by the Free Software Foundation; either
14  * version 2.1 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General
22  * Public License along with this library; if not, write to the
23  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24  * Boston, MA 02110-1301 USA
25  *
26  * END_COMMON_COPYRIGHT_HEADER */
27 
28 #include "procreaper.h"
29 #include "log.h"
30 #if defined(Q_OS_LINUX)
31 #include <sys/prctl.h>
32 #include <proc/readproc.h>
33 #elif defined(Q_OS_FREEBSD)
34 #include <sys/sysctl.h>
35 #include <sys/procctl.h>
36 #include <libutil.h>
37 #include <sys/user.h>
38 #include <signal.h>
39 #endif
40 #include <unistd.h>
41 #include <cstring>
42 #include <cerrno>
43 #include <sys/wait.h>
44 
45 /*-
46  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
47  *
48  * Copyright (c) 2007 Robert N. M. Watson
49  * Copyright (c) 2009 Ulf Lilleengen
50  * All rights reserved.
51  *
52  * Redistribution and use in source and binary forms, with or without
53  * modification, are permitted provided that the following conditions
54  * are met:
55  * 1. Redistributions of source code must retain the above copyright
56  *    notice, this list of conditions and the following disclaimer.
57  * 2. Redistributions in binary form must reproduce the above copyright
58  *    notice, this list of conditions and the following disclaimer in the
59  *    documentation and/or other materials provided with the distribution.
60  *
61  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
62  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
63  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
64  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
65  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
66  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
67  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
68  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
69  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
70  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
71  * SUCH DAMAGE.
72  *
73  * $FreeBSD$
74  */
75 
76 /*
77  * XXX DPORTS: This is NOT okay but given the time I have it should work and be
78  * enough for now
79  */
80 
81 static int
kinfo_proc_compare(const void * a,const void * b)82 kinfo_proc_compare(const void *a, const void *b)
83 {
84         int i;
85 
86         i = ((const struct kinfo_proc *)a)->kp_pid -
87             ((const struct kinfo_proc *)b)->kp_pid;
88         if (i != 0)
89                 return (i);
90         i = ((const struct kinfo_proc *)a)->kp_lwp.kl_tid -
91             ((const struct kinfo_proc *)b)->kp_lwp.kl_tid;
92         return (i);
93 }
94 
95 static void
kinfo_proc_sort(struct kinfo_proc * kipp,int count)96 kinfo_proc_sort(struct kinfo_proc *kipp, int count)
97 {
98 
99         qsort(kipp, count, sizeof(*kipp), kinfo_proc_compare);
100 }
101 
102 struct kinfo_proc *
kinfo_getallproc(int * cntp)103 kinfo_getallproc(int *cntp)
104 {
105         struct kinfo_proc *kipp;
106         size_t len;
107         int mib[3];
108 
109         mib[0] = CTL_KERN;
110         mib[1] = KERN_PROC;
111         mib[2] = KERN_PROC_ALL;
112 
113         len = 0;
114         if (::sysctl(mib, nitems(mib), NULL, &len, NULL, 0) < 0)
115                 return (NULL);
116 
117         kipp = (struct kinfo_proc *)malloc(len);
118         if (kipp == NULL)
119                 return (NULL);
120 
121         if (::sysctl(mib, nitems(mib), kipp, &len, NULL, 0) < 0)
122                 goto bad;
123         if (len % sizeof(*kipp) != 0)
124                 goto bad;
125         *cntp = len / sizeof(*kipp);
126         kinfo_proc_sort(kipp, len / sizeof(*kipp));
127         return (kipp);
128 bad:
129         *cntp = 0;
130         free(kipp);
131         return (NULL);
132 }
133 
ProcReaper()134 ProcReaper::ProcReaper()
135     : mShouldRun{true}
136 {
137 #if defined(Q_OS_LINUX)
138     int result = prctl(PR_SET_CHILD_SUBREAPER, 1);
139     if (result != 0)
140         qCWarning(SESSION) << "Unable to to set PR_SET_CHILD_SUBREAPER, " << result << " - " << strerror(errno);
141 #elif defined(Q_OS_FREEBSD)
142     int result = procctl(P_PID, ::getpid(), PROC_REAP_ACQUIRE, nullptr);
143     if (result != 0)
144         qCWarning(SESSION) << "Unable to to set PROC_REAP_ACQUIRE, " << result << " - " << strerror(errno);
145 #endif
146 }
147 
~ProcReaper()148 ProcReaper::~ProcReaper()
149 {
150     stop({});
151 }
152 
run()153 void ProcReaper::run()
154 {
155     pid_t pid = 0;
156     while (true)
157     {
158         if (pid <= 0)
159         {
160             QMutexLocker guard{&mMutex};
161             mWait.wait(&mMutex, 1000); // 1 second
162         }
163 
164         int status;
165         pid = ::waitpid(-1, &status, WNOHANG);
166         if (pid < 0)
167         {
168             if (ECHILD != errno)
169                 qCDebug(SESSION) << "waitpid failed " << strerror(errno);
170         } else if (pid > 0)
171         {
172             if (WIFEXITED(status))
173                 qCDebug(SESSION) << "Child process " << pid << " exited with status " << WEXITSTATUS(status);
174             else if (WIFSIGNALED(status))
175                 qCDebug(SESSION) << "Child process " << pid << " terminated on signal " << WTERMSIG(status);
176             else
177                 qCDebug(SESSION) << "Child process " << pid << " ended";
178         }
179         {
180             QMutexLocker guard{&mMutex};
181             if (!mShouldRun && pid <= 0)
182                 break;
183         }
184     }
185 }
186 
stop(const std::set<int64_t> & excludedPids)187 void ProcReaper::stop(const std::set<int64_t> & excludedPids)
188 {
189     {
190         QMutexLocker guard{&mMutex};
191         if (!mShouldRun)
192             return;
193     }
194     // send term to all children
195     const pid_t my_pid = ::getpid();
196     std::vector<pid_t> children;
197 #if defined(Q_OS_LINUX)
198     PROCTAB * proc_dir = ::openproc(PROC_FILLSTAT);
199     while (proc_t * proc = ::readproc(proc_dir, nullptr))
200     {
201         if (proc->ppid == my_pid)
202         {
203             children.push_back(proc->tgid);
204         }
205         ::freeproc(proc);
206     }
207     ::closeproc(proc_dir);
208 #elif defined(Q_OS_FREEBSD)
209     int cnt = 0;
210     if (kinfo_proc *proc_info = kinfo_getallproc(&cnt))
211     {
212         for (int i = 0; i < cnt; ++i)
213         {
214             if (proc_info[i].kp_ppid == my_pid)
215             {
216                 children.push_back(proc_info[i].kp_pid);
217             }
218         }
219         free(proc_info);
220     }
221 #endif
222     for (auto const & child : children)
223     {
224         if (excludedPids.count(child) == 0)
225         {
226             qCDebug(SESSION) << "Seding TERM to child " << child;
227             ::kill(child, SIGTERM);
228         }
229     }
230     mWait.wakeAll();
231     {
232         QMutexLocker guard{&mMutex};
233         mShouldRun = false;
234     }
235     QThread::wait(5000); // 5 seconds
236 }
237