Anklang-0.3.0.dev967+g08f67ae3 anklang-0.3.0.dev967+g08f67ae3
ASE — Anklang Sound Engine (C++)

« « « Anklang Documentation
Loading...
Searching...
No Matches
atquit.cc
Go to the documentation of this file.
1 // This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
2#include "atquit.hh"
3#include "utils.hh"
4#include <algorithm>
5#include <atomic>
6#include <cstring>
7#include <cerrno>
8#include <sys/wait.h>
9#include <sys/prctl.h>
10#include <sys/stat.h>
11#include <filesystem>
12#include <dirent.h>
13#include "path.hh"
14#include "strings.hh"
15
16#define QDEBUG(...) Ase::debug ("AtQuit", __VA_ARGS__)
17
18namespace Ase {
19
20static void atquit_run_terminate_handlers ();
21
23void
25{
26 DIR* dir = opendir (directory.c_str());
27 if (!dir)
28 return;
29 struct dirent *entry;
30 while ((entry = readdir (dir)) != nullptr) {
31 char fullpath[PATH_MAX + 1] = { 0, };
32 snprintf (fullpath, PATH_MAX, "%s/%s", directory.c_str(), entry->d_name);
33 struct stat sb;
34 if (stat (fullpath, &sb) == -1 || !S_ISREG (sb.st_mode))
35 continue; // also skips . and ..
36 FILE *file = fopen (fullpath, "r");
37 if (file) {
38 const int BUFFER_SIZE = 8192;
39 char buffer[BUFFER_SIZE + 1] = { 0, };
40 fread (buffer, BUFFER_SIZE, 1, file);
41 fclose (file);
42 const char *tag = "@@TEMPFILE_PID=";
43 char *tagp = strstr (buffer, tag);
44 if (tagp) {
45 const pid_t pid = strtol (tagp + strlen (tag), nullptr, 10);
46 if (pid > 1) { // check if pid_t exists and accessible for this user
47 char procpath[PATH_MAX + 1] = { 0, };
48 snprintf (procpath, PATH_MAX, "/proc/%u/environ", pid);
49 if (access (procpath, F_OK) != 0) {
50 if (unlink (fullpath) == 0)
51 errno = 0;
52 diag ("AtQuit: %s: remove \"%s\": %s", __func__, fullpath, strerror (errno));
53 }
54 }
55 }
56 }
57 }
58 closedir (dir);
59}
60
62struct PendingRemovals final {
63 std::function<void()> *atquit_handler = nullptr;
65 {
66 atquit_handler = new std::function<void()> ([&] {
67 this->unlink_all();
68 });
69 atquit_add (atquit_handler);
70 }
72 {
73 atquit_run_terminate_handlers();
74 atquit_del (atquit_handler);
75 (*atquit_handler) ();
76 delete atquit_handler;
77 }
78 void
79 add (const std::string &filename)
80 {
81 std::lock_guard<std::mutex> locker (mutex);
82 tentries.push_back (filename);
83 }
84 void
85 del (const std::string &filename)
86 {
87 std::lock_guard<std::mutex> locker (mutex);
88 Aux::erase_first (tentries, [&] (const std::string &e) { return e == filename; });
89 }
90 void
91 unlink_all()
92 {
93 std::lock_guard<std::mutex> locker (mutex);
94 while (tentries.size()) {
95 const std::string tentry = tentries.back();
96 tentries.pop_back();
98 std::filesystem::remove_all (tentry, ec);
99 errno = ec.value();
100 diag ("AtQuit: %s: remove \"%s\": %s", __func__, tentry, strerror (errno));
101 }
102 }
103private:
105 std::mutex mutex;
106};
107static PendingRemovals g_pending_removals;
108
110void
112{
113 g_pending_removals.add (filename);
114}
115
117void
119{
120 g_pending_removals.del (filename);
121}
122
124struct KillPids final {
125 ~KillPids()
126 {
127 atquit_run_terminate_handlers();
128 }
129 void
130 add (pid_t pid)
131 {
132 std::lock_guard<std::mutex> locker (mutex_);
133 pids_.push_back (pid);
134 }
135 void
136 del (pid_t pid)
137 {
138 std::lock_guard<std::mutex> locker (mutex_);
139 Aux::erase_first (pids_, [&] (pid_t p) { return p == pid; });
140 }
142 void
144 {
145 const auto current = read_children();
146 std::lock_guard<std::mutex> locker (mutex_);
147 for (pid_t pid : current) {
148 if (std::find (pids_.begin(), pids_.end(), pid) == pids_.end()) {
149 pids_.push_back (pid);
150 if (sig > 0) {
151 diag ("AtQuit: found orphan pid=%d, sending signal=%d", pid, sig);
152 kill (pid, sig);
153 }
154 }
155 }
156 }
158 void
160 {
161 int status = 0;
162 pid_t result;
163 while ((result = waitpid (-1, &status, WNOHANG)) > 0) {
164 std::lock_guard<std::mutex> locker (mutex_);
165 Aux::erase_first (pids_, [&] (pid_t p) { return p == result; });
166 }
167 }
170 void
171 kill_all (int sig)
172 {
173 std::lock_guard<std::mutex> locker (mutex_);
174 for (pid_t pid : pids_) {
175 diag ("AtQuit: %s: pid=%d signal=%d", __func__, pid, sig);
176 kill (pid, sig);
177 }
178 }
180 void
182 {
183 std::lock_guard<std::mutex> locker (mutex_);
184 pids_.clear();
185 }
187 bool
189 {
190 std::lock_guard<std::mutex> locker (mutex_);
191 return pids_.empty();
192 }
194 static std::vector<pid_t>
196 {
197 std::vector<pid_t> children;
198 char path[64];
199 snprintf (path, sizeof (path), "/proc/self/task/%d/children", getpid());
200 FILE *f = fopen (path, "r");
201 if (!f) return children;
202 pid_t pid;
203 while (fscanf (f, "%d", &pid) == 1 && pid > 0)
204 children.push_back (pid);
205 fclose (f);
206 return children;
207 }
208private:
209 std::vector<pid_t> pids_;
210 std::mutex mutex_;
211};
212static KillPids g_kill_pids;
213
215void
217{
218 g_kill_pids.add (pid);
219}
220
222void
224{
225 g_kill_pids.del (pid);
226}
227
229ErrorReason
230spawn_process (const std::vector<std::string> &argv, pid_t *child_pid, int pdeathsig, int stdio_fd, const std::vector<int> &keep_fds)
231{
233 for (const auto &arg : argv)
234 argvptr.push_back (arg.c_str());
235 argvptr.push_back (nullptr);
236 const char **child_argv = &argvptr[0];
237 // parent process
238 pid_t pid = fork();
239 if (pid < 0)
240 return { errno, "fork" };
241 if (pid) {
242 *child_pid = pid;
243 return { 0, "" }; // Success
244 }
245 // child process
246 pid = getpid();
247 int max_fd = sysconf (_SC_OPEN_MAX);
248 if (max_fd < 0)
249 max_fd = 1024; // fallback
250 for (int i = 3; i < max_fd; i++)
251 {
252 bool keep = (i == stdio_fd);
253 for (int kfd : keep_fds)
254 if (i == kfd)
255 {
256 keep = true;
257 break;
258 }
259 if (!keep)
260 close (i);
261 }
262 ErrorReason ereason;
263 const char *const home = getenv ("HOME");
264 if (false && home && chdir (home) < 0) {
265 ereason = { errno, "chdir" };
266 goto exec_error;
267 }
268 // Redirect stdout/stderr to stdio_fd if provided
269 if (stdio_fd >= 0) {
270 if (dup2 (stdio_fd, STDOUT_FILENO) < 0) {
271 ereason = { errno, "dup2 stdout" };
272 goto exec_error;
273 }
274 if (dup2 (stdio_fd, STDERR_FILENO) < 0) {
275 ereason = { errno, "dup2 stderr" };
276 goto exec_error;
277 }
278 close (stdio_fd);
279 }
280 if (unsetenv ("GTK_MODULES") < 0) {
281 ereason = { errno, "unsetenv" };
282 goto exec_error;
283 }
284 sigset_t empty_mask;
285 sigemptyset (&empty_mask);
286 if (sigprocmask (SIG_SETMASK, &empty_mask, nullptr) < 0) {
287 ereason = { errno, "sigprocmask" };
288 goto exec_error;
289 }
290 // Kill the child when the parent dies (though Chromium resets this)
291 if (pdeathsig > 0 && prctl (PR_SET_PDEATHSIG, SIGKILL) < 0) {
292 ereason = { errno, "prctl(PR_SET_PDEATHSIG)" };
293 goto exec_error;
294 }
295 execvp (child_argv[0], const_cast<char *const *> (child_argv));
296 // exec failed
297 ereason = { errno, "execvp" };
298 exec_error:
299 fprintf (stderr, "%s[pid=%d]: fork to exec %s: %s: %s\n", program_invocation_short_name, pid,
300 child_argv[0], ereason.what.c_str(), strerror (ereason.error));
301 _exit (127); // avoid atexit handlers that might destroy parent resources (e.g. xlib DISPLAY fd)
302}
303
305std::string // or returns errno
307{
308 std::string middlename = basename.size() ? basename : string_format ("anklang-%u", getuid());
309 middlename += "XXXXXX";
310 std::string tempname = std::filesystem::temp_directory_path() / middlename;
311 std::string dirname = mkdtemp (tempname.data());
312 if (dirname.size())
314 return dirname;
315}
316
317// == atquit ==
318static std::mutex atquit_mutex;
320 std::vector<std::function<void()>*> atquit_funcs;
322 {
323 // run atquit handlers also atexit
324 atquit_run_terminate_handlers();
325 }
326 void call_hooks();
327};
328static AtquitHandlers atquit_handlers;
329
330void
331atquit_add (std::function<void()> *func)
332{
333 std::lock_guard<std::mutex> locker (atquit_mutex);
334 if (func)
335 atquit_handlers.atquit_funcs.push_back (func);
336}
337
338void
339atquit_del (std::function<void()> *func)
340{
341 std::lock_guard<std::mutex> locker (atquit_mutex);
342 Aux::erase_first (atquit_handlers.atquit_funcs, [func] (std::function<void()> *ele) { return func == ele; });
343}
344
345static std::atomic<uint8_t> atquit_triggered_ = false;
346
347void
348AtquitHandlers::call_hooks()
349{
350 std::lock_guard<std::mutex> locker (atquit_mutex);
351 while (atquit_funcs.size())
352 {
353 std::function<void()> *func = atquit_funcs.back();
354 atquit_funcs.pop_back();
355 if (!func)
356 continue;
357 atquit_mutex.unlock();
358 (*func) ();
359 atquit_mutex.lock();
360 // intentionally leak func, we run only the bare minimum cleanups
361 }
362}
363
364void
365atquit_make_subreaper ()
366{
367 // Reparent orphaned grandchildren (e.g. htmlgui renderers) to this
368 // process, so we can reap or kill them at exit.
369 if (prctl (PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) < 0)
370 warning ("Ase: prctl(PR_SET_CHILD_SUBREAPER) failed: %s\n", strerror (errno));
371}
372
373static void
374atquit_run_terminate_handlers ()
375{
376 atquit_triggered_ = true;
377
378 // Terminate immediate child processes
379 g_kill_pids.reap();
380 g_kill_pids.kill_all (SIGTERM);
381
382 // Cleanups, like deleting temporary files
383 atquit_handlers.call_hooks();
384
385 // Wait in steps, reap zombies, kill new orphan processes
386 for (int step = 0; step < 10; step++) {
387 g_kill_pids.reap();
388 // On Linux, we also collect grand children
389 g_kill_pids.collect_children (SIGTERM);
390 if (g_kill_pids.empty())
391 break;
392 usleep (50000);
393 }
394
395 // SIGKILL any surviving processes
396 g_kill_pids.reap();
397 g_kill_pids.kill_all (SIGKILL);
398 g_kill_pids.collect_children (SIGKILL);
399 if (!g_kill_pids.empty()) {
400 usleep (50000);
401 g_kill_pids.reap();
403 g_kill_pids.clear();
404 g_kill_pids.collect_children (SIGKILL);
405 g_kill_pids.clear();
406 }
407}
408
409void
410atquit_terminate (int exitcode)
411{
412 atquit_run_terminate_handlers();
413 _Exit (exitcode);
414}
415
416bool
417atquit_triggered ()
418{
419 return atquit_triggered_;
420}
421
422} // Ase
_exit
access
T back(T... args)
basename
T begin(T... args)
T c_str(T... args)
chdir
T clear(T... args)
close
closedir
T data(T... args)
dirname
snprintf
dup2
T empty(T... args)
T end(T... args)
execvp
errno
fclose
opendir
T find(T... args)
fopen
fork
fread
fscanf
stat
getenv
getpid
getuid
kill
#define PATH_MAX
T lock(T... args)
mkdtemp
size_t erase_first(C &container, const std::function< bool(typename C::value_type const &value)> &pred)
Erase first element for which pred() is true in vector or list.
Definition utils.hh:267
The Anklang C++ API namespace.
Definition api.hh:8
std::string string_format(const char *format, const Args &...args) __attribute__((__format__(__printf__
Format a string similar to sprintf(3) with support for std::string and std::ostringstream convertible...
void atquit_add_removal(const std::string &filename)
Remove filename (or directory) when the program terminates.
Definition atquit.cc:111
void atquit_add_kill_pid(int pid)
Kill pid when the program terminates.
Definition atquit.cc:216
void atquit_del_kill_pid(int pid)
Undo a previous atquit_add_kill_pid() call.
Definition atquit.cc:223
void atquit_del_removal(const std::string &filename)
Undo a previous atquit_add_removal() call.
Definition atquit.cc:118
ErrorReason spawn_process(const std::vector< std::string > &argv, pid_t *child_pid, int pdeathsig, int stdio_fd, const std::vector< int > &keep_fds)
Spawn a child process after cleaning up the environment.
Definition atquit.cc:230
void cleanup_orphaned_tempfiles(const std::string &directory)
Delete all files that contain @TEMPFILE_PID=d@ without a running pid_t d.
Definition atquit.cc:24
std::string create_tempfile_dir(const std::string &basename)
Create temporary directory under /tmp, scheduled for removal atquit.
Definition atquit.cc:306
T pop_back(T... args)
sigprocmask
T push_back(T... args)
readdir
T remove_all(T... args)
sigemptyset
T size(T... args)
stderr
strerror
strlen
strstr
strtol
Cleanup list of child processes still running at exit.
Definition atquit.cc:124
void kill_all(int sig)
Send signal sig to all tracked PIDs.
Definition atquit.cc:171
void clear()
Clear the PID list after both kill phases are complete.
Definition atquit.cc:181
void reap()
Reap zombies and remove dead PIDs from the list.
Definition atquit.cc:159
bool empty()
Return true if no PIDs are tracked.
Definition atquit.cc:188
void collect_children(int sig)
Find previously orphaned child processes, optionally send signal.
Definition atquit.cc:143
static std::vector< pid_t > read_children()
Read orphaned grandchildren reparented to this process as subreaper.
Definition atquit.cc:195
Cleanup list of temporary files/dirs to be removed at exit.
Definition atquit.cc:62
typedef pid_t
sysconf
T temp_directory_path(T... args)
unlink
T unlock(T... args)
unsetenv
T value(T... args)
waitpid