open_by_handle: add filename to error reports
[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                         strcat(fname, ": open");
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                         strcat(fname2, ": unlink");
168                         perror(fname2);
169                         return EXIT_FAILURE;
170                 }
171         }
172
173         /* sync to get the new inodes to hit the disk */
174         sync();
175
176         /* create the handles */
177         for (i=0; i < numfiles; i++) {
178                 sprintf(fname, "%s/file%06d", test_dir, i);
179                 handle[i].fh.handle_bytes = MAX_HANDLE_SZ;
180                 ret = name_to_handle_at(AT_FDCWD, fname, &handle[i].fh, &mount_id, 0);
181                 if (ret < 0) {
182                         strcat(fname, ": name_to_handle");
183                         perror(fname);
184                         return EXIT_FAILURE;
185                 }
186         }
187
188         /* after creating test set only check that fs supports exportfs */
189         if (create)
190                 return EXIT_SUCCESS;
191
192         /* hardlink the files */
193         for (i=0; nlink > 1 && i < numfiles; i++) {
194                 sprintf(fname, "%s/file%06d", test_dir, i);
195                 sprintf(fname2, "%s/link%06d", test_dir, i);
196                 ret = link(fname, fname2);
197                 if (ret < 0) {
198                         strcat(fname2, ": link");
199                         perror(fname2);
200                         return EXIT_FAILURE;
201                 }
202         }
203
204         /* unlink the files */
205         for (i=0; delete && i < numfiles; i++) {
206                 sprintf(fname, "%s/file%06d", test_dir, i);
207                 sprintf(fname2, "%s/link%06d", test_dir, i);
208                 ret = unlink(fname);
209                 if (ret < 0) {
210                         strcat(fname, ": unlink");
211                         perror(fname);
212                         return EXIT_FAILURE;
213                 }
214                 /* with -d flag, delete the hardlink if it exists */
215                 if (!nlink)
216                         ret = unlink(fname2);
217                 if (ret < 0 && errno != ENOENT) {
218                         strcat(fname2, ": unlink");
219                         perror(fname2);
220                         return EXIT_FAILURE;
221                 }
222         }
223
224         /* sync to get log forced for unlink transactions to hit the disk */
225         sync();
226
227         /* sync once more FTW */
228         sync();
229
230         /*
231          * now drop the caches so that unlinked inodes are reclaimed and
232          * buftarg page cache is emptied so that the inode cluster has to be
233          * fetched from disk again for the open_by_handle() call.
234          */
235         ret = system("echo 3 > /proc/sys/vm/drop_caches");
236         if (ret < 0) {
237                 perror("drop_caches");
238                 return EXIT_FAILURE;
239         }
240
241         /*
242          * now try to open the files by the stored handles. Expecting ESTALE
243          * if all files and their hardlinks have been unlinked.
244          */
245         for (i=0; i < numfiles; i++) {
246                 errno = 0;
247                 fd = open_by_handle_at(mount_fd, &handle[i].fh, O_RDWR);
248                 if (nlink && fd >= 0) {
249                         close(fd);
250                         continue;
251                 } else if (!nlink && fd < 0 && (errno == ENOENT || errno == ESTALE)) {
252                         continue;
253                 }
254                 sprintf(fname, "%s/file%06d", test_dir, i);
255                 if (fd >= 0) {
256                         printf("open_by_handle(%s) opened an unlinked file!\n", fname);
257                         close(fd);
258                 } else {
259                         printf("open_by_handle(%s) returned %d incorrectly on %s file!\n",
260                                         fname, errno,
261                                         nlink ? "a linked" : "an unlinked");
262                 }
263                 failed++;
264         }
265         if (failed)
266                 return EXIT_FAILURE;
267         return EXIT_SUCCESS;
268 }