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