btrfs: test delayed subvolume deletion on mount and remount
[xfstests-dev.git] / src / deduperace.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (c) 2021 Oracle.  All Rights Reserved.
4  * Author: Darrick J. Wong <djwong@kernel.org>
5  *
6  * Race pwrite/mwrite with dedupe to see if we got the locking right.
7  *
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.
10  */
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 #include <sys/mman.h>
14 #include <sys/ioctl.h>
15 #include <linux/fs.h>
16 #include <string.h>
17 #include <stdio.h>
18 #include <unistd.h>
19 #include <fcntl.h>
20 #include <pthread.h>
21 #include <stdlib.h>
22 #include <errno.h>
23
24 #define GOOD_BYTE               0x58
25 #define BAD_BYTE                0x66
26
27 #ifndef FIDEDUPERANGE
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
31
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:
39          * < 0 for error
40          * == FILE_DEDUPE_RANGE_SAME if dedupe succeeds
41          * == FILE_DEDUPE_RANGE_DIFFERS if data differs
42          */
43         __s32 status;           /* out - see above description */
44         __u32 reserved;         /* must be zero */
45 };
46
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];
55 };
56 #define FIDEDUPERANGE   _IOWR(0x94, 54, struct file_dedupe_range)
57 #endif /* FIDEDUPERANGE */
58
59 static int fd1, fd2;
60 static loff_t offset = 37; /* Nice low offset to trick the compare */
61 static loff_t blksz;
62
63 /* Continuously dirty the pagecache for the region being dupe-tested. */
64 void *
65 mwriter(
66         void            *data)
67 {
68         volatile char   *p;
69
70         p = mmap(NULL, blksz, PROT_WRITE, MAP_SHARED, fd1, 0);
71         if (p == MAP_FAILED) {
72                 perror("mmap");
73                 exit(2);
74         }
75
76         while (1) {
77                 *(p + offset) = BAD_BYTE;
78                 *(p + offset) = GOOD_BYTE;
79         }
80 }
81
82 /* Continuously write to the region being dupe-tested. */
83 void *
84 pwriter(
85         void            *data)
86 {
87         char            v;
88         ssize_t         sz;
89
90         while (1) {
91                 v = BAD_BYTE;
92                 sz = pwrite(fd1, &v, sizeof(v), offset);
93                 if (sz != sizeof(v)) {
94                         perror("pwrite0");
95                         exit(2);
96                 }
97
98                 v = GOOD_BYTE;
99                 sz = pwrite(fd1, &v, sizeof(v), offset);
100                 if (sz != sizeof(v)) {
101                         perror("pwrite1");
102                         exit(2);
103                 }
104         }
105
106         return NULL;
107 }
108
109 static inline void
110 complain(
111         loff_t  offset,
112         char    bad)
113 {
114         fprintf(stderr, "ASSERT: offset %llu should be 0x%x, got 0x%x!\n",
115                         (unsigned long long)offset, GOOD_BYTE, bad);
116         abort();
117 }
118
119 /* Make sure the destination file pagecache never changes. */
120 void *
121 mreader(
122         void            *data)
123 {
124         volatile char   *p;
125
126         p = mmap(NULL, blksz, PROT_READ, MAP_SHARED, fd2, 0);
127         if (p == MAP_FAILED) {
128                 perror("mmap");
129                 exit(2);
130         }
131
132         while (1) {
133                 if (*(p + offset) != GOOD_BYTE)
134                         complain(offset, *(p + offset));
135         }
136 }
137
138 /* Make sure the destination file never changes. */
139 void *
140 preader(
141         void            *data)
142 {
143         char            v;
144         ssize_t         sz;
145
146         while (1) {
147                 sz = pread(fd2, &v, sizeof(v), offset);
148                 if (sz != sizeof(v)) {
149                         perror("pwrite0");
150                         exit(2);
151                 }
152
153                 if (v != GOOD_BYTE)
154                         complain(offset, v);
155         }
156
157         return NULL;
158 }
159
160 void
161 print_help(const char *progname)
162 {
163         printf("Usage: %s [-b blksz] [-c dir] [-n nr_ops] [-o offset] [-r] [-w] [-v]\n",
164                         progname);
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");
172 }
173
174 int
175 main(
176         int             argc,
177         char            *argv[])
178 {
179         struct file_dedupe_range *fdr;
180         char            *Xbuf;
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;
186         ssize_t         sz;
187         pthread_t       reader, writer;
188         int             verbose = 0;
189         int             c;
190         int             ret;
191
192         while ((c = getopt(argc, argv, "b:c:n:o:rvw")) != -1) {
193                 switch (c) {
194                 case 'b':
195                         errno = 0;
196                         blksz = strtoul(optarg, NULL, 0);
197                         if (errno) {
198                                 perror(optarg);
199                                 exit(1);
200                         }
201                         break;
202                 case 'c':
203                         ret = chdir(optarg);
204                         if (ret) {
205                                 perror("chdir");
206                                 exit(1);
207                         }
208                         break;
209                 case 'n':
210                         errno = 0;
211                         nr_ops = strtoul(optarg, NULL, 0);
212                         if (errno) {
213                                 perror(optarg);
214                                 exit(1);
215                         }
216                         break;
217                 case 'o':
218                         errno = 0;
219                         offset = strtoul(optarg, NULL, 0);
220                         if (errno) {
221                                 perror(optarg);
222                                 exit(1);
223                         }
224                         break;
225                 case 'r':
226                         reader_fn = preader;
227                         break;
228                 case 'v':
229                         verbose = 1;
230                         break;
231                 case 'w':
232                         writer_fn = pwriter;
233                         break;
234                 default:
235                         print_help(argv[0]);
236                         exit(1);
237                         break;
238                 }
239         }
240
241         fdr = malloc(sizeof(struct file_dedupe_range) +
242                         sizeof(struct file_dedupe_range_info));
243         if (!fdr) {
244                 perror("malloc");
245                 exit(1);
246         }
247
248         /* Initialize both files. */
249         fd1 = open("file1", O_RDWR | O_CREAT | O_TRUNC | O_NOATIME, 0600);
250         if (fd1 < 0) {
251                 perror("file1");
252                 exit(1);
253         }
254
255         fd2 = open("file2", O_RDWR | O_CREAT | O_TRUNC | O_NOATIME, 0600);
256         if (fd2 < 0) {
257                 perror("file2");
258                 exit(1);
259         }
260
261         if (blksz <= 0) {
262                 struct stat     statbuf;
263
264                 ret = fstat(fd1, &statbuf);
265                 if (ret) {
266                         perror("file1 stat");
267                         exit(1);
268                 }
269                 blksz = statbuf.st_blksize;
270         }
271
272         if (offset >= blksz) {
273                 fprintf(stderr, "offset (%llu) < blksz (%llu)?\n",
274                                 (unsigned long long)offset,
275                                 (unsigned long long)blksz);
276                 exit(1);
277         }
278
279         Xbuf = malloc(blksz);
280         if (!Xbuf) {
281                 perror("malloc buffer");
282                 exit(1);
283         }
284         memset(Xbuf, GOOD_BYTE, blksz);
285
286         sz = pwrite(fd1, Xbuf, blksz, 0);
287         if (sz != blksz) {
288                 perror("file1 write");
289                 exit(1);
290         }
291
292         sz = pwrite(fd2, Xbuf, blksz, 0);
293         if (sz != blksz) {
294                 perror("file2 write");
295                 exit(1);
296         }
297
298         ret = fsync(fd1);
299         if (ret) {
300                 perror("file1 fsync");
301                 exit(1);
302         }
303
304         ret = fsync(fd2);
305         if (ret) {
306                 perror("file2 fsync");
307                 exit(1);
308         }
309
310         /* Start our reader and writer threads. */
311         ret = pthread_create(&reader, NULL, reader_fn, NULL);
312         if (ret) {
313                 fprintf(stderr, "rthread: %s\n", strerror(ret));
314                 exit(1);
315         }
316
317         ret = pthread_create(&writer, NULL, writer_fn, NULL);
318         if (ret) {
319                 fprintf(stderr, "wthread: %s\n", strerror(ret));
320                 exit(1);
321         }
322
323         /*
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
327          * make it into fd2.
328          */
329         for (i = 1; i <= nr_ops; i++) {
330                 fdr->src_offset = 0;
331                 fdr->src_length = blksz;
332                 fdr->dest_count = 1;
333                 fdr->reserved1 = 0;
334                 fdr->reserved2 = 0;
335                 fdr->info[0].dest_fd = fd2;
336                 fdr->info[0].dest_offset = 0;
337                 fdr->info[0].reserved = 0;
338
339                 ret = ioctl(fd1, FIDEDUPERANGE, fdr);
340                 if (ret) {
341                         perror("dedupe");
342                         exit(2);
343                 }
344
345                 switch (fdr->info[0].status) {
346                 case FILE_DEDUPE_RANGE_DIFFERS:
347                         differs++;
348                         break;
349                 case FILE_DEDUPE_RANGE_SAME:
350                         same++;
351                         break;
352                 default:
353                         fprintf(stderr, "deduperange: %s\n",
354                                         strerror(-fdr->info[0].status));
355                         exit(2);
356                         break;
357                 }
358
359                 if (verbose && (i % 337) == 0)
360                         printf("nr_ops: %lu; same: %lu; differs: %lu\n",
361                                         i, same, differs);
362         }
363
364         if (verbose)
365                 printf("nr_ops: %lu; same: %lu; differs: %lu\n", i - 1, same,
366                                 differs);
367
368         /* Program termination will kill the threads and close the files. */
369         return 0;
370 }