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 2022 Oxide Computer Company
14 */
15
16 /*
17 * This is designed to act as a basic test of PORT_SOURCE_FILE associations and
18 * a regression test for illumos#14898. In particular we want to verify certain
19 * behaviors of association and disassociation with respect to the value in the
20 * user payload. We will create and tear down the underlying event port each
21 * time. The rough cases are:
22 *
23 * o associate, trigger, port_get -> first associate event
24 * o associate, associate, trigger, port_get -> second associate event
25 * o associate, trigger, associate, port_get -> second associate event
26 * o associate, disassociate, port_get -> no event
27 * o associate, trigger, disassociate, port_get -> no event
28 * o associate, trigger, disassociate, associate, port_get -> second associate
29 * event
30 * o associate, trigger, disassociate, fstat, associate, port_get -> no event
31 */
32
33 #include <port.h>
34 #include <err.h>
35 #include <stdio.h>
36 #include <unistd.h>
37 #include <fcntl.h>
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <errno.h>
41 #include <stdlib.h>
42 #include <strings.h>
43 #include <stdbool.h>
44 #include <sys/sysmacros.h>
45
46 static int fa_nfail = 0;
47 static uintptr_t fa_user = 1;
48 static char *fa_path;
49
50 /*
51 * This is a series of actions that we want to be able to take on our port. We
52 * keep going until we do encounter a FA_DONE, at which point we do a
53 * port_get() to compare things.
54 */
55 typedef enum {
56 FA_DONE,
57 FA_ASSOC,
58 FA_DEASSOC,
59 FA_FSTAT,
60 FA_TRIGGER
61 } fa_act_t;
62
63 #define FA_MAX_EVENTS 6
64
65 typedef struct {
66 bool fa_getevent;
67 const char *fa_msg;
68 fa_act_t fa_acts[FA_MAX_EVENTS];
69 } fa_test_t;
70
71 fa_test_t fa_tests[] = {
72 { false, "port_get -> no event",
73 { FA_TRIGGER, FA_DONE } },
74 { false, "associate, port_get -> no event",
75 { FA_ASSOC, FA_DONE } },
76 { true, "associate, trigger, port_get -> first user",
77 { FA_ASSOC, FA_TRIGGER, FA_DONE } },
78 { true, "associate, associate, trigger, port_get -> second user",
79 { FA_ASSOC, FA_ASSOC, FA_TRIGGER, FA_DONE } },
80 { true, "associate, trigger, associate, port_get -> second user",
81 { FA_ASSOC, FA_TRIGGER, FA_ASSOC, FA_DONE } },
82 { false, "associate, disassociate, port_get -> no event",
83 { FA_ASSOC, FA_DEASSOC, FA_DONE } },
84 { false, "associate, trigger, disassociate, port_get -> no event",
85 { FA_ASSOC, FA_TRIGGER, FA_DEASSOC, FA_DONE } },
86 { true, "associate, trigger, disassociate, associate, port_get -> "
87 "second user", { FA_ASSOC, FA_TRIGGER, FA_DEASSOC, FA_ASSOC,
88 FA_DONE } },
89 { false, "associate, trigger, disassociate, fstat, associate, port_get "
90 "-> no event", { FA_ASSOC, FA_TRIGGER, FA_DEASSOC, FA_FSTAT,
91 FA_ASSOC, FA_DONE } },
92 };
93
94 static void
fa_run_test(int portfd,int filefd,fa_test_t * test)95 fa_run_test(int portfd, int filefd, fa_test_t *test)
96 {
97 int ret;
98 uint_t nget;
99 struct stat st;
100 struct file_obj fo;
101 port_event_t pe;
102 struct timespec to;
103 bool pass;
104
105 /*
106 * At the beginning of a test we stat our underlying file so we can make
107 * sure our information is up to date. We purposefully keep it the same
108 * across a run so that way certain tests will automatically trigger an
109 * event on association.
110 */
111 if (fstat(filefd, &st) != 0) {
112 warn("failed to stat %s", fa_path);
113 (void) printf("TEST FAILED: %s\n", test->fa_msg);
114 fa_nfail = 1;
115 return;
116 }
117
118 bzero(&fo, sizeof (fo));
119
120 for (uint_t i = 0; test->fa_acts[i] != FA_DONE; i++) {
121 uint32_t data;
122
123 switch (test->fa_acts[i]) {
124 case FA_ASSOC:
125 bzero(&fo, sizeof (fo));
126 fo.fo_atime = st.st_atim;
127 fo.fo_mtime = st.st_mtim;
128 fo.fo_ctime = st.st_ctim;
129 fo.fo_name = fa_path;
130
131 fa_user++;
132 if (port_associate(portfd, PORT_SOURCE_FILE,
133 (uintptr_t)&fo, FILE_MODIFIED, (void *)fa_user) <
134 0) {
135 warn("failed to associate event");
136 fa_nfail = 1;
137 }
138 break;
139 case FA_DEASSOC:
140 if (port_dissociate(portfd, PORT_SOURCE_FILE,
141 (uintptr_t)&fo) != 0) {
142 warn("failed to dissociate event");
143 fa_nfail = 1;
144 }
145 break;
146 case FA_FSTAT:
147 if (fstat(filefd, &st) != 0) {
148 warn("failed to stat %s", fa_path);
149 fa_nfail = 1;
150 }
151 break;
152 case FA_TRIGGER:
153 data = arc4random();
154 if (write(filefd, &data, sizeof (data)) < 0) {
155 warn("failed to write data to %s", fa_path);
156 }
157 break;
158 default:
159 abort();
160 }
161 }
162
163 /*
164 * At this point we attempt to see if there's an event for us. We
165 * explicitly zero the timeout so we don't wait at all.
166 */
167 bzero(&to, sizeof (to));
168 bzero(&pe, sizeof (pe));
169 nget = 1;
170 ret = port_getn(portfd, &pe, 1, &nget, &to);
171 if (ret < 0) {
172 warn("port_getn failed unexpectedly");
173 (void) printf("TEST FAILED: %s\n", test->fa_msg);
174 fa_nfail = 1;
175 return;
176 }
177
178 if (!test->fa_getevent) {
179 if (nget != 0) {
180 warnx("port_getn() returned an event, but we expected "
181 "none");
182 (void) printf("portev_events: 0x%x, portev_source: "
183 "0x%x\n", pe.portev_events, pe.portev_source);
184 (void) printf("TEST FAILED: %s\n", test->fa_msg);
185 fa_nfail = 1;
186 } else {
187 (void) printf("TEST PASSED: %s\n", test->fa_msg);
188 }
189 return;
190 } else {
191 if (nget == 0) {
192 warnx("port_getn() returned no events, but we expected "
193 "one");
194 (void) printf("TEST FAILED: %s\n", test->fa_msg);
195 fa_nfail = 1;
196 return;
197 }
198 }
199
200 pass = true;
201 if (pe.portev_source != PORT_SOURCE_FILE) {
202 (void) printf("port source mismatch: found 0x%x, expected "
203 "0x%x\n", pe.portev_source, PORT_SOURCE_FILE);
204 pass = false;
205 }
206
207 if (pe.portev_events != FILE_MODIFIED) {
208 (void) printf("port events mismatch: found 0x%x, expected "
209 "0x%x\n", pe.portev_events, FILE_MODIFIED);
210 pass = false;
211 }
212
213 if ((uintptr_t)pe.portev_user != fa_user) {
214 (void) printf("port user mismatch: found 0x%p, expected "
215 "0x%lx\n", pe.portev_user, fa_user);
216 pass = false;
217
218 }
219
220 if (pass) {
221 (void) printf("TEST PASSED: %s\n", test->fa_msg);
222 } else {
223 fa_nfail = 1;
224 (void) printf("TEST FAILED: %s\n", test->fa_msg);
225 }
226 }
227
228 int
main(void)229 main(void)
230 {
231 int fd;
232
233
234 if (asprintf(&fa_path, "/tmp/file_assoc_test.%d", getpid()) < 0) {
235 err(EXIT_FAILURE, "failed to create temp file");
236 }
237
238 fd = open(fa_path, O_RDWR | O_CREAT, 0644);
239 if (fd < 0) {
240 err(EXIT_FAILURE, "failed to create %s", fa_path);
241 }
242
243 /*
244 * We open and close the underlying port that we're using for each run
245 * to make sure that any associations that were created do not persist.
246 */
247 for (uint_t i = 0; i < ARRAY_SIZE(fa_tests); i++) {
248 int port = port_create();
249 if (port < 0) {
250 err(EXIT_FAILURE, "failed to create event port");
251 }
252 fa_run_test(port, fd, &fa_tests[i]);
253 (void) close(port);
254 }
255
256 (void) close(fd);
257 (void) unlink(fa_path);
258 return (fa_nfail);
259 }
260