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