]>
Commit | Line | Data |
---|---|---|
1c79356b | 1 | /* |
91447636 | 2 | * Copyright (c) 2000-2004 Apple Computer, Inc. All rights reserved. |
1c79356b A |
3 | * |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
e5568f75 A |
6 | * The contents of this file constitute Original Code as defined in and |
7 | * are subject to the Apple Public Source License Version 1.1 (the | |
8 | * "License"). You may not use this file except in compliance with the | |
9 | * License. Please obtain a copy of the License at | |
10 | * http://www.apple.com/publicsource and read it before using this file. | |
1c79356b | 11 | * |
e5568f75 A |
12 | * This Original Code and all software distributed under the License are |
13 | * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
1c79356b A |
14 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, |
15 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
e5568f75 A |
16 | * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the |
17 | * License for the specific language governing rights and limitations | |
18 | * under the License. | |
1c79356b A |
19 | * |
20 | * @APPLE_LICENSE_HEADER_END@ | |
21 | */ | |
22 | /* Copyright (c) 1995 NeXT Computer, Inc. All Rights Reserved */ | |
23 | /* | |
24 | * Copyright (c) 1994, 1995 The Regents of the University of California. | |
25 | * Copyright (c) 1994, 1995 Jan-Simon Pendry. | |
26 | * 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 | * 3. All advertising materials mentioning features or use of this software | |
40 | * must display the following acknowledgement: | |
41 | * This product includes software developed by the University of | |
42 | * California, Berkeley and its contributors. | |
43 | * 4. Neither the name of the University nor the names of its contributors | |
44 | * may be used to endorse or promote products derived from this software | |
45 | * without specific prior written permission. | |
46 | * | |
47 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | |
48 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
49 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
50 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | |
51 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
52 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
53 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
54 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
55 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
56 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
57 | * SUCH DAMAGE. | |
58 | * | |
59 | * @(#)union_vfsops.c 8.20 (Berkeley) 5/20/95 | |
60 | */ | |
61 | ||
62 | /* | |
63 | * Union Layer | |
64 | */ | |
65 | ||
66 | #include <sys/param.h> | |
67 | #include <sys/systm.h> | |
68 | #include <sys/time.h> | |
69 | #include <sys/types.h> | |
91447636 A |
70 | #include <sys/proc_internal.h> |
71 | #include <sys/kauth.h> | |
72 | #include <sys/vnode_internal.h> | |
73 | #include <sys/mount_internal.h> | |
1c79356b A |
74 | #include <sys/namei.h> |
75 | #include <sys/malloc.h> | |
76 | #include <sys/filedesc.h> | |
77 | #include <sys/queue.h> | |
78 | #include <miscfs/union/union.h> | |
79 | ||
91447636 A |
80 | static int union_itercallback(__unused vnode_t, void *); |
81 | ||
1c79356b A |
82 | /* |
83 | * Mount union filesystem | |
84 | */ | |
85 | int | |
91447636 | 86 | union_mount(mount_t mp, __unused vnode_t devvp, user_addr_t data, vfs_context_t context) |
1c79356b | 87 | { |
91447636 | 88 | proc_t p = vfs_context_proc(context); |
1c79356b | 89 | int error = 0; |
91447636 | 90 | struct user_union_args args; |
1c79356b A |
91 | struct vnode *lowerrootvp = NULLVP; |
92 | struct vnode *upperrootvp = NULLVP; | |
93 | struct union_mount *um = 0; | |
91447636 | 94 | kauth_cred_t cred = NOCRED; |
1c79356b A |
95 | char *cp; |
96 | int len; | |
97 | u_int size; | |
91447636 A |
98 | struct nameidata nd; |
99 | ||
1c79356b A |
100 | #ifdef UNION_DIAGNOSTIC |
101 | printf("union_mount(mp = %x)\n", mp); | |
102 | #endif | |
103 | ||
104 | /* | |
105 | * Update is a no-op | |
106 | */ | |
107 | if (mp->mnt_flag & MNT_UPDATE) { | |
108 | /* | |
109 | * Need to provide. | |
110 | * 1. a way to convert between rdonly and rdwr mounts. | |
111 | * 2. support for nfs exports. | |
112 | */ | |
91447636 | 113 | error = ENOTSUP; |
1c79356b A |
114 | goto bad; |
115 | } | |
116 | ||
117 | /* | |
118 | * Get argument | |
119 | */ | |
91447636 A |
120 | if (vfs_context_is64bit(context)) { |
121 | error = copyin(data, (caddr_t)&args, sizeof(args)); | |
122 | } | |
123 | else { | |
124 | struct union_args temp; | |
125 | error = copyin(data, (caddr_t)&temp, sizeof (temp)); | |
126 | args.target = CAST_USER_ADDR_T(temp.target); | |
127 | args.mntflags = temp.mntflags; | |
128 | } | |
129 | if (error) | |
1c79356b A |
130 | goto bad; |
131 | ||
132 | lowerrootvp = mp->mnt_vnodecovered; | |
91447636 | 133 | vnode_get(lowerrootvp); |
1c79356b A |
134 | |
135 | /* | |
136 | * Find upper node. | |
137 | */ | |
91447636 A |
138 | NDINIT(&nd, LOOKUP, FOLLOW|WANTPARENT, |
139 | (IS_64BIT_PROCESS(p) ? UIO_USERSPACE64 : UIO_USERSPACE32), | |
140 | args.target, context); | |
1c79356b | 141 | |
91447636 | 142 | if ((error = namei(&nd))) |
1c79356b A |
143 | goto bad; |
144 | ||
91447636 A |
145 | nameidone(&nd); |
146 | upperrootvp = nd.ni_vp; | |
147 | vnode_put(nd.ni_dvp); | |
148 | nd.ni_dvp = NULL; | |
1c79356b A |
149 | |
150 | if (upperrootvp->v_type != VDIR) { | |
151 | error = EINVAL; | |
152 | goto bad; | |
153 | } | |
154 | ||
155 | // um = (struct union_mount *) malloc(sizeof(struct union_mount), | |
156 | // M_UFSMNT, M_WAITOK); /* XXX */ | |
157 | MALLOC(um, struct union_mount *, sizeof(struct union_mount), | |
158 | M_UFSMNT, M_WAITOK); | |
159 | ||
160 | /* | |
161 | * Keep a held reference to the target vnodes. | |
91447636 | 162 | * They are vnode_put'd in union_unmount. |
1c79356b A |
163 | * |
164 | * Depending on the _BELOW flag, the filesystems are | |
165 | * viewed in a different order. In effect, this is the | |
166 | * same as providing a mount under option to the mount syscall. | |
167 | */ | |
168 | ||
169 | um->um_op = args.mntflags & UNMNT_OPMASK; | |
170 | switch (um->um_op) { | |
171 | case UNMNT_ABOVE: | |
172 | um->um_lowervp = lowerrootvp; | |
173 | um->um_uppervp = upperrootvp; | |
174 | break; | |
175 | ||
176 | case UNMNT_BELOW: | |
177 | um->um_lowervp = upperrootvp; | |
178 | um->um_uppervp = lowerrootvp; | |
179 | break; | |
180 | ||
181 | case UNMNT_REPLACE: | |
91447636 | 182 | vnode_put(lowerrootvp); |
1c79356b A |
183 | lowerrootvp = NULLVP; |
184 | um->um_uppervp = upperrootvp; | |
185 | um->um_lowervp = lowerrootvp; | |
186 | break; | |
187 | ||
188 | default: | |
189 | error = EINVAL; | |
190 | goto bad; | |
191 | } | |
192 | ||
193 | /* | |
194 | * Unless the mount is readonly, ensure that the top layer | |
195 | * supports whiteout operations | |
196 | */ | |
197 | if ((mp->mnt_flag & MNT_RDONLY) == 0) { | |
91447636 A |
198 | error = VNOP_WHITEOUT(um->um_uppervp, (struct componentname *) 0, |
199 | LOOKUP, context); | |
1c79356b A |
200 | if (error) |
201 | goto bad; | |
202 | } | |
203 | ||
91447636 | 204 | um->um_cred = kauth_cred_get_with_ref(); |
1c79356b A |
205 | um->um_cmode = UN_DIRMODE &~ p->p_fd->fd_cmask; |
206 | ||
207 | /* | |
208 | * Depending on what you think the MNT_LOCAL flag might mean, | |
209 | * you may want the && to be || on the conditional below. | |
210 | * At the moment it has been defined that the filesystem is | |
211 | * only local if it is all local, ie the MNT_LOCAL flag implies | |
212 | * that the entire namespace is local. If you think the MNT_LOCAL | |
213 | * flag implies that some of the files might be stored locally | |
214 | * then you will want to change the conditional. | |
215 | */ | |
216 | if (um->um_op == UNMNT_ABOVE) { | |
217 | if (((um->um_lowervp == NULLVP) || | |
218 | (um->um_lowervp->v_mount->mnt_flag & MNT_LOCAL)) && | |
219 | (um->um_uppervp->v_mount->mnt_flag & MNT_LOCAL)) | |
220 | mp->mnt_flag |= MNT_LOCAL; | |
221 | } | |
222 | ||
223 | /* | |
224 | * Copy in the upper layer's RDONLY flag. This is for the benefit | |
225 | * of lookup() which explicitly checks the flag, rather than asking | |
226 | * the filesystem for it's own opinion. This means, that an update | |
227 | * mount of the underlying filesystem to go from rdonly to rdwr | |
228 | * will leave the unioned view as read-only. | |
229 | */ | |
230 | mp->mnt_flag |= (um->um_uppervp->v_mount->mnt_flag & MNT_RDONLY); | |
231 | ||
232 | mp->mnt_data = (qaddr_t) um; | |
233 | vfs_getnewfsid(mp); | |
234 | ||
1c79356b A |
235 | |
236 | switch (um->um_op) { | |
237 | case UNMNT_ABOVE: | |
238 | cp = "<above>:"; | |
239 | break; | |
240 | case UNMNT_BELOW: | |
241 | cp = "<below>:"; | |
242 | break; | |
243 | case UNMNT_REPLACE: | |
244 | cp = ""; | |
245 | break; | |
246 | } | |
247 | len = strlen(cp); | |
91447636 | 248 | bcopy(cp, mp->mnt_vfsstat.f_mntfromname, len); |
1c79356b | 249 | |
91447636 | 250 | cp = mp->mnt_vfsstat.f_mntfromname + len; |
1c79356b A |
251 | len = MNAMELEN - len; |
252 | ||
55e303ae | 253 | (void) copyinstr(args.target, cp, len - 1, (size_t *)&size); |
1c79356b A |
254 | bzero(cp + size, len - size); |
255 | ||
256 | #ifdef UNION_DIAGNOSTIC | |
257 | printf("union_mount: from %s, on %s\n", | |
91447636 | 258 | mp->mnt_vfsstat.f_mntfromname, mp->mnt_vfsstat.f_mntonname); |
1c79356b A |
259 | #endif |
260 | return (0); | |
261 | ||
262 | bad: | |
263 | if (um) | |
264 | _FREE(um, M_UFSMNT); | |
265 | if (cred != NOCRED) | |
91447636 | 266 | kauth_cred_rele(cred); |
1c79356b | 267 | if (upperrootvp) |
91447636 | 268 | vnode_put(upperrootvp); |
1c79356b | 269 | if (lowerrootvp) |
91447636 | 270 | vnode_put(lowerrootvp); |
1c79356b A |
271 | return (error); |
272 | } | |
273 | ||
274 | /* | |
275 | * VFS start. Nothing needed here - the start routine | |
276 | * on the underlying filesystem(s) will have been called | |
277 | * when that filesystem was mounted. | |
278 | */ | |
279 | int | |
91447636 | 280 | union_start(__unused struct mount *mp, __unused int flags, __unused vfs_context_t context) |
1c79356b A |
281 | { |
282 | ||
283 | return (0); | |
284 | } | |
285 | ||
91447636 A |
286 | static int |
287 | union_itercallback(__unused vnode_t vp, void *args) | |
288 | { | |
289 | int num = *(int *)args; | |
290 | ||
291 | *(int *)args = num + 1; | |
292 | return(VNODE_RETURNED); | |
293 | } | |
294 | ||
295 | ||
296 | ||
1c79356b A |
297 | /* |
298 | * Free reference to union layer | |
299 | */ | |
300 | int | |
91447636 | 301 | union_unmount(mount_t mp, int mntflags, __unused vfs_context_t context) |
1c79356b A |
302 | { |
303 | struct union_mount *um = MOUNTTOUNIONMOUNT(mp); | |
304 | struct vnode *um_rootvp; | |
305 | int error; | |
306 | int freeing; | |
307 | int flags = 0; | |
91447636 | 308 | kauth_cred_t cred; |
1c79356b A |
309 | |
310 | #ifdef UNION_DIAGNOSTIC | |
311 | printf("union_unmount(mp = %x)\n", mp); | |
312 | #endif | |
313 | ||
314 | if (mntflags & MNT_FORCE) | |
315 | flags |= FORCECLOSE; | |
316 | ||
91447636 | 317 | if ((error = union_root(mp, &um_rootvp))) |
1c79356b A |
318 | return (error); |
319 | ||
320 | /* | |
321 | * Keep flushing vnodes from the mount list. | |
322 | * This is needed because of the un_pvp held | |
323 | * reference to the parent vnode. | |
324 | * If more vnodes have been freed on a given pass, | |
325 | * the try again. The loop will iterate at most | |
326 | * (d) times, where (d) is the maximum tree depth | |
327 | * in the filesystem. | |
328 | */ | |
329 | for (freeing = 0; vflush(mp, um_rootvp, flags) != 0;) { | |
91447636 | 330 | int n = 0; |
1c79356b | 331 | |
91447636 | 332 | vnode_iterate(mp, VNODE_NOLOCK_INTERNAL, union_itercallback, &n); |
1c79356b A |
333 | |
334 | /* if this is unchanged then stop */ | |
335 | if (n == freeing) | |
336 | break; | |
337 | ||
338 | /* otherwise try once more time */ | |
339 | freeing = n; | |
340 | } | |
341 | ||
342 | /* At this point the root vnode should have a single reference */ | |
91447636 A |
343 | if (vnode_isinuse(um_rootvp, 0)) { |
344 | vnode_put(um_rootvp); | |
1c79356b A |
345 | return (EBUSY); |
346 | } | |
347 | ||
348 | #ifdef UNION_DIAGNOSTIC | |
349 | vprint("union root", um_rootvp); | |
350 | #endif | |
351 | /* | |
352 | * Discard references to upper and lower target vnodes. | |
353 | */ | |
354 | if (um->um_lowervp) | |
91447636 A |
355 | vnode_put(um->um_lowervp); |
356 | vnode_put(um->um_uppervp); | |
1c79356b A |
357 | cred = um->um_cred; |
358 | if (cred != NOCRED) { | |
359 | um->um_cred = NOCRED; | |
91447636 | 360 | kauth_cred_rele(cred); |
1c79356b A |
361 | } |
362 | /* | |
363 | * Release reference on underlying root vnode | |
364 | */ | |
91447636 | 365 | vnode_put(um_rootvp); |
1c79356b A |
366 | /* |
367 | * And blow it away for future re-use | |
368 | */ | |
91447636 | 369 | vnode_reclaim(um_rootvp); |
1c79356b A |
370 | /* |
371 | * Finally, throw away the union_mount structure | |
372 | */ | |
373 | _FREE(mp->mnt_data, M_UFSMNT); /* XXX */ | |
374 | mp->mnt_data = 0; | |
375 | return (0); | |
376 | } | |
377 | ||
378 | int | |
91447636 | 379 | union_root(mount_t mp, vnode_t *vpp, __unused vfs_context_t context) |
1c79356b | 380 | { |
1c79356b A |
381 | struct union_mount *um = MOUNTTOUNIONMOUNT(mp); |
382 | int error; | |
1c79356b A |
383 | |
384 | /* | |
385 | * Return locked reference to root. | |
386 | */ | |
91447636 | 387 | vnode_get(um->um_uppervp); |
1c79356b | 388 | if (um->um_lowervp) |
91447636 | 389 | vnode_get(um->um_lowervp); |
1c79356b A |
390 | error = union_allocvp(vpp, mp, |
391 | (struct vnode *) 0, | |
392 | (struct vnode *) 0, | |
393 | (struct componentname *) 0, | |
394 | um->um_uppervp, | |
395 | um->um_lowervp, | |
396 | 1); | |
397 | ||
398 | if (error) { | |
91447636 | 399 | vnode_put(um->um_uppervp); |
1c79356b | 400 | if (um->um_lowervp) |
91447636 A |
401 | vnode_put(um->um_lowervp); |
402 | } | |
1c79356b A |
403 | |
404 | return (error); | |
405 | } | |
406 | ||
91447636 A |
407 | static int |
408 | union_vfs_getattr(mount_t mp, struct vfs_attr *fsap, vfs_context_t context) | |
1c79356b A |
409 | { |
410 | int error; | |
411 | struct union_mount *um = MOUNTTOUNIONMOUNT(mp); | |
91447636 A |
412 | struct vfs_attr attr; |
413 | uint32_t lbsize = 0; | |
1c79356b A |
414 | |
415 | #ifdef UNION_DIAGNOSTIC | |
91447636 | 416 | printf("union_vfs_getattr(mp = %x, lvp = %x, uvp = %x)\n", mp, |
1c79356b A |
417 | um->um_lowervp, |
418 | um->um_uppervp); | |
419 | #endif | |
420 | ||
91447636 | 421 | /* Get values from lower file system (if any) */ |
1c79356b | 422 | if (um->um_lowervp) { |
91447636 A |
423 | VFSATTR_INIT(&attr); |
424 | VFSATTR_WANTED(&attr, f_bsize); | |
425 | VFSATTR_WANTED(&attr, f_blocks); | |
426 | VFSATTR_WANTED(&attr, f_bused); | |
427 | VFSATTR_WANTED(&attr, f_files); | |
428 | error = vfs_getattr(um->um_lowervp->v_mount, &attr, context); | |
1c79356b A |
429 | if (error) |
430 | return (error); | |
91447636 A |
431 | |
432 | /* now copy across the "interesting" information and fake the rest */ | |
433 | if (VFSATTR_IS_SUPPORTED(&attr, f_bsize)) | |
434 | lbsize = attr.f_bsize; | |
435 | else | |
436 | lbsize = um->um_lowervp->v_mount->mnt_devblocksize; | |
437 | fsap->f_blocks = VFSATTR_IS_SUPPORTED(&attr, f_blocks) ? attr.f_blocks : 0; | |
438 | fsap->f_bused = VFSATTR_IS_SUPPORTED(&attr, f_bused) ? attr.f_bused : 0; | |
439 | fsap->f_files = VFSATTR_IS_SUPPORTED(&attr, f_files) ? attr.f_files : 0; | |
440 | } else { | |
441 | fsap->f_blocks = 0; | |
442 | fsap->f_bused = 0; | |
443 | fsap->f_files = 0; | |
1c79356b A |
444 | } |
445 | ||
91447636 A |
446 | VFSATTR_INIT(&attr); |
447 | VFSATTR_WANTED(&attr, f_bsize); | |
448 | VFSATTR_WANTED(&attr, f_blocks); | |
449 | VFSATTR_WANTED(&attr, f_bfree); | |
450 | VFSATTR_WANTED(&attr, f_bavail); | |
451 | VFSATTR_WANTED(&attr, f_files); | |
452 | VFSATTR_WANTED(&attr, f_ffree); | |
453 | error = vfs_getattr(um->um_uppervp->v_mount, &attr, context); | |
1c79356b A |
454 | if (error) |
455 | return (error); | |
456 | ||
91447636 A |
457 | if (VFSATTR_IS_SUPPORTED(&attr, f_bsize)) { |
458 | fsap->f_bsize = attr.f_bsize; | |
459 | VFSATTR_SET_SUPPORTED(fsap, f_bsize); | |
460 | } | |
461 | if (VFSATTR_IS_SUPPORTED(&attr, f_iosize)) { | |
462 | fsap->f_iosize = attr.f_iosize; | |
463 | VFSATTR_SET_SUPPORTED(fsap, f_iosize); | |
464 | } | |
1c79356b A |
465 | |
466 | /* | |
467 | * if the lower and upper blocksizes differ, then frig the | |
468 | * block counts so that the sizes reported by df make some | |
469 | * kind of sense. none of this makes sense though. | |
470 | */ | |
91447636 A |
471 | if (VFSATTR_IS_SUPPORTED(&attr, f_bsize)) |
472 | fsap->f_bsize = attr.f_bsize; | |
473 | else | |
474 | fsap->f_bsize = um->um_uppervp->v_mount->mnt_devblocksize; | |
475 | VFSATTR_RETURN(fsap, f_bsize, attr.f_bsize); | |
476 | if (fsap->f_bsize != lbsize) | |
477 | fsap->f_blocks = fsap->f_blocks * lbsize / attr.f_bsize; | |
1c79356b A |
478 | |
479 | /* | |
480 | * The "total" fields count total resources in all layers, | |
481 | * the "free" fields count only those resources which are | |
482 | * free in the upper layer (since only the upper layer | |
483 | * is writeable). | |
484 | */ | |
91447636 A |
485 | if (VFSATTR_IS_SUPPORTED(&attr, f_blocks)) |
486 | fsap->f_blocks += attr.f_blocks; | |
487 | if (VFSATTR_IS_SUPPORTED(&attr, f_bfree)) | |
488 | fsap->f_bfree = attr.f_bfree; | |
489 | if (VFSATTR_IS_SUPPORTED(&attr, f_bavail)) | |
490 | fsap->f_bavail = attr.f_bavail; | |
491 | if (VFSATTR_IS_SUPPORTED(&attr, f_bused)) | |
492 | fsap->f_bused += attr.f_bused; | |
493 | if (VFSATTR_IS_SUPPORTED(&attr, f_files)) | |
494 | fsap->f_files += attr.f_files; | |
495 | if (VFSATTR_IS_SUPPORTED(&attr, f_ffree)) | |
496 | fsap->f_ffree = attr.f_ffree; | |
497 | ||
498 | VFSATTR_SET_SUPPORTED(fsap, f_bsize); | |
499 | VFSATTR_SET_SUPPORTED(fsap, f_blocks); | |
500 | VFSATTR_SET_SUPPORTED(fsap, f_bfree); | |
501 | VFSATTR_SET_SUPPORTED(fsap, f_bavail); | |
502 | VFSATTR_SET_SUPPORTED(fsap, f_bused); | |
503 | VFSATTR_SET_SUPPORTED(fsap, f_files); | |
504 | VFSATTR_SET_SUPPORTED(fsap, f_ffree); | |
505 | ||
1c79356b A |
506 | return (0); |
507 | } | |
508 | ||
509 | /* | |
510 | * XXX - Assumes no data cached at union layer. | |
511 | */ | |
91447636 A |
512 | #define union_sync (int (*) (mount_t, int, ucred_t, vfs_context_t))nullop |
513 | ||
514 | #define union_fhtovp (int (*) (mount_t, int, unsigned char *, vnode_t *, vfs_context_t))eopnotsupp | |
515 | int union_init (struct vfsconf *); | |
516 | #define union_sysctl (int (*) (int *, u_int, user_addr_t, size_t *, user_addr_t, size_t, vfs_context_t))eopnotsupp | |
517 | #define union_vget (int (*) (mount_t, ino64_t, vnode_t *, vfs_context_t))eopnotsupp | |
518 | #define union_vptofh (int (*) (vnode_t, int *, unsigned char *, vfs_context_t))eopnotsupp | |
1c79356b A |
519 | |
520 | struct vfsops union_vfsops = { | |
521 | union_mount, | |
522 | union_start, | |
523 | union_unmount, | |
524 | union_root, | |
91447636 A |
525 | NULL, /* quotactl */ |
526 | union_vfs_getattr, | |
1c79356b A |
527 | union_sync, |
528 | union_vget, | |
529 | union_fhtovp, | |
530 | union_vptofh, | |
531 | union_init, | |
91447636 | 532 | union_sysctl |
1c79356b | 533 | }; |