1 // SPDX-License-Identifier: GPL-2.0+
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>
7 * Attempt to create a file handle and open it with open_by_handle_at() syscall
15 usage: open_by_handle [-cludmrwapknhs] [<-i|-o> <handles_file>] <test_dir> [num_files]
19 1. Create test set of of test_dir with N files and try to get their NFS handles:
21 open_by_handle -cp <test_dir> [N]
23 This is used by new helper _require_exportfs() to check
24 if filesystem supports exportfs
26 2. Get file handles for existing test set, drop caches and try to
27 open all files by handle:
29 open_by_handle -p <test_dir> [N]
31 3. Get file handles for existing test set and write them to a file:
33 open_by_handle -p -o <handles_file> <test_dir> [N]
35 4. Read file handles from file and open files by handle without
36 dropping caches beforehand. Sleep afterhand to keep files open:
38 open_by_handle -nps -i <handles_file> <test_dir> [N]
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:
44 open_by_handle -rwa <test_dir> [N]
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
50 open_by_handle -dp <test_dir> [N]
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):
56 open_by_handle -dk <test_dir> [N]
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):
61 open_by_handle -m <test_dir> [N]
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):
67 open_by_handle -l <test_dir> [N]
68 open_by_handle -u <test_dir> [N]
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.
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.
86 #include <sys/types.h>
88 #include <linux/limits.h>
94 struct file_handle fh;
95 unsigned char fid[MAX_HANDLE_SZ];
96 } handle[MAXFILES], dir_handle;
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");
121 int main(int argc, char **argv)
127 char mname[PATH_MAX];
128 char dname[PATH_MAX];
129 char fname[PATH_MAX];
130 char fname2[PATH_MAX];
133 int mount_fd, mount_id;
134 char *infile = NULL, *outfile = NULL;
135 int in_fd = 0, out_fd = 0;
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;
145 while ((c = getopt(argc, argv, "cludmrwapknhi:o:sz")) != -1) {
151 /* Write data before open_by_handle_at() */
155 /* Read data after open_by_handle_at() */
159 /* Write data after open_by_handle_at() */
187 in_fd = open(infile, O_RDONLY);
195 out_fd = creat(outfile, 0644);
208 fprintf(stderr, "illegal option '%s'\n", argv[optind]);
215 test_dir = argv[optind++];
217 numfiles = atoi(argv[optind]);
218 if (!numfiles || numfiles > MAXFILES) {
219 fprintf(stderr, "illegal value '%s' for num_files\n", argv[optind]);
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:
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
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.
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.
249 strcpy(dname, test_dir);
250 if (parent && !in_fd) {
251 strcpy(mname, test_dir);
252 mount_dir = dirname(mname);
254 ret = mkdir(test_dir, 0700);
255 if (ret < 0 && errno != EEXIST) {
256 strcat(dname, ": mkdir");
261 mount_dir = test_dir;
264 mount_fd = open(mount_dir, O_RDONLY|O_DIRECTORY);
271 * Create the test files and remove leftover hardlinks from previous run
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);
278 strcat(fname, ": open(O_CREAT)");
283 /* blow up leftovers hardlinks if they exist */
284 ret = unlink(fname2);
285 if (ret < 0 && errno != ENOENT) {
286 strcat(fname2, ": unlink");
292 /* sync to get the new inodes to hit the disk */
296 * encode the file handles or read them from file (-i) and maybe store
297 * them to a file (-o).
299 for (i=0; i < numfiles; i++) {
300 sprintf(fname, "%s/file%06d", test_dir, i);
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);
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);
316 ret = name_to_handle_at(AT_FDCWD, fname, &handle[i].fh, &mount_id, 0);
319 strcat(fname, ": name_to_handle");
325 /* Open without close to keep unlinked files around */
326 fd = open(fname, O_RDONLY);
328 strcat(fname, ": open(O_RDONLY)");
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);
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);
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);
358 ret = name_to_handle_at(AT_FDCWD, test_dir, &dir_handle.fh, &mount_id, 0);
361 strcat(dname, ": name_to_handle");
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);
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);
380 strcat(fname, ": open");
384 if (write(fd, "aaaa", 4) != 4) {
385 strcat(fname, ": write before");
392 /* If creating test set or saving files handles, we are done */
393 if (create || out_fd)
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);
402 strcat(fname2, ": link");
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);
414 strcat(fname2, ": rename");
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);
426 strcat(fname, ": unlink");
430 /* with -d flag, delete the hardlink if it exists */
432 ret = unlink(fname2);
433 if (ret < 0 && errno != ENOENT) {
434 strcat(fname2, ": unlink");
440 if (parent && delete && !nlink) {
441 ret = rmdir(test_dir);
443 strcat(dname, ": rmdir");
449 /* sync to get log forced for unlink transactions to hit the disk */
452 /* sync once more FTW */
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.
461 ret = system("echo 3 > /proc/sys/vm/drop_caches");
463 perror("drop_caches");
469 * now try to open the files by the stored handles. Expecting ESTALE
470 * if all files and their hardlinks have been unlinked.
472 for (i=0; i < numfiles; i++) {
474 fd = open_by_handle_at(mount_fd, &handle[i].fh, wrafter ? O_RDWR : O_RDONLY);
475 if ((nlink || keepopen) && fd >= 0) {
478 int size = read(fd, buf, 4);
480 strcat(fname, ": read");
484 if (size < 4 || memcmp(buf, "aaaa", 4)) {
485 printf("open_by_handle(%s) returned stale data '%.*s'!\n", fname, size, buf);
488 if (wrafter && write(fd, "aaaa", 4) != 4) {
489 strcat(fname, ": write after");
496 } else if (!nlink && !keepopen && fd < 0 && (errno == ENOENT || errno == ESTALE)) {
499 sprintf(fname, "%s/file%06d", test_dir, i);
501 printf("open_by_handle(%s) opened an unlinked file!\n", fname);
504 printf("open_by_handle(%s) returned %d incorrectly on %s%s file!\n",
506 nlink ? "a linked" : "an unlinked",
507 keepopen ? " open" : "");
513 fd = open_by_handle_at(mount_fd, &dir_handle.fh, O_RDONLY|O_DIRECTORY);
516 printf("open_by_handle(%s) opened an unlinked dir!\n", dname);
520 * Sanity check dir fd - expect to access orig file IFF
521 * it was not unlinked nor renamed.
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");
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).
536 strcpy(fname2, "link000000");
537 ret = faccessat(fd, fname2, F_OK, 0);
538 if (ret < 0 && (nlink > 1 || delete || move ||
540 strcat(fname2, ": unexpected result from faccessat");
547 } else if (nlink || !(errno == ENOENT || errno == ESTALE)) {
548 printf("open_by_handle(%s) returned %d incorrectly on %s dir!\n",
550 nlink ? "a linked" : "an unlinked");
559 * Sleep keeping files open by handle - the program need to be killed
560 * to release the open files.