2 * open_by_handle.c - attempt to create a file handle and open it
3 * with open_by_handle_at() syscall
5 * Copyright (C) 2017 CTERA Networks. All Rights Reserved.
6 * Author: Amir Goldstein <amir73il@gmail.com>
11 * Copyright (C) 2010 Red Hat, Inc. All Rights reserved.
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.
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.
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.
30 usage: open_by_handle [-cludmrwapknhs] [<-i|-o> <handles_file>] <test_dir> [num_files]
34 1. Create test set of of test_dir with N files and try to get their NFS handles:
36 open_by_handle -cp <test_dir> [N]
38 This is used by new helper _require_exportfs() to check
39 if filesystem supports exportfs
41 2. Get file handles for existing test set, drop caches and try to
42 open all files by handle:
44 open_by_handle -p <test_dir> [N]
46 3. Get file handles for existing test set and write them to a file:
48 open_by_handle -p -o <handles_file> <test_dir> [N]
50 4. Read file handles from file and open files by handle without
51 dropping caches beforehand. Sleep afterhand to keep files open:
53 open_by_handle -nps -i <handles_file> <test_dir> [N]
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:
59 open_by_handle -rwa <test_dir> [N]
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
65 open_by_handle -dp <test_dir> [N]
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):
71 open_by_handle -dk <test_dir> [N]
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):
76 open_by_handle -m <test_dir> [N]
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):
82 open_by_handle -l <test_dir> [N]
83 open_by_handle -u <test_dir> [N]
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.
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.
100 #include <sys/stat.h>
101 #include <sys/types.h>
103 #include <linux/limits.h>
106 #define MAXFILES 1024
109 struct file_handle fh;
110 unsigned char fid[MAX_HANDLE_SZ];
111 } handle[MAXFILES], dir_handle;
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");
135 int main(int argc, char **argv)
141 char dname[PATH_MAX];
142 char fname[PATH_MAX];
143 char fname2[PATH_MAX];
146 int mount_fd, mount_id;
147 char *infile = NULL, *outfile = NULL;
148 int in_fd = 0, out_fd = 0;
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;
157 while ((c = getopt(argc, argv, "cludmrwapknhi:o:s")) != -1) {
163 /* Write data before open_by_handle_at() */
167 /* Read data after open_by_handle_at() */
171 /* Write data after open_by_handle_at() */
199 in_fd = open(infile, O_RDONLY);
207 out_fd = creat(outfile, 0644);
217 fprintf(stderr, "illegal option '%s'\n", argv[optind]);
224 test_dir = argv[optind++];
226 numfiles = atoi(argv[optind]);
227 if (!numfiles || numfiles > MAXFILES) {
228 fprintf(stderr, "illegal value '%s' for num_files\n", argv[optind]);
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:
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
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.
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.
258 if (parent && !in_fd) {
259 strcpy(dname, test_dir);
260 mount_dir = dirname(dname);
262 ret = mkdir(test_dir, 0700);
263 if (ret < 0 && errno != EEXIST) {
264 strcat(dname, ": mkdir");
269 mount_dir = test_dir;
272 mount_fd = open(mount_dir, O_RDONLY|O_DIRECTORY);
279 * Create the test files and remove leftover hardlinks from previous run
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);
286 strcat(fname, ": open(O_CREAT)");
291 /* blow up leftovers hardlinks if they exist */
292 ret = unlink(fname2);
293 if (ret < 0 && errno != ENOENT) {
294 strcat(fname2, ": unlink");
300 /* sync to get the new inodes to hit the disk */
304 * encode the file handles or read them from file (-i) and maybe store
305 * them to a file (-o).
307 for (i=0; i < numfiles; i++) {
308 sprintf(fname, "%s/file%06d", test_dir, i);
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);
316 handle[i].fh.handle_bytes = MAX_HANDLE_SZ;
317 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 = MAX_HANDLE_SZ;
351 ret = name_to_handle_at(AT_FDCWD, test_dir, &dir_handle.fh, &mount_id, 0);
353 strcat(dname, ": name_to_handle");
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);
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);
372 strcat(fname, ": open");
376 if (write(fd, "aaaa", 4) != 4) {
377 strcat(fname, ": write before");
384 /* If creating test set or saving files handles, we are done */
385 if (create || out_fd)
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);
394 strcat(fname2, ": link");
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);
406 strcat(fname2, ": rename");
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);
418 strcat(fname, ": unlink");
422 /* with -d flag, delete the hardlink if it exists */
424 ret = unlink(fname2);
425 if (ret < 0 && errno != ENOENT) {
426 strcat(fname2, ": unlink");
432 if (parent && delete && !nlink) {
433 ret = rmdir(test_dir);
435 strcat(dname, ": rmdir");
441 /* sync to get log forced for unlink transactions to hit the disk */
444 /* sync once more FTW */
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.
453 ret = system("echo 3 > /proc/sys/vm/drop_caches");
455 perror("drop_caches");
461 * now try to open the files by the stored handles. Expecting ESTALE
462 * if all files and their hardlinks have been unlinked.
464 for (i=0; i < numfiles; i++) {
466 fd = open_by_handle_at(mount_fd, &handle[i].fh, wrafter ? O_RDWR : O_RDONLY);
467 if ((nlink || keepopen) && fd >= 0) {
470 int size = read(fd, buf, 4);
472 strcat(fname, ": read");
476 if (size < 4 || memcmp(buf, "aaaa", 4)) {
477 printf("open_by_handle(%s) returned stale data '%.*s'!\n", fname, size, buf);
480 if (wrafter && write(fd, "aaaa", 4) != 4) {
481 strcat(fname, ": write after");
488 } else if (!nlink && !keepopen && fd < 0 && (errno == ENOENT || errno == ESTALE)) {
491 sprintf(fname, "%s/file%06d", test_dir, i);
493 printf("open_by_handle(%s) opened an unlinked file!\n", fname);
496 printf("open_by_handle(%s) returned %d incorrectly on %s%s file!\n",
498 nlink ? "a linked" : "an unlinked",
499 keepopen ? " open" : "");
505 fd = open_by_handle_at(mount_fd, &dir_handle.fh, O_RDONLY|O_DIRECTORY);
508 printf("open_by_handle(%s) opened an unlinked dir!\n", dname);
512 * Sanity check dir fd - expect to access orig file IFF
513 * it was not unlinked nor renamed.
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");
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).
528 strcpy(fname2, "link000000");
529 ret = faccessat(fd, fname2, F_OK, 0);
530 if (ret < 0 && (nlink > 1 || delete || move ||
532 strcat(fname2, ": unexpected result from faccessat");
539 } else if (nlink || !(errno == ENOENT || errno == ESTALE)) {
540 printf("open_by_handle(%s) returned %d incorrectly on %s dir!\n",
542 nlink ? "a linked" : "an unlinked");
551 * Sleep keeping files open by handle - the program need to be killed
552 * to release the open files.