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#define SIZEOF_MEMBER(type, member) (sizeof(((type *)0)->member))
84#define MAX_MNT_FROM_LENGTH (SIZEOF_MEMBER(struct vfsstatfs, f_mntfromname))
85
86static int
87nullfs_vfs_getlowerattr(mount_t mp, struct vfs_attr * vfap, vfs_context_t ctx)
88{
89 memset(s: vfap, c: 0, n: sizeof(*vfap));
90 VFSATTR_INIT(vfap);
91 VFSATTR_WANTED(vfap, f_bsize);
92 VFSATTR_WANTED(vfap, f_iosize);
93 VFSATTR_WANTED(vfap, f_blocks);
94 VFSATTR_WANTED(vfap, f_bfree);
95 VFSATTR_WANTED(vfap, f_bavail);
96 VFSATTR_WANTED(vfap, f_bused);
97 VFSATTR_WANTED(vfap, f_files);
98 VFSATTR_WANTED(vfap, f_ffree);
99 VFSATTR_WANTED(vfap, f_capabilities);
100
101 return vfs_getattr(mp, vfa: vfap, ctx);
102}
103
104/*
105 * Mount null layer
106 */
107static int
108nullfs_mount(struct mount * mp, __unused vnode_t devvp, user_addr_t user_data, vfs_context_t ctx)
109{
110 int error = 0;
111 struct vnode *lowerrootvp = NULL, *vp = NULL;
112 struct vfsstatfs * sp = NULL;
113 struct null_mount * xmp = NULL;
114 struct null_mount_conf conf = {0};
115 char path[MAXPATHLEN];
116
117 size_t count;
118 struct vfs_attr vfa;
119 /* set defaults (arbitrary since this file system is readonly) */
120 uint32_t bsize = BLKDEV_IOSIZE;
121 size_t iosize = BLKDEV_IOSIZE;
122 uint64_t blocks = 4711 * 4711;
123 uint64_t bfree = 0;
124 uint64_t bavail = 0;
125 uint64_t bused = 4711;
126 uint64_t files = 4711;
127 uint64_t ffree = 0;
128
129 kauth_cred_t cred = vfs_context_ucred(ctx);
130
131 NULLFSDEBUG("nullfs_mount(mp = %p) %llx\n", (void *)mp, vfs_flags(mp));
132
133 if (vfs_flags(mp) & MNT_ROOTFS) {
134 return EOPNOTSUPP;
135 }
136
137 /*
138 * Update is a no-op
139 */
140 if (vfs_isupdate(mp)) {
141 return ENOTSUP;
142 }
143
144 /* check entitlement */
145 if (!IOCurrentTaskHasEntitlement(NULLFS_ENTITLEMENT)) {
146 return EPERM;
147 }
148
149 /*
150 * Get configuration
151 */
152 error = copyin(user_data, &conf, sizeof(conf));
153 if (error) {
154 NULLFSDEBUG("nullfs: error copying configuration form user %d\n", error);
155 goto error;
156 }
157
158 /*
159 * Get argument
160 */
161 error = copyinstr(uaddr: user_data + sizeof(conf), kaddr: path, MAXPATHLEN - 1, done: &count);
162 if (error) {
163 NULLFSDEBUG("nullfs: error copying data form user %d\n", error);
164 goto error;
165 }
166
167 /* This could happen if the system is configured for 32 bit inodes instead of
168 * 64 bit */
169 if (count > MAX_MNT_FROM_LENGTH) {
170 error = EINVAL;
171 NULLFSDEBUG("nullfs: path to translocate too large for this system %ld vs %ld\n", count, MAX_MNT_FROM_LENGTH);
172 goto error;
173 }
174
175 error = vnode_lookup(path, flags: 0, vpp: &lowerrootvp, ctx);
176 if (error) {
177 NULLFSDEBUG("lookup %s -> %d\n", path, error);
178 goto error;
179 }
180
181 /* lowervrootvp has an iocount after vnode_lookup, drop that for a usecount.
182 * Keep this to signal what we want to keep around the thing we are mirroring.
183 * Drop it in unmount.*/
184 error = vnode_ref(vp: lowerrootvp);
185 vnode_put(vp: lowerrootvp);
186 if (error) {
187 // If vnode_ref failed, then null it out so it can't be used anymore in cleanup.
188 lowerrootvp = NULL;
189 goto error;
190 }
191
192 NULLFSDEBUG("mount %s\n", path);
193
194 xmp = kalloc_type(struct null_mount, Z_WAITOK | Z_ZERO | Z_NOFAIL);
195
196 /*
197 * Grab the uid/gid of the caller, which may be used for unveil later
198 */
199 xmp->uid = kauth_cred_getuid(cred: cred);
200 xmp->gid = kauth_cred_getgid(cred: cred);
201
202 /*
203 * Save reference to underlying FS
204 */
205 xmp->nullm_lowerrootvp = lowerrootvp;
206 xmp->nullm_lowerrootvid = vnode_vid(vp: lowerrootvp);
207
208 error = null_getnewvnode(mp, NULL, NULL, vpp: &vp, NULL, root: 1);
209 if (error) {
210 goto error;
211 }
212
213 /* vp has an iocount on it from vnode_create. drop that for a usecount. This
214 * is our root vnode so we drop the ref in unmount
215 *
216 * Assuming for now that because we created this vnode and we aren't finished mounting we can get a ref*/
217 vnode_ref(vp);
218 vnode_put(vp);
219
220 nullfs_init_lck(lck: &xmp->nullm_lock);
221
222 xmp->nullm_rootvp = vp;
223
224 /* read the flags the user set, but then ignore some of them, we will only
225 * allow them if they are set on the lower file system */
226 uint64_t flags = vfs_flags(mp) & (~(MNT_IGNORE_OWNERSHIP | MNT_LOCAL));
227 uint64_t lowerflags = vfs_flags(mp: vnode_mount(vp: lowerrootvp)) & (MNT_LOCAL | MNT_QUARANTINE | MNT_IGNORE_OWNERSHIP | MNT_NOEXEC);
228
229 if (lowerflags) {
230 flags |= lowerflags;
231 }
232
233 /* force these flags */
234 flags |= (MNT_DONTBROWSE | MNT_MULTILABEL | MNT_NOSUID | MNT_RDONLY);
235 vfs_setflags(mp, flags);
236
237 vfs_setfsprivate(mp, mntdata: xmp);
238 vfs_getnewfsid(mp);
239 vfs_setlocklocal(mp);
240
241 /* fill in the stat block */
242 sp = vfs_statfs(mp);
243 strlcpy(dst: sp->f_mntfromname, src: path, MAX_MNT_FROM_LENGTH);
244
245 sp->f_flags = flags;
246
247 xmp->nullm_flags = NULLM_CASEINSENSITIVE; /* default to case insensitive */
248
249 // Set the flags that are requested
250 xmp->nullm_flags |= conf.flags & NULLM_UNVEIL;
251
252 error = nullfs_vfs_getlowerattr(mp: vnode_mount(vp: lowerrootvp), vfap: &vfa, ctx);
253 if (error == 0) {
254 if (VFSATTR_IS_SUPPORTED(&vfa, f_bsize)) {
255 bsize = vfa.f_bsize;
256 }
257 if (VFSATTR_IS_SUPPORTED(&vfa, f_iosize)) {
258 iosize = vfa.f_iosize;
259 }
260 if (VFSATTR_IS_SUPPORTED(&vfa, f_blocks)) {
261 blocks = vfa.f_blocks;
262 }
263 if (VFSATTR_IS_SUPPORTED(&vfa, f_bfree)) {
264 bfree = vfa.f_bfree;
265 }
266 if (VFSATTR_IS_SUPPORTED(&vfa, f_bavail)) {
267 bavail = vfa.f_bavail;
268 }
269 if (VFSATTR_IS_SUPPORTED(&vfa, f_bused)) {
270 bused = vfa.f_bused;
271 }
272 if (VFSATTR_IS_SUPPORTED(&vfa, f_files)) {
273 files = vfa.f_files;
274 }
275 if (VFSATTR_IS_SUPPORTED(&vfa, f_ffree)) {
276 ffree = vfa.f_ffree;
277 }
278 if (VFSATTR_IS_SUPPORTED(&vfa, f_capabilities)) {
279 if ((vfa.f_capabilities.capabilities[VOL_CAPABILITIES_FORMAT] & (VOL_CAP_FMT_CASE_SENSITIVE)) &&
280 (vfa.f_capabilities.valid[VOL_CAPABILITIES_FORMAT] & (VOL_CAP_FMT_CASE_SENSITIVE))) {
281 xmp->nullm_flags &= ~NULLM_CASEINSENSITIVE;
282 }
283 }
284 } else {
285 goto error;
286 }
287
288 sp->f_bsize = bsize;
289 sp->f_iosize = iosize;
290 sp->f_blocks = blocks;
291 sp->f_bfree = bfree;
292 sp->f_bavail = bavail;
293 sp->f_bused = bused;
294 sp->f_files = files;
295 sp->f_ffree = ffree;
296
297 /* Associate the mac label information from the mirrored filesystem with the
298 * mirror */
299 MAC_PERFORM(mount_label_associate, cred, vnode_mount(lowerrootvp), vfs_mntlabel(mp));
300
301 NULLFSDEBUG("nullfs_mount: lower %s, alias at %s\n", sp->f_mntfromname, sp->f_mntonname);
302 return 0;
303
304error:
305 if (xmp) {
306 kfree_type(struct null_mount, xmp);
307 }
308 if (lowerrootvp) {
309 vnode_getwithref(vp: lowerrootvp);
310 vnode_rele(vp: lowerrootvp);
311 vnode_put(vp: lowerrootvp);
312 }
313 if (vp) {
314 /* we made the root vnode but the mount is failed, so clean it up */
315 vnode_getwithref(vp);
316 vnode_rele(vp);
317 /* give vp back */
318 vnode_recycle(vp);
319 vnode_put(vp);
320 }
321 return error;
322}
323
324/*
325 * Free reference to null layer
326 */
327static int
328nullfs_unmount(struct mount * mp, int mntflags, __unused vfs_context_t ctx)
329{
330 struct null_mount * mntdata;
331 struct vnode * vp;
332 int error, flags;
333
334 NULLFSDEBUG("nullfs_unmount: mp = %p\n", (void *)mp);
335
336 /* check entitlement or superuser*/
337 if (!IOCurrentTaskHasEntitlement(NULLFS_ENTITLEMENT) &&
338 vfs_context_suser(ctx) != 0) {
339 return EPERM;
340 }
341
342 if (mntflags & MNT_FORCE) {
343 flags = FORCECLOSE;
344 } else {
345 flags = 0;
346 }
347
348 mntdata = MOUNTTONULLMOUNT(mp);
349 vp = mntdata->nullm_rootvp;
350
351 // release our reference on the root before flushing.
352 // it will get pulled out of the mount structure by reclaim
353 vnode_getalways(vp);
354
355 error = vflush(mp, skipvp: vp, flags);
356 if (error) {
357 vnode_put(vp);
358 return error;
359 }
360
361 if (vnode_isinuse(vp, refcnt: 1) && flags == 0) {
362 vnode_put(vp);
363 return EBUSY;
364 }
365
366 vnode_rele(vp); // Drop reference taken by nullfs_mount
367 vnode_put(vp); // Drop ref taken above
368
369 //Force close to get rid of the last vnode
370 (void)vflush(mp, NULL, FORCECLOSE);
371
372 /* no more vnodes, so tear down the mountpoint */
373
374 lck_mtx_lock(lck: &mntdata->nullm_lock);
375
376 vfs_setfsprivate(mp, NULL);
377
378 vnode_getalways(mntdata->nullm_lowerrootvp);
379 vnode_rele(vp: mntdata->nullm_lowerrootvp);
380 vnode_put(vp: mntdata->nullm_lowerrootvp);
381
382 lck_mtx_unlock(lck: &mntdata->nullm_lock);
383
384 nullfs_destroy_lck(lck: &mntdata->nullm_lock);
385
386 kfree_type(struct null_mount, mntdata);
387
388 uint64_t vflags = vfs_flags(mp);
389 vfs_setflags(mp, flags: vflags & ~MNT_LOCAL);
390
391 return 0;
392}
393
394static int
395nullfs_root(struct mount * mp, struct vnode ** vpp, __unused vfs_context_t ctx)
396{
397 struct vnode * vp;
398 int error;
399
400 NULLFSDEBUG("nullfs_root(mp = %p, vp = %p)\n", (void *)mp, (void *)MOUNTTONULLMOUNT(mp)->nullm_rootvp);
401
402 /*
403 * Return locked reference to root.
404 */
405 vp = MOUNTTONULLMOUNT(mp)->nullm_rootvp;
406
407 error = vnode_get(vp);
408 if (error) {
409 return error;
410 }
411
412 *vpp = vp;
413 return 0;
414}
415
416static int
417nullfs_vfs_getattr(struct mount * mp, struct vfs_attr * vfap, vfs_context_t ctx)
418{
419 struct vnode * coveredvp = NULL;
420 struct vfs_attr vfa;
421 struct null_mount * null_mp = MOUNTTONULLMOUNT(mp);
422 vol_capabilities_attr_t capabilities;
423 struct vfsstatfs * sp = vfs_statfs(mp);
424 vfs_context_t ectx = nullfs_get_patched_context(null_mp, ctx);
425
426 struct timespec tzero = {.tv_sec = 0, .tv_nsec = 0};
427
428 NULLFSDEBUG("%s\n", __FUNCTION__);
429
430 /* Set default capabilities in case the lower file system is gone */
431 memset(s: &capabilities, c: 0, n: sizeof(capabilities));
432 capabilities.capabilities[VOL_CAPABILITIES_FORMAT] = VOL_CAP_FMT_FAST_STATFS | VOL_CAP_FMT_HIDDEN_FILES;
433 capabilities.valid[VOL_CAPABILITIES_FORMAT] = VOL_CAP_FMT_FAST_STATFS | VOL_CAP_FMT_HIDDEN_FILES;
434
435 if (nullfs_vfs_getlowerattr(mp: vnode_mount(vp: null_mp->nullm_lowerrootvp), vfap: &vfa, ctx: ectx) == 0) {
436 if (VFSATTR_IS_SUPPORTED(&vfa, f_capabilities)) {
437 memcpy(dst: &capabilities, src: &vfa.f_capabilities, n: sizeof(capabilities));
438 /* don't support vget */
439 capabilities.capabilities[VOL_CAPABILITIES_FORMAT] &= ~(VOL_CAP_FMT_PERSISTENTOBJECTIDS | VOL_CAP_FMT_PATH_FROM_ID);
440
441 capabilities.capabilities[VOL_CAPABILITIES_FORMAT] |= VOL_CAP_FMT_HIDDEN_FILES; /* Always support UF_HIDDEN */
442
443 capabilities.valid[VOL_CAPABILITIES_FORMAT] &= ~(VOL_CAP_FMT_PERSISTENTOBJECTIDS | VOL_CAP_FMT_PATH_FROM_ID);
444
445 capabilities.valid[VOL_CAPABILITIES_FORMAT] |= VOL_CAP_FMT_HIDDEN_FILES; /* Always support UF_HIDDEN */
446
447 /* dont' support interfaces that only make sense on a writable file system
448 * or one with specific vnops implemented */
449 capabilities.capabilities[VOL_CAPABILITIES_INTERFACES] = 0;
450
451 capabilities.valid[VOL_CAPABILITIES_INTERFACES] &=
452 ~(VOL_CAP_INT_SEARCHFS | VOL_CAP_INT_ATTRLIST | VOL_CAP_INT_READDIRATTR | VOL_CAP_INT_EXCHANGEDATA |
453 VOL_CAP_INT_COPYFILE | VOL_CAP_INT_ALLOCATE | VOL_CAP_INT_VOL_RENAME | VOL_CAP_INT_ADVLOCK | VOL_CAP_INT_FLOCK);
454 }
455 }
456
457 if (VFSATTR_IS_ACTIVE(vfap, f_create_time)) {
458 VFSATTR_RETURN(vfap, f_create_time, tzero);
459 }
460
461 if (VFSATTR_IS_ACTIVE(vfap, f_modify_time)) {
462 VFSATTR_RETURN(vfap, f_modify_time, tzero);
463 }
464
465 if (VFSATTR_IS_ACTIVE(vfap, f_access_time)) {
466 VFSATTR_RETURN(vfap, f_access_time, tzero);
467 }
468
469 if (VFSATTR_IS_ACTIVE(vfap, f_bsize)) {
470 VFSATTR_RETURN(vfap, f_bsize, sp->f_bsize);
471 }
472
473 if (VFSATTR_IS_ACTIVE(vfap, f_iosize)) {
474 VFSATTR_RETURN(vfap, f_iosize, sp->f_iosize);
475 }
476
477 if (VFSATTR_IS_ACTIVE(vfap, f_owner)) {
478 VFSATTR_RETURN(vfap, f_owner, 0);
479 }
480
481 if (VFSATTR_IS_ACTIVE(vfap, f_blocks)) {
482 VFSATTR_RETURN(vfap, f_blocks, sp->f_blocks);
483 }
484
485 if (VFSATTR_IS_ACTIVE(vfap, f_bfree)) {
486 VFSATTR_RETURN(vfap, f_bfree, sp->f_bfree);
487 }
488
489 if (VFSATTR_IS_ACTIVE(vfap, f_bavail)) {
490 VFSATTR_RETURN(vfap, f_bavail, sp->f_bavail);
491 }
492
493 if (VFSATTR_IS_ACTIVE(vfap, f_bused)) {
494 VFSATTR_RETURN(vfap, f_bused, sp->f_bused);
495 }
496
497 if (VFSATTR_IS_ACTIVE(vfap, f_files)) {
498 VFSATTR_RETURN(vfap, f_files, sp->f_files);
499 }
500
501 if (VFSATTR_IS_ACTIVE(vfap, f_ffree)) {
502 VFSATTR_RETURN(vfap, f_ffree, sp->f_ffree);
503 }
504
505 if (VFSATTR_IS_ACTIVE(vfap, f_fssubtype)) {
506 VFSATTR_RETURN(vfap, f_fssubtype, 0);
507 }
508
509 if (VFSATTR_IS_ACTIVE(vfap, f_capabilities)) {
510 memcpy(dst: &vfap->f_capabilities, src: &capabilities, n: sizeof(vol_capabilities_attr_t));
511
512 VFSATTR_SET_SUPPORTED(vfap, f_capabilities);
513 }
514
515 if (VFSATTR_IS_ACTIVE(vfap, f_attributes)) {
516 vol_attributes_attr_t * volattr = &vfap->f_attributes;
517
518 volattr->validattr.commonattr = 0;
519 volattr->validattr.volattr = ATTR_VOL_NAME | ATTR_VOL_CAPABILITIES | ATTR_VOL_ATTRIBUTES;
520 volattr->validattr.dirattr = 0;
521 volattr->validattr.fileattr = 0;
522 volattr->validattr.forkattr = 0;
523
524 volattr->nativeattr.commonattr = 0;
525 volattr->nativeattr.volattr = ATTR_VOL_NAME | ATTR_VOL_CAPABILITIES | ATTR_VOL_ATTRIBUTES;
526 volattr->nativeattr.dirattr = 0;
527 volattr->nativeattr.fileattr = 0;
528 volattr->nativeattr.forkattr = 0;
529
530 VFSATTR_SET_SUPPORTED(vfap, f_attributes);
531 }
532
533 if (VFSATTR_IS_ACTIVE(vfap, f_vol_name)) {
534 /* The name of the volume is the same as the directory we mounted on */
535 coveredvp = vfs_vnodecovered(mp);
536 if (coveredvp) {
537 const char * name = vnode_getname_printable(vp: coveredvp);
538 strlcpy(dst: vfap->f_vol_name, src: name, MAXPATHLEN);
539 vnode_putname_printable(name);
540
541 VFSATTR_SET_SUPPORTED(vfap, f_vol_name);
542 vnode_put(vp: coveredvp);
543 }
544 }
545
546 nullfs_cleanup_patched_context(null_mp, ctx: ectx);
547
548 return 0;
549}
550
551static int
552nullfs_sync(__unused struct mount * mp, __unused int waitfor, __unused vfs_context_t ctx)
553{
554 /*
555 * XXX - Assumes no data cached at null layer.
556 */
557 return 0;
558}
559
560
561
562static int
563nullfs_vfs_start(__unused struct mount * mp, __unused int flags, __unused vfs_context_t ctx)
564{
565 NULLFSDEBUG("%s\n", __FUNCTION__);
566 return 0;
567}
568
569extern const struct vnodeopv_desc nullfs_vnodeop_opv_desc;
570
571const struct vnodeopv_desc * nullfs_vnodeopv_descs[] = {
572 &nullfs_vnodeop_opv_desc,
573};
574
575struct vfsops nullfs_vfsops = {
576 .vfs_mount = nullfs_mount,
577 .vfs_unmount = nullfs_unmount,
578 .vfs_start = nullfs_vfs_start,
579 .vfs_root = nullfs_root,
580 .vfs_getattr = nullfs_vfs_getattr,
581 .vfs_sync = nullfs_sync,
582 .vfs_init = nullfs_init,
583 .vfs_sysctl = NULL,
584 .vfs_setattr = NULL,
585};
586