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