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 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 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