src/open_by_handle: flexible usage options
[xfstests-dev.git] / src / open_by_handle.c
1 /*
2  * open_by_handle.c - attempt to create a file handle and open it
3  *                    with open_by_handle_at() syscall
4  *
5  * Copyright (C) 2017 CTERA Networks. All Rights Reserved.
6  * Author: Amir Goldstein <amir73il@gmail.com>
7  *
8  * from:
9  *  stale_handle.c
10  *
11  *  Copyright (C) 2010 Red Hat, Inc. All Rights reserved.
12  *
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.
17  *
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.
22  *
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.
26  */
27
28 /*
29
30 usage: open_by_handle [-c|-l|-u|-d] <test_dir> [num_files]
31
32 Examples:
33
34 1. Create test set of N files and try to get their NFS handles:
35
36    open_by_handle -c <test_dir> [N]
37
38    This is used by new helper _require_exportfs() to check
39    if filesystem supports exportfs
40
41 2. Get file handles for existing test set, drop caches and try to
42    open all files by handle:
43
44    open_by_handle <test_dir> [N]
45
46 3. Get file handles for existing test set, unlink all test files,
47    drop caches, try to open all files by handle and expect ESTALE:
48
49    open_by_handle -d <test_dir> [N]
50
51 4. Get file handles for existing test set, hardlink all test files,
52    then unlink the original files, drop caches and try to open all
53    files by handle (should work):
54
55    open_by_handle -l <test_dir> [N]
56    open_by_handle -u <test_dir> [N]
57
58    This test is done with 2 invocations of the program, first to
59    hardlink (-l) and then to unlink the originals (-u), because
60    we would like to be able to perform the hardlinks on overlay
61    lower layer and unlink on upper layer.
62
63    NOTE that open_by_handle -u doesn't check if the files are
64    hardlinked, it just assumes that they are.  If they are not
65    then the test will fail, because file handles would be stale.
66 */
67
68 #include <stdio.h>
69 #include <stdlib.h>
70 #include <string.h>
71 #include <fcntl.h>
72 #include <unistd.h>
73 #include <sys/stat.h>
74 #include <sys/types.h>
75 #include <errno.h>
76 #include <linux/limits.h>
77
78 #define MAXFILES 1024
79
80 struct handle {
81         struct file_handle fh;
82         unsigned char fid[MAX_HANDLE_SZ];
83 } handle[MAXFILES];
84
85 void usage(void)
86 {
87         fprintf(stderr, "usage: open_by_handle [-c|-l|-u|-d] <test_dir> [num_files]\n");
88         fprintf(stderr, "\n");
89         fprintf(stderr, "open_by_handle -c <test_dir> [N] - create N test files under test_dir, try to get file handles and exit\n");
90         fprintf(stderr, "open_by_handle    <test_dir> [N] - get file handles of test files, drop caches and try to open by handle\n");
91         fprintf(stderr, "open_by_handle -l <test_dir> [N] - create hardlinks to test files, drop caches and try to open by handle\n");
92         fprintf(stderr, "open_by_handle -u <test_dir> [N] - unlink (hardlinked) test files, drop caches and try to open by handle\n");
93         fprintf(stderr, "open_by_handle -d <test_dir> [N] - unlink test files and hardlinks, drop caches and try to open by handle\n");
94         exit(EXIT_FAILURE);
95 }
96
97 int main(int argc, char **argv)
98 {
99         int     i, c;
100         int     fd;
101         int     ret;
102         int     failed = 0;
103         char    fname[PATH_MAX];
104         char    fname2[PATH_MAX];
105         char    *test_dir;
106         int     mount_fd, mount_id;
107         int     numfiles = 1;
108         int     create = 0, delete = 0, nlink = 1;
109
110         if (argc < 2 || argc > 4)
111                 usage();
112
113         while ((c = getopt(argc, argv, "clud")) != -1) {
114                 switch (c) {
115                 case 'c':
116                         create = 1;
117                         break;
118                 case 'l':
119                         nlink = 2;
120                         break;
121                 case 'u':
122                         delete = 1;
123                         nlink = 1;
124                         break;
125                 case 'd':
126                         delete = 1;
127                         nlink = 0;
128                         break;
129                 default:
130                         fprintf(stderr, "illegal option '%s'\n", argv[optind]);
131                 case 'h':
132                         usage();
133                 }
134         }
135         if (optind == argc || optind > 2)
136             usage();
137         test_dir = argv[optind++];
138         if (optind < argc)
139                 numfiles = atoi(argv[optind]);
140         if (!numfiles || numfiles > MAXFILES) {
141                 fprintf(stderr, "illegal value '%s' for num_files\n", argv[optind]);
142                 usage();
143         }
144
145         mount_fd = open(test_dir, O_RDONLY|O_DIRECTORY);
146         if (mount_fd < 0) {
147                 perror(test_dir);
148                 return EXIT_FAILURE;
149         }
150
151         /*
152          * Create the test files and remove leftover hardlinks from previous run
153          */
154         for (i=0; create && i < numfiles; i++) {
155                 sprintf(fname, "%s/file%06d", test_dir, i);
156                 sprintf(fname2, "%s/link%06d", test_dir, i);
157                 fd = open(fname, O_RDWR | O_CREAT | O_TRUNC, 0644);
158                 if (fd < 0) {
159                         printf("Warning (%s,%d), open(%s) failed.\n", __FILE__, __LINE__, fname);
160                         perror(fname);
161                         return EXIT_FAILURE;
162                 }
163                 close(fd);
164                 /* blow up leftovers hardlinks if they exist */
165                 ret = unlink(fname2);
166                 if (ret < 0 && errno != ENOENT) {
167                         perror("unlink");
168                         return EXIT_FAILURE;
169                 }
170         }
171
172         /* sync to get the new inodes to hit the disk */
173         sync();
174
175         /* create the handles */
176         for (i=0; i < numfiles; i++) {
177                 sprintf(fname, "%s/file%06d", test_dir, i);
178                 handle[i].fh.handle_bytes = MAX_HANDLE_SZ;
179                 ret = name_to_handle_at(AT_FDCWD, fname, &handle[i].fh, &mount_id, 0);
180                 if (ret < 0) {
181                         perror("name_to_handle");
182                         return EXIT_FAILURE;
183                 }
184         }
185
186         /* after creating test set only check that fs supports exportfs */
187         if (create)
188                 return EXIT_SUCCESS;
189
190         /* hardlink the files */
191         for (i=0; nlink > 1 && i < numfiles; i++) {
192                 sprintf(fname, "%s/file%06d", test_dir, i);
193                 sprintf(fname2, "%s/link%06d", test_dir, i);
194                 ret = link(fname, fname2);
195                 if (ret < 0) {
196                         perror("link");
197                         return EXIT_FAILURE;
198                 }
199         }
200
201         /* unlink the files */
202         for (i=0; delete && i < numfiles; i++) {
203                 sprintf(fname, "%s/file%06d", test_dir, i);
204                 sprintf(fname2, "%s/link%06d", test_dir, i);
205                 ret = unlink(fname);
206                 if (ret < 0) {
207                         perror("unlink");
208                         return EXIT_FAILURE;
209                 }
210                 /* with -d flag, delete the hardlink if it exists */
211                 if (!nlink)
212                         ret = unlink(fname2);
213                 if (ret < 0 && errno != ENOENT) {
214                         perror("unlink");
215                         return EXIT_FAILURE;
216                 }
217         }
218
219         /* sync to get log forced for unlink transactions to hit the disk */
220         sync();
221
222         /* sync once more FTW */
223         sync();
224
225         /*
226          * now drop the caches so that unlinked inodes are reclaimed and
227          * buftarg page cache is emptied so that the inode cluster has to be
228          * fetched from disk again for the open_by_handle() call.
229          */
230         ret = system("echo 3 > /proc/sys/vm/drop_caches");
231         if (ret < 0) {
232                 perror("drop_caches");
233                 return EXIT_FAILURE;
234         }
235
236         /*
237          * now try to open the files by the stored handles. Expecting ESTALE
238          * if all files and their hardlinks have been unlinked.
239          */
240         for (i=0; i < numfiles; i++) {
241                 errno = 0;
242                 fd = open_by_handle_at(mount_fd, &handle[i].fh, O_RDWR);
243                 if (nlink && fd >= 0) {
244                         close(fd);
245                         continue;
246                 } else if (!nlink && fd < 0 && (errno == ENOENT || errno == ESTALE)) {
247                         continue;
248                 }
249                 if (fd >= 0) {
250                         printf("open_by_handle(%d) opened an unlinked file!\n", i);
251                         close(fd);
252                 } else {
253                         printf("open_by_handle(%d) returned %d incorrectly on %s file!\n", i, errno,
254                                         nlink ? "a linked" : "an unlinked");
255                 }
256                 failed++;
257         }
258         if (failed)
259                 return EXIT_FAILURE;
260         return EXIT_SUCCESS;
261 }