| // SPDX-License-Identifier: GPL-2.0-or-newer | |
| /* | |
| * Copyright (c) 2019 Oracle. | |
| * All Rights Reserved. | |
| * | |
| * Create writable mappings to multiple files and write them all to test | |
| * concurrent mmap writes to the same shared blocks. | |
| */ | |
| #include <sys/mman.h> | |
| #include <sys/types.h> | |
| #include <sys/stat.h> | |
| #include <unistd.h> | |
| #include <fcntl.h> | |
| #include <stdio.h> | |
| #include <errno.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| struct file_info { | |
| char *mapping; | |
| off_t file_offset; | |
| off_t file_length; | |
| int fd; | |
| }; | |
| int | |
| main( | |
| int argc, | |
| char *argv[]) | |
| { | |
| struct file_info *fi; | |
| size_t length; | |
| char *endptr; | |
| unsigned int nr_files; | |
| unsigned int i; | |
| char *buf; | |
| int ret; | |
| if (argc < 4) { | |
| printf("Usage: %s len offset file [offset file]...\n", argv[0]); | |
| return 1; | |
| } | |
| /* Parse mwrite length. */ | |
| errno = 0; | |
| length = strtoul(argv[1], &endptr, 0); | |
| if (errno) { | |
| perror(argv[1]); | |
| return 1; | |
| } | |
| if (*endptr != '\0') { | |
| fprintf(stderr, "%s: not a proper file length?\n", argv[2]); | |
| return 1; | |
| } | |
| /* Allocate file info */ | |
| nr_files = (argc - 2) / 2; | |
| fi = calloc(nr_files, sizeof(struct file_info)); | |
| if (!fi) { | |
| perror("calloc file info"); | |
| return 1; | |
| } | |
| buf = malloc(length); | |
| if (!buf) { | |
| perror("malloc buf"); | |
| return 1; | |
| } | |
| for (i = 0; i < nr_files; i++) { | |
| struct stat statbuf; | |
| char *offset = argv[((i + 1) * 2)]; | |
| char *fname = argv[((i + 1) * 2) + 1]; | |
| /* Open file, create mapping for the range we want. */ | |
| fi[i].fd = open(fname, O_RDWR); | |
| if (fi[i].fd < 0) { | |
| perror(fname); | |
| return 1; | |
| } | |
| /* Parse mwrite offset */ | |
| errno = 0; | |
| fi[i].file_offset = strtoul(offset, &endptr, 0); | |
| if (errno) { | |
| perror(argv[1]); | |
| return 1; | |
| } | |
| /* Remember file size */ | |
| ret = fstat(fi[i].fd, &statbuf); | |
| if (ret) { | |
| perror(fname); | |
| return 1; | |
| } | |
| fi[i].file_length = statbuf.st_size; | |
| if (fi[i].file_offset + length > fi[i].file_length) { | |
| fprintf(stderr, "%s: file must be %llu bytes\n", | |
| fname, | |
| (unsigned long long)fi[i].file_offset + length); | |
| return 1; | |
| } | |
| /* Create the mapping */ | |
| fi[i].mapping = mmap(NULL, fi[i].file_length, | |
| PROT_READ | PROT_WRITE, MAP_SHARED, | |
| fi[i].fd, 0); | |
| if (fi[i].mapping == MAP_FAILED) { | |
| perror(fname); | |
| return 1; | |
| } | |
| /* | |
| * Make sure the mapping for region we're going to write is | |
| * already populated in the page cache. | |
| */ | |
| memcpy(buf, fi[i].mapping + fi[i].file_offset, length); | |
| } | |
| /* Dirty the same region in each file to test COW. */ | |
| for (i = 0; i < nr_files; i++) { | |
| memset(buf, 0x62 + i, length); | |
| memcpy(fi[i].mapping + fi[i].file_offset, buf, length); | |
| } | |
| for (i = 0; i < nr_files; i++) { | |
| ret = msync(fi[i].mapping, fi[i].file_offset + length, MS_SYNC); | |
| if (ret) { | |
| perror("msync"); | |
| return 1; | |
| } | |
| } | |
| /* Close everything. */ | |
| for (i = 0; i < nr_files; i++) { | |
| ret = munmap(fi[i].mapping, fi[i].file_length); | |
| if (ret) { | |
| perror("munmap"); | |
| return 1; | |
| } | |
| ret = close(fi[i].fd); | |
| if (ret) { | |
| perror("close"); | |
| return 1; | |
| } | |
| } | |
| /* Free everything. */ | |
| free(buf); | |
| free(fi); | |
| return 0; | |
| } |