open_by_handle: test content of open file handle
[xfstests-dev.git] / src / open_by_handle.c
1 /*
2  * open_by_handle.c - attempt to create a file handle and open it
3  *                    with open_by_handle_at() syscall
4  *
5  * Copyright (C) 2017 CTERA Networks. All Rights Reserved.
6  * Author: Amir Goldstein <amir73il@gmail.com>
7  *
8  * from:
9  *  stale_handle.c
10  *
11  *  Copyright (C) 2010 Red Hat, Inc. All Rights reserved.
12  *
13  *  This program is free software; you can redistribute it and/or modify
14  *  it under the terms of the GNU General Public License as published by
15  *  the Free Software Foundation; either version 2 of the License, or
16  *  (at your option) any later version.
17  *
18  *  This program is distributed in the hope that it will be useful,
19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  *  GNU General Public License for more details.
22  *
23  *  You should have received a copy of the GNU General Public License
24  *  along with this program; if not, write to the Free Software
25  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
26  */
27
28 /*
29
30 usage: open_by_handle [-cludmrwa] <test_dir> [num_files]
31
32 Examples:
33
34 1. Create test set of N files and try to get their NFS handles:
35
36    open_by_handle -c <test_dir> [N]
37
38    This is used by new helper _require_exportfs() to check
39    if filesystem supports exportfs
40
41 2. Get file handles for existing test set, drop caches and try to
42    open all files by handle:
43
44    open_by_handle <test_dir> [N]
45
46 3. Get file handles for existing test set, write data to files,
47    drop caches, open all files by handle, read and verify written
48    data, write new data to file:
49
50    open_by_handle -rwa <test_dir> [N]
51
52 4. Get file handles for existing test set, unlink all test files,
53    drop caches, try to open all files by handle and expect ESTALE:
54
55    open_by_handle -d <test_dir> [N]
56
57 5. Get file handles for existing test set, rename all test files,
58    drop caches, try to open all files by handle (should work):
59
60    open_by_handle -m <test_dir> [N]
61
62 6. Get file handles for existing test set, hardlink all test files,
63    then unlink the original files, drop caches and try to open all
64    files by handle (should work):
65
66    open_by_handle -l <test_dir> [N]
67    open_by_handle -u <test_dir> [N]
68
69    This test is done with 2 invocations of the program, first to
70    hardlink (-l) and then to unlink the originals (-u), because
71    we would like to be able to perform the hardlinks on overlay
72    lower layer and unlink on upper layer.
73
74    NOTE that open_by_handle -u doesn't check if the files are
75    hardlinked, it just assumes that they are.  If they are not
76    then the test will fail, because file handles would be stale.
77 */
78
79 #include <stdio.h>
80 #include <stdlib.h>
81 #include <string.h>
82 #include <fcntl.h>
83 #include <unistd.h>
84 #include <sys/stat.h>
85 #include <sys/types.h>
86 #include <errno.h>
87 #include <linux/limits.h>
88
89 #define MAXFILES 1024
90
91 struct handle {
92         struct file_handle fh;
93         unsigned char fid[MAX_HANDLE_SZ];
94 } handle[MAXFILES];
95
96 void usage(void)
97 {
98         fprintf(stderr, "usage: open_by_handle [-cludmrwa] <test_dir> [num_files]\n");
99         fprintf(stderr, "\n");
100         fprintf(stderr, "open_by_handle -c <test_dir> [N] - create N test files under test_dir, try to get file handles and exit\n");
101         fprintf(stderr, "open_by_handle    <test_dir> [N] - get file handles of test files, drop caches and try to open by handle\n");
102         fprintf(stderr, "open_by_handle -w <test_dir> [N] - write data to test files before open by handle\n");
103         fprintf(stderr, "open_by_handle -r <test_dir> [N] - read data from test files after open by handle and verify written data\n");
104         fprintf(stderr, "open_by_handle -a <test_dir> [N] - write data to test files after open by handle\n");
105         fprintf(stderr, "open_by_handle -l <test_dir> [N] - create hardlinks to test files, drop caches and try to open by handle\n");
106         fprintf(stderr, "open_by_handle -u <test_dir> [N] - unlink (hardlinked) test files, drop caches and try to open by handle\n");
107         fprintf(stderr, "open_by_handle -d <test_dir> [N] - unlink test files and hardlinks, drop caches and try to open by handle\n");
108         fprintf(stderr, "open_by_handle -m <test_dir> [N] - rename test files, drop caches and try to open by handle\n");
109         exit(EXIT_FAILURE);
110 }
111
112 int main(int argc, char **argv)
113 {
114         int     i, c;
115         int     fd;
116         int     ret;
117         int     failed = 0;
118         char    fname[PATH_MAX];
119         char    fname2[PATH_MAX];
120         char    *test_dir;
121         int     mount_fd, mount_id;
122         int     numfiles = 1;
123         int     create = 0, delete = 0, nlink = 1, move = 0;
124         int     rd = 0, wr = 0, wrafter = 0;
125
126         if (argc < 2 || argc > 4)
127                 usage();
128
129         while ((c = getopt(argc, argv, "cludmrwa")) != -1) {
130                 switch (c) {
131                 case 'c':
132                         create = 1;
133                         break;
134                 case 'w':
135                         /* Write data before open_by_handle_at() */
136                         wr = 1;
137                         break;
138                 case 'r':
139                         /* Read data after open_by_handle_at() */
140                         rd = 1;
141                         break;
142                 case 'a':
143                         /* Write data after open_by_handle_at() */
144                         wrafter = 1;
145                         break;
146                 case 'l':
147                         nlink = 2;
148                         break;
149                 case 'u':
150                         delete = 1;
151                         nlink = 1;
152                         break;
153                 case 'd':
154                         delete = 1;
155                         nlink = 0;
156                         break;
157                 case 'm':
158                         move = 1;
159                         break;
160                 default:
161                         fprintf(stderr, "illegal option '%s'\n", argv[optind]);
162                 case 'h':
163                         usage();
164                 }
165         }
166         if (optind == argc || optind > 2)
167             usage();
168         test_dir = argv[optind++];
169         if (optind < argc)
170                 numfiles = atoi(argv[optind]);
171         if (!numfiles || numfiles > MAXFILES) {
172                 fprintf(stderr, "illegal value '%s' for num_files\n", argv[optind]);
173                 usage();
174         }
175
176         mount_fd = open(test_dir, O_RDONLY|O_DIRECTORY);
177         if (mount_fd < 0) {
178                 perror(test_dir);
179                 return EXIT_FAILURE;
180         }
181
182         /*
183          * Create the test files and remove leftover hardlinks from previous run
184          */
185         for (i=0; create && i < numfiles; i++) {
186                 sprintf(fname, "%s/file%06d", test_dir, i);
187                 sprintf(fname2, "%s/link%06d", test_dir, i);
188                 fd = open(fname, O_RDWR | O_CREAT | O_TRUNC, 0644);
189                 if (fd < 0) {
190                         strcat(fname, ": open");
191                         perror(fname);
192                         return EXIT_FAILURE;
193                 }
194                 close(fd);
195                 /* blow up leftovers hardlinks if they exist */
196                 ret = unlink(fname2);
197                 if (ret < 0 && errno != ENOENT) {
198                         strcat(fname2, ": unlink");
199                         perror(fname2);
200                         return EXIT_FAILURE;
201                 }
202         }
203
204         /* sync to get the new inodes to hit the disk */
205         sync();
206
207         /* create the handles */
208         for (i=0; i < numfiles; i++) {
209                 sprintf(fname, "%s/file%06d", test_dir, i);
210                 handle[i].fh.handle_bytes = MAX_HANDLE_SZ;
211                 ret = name_to_handle_at(AT_FDCWD, fname, &handle[i].fh, &mount_id, 0);
212                 if (ret < 0) {
213                         strcat(fname, ": name_to_handle");
214                         perror(fname);
215                         return EXIT_FAILURE;
216                 }
217         }
218
219         /* write data to files */
220         for (i=0; wr && i < numfiles; i++) {
221                 sprintf(fname, "%s/file%06d", test_dir, i);
222                 fd = open(fname, O_WRONLY, 0644);
223                 if (fd < 0) {
224                         strcat(fname, ": open");
225                         perror(fname);
226                         return EXIT_FAILURE;
227                 }
228                 if (write(fd, "aaaa", 4) != 4) {
229                         strcat(fname, ": write before");
230                         perror(fname);
231                         return EXIT_FAILURE;
232                 }
233                 close(fd);
234         }
235
236         /* after creating test set only check that fs supports exportfs */
237         if (create)
238                 return EXIT_SUCCESS;
239
240         /* hardlink the files */
241         for (i=0; nlink > 1 && i < numfiles; i++) {
242                 sprintf(fname, "%s/file%06d", test_dir, i);
243                 sprintf(fname2, "%s/link%06d", test_dir, i);
244                 ret = link(fname, fname2);
245                 if (ret < 0) {
246                         strcat(fname2, ": link");
247                         perror(fname2);
248                         return EXIT_FAILURE;
249                 }
250         }
251
252         /* rename the files */
253         for (i=0; move && i < numfiles; i++) {
254                 sprintf(fname, "%s/file%06d", test_dir, i);
255                 sprintf(fname2, "%s/link%06d", test_dir, i);
256                 ret = rename(fname, fname2);
257                 if (ret < 0) {
258                         strcat(fname2, ": rename");
259                         perror(fname2);
260                         return EXIT_FAILURE;
261                 }
262         }
263
264         /* unlink the files */
265         for (i=0; delete && i < numfiles; i++) {
266                 sprintf(fname, "%s/file%06d", test_dir, i);
267                 sprintf(fname2, "%s/link%06d", test_dir, i);
268                 ret = unlink(fname);
269                 if (ret < 0) {
270                         strcat(fname, ": unlink");
271                         perror(fname);
272                         return EXIT_FAILURE;
273                 }
274                 /* with -d flag, delete the hardlink if it exists */
275                 if (!nlink)
276                         ret = unlink(fname2);
277                 if (ret < 0 && errno != ENOENT) {
278                         strcat(fname2, ": unlink");
279                         perror(fname2);
280                         return EXIT_FAILURE;
281                 }
282         }
283
284         /* sync to get log forced for unlink transactions to hit the disk */
285         sync();
286
287         /* sync once more FTW */
288         sync();
289
290         /*
291          * now drop the caches so that unlinked inodes are reclaimed and
292          * buftarg page cache is emptied so that the inode cluster has to be
293          * fetched from disk again for the open_by_handle() call.
294          */
295         ret = system("echo 3 > /proc/sys/vm/drop_caches");
296         if (ret < 0) {
297                 perror("drop_caches");
298                 return EXIT_FAILURE;
299         }
300
301         /*
302          * now try to open the files by the stored handles. Expecting ESTALE
303          * if all files and their hardlinks have been unlinked.
304          */
305         for (i=0; i < numfiles; i++) {
306                 errno = 0;
307                 fd = open_by_handle_at(mount_fd, &handle[i].fh, wrafter ? O_RDWR : O_RDONLY);
308                 if (nlink && fd >= 0) {
309                         if (rd) {
310                                 char buf[4] = {0};
311                                 int size = read(fd, buf, 4);
312                                 if (size < 0) {
313                                         strcat(fname, ": read");
314                                         perror(fname);
315                                         return EXIT_FAILURE;
316                                 }
317                                 if (size < 4 || memcmp(buf, "aaaa", 4)) {
318                                         printf("open_by_handle(%s) returned stale data '%.*s'!\n", fname, size, buf);
319                                 }
320                         }
321                         if (wrafter && write(fd, "aaaa", 4) != 4) {
322                                 strcat(fname, ": write after");
323                                 perror(fname);
324                                 return EXIT_FAILURE;
325                         }
326                         close(fd);
327                         continue;
328                 } else if (!nlink && fd < 0 && (errno == ENOENT || errno == ESTALE)) {
329                         continue;
330                 }
331                 sprintf(fname, "%s/file%06d", test_dir, i);
332                 if (fd >= 0) {
333                         printf("open_by_handle(%s) opened an unlinked file!\n", fname);
334                         close(fd);
335                 } else {
336                         printf("open_by_handle(%s) returned %d incorrectly on %s file!\n",
337                                         fname, errno,
338                                         nlink ? "a linked" : "an unlinked");
339                 }
340                 failed++;
341         }
342         if (failed)
343                 return EXIT_FAILURE;
344         return EXIT_SUCCESS;
345 }