]> git.saurik.com Git - apple/xnu.git/blob - bsd/miscfs/nullfs/null_vfsops.c
xnu-7195.60.75.tar.gz
[apple/xnu.git] / bsd / miscfs / nullfs / null_vfsops.c
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
86 static int
87 nullfs_vfs_getlowerattr(mount_t mp, struct vfs_attr * vfap, vfs_context_t ctx)
88 {
89 memset(vfap, 0, 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, vfap, ctx);
102 }
103
104 /*
105 * Mount null layer
106 */
107 static int
108 nullfs_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 (!IOTaskHasEntitlement(current_task(), 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(user_data + sizeof(conf), path, MAXPATHLEN - 1, &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, 0, &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(lowerrootvp);
185 vnode_put(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 MALLOC(xmp, struct null_mount *, sizeof(*xmp), M_TEMP, M_WAITOK | M_ZERO);
195 if (xmp == NULL) {
196 error = ENOMEM;
197 goto error;
198 }
199
200 /*
201 * Grab the uid/gid of the caller, which may be used for unveil later
202 */
203 xmp->uid = kauth_cred_getuid(cred);
204 xmp->gid = kauth_cred_getgid(cred);
205
206 /*
207 * Save reference to underlying FS
208 */
209 xmp->nullm_lowerrootvp = lowerrootvp;
210 xmp->nullm_lowerrootvid = vnode_vid(lowerrootvp);
211
212 error = null_getnewvnode(mp, NULL, NULL, &vp, NULL, 1);
213 if (error) {
214 goto error;
215 }
216
217 /* vp has an iocount on it from vnode_create. drop that for a usecount. This
218 * is our root vnode so we drop the ref in unmount
219 *
220 * Assuming for now that because we created this vnode and we aren't finished mounting we can get a ref*/
221 vnode_ref(vp);
222 vnode_put(vp);
223
224 error = nullfs_init_lck(&xmp->nullm_lock);
225 if (error) {
226 goto error;
227 }
228
229 xmp->nullm_rootvp = vp;
230
231 /* read the flags the user set, but then ignore some of them, we will only
232 * allow them if they are set on the lower file system */
233 uint64_t flags = vfs_flags(mp) & (~(MNT_IGNORE_OWNERSHIP | MNT_LOCAL));
234 uint64_t lowerflags = vfs_flags(vnode_mount(lowerrootvp)) & (MNT_LOCAL | MNT_QUARANTINE | MNT_IGNORE_OWNERSHIP | MNT_NOEXEC);
235
236 if (lowerflags) {
237 flags |= lowerflags;
238 }
239
240 /* force these flags */
241 flags |= (MNT_DONTBROWSE | MNT_MULTILABEL | MNT_NOSUID | MNT_RDONLY);
242 vfs_setflags(mp, flags);
243
244 vfs_setfsprivate(mp, xmp);
245 vfs_getnewfsid(mp);
246 vfs_setlocklocal(mp);
247
248 /* fill in the stat block */
249 sp = vfs_statfs(mp);
250 strlcpy(sp->f_mntfromname, path, MAX_MNT_FROM_LENGTH);
251
252 sp->f_flags = flags;
253
254 xmp->nullm_flags = NULLM_CASEINSENSITIVE; /* default to case insensitive */
255
256 // Set the flags that are requested
257 xmp->nullm_flags |= conf.flags & NULLM_UNVEIL;
258
259 error = nullfs_vfs_getlowerattr(vnode_mount(lowerrootvp), &vfa, ctx);
260 if (error == 0) {
261 if (VFSATTR_IS_SUPPORTED(&vfa, f_bsize)) {
262 bsize = vfa.f_bsize;
263 }
264 if (VFSATTR_IS_SUPPORTED(&vfa, f_iosize)) {
265 iosize = vfa.f_iosize;
266 }
267 if (VFSATTR_IS_SUPPORTED(&vfa, f_blocks)) {
268 blocks = vfa.f_blocks;
269 }
270 if (VFSATTR_IS_SUPPORTED(&vfa, f_bfree)) {
271 bfree = vfa.f_bfree;
272 }
273 if (VFSATTR_IS_SUPPORTED(&vfa, f_bavail)) {
274 bavail = vfa.f_bavail;
275 }
276 if (VFSATTR_IS_SUPPORTED(&vfa, f_bused)) {
277 bused = vfa.f_bused;
278 }
279 if (VFSATTR_IS_SUPPORTED(&vfa, f_files)) {
280 files = vfa.f_files;
281 }
282 if (VFSATTR_IS_SUPPORTED(&vfa, f_ffree)) {
283 ffree = vfa.f_ffree;
284 }
285 if (VFSATTR_IS_SUPPORTED(&vfa, f_capabilities)) {
286 if ((vfa.f_capabilities.capabilities[VOL_CAPABILITIES_FORMAT] & (VOL_CAP_FMT_CASE_SENSITIVE)) &&
287 (vfa.f_capabilities.valid[VOL_CAPABILITIES_FORMAT] & (VOL_CAP_FMT_CASE_SENSITIVE))) {
288 xmp->nullm_flags &= ~NULLM_CASEINSENSITIVE;
289 }
290 }
291 } else {
292 goto error;
293 }
294
295 sp->f_bsize = bsize;
296 sp->f_iosize = iosize;
297 sp->f_blocks = blocks;
298 sp->f_bfree = bfree;
299 sp->f_bavail = bavail;
300 sp->f_bused = bused;
301 sp->f_files = files;
302 sp->f_ffree = ffree;
303
304 /* Associate the mac label information from the mirrored filesystem with the
305 * mirror */
306 MAC_PERFORM(mount_label_associate, cred, vnode_mount(lowerrootvp), vfs_mntlabel(mp));
307
308 NULLFSDEBUG("nullfs_mount: lower %s, alias at %s\n", sp->f_mntfromname, sp->f_mntonname);
309 return 0;
310
311 error:
312 if (xmp) {
313 FREE(xmp, M_TEMP);
314 }
315 if (lowerrootvp) {
316 vnode_getwithref(lowerrootvp);
317 vnode_rele(lowerrootvp);
318 vnode_put(lowerrootvp);
319 }
320 if (vp) {
321 /* we made the root vnode but the mount is failed, so clean it up */
322 vnode_getwithref(vp);
323 vnode_rele(vp);
324 /* give vp back */
325 vnode_recycle(vp);
326 vnode_put(vp);
327 }
328 return error;
329 }
330
331 /*
332 * Free reference to null layer
333 */
334 static int
335 nullfs_unmount(struct mount * mp, int mntflags, __unused vfs_context_t ctx)
336 {
337 struct null_mount * mntdata;
338 struct vnode * vp;
339 int error, flags;
340
341 NULLFSDEBUG("nullfs_unmount: mp = %p\n", (void *)mp);
342
343 /* check entitlement or superuser*/
344 if (!IOTaskHasEntitlement(current_task(), NULLFS_ENTITLEMENT) &&
345 vfs_context_suser(ctx) != 0) {
346 return EPERM;
347 }
348
349 if (mntflags & MNT_FORCE) {
350 flags = FORCECLOSE;
351 } else {
352 flags = 0;
353 }
354
355 mntdata = MOUNTTONULLMOUNT(mp);
356 vp = mntdata->nullm_rootvp;
357
358 // release our reference on the root before flushing.
359 // it will get pulled out of the mount structure by reclaim
360 vnode_getalways(vp);
361
362 error = vflush(mp, vp, flags);
363 if (error) {
364 vnode_put(vp);
365 return error;
366 }
367
368 if (vnode_isinuse(vp, 1) && flags == 0) {
369 vnode_put(vp);
370 return EBUSY;
371 }
372
373 vnode_rele(vp); // Drop reference taken by nullfs_mount
374 vnode_put(vp); // Drop ref taken above
375
376 //Force close to get rid of the last vnode
377 (void)vflush(mp, NULL, FORCECLOSE);
378
379 /* no more vnodes, so tear down the mountpoint */
380
381 lck_mtx_lock(&mntdata->nullm_lock);
382
383 vfs_setfsprivate(mp, NULL);
384
385 vnode_getalways(mntdata->nullm_lowerrootvp);
386 vnode_rele(mntdata->nullm_lowerrootvp);
387 vnode_put(mntdata->nullm_lowerrootvp);
388
389 lck_mtx_unlock(&mntdata->nullm_lock);
390
391 nullfs_destroy_lck(&mntdata->nullm_lock);
392
393 FREE(mntdata, M_TEMP);
394
395 uint64_t vflags = vfs_flags(mp);
396 vfs_setflags(mp, vflags & ~MNT_LOCAL);
397
398 return 0;
399 }
400
401 static int
402 nullfs_root(struct mount * mp, struct vnode ** vpp, __unused vfs_context_t ctx)
403 {
404 struct vnode * vp;
405 int error;
406
407 NULLFSDEBUG("nullfs_root(mp = %p, vp = %p)\n", (void *)mp, (void *)MOUNTTONULLMOUNT(mp)->nullm_rootvp);
408
409 /*
410 * Return locked reference to root.
411 */
412 vp = MOUNTTONULLMOUNT(mp)->nullm_rootvp;
413
414 error = vnode_get(vp);
415 if (error) {
416 return error;
417 }
418
419 *vpp = vp;
420 return 0;
421 }
422
423 static int
424 nullfs_vfs_getattr(struct mount * mp, struct vfs_attr * vfap, vfs_context_t ctx)
425 {
426 struct vnode * coveredvp = NULL;
427 struct vfs_attr vfa;
428 struct null_mount * null_mp = MOUNTTONULLMOUNT(mp);
429 vol_capabilities_attr_t capabilities;
430 struct vfsstatfs * sp = vfs_statfs(mp);
431 vfs_context_t ectx = nullfs_get_patched_context(null_mp, ctx);
432
433 struct timespec tzero = {.tv_sec = 0, .tv_nsec = 0};
434
435 NULLFSDEBUG("%s\n", __FUNCTION__);
436
437 /* Set default capabilities in case the lower file system is gone */
438 memset(&capabilities, 0, sizeof(capabilities));
439 capabilities.capabilities[VOL_CAPABILITIES_FORMAT] = VOL_CAP_FMT_FAST_STATFS | VOL_CAP_FMT_HIDDEN_FILES;
440 capabilities.valid[VOL_CAPABILITIES_FORMAT] = VOL_CAP_FMT_FAST_STATFS | VOL_CAP_FMT_HIDDEN_FILES;
441
442 if (nullfs_vfs_getlowerattr(vnode_mount(null_mp->nullm_lowerrootvp), &vfa, ectx) == 0) {
443 if (VFSATTR_IS_SUPPORTED(&vfa, f_capabilities)) {
444 memcpy(&capabilities, &vfa.f_capabilities, sizeof(capabilities));
445 /* don't support vget */
446 capabilities.capabilities[VOL_CAPABILITIES_FORMAT] &= ~(VOL_CAP_FMT_PERSISTENTOBJECTIDS | VOL_CAP_FMT_PATH_FROM_ID);
447
448 capabilities.capabilities[VOL_CAPABILITIES_FORMAT] |= VOL_CAP_FMT_HIDDEN_FILES; /* Always support UF_HIDDEN */
449
450 capabilities.valid[VOL_CAPABILITIES_FORMAT] &= ~(VOL_CAP_FMT_PERSISTENTOBJECTIDS | VOL_CAP_FMT_PATH_FROM_ID);
451
452 capabilities.valid[VOL_CAPABILITIES_FORMAT] |= VOL_CAP_FMT_HIDDEN_FILES; /* Always support UF_HIDDEN */
453
454 /* dont' support interfaces that only make sense on a writable file system
455 * or one with specific vnops implemented */
456 capabilities.capabilities[VOL_CAPABILITIES_INTERFACES] = 0;
457
458 capabilities.valid[VOL_CAPABILITIES_INTERFACES] &=
459 ~(VOL_CAP_INT_SEARCHFS | VOL_CAP_INT_ATTRLIST | VOL_CAP_INT_READDIRATTR | VOL_CAP_INT_EXCHANGEDATA |
460 VOL_CAP_INT_COPYFILE | VOL_CAP_INT_ALLOCATE | VOL_CAP_INT_VOL_RENAME | VOL_CAP_INT_ADVLOCK | VOL_CAP_INT_FLOCK);
461 }
462 }
463
464 if (VFSATTR_IS_ACTIVE(vfap, f_create_time)) {
465 VFSATTR_RETURN(vfap, f_create_time, tzero);
466 }
467
468 if (VFSATTR_IS_ACTIVE(vfap, f_modify_time)) {
469 VFSATTR_RETURN(vfap, f_modify_time, tzero);
470 }
471
472 if (VFSATTR_IS_ACTIVE(vfap, f_access_time)) {
473 VFSATTR_RETURN(vfap, f_access_time, tzero);
474 }
475
476 if (VFSATTR_IS_ACTIVE(vfap, f_bsize)) {
477 VFSATTR_RETURN(vfap, f_bsize, sp->f_bsize);
478 }
479
480 if (VFSATTR_IS_ACTIVE(vfap, f_iosize)) {
481 VFSATTR_RETURN(vfap, f_iosize, sp->f_iosize);
482 }
483
484 if (VFSATTR_IS_ACTIVE(vfap, f_owner)) {
485 VFSATTR_RETURN(vfap, f_owner, 0);
486 }
487
488 if (VFSATTR_IS_ACTIVE(vfap, f_blocks)) {
489 VFSATTR_RETURN(vfap, f_blocks, sp->f_blocks);
490 }
491
492 if (VFSATTR_IS_ACTIVE(vfap, f_bfree)) {
493 VFSATTR_RETURN(vfap, f_bfree, sp->f_bfree);
494 }
495
496 if (VFSATTR_IS_ACTIVE(vfap, f_bavail)) {
497 VFSATTR_RETURN(vfap, f_bavail, sp->f_bavail);
498 }
499
500 if (VFSATTR_IS_ACTIVE(vfap, f_bused)) {
501 VFSATTR_RETURN(vfap, f_bused, sp->f_bused);
502 }
503
504 if (VFSATTR_IS_ACTIVE(vfap, f_files)) {
505 VFSATTR_RETURN(vfap, f_files, sp->f_files);
506 }
507
508 if (VFSATTR_IS_ACTIVE(vfap, f_ffree)) {
509 VFSATTR_RETURN(vfap, f_ffree, sp->f_ffree);
510 }
511
512 if (VFSATTR_IS_ACTIVE(vfap, f_fssubtype)) {
513 VFSATTR_RETURN(vfap, f_fssubtype, 0);
514 }
515
516 if (VFSATTR_IS_ACTIVE(vfap, f_capabilities)) {
517 memcpy(&vfap->f_capabilities, &capabilities, sizeof(vol_capabilities_attr_t));
518
519 VFSATTR_SET_SUPPORTED(vfap, f_capabilities);
520 }
521
522 if (VFSATTR_IS_ACTIVE(vfap, f_attributes)) {
523 vol_attributes_attr_t * volattr = &vfap->f_attributes;
524
525 volattr->validattr.commonattr = 0;
526 volattr->validattr.volattr = ATTR_VOL_NAME | ATTR_VOL_CAPABILITIES | ATTR_VOL_ATTRIBUTES;
527 volattr->validattr.dirattr = 0;
528 volattr->validattr.fileattr = 0;
529 volattr->validattr.forkattr = 0;
530
531 volattr->nativeattr.commonattr = 0;
532 volattr->nativeattr.volattr = ATTR_VOL_NAME | ATTR_VOL_CAPABILITIES | ATTR_VOL_ATTRIBUTES;
533 volattr->nativeattr.dirattr = 0;
534 volattr->nativeattr.fileattr = 0;
535 volattr->nativeattr.forkattr = 0;
536
537 VFSATTR_SET_SUPPORTED(vfap, f_attributes);
538 }
539
540 if (VFSATTR_IS_ACTIVE(vfap, f_vol_name)) {
541 /* The name of the volume is the same as the directory we mounted on */
542 coveredvp = vfs_vnodecovered(mp);
543 if (coveredvp) {
544 const char * name = vnode_getname_printable(coveredvp);
545 strlcpy(vfap->f_vol_name, name, MAXPATHLEN);
546 vnode_putname_printable(name);
547
548 VFSATTR_SET_SUPPORTED(vfap, f_vol_name);
549 vnode_put(coveredvp);
550 }
551 }
552
553 nullfs_cleanup_patched_context(null_mp, ectx);
554
555 return 0;
556 }
557
558 static int
559 nullfs_sync(__unused struct mount * mp, __unused int waitfor, __unused vfs_context_t ctx)
560 {
561 /*
562 * XXX - Assumes no data cached at null layer.
563 */
564 return 0;
565 }
566
567
568
569 static int
570 nullfs_vfs_start(__unused struct mount * mp, __unused int flags, __unused vfs_context_t ctx)
571 {
572 NULLFSDEBUG("%s\n", __FUNCTION__);
573 return 0;
574 }
575
576 extern const struct vnodeopv_desc nullfs_vnodeop_opv_desc;
577
578 const struct vnodeopv_desc * nullfs_vnodeopv_descs[] = {
579 &nullfs_vnodeop_opv_desc,
580 };
581
582 struct vfsops nullfs_vfsops = {
583 .vfs_mount = nullfs_mount,
584 .vfs_unmount = nullfs_unmount,
585 .vfs_start = nullfs_vfs_start,
586 .vfs_root = nullfs_root,
587 .vfs_getattr = nullfs_vfs_getattr,
588 .vfs_sync = nullfs_sync,
589 .vfs_init = nullfs_init,
590 .vfs_sysctl = NULL,
591 .vfs_setattr = NULL,
592 };