summaryrefslogtreecommitdiffstats
path: root/src/shared/async.c
blob: 41f6b97e02861934466f599914585305ea44a833 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include <errno.h>
#include <stddef.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <unistd.h>

#include "async.h"
#include "errno-util.h"
#include "fd-util.h"
#include "log.h"
#include "macro.h"
#include "process-util.h"
#include "signal-util.h"

int asynchronous_sync(pid_t *ret_pid) {
        int r;

        /* This forks off an invocation of fork() as a child process, in order to initiate synchronization to
         * disk. Note that we implement this as helper process rather than thread as we don't want the sync() to hang our
         * original process ever, and a thread would do that as the process can't exit with threads hanging in blocking
         * syscalls. */

        r = safe_fork("(sd-sync)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|(ret_pid ? 0 : FORK_DETACH), ret_pid);
        if (r < 0)
                return r;
        if (r == 0) {
                /* Child process */
                sync();
                _exit(EXIT_SUCCESS);
        }

        return 0;
}

/* We encode the fd to close in the userdata pointer as an unsigned value. The highest bit indicates whether
 * we need to fork again */
#define NEED_DOUBLE_FORK (1U << (sizeof(unsigned) * 8 - 1))

static int close_func(void *p) {
        unsigned v = PTR_TO_UINT(p);

        (void) prctl(PR_SET_NAME, (unsigned long*) "(sd-close)");

        /* Note: 💣 This function is invoked in a child process created via glibc's clone() wrapper. In such
         *       children memory allocation is not allowed, since glibc does not release malloc mutexes in
         *       clone() 💣 */

        if (v & NEED_DOUBLE_FORK) {
                pid_t pid;

                v &= ~NEED_DOUBLE_FORK;

                /* This inner child will be reparented to the subreaper/PID 1. Here we turn on SIGCHLD, so
                 * that the reaper knows when it's time to reap. */
                pid = clone_with_nested_stack(close_func, SIGCHLD|CLONE_FILES, UINT_TO_PTR(v));
                if (pid >= 0)
                        return 0;
        }

        close((int) v); /* no assert() here, we are in the child and the result would be eaten up anyway */
        return 0;
}

int asynchronous_close(int fd) {
        unsigned v;
        pid_t pid;
        int r;

        /* This is supposed to behave similar to safe_close(), but actually invoke close() asynchronously, so
         * that it will never block. Ideally the kernel would have an API for this, but it doesn't, so we
         * work around it, and hide this as a far away as we can.
         *
         * It is important to us that we don't use threads (via glibc pthread) in PID 1, hence we'll do a
         * minimal subprocess instead which shares our fd table via CLONE_FILES. */

        if (fd < 0)
                return -EBADF; /* already invalid */

        PROTECT_ERRNO;

        v = (unsigned) fd;

        /* We want to fork off a process that is automatically reaped. For that we'd usually double-fork. But
         * we can optimize this a bit: if we are PID 1 or a subreaper anyway (the systemd service manager
         * process qualifies as this), we can avoid the double forking, since the double forked process would
         * be reparented back to us anyway. */
        r = is_reaper_process();
        if (r < 0)
                log_debug_errno(r, "Cannot determine if we are a reaper process, assuming we are not: %m");
        if (r <= 0)
                v |= NEED_DOUBLE_FORK;

        pid = clone_with_nested_stack(close_func, CLONE_FILES | ((v & NEED_DOUBLE_FORK) ? 0 : SIGCHLD), UINT_TO_PTR(v));
        if (pid < 0)
                assert_se(close_nointr(fd) != -EBADF); /* local fallback */
        else if (v & NEED_DOUBLE_FORK) {

                /* Reap the intermediate child. Key here is that we specify __WCLONE, since we didn't ask for
                 * any signal to be sent to us on process exit, and otherwise waitid() would refuse waiting
                 * then.
                 *
                 * We usually prefer calling waitid(), but before kernel 4.7 it didn't support __WCLONE while
                 * waitpid() did. Hence let's use waitpid() here, it's good enough for our purposes here. */
                for (;;)
                        if (waitpid(pid, NULL, __WCLONE) >= 0 || errno != EINTR)
                                break;
        }

        return -EBADF; /* return an invalidated fd */
}

int asynchronous_rm_rf(const char *p, RemoveFlags flags) {
        int r;

        assert(p);

        /* Forks off a child that destroys the specified path. This will be best effort only, i.e. the child
         * will attempt to do its thing, but we won't wait for it or check its success. */

        r = safe_fork("(sd-rmrf)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DETACH, NULL);
        if (r != 0)
                return r;

        /* Child */

        r = rm_rf(p, flags);
        if (r < 0) {
                log_debug_errno(r, "Failed to rm -rf '%s', ignoring: %m", p);
                _exit(EXIT_FAILURE); /* This is a detached process, hence no one really cares, but who knows
                                      * maybe it's good for debugging/tracing to return an exit code
                                      * indicative of our failure here. */
        }

        _exit(EXIT_SUCCESS);
}