xfs: regression test for allocsp handing out stale disk contents
[xfstests-dev.git] / src / allocstale.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (C) 2022 Oracle.  All Rights Reserved.
4  * Author: Darrick J. Wong <djwong@kernel.org>
5  *
6  * Test program to try to trip over XFS_IOC_ALLOCSP mapping stale disk blocks
7  * into a file.
8  */
9 #include <xfs/xfs.h>
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <unistd.h>
13 #include <sys/types.h>
14 #include <sys/stat.h>
15 #include <fcntl.h>
16 #include <errno.h>
17 #include <unistd.h>
18 #include <string.h>
19
20 #ifndef XFS_IOC_ALLOCSP
21 # define XFS_IOC_ALLOCSP        _IOW ('X', 10, struct xfs_flock64)
22 #endif
23
24 int
25 main(
26         int             argc,
27         char            *argv[])
28 {
29         struct stat     sb;
30         char            *buf, *zeroes;
31         unsigned long   i;
32         unsigned long   iterations;
33         int             fd, ret;
34
35         if (argc != 3) {
36                 fprintf(stderr, "Usage: %s filename iterations\n", argv[0]);
37                 return 1;
38         }
39
40         errno = 0;
41         iterations = strtoul(argv[2], NULL, 0);
42         if (errno) {
43                 perror(argv[2]);
44                 return 1;
45         }
46
47         fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, 0600);
48         if (fd < 0) {
49                 perror(argv[1]);
50                 return 1;
51         }
52
53         ret = fstat(fd, &sb);
54         if (ret) {
55                 perror(argv[1]);
56                 return 1;
57         }
58
59         buf = malloc(sb.st_blksize);
60         if (!buf) {
61                 perror("pread buffer");
62                 return 1;
63         }
64
65         zeroes = calloc(1, sb.st_blksize);
66         if (!zeroes) {
67                 perror("zeroes buffer");
68                 return 1;
69         }
70
71         for (i = 1; i <= iterations; i++) {
72                 struct xfs_flock64      arg = { };
73                 ssize_t                 read_bytes;
74                 off_t                   offset = sb.st_blksize * i;
75
76                 /* Ensure the last block of the file is a hole... */
77                 ret = ftruncate(fd, offset - 1);
78                 if (ret) {
79                         perror("truncate");
80                         return 1;
81                 }
82
83                 /*
84                  * ...then use ALLOCSP to allocate the last block in the file.
85                  * An unpatched kernel neglects to mark the new mapping
86                  * unwritten or to zero the ondisk block, so...
87                  */
88                 arg.l_whence = SEEK_SET;
89                 arg.l_start = offset;
90                 ret = ioctl(fd, XFS_IOC_ALLOCSP, &arg);
91                 if (ret < 0) {
92                         perror("ioctl");
93                         return 1;
94                 }
95
96                 /* ... we can read old disk contents here. */
97                 read_bytes = pread(fd, buf, sb.st_blksize,
98                                                 offset - sb.st_blksize);
99                 if (read_bytes < 0) {
100                         perror(argv[1]);
101                         return 1;
102                 }
103                 if (read_bytes != sb.st_blksize) {
104                         fprintf(stderr, "%s: short read of %zd bytes\n",
105                                         argv[1], read_bytes);
106                         return 1;
107                 }
108
109                 if (memcmp(zeroes, buf, sb.st_blksize) != 0) {
110                         fprintf(stderr, "%s: found junk near offset %zd!\n",
111                                         argv[1], offset - sb.st_blksize);
112                         return 2;
113                 }
114         }
115
116         return 0;
117 }