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");
120 int main(int argc, char **argv)
126 char mname[PATH_MAX];
127 char dname[PATH_MAX];
128 char fname[PATH_MAX];
129 char fname2[PATH_MAX];
132 int mount_fd, mount_id;
133 char *infile = NULL, *outfile = NULL;
134 int in_fd = 0, out_fd = 0;
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;
143 while ((c = getopt(argc, argv, "cludmrwapknhi:o:s")) != -1) {
149 /* Write data before open_by_handle_at() */
153 /* Read data after open_by_handle_at() */
157 /* Write data after open_by_handle_at() */
185 in_fd = open(infile, O_RDONLY);
193 out_fd = creat(outfile, 0644);
203 fprintf(stderr, "illegal option '%s'\n", argv[optind]);
210 test_dir = argv[optind++];
212 numfiles = atoi(argv[optind]);
213 if (!numfiles || numfiles > MAXFILES) {
214 fprintf(stderr, "illegal value '%s' for num_files\n", argv[optind]);
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:
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
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.
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.
244 strcpy(dname, test_dir);
245 if (parent && !in_fd) {
246 strcpy(mname, test_dir);
247 mount_dir = dirname(mname);
249 ret = mkdir(test_dir, 0700);
250 if (ret < 0 && errno != EEXIST) {
251 strcat(dname, ": mkdir");
256 mount_dir = test_dir;
259 mount_fd = open(mount_dir, O_RDONLY|O_DIRECTORY);
266 * Create the test files and remove leftover hardlinks from previous run
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);
273 strcat(fname, ": open(O_CREAT)");
278 /* blow up leftovers hardlinks if they exist */
279 ret = unlink(fname2);
280 if (ret < 0 && errno != ENOENT) {
281 strcat(fname2, ": unlink");
287 /* sync to get the new inodes to hit the disk */
291 * encode the file handles or read them from file (-i) and maybe store
292 * them to a file (-o).
294 for (i=0; i < numfiles; i++) {
295 sprintf(fname, "%s/file%06d", test_dir, i);
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);
303 handle[i].fh.handle_bytes = MAX_HANDLE_SZ;
304 ret = name_to_handle_at(AT_FDCWD, fname, &handle[i].fh, &mount_id, 0);
306 strcat(fname, ": name_to_handle");
312 /* Open without close to keep unlinked files around */
313 fd = open(fname, O_RDONLY);
315 strcat(fname, ": open(O_RDONLY)");
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);
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);
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);
340 strcat(dname, ": name_to_handle");
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);
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);
359 strcat(fname, ": open");
363 if (write(fd, "aaaa", 4) != 4) {
364 strcat(fname, ": write before");
371 /* If creating test set or saving files handles, we are done */
372 if (create || out_fd)
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);
381 strcat(fname2, ": link");
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);
393 strcat(fname2, ": rename");
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);
405 strcat(fname, ": unlink");
409 /* with -d flag, delete the hardlink if it exists */
411 ret = unlink(fname2);
412 if (ret < 0 && errno != ENOENT) {
413 strcat(fname2, ": unlink");
419 if (parent && delete && !nlink) {
420 ret = rmdir(test_dir);
422 strcat(dname, ": rmdir");
428 /* sync to get log forced for unlink transactions to hit the disk */
431 /* sync once more FTW */
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.
440 ret = system("echo 3 > /proc/sys/vm/drop_caches");
442 perror("drop_caches");
448 * now try to open the files by the stored handles. Expecting ESTALE
449 * if all files and their hardlinks have been unlinked.
451 for (i=0; i < numfiles; i++) {
453 fd = open_by_handle_at(mount_fd, &handle[i].fh, wrafter ? O_RDWR : O_RDONLY);
454 if ((nlink || keepopen) && fd >= 0) {
457 int size = read(fd, buf, 4);
459 strcat(fname, ": read");
463 if (size < 4 || memcmp(buf, "aaaa", 4)) {
464 printf("open_by_handle(%s) returned stale data '%.*s'!\n", fname, size, buf);
467 if (wrafter && write(fd, "aaaa", 4) != 4) {
468 strcat(fname, ": write after");
475 } else if (!nlink && !keepopen && fd < 0 && (errno == ENOENT || errno == ESTALE)) {
478 sprintf(fname, "%s/file%06d", test_dir, i);
480 printf("open_by_handle(%s) opened an unlinked file!\n", fname);
483 printf("open_by_handle(%s) returned %d incorrectly on %s%s file!\n",
485 nlink ? "a linked" : "an unlinked",
486 keepopen ? " open" : "");
492 fd = open_by_handle_at(mount_fd, &dir_handle.fh, O_RDONLY|O_DIRECTORY);
495 printf("open_by_handle(%s) opened an unlinked dir!\n", dname);
499 * Sanity check dir fd - expect to access orig file IFF
500 * it was not unlinked nor renamed.
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");
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).
515 strcpy(fname2, "link000000");
516 ret = faccessat(fd, fname2, F_OK, 0);
517 if (ret < 0 && (nlink > 1 || delete || move ||
519 strcat(fname2, ": unexpected result from faccessat");
526 } else if (nlink || !(errno == ENOENT || errno == ESTALE)) {
527 printf("open_by_handle(%s) returned %d incorrectly on %s dir!\n",
529 nlink ? "a linked" : "an unlinked");
538 * Sleep keeping files open by handle - the program need to be killed
539 * to release the open files.