1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 /*
3 * This file is part of libmount from util-linux project.
4 *
5 * Copyright (C) 2022 Karel Zak <kzak@redhat.com>
6 *
7 * libmount is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation; either version 2.1 of the License, or
10 * (at your option) any later version.
11 *
12 *
13 * This is fsconfig/fsopen based mount.
14 *
15 * Please, see the comment in libmount/src/hooks.c to understand how hooks work.
16 *
17 * Operations: functions and STAGE, all is prepared in hook_prepare():
18 *
19 * mount:
20 * - fsopen PRE
21 * - fsmount MOUNT
22 * - mount_setattr MOUNT (VFS flags)
23 * - mount_move POST
24 * - mount_setattr POST (propagation)
25 *
26 * remount:
27 * - open_tree PRE
28 * - fsconfig MOUNT (FS reconfigure)
29 * - mount_setattr MOUNT (VFS flags)
30 * - mount_setattr POST (propagation)
31 *
32 * propagation-only:
33 * - open_tree PRE
34 * - mount_setattr POST (propagation)
35 *
36 * move:
37 * - open_tree PRE
38 * - mount_move POST
39 *
40 * bind:
41 * - open_tree PRE (clone)
42 * - mount_setattr MOUNT (VFS flags)
43 * - mount_move POST
44 */
45
46 #include "mountP.h"
47 #include "fileutils.h" /* statx() fallback */
48 #include "mount-api-utils.h"
49
50 #include <inttypes.h>
51
52 #ifdef USE_LIBMOUNT_MOUNTFD_SUPPORT
53
54 #define get_sysapi(_cxt) mnt_context_get_sysapi(_cxt)
55
56 static void close_sysapi_fds(struct libmnt_sysapi *api)
57 {
58 if (api->fd_fs >= 0)
59 close(api->fd_fs);
60 if (api->fd_tree >= 0)
61 close(api->fd_tree);
62
63 api->fd_tree = api->fd_fs = -1;
64 }
65
66 /*
67 * This hookset uses 'struct libmnt_sysapi' (mountP.h) as hookset data.
68 */
69 static void free_hookset_data( struct libmnt_context *cxt,
70 const struct libmnt_hookset *hs)
71 {
72 struct libmnt_sysapi *api = mnt_context_get_hookset_data(cxt, hs);
73
74 if (!api)
75 return;
76
77 close_sysapi_fds(api);
78
79 free(api);
80 mnt_context_set_hookset_data(cxt, hs, NULL);
81 }
82
83 /* global data, used by all callbacks */
84 static struct libmnt_sysapi *new_hookset_data(
85 struct libmnt_context *cxt,
86 const struct libmnt_hookset *hs)
87 {
88 struct libmnt_sysapi *api = calloc(1, sizeof(struct libmnt_sysapi));
89
90 if (!api)
91 return NULL;
92 api->fd_fs = api->fd_tree = -1;
93
94 if (mnt_context_set_hookset_data(cxt, hs, api) != 0) {
95 /* probably ENOMEM problem */
96 free(api);
97 api = NULL;
98 }
99 return api;
100 }
101
102 /* de-initiallize this module */
103 static int hookset_deinit(struct libmnt_context *cxt, const struct libmnt_hookset *hs)
104 {
105 DBG(HOOK, ul_debugobj(hs, "deinit '%s'", hs->name));
106
107 /* remove all our hooks */
108 while (mnt_context_remove_hook(cxt, hs, 0, NULL) == 0);
109
110 /* free and remove global hookset data */
111 free_hookset_data(cxt, hs);
112
113 return 0;
114 }
115
116 static inline int fsconfig_set_value(
117 struct libmnt_context *cxt,
118 const struct libmnt_hookset *hs,
119 int fd,
120 const char *name, const char *value)
121 {
122 int rc;
123
124 DBG(HOOK, ul_debugobj(hs, " fsconfig(name=%s,value=%s)", name,
125 value ? : ""));
126 if (value)
127 rc = fsconfig(fd, FSCONFIG_SET_STRING, name, value, 0);
128 else
129 rc = fsconfig(fd, FSCONFIG_SET_FLAG, name, NULL, 0);
130
131 set_syscall_status(cxt, "fsconfig", rc == 0);
132 return rc;
133 }
134
135 static int configure_superblock(struct libmnt_context *cxt,
136 const struct libmnt_hookset *hs,
137 int fd, int force_rwro)
138 {
139 struct libmnt_optlist *ol;
140 struct libmnt_iter itr;
141 struct libmnt_opt *opt;
142 int rc = 0, has_rwro = 0;
143
144 DBG(HOOK, ul_debugobj(hs, " config FS"));
145
146 ol = mnt_context_get_optlist(cxt);
147 if (!ol)
148 return -ENOMEM;
149
150 mnt_reset_iter(&itr, MNT_ITER_FORWARD);
151
152 while (mnt_optlist_next_opt(ol, &itr, &opt) == 0) {
153 const char *name = mnt_opt_get_name(opt);
154 const char *value = mnt_opt_get_value(opt);
155 const struct libmnt_optmap *ent = mnt_opt_get_mapent(opt);
156
157 if (ent && mnt_opt_get_map(opt) == cxt->map_linux &&
158 ent->id == MS_RDONLY) {
159 value = NULL;
160 has_rwro = 1;
161 } else if (!name || mnt_opt_get_map(opt) || mnt_opt_is_external(opt))
162 continue;
163
164 rc = fsconfig_set_value(cxt, hs, fd, name, value);
165 if (rc != 0)
166 goto done;
167 }
168
169 if (force_rwro && !has_rwro)
170 rc = fsconfig_set_value(cxt, hs, fd, "rw", NULL);
171
172 done:
173 DBG(HOOK, ul_debugobj(hs, " config done [rc=%d]", rc));
174 return rc != 0 && errno ? -errno : rc;
175 }
176
177 static int open_fs_configuration_context(struct libmnt_context *cxt,
178 struct libmnt_sysapi *api,
179 const char *type)
180 {
181 DBG(HOOK, ul_debug(" new FS '%s'", type));
182
183 if (!type)
184 return -EINVAL;
185
186 DBG(HOOK, ul_debug(" fsopen(%s)", type));
187
188 api->fd_fs = fsopen(type, FSOPEN_CLOEXEC);
189 set_syscall_status(cxt, "fsopen", api->fd_fs >= 0);
190 if (api->fd_fs < 0)
191 return -errno;
192 api->is_new_fs = 1;
193 return api->fd_fs;
194 }
195
196 static int open_mount_tree(struct libmnt_context *cxt, const char *path, unsigned long mflg)
197 {
198 unsigned long oflg = OPEN_TREE_CLOEXEC;
199 int rc = 0, fd = -1;
200
201 if (mflg == (unsigned long) -1) {
202 rc = mnt_optlist_get_flags(cxt->optlist, &mflg, cxt->map_linux, 0);
203 if (rc)
204 return rc;
205 }
206 if (!path) {
207 path = mnt_fs_get_target(cxt->fs);
208 if (!path)
209 return -EINVAL;
210 }
211
212 /* Classic -oremount,bind,ro is not bind operation, it's just
213 * VFS flags update only */
214 if ((mflg & MS_BIND) && !(mflg & MS_REMOUNT)) {
215 oflg |= OPEN_TREE_CLONE;
216
217 if (mnt_optlist_is_rbind(cxt->optlist))
218 oflg |= AT_RECURSIVE;
219 }
220
221 if (cxt->force_clone)
222 oflg |= OPEN_TREE_CLONE;
223
224 DBG(HOOK, ul_debug("open_tree(path=%s%s%s)", path,
225 oflg & OPEN_TREE_CLONE ? " clone" : "",
226 oflg & AT_RECURSIVE ? " recursive" : ""));
227 fd = open_tree(AT_FDCWD, path, oflg);
228 set_syscall_status(cxt, "open_tree", fd >= 0);
229
230 return fd;
231 }
232
233 static int hook_create_mount(struct libmnt_context *cxt,
234 const struct libmnt_hookset *hs,
235 void *data __attribute__((__unused__)))
236 {
237 struct libmnt_sysapi *api;
238 const char *src;
239 int rc = 0;
240
241 assert(cxt);
242 assert(cxt->fs);
243
244 api = get_sysapi(cxt);
245 assert(api);
246
247 if (api->fd_fs < 0) {
248 const char *type = mnt_fs_get_fstype(cxt->fs);
249
250 rc = open_fs_configuration_context(cxt, api, type);
251 if (rc < 0) {
252 rc = api->fd_fs;
253 goto done;
254 }
255 }
256
257 src = mnt_fs_get_srcpath(cxt->fs);
258 if (!src)
259 return -EINVAL;
260
261 DBG(HOOK, ul_debugobj(hs, "init FS"));
262
263 rc = fsconfig(api->fd_fs, FSCONFIG_SET_STRING, "source", src, 0);
264 set_syscall_status(cxt, "fsconfig", rc == 0);
265
266 if (!rc)
267 rc = configure_superblock(cxt, hs, api->fd_fs, 0);
268 if (!rc) {
269 DBG(HOOK, ul_debugobj(hs, "create FS"));
270 rc = fsconfig(api->fd_fs, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
271 set_syscall_status(cxt, "fsconfig", rc == 0);
272 }
273
274 if (!rc) {
275 api->fd_tree = fsmount(api->fd_fs, FSMOUNT_CLOEXEC, 0);
276 set_syscall_status(cxt, "fsmount", api->fd_tree >= 0);
277 if (api->fd_tree < 0)
278 rc = -errno;
279 }
280
281 if (rc)
282 /* cleanup after fail (libmount may only try the FS type) */
283 close_sysapi_fds(api);
284
285 if (!rc && cxt->fs) {
286 struct statx st;
287
288 rc = statx(api->fd_tree, "", AT_EMPTY_PATH, STATX_MNT_ID, &st);
289 cxt->fs->id = (int) st.stx_mnt_id;
290
291 if (cxt->update) {
292 struct libmnt_fs *fs = mnt_update_get_fs(cxt->update);
293 if (fs)
294 fs->id = cxt->fs->id;
295 }
296 }
297
298 done:
299 DBG(HOOK, ul_debugobj(hs, "create FS done [rc=%d, id=%d]", rc, cxt->fs ? cxt->fs->id : -1));
300 return rc;
301 }
302
303 static int hook_reconfigure_mount(struct libmnt_context *cxt,
304 const struct libmnt_hookset *hs,
305 void *data __attribute__((__unused__)))
306 {
307 struct libmnt_sysapi *api;
308 int rc = 0;
309
310 assert(cxt);
311
312 api = get_sysapi(cxt);
313 assert(api);
314 assert(api->fd_tree >= 0);
315
316 if (api->fd_fs < 0) {
317 api->fd_fs = fspick(api->fd_tree, "", FSPICK_EMPTY_PATH |
318 FSPICK_NO_AUTOMOUNT);
319 set_syscall_status(cxt, "fspick", api->fd_fs >= 0);
320 if (api->fd_fs < 0)
321 return -errno;
322 }
323
324 rc = configure_superblock(cxt, hs, api->fd_fs, 1);
325 if (!rc) {
326 DBG(HOOK, ul_debugobj(hs, "re-configurate FS"));
327 rc = fsconfig(api->fd_fs, FSCONFIG_CMD_RECONFIGURE, NULL, NULL, 0);
328 set_syscall_status(cxt, "fsconfig", rc == 0);
329 }
330
331 DBG(HOOK, ul_debugobj(hs, "reconf FS done [rc=%d]", rc));
332 return rc;
333 }
334
335 static int set_vfsflags(struct libmnt_context *cxt,
336 const struct libmnt_hookset *hs,
337 uint64_t set, uint64_t clr, int recursive)
338 {
339 struct libmnt_sysapi *api;
340 struct mount_attr attr = { .attr_clr = 0 };
341 unsigned int callflags = AT_EMPTY_PATH;
342 int rc;
343
344 api = get_sysapi(cxt);
345 assert(api);
346
347 /* fallback only; necessary when init_sysapi() during preparation
348 * cannot open the tree -- for example when we call /sbin/mount.<type> */
349 if (api->fd_tree < 0 && mnt_fs_get_target(cxt->fs)) {
350 rc = api->fd_tree = open_mount_tree(cxt, NULL, (unsigned long) -1);
351 if (rc < 0)
352 return rc;
353 rc = 0;
354 }
355
356 if (recursive)
357 callflags |= AT_RECURSIVE;
358
359 DBG(HOOK, ul_debugobj(hs,
360 "mount_setattr(set=0x%08" PRIx64" clr=0x%08" PRIx64")", set, clr));
361 attr.attr_set = set;
362 attr.attr_clr = clr;
363
364 errno = 0;
365 rc = mount_setattr(api->fd_tree, "", callflags, &attr, sizeof(attr));
366 set_syscall_status(cxt, "mount_setattr", rc == 0);
367
368 if (rc && errno == EINVAL)
369 return -MNT_ERR_APPLYFLAGS;
370
371 return rc == 0 ? 0 : -errno;
372 }
373
374 static int hook_set_vfsflags(struct libmnt_context *cxt,
375 const struct libmnt_hookset *hs,
376 void *data __attribute__((__unused__)))
377 {
378 struct libmnt_optlist *ol;
379 uint64_t set = 0, clr = 0;
380 int rc = 0;
381
382 DBG(HOOK, ul_debugobj(hs, "setting VFS flags"));
383
384 ol = mnt_context_get_optlist(cxt);
385 if (!ol)
386 return -ENOMEM;
387
388 /* normal flags */
389 rc = mnt_optlist_get_attrs(ol, &set, &clr, MNT_OL_NOREC);
390 if (!rc && (set || clr))
391 rc = set_vfsflags(cxt, hs, set, clr, 0);
392
393 /* recursive flags */
394 set = clr = 0;
395 if (!rc)
396 rc = mnt_optlist_get_attrs(ol, &set, &clr, MNT_OL_REC);
397 if (!rc && (set || clr))
398 rc = set_vfsflags(cxt, hs, set, clr, 1);
399
400 return rc;
401 }
402
403 static int hook_set_propagation(struct libmnt_context *cxt,
404 const struct libmnt_hookset *hs,
405 void *data __attribute__((__unused__)))
406 {
407 struct libmnt_sysapi *api;
408 struct libmnt_optlist *ol;
409 struct libmnt_iter itr;
410 struct libmnt_opt *opt;
411 int rc = 0;
412
413 DBG(HOOK, ul_debugobj(hs, "setting propagation"));
414
415 ol = mnt_context_get_optlist(cxt);
416 if (!ol)
417 return -ENOMEM;
418
419 api = get_sysapi(cxt);
420 assert(api);
421
422 /* fallback only; necessary when init_sysapi() during preparation
423 * cannot open the tree -- for example when we call /sbin/mount.<type> */
424 if (api->fd_tree < 0 && mnt_fs_get_target(cxt->fs)) {
425 rc = api->fd_tree = open_mount_tree(cxt, NULL, (unsigned long) -1);
426 if (rc < 0)
427 goto done;
428 rc = 0;
429 }
430
431 mnt_reset_iter(&itr, MNT_ITER_FORWARD);
432
433 while (mnt_optlist_next_opt(ol, &itr, &opt) == 0) {
434 const struct libmnt_optmap *map = mnt_opt_get_map(opt);
435 const struct libmnt_optmap *ent = mnt_opt_get_mapent(opt);
436 struct mount_attr attr = { .attr_clr = 0 };
437 unsigned int flgs = AT_EMPTY_PATH;
438
439 if (cxt->map_linux != map)
440 continue;
441 if (mnt_opt_is_external(opt))
442 continue;
443 if (!ent || !ent->id || !(ent->id & MS_PROPAGATION))
444 continue;
445
446 attr.propagation = ent->id & MS_PROPAGATION;
447 if (ent->id & MS_REC)
448 flgs |= AT_RECURSIVE;
449
450 DBG(HOOK, ul_debugobj(hs,
451 "mount_setattr(propagation=0x%08" PRIx64")",
452 (uint64_t) attr.propagation));
453
454 rc = mount_setattr(api->fd_tree, "", flgs, &attr, sizeof(attr));
455 set_syscall_status(cxt, "move_setattr", rc == 0);
456
457 if (rc && errno == EINVAL)
458 return -MNT_ERR_APPLYFLAGS;
459 if (rc != 0)
460 break;
461 }
462 done:
463 return rc == 0 ? 0 : -errno;
464 }
465
466 static int hook_attach_target(struct libmnt_context *cxt,
467 const struct libmnt_hookset *hs,
468 void *data __attribute__((__unused__)))
469 {
470 struct libmnt_sysapi *api;
471 const char *target;
472 int rc = 0;
473
474 target = mnt_fs_get_target(cxt->fs);
475 if (!target)
476 return -EINVAL;
477
478 api = get_sysapi(cxt);
479 assert(api);
480 assert(api->fd_tree >= 0);
481
482 DBG(HOOK, ul_debugobj(hs, "move_mount(to=%s)", target));
483
484 /* umount old target if we created a clone */
485 if (cxt->force_clone
486 && !api->is_new_fs
487 && !mnt_optlist_is_bind(cxt->optlist)) {
488
489 DBG(HOOK, ul_debugobj(hs, "remove expired target"));
490 umount2(target, MNT_DETACH);
491 }
492
493 rc = move_mount(api->fd_tree, "", AT_FDCWD, target, MOVE_MOUNT_F_EMPTY_PATH);
494 set_syscall_status(cxt, "move_mount", rc == 0);
495
496 return rc == 0 ? 0 : -errno;
497 }
498
499 static inline int fsopen_is_supported(void)
500 {
501 int dummy, rc = 1;
502
503 errno = 0;
504 dummy = fsopen(NULL, FSOPEN_CLOEXEC);
505
506 if (errno == ENOSYS)
507 rc = 0;
508 if (dummy >= 0)
509 close(dummy);
510 return rc;
511 }
512
513 /*
514 * open_tree() and fsopen()
515 */
516 static int init_sysapi(struct libmnt_context *cxt,
517 const struct libmnt_hookset *hs,
518 unsigned long flags)
519 {
520 struct libmnt_sysapi *api;
521 const char *path = NULL;
522
523 assert(cxt);
524 assert(hs);
525
526 DBG(HOOK, ul_debugobj(hs, "initialize API fds"));
527
528 /* A) tree based operation -- the tree is mount point */
529 if ((flags & MS_REMOUNT)
530 || mnt_context_propagation_only(cxt)) {
531 DBG(HOOK, ul_debugobj(hs, " REMOUNT/propagation"));
532 path = mnt_fs_get_target(cxt->fs);
533 if (!path)
534 return -EINVAL;
535
536 /* B) tree based operation -- the tree is mount source */
537 } else if ((flags & MS_BIND)
538 || (flags & MS_MOVE)) {
539 DBG(HOOK, ul_debugobj(hs, " BIND/MOVE"));
540 path = mnt_fs_get_srcpath(cxt->fs);
541 if (!path)
542 return -EINVAL;
543 }
544
545 api = new_hookset_data(cxt, hs);
546 if (!api)
547 return -ENOMEM;
548
549 if (mnt_context_is_fake(cxt))
550 goto fake;
551
552 if (path) {
553 api->fd_tree = open_mount_tree(cxt, path, flags);
554 if (api->fd_tree < 0)
555 goto fail;
556
557 /* C) FS based operation
558 *
559 * Note, fstype is optinal and may be specified later if mount by
560 * list of FS types (mount -t foo,bar,ext4). In this case fsopen()
561 * is called later in hook_create_mount(). */
562 } else {
563 const char *type = mnt_fs_get_fstype(cxt->fs);
564 int rc = 0;
565
566 /* fsopen() to create a superblock */
567 if (cxt->helper == NULL && type && !strchr(type, ','))
568 rc = open_fs_configuration_context(cxt, api, type);
569
570 /* dummy fsopen() to test if API is available */
571 else if (!fsopen_is_supported()) {
572 errno = ENOSYS;
573 rc = -errno;
574 set_syscall_status(cxt, "fsopen", rc == 0);
575 }
576 if (rc < 0)
577 goto fail;
578 }
579
580 return 0;
581 fail:
582 DBG(HOOK, ul_debugobj(hs, "init fs/tree failed [errno=%d %m]", errno));
583 return -errno;
584 fake:
585 DBG(CXT, ul_debugobj(cxt, " FAKE (-f)"));
586 cxt->syscall_status = 0;
587 return 0;
588 }
589
590 /*
591 * Analyze library context and register hook to call mount-like syscalls.
592 *
593 * Note that this function interprets classic MS_* flags by new Linux mount FD
594 * based API.
595 *
596 * Returns: 0 on success, <0 on error, >0 on recover-able error
597 */
598 static int hook_prepare(struct libmnt_context *cxt,
599 const struct libmnt_hookset *hs,
600 void *data __attribute__((__unused__)))
601 {
602 struct libmnt_optlist *ol;
603 unsigned long flags = 0;
604 uint64_t set = 0, clr = 0;
605 int rc = 0;
606
607 assert(cxt);
608 assert(hs == &hookset_mount);
609
610 /*
611 * The current kernel btrfs driver does not completely implement
612 * fsconfig() as it does not work with selinux stuff.
613 *
614 * Don't use the new mount API in this situation. Let's hope this issue
615 * is temporary.
616 */
617 {
618 const char *type = mnt_fs_get_fstype(cxt->fs);
619
620 if (type && strcmp(type, "btrfs") == 0 && cxt->has_selinux_opt) {
621 DBG(HOOK, ul_debugobj(hs, "don't use new API (btrfs issue)"));
622 return 0;
623 }
624 }
625
626 DBG(HOOK, ul_debugobj(hs, "prepare mount"));
627
628 ol = mnt_context_get_optlist(cxt);
629 if (!ol)
630 return -ENOMEM;
631
632 /* classic MS_* flags (include oprations like MS_REMOUNT, etc) */
633 rc = mnt_optlist_get_flags(ol, &flags, cxt->map_linux, 0);
634
635 /* MOUNT_ATTR_* flags for mount_setattr() */
636 if (!rc)
637 rc = mnt_optlist_get_attrs(ol, &set, &clr, 0);
638
639 /* open_tree() or fsopen() */
640 if (!rc) {
641 rc = init_sysapi(cxt, hs, flags);
642 if (rc && cxt->syscall_status == -ENOSYS) {
643 /* we need to recover from this error, so hook_mount_legacy.c
644 * can try to continue */
645 reset_syscall_status(cxt);
646 free_hookset_data(cxt, hs);
647 return 1;
648 }
649 }
650
651 /* check mutually exclusive operations */
652 if (!rc && (flags & MS_BIND) && (flags & MS_MOVE))
653 return -EINVAL;
654 if (!rc && (flags & MS_MOVE) && (flags & MS_REMOUNT))
655 return -EINVAL;
656
657 /* classic remount (note -oremount,bind,ro is not superblock reconfiguration) */
658 if (!rc
659 && cxt->helper == NULL
660 && (flags & MS_REMOUNT)
661 && !(flags & MS_BIND))
662 rc = mnt_context_append_hook(cxt, hs, MNT_STAGE_MOUNT, NULL,
663 hook_reconfigure_mount);
664
665 /* create a new FS instance */
666 else if (!rc
667 && cxt->helper == NULL
668 && !(flags & MS_BIND)
669 && !(flags & MS_MOVE)
670 && !(flags & MS_REMOUNT)
671 && !mnt_context_propagation_only(cxt))
672 rc = mnt_context_append_hook(cxt, hs, MNT_STAGE_MOUNT, NULL,
673 hook_create_mount);
674
675 /* call mount_setattr() */
676 if (!rc
677 && cxt->helper == NULL
678 && (set != 0 || clr != 0 || (flags & MS_REMOUNT)))
679 rc = mnt_context_append_hook(cxt, hs, MNT_STAGE_MOUNT, NULL,
680 hook_set_vfsflags);
681
682 /* call move_mount() to attach target */
683 if (!rc
684 && cxt->helper == NULL
685 && (cxt->force_clone ||
686 (!(flags & MS_REMOUNT) && !mnt_context_propagation_only(cxt))))
687 rc = mnt_context_append_hook(cxt, hs, MNT_STAGE_MOUNT_POST, NULL,
688 hook_attach_target);
689
690 /* set propagation (has to be attached to VFS) */
691 if (!rc && mnt_optlist_get_propagation(ol))
692 rc = mnt_context_append_hook(cxt, hs, MNT_STAGE_MOUNT_POST, NULL,
693 hook_set_propagation);
694
695 DBG(HOOK, ul_debugobj(hs, "prepare mount done [rc=%d]", rc));
696 return rc;
697 }
698
699 const struct libmnt_hookset hookset_mount =
700 {
701 .name = "__mount",
702
703 .firststage = MNT_STAGE_PREP,
704 .firstcall = hook_prepare,
705
706 .deinit = hookset_deinit
707 };
708 #endif /* USE_LIBMOUNT_MOUNTFD_SUPPORT */