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