1 /*
2 * Copyright (c) 2019 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 /*-
25 * Portions Copyright (c) 1992, 1993, 1995
26 * The Regents of the University of California. All rights reserved.
27 *
28 * This code is derived from software donated to Berkeley by
29 * Jan-Simon Pendry.
30 *
31 * Redistribution and use in source and binary forms, with or without
32 * modification, are permitted provided that the following conditions
33 * are met:
34 * 1. Redistributions of source code must retain the above copyright
35 * notice, this list of conditions and the following disclaimer.
36 * 2. Redistributions in binary form must reproduce the above copyright
37 * notice, this list of conditions and the following disclaimer in the
38 * documentation and/or other materials provided with the distribution.
39 * 4. Neither the name of the University nor the names of its contributors
40 * may be used to endorse or promote products derived from this software
41 * without specific prior written permission.
42 *
43 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
44 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
45 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
46 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
47 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
48 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
49 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
50 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
51 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
52 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
53 * SUCH DAMAGE.
54 *
55 * @(#)null_vfsops.c 8.2 (Berkeley) 1/21/94
56 *
57 * @(#)lofs_vfsops.c 1.2 (Berkeley) 6/18/92
58 * $FreeBSD$
59 */
60
61 #include <sys/param.h>
62 #include <sys/systm.h>
63 #include <sys/fcntl.h>
64 #include <sys/kernel.h>
65 #include <sys/lock.h>
66 #include <sys/malloc.h>
67 #include <sys/mount.h>
68 #include <sys/namei.h>
69 #include <sys/proc.h>
70 #include <sys/vnode.h>
71 #include <sys/vnode_internal.h>
72 #include <security/mac_internal.h>
73 #include <sys/kauth.h>
74
75 #include <sys/param.h>
76
77 #include <IOKit/IOBSD.h>
78
79 #include "nullfs.h"
80
81 #define NULLFS_ENTITLEMENT "com.apple.private.nullfs_allow"
82
83 static int
nullfs_vfs_getlowerattr(mount_t mp,struct vfs_attr * vfap,vfs_context_t ctx)84 nullfs_vfs_getlowerattr(mount_t mp, struct vfs_attr * vfap, vfs_context_t ctx)
85 {
86 memset(vfap, 0, sizeof(*vfap));
87 VFSATTR_INIT(vfap);
88 VFSATTR_WANTED(vfap, f_bsize);
89 VFSATTR_WANTED(vfap, f_iosize);
90 VFSATTR_WANTED(vfap, f_blocks);
91 VFSATTR_WANTED(vfap, f_bfree);
92 VFSATTR_WANTED(vfap, f_bavail);
93 VFSATTR_WANTED(vfap, f_bused);
94 VFSATTR_WANTED(vfap, f_files);
95 VFSATTR_WANTED(vfap, f_ffree);
96 VFSATTR_WANTED(vfap, f_capabilities);
97
98 return vfs_getattr(mp, vfap, ctx);
99 }
100
101 /*
102 * Mount null layer
103 */
104 static int
nullfs_mount(struct mount * mp,__unused vnode_t devvp,user_addr_t user_data,vfs_context_t ctx)105 nullfs_mount(struct mount * mp, __unused vnode_t devvp, user_addr_t user_data, vfs_context_t ctx)
106 {
107 int error = 0;
108 struct vnode *lowerrootvp = NULL, *vp = NULL;
109 struct vfsstatfs * sp = NULL;
110 struct null_mount * xmp = NULL;
111 struct null_mount_conf conf = {0};
112 char path[MAXPATHLEN];
113
114 size_t count;
115 struct vfs_attr vfa;
116 /* set defaults (arbitrary since this file system is readonly) */
117 uint32_t bsize = BLKDEV_IOSIZE;
118 size_t iosize = BLKDEV_IOSIZE;
119 uint64_t blocks = 4711 * 4711;
120 uint64_t bfree = 0;
121 uint64_t bavail = 0;
122 uint64_t bused = 4711;
123 uint64_t files = 4711;
124 uint64_t ffree = 0;
125
126 kauth_cred_t cred = vfs_context_ucred(ctx);
127
128 NULLFSDEBUG("nullfs_mount(mp = %p) %llx\n", (void *)mp, vfs_flags(mp));
129
130 if (vfs_flags(mp) & MNT_ROOTFS) {
131 return EOPNOTSUPP;
132 }
133
134 /*
135 * Update is a no-op
136 */
137 if (vfs_isupdate(mp)) {
138 return ENOTSUP;
139 }
140
141 /* check entitlement */
142 if (!IOCurrentTaskHasEntitlement(NULLFS_ENTITLEMENT)) {
143 return EPERM;
144 }
145
146 /*
147 * Get configuration
148 */
149 error = copyin(user_data, &conf, sizeof(conf));
150 if (error) {
151 NULLFSDEBUG("nullfs: error copying configuration form user %d\n", error);
152 goto error;
153 }
154
155 /*
156 * Get argument
157 */
158 error = copyinstr(user_data + sizeof(conf), path, MAXPATHLEN - 1, &count);
159 if (error) {
160 NULLFSDEBUG("nullfs: error copying data form user %d\n", error);
161 goto error;
162 }
163
164 /* This could happen if the system is configured for 32 bit inodes instead of
165 * 64 bit */
166 if (count > sizeof(vfs_statfs(mp)->f_mntfromname)) {
167 error = EINVAL;
168 NULLFSDEBUG("nullfs: path to translocate too large for this system %ld vs %ld\n", count, sizeof(vfs_statfs(mp)->f_mntfromname));
169 goto error;
170 }
171
172 error = vnode_lookup(path, 0, &lowerrootvp, ctx);
173 if (error) {
174 NULLFSDEBUG("lookup %s -> %d\n", path, error);
175 goto error;
176 }
177
178 /* lowervrootvp has an iocount after vnode_lookup, drop that for a usecount.
179 * Keep this to signal what we want to keep around the thing we are mirroring.
180 * Drop it in unmount.*/
181 error = vnode_ref(lowerrootvp);
182 vnode_put(lowerrootvp);
183 if (error) {
184 // If vnode_ref failed, then null it out so it can't be used anymore in cleanup.
185 lowerrootvp = NULL;
186 goto error;
187 }
188
189 NULLFSDEBUG("mount %s\n", path);
190
191 xmp = kalloc_type(struct null_mount, Z_WAITOK | Z_ZERO | Z_NOFAIL);
192
193 /*
194 * Grab the uid/gid of the caller, which may be used for unveil later
195 */
196 xmp->uid = kauth_cred_getuid(cred);
197 xmp->gid = kauth_cred_getgid(cred);
198
199 /*
200 * Save reference to underlying FS
201 */
202 xmp->nullm_lowerrootvp = lowerrootvp;
203 xmp->nullm_lowerrootvid = vnode_vid(lowerrootvp);
204
205 error = null_getnewvnode(mp, NULL, NULL, &vp, NULL, 1);
206 if (error) {
207 goto error;
208 }
209
210 /* vp has an iocount on it from vnode_create. drop that for a usecount. This
211 * is our root vnode so we drop the ref in unmount
212 *
213 * Assuming for now that because we created this vnode and we aren't finished mounting we can get a ref*/
214 vnode_ref(vp);
215 vnode_put(vp);
216
217 nullfs_init_lck(&xmp->nullm_lock);
218
219 xmp->nullm_rootvp = vp;
220
221 /* read the flags the user set, but then ignore some of them, we will only
222 * allow them if they are set on the lower file system */
223 uint64_t flags = vfs_flags(mp) & (~(MNT_IGNORE_OWNERSHIP | MNT_LOCAL));
224 uint64_t lowerflags = vfs_flags(vnode_mount(lowerrootvp)) & (MNT_LOCAL | MNT_QUARANTINE | MNT_IGNORE_OWNERSHIP | MNT_NOEXEC);
225
226 if (lowerflags) {
227 flags |= lowerflags;
228 }
229
230 /* force these flags */
231 flags |= (MNT_DONTBROWSE | MNT_MULTILABEL | MNT_NOSUID | MNT_RDONLY);
232 vfs_setflags(mp, flags);
233
234 vfs_setfsprivate(mp, xmp);
235 vfs_getnewfsid(mp);
236 vfs_setlocklocal(mp);
237
238 /* fill in the stat block */
239 sp = vfs_statfs(mp);
240 strlcpy(sp->f_mntfromname, path, sizeof(sp->f_mntfromname));
241
242 sp->f_flags = flags;
243
244 xmp->nullm_flags = NULLM_CASEINSENSITIVE; /* default to case insensitive */
245
246 // Set the flags that are requested
247 xmp->nullm_flags |= conf.flags & NULLM_UNVEIL;
248
249 error = nullfs_vfs_getlowerattr(vnode_mount(lowerrootvp), &vfa, ctx);
250 if (error == 0) {
251 if (VFSATTR_IS_SUPPORTED(&vfa, f_bsize)) {
252 bsize = vfa.f_bsize;
253 }
254 if (VFSATTR_IS_SUPPORTED(&vfa, f_iosize)) {
255 iosize = vfa.f_iosize;
256 }
257 if (VFSATTR_IS_SUPPORTED(&vfa, f_blocks)) {
258 blocks = vfa.f_blocks;
259 }
260 if (VFSATTR_IS_SUPPORTED(&vfa, f_bfree)) {
261 bfree = vfa.f_bfree;
262 }
263 if (VFSATTR_IS_SUPPORTED(&vfa, f_bavail)) {
264 bavail = vfa.f_bavail;
265 }
266 if (VFSATTR_IS_SUPPORTED(&vfa, f_bused)) {
267 bused = vfa.f_bused;
268 }
269 if (VFSATTR_IS_SUPPORTED(&vfa, f_files)) {
270 files = vfa.f_files;
271 }
272 if (VFSATTR_IS_SUPPORTED(&vfa, f_ffree)) {
273 ffree = vfa.f_ffree;
274 }
275 if (VFSATTR_IS_SUPPORTED(&vfa, f_capabilities)) {
276 if ((vfa.f_capabilities.capabilities[VOL_CAPABILITIES_FORMAT] & (VOL_CAP_FMT_CASE_SENSITIVE)) &&
277 (vfa.f_capabilities.valid[VOL_CAPABILITIES_FORMAT] & (VOL_CAP_FMT_CASE_SENSITIVE))) {
278 xmp->nullm_flags &= ~NULLM_CASEINSENSITIVE;
279 }
280 }
281 } else {
282 goto error;
283 }
284
285 sp->f_bsize = bsize;
286 sp->f_iosize = iosize;
287 sp->f_blocks = blocks;
288 sp->f_bfree = bfree;
289 sp->f_bavail = bavail;
290 sp->f_bused = bused;
291 sp->f_files = files;
292 sp->f_ffree = ffree;
293
294 /* Associate the mac label information from the mirrored filesystem with the
295 * mirror */
296 MAC_PERFORM(mount_label_associate, cred, vnode_mount(lowerrootvp), vfs_mntlabel(mp));
297
298 NULLFSDEBUG("nullfs_mount: lower %s, alias at %s\n", sp->f_mntfromname, sp->f_mntonname);
299 return 0;
300
301 error:
302 if (xmp) {
303 kfree_type(struct null_mount, xmp);
304 }
305 if (lowerrootvp) {
306 vnode_getwithref(lowerrootvp);
307 vnode_rele(lowerrootvp);
308 vnode_put(lowerrootvp);
309 }
310 if (vp) {
311 /* we made the root vnode but the mount is failed, so clean it up */
312 vnode_getwithref(vp);
313 vnode_rele(vp);
314 /* give vp back */
315 vnode_recycle(vp);
316 vnode_put(vp);
317 }
318 return error;
319 }
320
321 /*
322 * Free reference to null layer
323 */
324 static int
nullfs_unmount(struct mount * mp,int mntflags,__unused vfs_context_t ctx)325 nullfs_unmount(struct mount * mp, int mntflags, __unused vfs_context_t ctx)
326 {
327 struct null_mount * mntdata;
328 struct vnode * vp;
329 int error, flags;
330
331 NULLFSDEBUG("nullfs_unmount: mp = %p\n", (void *)mp);
332
333 /* check entitlement or superuser*/
334 if (!IOCurrentTaskHasEntitlement(NULLFS_ENTITLEMENT) &&
335 vfs_context_suser(ctx) != 0) {
336 return EPERM;
337 }
338
339 if (mntflags & MNT_FORCE) {
340 flags = FORCECLOSE;
341 } else {
342 flags = 0;
343 }
344
345 mntdata = MOUNTTONULLMOUNT(mp);
346 vp = mntdata->nullm_rootvp;
347
348 // release our reference on the root before flushing.
349 // it will get pulled out of the mount structure by reclaim
350 vnode_getalways(vp);
351
352 error = vflush(mp, vp, flags);
353 if (error) {
354 vnode_put(vp);
355 return error;
356 }
357
358 if (vnode_isinuse(vp, 1) && flags == 0) {
359 vnode_put(vp);
360 return EBUSY;
361 }
362
363 vnode_rele(vp); // Drop reference taken by nullfs_mount
364 vnode_put(vp); // Drop ref taken above
365
366 //Force close to get rid of the last vnode
367 (void)vflush(mp, NULL, FORCECLOSE);
368
369 /* no more vnodes, so tear down the mountpoint */
370
371 lck_mtx_lock(&mntdata->nullm_lock);
372
373 vfs_setfsprivate(mp, NULL);
374
375 vnode_getalways(mntdata->nullm_lowerrootvp);
376 vnode_rele(mntdata->nullm_lowerrootvp);
377 vnode_put(mntdata->nullm_lowerrootvp);
378
379 lck_mtx_unlock(&mntdata->nullm_lock);
380
381 nullfs_destroy_lck(&mntdata->nullm_lock);
382
383 kfree_type(struct null_mount, mntdata);
384
385 uint64_t vflags = vfs_flags(mp);
386 vfs_setflags(mp, vflags & ~MNT_LOCAL);
387
388 return 0;
389 }
390
391 static int
nullfs_root(struct mount * mp,struct vnode ** vpp,__unused vfs_context_t ctx)392 nullfs_root(struct mount * mp, struct vnode ** vpp, __unused vfs_context_t ctx)
393 {
394 struct vnode * vp;
395 int error;
396
397 NULLFSDEBUG("nullfs_root(mp = %p, vp = %p)\n", (void *)mp, (void *)MOUNTTONULLMOUNT(mp)->nullm_rootvp);
398
399 /*
400 * Return locked reference to root.
401 */
402 vp = MOUNTTONULLMOUNT(mp)->nullm_rootvp;
403
404 error = vnode_get(vp);
405 if (error) {
406 return error;
407 }
408
409 *vpp = vp;
410 return 0;
411 }
412
413 static int
nullfs_vfs_getattr(struct mount * mp,struct vfs_attr * vfap,vfs_context_t ctx)414 nullfs_vfs_getattr(struct mount * mp, struct vfs_attr * vfap, vfs_context_t ctx)
415 {
416 struct vnode * coveredvp = NULL;
417 struct vfs_attr vfa;
418 struct null_mount * null_mp = MOUNTTONULLMOUNT(mp);
419 vol_capabilities_attr_t capabilities;
420 struct vfsstatfs * sp = vfs_statfs(mp);
421 vfs_context_t ectx = nullfs_get_patched_context(null_mp, ctx);
422
423 struct timespec tzero = {.tv_sec = 0, .tv_nsec = 0};
424
425 NULLFSDEBUG("%s\n", __FUNCTION__);
426
427 /* Set default capabilities in case the lower file system is gone */
428 memset(&capabilities, 0, sizeof(capabilities));
429 capabilities.capabilities[VOL_CAPABILITIES_FORMAT] = VOL_CAP_FMT_FAST_STATFS | VOL_CAP_FMT_HIDDEN_FILES;
430 capabilities.valid[VOL_CAPABILITIES_FORMAT] = VOL_CAP_FMT_FAST_STATFS | VOL_CAP_FMT_HIDDEN_FILES;
431
432 if (nullfs_vfs_getlowerattr(vnode_mount(null_mp->nullm_lowerrootvp), &vfa, ectx) == 0) {
433 if (VFSATTR_IS_SUPPORTED(&vfa, f_capabilities)) {
434 memcpy(&capabilities, &vfa.f_capabilities, sizeof(capabilities));
435 /* don't support vget */
436 capabilities.capabilities[VOL_CAPABILITIES_FORMAT] &= ~(VOL_CAP_FMT_PERSISTENTOBJECTIDS | VOL_CAP_FMT_PATH_FROM_ID);
437
438 capabilities.capabilities[VOL_CAPABILITIES_FORMAT] |= VOL_CAP_FMT_HIDDEN_FILES; /* Always support UF_HIDDEN */
439
440 capabilities.valid[VOL_CAPABILITIES_FORMAT] &= ~(VOL_CAP_FMT_PERSISTENTOBJECTIDS | VOL_CAP_FMT_PATH_FROM_ID);
441
442 capabilities.valid[VOL_CAPABILITIES_FORMAT] |= VOL_CAP_FMT_HIDDEN_FILES; /* Always support UF_HIDDEN */
443
444 /* dont' support interfaces that only make sense on a writable file system
445 * or one with specific vnops implemented */
446 capabilities.capabilities[VOL_CAPABILITIES_INTERFACES] = 0;
447
448 capabilities.valid[VOL_CAPABILITIES_INTERFACES] &=
449 ~(VOL_CAP_INT_SEARCHFS | VOL_CAP_INT_ATTRLIST | VOL_CAP_INT_READDIRATTR | VOL_CAP_INT_EXCHANGEDATA |
450 VOL_CAP_INT_COPYFILE | VOL_CAP_INT_ALLOCATE | VOL_CAP_INT_VOL_RENAME | VOL_CAP_INT_ADVLOCK | VOL_CAP_INT_FLOCK);
451 }
452 }
453
454 if (VFSATTR_IS_ACTIVE(vfap, f_create_time)) {
455 VFSATTR_RETURN(vfap, f_create_time, tzero);
456 }
457
458 if (VFSATTR_IS_ACTIVE(vfap, f_modify_time)) {
459 VFSATTR_RETURN(vfap, f_modify_time, tzero);
460 }
461
462 if (VFSATTR_IS_ACTIVE(vfap, f_access_time)) {
463 VFSATTR_RETURN(vfap, f_access_time, tzero);
464 }
465
466 if (VFSATTR_IS_ACTIVE(vfap, f_bsize)) {
467 VFSATTR_RETURN(vfap, f_bsize, sp->f_bsize);
468 }
469
470 if (VFSATTR_IS_ACTIVE(vfap, f_iosize)) {
471 VFSATTR_RETURN(vfap, f_iosize, sp->f_iosize);
472 }
473
474 if (VFSATTR_IS_ACTIVE(vfap, f_owner)) {
475 VFSATTR_RETURN(vfap, f_owner, 0);
476 }
477
478 if (VFSATTR_IS_ACTIVE(vfap, f_blocks)) {
479 VFSATTR_RETURN(vfap, f_blocks, sp->f_blocks);
480 }
481
482 if (VFSATTR_IS_ACTIVE(vfap, f_bfree)) {
483 VFSATTR_RETURN(vfap, f_bfree, sp->f_bfree);
484 }
485
486 if (VFSATTR_IS_ACTIVE(vfap, f_bavail)) {
487 VFSATTR_RETURN(vfap, f_bavail, sp->f_bavail);
488 }
489
490 if (VFSATTR_IS_ACTIVE(vfap, f_bused)) {
491 VFSATTR_RETURN(vfap, f_bused, sp->f_bused);
492 }
493
494 if (VFSATTR_IS_ACTIVE(vfap, f_files)) {
495 VFSATTR_RETURN(vfap, f_files, sp->f_files);
496 }
497
498 if (VFSATTR_IS_ACTIVE(vfap, f_ffree)) {
499 VFSATTR_RETURN(vfap, f_ffree, sp->f_ffree);
500 }
501
502 if (VFSATTR_IS_ACTIVE(vfap, f_fssubtype)) {
503 VFSATTR_RETURN(vfap, f_fssubtype, 0);
504 }
505
506 if (VFSATTR_IS_ACTIVE(vfap, f_capabilities)) {
507 memcpy(&vfap->f_capabilities, &capabilities, sizeof(vol_capabilities_attr_t));
508
509 VFSATTR_SET_SUPPORTED(vfap, f_capabilities);
510 }
511
512 if (VFSATTR_IS_ACTIVE(vfap, f_attributes)) {
513 vol_attributes_attr_t * volattr = &vfap->f_attributes;
514
515 volattr->validattr.commonattr = 0;
516 volattr->validattr.volattr = ATTR_VOL_NAME | ATTR_VOL_CAPABILITIES | ATTR_VOL_ATTRIBUTES;
517 volattr->validattr.dirattr = 0;
518 volattr->validattr.fileattr = 0;
519 volattr->validattr.forkattr = 0;
520
521 volattr->nativeattr.commonattr = 0;
522 volattr->nativeattr.volattr = ATTR_VOL_NAME | ATTR_VOL_CAPABILITIES | ATTR_VOL_ATTRIBUTES;
523 volattr->nativeattr.dirattr = 0;
524 volattr->nativeattr.fileattr = 0;
525 volattr->nativeattr.forkattr = 0;
526
527 VFSATTR_SET_SUPPORTED(vfap, f_attributes);
528 }
529
530 if (VFSATTR_IS_ACTIVE(vfap, f_vol_name)) {
531 /* The name of the volume is the same as the directory we mounted on */
532 coveredvp = vfs_vnodecovered(mp);
533 if (coveredvp) {
534 const char * name = vnode_getname_printable(coveredvp);
535 strlcpy(vfap->f_vol_name, name, MAXPATHLEN);
536 vnode_putname_printable(name);
537
538 VFSATTR_SET_SUPPORTED(vfap, f_vol_name);
539 vnode_put(coveredvp);
540 }
541 }
542
543 nullfs_cleanup_patched_context(null_mp, ectx);
544
545 return 0;
546 }
547
548 static int
nullfs_sync(__unused struct mount * mp,__unused int waitfor,__unused vfs_context_t ctx)549 nullfs_sync(__unused struct mount * mp, __unused int waitfor, __unused vfs_context_t ctx)
550 {
551 /*
552 * XXX - Assumes no data cached at null layer.
553 */
554 return 0;
555 }
556
557
558
559 static int
nullfs_vfs_start(__unused struct mount * mp,__unused int flags,__unused vfs_context_t ctx)560 nullfs_vfs_start(__unused struct mount * mp, __unused int flags, __unused vfs_context_t ctx)
561 {
562 NULLFSDEBUG("%s\n", __FUNCTION__);
563 return 0;
564 }
565
566 extern const struct vnodeopv_desc nullfs_vnodeop_opv_desc;
567
568 const struct vnodeopv_desc * nullfs_vnodeopv_descs[] = {
569 &nullfs_vnodeop_opv_desc,
570 };
571
572 struct vfsops nullfs_vfsops = {
573 .vfs_mount = nullfs_mount,
574 .vfs_unmount = nullfs_unmount,
575 .vfs_start = nullfs_vfs_start,
576 .vfs_root = nullfs_root,
577 .vfs_getattr = nullfs_vfs_getattr,
578 .vfs_sync = nullfs_sync,
579 .vfs_init = nullfs_init,
580 .vfs_sysctl = NULL,
581 .vfs_setattr = NULL,
582 };
583