* Copyright (c) 1992, 1993, 1994 The Regents of the University of California.
* Copyright (c) 1992, 1993, 1994 Jan-Simon Pendry.
* This code is derived from software contributed to Berkeley by
* %sccs.include.redist.c%
* @(#)union_vnops.c 1.5 (Berkeley) %G%
#include <sys/filedesc.h>
* Create a shadow directory in the upper layer.
* The new vnode is returned locked.
union_mkshadow(dvp
, cnp
, vpp
)
struct componentname
*cnp
;
struct proc
*p
= cnp
->cn_proc
;
* policy: when creating the shadow directory in the
* upper layer, create it owned by the current user,
* group from parent directory, and mode 777 modified
* by umask (ie mostly identical to the mkdir syscall).
* TODO: create the directory owned by the user who
* did the mount (um->um_cred).
* A new componentname structure must be faked up because
* there is no way to know where the upper level cnp came
* from or what it is being used for. This must duplicate
* some of the work done by NDINIT, some of the work done
* by namei, some of the work done by lookup and some of
* the work done by VOP_LOOKUP when given a CREATE flag.
* The pathname buffer will be FREEed by VOP_MKDIR.
cn
.cn_pnbuf
= malloc(cnp
->cn_namelen
+1, M_NAMEI
, M_WAITOK
);
bcopy(cnp
->cn_nameptr
, cn
.cn_pnbuf
, cnp
->cn_namelen
+1);
cn
.cn_flags
= HASBUF
| SAVENAME
| ISLASTCN
;
cn
.cn_proc
= cnp
->cn_proc
;
cn
.cn_cred
= cnp
->cn_cred
;
cn
.cn_nameptr
= cn
.cn_pnbuf
;
cn
.cn_namelen
= cnp
->cn_namelen
;
cn
.cn_hash
= cnp
->cn_hash
;
cn
.cn_consume
= cnp
->cn_consume
;
va
.va_mode
= UN_DIRMODE
&~ p
->p_fd
->fd_cmask
;
/* LEASE_CHECK: dvp is locked */
LEASE_CHECK(dvp
, p
, p
->p_ucred
, LEASE_WRITE
);
error
= VOP_MKDIR(dvp
, vpp
, &cn
, &va
);
union_lookup1(udvp
, dvp
, vpp
, cnp
)
struct componentname
*cnp
;
* If stepping up the directory tree, check for going
* back across the mount point, in which case do what
* lookup would do by stepping back down the mount
if (cnp
->cn_flags
& ISDOTDOT
) {
if ((dvp
->v_flag
& VROOT
) == 0 ||
(cnp
->cn_flags
& NOCROSSMOUNT
))
dvp
= dvp
->v_mount
->mnt_vnodecovered
;
error
= VOP_LOOKUP(dvp
, &tdvp
, cnp
);
* If going back up the directory tree, then the parent directory
* will have been unlocked, unless lookup found the last
* component. In which case, re-lock the node here to allow
* it to be unlocked again (phew) in union_lookup.
if ((cnp
->cn_flags
& ISDOTDOT
) && !(cnp
->cn_flags
& ISLASTCN
))
* Lastly check if the current node is a mount point in
* which cse walk up the mount hierarchy making sure not to
* bump into the root of the mount tree (ie. dvp != udvp).
while (dvp
!= udvp
&& (dvp
->v_type
== VDIR
) &&
(mp
= dvp
->v_mountedhere
) &&
(cnp
->cn_flags
& NOCROSSMOUNT
) == 0) {
if (mp
->mnt_flag
& MNT_MLOCK
) {
mp
->mnt_flag
|= MNT_MWAIT
;
sleep((caddr_t
) mp
, PVFS
);
if (error
= VFS_ROOT(mp
, &tdvp
)) {
struct vop_lookup_args
/* {
struct vnodeop_desc *a_desc;
struct componentname *a_cnp;
struct vnode
*uppervp
, *lowervp
;
struct vnode
*upperdvp
, *lowerdvp
;
struct vnode
*dvp
= ap
->a_dvp
;
struct union_node
*dun
= VTOUNION(dvp
);
struct componentname
*cnp
= ap
->a_cnp
;
int lockparent
= cnp
->cn_flags
& LOCKPARENT
;
int rdonly
= cnp
->cn_flags
& RDONLY
;
cnp
->cn_flags
|= LOCKPARENT
;
upperdvp
= dun
->un_uppervp
;
lowerdvp
= dun
->un_lowervp
;
* do the lookup in the upper level.
* if that level comsumes additional pathnames,
* then assume that something special is going
* on and just return that vnode.
MOUNTTOUNIONMOUNT(dvp
->v_mount
)->um_uppervp
,
upperdvp
, &uppervp
, cnp
);
if (cnp
->cn_consume
!= 0) {
cnp
->cn_flags
&= ~LOCKPARENT
;
* in a similar way to the upper layer, do the lookup
* in the lower layer. this time, if there is some
* component magic going on, then vput whatever we got
* back from the upper layer and return the lower vnode
MOUNTTOUNIONMOUNT(dvp
->v_mount
)->um_lowervp
,
lowerdvp
, &lowervp
, cnp
);
if (cnp
->cn_consume
!= 0) {
cnp
->cn_flags
&= ~LOCKPARENT
;
cnp
->cn_flags
&= ~LOCKPARENT
;
* at this point, we have uerror and lerror indicating
* possible errors with the lookups in the upper and lower
* layers. additionally, uppervp and lowervp are (locked)
* references to existing vnodes in the upper and lower layers.
* there are now three cases to consider.
* 1. if both layers returned an error, then return whatever
* error the upper layer generated.
* 2. if the top layer failed and the bottom layer succeeded
* then two subcases occur.
* a. the bottom vnode is not a directory, in which
* case just return a new union vnode referencing
* an empty top layer and the existing bottom layer.
* b. the bottom vnode is a directory, in which case
* create a new directory in the top-level and
* 3. if the top layer succeeded then return a new union
* vnode referencing whatever the new top layer and
* whatever the bottom layer returned.
if ((uerror
!= 0) && (lerror
!= 0)) {
if (uerror
!= 0 /* && (lerror == 0) */ ) {
if (lowervp
->v_type
== VDIR
) { /* case 2b. */
uerror
= union_mkshadow(upperdvp
, cnp
, &uppervp
);
error
= union_allocvp(ap
->a_vpp
, dvp
->v_mount
, dvp
, upperdvp
, cnp
,
if (!lockparent
|| !(cnp
->cn_flags
& ISLASTCN
))
struct vop_create_args
/* {
struct componentname *a_cnp;
struct union_node
*un
= VTOUNION(ap
->a_dvp
);
struct vnode
*dvp
= un
->un_uppervp
;
error
= VOP_CREATE(dvp
, &vp
, ap
->a_cnp
, ap
->a_vap
);
struct vop_mknod_args
/* {
struct componentname *a_cnp;
struct union_node
*un
= VTOUNION(ap
->a_dvp
);
struct vnode
*dvp
= un
->un_uppervp
;
error
= VOP_MKNOD(dvp
, &vp
, ap
->a_cnp
, ap
->a_vap
);
struct vop_open_args
/* {
struct vnodeop_desc *a_desc;
struct union_node
*un
= VTOUNION(ap
->a_vp
);
struct ucred
*cred
= ap
->a_cred
;
struct proc
*p
= ap
->a_p
;
* If there is an existing upper vp then simply open that.
* If the lower vnode is being opened for writing, then
* copy the file contents to the upper vnode and open that,
* otherwise can simply open the lower vnode.
if ((ap
->a_mode
& FWRITE
) && (tvp
->v_type
== VREG
)) {
struct filedesc
*fdp
= p
->p_fd
;
* Open the named file in the upper layer. Note that
* the file may have come into existence *since* the
* lookup was done, since the upper layer may really
* be a loopback mount of some other filesystem...
* so open the file with exclusive create and barf if
* XXX - perhaps shoudl re-lookup the node (once more
* with feeling) and simply open that. Who knows.
NDINIT(&nd, CREATE, 0, UIO_SYSSPACE, un->un_path, p);
fmode = (O_CREAT|O_TRUNC|O_EXCL);
cmode
= UN_FILEMODE
& ~fdp
->fd_cmask
;
error
= union_vn_create(&vp
, un
, cmode
, p
);
un
->un_uppervp
= vp
; /* XXX */
/* at this point, uppervp is locked */
* Now, if the file is being opened with truncation,
* then the (new) upper vnode is ready to fly,
* otherwise the data from the lower vnode must be
* copied to the upper layer first. This only works
* for regular files (check is made above).
if ((mode
& O_TRUNC
) == 0) {
* XXX - should not ignore errors
error
= VOP_OPEN(tvp
, FREAD
, cred
, p
);
error
= union_copyfile(p
, cred
,
(void) VOP_CLOSE(tvp
, FREAD
);
VOP_UNLOCK(un
->un_uppervp
);
(void) VOP_CLOSE(un
->un_uppervp
, FWRITE
);
VOP_LOCK(un
->un_uppervp
);
error
= VOP_OPEN(un
->un_uppervp
, mode
, cred
, p
);
VOP_UNLOCK(un
->un_uppervp
);
error
= VOP_OPEN(tvp
, mode
, cred
, p
);
struct vop_close_args
/* {
return (VOP_CLOSE(OTHERVP(ap
->a_vp
), ap
->a_fflag
, ap
->a_cred
, ap
->a_p
));
* Check access permission on the union vnode.
* The access check being enforced is to check
* against both the underlying vnode, and any
* copied vnode. This ensures that no additional
* file permissions are given away simply because
* the user caused an implicit file copy.
struct vop_access_args
/* {
struct vnodeop_desc *a_desc;
struct union_node
*un
= VTOUNION(ap
->a_vp
);
if (vp
= un
->un_lowervp
) {
error
= VOP_ACCESS(vp
, ap
->a_mode
, ap
->a_cred
, ap
->a_p
);
if (vp
= un
->un_uppervp
) {
error
= VOP_ACCESS(vp
, ap
->a_mode
, ap
->a_cred
, ap
->a_p
);
* We handle getattr only to change the fsid.
struct vop_getattr_args
/* {
struct vnode
*vp
= OTHERVP(ap
->a_vp
);
error
= VOP_GETATTR(vp
, ap
->a_vap
, ap
->a_cred
, ap
->a_p
);
/* Requires that arguments be restored. */
ap
->a_vap
->va_fsid
= ap
->a_vp
->v_mount
->mnt_stat
.f_fsid
.val
[0];
struct vop_setattr_args
/* {
struct union_node
*un
= VTOUNION(ap
->a_vp
);
VOP_LOCK(un
->un_uppervp
);
error
= VOP_SETATTR(un
->un_uppervp
, ap
->a_vap
,
VOP_UNLOCK(un
->un_uppervp
);
* XXX should do a copyfile (perhaps only if
* the file permission change, which would not
* track va_ctime correctly).
struct vop_read_args
/* {
struct vnode
*vp
= OTHERVP(ap
->a_vp
);
error
= VOP_READ(vp
, ap
->a_uio
, ap
->a_ioflag
, ap
->a_cred
);
struct vop_read_args
/* {
struct vnode
*vp
= OTHERVP(ap
->a_vp
);
error
= VOP_WRITE(vp
, ap
->a_uio
, ap
->a_ioflag
, ap
->a_cred
);
struct vop_ioctl_args
/* {
return (VOP_IOCTL(OTHERVP(ap
->a_vp
), ap
->a_command
, ap
->a_data
,
ap
->a_fflag
, ap
->a_cred
, ap
->a_p
));
struct vop_select_args
/* {
return (VOP_SELECT(OTHERVP(ap
->a_vp
), ap
->a_which
, ap
->a_fflags
,
struct vop_mmap_args
/* {
return (VOP_MMAP(OTHERVP(ap
->a_vp
), ap
->a_fflags
,
struct vop_fsync_args
/* {
struct vnode
*targetvp
= OTHERVP(ap
->a_vp
);
error
= VOP_FSYNC(targetvp
, ap
->a_cred
,
struct vop_seek_args
/* {
return (VOP_SEEK(OTHERVP(ap
->a_vp
), ap
->a_oldoff
, ap
->a_newoff
, ap
->a_cred
));
struct vop_remove_args
/* {
struct componentname *a_cnp;
struct union_node
*dun
= VTOUNION(ap
->a_dvp
);
struct union_node
*un
= VTOUNION(ap
->a_vp
);
if (dun
->un_uppervp
&& un
->un_uppervp
) {
struct vnode
*dvp
= dun
->un_uppervp
;
struct vnode
*vp
= un
->un_uppervp
;
error
= VOP_REMOVE(dvp
, vp
, ap
->a_cnp
);
* XXX: should create a whiteout here
struct vop_link_args
/* {
struct componentname *a_cnp;
struct union_node
*dun
= VTOUNION(ap
->a_vp
);
struct union_node
*un
= VTOUNION(ap
->a_tdvp
);
if (dun
->un_uppervp
&& un
->un_uppervp
) {
struct vnode
*dvp
= dun
->un_uppervp
;
struct vnode
*vp
= un
->un_uppervp
;
error
= VOP_LINK(dvp
, vp
, ap
->a_cnp
);
* XXX: need to copy to upper layer
struct vop_rename_args
/* {
struct componentname *a_fcnp;
struct componentname *a_tcnp;
struct vnode
*fdvp
= ap
->a_fdvp
;
struct vnode
*fvp
= ap
->a_fvp
;
struct vnode
*tdvp
= ap
->a_tdvp
;
struct vnode
*tvp
= ap
->a_tvp
;
if (fdvp
->v_op
== union_vnodeop_p
) { /* always true */
struct union_node
*un
= VTOUNION(fdvp
);
if (un
->un_uppervp
== 0) {
if (fvp
->v_op
== union_vnodeop_p
) { /* always true */
struct union_node
*un
= VTOUNION(fvp
);
if (un
->un_uppervp
== 0) {
if (tdvp
->v_op
== union_vnodeop_p
) {
struct union_node
*un
= VTOUNION(tdvp
);
if (un
->un_uppervp
== 0) {
if (tvp
&& tvp
->v_op
== union_vnodeop_p
) {
struct union_node
*un
= VTOUNION(tvp
);
if (un
->un_uppervp
== 0) {
return (VOP_RENAME(fdvp
, fvp
, ap
->a_fcnp
, tdvp
, tvp
, ap
->a_tcnp
));
struct vop_mkdir_args
/* {
struct componentname *a_cnp;
struct union_node
*un
= VTOUNION(ap
->a_dvp
);
struct vnode
*dvp
= un
->un_uppervp
;
error
= VOP_MKDIR(dvp
, &vp
, ap
->a_cnp
, ap
->a_vap
);
struct vop_rmdir_args
/* {
struct componentname *a_cnp;
struct union_node
*dun
= VTOUNION(ap
->a_dvp
);
struct union_node
*un
= VTOUNION(ap
->a_vp
);
if (dun
->un_uppervp
&& un
->un_uppervp
) {
struct vnode
*dvp
= dun
->un_uppervp
;
struct vnode
*vp
= un
->un_uppervp
;
error
= VOP_REMOVE(dvp
, vp
, ap
->a_cnp
);
* XXX: should create a whiteout here
struct vop_symlink_args
/* {
struct componentname *a_cnp;
struct union_node
*un
= VTOUNION(ap
->a_dvp
);
struct vnode
*dvp
= un
->un_uppervp
;
struct mount
*mp
= ap
->a_dvp
->v_mount
;
error
= VOP_SYMLINK(dvp
, &vp
, ap
->a_cnp
,
ap
->a_vap
, ap
->a_target
);
* union_readdir works in concert with getdirentries and
* readdir(3) to provide a list of entries in the unioned
* directories. getdirentries is responsible for walking
* down the union stack. readdir(3) is responsible for
* eliminating duplicate names from the returned data stream.
struct vop_readdir_args
/* {
struct vnodeop_desc *a_desc;
struct union_node
*un
= VTOUNION(ap
->a_vp
);
struct vnode
*vp
= OTHERVP(ap
->a_vp
);
error
= VOP_READLINK(vp
, ap
->a_uio
, ap
->a_cred
);
struct vop_readlink_args
/* {
struct vnode
*vp
= OTHERVP(ap
->a_vp
);
error
= VOP_READLINK(vp
, ap
->a_uio
, ap
->a_cred
);
struct vop_abortop_args
/* {
struct componentname *a_cnp;
struct vnode
*vp
= OTHERVP(ap
->a_dvp
);
struct union_node
*un
= VTOUNION(ap
->a_dvp
);
int islocked
= un
->un_flags
& UN_LOCKED
;
error
= VOP_ABORTOP(vp
, ap
->a_cnp
);
struct vop_inactive_args
/* {
* Do nothing (and _don't_ bypass).
* Wait to vrele lowervp until reclaim,
* so that until then our union_node is in the
* NEEDSWORK: Someday, consider inactive'ing
* the lowervp and then trying to reactivate it
* with capabilities (v_id)
* like they do in the name lookup cache code.
* That's too much work for now.
struct union_node
*un
= VTOUNION(ap
->a_vp
);
if (un
->un_flags
& UN_LOCKED
)
panic("union: inactivating locked node");
struct vop_reclaim_args
/* {
struct vnode
*vp
= ap
->a_vp
;
struct union_node
*un
= VTOUNION(vp
);
struct vnode
*uppervp
= un
->un_uppervp
;
struct vnode
*lowervp
= un
->un_lowervp
;
struct vnode
*dirvp
= un
->un_dirvp
;
char *path
= un
->un_path
;
* Note: in vop_reclaim, vp->v_op == dead_vnodeop_p,
* so we can't call VOPs on ourself.
/* After this assignment, this node will not be re-used. */
struct vop_lock_args
*ap
;
struct union_node
*un
= VTOUNION(ap
->a_vp
);
while (un
->un_flags
& UN_LOCKED
) {
if (curproc
&& un
->un_pid
== curproc
->p_pid
&&
un
->un_pid
> -1 && curproc
->p_pid
> -1)
panic("union: locking against myself");
sleep((caddr_t
) &un
->un_flags
, PINOD
);
un
->un_flags
|= UN_LOCKED
;
un
->un_pid
= curproc
->p_pid
;
struct vop_lock_args
*ap
;
struct union_node
*un
= VTOUNION(ap
->a_vp
);
if ((un
->un_flags
& UN_LOCKED
) == 0)
panic("union: unlock unlocked node");
if (curproc
&& un
->un_pid
!= curproc
->p_pid
&&
curproc
->p_pid
> -1 && un
->un_pid
> -1)
panic("union: unlocking other process's union node");
un
->un_flags
&= ~UN_LOCKED
;
if (un
->un_flags
& UN_WANT
) {
un
->un_flags
&= ~UN_WANT
;
wakeup((caddr_t
) &un
->un_flags
);
struct vop_bmap_args
/* {
struct vnode
*vp
= OTHERVP(ap
->a_vp
);
error
= VOP_BMAP(vp
, ap
->a_bn
, ap
->a_vpp
, ap
->a_bnp
, ap
->a_runp
);
struct vop_print_args
/* {
struct vnode
*vp
= ap
->a_vp
;
printf("\ttag VT_UNION, vp=%x, uppervp=%x, lowervp=%x\n",
vp
, UPPERVP(vp
), LOWERVP(vp
));
struct vop_islocked_args
/* {
return ((VTOUNION(ap
->a_vp
)->un_flags
& UN_LOCKED
) ? 1 : 0);
struct vop_pathconf_args
/* {
struct vnode
*vp
= OTHERVP(ap
->a_vp
);
error
= VOP_PATHCONF(vp
, ap
->a_name
, ap
->a_retval
);
struct vop_advlock_args
/* {
return (VOP_ADVLOCK(OTHERVP(ap
->a_vp
), ap
->a_id
, ap
->a_op
,
* XXX - vop_strategy must be hand coded because it has no
* vnode in its arguments.
* This goes away with a merged VM/buffer cache.
struct vop_strategy_args
/* {
struct buf
*bp
= ap
->a_bp
;
bp
->b_vp
= OTHERVP(bp
->b_vp
);
panic("union_strategy: nil vp");
if (((bp
->b_flags
& B_READ
) == 0) &&
(bp
->b_vp
== LOWERVP(savedvp
)))
panic("union_strategy: writing to lowervp");
error
= VOP_STRATEGY(bp
);
* Global vfs data structures
int (**union_vnodeop_p
)();
struct vnodeopv_entry_desc union_vnodeop_entries
[] = {
{ &vop_default_desc
, vn_default_error
},
{ &vop_lookup_desc
, union_lookup
}, /* lookup */
{ &vop_create_desc
, union_create
}, /* create */
{ &vop_mknod_desc
, union_mknod
}, /* mknod */
{ &vop_open_desc
, union_open
}, /* open */
{ &vop_close_desc
, union_close
}, /* close */
{ &vop_access_desc
, union_access
}, /* access */
{ &vop_getattr_desc
, union_getattr
}, /* getattr */
{ &vop_setattr_desc
, union_setattr
}, /* setattr */
{ &vop_read_desc
, union_read
}, /* read */
{ &vop_write_desc
, union_write
}, /* write */
{ &vop_ioctl_desc
, union_ioctl
}, /* ioctl */
{ &vop_select_desc
, union_select
}, /* select */
{ &vop_mmap_desc
, union_mmap
}, /* mmap */
{ &vop_fsync_desc
, union_fsync
}, /* fsync */
{ &vop_seek_desc
, union_seek
}, /* seek */
{ &vop_remove_desc
, union_remove
}, /* remove */
{ &vop_link_desc
, union_link
}, /* link */
{ &vop_rename_desc
, union_rename
}, /* rename */
{ &vop_mkdir_desc
, union_mkdir
}, /* mkdir */
{ &vop_rmdir_desc
, union_rmdir
}, /* rmdir */
{ &vop_symlink_desc
, union_symlink
}, /* symlink */
{ &vop_readdir_desc
, union_readdir
}, /* readdir */
{ &vop_readlink_desc
, union_readlink
}, /* readlink */
{ &vop_abortop_desc
, union_abortop
}, /* abortop */
{ &vop_inactive_desc
, union_inactive
}, /* inactive */
{ &vop_reclaim_desc
, union_reclaim
}, /* reclaim */
{ &vop_lock_desc
, union_lock
}, /* lock */
{ &vop_unlock_desc
, union_unlock
}, /* unlock */
{ &vop_bmap_desc
, union_bmap
}, /* bmap */
{ &vop_strategy_desc
, union_strategy
}, /* strategy */
{ &vop_print_desc
, union_print
}, /* print */
{ &vop_islocked_desc
, union_islocked
}, /* islocked */
{ &vop_pathconf_desc
, union_pathconf
}, /* pathconf */
{ &vop_advlock_desc
, union_advlock
}, /* advlock */
{ &vop_blkatoff_desc
, union_blkatoff
}, /* blkatoff */
{ &vop_valloc_desc
, union_valloc
}, /* valloc */
{ &vop_vfree_desc
, union_vfree
}, /* vfree */
{ &vop_truncate_desc
, union_truncate
}, /* truncate */
{ &vop_update_desc
, union_update
}, /* update */
{ &vop_bwrite_desc
, union_bwrite
}, /* bwrite */
{ (struct vnodeop_desc
*)NULL
, (int(*)())NULL
}
struct vnodeopv_desc union_vnodeop_opv_desc
=
{ &union_vnodeop_p
, union_vnodeop_entries
};