xref: /illumos-gate/usr/src/test/libc-tests/tests/aligned_alloc.c (revision 8119dad84d6416f13557b0ba8e2aaf9064cbcfd3)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2016 Joyent, Inc.
14  * Copyright 2024 Oxide Computer Company
15  */
16 
17 /*
18  * Basic tests for aligned_alloc(3C). Note that we test ENOMEM failure by
19  * relying on the implementation of the current libc malloc. Specifically we go
20  * through and add a mapping so we can't expand the heap and then use it up. If
21  * the memory allocator is ever changed, this test will start failing, at which
22  * point, it may not be worth the cost of keeping it around.
23  */
24 
25 #include <stdlib.h>
26 #include <errno.h>
27 #include <libproc.h>
28 #include <stdio.h>
29 #include <stdalign.h>
30 #include <err.h>
31 #include <sys/sysmacros.h>
32 #include <sys/mman.h>
33 #include <sys/debug.h>
34 #include <stdbool.h>
35 #include <string.h>
36 
37 typedef struct {
38 	size_t at_size;
39 	size_t at_align;
40 	int at_errno;
41 	const char *at_desc;
42 } alloc_test_t;
43 
44 static const alloc_test_t alloc_tests[] = {
45 	{ 0, sizeof (long), EINVAL, "zero alignment fails with EINVAL" },
46 	{ sizeof (long), 0, EINVAL, "zero size fails with EINVAL" },
47 	{ 128, 3, EINVAL, "3-byte alignment fails with EINVAL" },
48 	{ 128, 7777, EINVAL, "7777-byte alignment fails with EINVAL" },
49 	{ 128, 23, EINVAL, "23-byte alignment fails with EINVAL" },
50 	{ sizeof (char), alignof (char), 0, "alignof (char), 1 byte" },
51 	{ 5, alignof (char), 0, "alignof (char), multiple bytes" },
52 	{ 1, alignof (short), 0, "alignof (short), 1 byte" },
53 	{ 16, alignof (short), 0, "alignof (short), 16 byte" },
54 	{ 1, alignof (int), 0, "alignof (int), 1 byte" },
55 	{ 4, alignof (int), 0, "alignof (int), 4 bytes" },
56 	{ 22, alignof (int), 0, "alignof (int), 22 bytes" },
57 	/* We skip long here because it varies between ILP32/LP64 */
58 	{ 7, alignof (long long), 0, "alignof (long long), 7 bytes" },
59 	{ 128, alignof (long long), 0, "alignof (long long), 128 bytes" },
60 	{ 511, alignof (long long), 0, "alignof (long long), 511 bytes" },
61 	{ 16, 16, 0, "16-byte alignment), 16 bytes" },
62 	{ 256, 16, 0, "16-byte alignment), 256 bytes" },
63 	{ 256, 4096, 0, "4096-byte alignment), 256 bytes" },
64 	{ 4096, 4096, 0, "4096-byte alignment), 4096 bytes" },
65 };
66 
67 /*
68  * Disable the per-thread caches and enable debugging if launched with umem.
69  */
70 const char *
71 _umem_debug_init(void)
72 {
73 	return ("default,verbose");
74 }
75 
76 const char *
77 _umem_options_init(void)
78 {
79 	return ("perthreadcache=0");
80 }
81 
82 static bool
83 alloc_test_one(const alloc_test_t *test)
84 {
85 	bool ret = false;
86 	void *buf = aligned_alloc(test->at_align, test->at_size);
87 
88 	if (buf == NULL) {
89 		if (test->at_errno == 0) {
90 			warnx("TEST FAILED: %s: allocation failed with %s, but "
91 			    "expected success", test->at_desc,
92 			    strerrorname_np(errno));
93 		} else if (errno != test->at_errno) {
94 			warnx("TEST FAILED: %s: allocation failed with %s, but "
95 			    "expected errno %s", test->at_desc,
96 			    strerrorname_np(errno),
97 			    strerrorname_np(test->at_errno));
98 		} else {
99 			(void) printf("TEST PASSED: %s\n", test->at_desc);
100 			ret = true;
101 		}
102 	} else if (test->at_errno != 0) {
103 		warnx("TEST FAILED: %s: allocation succeeded, but expected "
104 		    "errno %s", test->at_desc, strerrorname_np(test->at_errno));
105 	} else {
106 		(void) printf("TEST PASSED: %s\n", test->at_desc);
107 		ret = true;
108 	}
109 
110 	free(buf);
111 	return (ret);
112 }
113 
114 static bool
115 alloc_test_enomem(const alloc_test_t *test)
116 {
117 	bool ret = false;
118 	void *buf = aligned_alloc(test->at_align, test->at_size);
119 
120 	if (buf != NULL) {
121 		warnx("TEST FAILED: %s (forced ENOMEM/EAGAIN): succeeded, but "
122 		    "expected ENOMEM", test->at_desc);
123 	} else if (errno != ENOMEM && errno != EAGAIN) {
124 		warnx("TEST FAILED: %s (forced ENOMEM/EAGAIN): failed with %s, "
125 		    "but expected ENOMEM", test->at_desc,
126 		    strerrordesc_np(errno));
127 	} else {
128 		(void) printf("TEST PASSED: %s: ENOMEM/EAGAIN forced\n",
129 		    test->at_desc);
130 		ret = true;
131 	}
132 
133 	return (ret);
134 }
135 
136 static void
137 libc_enomem(void)
138 {
139 	pstatus_t status;
140 
141 	VERIFY0(proc_get_status(getpid(), &status));
142 	VERIFY3P(mmap((caddr_t)P2ROUNDUP(status.pr_brkbase +
143 	    status.pr_brksize, 0x1000), 0x1000,
144 	    PROT_READ, MAP_ANON | MAP_FIXED | MAP_PRIVATE, -1, 0),
145 	    !=, (void *)-1);
146 
147 	for (;;) {
148 		if (malloc(16) == NULL)
149 			break;
150 	}
151 
152 	for (;;) {
153 		if (aligned_alloc(sizeof (void *), 16) == NULL)
154 			break;
155 	}
156 }
157 
158 /*
159  * Because this test is leveraging LD_PRELOAD in the test runner to test
160  * different malloc libraries, we can't call umem_setmtbf() directly. Instead we
161  * ask rtld to find it for us, which is a bit gross, but works.
162  */
163 static void
164 libumem_enomem(void)
165 {
166 	void (*mtbf)(uint32_t) = dlsym(RTLD_DEFAULT, "umem_setmtbf");
167 	if (mtbf == NULL) {
168 		errx(EXIT_FAILURE, "failed to find umem_setmtbf: %s",
169 		    dlerror());
170 	}
171 
172 	mtbf(1);
173 }
174 
175 int
176 main(void)
177 {
178 	const char *preload;
179 	int ret = EXIT_SUCCESS;
180 
181 	for (size_t i = 0; i < ARRAY_SIZE(alloc_tests); i++) {
182 		if (!alloc_test_one(&alloc_tests[i])) {
183 			ret = EXIT_FAILURE;
184 		}
185 	}
186 
187 	/*
188 	 * To catch failure tests, we need to know what memory allocator we're
189 	 * using which we expect to be indicated via an LD_PRELOAD environment
190 	 * variable. If it's not set, assume libc.
191 	 */
192 	preload = getenv("LD_PRELOAD");
193 	if (preload != NULL) {
194 		if (strstr(preload, "umem") != NULL) {
195 			libumem_enomem();
196 		} else {
197 			warnx("malloc(3C) library %s not supported, skipping "
198 			    "ENOMEM tests", preload);
199 			goto skip;
200 		}
201 	} else {
202 		libc_enomem();
203 	}
204 
205 	for (size_t i = 0; i < ARRAY_SIZE(alloc_tests); i++) {
206 		if (alloc_tests[i].at_errno != 0)
207 			continue;
208 		if (!alloc_test_enomem(&alloc_tests[i])) {
209 			ret = EXIT_FAILURE;
210 		}
211 	}
212 
213 skip:
214 	if (ret == EXIT_SUCCESS) {
215 		(void) printf("All tests passed successfully\n");
216 	}
217 
218 	return (ret);
219 }
220