Anklang-0.3.0.dev712+gdc4e642f anklang-0.3.0.dev712+gdc4e642f
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 <atomic>
5#include <cstring>
6#include <cerrno>
7#include <sys/wait.h>
8#include <sys/prctl.h>
9#include <sys/stat.h>
10#include <filesystem>
11#include <dirent.h>
12#include "path.hh"
13#include "strings.hh"
14
15#define QDEBUG(...) Ase::debug ("AtQuit", __VA_ARGS__)
16
17namespace Ase {
18
20void
22{
23 DIR* dir = opendir (directory.c_str());
24 if (!dir)
25 return;
26 struct dirent *entry;
27 while ((entry = readdir (dir)) != nullptr) {
28 char fullpath[PATH_MAX + 1] = { 0, };
29 snprintf (fullpath, PATH_MAX, "%s/%s", directory.c_str(), entry->d_name);
30 struct stat sb;
31 if (stat (fullpath, &sb) == -1 || !S_ISREG (sb.st_mode))
32 continue; // also skips . and ..
33 FILE *file = fopen (fullpath, "r");
34 if (file) {
35 const int BUFFER_SIZE = 8192;
36 char buffer[BUFFER_SIZE + 1] = { 0, };
37 fread (buffer, BUFFER_SIZE, 1, file);
38 fclose (file);
39 const char *tag = "@@TEMPFILE_PID=";
40 char *tagp = strstr (buffer, tag);
41 if (tagp) {
42 const pid_t pid = strtol (tagp + strlen (tag), nullptr, 10);
43 if (pid > 1) { // check if pid_t exists and accessible for this user
44 char procpath[PATH_MAX + 1] = { 0, };
45 snprintf (procpath, PATH_MAX, "/proc/%u/environ", pid);
46 if (access (procpath, F_OK) != 0) {
47 if (unlink (fullpath) == 0)
48 errno = 0;
49 diag ("AtQuit: %s: remove \"%s\": %s", __func__, fullpath, strerror (errno));
50 }
51 }
52 }
53 }
54 }
55 closedir (dir);
56}
57
59struct PendingRemovals final {
60 std::function<void()> *atquit_handler = nullptr;
62 {
63 atquit_handler = new std::function<void()> ([&] {
64 this->unlink_all();
65 });
66 atquit_add (atquit_handler);
67 }
69 {
70 atquit_del (atquit_handler);
71 (*atquit_handler) ();
72 delete atquit_handler;
73 }
74 void
75 add (const std::string &filename)
76 {
77 std::lock_guard<std::mutex> locker (mutex);
78 tentries.push_back (filename);
79 }
80 void
81 del (const std::string &filename)
82 {
83 std::lock_guard<std::mutex> locker (mutex);
84 Aux::erase_first (tentries, [&] (const std::string &e) { return e == filename; });
85 }
86 void
87 unlink_all()
88 {
89 std::lock_guard<std::mutex> locker (mutex);
90 while (tentries.size()) {
91 const std::string tentry = tentries.back();
92 tentries.pop_back();
94 std::filesystem::remove_all (tentry, ec);
95 errno = ec.value();
96 diag ("AtQuit: %s: remove \"%s\": %s", __func__, tentry, strerror (errno));
97 }
98 }
99private:
101 std::mutex mutex;
102};
103static PendingRemovals g_pending_removals;
104
106void
108{
109 g_pending_removals.add (filename);
110}
111
113void
115{
116 g_pending_removals.del (filename);
117}
118
120struct KillPids final {
121 std::function<void()> *atquit_handler = nullptr;
122 KillPids()
123 {
124 atquit_handler = new std::function<void()> ([&] {
125 this->kill_all (SIGTERM);
126 });
127 atquit_add (atquit_handler);
128 }
129 ~KillPids()
130 {
131 atquit_del (atquit_handler);
132 (*atquit_handler) ();
133 delete atquit_handler;
134 }
135 void
136 add (pid_t pid)
137 {
138 std::lock_guard<std::mutex> locker (mutex);
139 pids.push_back (pid);
140 }
141 void
142 del (pid_t pid)
143 {
144 std::lock_guard<std::mutex> locker (mutex);
145 Aux::erase_first (pids, [&] (pid_t p) { return p == pid; });
146 }
147 void
148 kill_all (int sig)
149 {
150 std::lock_guard<std::mutex> locker (mutex);
151 while (pids.size()) {
152 const pid_t pid = pids.back();
153 pids.pop_back();
154 diag ("AtQuit: %s: pid=%d signal=%d", __func__, pid, sig);
155 kill (pid, sig);
156 }
157 }
158private:
160 std::mutex mutex;
161};
162static KillPids g_kill_pids;
163
165void
167{
168 g_kill_pids.add (pid);
169}
170
172void
174{
175 g_kill_pids.del (pid);
176}
177
179ErrorReason
180spawn_process (const std::vector<std::string> &argv, pid_t *child_pid, int pdeathsig)
181{
183 for (const auto &arg : argv)
184 argvptr.push_back (arg.c_str());
185 argvptr.push_back (nullptr);
186 const char **child_argv = &argvptr[0];
187 // parent process
188 pid_t pid = fork();
189 if (pid < 0)
190 return { errno, "fork" };
191 if (pid) {
192 *child_pid = pid;
193 return { 0, "" }; // Success
194 }
195 // child process
196 pid = getpid();
197 int max_fd = sysconf (_SC_OPEN_MAX);
198 if (max_fd < 0)
199 max_fd = 1024; // fallback
200 for (int i = 3; i < max_fd; i++)
201 close (i);
202 ErrorReason ereason;
203 const char *const home = getenv ("HOME");
204 if (home && chdir (home) < 0) {
205 ereason = { errno, "chdir" };
206 goto exec_error;
207 }
208 if (unsetenv ("GTK_MODULES") < 0) {
209 ereason = { errno, "unsetenv" };
210 goto exec_error;
211 }
212 sigset_t empty_mask;
213 sigemptyset (&empty_mask);
214 if (sigprocmask (SIG_SETMASK, &empty_mask, nullptr) < 0) {
215 ereason = { errno, "sigprocmask" };
216 goto exec_error;
217 }
218 // Kill the child when the parent dies (though Chromium resets this)
219 if (pdeathsig > 0 && prctl (PR_SET_PDEATHSIG, SIGKILL) < 0) {
220 ereason = { errno, "prctl(PR_SET_PDEATHSIG)" };
221 goto exec_error;
222 }
223 execvp (child_argv[0], const_cast<char *const *> (child_argv));
224 // exec failed
225 ereason = { errno, "execvp" };
226 exec_error:
227 fprintf (stderr, "%s[pid=%d]: fork to exec %s: %s: %s\n", program_invocation_short_name, pid,
228 child_argv[0], ereason.what.c_str(), strerror (ereason.error));
229 _exit (127); // avoid atexit handlers that might destroy parent resources (e.g. xlib DISPLAY fd)
230}
231
233std::string // or returns errno
235{
236 std::string middlename = basename.size() ? basename : string_format ("anklang-%u", getuid());
237 middlename += "XXXXXX";
238 std::string tempname = std::filesystem::temp_directory_path() / middlename;
239 std::string dirname = mkdtemp (tempname.data());
240 if (dirname.size())
242 return dirname;
243}
244
245// == atquit ==
246static std::mutex atquit_mutex;
248 std::vector<std::function<void()>*> atquit_funcs;
250 {
251 // run atquit handlers also atexit
252 call_hooks();
253 }
254 void call_hooks();
255};
256static AtquitHandlers atquit_handlers;
257
258void
259atquit_add (std::function<void()> *func)
260{
261 std::lock_guard<std::mutex> locker (atquit_mutex);
262 if (func)
263 atquit_handlers.atquit_funcs.push_back (func);
264}
265
266void
267atquit_del (std::function<void()> *func)
268{
269 std::lock_guard<std::mutex> locker (atquit_mutex);
270 Aux::erase_first (atquit_handlers.atquit_funcs, [func] (std::function<void()> *ele) { return func == ele; });
271}
272
273static std::atomic<uint8_t> atquit_triggered_ = false;
274
275void
276AtquitHandlers::call_hooks()
277{
278 std::lock_guard<std::mutex> locker (atquit_mutex);
279 while (atquit_funcs.size())
280 {
281 std::function<void()> *func = atquit_funcs.back();
282 atquit_funcs.pop_back();
283 if (!func)
284 continue;
285 atquit_mutex.unlock();
286 (*func) ();
287 atquit_mutex.lock();
288 // intentionally leak func, we run only the bare minimum cleanups
289 }
290}
291
292void
293atquit_terminate (int exitcode, int pgroup)
294{
295 atquit_triggered_ = true;
296 atquit_handlers.call_hooks();
297 if (pgroup > 1) {
298 struct sigaction sa;
299 sa.sa_handler = SIG_DFL;
300 sigemptyset (&sa.sa_mask);
301 sa.sa_flags = 0;
302 sigaction (SIGTERM, &sa, nullptr);
303 diag ("AtQuit: killing process group: pid=%d signal=%d", pgroup, SIGTERM);
304 kill (-pgroup, SIGTERM);
305 }
306 _Exit (exitcode); // ends all threads with exit_group()
307}
308
309bool
310atquit_triggered ()
311{
312 // atquit_tester(); // FIXME
313 return atquit_triggered_;
314}
315
316} // Ase
_exit
access
T back(T... args)
basename
T c_str(T... args)
chdir
close
closedir
T data(T... args)
dirname
snprintf
execvp
errno
fclose
opendir
fopen
fork
fread
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:268
The Anklang C++ API namespace.
Definition api.hh:9
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:107
ErrorReason spawn_process(const std::vector< std::string > &argv, pid_t *child_pid, int pdeathsig)
Span a child process after cleaning up the environment.
Definition atquit.cc:180
void atquit_del_killl_pid(int pid)
Undo a previous atquit_add_killl_pid() call.
Definition atquit.cc:173
void atquit_del_removal(const std::string &filename)
Undo a previous atquit_add_removal() call.
Definition atquit.cc:114
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:21
void atquit_add_killl_pid(int pid)
Kill pid when the program terminates.
Definition atquit.cc:166
std::string create_tempfile_dir(const std::string &basename)
Create temporary directory under /tmp, scheduled for removal atquit.
Definition atquit.cc:234
T pop_back(T... args)
sigprocmask
T push_back(T... args)
readdir
T remove_all(T... args)
sigaction
sigemptyset
T size(T... args)
stderr
strerror
strlen
strstr
strtol
Cleanup list of child processes still running at exit.
Definition atquit.cc:120
Cleanup list of temporary files/dirs to be removed at exit.
Definition atquit.cc:59
typedef pid_t
sysconf
T temp_directory_path(T... args)
unlink
T unlock(T... args)
unsetenv
T value(T... args)