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