1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <sched.h>
4 #include <signal.h>
5 #include <stdlib.h>
6 #include <sys/mman.h>
7 #include <sys/mount.h>
8 #include <sys/wait.h>
9 #include <util.h>
10
11 /* When we include libgen.h because we need dirname() we immediately
12 * undefine basename() since libgen.h defines it as a macro to the POSIX
13 * version which is really broken. We prefer GNU basename(). */
14 #include <libgen.h>
15 #undef basename
16
17 #include "sd-bus.h"
18
19 #include "alloc-util.h"
20 #include "bus-error.h"
21 #include "bus-locator.h"
22 #include "bus-util.h"
23 #include "bus-wait-for-jobs.h"
24 #include "cgroup-setup.h"
25 #include "cgroup-util.h"
26 #include "env-file.h"
27 #include "env-util.h"
28 #include "fd-util.h"
29 #include "fs-util.h"
30 #include "log.h"
31 #include "namespace-util.h"
32 #include "path-util.h"
33 #include "process-util.h"
34 #include "random-util.h"
35 #include "strv.h"
36 #include "tests.h"
37 #include "tmpfile-util.h"
38
setup_fake_runtime_dir(void)39 char* setup_fake_runtime_dir(void) {
40 char t[] = "/tmp/fake-xdg-runtime-XXXXXX", *p;
41
42 assert_se(mkdtemp(t));
43 assert_se(setenv("XDG_RUNTIME_DIR", t, 1) >= 0);
44 assert_se(p = strdup(t));
45
46 return p;
47 }
48
load_testdata_env(void)49 static void load_testdata_env(void) {
50 static bool called = false;
51 _cleanup_free_ char *s = NULL, *d = NULL, *envpath = NULL;
52 _cleanup_strv_free_ char **pairs = NULL;
53 int r;
54
55 if (called)
56 return;
57 called = true;
58
59 assert_se(readlink_and_make_absolute("/proc/self/exe", &s) >= 0);
60 assert_se(path_extract_directory(s, &d) >= 0);
61 assert_se(envpath = path_join(d, "systemd-runtest.env"));
62
63 r = load_env_file_pairs(NULL, envpath, &pairs);
64 if (r < 0) {
65 log_debug_errno(r, "Reading %s failed: %m", envpath);
66 return;
67 }
68
69 STRV_FOREACH_PAIR(k, v, pairs)
70 assert_se(setenv(*k, *v, 0) >= 0);
71 }
72
get_testdata_dir(const char * suffix,char ** ret)73 int get_testdata_dir(const char *suffix, char **ret) {
74 const char *dir;
75 char *p;
76
77 load_testdata_env();
78
79 /* if the env var is set, use that */
80 dir = getenv("SYSTEMD_TEST_DATA");
81 if (!dir)
82 dir = SYSTEMD_TEST_DATA;
83 if (access(dir, F_OK) < 0)
84 return log_error_errno(errno, "ERROR: $SYSTEMD_TEST_DATA directory [%s] not accessible: %m", dir);
85
86 p = path_join(dir, suffix);
87 if (!p)
88 return log_oom();
89
90 *ret = p;
91 return 0;
92 }
93
get_catalog_dir(void)94 const char* get_catalog_dir(void) {
95 const char *env;
96
97 load_testdata_env();
98
99 /* if the env var is set, use that */
100 env = getenv("SYSTEMD_CATALOG_DIR");
101 if (!env)
102 env = SYSTEMD_CATALOG_DIR;
103 if (access(env, F_OK) < 0) {
104 fprintf(stderr, "ERROR: $SYSTEMD_CATALOG_DIR directory [%s] does not exist\n", env);
105 exit(EXIT_FAILURE);
106 }
107 return env;
108 }
109
slow_tests_enabled(void)110 bool slow_tests_enabled(void) {
111 int r;
112
113 r = getenv_bool("SYSTEMD_SLOW_TESTS");
114 if (r >= 0)
115 return r;
116
117 if (r != -ENXIO)
118 log_warning_errno(r, "Cannot parse $SYSTEMD_SLOW_TESTS, ignoring.");
119 return SYSTEMD_SLOW_TESTS_DEFAULT;
120 }
121
test_setup_logging(int level)122 void test_setup_logging(int level) {
123 log_set_max_level(level);
124 log_parse_environment();
125 log_open();
126 }
127
log_tests_skipped(const char * message)128 int log_tests_skipped(const char *message) {
129 log_notice("%s: %s, skipping tests.",
130 program_invocation_short_name, message);
131 return EXIT_TEST_SKIP;
132 }
133
log_tests_skipped_errno(int r,const char * message)134 int log_tests_skipped_errno(int r, const char *message) {
135 log_notice_errno(r, "%s: %s, skipping tests: %m",
136 program_invocation_short_name, message);
137 return EXIT_TEST_SKIP;
138 }
139
write_tmpfile(char * pattern,const char * contents)140 int write_tmpfile(char *pattern, const char *contents) {
141 _cleanup_close_ int fd = -1;
142
143 assert(pattern);
144 assert(contents);
145
146 fd = mkostemp_safe(pattern);
147 if (fd < 0)
148 return fd;
149
150 ssize_t l = strlen(contents);
151 errno = 0;
152 if (write(fd, contents, l) != l)
153 return errno_or_else(EIO);
154 return 0;
155 }
156
have_namespaces(void)157 bool have_namespaces(void) {
158 siginfo_t si = {};
159 pid_t pid;
160
161 /* Checks whether namespaces are available. In some cases they aren't. We do this by calling unshare(), and we
162 * do so in a child process in order not to affect our own process. */
163
164 pid = fork();
165 assert_se(pid >= 0);
166
167 if (pid == 0) {
168 /* child */
169 if (detach_mount_namespace() < 0)
170 _exit(EXIT_FAILURE);
171
172 _exit(EXIT_SUCCESS);
173 }
174
175 assert_se(waitid(P_PID, pid, &si, WEXITED) >= 0);
176 assert_se(si.si_code == CLD_EXITED);
177
178 if (si.si_status == EXIT_SUCCESS)
179 return true;
180
181 if (si.si_status == EXIT_FAILURE)
182 return false;
183
184 assert_not_reached();
185 }
186
can_memlock(void)187 bool can_memlock(void) {
188 /* Let's see if we can mlock() a larger blob of memory. BPF programs are charged against
189 * RLIMIT_MEMLOCK, hence let's first make sure we can lock memory at all, and skip the test if we
190 * cannot. Why not check RLIMIT_MEMLOCK explicitly? Because in container environments the
191 * RLIMIT_MEMLOCK value we see might not match the RLIMIT_MEMLOCK value actually in effect. */
192
193 void *p = mmap(NULL, CAN_MEMLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED, -1, 0);
194 if (p == MAP_FAILED)
195 return false;
196
197 bool b = mlock(p, CAN_MEMLOCK_SIZE) >= 0;
198 if (b)
199 assert_se(munlock(p, CAN_MEMLOCK_SIZE) >= 0);
200
201 assert_se(munmap(p, CAN_MEMLOCK_SIZE) >= 0);
202 return b;
203 }
204
allocate_scope(void)205 static int allocate_scope(void) {
206 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
207 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
208 _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
209 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
210 _cleanup_free_ char *scope = NULL;
211 const char *object;
212 int r;
213
214 /* Let's try to run this test in a scope of its own, with delegation turned on, so that PID 1 doesn't
215 * interfere with our cgroup management. */
216
217 r = sd_bus_default_system(&bus);
218 if (r < 0)
219 return log_error_errno(r, "Failed to connect to system bus: %m");
220
221 r = bus_wait_for_jobs_new(bus, &w);
222 if (r < 0)
223 return log_oom();
224
225 if (asprintf(&scope, "%s-%" PRIx64 ".scope", program_invocation_short_name, random_u64()) < 0)
226 return log_oom();
227
228 r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit");
229 if (r < 0)
230 return bus_log_create_error(r);
231
232 /* Name and Mode */
233 r = sd_bus_message_append(m, "ss", scope, "fail");
234 if (r < 0)
235 return bus_log_create_error(r);
236
237 /* Properties */
238 r = sd_bus_message_open_container(m, 'a', "(sv)");
239 if (r < 0)
240 return bus_log_create_error(r);
241
242 r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, (uint32_t) getpid_cached());
243 if (r < 0)
244 return bus_log_create_error(r);
245
246 r = sd_bus_message_append(m, "(sv)", "Delegate", "b", 1);
247 if (r < 0)
248 return bus_log_create_error(r);
249
250 r = sd_bus_message_append(m, "(sv)", "CollectMode", "s", "inactive-or-failed");
251 if (r < 0)
252 return bus_log_create_error(r);
253
254 r = sd_bus_message_close_container(m);
255 if (r < 0)
256 return bus_log_create_error(r);
257
258 /* Auxiliary units */
259 r = sd_bus_message_append(m, "a(sa(sv))", 0);
260 if (r < 0)
261 return bus_log_create_error(r);
262
263 r = sd_bus_call(bus, m, 0, &error, &reply);
264 if (r < 0)
265 return log_error_errno(r, "Failed to start transient scope unit: %s", bus_error_message(&error, r));
266
267 r = sd_bus_message_read(reply, "o", &object);
268 if (r < 0)
269 return bus_log_parse_error(r);
270
271 r = bus_wait_for_jobs_one(w, object, false, NULL);
272 if (r < 0)
273 return r;
274
275 return 0;
276 }
277
enter_cgroup(char ** ret_cgroup,bool enter_subroot)278 static int enter_cgroup(char **ret_cgroup, bool enter_subroot) {
279 _cleanup_free_ char *cgroup_root = NULL, *cgroup_subroot = NULL;
280 CGroupMask supported;
281 int r;
282
283 r = allocate_scope();
284 if (r < 0)
285 log_warning_errno(r, "Couldn't allocate a scope unit for this test, proceeding without.");
286
287 r = cg_pid_get_path(NULL, 0, &cgroup_root);
288 if (r == -ENOMEDIUM)
289 return log_warning_errno(r, "cg_pid_get_path(NULL, 0, ...) failed: %m");
290 assert(r >= 0);
291
292 if (enter_subroot)
293 assert_se(asprintf(&cgroup_subroot, "%s/%" PRIx64, cgroup_root, random_u64()) >= 0);
294 else {
295 cgroup_subroot = strdup(cgroup_root);
296 assert_se(cgroup_subroot != NULL);
297 }
298
299 assert_se(cg_mask_supported(&supported) >= 0);
300
301 /* If this fails, then we don't mind as the later cgroup operations will fail too, and it's fine if
302 * we handle any errors at that point. */
303
304 r = cg_create_everywhere(supported, _CGROUP_MASK_ALL, cgroup_subroot);
305 if (r < 0)
306 return r;
307
308 r = cg_attach_everywhere(supported, cgroup_subroot, 0, NULL, NULL);
309 if (r < 0)
310 return r;
311
312 if (ret_cgroup)
313 *ret_cgroup = TAKE_PTR(cgroup_subroot);
314
315 return 0;
316 }
317
enter_cgroup_subroot(char ** ret_cgroup)318 int enter_cgroup_subroot(char **ret_cgroup) {
319 return enter_cgroup(ret_cgroup, true);
320 }
321
enter_cgroup_root(char ** ret_cgroup)322 int enter_cgroup_root(char **ret_cgroup) {
323 return enter_cgroup(ret_cgroup, false);
324 }
325
ci_environment(void)326 const char *ci_environment(void) {
327 /* We return a string because we might want to provide multiple bits of information later on: not
328 * just the general CI environment type, but also whether we're sanitizing or not, etc. The caller is
329 * expected to use strstr on the returned value. */
330 static const char *ans = POINTER_MAX;
331 int r;
332
333 if (ans != POINTER_MAX)
334 return ans;
335
336 /* We allow specifying the environment with $CITYPE. Nobody uses this so far, but we are ready. */
337 const char *citype = getenv("CITYPE");
338 if (!isempty(citype))
339 return (ans = citype);
340
341 if (getenv_bool("TRAVIS") > 0)
342 return (ans = "travis");
343 if (getenv_bool("SEMAPHORE") > 0)
344 return (ans = "semaphore");
345 if (getenv_bool("GITHUB_ACTIONS") > 0)
346 return (ans = "github-actions");
347 if (getenv("AUTOPKGTEST_ARTIFACTS") || getenv("AUTOPKGTEST_TMP"))
348 return (ans = "autopkgtest");
349
350 FOREACH_STRING(var, "CI", "CONTINOUS_INTEGRATION") {
351 /* Those vars are booleans according to Semaphore and Travis docs:
352 * https://docs.travis-ci.com/user/environment-variables/#default-environment-variables
353 * https://docs.semaphoreci.com/ci-cd-environment/environment-variables/#ci
354 */
355 r = getenv_bool(var);
356 if (r > 0)
357 return (ans = "unknown"); /* Some other unknown thing */
358 if (r == 0)
359 return (ans = NULL);
360 }
361
362 return (ans = NULL);
363 }
364