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