open_by_handle: add -n option to suppress drop caches
[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 [-cludmrwapknh] [<-i|-o> <handles_file>] <test_dir> [num_files]
31
32 Examples:
33
34 1. Create test set of of test_dir with N files and try to get their NFS handles:
35
36    open_by_handle -cp <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 -p <test_dir> [N]
45
46 3. Get file handles for existing test set and write them to a file:
47
48    open_by_handle -p -o <handles_file> <test_dir> [N]
49
50 4. Read file handles from file and open files by handle without
51    dropping caches beforehand:
52
53    open_by_handle -np -i <handles_file> <test_dir> [N]
54
55 5. Get file handles for existing test set, write data to files,
56    drop caches, open all files by handle, read and verify written
57    data, write new data to file:
58
59    open_by_handle -rwa <test_dir> [N]
60
61 6. Get file handles for existing test set, unlink all test files,
62    remove test_dir, drop caches, try to open all files by handle
63    and expect ESTALE:
64
65    open_by_handle -dp <test_dir> [N]
66
67 7. Get file handles for existing test set, keep open file handles for all
68    test files, unlink all test files, drop caches and try to open all files
69    by handle (should work):
70
71    open_by_handle -dk <test_dir> [N]
72
73 8. Get file handles for existing test set, rename all test files,
74    drop caches, try to open all files by handle (should work):
75
76    open_by_handle -m <test_dir> [N]
77
78 9. Get file handles for existing test set, hardlink all test files,
79    then unlink the original files, drop caches and try to open all
80    files by handle (should work):
81
82    open_by_handle -l <test_dir> [N]
83    open_by_handle -u <test_dir> [N]
84
85    This test is done with 2 invocations of the program, first to
86    hardlink (-l) and then to unlink the originals (-u), because
87    we would like to be able to perform the hardlinks on overlay
88    lower layer and unlink on upper layer.
89
90    NOTE that open_by_handle -u doesn't check if the files are
91    hardlinked, it just assumes that they are.  If they are not
92    then the test will fail, because file handles would be stale.
93 */
94
95 #include <stdio.h>
96 #include <stdlib.h>
97 #include <string.h>
98 #include <fcntl.h>
99 #include <unistd.h>
100 #include <sys/stat.h>
101 #include <sys/types.h>
102 #include <errno.h>
103 #include <linux/limits.h>
104 #include <libgen.h>
105
106 #define MAXFILES 1024
107
108 struct handle {
109         struct file_handle fh;
110         unsigned char fid[MAX_HANDLE_SZ];
111 } handle[MAXFILES], dir_handle;
112
113 void usage(void)
114 {
115         fprintf(stderr, "usage: open_by_handle [-cludmrwapknh] [<-i|-o> <handles_file>] <test_dir> [num_files]\n");
116         fprintf(stderr, "\n");
117         fprintf(stderr, "open_by_handle -c <test_dir> [N] - create N test files under test_dir, try to get file handles and exit\n");
118         fprintf(stderr, "open_by_handle    <test_dir> [N] - get file handles of test files, drop caches and try to open by handle\n");
119         fprintf(stderr, "open_by_handle -n <test_dir> [N] - get file handles of test files and try to open by handle without drop caches\n");
120         fprintf(stderr, "open_by_handle -k <test_dir> [N] - get file handles of files that are kept open, drop caches and try to open by handle\n");
121         fprintf(stderr, "open_by_handle -w <test_dir> [N] - write data to test files before open by handle\n");
122         fprintf(stderr, "open_by_handle -r <test_dir> [N] - read data from test files after open by handle and verify written data\n");
123         fprintf(stderr, "open_by_handle -a <test_dir> [N] - write data to test files after open by handle\n");
124         fprintf(stderr, "open_by_handle -l <test_dir> [N] - create hardlinks to test files, drop caches and try to open by handle\n");
125         fprintf(stderr, "open_by_handle -u <test_dir> [N] - unlink (hardlinked) test files, drop caches and try to open by handle\n");
126         fprintf(stderr, "open_by_handle -d <test_dir> [N] - unlink test files and hardlinks, drop caches and try to open by handle\n");
127         fprintf(stderr, "open_by_handle -m <test_dir> [N] - rename test files, drop caches and try to open by handle\n");
128         fprintf(stderr, "open_by_handle -p <test_dir>     - create/delete and try to open by handle also test_dir itself\n");
129         fprintf(stderr, "open_by_handle -i <handles_file> <test_dir> [N] - read test files handles from file and try to open by handle\n");
130         fprintf(stderr, "open_by_handle -o <handles_file> <test_dir> [N] - get file handles of test files and write handles to file\n");
131         exit(EXIT_FAILURE);
132 }
133
134 int main(int argc, char **argv)
135 {
136         int     i, c;
137         int     fd;
138         int     ret = 0;
139         int     failed = 0;
140         char    dname[PATH_MAX];
141         char    fname[PATH_MAX];
142         char    fname2[PATH_MAX];
143         char    *test_dir;
144         char    *mount_dir;
145         int     mount_fd, mount_id;
146         char    *infile = NULL, *outfile = NULL;
147         int     in_fd = 0, out_fd = 0;
148         int     numfiles = 1;
149         int     create = 0, delete = 0, nlink = 1, move = 0;
150         int     rd = 0, wr = 0, wrafter = 0, parent = 0;
151         int     keepopen = 0, drop_caches = 1;
152
153         if (argc < 2)
154                 usage();
155
156         while ((c = getopt(argc, argv, "cludmrwapknhi:o:")) != -1) {
157                 switch (c) {
158                 case 'c':
159                         create = 1;
160                         break;
161                 case 'w':
162                         /* Write data before open_by_handle_at() */
163                         wr = 1;
164                         break;
165                 case 'r':
166                         /* Read data after open_by_handle_at() */
167                         rd = 1;
168                         break;
169                 case 'a':
170                         /* Write data after open_by_handle_at() */
171                         wrafter = 1;
172                         break;
173                 case 'l':
174                         nlink = 2;
175                         break;
176                 case 'u':
177                         delete = 1;
178                         nlink = 1;
179                         break;
180                 case 'd':
181                         delete = 1;
182                         nlink = 0;
183                         break;
184                 case 'm':
185                         move = 1;
186                         break;
187                 case 'p':
188                         parent = 1;
189                         break;
190                 case 'k':
191                         keepopen = 1;
192                         break;
193                 case 'n':
194                         drop_caches = 0;
195                         break;
196                 case 'i':
197                         infile = optarg;
198                         in_fd = open(infile, O_RDONLY);
199                         if (in_fd < 0) {
200                                 perror(infile);
201                                 return EXIT_FAILURE;
202                         }
203                         break;
204                 case 'o':
205                         outfile = optarg;
206                         out_fd = creat(outfile, 0644);
207                         if (out_fd < 0) {
208                                 perror(outfile);
209                                 return EXIT_FAILURE;
210                         }
211                         break;
212                 default:
213                         fprintf(stderr, "illegal option '%s'\n", argv[optind]);
214                 case 'h':
215                         usage();
216                 }
217         }
218         if (optind == argc)
219             usage();
220         test_dir = argv[optind++];
221         if (optind < argc)
222                 numfiles = atoi(argv[optind]);
223         if (!numfiles || numfiles > MAXFILES) {
224                 fprintf(stderr, "illegal value '%s' for num_files\n", argv[optind]);
225                 usage();
226         }
227
228         /*
229          * The way we determine the mount_dir to be used for mount_fd argument
230          * for open_by_handle_at() depends on other command line arguments:
231          *
232          * -p flag usually (see -i below) implies that test_dir is NOT a mount
233          *    point, but a directory inside a mount point that we will create
234          *    and/or encode/decode during the test, so we use test_dir's parent
235          *    for mount_fd. Even when not creatig test_dir, if we would use
236          *    test_dir as mount_fd, then drop_caches will not drop the test_dir
237          *    dcache entry.
238          *
239          * If -p is not specified, we don't have a hint whether test_dir is a
240          *    mount point or not, so we assume the worst case, that it is a
241          *    mount point and therefore, we cannnot use parent as mount_fd,
242          *    because parent may be on a differnt file system.
243          *
244          * -i flag, even with -p flag, implies that test_dir IS a mount point,
245          *    because we are testing open by handle of dir, which may have been
246          *    deleted or renamed and we are not creating nor encoding the
247          *    directory file handle. -i flag is meant to be used for tests
248          *    after encoding file handles and mount cycle the file system. If
249          *    we would require the test to pass in with -ip the test_dir we
250          *    want to decode and not the mount point, that would have populated
251          *    the dentry cache and the use of -ip flag combination would not
252          *    allow testing decode of dir file handle in cold dcache scenario.
253          */
254         if (parent && !in_fd) {
255                 strcpy(dname, test_dir);
256                 mount_dir = dirname(dname);
257                 if (create)
258                         ret = mkdir(test_dir, 0700);
259                 if (ret < 0 && errno != EEXIST) {
260                         strcat(dname, ": mkdir");
261                         perror(dname);
262                         return EXIT_FAILURE;
263                 }
264         } else {
265                 mount_dir = test_dir;
266         }
267
268         mount_fd = open(mount_dir, O_RDONLY|O_DIRECTORY);
269         if (mount_fd < 0) {
270                 perror(mount_dir);
271                 return EXIT_FAILURE;
272         }
273
274         /*
275          * Create the test files and remove leftover hardlinks from previous run
276          */
277         for (i=0; create && i < numfiles; i++) {
278                 sprintf(fname, "%s/file%06d", test_dir, i);
279                 sprintf(fname2, "%s/link%06d", test_dir, i);
280                 fd = open(fname, O_RDWR | O_CREAT | O_TRUNC, 0644);
281                 if (fd < 0) {
282                         strcat(fname, ": open(O_CREAT)");
283                         perror(fname);
284                         return EXIT_FAILURE;
285                 }
286                 close(fd);
287                 /* blow up leftovers hardlinks if they exist */
288                 ret = unlink(fname2);
289                 if (ret < 0 && errno != ENOENT) {
290                         strcat(fname2, ": unlink");
291                         perror(fname2);
292                         return EXIT_FAILURE;
293                 }
294         }
295
296         /* sync to get the new inodes to hit the disk */
297         sync();
298
299         /*
300          * encode the file handles or read them from file (-i) and maybe store
301          * them to a file (-o).
302          */
303         for (i=0; i < numfiles; i++) {
304                 sprintf(fname, "%s/file%06d", test_dir, i);
305                 if (in_fd) {
306                         ret = read(in_fd, (char *)&handle[i], sizeof(*handle));
307                         if (ret < sizeof(*handle)) {
308                                 fprintf(stderr, "failed reading file handle #%d from '%s'\n", i, infile);
309                                 return EXIT_FAILURE;
310                         }
311                 } else {
312                         handle[i].fh.handle_bytes = MAX_HANDLE_SZ;
313                         ret = name_to_handle_at(AT_FDCWD, fname, &handle[i].fh, &mount_id, 0);
314                         if (ret < 0) {
315                                 strcat(fname, ": name_to_handle");
316                                 perror(fname);
317                                 return EXIT_FAILURE;
318                         }
319                 }
320                 if (keepopen) {
321                         /* Open without close to keep unlinked files around */
322                         fd = open(fname, O_RDONLY);
323                         if (fd < 0) {
324                                 strcat(fname, ": open(O_RDONLY)");
325                                 perror(fname);
326                                 return EXIT_FAILURE;
327                         }
328                 }
329                 if (out_fd) {
330                         ret = write(out_fd, (char *)&handle[i], sizeof(*handle));
331                         if (ret < sizeof(*handle)) {
332                                 fprintf(stderr, "failed writing file handle #%d to '%s'\n", i, outfile);
333                                 return EXIT_FAILURE;
334                         }
335                 }
336         }
337
338         if (parent) {
339                 if (in_fd) {
340                         ret = read(in_fd, (char *)&dir_handle, sizeof(*handle));
341                         if (ret < sizeof(*handle)) {
342                                 fprintf(stderr, "failed reading dir file handle from '%s'\n", infile);
343                                 return EXIT_FAILURE;
344                         }
345                 } else {
346                         dir_handle.fh.handle_bytes = MAX_HANDLE_SZ;
347                         ret = name_to_handle_at(AT_FDCWD, test_dir, &dir_handle.fh, &mount_id, 0);
348                         if (ret < 0) {
349                                 strcat(dname, ": name_to_handle");
350                                 perror(dname);
351                                 return EXIT_FAILURE;
352                         }
353                 }
354                 if (out_fd) {
355                         ret = write(out_fd, (char *)&dir_handle, sizeof(*handle));
356                         if (ret < sizeof(*handle)) {
357                                 fprintf(stderr, "failed writing dir file handle to '%s'\n", outfile);
358                                 return EXIT_FAILURE;
359                         }
360                 }
361         }
362
363         /* write data to files */
364         for (i=0; wr && i < numfiles; i++) {
365                 sprintf(fname, "%s/file%06d", test_dir, i);
366                 fd = open(fname, O_WRONLY, 0644);
367                 if (fd < 0) {
368                         strcat(fname, ": open");
369                         perror(fname);
370                         return EXIT_FAILURE;
371                 }
372                 if (write(fd, "aaaa", 4) != 4) {
373                         strcat(fname, ": write before");
374                         perror(fname);
375                         return EXIT_FAILURE;
376                 }
377                 close(fd);
378         }
379
380         /* If creating test set or saving files handles, we are done */
381         if (create || out_fd)
382                 return EXIT_SUCCESS;
383
384         /* hardlink the files */
385         for (i=0; nlink > 1 && i < numfiles; i++) {
386                 sprintf(fname, "%s/file%06d", test_dir, i);
387                 sprintf(fname2, "%s/link%06d", test_dir, i);
388                 ret = link(fname, fname2);
389                 if (ret < 0) {
390                         strcat(fname2, ": link");
391                         perror(fname2);
392                         return EXIT_FAILURE;
393                 }
394         }
395
396         /* rename the files */
397         for (i=0; move && i < numfiles; i++) {
398                 sprintf(fname, "%s/file%06d", test_dir, i);
399                 sprintf(fname2, "%s/link%06d", test_dir, i);
400                 ret = rename(fname, fname2);
401                 if (ret < 0) {
402                         strcat(fname2, ": rename");
403                         perror(fname2);
404                         return EXIT_FAILURE;
405                 }
406         }
407
408         /* unlink the files */
409         for (i=0; delete && i < numfiles; i++) {
410                 sprintf(fname, "%s/file%06d", test_dir, i);
411                 sprintf(fname2, "%s/link%06d", test_dir, i);
412                 ret = unlink(fname);
413                 if (ret < 0) {
414                         strcat(fname, ": unlink");
415                         perror(fname);
416                         return EXIT_FAILURE;
417                 }
418                 /* with -d flag, delete the hardlink if it exists */
419                 if (!nlink)
420                         ret = unlink(fname2);
421                 if (ret < 0 && errno != ENOENT) {
422                         strcat(fname2, ": unlink");
423                         perror(fname2);
424                         return EXIT_FAILURE;
425                 }
426         }
427
428         if (parent && delete && !nlink) {
429                 ret = rmdir(test_dir);
430                 if (ret < 0) {
431                         strcat(dname, ": rmdir");
432                         perror(dname);
433                         return EXIT_FAILURE;
434                 }
435         }
436
437         /* sync to get log forced for unlink transactions to hit the disk */
438         sync();
439
440         /* sync once more FTW */
441         sync();
442
443         /*
444          * now drop the caches so that unlinked inodes are reclaimed and
445          * buftarg page cache is emptied so that the inode cluster has to be
446          * fetched from disk again for the open_by_handle() call.
447          */
448         if (drop_caches) {
449                 ret = system("echo 3 > /proc/sys/vm/drop_caches");
450                 if (ret < 0) {
451                         perror("drop_caches");
452                         return EXIT_FAILURE;
453                 }
454         }
455
456         /*
457          * now try to open the files by the stored handles. Expecting ESTALE
458          * if all files and their hardlinks have been unlinked.
459          */
460         for (i=0; i < numfiles; i++) {
461                 errno = 0;
462                 fd = open_by_handle_at(mount_fd, &handle[i].fh, wrafter ? O_RDWR : O_RDONLY);
463                 if ((nlink || keepopen) && fd >= 0) {
464                         if (rd) {
465                                 char buf[4] = {0};
466                                 int size = read(fd, buf, 4);
467                                 if (size < 0) {
468                                         strcat(fname, ": read");
469                                         perror(fname);
470                                         return EXIT_FAILURE;
471                                 }
472                                 if (size < 4 || memcmp(buf, "aaaa", 4)) {
473                                         printf("open_by_handle(%s) returned stale data '%.*s'!\n", fname, size, buf);
474                                 }
475                         }
476                         if (wrafter && write(fd, "aaaa", 4) != 4) {
477                                 strcat(fname, ": write after");
478                                 perror(fname);
479                                 return EXIT_FAILURE;
480                         }
481                         close(fd);
482                         continue;
483                 } else if (!nlink && !keepopen && fd < 0 && (errno == ENOENT || errno == ESTALE)) {
484                         continue;
485                 }
486                 sprintf(fname, "%s/file%06d", test_dir, i);
487                 if (fd >= 0) {
488                         printf("open_by_handle(%s) opened an unlinked file!\n", fname);
489                         close(fd);
490                 } else {
491                         printf("open_by_handle(%s) returned %d incorrectly on %s%s file!\n",
492                                         fname, errno,
493                                         nlink ? "a linked" : "an unlinked",
494                                         keepopen ? " open" : "");
495                 }
496                 failed++;
497         }
498
499         if (parent) {
500                 fd = open_by_handle_at(mount_fd, &dir_handle.fh, O_RDONLY|O_DIRECTORY);
501                 if (fd >= 0) {
502                         if (!nlink) {
503                                 printf("open_by_handle(%s) opened an unlinked dir!\n", dname);
504                                 return EXIT_FAILURE;
505                         } else if (rd) {
506                                 /*
507                                  * Sanity check dir fd - expect to access orig file IFF
508                                  * it was not unlinked nor renamed.
509                                  */
510                                 strcpy(fname, "file000000");
511                                 ret = faccessat(fd, fname, F_OK, 0);
512                                 if ((ret == 0) != (!delete && !move) ||
513                                     ((ret < 0) && errno != ENOENT)) {
514                                         strcat(fname, ": unexpected result from faccessat");
515                                         perror(fname);
516                                         return EXIT_FAILURE;
517                                 }
518                                 /*
519                                  * Expect to access link file if ran test with -l flag
520                                  * (nlink > 1), -m flag (orig moved to link name) or
521                                  * -u flag (which implied previous -l run).
522                                  */
523                                 strcpy(fname2, "link000000");
524                                 ret = faccessat(fd, fname2, F_OK, 0);
525                                 if (ret < 0 && (nlink > 1 || delete || move ||
526                                                 errno != ENOENT)) {
527                                         strcat(fname2, ": unexpected result from faccessat");
528                                         perror(fname2);
529                                         return EXIT_FAILURE;
530                                 }
531                         }
532                         close(fd);
533                 } else if (nlink || !(errno == ENOENT || errno == ESTALE)) {
534                         printf("open_by_handle(%s) returned %d incorrectly on %s dir!\n",
535                                         dname, errno,
536                                         nlink ? "a linked" : "an unlinked");
537                         return EXIT_FAILURE;
538                 }
539         }
540
541         if (failed)
542                 return EXIT_FAILURE;
543         return EXIT_SUCCESS;
544 }