1 // SPDX-License-Identifier: CDDL-1.0 2 /* 3 * CDDL HEADER START 4 * 5 * The contents of this file are subject to the terms of the 6 * Common Development and Distribution License (the "License"). 7 * You may not use this file except in compliance with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or https://opensource.org/licenses/CDDL-1.0. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 23 /* 24 * Copyright (c) 2024 by Triad National Security, LLC. 25 */ 26 27 #include <sys/types.h> 28 #include <sys/stat.h> 29 #include <errno.h> 30 #include <fcntl.h> 31 #include <stdio.h> 32 #include <unistd.h> 33 #include <stdlib.h> 34 #include <string.h> 35 #include <time.h> 36 #include <pthread.h> 37 #include <assert.h> 38 39 #ifndef MIN 40 #define MIN(a, b) ((a) < (b)) ? (a) : (b) 41 #endif 42 43 static char *filename = NULL; 44 static int blocksize = 131072; /* 128K */ 45 static int err_expected = 0; 46 static int read_op = 0; 47 static int write_op = 0; 48 static int numblocks = 100; 49 static char *execname = NULL; 50 static int print_usage = 0; 51 static int randompattern = 0; 52 static int fd; 53 char *buf = NULL; 54 55 typedef struct { 56 int entire_file_completed; 57 } pthread_args_t; 58 59 static void 60 usage(void) 61 { 62 (void) fprintf(stderr, 63 "usage %s -f filename [-b blocksize] [-e wr_error_expected]\n" 64 " [-n numblocks] [-p randompattern] -r read_op \n" 65 " -w write_op [-h help]\n" 66 "\n" 67 "Testing whether checksum verify works correctly for O_DIRECT.\n" 68 "when manipulating the contents of a userspace buffer.\n" 69 "\n" 70 " filename: File to read or write to.\n" 71 " blocksize: Size of each block to write (must be at \n" 72 " least >= 512).\n" 73 " err_expected: Whether write() is expected to return EIO\n" 74 " while manipulating the contents of the\n" 75 " buffer.\n" 76 " numblocks: Total number of blocksized blocks to\n" 77 " write.\n" 78 " read_op: Perform reads to the filename file while\n" 79 " while manipulating the buffer contents\n" 80 " write_op: Perform writes to the filename file while\n" 81 " manipulating the buffer contents\n" 82 " randompattern: Fill data buffer with random data for \n" 83 " writes. Default behavior is to fill the \n" 84 " buffer with known data pattern (0xdeadbeef)\n" 85 " help: Print usage information and exit.\n" 86 "\n" 87 " Required parameters:\n" 88 " filename\n" 89 " read_op or write_op\n" 90 "\n" 91 " Default Values:\n" 92 " blocksize -> 131072\n" 93 " wr_err_expexted -> false\n" 94 " numblocks -> 100\n" 95 " randompattern -> false\n", 96 execname); 97 (void) exit(1); 98 } 99 100 static void 101 parse_options(int argc, char *argv[]) 102 { 103 int c; 104 int errflag = 0; 105 extern char *optarg; 106 extern int optind, optopt; 107 execname = argv[0]; 108 109 while ((c = getopt(argc, argv, "b:ef:hn:rw")) != -1) { 110 switch (c) { 111 case 'b': 112 blocksize = atoi(optarg); 113 break; 114 115 case 'e': 116 err_expected = 1; 117 break; 118 119 case 'f': 120 filename = optarg; 121 break; 122 123 124 case 'h': 125 print_usage = 1; 126 break; 127 128 case 'n': 129 numblocks = atoi(optarg); 130 break; 131 132 case 'r': 133 read_op = 1; 134 break; 135 136 case 'w': 137 write_op = 1; 138 break; 139 140 case ':': 141 (void) fprintf(stderr, 142 "Option -%c requires an opertand\n", 143 optopt); 144 errflag++; 145 break; 146 case '?': 147 default: 148 (void) fprintf(stderr, 149 "Unrecognized option: -%c\n", optopt); 150 errflag++; 151 break; 152 } 153 } 154 155 if (errflag || print_usage == 1) 156 (void) usage(); 157 158 if (blocksize < 512 || filename == NULL || numblocks <= 0 || 159 (read_op == 0 && write_op == 0)) { 160 (void) fprintf(stderr, 161 "Required paramater(s) missing or invalid.\n"); 162 (void) usage(); 163 } 164 } 165 166 /* 167 * Write blocksize * numblocks to the file using O_DIRECT. 168 */ 169 static void * 170 write_thread(void *arg) 171 { 172 size_t offset = 0; 173 int total_data = blocksize * numblocks; 174 int left = total_data; 175 ssize_t wrote = 0; 176 pthread_args_t *args = (pthread_args_t *)arg; 177 178 while (!args->entire_file_completed) { 179 wrote = pwrite(fd, buf, blocksize, offset); 180 if (wrote != blocksize) { 181 if (err_expected) 182 assert(errno == EIO); 183 else 184 exit(2); 185 } 186 187 offset = ((offset + blocksize) % total_data); 188 left -= blocksize; 189 190 if (left == 0) 191 args->entire_file_completed = 1; 192 } 193 194 pthread_exit(NULL); 195 } 196 197 /* 198 * Read blocksize * numblocks to the file using O_DIRECT. 199 */ 200 static void * 201 read_thread(void *arg) 202 { 203 size_t offset = 0; 204 int total_data = blocksize * numblocks; 205 int left = total_data; 206 ssize_t read = 0; 207 pthread_args_t *args = (pthread_args_t *)arg; 208 209 while (!args->entire_file_completed) { 210 read = pread(fd, buf, blocksize, offset); 211 if (read != blocksize) { 212 exit(2); 213 } 214 215 offset = ((offset + blocksize) % total_data); 216 left -= blocksize; 217 218 if (left == 0) 219 args->entire_file_completed = 1; 220 } 221 222 pthread_exit(NULL); 223 } 224 225 /* 226 * Update the buffers contents with random data. 227 */ 228 static void * 229 manipulate_buf_thread(void *arg) 230 { 231 size_t rand_offset; 232 char rand_char; 233 pthread_args_t *args = (pthread_args_t *)arg; 234 235 while (!args->entire_file_completed) { 236 rand_offset = (rand() % blocksize); 237 rand_char = (rand() % (126 - 33) + 33); 238 buf[rand_offset] = rand_char; 239 } 240 241 pthread_exit(NULL); 242 } 243 244 int 245 main(int argc, char *argv[]) 246 { 247 const char *datapattern = "0xdeadbeef"; 248 int fd_flags = O_DIRECT; 249 mode_t mode = S_IRUSR | S_IWUSR; 250 pthread_t io_thr; 251 pthread_t manipul_thr; 252 int left = blocksize; 253 int offset = 0; 254 int rc; 255 pthread_args_t args = { 0 }; 256 257 parse_options(argc, argv); 258 259 if (write_op) { 260 fd_flags |= (O_WRONLY | O_CREAT); 261 } else { 262 fd_flags |= O_RDONLY; 263 } 264 265 fd = open(filename, fd_flags, mode); 266 if (fd == -1) { 267 (void) fprintf(stderr, "%s, %s\n", execname, filename); 268 perror("open"); 269 exit(2); 270 } 271 272 int err = posix_memalign((void **)&buf, sysconf(_SC_PAGE_SIZE), 273 blocksize); 274 if (err != 0) { 275 (void) fprintf(stderr, 276 "%s: %s\n", execname, strerror(err)); 277 exit(2); 278 } 279 280 if (write_op) { 281 if (!randompattern) { 282 /* Putting known data pattern in buffer */ 283 while (left) { 284 size_t amt = MIN(strlen(datapattern), left); 285 memcpy(&buf[offset], datapattern, amt); 286 offset += amt; 287 left -= amt; 288 } 289 } else { 290 /* Putting random data in buffer */ 291 for (int i = 0; i < blocksize; i++) 292 buf[i] = rand(); 293 } 294 } 295 296 if ((rc = pthread_create(&manipul_thr, NULL, manipulate_buf_thread, 297 &args))) { 298 fprintf(stderr, "error: pthreads_create, manipul_thr, " 299 "rc: %d\n", rc); 300 exit(2); 301 } 302 303 if (write_op) { 304 /* 305 * Writing using O_DIRECT while manipulating the buffer contents 306 * until the entire file is written. 307 */ 308 if ((rc = pthread_create(&io_thr, NULL, write_thread, &args))) { 309 fprintf(stderr, "error: pthreads_create, io_thr, " 310 "rc: %d\n", rc); 311 exit(2); 312 } 313 } else { 314 /* 315 * Reading using O_DIRECT while manipulating the buffer contents 316 * until the entire file is read. 317 */ 318 if ((rc = pthread_create(&io_thr, NULL, read_thread, &args))) { 319 fprintf(stderr, "error: pthreads_create, io_thr, " 320 "rc: %d\n", rc); 321 exit(2); 322 } 323 } 324 325 pthread_join(io_thr, NULL); 326 pthread_join(manipul_thr, NULL); 327 328 assert(args.entire_file_completed == 1); 329 330 (void) close(fd); 331 332 free(buf); 333 334 return (0); 335 } 336