xref: /src/lib/libc/tests/stdlib/system_test.c (revision 863b5c137a98d29dc6964cba0e0c4fe2a8bebab8)
1 /*-
2  * Copyright (c) 2026 Klara, Inc.
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  */
6 
7 #include <sys/wait.h>
8 
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <pthread.h>
12 #include <signal.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <unistd.h>
16 
17 #include <atf-c.h>
18 
19 ATF_TC(system_true);
ATF_TC_HEAD(system_true,tc)20 ATF_TC_HEAD(system_true, tc)
21 {
22 	atf_tc_set_md_var(tc, "descr", "system(\"true\")");
23 }
ATF_TC_BODY(system_true,tc)24 ATF_TC_BODY(system_true, tc)
25 {
26 	ATF_REQUIRE_EQ(W_EXITCODE(0, 0), system("true"));
27 }
28 
29 ATF_TC(system_false);
ATF_TC_HEAD(system_false,tc)30 ATF_TC_HEAD(system_false, tc)
31 {
32 	atf_tc_set_md_var(tc, "descr", "system(\"false\")");
33 }
ATF_TC_BODY(system_false,tc)34 ATF_TC_BODY(system_false, tc)
35 {
36 	ATF_REQUIRE_EQ(W_EXITCODE(1, 0), system("false"));
37 }
38 
39 ATF_TC(system_touch);
ATF_TC_HEAD(system_touch,tc)40 ATF_TC_HEAD(system_touch, tc)
41 {
42 	atf_tc_set_md_var(tc, "descr", "system(\"touch file\")");
43 }
ATF_TC_BODY(system_touch,tc)44 ATF_TC_BODY(system_touch, tc)
45 {
46 	/* The file does not exist */
47 	ATF_CHECK_ERRNO(ENOENT, unlink("file"));
48 
49 	/* Run a command that creates it */
50 	ATF_REQUIRE_EQ(W_EXITCODE(0, 0), system("touch file"));
51 
52 	/* Now the file exists */
53 	ATF_CHECK_EQ(0, unlink("file"));
54 }
55 
56 ATF_TC(system_null);
ATF_TC_HEAD(system_null,tc)57 ATF_TC_HEAD(system_null, tc)
58 {
59 	atf_tc_set_md_var(tc, "descr", "system(NULL)");
60 	atf_tc_set_md_var(tc, "require.user", "root");
61 }
ATF_TC_BODY(system_null,tc)62 ATF_TC_BODY(system_null, tc)
63 {
64 	/* First, test in a normal environment */
65 	ATF_REQUIRE_EQ(1, system(NULL));
66 
67 	/* Now enter an empty chroot */
68 	ATF_REQUIRE_EQ(0, chroot("."));
69 	ATF_REQUIRE_EQ(0, chdir("/"));
70 
71 	/* Test again with no shell available */
72 	ATF_REQUIRE_EQ(0, system(NULL));
73 	ATF_REQUIRE_EQ(W_EXITCODE(127, 0), system("true"));
74 }
75 
76 /*
77  * Define PROCMASK_IS_THREADMASK if sigprocmask() gets / sets the thread
78  * mask in multithreaded programs, which makes it impossible to verify
79  * that system(3) correctly blocks and unblocks SIGCHLD.
80  */
81 #ifdef __FreeBSD__
82 #define PROCMASK_IS_THREADMASK 1
83 #endif
84 
85 static void *
system_thread(void * arg)86 system_thread(void *arg)
87 {
88 	char cmd[64];
89 	int i = (int)(intptr_t)arg;
90 
91 	snprintf(cmd, sizeof(cmd), "rm flag%d ; lockf -ns lock%d true", i, i);
92 	return ((void *)(intptr_t)system(cmd));
93 }
94 
95 static inline int
sigcmpset(const sigset_t * a,const sigset_t * b)96 sigcmpset(const sigset_t *a, const sigset_t *b)
97 {
98 	return (memcmp(a, b, sizeof(sigset_t)));
99 }
100 
101 ATF_TC(system_concurrent);
ATF_TC_HEAD(system_concurrent,tc)102 ATF_TC_HEAD(system_concurrent, tc)
103 {
104 	atf_tc_set_md_var(tc, "descr", "Concurrent calls");
105 }
ATF_TC_BODY(system_concurrent,tc)106 ATF_TC_BODY(system_concurrent, tc)
107 {
108 	enum { N = 3 };
109 	struct sigaction sigint, sigquit, sigact;
110 	sigset_t normset, sigset;
111 	pthread_t thr[N];
112 	char fn[8];
113 	int fd[N];
114 	void *arg, *ret;
115 
116 	/* Create and lock the locks */
117 	for (int i = 0; i < N; i++) {
118 		snprintf(fn, sizeof(fn), "lock%d", i);
119 		fd[i] = open(fn, O_CREAT|O_EXCL|O_EXLOCK|O_CLOEXEC, 0644);
120 		ATF_REQUIRE_MSG(fd[i] >= 0, "%s: %m", fn);
121 	}
122 
123 	/* Create the flags */
124 	for (int i = 0; i < N; i++) {
125 		snprintf(fn, sizeof(fn), "flag%d", i);
126 		ATF_REQUIRE_EQ(0, symlink(fn, fn));
127 	}
128 
129 	/* Save the current signal dispositions */
130 	ATF_REQUIRE_EQ(0, sigaction(SIGINT, NULL, &sigint));
131 	ATF_REQUIRE_EQ(0, sigaction(SIGQUIT, NULL, &sigquit));
132 	ATF_REQUIRE_EQ(0, sigprocmask(0, NULL, &normset));
133 
134 	/* Spawn threads which block on these files */
135 	for (int i = 0; i < N; i++) {
136 		arg = (void *)(intptr_t)i;
137 		ATF_REQUIRE_INTEQ(0,
138 		    pthread_create(&thr[i], NULL, system_thread, arg));
139 	}
140 
141 	/* Wait until the flags are gone */
142 	for (int i = 0; i < N; i++) {
143 		snprintf(fn, sizeof(fn), "flag%d", i);
144 		while (readlink(fn, fn, sizeof(fn)) > 0)
145 			usleep(10000);
146 		ATF_REQUIRE_EQ(ENOENT, errno);
147 	}
148 
149 	/* Release the locks */
150 	for (int i = 0; i < N; i++) {
151 		/* Check the signal dispositions */
152 		ATF_REQUIRE_EQ(0, sigaction(SIGINT, NULL, &sigact));
153 		ATF_CHECK_EQ(SIG_IGN, sigact.sa_handler);
154 		ATF_REQUIRE_EQ(0, sigaction(SIGQUIT, NULL, &sigact));
155 		ATF_CHECK_EQ(SIG_IGN, sigact.sa_handler);
156 #ifndef PROCMASK_IS_THREADMASK
157 		ATF_REQUIRE_EQ(0, sigprocmask(0, NULL, &sigset));
158 		ATF_CHECK(sigismember(&sigset, SIGCHLD));
159 #endif
160 
161 		/* Close the file, releasing the lock */
162 		ATF_REQUIRE_INTEQ(0, close(fd[i]));
163 
164 		/* Join the thread and check the return value */
165 		ATF_CHECK_INTEQ(0, pthread_join(thr[i], &ret));
166 		ATF_CHECK_INTEQ(W_EXITCODE(0, 0), (int)(intptr_t)ret);
167 	}
168 
169 	/* Check the signal dispositions */
170 	ATF_REQUIRE_EQ(0, sigaction(SIGINT, NULL, &sigact));
171 	ATF_CHECK_EQ(sigint.sa_handler, sigact.sa_handler);
172 	ATF_CHECK_EQ(sigint.sa_flags, sigact.sa_flags);
173 	ATF_CHECK_EQ(0, sigcmpset(&sigint.sa_mask, &sigact.sa_mask));
174 	ATF_REQUIRE_EQ(0, sigaction(SIGQUIT, NULL, &sigact));
175 	ATF_CHECK_EQ(sigquit.sa_handler, sigact.sa_handler);
176 	ATF_CHECK_EQ(sigquit.sa_flags, sigact.sa_flags);
177 	ATF_CHECK_EQ(0, sigcmpset(&sigquit.sa_mask, &sigact.sa_mask));
178 	ATF_REQUIRE_EQ(0, sigprocmask(0, NULL, &sigset));
179 	ATF_CHECK_EQ(0, sigcmpset(&sigset, &normset));
180 }
181 
ATF_TP_ADD_TCS(tp)182 ATF_TP_ADD_TCS(tp)
183 {
184 	ATF_TP_ADD_TC(tp, system_true);
185 	ATF_TP_ADD_TC(tp, system_false);
186 	ATF_TP_ADD_TC(tp, system_touch);
187 	ATF_TP_ADD_TC(tp, system_null);
188 	ATF_TP_ADD_TC(tp, system_concurrent);
189 	return (atf_no_error());
190 }
191