int union_root(struct mount *mp, struct vnode **vpp) { struct union_mount *um = MOUNTTOUNIONMOUNT(mp); int error; /* * Return locked reference to root. */ vref(um->um_uppervp); if (um->um_lowervp) vref(um->um_lowervp); error = union_allocvp(vpp, mp, NULL, NULL, NULL, um->um_uppervp, um->um_lowervp, 1); if (error) { vrele(um->um_uppervp); if (um->um_lowervp) vrele(um->um_lowervp); return error; } vn_lock(*vpp, LK_EXCLUSIVE | LK_RETRY); return 0; }
/* * union_create: * * a_dvp is locked on entry and remains locked on return. a_vpp is returned * locked if no error occurs, otherwise it is garbage. * * union_create(struct vnode *a_dvp, struct vnode **a_vpp, * struct componentname *a_cnp, struct vattr *a_vap) */ static int union_create(struct vop_old_create_args *ap) { struct union_node *dun = VTOUNION(ap->a_dvp); struct componentname *cnp = ap->a_cnp; struct thread *td = cnp->cn_td; struct vnode *dvp; int error = EROFS; if ((dvp = union_lock_upper(dun, td)) != NULL) { struct vnode *vp; struct mount *mp; error = VOP_CREATE(dvp, &vp, cnp, ap->a_vap); if (error == 0) { mp = ap->a_dvp->v_mount; vn_unlock(vp); UDEBUG(("ALLOCVP-1 FROM %p REFS %d\n", vp, vp->v_sysref.refcnt)); error = union_allocvp(ap->a_vpp, mp, NULLVP, NULLVP, cnp, vp, NULLVP, 1); UDEBUG(("ALLOCVP-2B FROM %p REFS %d\n", *ap->a_vpp, vp->v_sysref.refcnt)); } union_unlock_upper(dvp, td); } return (error); }
/* * union_mkdir(struct vnode *a_dvp, struct vnode **a_vpp, * struct componentname *a_cnp, struct vattr *a_vap) */ static int union_mkdir(struct vop_old_mkdir_args *ap) { struct union_node *dun = VTOUNION(ap->a_dvp); struct componentname *cnp = ap->a_cnp; struct thread *td = cnp->cn_td; struct vnode *upperdvp; int error = EROFS; if ((upperdvp = union_lock_upper(dun, td)) != NULLVP) { struct vnode *vp; error = VOP_MKDIR(upperdvp, &vp, cnp, ap->a_vap); union_unlock_upper(upperdvp, td); if (error == 0) { vn_unlock(vp); UDEBUG(("ALLOCVP-2 FROM %p REFS %d\n", vp, vp->v_sysref.refcnt)); error = union_allocvp(ap->a_vpp, ap->a_dvp->v_mount, ap->a_dvp, NULLVP, cnp, vp, NULLVP, 1); UDEBUG(("ALLOCVP-2B FROM %p REFS %d\n", *ap->a_vpp, vp->v_sysref.refcnt)); } } return (error); }
/* * union_lookup(struct vnode *a_dvp, struct vnode **a_vpp, * struct componentname *a_cnp) */ static int union_lookup(struct vop_old_lookup_args *ap) { int error; int uerror, lerror; struct vnode *uppervp, *lowervp; struct vnode *upperdvp, *lowerdvp; struct vnode *dvp = ap->a_dvp; /* starting dir */ struct union_node *dun = VTOUNION(dvp); /* associated union node */ struct componentname *cnp = ap->a_cnp; struct thread *td = cnp->cn_td; int lockparent = cnp->cn_flags & CNP_LOCKPARENT; struct union_mount *um = MOUNTTOUNIONMOUNT(dvp->v_mount); struct ucred *saved_cred = NULL; int iswhiteout; struct vattr va; *ap->a_vpp = NULLVP; /* * Disallow write attemps to the filesystem mounted read-only. */ if ((dvp->v_mount->mnt_flag & MNT_RDONLY) && (cnp->cn_nameiop == NAMEI_DELETE || cnp->cn_nameiop == NAMEI_RENAME)) { return (EROFS); } /* * For any lookup's we do, always return with the parent locked */ cnp->cn_flags |= CNP_LOCKPARENT; lowerdvp = dun->un_lowervp; uppervp = NULLVP; lowervp = NULLVP; iswhiteout = 0; uerror = ENOENT; lerror = ENOENT; /* * Get a private lock on uppervp and a reference, effectively * taking it out of the union_node's control. * * We must lock upperdvp while holding our lock on dvp * to avoid a deadlock. */ upperdvp = union_lock_upper(dun, td); /* * 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. */ if (upperdvp != NULLVP) { /* * We do not have to worry about the DOTDOT case, we've * already unlocked dvp. */ UDEBUG(("A %p\n", upperdvp)); /* * Do the lookup. We must supply a locked and referenced * upperdvp to the function and will get a new locked and * referenced upperdvp back with the old having been * dereferenced. * * If an error is returned, uppervp will be NULLVP. If no * error occurs, uppervp will be the locked and referenced * return vnode or possibly NULL, depending on what is being * requested. It is possible that the returned uppervp * will be the same as upperdvp. */ uerror = union_lookup1(um->um_uppervp, &upperdvp, &uppervp, cnp); UDEBUG(( "uerror %d upperdvp %p %d/%d, uppervp %p ref=%d/lck=%d\n", uerror, upperdvp, upperdvp->v_sysref.refcnt, vn_islocked(upperdvp), uppervp, (uppervp ? uppervp->v_sysref.refcnt : -99), (uppervp ? vn_islocked(uppervp) : -99) )); /* * Disallow write attemps to the filesystem mounted read-only. */ if (uerror == EJUSTRETURN && (dvp->v_mount->mnt_flag & MNT_RDONLY) && (cnp->cn_nameiop == NAMEI_CREATE || cnp->cn_nameiop == NAMEI_RENAME)) { error = EROFS; goto out; } /* * Special case. If cn_consume != 0 skip out. The result * of the lookup is transfered to our return variable. If * an error occured we have to throw away the results. */ if (cnp->cn_consume != 0) { if ((error = uerror) == 0) { *ap->a_vpp = uppervp; uppervp = NULL; } goto out; } /* * Calculate whiteout, fall through */ if (uerror == ENOENT || uerror == EJUSTRETURN) { if (cnp->cn_flags & CNP_ISWHITEOUT) { iswhiteout = 1; } else if (lowerdvp != NULLVP) { int terror; terror = VOP_GETATTR(upperdvp, &va); if (terror == 0 && (va.va_flags & OPAQUE)) iswhiteout = 1; } } } /* * 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 * instead. */ if (lowerdvp != NULLVP && !iswhiteout) { int nameiop; UDEBUG(("B %p\n", lowerdvp)); /* * Force only LOOKUPs on the lower node, since * we won't be making changes to it anyway. */ nameiop = cnp->cn_nameiop; cnp->cn_nameiop = NAMEI_LOOKUP; if (um->um_op == UNMNT_BELOW) { saved_cred = cnp->cn_cred; cnp->cn_cred = um->um_cred; } /* * We shouldn't have to worry about locking interactions * between the lower layer and our union layer (w.r.t. * `..' processing) because we don't futz with lowervp * locks in the union-node instantiation code path. * * union_lookup1() requires lowervp to be locked on entry, * and it will be unlocked on return. The ref count will * not change. On return lowervp doesn't represent anything * to us so we NULL it out. */ vref(lowerdvp); vn_lock(lowerdvp, LK_EXCLUSIVE | LK_RETRY); lerror = union_lookup1(um->um_lowervp, &lowerdvp, &lowervp, cnp); if (lowerdvp == lowervp) vrele(lowerdvp); else vput(lowerdvp); lowerdvp = NULL; /* lowerdvp invalid after vput */ if (um->um_op == UNMNT_BELOW) cnp->cn_cred = saved_cred; cnp->cn_nameiop = nameiop; if (cnp->cn_consume != 0 || lerror == EACCES) { if ((error = lerror) == 0) { *ap->a_vpp = lowervp; lowervp = NULL; } goto out; } } else { UDEBUG(("C %p\n", lowerdvp)); if ((cnp->cn_flags & CNP_ISDOTDOT) && dun->un_pvp != NULLVP) { if ((lowervp = LOWERVP(dun->un_pvp)) != NULL) { vref(lowervp); vn_lock(lowervp, LK_EXCLUSIVE | LK_RETRY); lerror = 0; } } } /* * Ok. Now we have uerror, uppervp, upperdvp, lerror, and lowervp. * * 1. If both layers returned an error, select the upper layer. * * 2. If the upper layer faile and the bottom layer succeeded, * 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 button vnode is a directory, in which case * create a new directory in the top layer and * and fall through to case 3. * * 3. If the top layer succeeded then return a new union * vnode referencing whatever the new top layer and * whatever the bottom layer returned. */ /* case 1. */ if ((uerror != 0) && (lerror != 0)) { error = uerror; goto out; } /* case 2. */ if (uerror != 0 /* && (lerror == 0) */ ) { if (lowervp->v_type == VDIR) { /* case 2b. */ KASSERT(uppervp == NULL, ("uppervp unexpectedly non-NULL")); /* * oops, uppervp has a problem, we may have to shadow. */ uerror = union_mkshadow(um, upperdvp, cnp, &uppervp); if (uerror) { error = uerror; goto out; } } } /* * Must call union_allocvp with both the upper and lower vnodes * referenced and the upper vnode locked. ap->a_vpp is returned * referenced and locked. lowervp, uppervp, and upperdvp are * absorbed by union_allocvp() whether it succeeds or fails. * * upperdvp is the parent directory of uppervp which may be * different, depending on the path, from dvp->un_uppervp. That's * why it is a separate argument. Note that it must be unlocked. * * dvp must be locked on entry to the call and will be locked on * return. */ if (uppervp && uppervp != upperdvp) vn_unlock(uppervp); if (lowervp) vn_unlock(lowervp); if (upperdvp) vn_unlock(upperdvp); error = union_allocvp(ap->a_vpp, dvp->v_mount, dvp, upperdvp, cnp, uppervp, lowervp, 1); UDEBUG(("Create %p = %p %p refs=%d\n", *ap->a_vpp, uppervp, lowervp, (*ap->a_vpp) ? ((*ap->a_vpp)->v_sysref.refcnt) : -99)); uppervp = NULL; upperdvp = NULL; lowervp = NULL; /* * Termination Code * * - put away any extra junk laying around. Note that lowervp * (if not NULL) will never be the same as *ap->a_vp and * neither will uppervp, because when we set that state we * NULL-out lowervp or uppervp. On the otherhand, upperdvp * may match uppervp or *ap->a_vpp. * * - relock/unlock dvp if appropriate. */ out: if (upperdvp) { if (upperdvp == uppervp || upperdvp == *ap->a_vpp) vrele(upperdvp); else vput(upperdvp); } if (uppervp) vput(uppervp); if (lowervp) vput(lowervp); /* * Restore LOCKPARENT state */ if (!lockparent) cnp->cn_flags &= ~CNP_LOCKPARENT; UDEBUG(("Out %d vpp %p/%d lower %p upper %p\n", error, *ap->a_vpp, ((*ap->a_vpp) ? (*ap->a_vpp)->v_sysref.refcnt : -99), lowervp, uppervp)); /* * dvp lock state, determine whether to relock dvp. dvp is expected * to be locked on return if: * * - there was an error (except not EJUSTRETURN), or * - we hit the last component and lockparent is true * * dvp_is_locked is the current state of the dvp lock, not counting * the possibility that *ap->a_vpp == dvp (in which case it is locked * anyway). Note that *ap->a_vpp == dvp only if no error occured. */ if (*ap->a_vpp != dvp) { if ((error == 0 || error == EJUSTRETURN) && !lockparent) { vn_unlock(dvp); } } /* * Diagnostics */ #ifdef DIAGNOSTIC if (cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.' && *ap->a_vpp != dvp) { panic("union_lookup returning . (%p) not same as startdir (%p)", ap->a_vpp, dvp); } #endif return (error); }