1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Copyright (c) 2021 Oracle. All Rights Reserved.
4 * Author: Darrick J. Wong <djwong@kernel.org>
6 * Race pwrite/mwrite with dedupe to see if we got the locking right.
8 * File writes and mmap writes should not be able to change the src_fd's
9 * contents after dedupe prep has verified that the file contents are the same.
11 #include <sys/types.h>
14 #include <sys/ioctl.h>
24 #define GOOD_BYTE 0x58
28 /* extent-same (dedupe) ioctls; these MUST match the btrfs ioctl definitions */
29 #define FILE_DEDUPE_RANGE_SAME 0
30 #define FILE_DEDUPE_RANGE_DIFFERS 1
32 /* from struct btrfs_ioctl_file_extent_same_info */
33 struct file_dedupe_range_info {
34 __s64 dest_fd; /* in - destination file */
35 __u64 dest_offset; /* in - start of extent in destination */
36 __u64 bytes_deduped; /* out - total # of bytes we were able
37 * to dedupe from this file. */
38 /* status of this dedupe operation:
40 * == FILE_DEDUPE_RANGE_SAME if dedupe succeeds
41 * == FILE_DEDUPE_RANGE_DIFFERS if data differs
43 __s32 status; /* out - see above description */
44 __u32 reserved; /* must be zero */
47 /* from struct btrfs_ioctl_file_extent_same_args */
48 struct file_dedupe_range {
49 __u64 src_offset; /* in - start of extent in source */
50 __u64 src_length; /* in - length of extent */
51 __u16 dest_count; /* in - total elements in info array */
52 __u16 reserved1; /* must be zero */
53 __u32 reserved2; /* must be zero */
54 struct file_dedupe_range_info info[0];
56 #define FIDEDUPERANGE _IOWR(0x94, 54, struct file_dedupe_range)
57 #endif /* FIDEDUPERANGE */
60 static loff_t offset = 37; /* Nice low offset to trick the compare */
63 /* Continuously dirty the pagecache for the region being dupe-tested. */
70 p = mmap(NULL, blksz, PROT_WRITE, MAP_SHARED, fd1, 0);
71 if (p == MAP_FAILED) {
77 *(p + offset) = BAD_BYTE;
78 *(p + offset) = GOOD_BYTE;
82 /* Continuously write to the region being dupe-tested. */
92 sz = pwrite(fd1, &v, sizeof(v), offset);
93 if (sz != sizeof(v)) {
99 sz = pwrite(fd1, &v, sizeof(v), offset);
100 if (sz != sizeof(v)) {
114 fprintf(stderr, "ASSERT: offset %llu should be 0x%x, got 0x%x!\n",
115 (unsigned long long)offset, GOOD_BYTE, bad);
119 /* Make sure the destination file pagecache never changes. */
126 p = mmap(NULL, blksz, PROT_READ, MAP_SHARED, fd2, 0);
127 if (p == MAP_FAILED) {
133 if (*(p + offset) != GOOD_BYTE)
134 complain(offset, *(p + offset));
138 /* Make sure the destination file never changes. */
147 sz = pread(fd2, &v, sizeof(v), offset);
148 if (sz != sizeof(v)) {
161 print_help(const char *progname)
163 printf("Usage: %s [-b blksz] [-c dir] [-n nr_ops] [-o offset] [-r] [-w] [-v]\n",
165 printf("-b sets the block size (default is autoconfigured)\n");
166 printf("-c chdir to this path before starting\n");
167 printf("-n controls the number of dedupe ops (default 10000)\n");
168 printf("-o reads and writes to this offset (default 37)\n");
169 printf("-r uses pread instead of mmap read.\n");
170 printf("-v prints status updates.\n");
171 printf("-w uses pwrite instead of mmap write.\n");
179 struct file_dedupe_range *fdr;
181 void *(*reader_fn)(void *) = mreader;
182 void *(*writer_fn)(void *) = mwriter;
183 unsigned long same = 0;
184 unsigned long differs = 0;
185 unsigned long i, nr_ops = 10000;
187 pthread_t reader, writer;
192 while ((c = getopt(argc, argv, "b:c:n:o:rvw")) != -1) {
196 blksz = strtoul(optarg, NULL, 0);
211 nr_ops = strtoul(optarg, NULL, 0);
219 offset = strtoul(optarg, NULL, 0);
241 fdr = malloc(sizeof(struct file_dedupe_range) +
242 sizeof(struct file_dedupe_range_info));
248 /* Initialize both files. */
249 fd1 = open("file1", O_RDWR | O_CREAT | O_TRUNC | O_NOATIME, 0600);
255 fd2 = open("file2", O_RDWR | O_CREAT | O_TRUNC | O_NOATIME, 0600);
264 ret = fstat(fd1, &statbuf);
266 perror("file1 stat");
269 blksz = statbuf.st_blksize;
272 if (offset >= blksz) {
273 fprintf(stderr, "offset (%llu) < blksz (%llu)?\n",
274 (unsigned long long)offset,
275 (unsigned long long)blksz);
279 Xbuf = malloc(blksz);
281 perror("malloc buffer");
284 memset(Xbuf, GOOD_BYTE, blksz);
286 sz = pwrite(fd1, Xbuf, blksz, 0);
288 perror("file1 write");
292 sz = pwrite(fd2, Xbuf, blksz, 0);
294 perror("file2 write");
300 perror("file1 fsync");
306 perror("file2 fsync");
310 /* Start our reader and writer threads. */
311 ret = pthread_create(&reader, NULL, reader_fn, NULL);
313 fprintf(stderr, "rthread: %s\n", strerror(ret));
317 ret = pthread_create(&writer, NULL, writer_fn, NULL);
319 fprintf(stderr, "wthread: %s\n", strerror(ret));
324 * Now start deduping. If the contents match, fd1's blocks will be
325 * remapped into fd2, which is why the writer thread targets fd1 and
326 * the reader checks fd2 to make sure that none of fd1's writes ever
329 for (i = 1; i <= nr_ops; i++) {
331 fdr->src_length = blksz;
335 fdr->info[0].dest_fd = fd2;
336 fdr->info[0].dest_offset = 0;
337 fdr->info[0].reserved = 0;
339 ret = ioctl(fd1, FIDEDUPERANGE, fdr);
345 switch (fdr->info[0].status) {
346 case FILE_DEDUPE_RANGE_DIFFERS:
349 case FILE_DEDUPE_RANGE_SAME:
353 fprintf(stderr, "deduperange: %s\n",
354 strerror(-fdr->info[0].status));
359 if (verbose && (i % 337) == 0)
360 printf("nr_ops: %lu; same: %lu; differs: %lu\n",
365 printf("nr_ops: %lu; same: %lu; differs: %lu\n", i - 1, same,
368 /* Program termination will kill the threads and close the files. */