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