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