xref: /third_party/musl/src/exit/linux/atexit.c (revision 570af302)
1#include <stdlib.h>
2#include <stdint.h>
3#include <stddef.h>
4#include "libc.h"
5#include "lock.h"
6#include "fork_impl.h"
7#include "dynlink.h"
8#include "musl_log.h"
9
10#define malloc __libc_malloc
11#define calloc __libc_calloc
12#define realloc undef
13#define free __libc_free
14
15/* Ensure that at least 32 atexit handlers can be registered without malloc */
16#define COUNT 32
17
18struct node {
19	void (*func)(void *);
20	void *arg;
21	void *dso;
22	struct dso *internal_dso; // the internal dso weekptr, used for dlclose
23	struct node *prev;
24	struct node *next;
25};
26
27static size_t len;                  // the number of nodes currently in use
28static size_t capacity;             // the number of available nodes
29static struct node builtin[COUNT];  // 32 builtin nodes without malloc
30static struct node *tail;           // point to the last node, or NULL
31static struct node *head;           // point to the first node
32
33static volatile int lock[1];
34volatile int *const __atexit_lockptr = lock;
35
36static int grow()
37{
38	struct node *nodes;
39
40	if (capacity == 0) {
41		nodes = builtin;
42		head = nodes;
43	} else {
44		nodes = malloc(sizeof(struct node) * COUNT);
45		if (nodes == NULL) {
46			return -1;
47		}
48	}
49
50	for (size_t i = 0; i < COUNT - 1; i++) {
51		nodes[i].next = nodes + (i + 1);
52	}
53	nodes[COUNT - 1].next = NULL;
54
55	// link new nodes after tail
56	if (tail) {
57		tail->next = nodes;
58	}
59
60	capacity += COUNT;
61	return 0;
62}
63
64static void append_node(void (*func)(void *), void *arg, void *dso, struct dso *internal_dso) {
65	struct node *new_tail;
66	if (tail == NULL) {
67		new_tail = head;
68	} else {
69		new_tail = tail->next;
70	}
71
72	new_tail->func = func;
73	new_tail->arg = arg;
74	new_tail->dso = dso;
75	new_tail->internal_dso = internal_dso;
76
77	new_tail->prev = tail;
78	tail = new_tail;
79
80	len++;
81}
82
83static struct node* remove_node(struct node *node) {
84	struct node *prev = node->prev;
85	if (tail == node) {
86		// move back
87		tail = prev;
88		if (tail == NULL) {
89			head = node;
90		}
91	} else {
92		// remove node
93		struct node *next = node->next;
94		if (next) {
95			next->prev = prev;
96		}
97		if (prev) {
98			prev->next = next;
99		}
100
101		// insert node after tail
102		struct node *tail_next = tail->next;
103		node->prev = tail;
104		node->next = tail_next;
105		tail->next = node;
106		if (tail_next) {
107			tail_next->prev = node;
108		}
109	}
110
111	len--;
112	return prev;
113}
114
115void __funcs_on_exit()
116{
117	void (*func)(void *), *arg;
118
119	LOCK(lock);
120	for (; tail; tail = tail->prev) {
121		func = tail->func;
122		tail->func = NULL; // Avoid repeated invocation.
123		if (func != NULL) {
124			arg = tail->arg;
125			UNLOCK(lock);
126			func(arg);
127			LOCK(lock);
128		}
129	}
130	UNLOCK(lock);
131}
132
133void __cxa_finalize(void *dso)
134{
135	void (*func)(void *), *arg;
136	struct node *node;
137
138	LOCK(lock);
139	for (node = tail; node;) {
140		if (dso == node->dso) {
141			func = node->func;
142			if (func != NULL) {
143				arg = node->arg;
144				UNLOCK(lock);
145				func(arg);
146				LOCK(lock);
147			}
148
149			node = remove_node(node);
150			continue;
151		}
152
153		node = node->prev;
154	}
155
156	UNLOCK(lock);
157}
158
159static void call(void *p);
160__attribute__ ((__weak__)) extern void *addr2dso(size_t a);
161
162int __cxa_atexit(void (*func)(void *), void *arg, void *dso)
163{
164	struct dso *p = NULL;
165	LOCK(lock);
166
167#if (defined(FEATURE_ATEXIT_CB_PROTECT))
168	if ((func == (void *)call) && (dso == NULL)) {
169		if (addr2dso != NULL) {
170			p = addr2dso((size_t)arg);
171			if (p == NULL) {
172				UNLOCK(lock);
173				MUSL_LOGE("call atexit with invalid callback ptr=%{public}p", arg);
174				return -1;
175			}
176		}
177	}
178#endif
179
180	if (len >= capacity) {
181		if (grow()) {
182			UNLOCK(lock);
183			return -1;
184		}
185	}
186
187	append_node(func, arg, dso, p);
188
189	UNLOCK(lock);
190	return 0;
191}
192
193static void call(void *p)
194{
195	if (p != NULL)
196		((void (*)(void))(uintptr_t)p)();
197}
198
199int atexit(void (*func)(void))
200{
201	return __cxa_atexit(call, (void *)(uintptr_t)func, 0);
202}
203
204int invalidate_exit_funcs(struct dso *p)
205{
206	struct node *node;
207
208	LOCK(lock);
209	for (node = tail; node; node = node->prev) {
210		// if found exit callback relative to this dso, and
211		if (p == node->internal_dso) {
212			if ((node->dso == NULL) && node->func == (void *)call) {
213				MUSL_LOGD("invalidate callback ptr=%{public}p when uninstall %{public}s", node->arg, p->name);
214				node->arg = NULL;
215			}
216		}
217	}
218	UNLOCK(lock);
219
220	return 0;
221}