dbc5b0f4d0affad8e632ccd92aedeb3f803431aa
[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 [-cludmrwapk] <test_dir> [num_files]
31
32 Examples:
33
34 1. Create test set of of test_dir with N files and try to get their NFS handles:
35
36    open_by_handle -cp <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 -p <test_dir> [N]
45
46 3. Get file handles for existing test set, write data to files,
47    drop caches, open all files by handle, read and verify written
48    data, write new data to file:
49
50    open_by_handle -rwa <test_dir> [N]
51
52 4. Get file handles for existing test set, unlink all test files,
53    remove test_dir, drop caches, try to open all files by handle
54    and expect ESTALE:
55
56    open_by_handle -dp <test_dir> [N]
57
58 5. Get file handles for existing test set, keep open file handles for all
59    test files, unlink all test files, drop caches and try to open all files
60    by handle (should work):
61
62    open_by_handle -dk <test_dir> [N]
63
64 6. Get file handles for existing test set, rename all test files,
65    drop caches, try to open all files by handle (should work):
66
67    open_by_handle -m <test_dir> [N]
68
69 7. Get file handles for existing test set, hardlink all test files,
70    then unlink the original files, drop caches and try to open all
71    files by handle (should work):
72
73    open_by_handle -l <test_dir> [N]
74    open_by_handle -u <test_dir> [N]
75
76    This test is done with 2 invocations of the program, first to
77    hardlink (-l) and then to unlink the originals (-u), because
78    we would like to be able to perform the hardlinks on overlay
79    lower layer and unlink on upper layer.
80
81    NOTE that open_by_handle -u doesn't check if the files are
82    hardlinked, it just assumes that they are.  If they are not
83    then the test will fail, because file handles would be stale.
84 */
85
86 #include <stdio.h>
87 #include <stdlib.h>
88 #include <string.h>
89 #include <fcntl.h>
90 #include <unistd.h>
91 #include <sys/stat.h>
92 #include <sys/types.h>
93 #include <errno.h>
94 #include <linux/limits.h>
95 #include <libgen.h>
96
97 #define MAXFILES 1024
98
99 struct handle {
100         struct file_handle fh;
101         unsigned char fid[MAX_HANDLE_SZ];
102 } handle[MAXFILES], dir_handle;
103
104 void usage(void)
105 {
106         fprintf(stderr, "usage: open_by_handle [-cludmrwapk] <test_dir> [num_files]\n");
107         fprintf(stderr, "\n");
108         fprintf(stderr, "open_by_handle -c <test_dir> [N] - create N test files under test_dir, try to get file handles and exit\n");
109         fprintf(stderr, "open_by_handle    <test_dir> [N] - get file handles of test files, drop caches and try to open by handle\n");
110         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");
111         fprintf(stderr, "open_by_handle -w <test_dir> [N] - write data to test files before open by handle\n");
112         fprintf(stderr, "open_by_handle -r <test_dir> [N] - read data from test files after open by handle and verify written data\n");
113         fprintf(stderr, "open_by_handle -a <test_dir> [N] - write data to test files after open by handle\n");
114         fprintf(stderr, "open_by_handle -l <test_dir> [N] - create hardlinks to test files, drop caches and try to open by handle\n");
115         fprintf(stderr, "open_by_handle -u <test_dir> [N] - unlink (hardlinked) test files, drop caches and try to open by handle\n");
116         fprintf(stderr, "open_by_handle -d <test_dir> [N] - unlink test files and hardlinks, drop caches and try to open by handle\n");
117         fprintf(stderr, "open_by_handle -m <test_dir> [N] - rename test files, drop caches and try to open by handle\n");
118         fprintf(stderr, "open_by_handle -p <test_dir>     - create/delete and try to open by handle also test_dir itself\n");
119         exit(EXIT_FAILURE);
120 }
121
122 int main(int argc, char **argv)
123 {
124         int     i, c;
125         int     fd;
126         int     ret = 0;
127         int     failed = 0;
128         char    dname[PATH_MAX];
129         char    fname[PATH_MAX];
130         char    fname2[PATH_MAX];
131         char    *test_dir;
132         char    *mount_dir;
133         int     mount_fd, mount_id;
134         int     numfiles = 1;
135         int     create = 0, delete = 0, nlink = 1, move = 0;
136         int     rd = 0, wr = 0, wrafter = 0, parent = 0;
137         int     keepopen = 0;
138
139         if (argc < 2 || argc > 4)
140                 usage();
141
142         while ((c = getopt(argc, argv, "cludmrwapk")) != -1) {
143                 switch (c) {
144                 case 'c':
145                         create = 1;
146                         break;
147                 case 'w':
148                         /* Write data before open_by_handle_at() */
149                         wr = 1;
150                         break;
151                 case 'r':
152                         /* Read data after open_by_handle_at() */
153                         rd = 1;
154                         break;
155                 case 'a':
156                         /* Write data after open_by_handle_at() */
157                         wrafter = 1;
158                         break;
159                 case 'l':
160                         nlink = 2;
161                         break;
162                 case 'u':
163                         delete = 1;
164                         nlink = 1;
165                         break;
166                 case 'd':
167                         delete = 1;
168                         nlink = 0;
169                         break;
170                 case 'm':
171                         move = 1;
172                         break;
173                 case 'p':
174                         parent = 1;
175                         break;
176                 case 'k':
177                         keepopen = 1;
178                         break;
179                 default:
180                         fprintf(stderr, "illegal option '%s'\n", argv[optind]);
181                 case 'h':
182                         usage();
183                 }
184         }
185         if (optind == argc || optind > 2)
186             usage();
187         test_dir = argv[optind++];
188         if (optind < argc)
189                 numfiles = atoi(argv[optind]);
190         if (!numfiles || numfiles > MAXFILES) {
191                 fprintf(stderr, "illegal value '%s' for num_files\n", argv[optind]);
192                 usage();
193         }
194
195         if (parent) {
196                 strcpy(dname, test_dir);
197                 /*
198                  * -p flag implies that test_dir is NOT a mount point,
199                  * so its parent can be used as mount_fd for open_by_handle_at.
200                  */
201                 mount_dir = dirname(dname);
202                 if (create)
203                         ret = mkdir(test_dir, 0700);
204                 if (ret < 0 && errno != EEXIST) {
205                         strcat(dname, ": mkdir");
206                         perror(dname);
207                         return EXIT_FAILURE;
208                 }
209         } else {
210                 mount_dir = test_dir;
211         }
212
213         mount_fd = open(mount_dir, O_RDONLY|O_DIRECTORY);
214         if (mount_fd < 0) {
215                 perror(mount_dir);
216                 return EXIT_FAILURE;
217         }
218
219         /*
220          * Create the test files and remove leftover hardlinks from previous run
221          */
222         for (i=0; create && i < numfiles; i++) {
223                 sprintf(fname, "%s/file%06d", test_dir, i);
224                 sprintf(fname2, "%s/link%06d", test_dir, i);
225                 fd = open(fname, O_RDWR | O_CREAT | O_TRUNC, 0644);
226                 if (fd < 0) {
227                         strcat(fname, ": open(O_CREAT)");
228                         perror(fname);
229                         return EXIT_FAILURE;
230                 }
231                 close(fd);
232                 /* blow up leftovers hardlinks if they exist */
233                 ret = unlink(fname2);
234                 if (ret < 0 && errno != ENOENT) {
235                         strcat(fname2, ": unlink");
236                         perror(fname2);
237                         return EXIT_FAILURE;
238                 }
239         }
240
241         /* sync to get the new inodes to hit the disk */
242         sync();
243
244         /* create the handles */
245         for (i=0; i < numfiles; i++) {
246                 sprintf(fname, "%s/file%06d", test_dir, i);
247                 handle[i].fh.handle_bytes = MAX_HANDLE_SZ;
248                 ret = name_to_handle_at(AT_FDCWD, fname, &handle[i].fh, &mount_id, 0);
249                 if (ret < 0) {
250                         strcat(fname, ": name_to_handle");
251                         perror(fname);
252                         return EXIT_FAILURE;
253                 }
254                 if (keepopen) {
255                         /* Open without close to keep unlinked files around */
256                         fd = open(fname, O_RDONLY);
257                         if (fd < 0) {
258                                 strcat(fname, ": open(O_RDONLY)");
259                                 perror(fname);
260                                 return EXIT_FAILURE;
261                         }
262                 }
263         }
264
265         if (parent) {
266                 dir_handle.fh.handle_bytes = MAX_HANDLE_SZ;
267                 ret = name_to_handle_at(AT_FDCWD, test_dir, &dir_handle.fh, &mount_id, 0);
268                 if (ret < 0) {
269                         strcat(dname, ": name_to_handle");
270                         perror(dname);
271                         return EXIT_FAILURE;
272                 }
273         }
274
275         /* write data to files */
276         for (i=0; wr && i < numfiles; i++) {
277                 sprintf(fname, "%s/file%06d", test_dir, i);
278                 fd = open(fname, O_WRONLY, 0644);
279                 if (fd < 0) {
280                         strcat(fname, ": open");
281                         perror(fname);
282                         return EXIT_FAILURE;
283                 }
284                 if (write(fd, "aaaa", 4) != 4) {
285                         strcat(fname, ": write before");
286                         perror(fname);
287                         return EXIT_FAILURE;
288                 }
289                 close(fd);
290         }
291
292         /* after creating test set only check that fs supports exportfs */
293         if (create)
294                 return EXIT_SUCCESS;
295
296         /* hardlink the files */
297         for (i=0; nlink > 1 && i < numfiles; i++) {
298                 sprintf(fname, "%s/file%06d", test_dir, i);
299                 sprintf(fname2, "%s/link%06d", test_dir, i);
300                 ret = link(fname, fname2);
301                 if (ret < 0) {
302                         strcat(fname2, ": link");
303                         perror(fname2);
304                         return EXIT_FAILURE;
305                 }
306         }
307
308         /* rename the files */
309         for (i=0; move && i < numfiles; i++) {
310                 sprintf(fname, "%s/file%06d", test_dir, i);
311                 sprintf(fname2, "%s/link%06d", test_dir, i);
312                 ret = rename(fname, fname2);
313                 if (ret < 0) {
314                         strcat(fname2, ": rename");
315                         perror(fname2);
316                         return EXIT_FAILURE;
317                 }
318         }
319
320         /* unlink the files */
321         for (i=0; delete && i < numfiles; i++) {
322                 sprintf(fname, "%s/file%06d", test_dir, i);
323                 sprintf(fname2, "%s/link%06d", test_dir, i);
324                 ret = unlink(fname);
325                 if (ret < 0) {
326                         strcat(fname, ": unlink");
327                         perror(fname);
328                         return EXIT_FAILURE;
329                 }
330                 /* with -d flag, delete the hardlink if it exists */
331                 if (!nlink)
332                         ret = unlink(fname2);
333                 if (ret < 0 && errno != ENOENT) {
334                         strcat(fname2, ": unlink");
335                         perror(fname2);
336                         return EXIT_FAILURE;
337                 }
338         }
339
340         if (parent && delete && !nlink) {
341                 ret = rmdir(test_dir);
342                 if (ret < 0) {
343                         strcat(dname, ": rmdir");
344                         perror(dname);
345                         return EXIT_FAILURE;
346                 }
347         }
348
349         /* sync to get log forced for unlink transactions to hit the disk */
350         sync();
351
352         /* sync once more FTW */
353         sync();
354
355         /*
356          * now drop the caches so that unlinked inodes are reclaimed and
357          * buftarg page cache is emptied so that the inode cluster has to be
358          * fetched from disk again for the open_by_handle() call.
359          */
360         ret = system("echo 3 > /proc/sys/vm/drop_caches");
361         if (ret < 0) {
362                 perror("drop_caches");
363                 return EXIT_FAILURE;
364         }
365
366         /*
367          * now try to open the files by the stored handles. Expecting ESTALE
368          * if all files and their hardlinks have been unlinked.
369          */
370         for (i=0; i < numfiles; i++) {
371                 errno = 0;
372                 fd = open_by_handle_at(mount_fd, &handle[i].fh, wrafter ? O_RDWR : O_RDONLY);
373                 if ((nlink || keepopen) && fd >= 0) {
374                         if (rd) {
375                                 char buf[4] = {0};
376                                 int size = read(fd, buf, 4);
377                                 if (size < 0) {
378                                         strcat(fname, ": read");
379                                         perror(fname);
380                                         return EXIT_FAILURE;
381                                 }
382                                 if (size < 4 || memcmp(buf, "aaaa", 4)) {
383                                         printf("open_by_handle(%s) returned stale data '%.*s'!\n", fname, size, buf);
384                                 }
385                         }
386                         if (wrafter && write(fd, "aaaa", 4) != 4) {
387                                 strcat(fname, ": write after");
388                                 perror(fname);
389                                 return EXIT_FAILURE;
390                         }
391                         close(fd);
392                         continue;
393                 } else if (!nlink && !keepopen && fd < 0 && (errno == ENOENT || errno == ESTALE)) {
394                         continue;
395                 }
396                 sprintf(fname, "%s/file%06d", test_dir, i);
397                 if (fd >= 0) {
398                         printf("open_by_handle(%s) opened an unlinked file!\n", fname);
399                         close(fd);
400                 } else {
401                         printf("open_by_handle(%s) returned %d incorrectly on %s%s file!\n",
402                                         fname, errno,
403                                         nlink ? "a linked" : "an unlinked",
404                                         keepopen ? " open" : "");
405                 }
406                 failed++;
407         }
408
409         if (parent) {
410                 fd = open_by_handle_at(mount_fd, &dir_handle.fh, O_RDONLY|O_DIRECTORY);
411                 if (fd >= 0) {
412                         if (!nlink) {
413                                 printf("open_by_handle(%s) opened an unlinked dir!\n", dname);
414                                 return EXIT_FAILURE;
415                         } else {
416                                 /*
417                                  * Sanity check dir fd - expect to access orig file IFF
418                                  * it was not unlinked nor renamed.
419                                  */
420                                 strcpy(fname, "file000000");
421                                 ret = faccessat(fd, fname, F_OK, 0);
422                                 if ((ret == 0) != (!delete && !move) ||
423                                     ((ret < 0) && errno != ENOENT)) {
424                                         strcat(fname, ": unexpected result from faccessat");
425                                         perror(fname);
426                                         return EXIT_FAILURE;
427                                 }
428                                 /*
429                                  * Expect to access link file if ran test with -l flag
430                                  * (nlink > 1), -m flag (orig moved to link name) or
431                                  * -u flag (which implied previous -l run).
432                                  */
433                                 strcpy(fname2, "link000000");
434                                 ret = faccessat(fd, fname2, F_OK, 0);
435                                 if (ret < 0 && (nlink > 1 || delete || move ||
436                                                 errno != ENOENT)) {
437                                         strcat(fname2, ": unexpected result from faccessat");
438                                         perror(fname2);
439                                         return EXIT_FAILURE;
440                                 }
441                         }
442                         close(fd);
443                 } else if (nlink || !(errno == ENOENT || errno == ESTALE)) {
444                         printf("open_by_handle(%s) returned %d incorrectly on %s dir!\n",
445                                         dname, errno,
446                                         nlink ? "a linked" : "an unlinked");
447                         return EXIT_FAILURE;
448                 }
449         }
450
451         if (failed)
452                 return EXIT_FAILURE;
453         return EXIT_SUCCESS;
454 }