Anklang-0.3.0.dev835+g24d8ae08 anklang-0.3.0.dev835+g24d8ae08
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, int stdio_fd, const std::vector<int> &keep_fds)
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 {
202 bool keep = (i == stdio_fd);
203 for (int kfd : keep_fds)
204 if (i == kfd)
205 {
206 keep = true;
207 break;
208 }
209 if (!keep)
210 close (i);
211 }
212 ErrorReason ereason;
213 const char *const home = getenv ("HOME");
214 if (false && home && chdir (home) < 0) {
215 ereason = { errno, "chdir" };
216 goto exec_error;
217 }
218 // Redirect stdout/stderr to stdio_fd if provided
219 if (stdio_fd >= 0) {
220 if (dup2 (stdio_fd, STDOUT_FILENO) < 0) {
221 ereason = { errno, "dup2 stdout" };
222 goto exec_error;
223 }
224 if (dup2 (stdio_fd, STDERR_FILENO) < 0) {
225 ereason = { errno, "dup2 stderr" };
226 goto exec_error;
227 }
228 close (stdio_fd);
229 }
230 if (unsetenv ("GTK_MODULES") < 0) {
231 ereason = { errno, "unsetenv" };
232 goto exec_error;
233 }
234 sigset_t empty_mask;
235 sigemptyset (&empty_mask);
236 if (sigprocmask (SIG_SETMASK, &empty_mask, nullptr) < 0) {
237 ereason = { errno, "sigprocmask" };
238 goto exec_error;
239 }
240 // Kill the child when the parent dies (though Chromium resets this)
241 if (pdeathsig > 0 && prctl (PR_SET_PDEATHSIG, SIGKILL) < 0) {
242 ereason = { errno, "prctl(PR_SET_PDEATHSIG)" };
243 goto exec_error;
244 }
245 execvp (child_argv[0], const_cast<char *const *> (child_argv));
246 // exec failed
247 ereason = { errno, "execvp" };
248 exec_error:
249 fprintf (stderr, "%s[pid=%d]: fork to exec %s: %s: %s\n", program_invocation_short_name, pid,
250 child_argv[0], ereason.what.c_str(), strerror (ereason.error));
251 _exit (127); // avoid atexit handlers that might destroy parent resources (e.g. xlib DISPLAY fd)
252}
253
255std::string // or returns errno
257{
258 std::string middlename = basename.size() ? basename : string_format ("anklang-%u", getuid());
259 middlename += "XXXXXX";
260 std::string tempname = std::filesystem::temp_directory_path() / middlename;
261 std::string dirname = mkdtemp (tempname.data());
262 if (dirname.size())
264 return dirname;
265}
266
267// == atquit ==
268static std::mutex atquit_mutex;
270 std::vector<std::function<void()>*> atquit_funcs;
272 {
273 // run atquit handlers also atexit
274 call_hooks();
275 }
276 void call_hooks();
277};
278static AtquitHandlers atquit_handlers;
279
280void
281atquit_add (std::function<void()> *func)
282{
283 std::lock_guard<std::mutex> locker (atquit_mutex);
284 if (func)
285 atquit_handlers.atquit_funcs.push_back (func);
286}
287
288void
289atquit_del (std::function<void()> *func)
290{
291 std::lock_guard<std::mutex> locker (atquit_mutex);
292 Aux::erase_first (atquit_handlers.atquit_funcs, [func] (std::function<void()> *ele) { return func == ele; });
293}
294
295static std::atomic<uint8_t> atquit_triggered_ = false;
296
297void
298AtquitHandlers::call_hooks()
299{
300 std::lock_guard<std::mutex> locker (atquit_mutex);
301 while (atquit_funcs.size())
302 {
303 std::function<void()> *func = atquit_funcs.back();
304 atquit_funcs.pop_back();
305 if (!func)
306 continue;
307 atquit_mutex.unlock();
308 (*func) ();
309 atquit_mutex.lock();
310 // intentionally leak func, we run only the bare minimum cleanups
311 }
312}
313
314void
315atquit_terminate (int exitcode, int pgroup)
316{
317 atquit_triggered_ = true;
318 atquit_handlers.call_hooks();
319 if (pgroup > 1) {
320 struct sigaction sa;
321 sa.sa_handler = SIG_DFL;
322 sigemptyset (&sa.sa_mask);
323 sa.sa_flags = 0;
324 sigaction (SIGTERM, &sa, nullptr);
325 diag ("AtQuit: killing process group: pid=%d signal=%d", pgroup, SIGTERM);
326 kill (-pgroup, SIGTERM);
327 }
328 _Exit (exitcode); // ends all threads with exit_group()
329}
330
331bool
332atquit_triggered ()
333{
334 // atquit_tester(); // FIXME
335 return atquit_triggered_;
336}
337
338} // Ase
_exit
access
T back(T... args)
basename
T c_str(T... args)
chdir
close
closedir
T data(T... args)
dirname
snprintf
dup2
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
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
ErrorReason spawn_process(const std::vector< std::string > &argv, pid_t *child_pid, int pdeathsig, int stdio_fd, const std::vector< int > &keep_fds)
Span a child process after cleaning up the environment.
Definition atquit.cc:180
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:256
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)