msdosfs: fix rename

(cherry picked from commit 95d42526e9)
This commit is contained in:
Konstantin Belousov 2021-08-01 20:46:59 +03:00
parent 7b81dd6f3d
commit 2c9cbc2d45
4 changed files with 276 additions and 243 deletions

View file

@ -267,7 +267,7 @@ int msdosfs_lookup(struct vop_cachedlookup_args *);
int msdosfs_inactive(struct vop_inactive_args *);
int msdosfs_reclaim(struct vop_reclaim_args *);
int msdosfs_lookup_ino(struct vnode *vdp, struct vnode **vpp,
struct componentname *cnp, uint64_t *inum);
struct componentname *cnp, daddr_t *scnp, u_long *blkoffp);
#endif
/*
@ -286,6 +286,7 @@ int createde(struct denode *dep, struct denode *ddep, struct denode **depp, stru
int deupdat(struct denode *dep, int waitfor);
int removede(struct denode *pdep, struct denode *dep);
int detrunc(struct denode *dep, u_long length, int flags, struct ucred *cred);
int doscheckpath( struct denode *source, struct denode *target);
int doscheckpath( struct denode *source, struct denode *target,
daddr_t *wait_scn);
#endif /* _KERNEL || MAKEFS */
#endif /* !_FS_MSDOSFS_DENODE_H_ */

View file

@ -166,6 +166,7 @@ deget(struct msdosfsmount *pmp, u_long dirclust, u_long diroffset,
ldep->de_diroffset = diroffset;
ldep->de_inode = inode;
lockmgr(nvp->v_vnlock, LK_EXCLUSIVE, NULL);
VN_LOCK_AREC(nvp); /* for doscheckpath */
fc_purge(ldep, 0); /* init the FAT cache for this denode */
error = insmntque(nvp, mntp);
if (error != 0) {

View file

@ -67,7 +67,8 @@ int
msdosfs_lookup(struct vop_cachedlookup_args *ap)
{
return (msdosfs_lookup_ino(ap->a_dvp, ap->a_vpp, ap->a_cnp, NULL));
return (msdosfs_lookup_ino(ap->a_dvp, ap->a_vpp, ap->a_cnp, NULL,
NULL));
}
struct deget_dotdot {
@ -109,8 +110,8 @@ msdosfs_deget_dotdot(struct mount *mp, void *arg, int lkflags,
* memory denode's will be in synch.
*/
int
msdosfs_lookup_ino(struct vnode *vdp, struct vnode **vpp,
struct componentname *cnp, uint64_t *dd_inum)
msdosfs_lookup_ino(struct vnode *vdp, struct vnode **vpp, struct componentname
*cnp, daddr_t *scnp, u_long *blkoffp)
{
struct mbnambuf nb;
daddr_t bn;
@ -119,11 +120,11 @@ msdosfs_lookup_ino(struct vnode *vdp, struct vnode **vpp,
int slotoffset = 0;
int frcn;
u_long cluster;
int blkoff;
u_long blkoff;
int diroff;
int blsize;
int isadir; /* ~0 if found direntry is a directory */
u_long scn; /* starting cluster number */
daddr_t scn; /* starting cluster number */
struct vnode *pdp;
struct denode *dp;
struct denode *tdp;
@ -464,8 +465,9 @@ foundroot:
if (FAT32(pmp) && scn == MSDOSFSROOT)
scn = pmp->pm_rootdirblk;
if (dd_inum != NULL) {
*dd_inum = (uint64_t)pmp->pm_bpcluster * scn + blkoff;
if (scnp != NULL) {
*scnp = cluster;
*blkoffp = blkoff;
return (0);
}
@ -557,12 +559,15 @@ foundroot:
* Recheck that ".." still points to the inode we
* looked up before pdp lock was dropped.
*/
error = msdosfs_lookup_ino(pdp, NULL, cnp, &inode1);
error = msdosfs_lookup_ino(pdp, NULL, cnp, &scn, &blkoff);
if (error) {
vput(*vpp);
*vpp = NULL;
return (error);
}
if (FAT32(pmp) && scn == MSDOSFSROOT)
scn = pmp->pm_rootdirblk;
inode1 = scn * pmp->pm_bpcluster + blkoff;
if (VTODE(*vpp)->de_inode != inode1) {
vput(*vpp);
goto restart;
@ -794,10 +799,9 @@ dosdirempty(struct denode *dep)
*
* Returns 0 if target is NOT a subdirectory of source.
* Otherwise returns a non-zero error number.
* The target inode is always unlocked on return.
*/
int
doscheckpath(struct denode *source, struct denode *target)
doscheckpath(struct denode *source, struct denode *target, daddr_t *wait_scn)
{
daddr_t scn;
struct msdosfsmount *pmp;
@ -806,26 +810,25 @@ doscheckpath(struct denode *source, struct denode *target)
struct buf *bp = NULL;
int error = 0;
dep = target;
if ((target->de_Attributes & ATTR_DIRECTORY) == 0 ||
(source->de_Attributes & ATTR_DIRECTORY) == 0) {
error = ENOTDIR;
goto out;
}
if (dep->de_StartCluster == source->de_StartCluster) {
error = EEXIST;
goto out;
}
if (dep->de_StartCluster == MSDOSFSROOT)
goto out;
pmp = dep->de_pmp;
#ifdef DIAGNOSTIC
if (pmp != source->de_pmp)
panic("doscheckpath: source and target on different filesystems");
#endif
if (FAT32(pmp) && dep->de_StartCluster == pmp->pm_rootdirblk)
goto out;
*wait_scn = 0;
pmp = target->de_pmp;
KASSERT(pmp == source->de_pmp,
("doscheckpath: source and target on different filesystems"));
if ((target->de_Attributes & ATTR_DIRECTORY) == 0 ||
(source->de_Attributes & ATTR_DIRECTORY) == 0)
return (ENOTDIR);
if (target->de_StartCluster == source->de_StartCluster)
return (EEXIST);
if (target->de_StartCluster == MSDOSFSROOT ||
(FAT32(pmp) && target->de_StartCluster == pmp->pm_rootdirblk))
return (0);
dep = target;
vget(DETOV(dep), LK_EXCLUSIVE);
for (;;) {
if ((dep->de_Attributes & ATTR_DIRECTORY) == 0) {
error = ENOTDIR;
@ -833,19 +836,22 @@ doscheckpath(struct denode *source, struct denode *target)
}
scn = dep->de_StartCluster;
error = bread(pmp->pm_devvp, cntobn(pmp, scn),
pmp->pm_bpcluster, NOCRED, &bp);
if (error)
pmp->pm_bpcluster, NOCRED, &bp);
if (error != 0)
break;
ep = (struct direntry *) bp->b_data + 1;
ep = (struct direntry *)bp->b_data + 1;
if ((ep->deAttributes & ATTR_DIRECTORY) == 0 ||
bcmp(ep->deName, ".. ", 11) != 0) {
error = ENOTDIR;
brelse(bp);
break;
}
scn = getushort(ep->deStartCluster);
if (FAT32(pmp))
scn |= getushort(ep->deHighClust) << 16;
brelse(bp);
if (scn == source->de_StartCluster) {
error = EINVAL;
@ -862,15 +868,14 @@ doscheckpath(struct denode *source, struct denode *target)
}
vput(DETOV(dep));
brelse(bp);
bp = NULL;
dep = NULL;
/* NOTE: deget() clears dep on error */
if ((error = deget(pmp, scn, 0, LK_EXCLUSIVE, &dep)) != 0)
error = deget(pmp, scn, 0, LK_EXCLUSIVE | LK_NOWAIT, &dep);
if (error != 0) {
*wait_scn = scn;
break;
}
}
out:;
if (bp)
brelse(bp);
#ifdef MSDOSFS_DEBUG
if (error == ENOTDIR)
printf("doscheckpath(): .. not a directory?\n");

View file

@ -937,24 +937,27 @@ msdosfs_link(struct vop_link_args *ap)
static int
msdosfs_rename(struct vop_rename_args *ap)
{
struct vnode *tdvp = ap->a_tdvp;
struct vnode *fvp = ap->a_fvp;
struct vnode *fdvp = ap->a_fdvp;
struct vnode *tvp = ap->a_tvp;
struct componentname *tcnp = ap->a_tcnp;
struct componentname *fcnp = ap->a_fcnp;
struct denode *ip, *xp, *dp, *zp;
struct vnode *fdvp, *fvp, *tdvp, *tvp, *vp;
struct componentname *fcnp, *tcnp;
struct denode *fdip, *fip, *tdip, *tip, *nip;
u_char toname[12], oldname[11];
u_long from_diroffset, to_diroffset;
bool doingdirectory, newparent;
u_char to_count;
int doingdirectory = 0, newparent = 0;
int error;
u_long cn, pcl;
daddr_t bn;
u_long cn, pcl, blkoff;
daddr_t bn, wait_scn, scn;
struct msdosfsmount *pmp;
struct mount *mp;
struct direntry *dotdotp;
struct buf *bp;
tdvp = ap->a_tdvp;
fvp = ap->a_fvp;
fdvp = ap->a_fdvp;
tvp = ap->a_tvp;
tcnp = ap->a_tcnp;
fcnp = ap->a_fcnp;
pmp = VFSTOMSDOSFS(fdvp->v_mount);
#ifdef DIAGNOSTIC
@ -965,19 +968,11 @@ msdosfs_rename(struct vop_rename_args *ap)
/*
* Check for cross-device rename.
*/
mp = fvp->v_mount;
if (fvp->v_mount != tdvp->v_mount ||
(tvp && fvp->v_mount != tvp->v_mount)) {
(tvp != NULL && fvp->v_mount != tvp->v_mount)) {
error = EXDEV;
abortit:
if (tdvp == tvp)
vrele(tdvp);
else
vput(tdvp);
if (tvp)
vput(tvp);
vrele(fdvp);
vrele(fvp);
return (error);
goto abortit;
}
/*
@ -988,11 +983,99 @@ abortit:
goto abortit;
}
error = vn_lock(fvp, LK_EXCLUSIVE);
if (error)
goto abortit;
dp = VTODE(fdvp);
ip = VTODE(fvp);
/*
* When the target exists, both the directory
* and target vnodes are passed locked.
*/
VOP_UNLOCK(tdvp);
if (tvp != NULL && tvp != tdvp)
VOP_UNLOCK(tvp);
relock:
doingdirectory = newparent = false;
error = vn_lock(fdvp, LK_EXCLUSIVE);
if (error != 0)
goto releout;
if (vn_lock(tdvp, LK_EXCLUSIVE | LK_NOWAIT) != 0) {
VOP_UNLOCK(fdvp);
error = vn_lock(tdvp, LK_EXCLUSIVE);
if (error != 0)
goto releout;
VOP_UNLOCK(tdvp);
goto relock;
}
error = msdosfs_lookup_ino(fdvp, NULL, fcnp, &scn, &blkoff);
if (error != 0) {
VOP_UNLOCK(fdvp);
VOP_UNLOCK(tdvp);
goto releout;
}
error = deget(pmp, scn, blkoff, LK_EXCLUSIVE | LK_NOWAIT, &nip);
if (error != 0) {
VOP_UNLOCK(fdvp);
VOP_UNLOCK(tdvp);
if (error != EBUSY)
goto releout;
error = deget(pmp, scn, blkoff, LK_EXCLUSIVE, &nip);
if (error != 0)
goto releout;
vp = fvp;
fvp = DETOV(nip);
VOP_UNLOCK(fvp);
vrele(vp);
goto relock;
}
vrele(fvp);
fvp = DETOV(nip);
from_diroffset = fdip->de_fndoffset;
error = msdosfs_lookup_ino(tdvp, NULL, tcnp, &scn, &blkoff);
if (error != 0 && error != EJUSTRETURN) {
VOP_UNLOCK(fdvp);
VOP_UNLOCK(tdvp);
VOP_UNLOCK(fvp);
goto releout;
}
if (error == EJUSTRETURN && tvp != NULL) {
vrele(tvp);
tvp = NULL;
}
if (error == 0) {
nip = NULL;
error = deget(pmp, scn, blkoff, LK_EXCLUSIVE | LK_NOWAIT,
&nip);
if (tvp != NULL) {
vrele(tvp);
tvp = NULL;
}
if (error != 0) {
VOP_UNLOCK(fdvp);
VOP_UNLOCK(tdvp);
VOP_UNLOCK(fvp);
if (error != EBUSY)
goto releout;
error = deget(pmp, scn, blkoff, LK_EXCLUSIVE,
&nip);
if (error != 0)
goto releout;
vput(DETOV(nip));
goto relock;
}
tvp = DETOV(nip);
}
fdip = VTODE(fdvp);
fip = VTODE(fvp);
tdip = VTODE(tdvp);
tip = tvp != NULL ? VTODE(tvp) : NULL;
/*
* Remember direntry place to use for destination
*/
to_diroffset = tdip->de_fndoffset;
to_count = tdip->de_fndcnt;
/*
* Be sure we are not renaming ".", "..", or an alias of ".". This
@ -1000,35 +1083,20 @@ abortit:
* "ls" or "pwd" with the "." directory entry missing, and "cd .."
* doesn't work if the ".." entry is missing.
*/
if (ip->de_Attributes & ATTR_DIRECTORY) {
if ((fip->de_Attributes & ATTR_DIRECTORY) != 0) {
/*
* Avoid ".", "..", and aliases of "." for obvious reasons.
*/
if ((fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.') ||
dp == ip ||
(fcnp->cn_flags & ISDOTDOT) ||
(tcnp->cn_flags & ISDOTDOT) ||
(ip->de_flag & DE_RENAME)) {
VOP_UNLOCK(fvp);
fdip == fip ||
(fcnp->cn_flags & ISDOTDOT) != 0 ||
(tcnp->cn_flags & ISDOTDOT) != 0) {
error = EINVAL;
goto abortit;
goto unlock;
}
ip->de_flag |= DE_RENAME;
doingdirectory++;
doingdirectory = true;
}
/*
* When the target exists, both the directory
* and target vnodes are returned locked.
*/
dp = VTODE(tdvp);
xp = tvp ? VTODE(tvp) : NULL;
/*
* Remember direntry place to use for destination
*/
to_diroffset = dp->de_fndoffset;
to_count = dp->de_fndcnt;
/*
* If ".." must be changed (ie the directory gets a new
* parent) then the source directory must not be in the
@ -1040,55 +1108,59 @@ abortit:
* call to doscheckpath().
*/
error = VOP_ACCESS(fvp, VWRITE, tcnp->cn_cred, tcnp->cn_thread);
VOP_UNLOCK(fvp);
if (VTODE(fdvp)->de_StartCluster != VTODE(tdvp)->de_StartCluster)
newparent = 1;
if (fdip->de_StartCluster != tdip->de_StartCluster)
newparent = true;
if (doingdirectory && newparent) {
if (error) /* write access check above */
goto bad;
if (xp != NULL)
vput(tvp);
/*
* doscheckpath() vput()'s dp,
* so we have to do a relookup afterwards
*/
error = doscheckpath(ip, dp);
if (error)
goto out;
if (error != 0) /* write access check above */
goto unlock;
error = doscheckpath(fip, tdip, &wait_scn);
if (wait_scn != 0) {
VOP_UNLOCK(fdvp);
VOP_UNLOCK(tdvp);
VOP_UNLOCK(fvp);
if (tvp != NULL && tvp != tdvp)
VOP_UNLOCK(tvp);
error = deget(pmp, wait_scn, 0, LK_EXCLUSIVE,
&nip);
if (error == 0) {
vput(DETOV(nip));
goto relock;
}
}
if (error != 0)
goto unlock;
if ((tcnp->cn_flags & SAVESTART) == 0)
panic("msdosfs_rename: lost to startdir");
error = relookup(tdvp, &tvp, tcnp);
if (error)
goto out;
dp = VTODE(tdvp);
xp = tvp ? VTODE(tvp) : NULL;
}
if (xp != NULL) {
if (tip != NULL) {
/*
* Target must be empty if a directory and have no links
* to it. Also, ensure source and target are compatible
* (both directories, or both not directories).
*/
if (xp->de_Attributes & ATTR_DIRECTORY) {
if (!dosdirempty(xp)) {
if ((tip->de_Attributes & ATTR_DIRECTORY) != 0) {
if (!dosdirempty(tip)) {
error = ENOTEMPTY;
goto bad;
goto unlock;
}
if (!doingdirectory) {
error = ENOTDIR;
goto bad;
goto unlock;
}
cache_purge(tdvp);
} else if (doingdirectory) {
error = EISDIR;
goto bad;
goto unlock;
}
error = removede(dp, xp);
if (error)
goto bad;
error = msdosfs_lookup_ino(tdvp, NULL, tcnp, &scn, &blkoff);
MPASS(error == 0);
error = removede(tdip, tip);
if (error != 0)
goto unlock;
vput(tvp);
xp = NULL;
tvp = NULL;
tip = NULL;
}
/*
@ -1096,146 +1168,83 @@ abortit:
* into the denode and directory entry for the destination
* file/directory.
*/
error = uniqdosname(VTODE(tdvp), tcnp, toname);
if (error)
goto abortit;
error = uniqdosname(tdip, tcnp, toname);
if (error != 0)
goto unlock;
/*
* Since from wasn't locked at various places above,
* have to do a relookup here.
* First write a new entry in the destination
* directory and mark the entry in the source directory
* as deleted. Then move the denode to the correct hash
* chain for its new location in the filesystem. And, if
* we moved a directory, then update its .. entry to point
* to the new parent directory.
*/
fcnp->cn_flags &= ~MODMASK;
fcnp->cn_flags |= LOCKPARENT | LOCKLEAF;
if ((fcnp->cn_flags & SAVESTART) == 0)
panic("msdosfs_rename: lost from startdir");
if (!newparent)
VOP_UNLOCK(tdvp);
if (relookup(fdvp, &fvp, fcnp) == 0)
vrele(fdvp);
if (fvp == NULL) {
/*
* From name has disappeared.
*/
if (doingdirectory)
panic("rename: lost dir entry");
if (newparent)
VOP_UNLOCK(tdvp);
vrele(tdvp);
vrele(ap->a_fvp);
/*
* fdvp may be locked and has a reference. We need to
* release the lock and reference, unless to and from
* directories are the same. In that case it is already
* unlocked.
*/
if (tdvp != fdvp)
vput(fdvp);
return 0;
memcpy(oldname, fip->de_Name, 11);
memcpy(fip->de_Name, toname, 11); /* update denode */
error = msdosfs_lookup_ino(tdvp, NULL, tcnp, &scn, &blkoff);
MPASS(error == EJUSTRETURN);
error = createde(fip, tdip, NULL, tcnp);
if (error != 0) {
memcpy(fip->de_Name, oldname, 11);
goto unlock;
}
xp = VTODE(fvp);
zp = VTODE(fdvp);
from_diroffset = zp->de_fndoffset;
/*
* Ensure that the directory entry still exists and has not
* changed till now. If the source is a file the entry may
* have been unlinked or renamed. In either case there is
* no further work to be done. If the source is a directory
* then it cannot have been rmdir'ed or renamed; this is
* prohibited by the DE_RENAME flag.
* If fip is for a directory, then its name should always
* be "." since it is for the directory entry in the
* directory itself (msdosfs_lookup() always translates
* to the "." entry so as to get a unique denode, except
* for the root directory there are different
* complications). However, we just corrupted its name
* to pass the correct name to createde(). Undo this.
*/
if (xp != ip) {
if (doingdirectory)
panic("rename: lost dir entry");
if (newparent)
VOP_UNLOCK(fdvp);
vrele(ap->a_fvp);
xp = NULL;
} else {
vrele(fvp);
xp = NULL;
/*
* First write a new entry in the destination
* directory and mark the entry in the source directory
* as deleted. Then move the denode to the correct hash
* chain for its new location in the filesystem. And, if
* we moved a directory, then update its .. entry to point
* to the new parent directory.
*/
memcpy(oldname, ip->de_Name, 11);
memcpy(ip->de_Name, toname, 11); /* update denode */
dp->de_fndoffset = to_diroffset;
dp->de_fndcnt = to_count;
error = createde(ip, dp, (struct denode **)0, tcnp);
if (error) {
memcpy(ip->de_Name, oldname, 11);
if (newparent)
VOP_UNLOCK(fdvp);
VOP_UNLOCK(fvp);
goto bad;
}
/*
* If ip is for a directory, then its name should always
* be "." since it is for the directory entry in the
* directory itself (msdosfs_lookup() always translates
* to the "." entry so as to get a unique denode, except
* for the root directory there are different
* complications). However, we just corrupted its name
* to pass the correct name to createde(). Undo this.
*/
if ((ip->de_Attributes & ATTR_DIRECTORY) != 0)
memcpy(ip->de_Name, oldname, 11);
ip->de_refcnt++;
zp->de_fndoffset = from_diroffset;
error = removede(zp, ip);
if (error) {
/* XXX should downgrade to ro here, fs is corrupt */
if (newparent)
VOP_UNLOCK(fdvp);
VOP_UNLOCK(fvp);
goto bad;
}
if (!doingdirectory) {
error = pcbmap(dp, de_cluster(pmp, to_diroffset), 0,
&ip->de_dirclust, 0);
if (error) {
/* XXX should downgrade to ro here, fs is corrupt */
if (newparent)
VOP_UNLOCK(fdvp);
VOP_UNLOCK(fvp);
goto bad;
}
if (ip->de_dirclust == MSDOSFSROOT)
ip->de_diroffset = to_diroffset;
else
ip->de_diroffset = to_diroffset & pmp->pm_crbomask;
}
reinsert(ip);
if (newparent)
VOP_UNLOCK(fdvp);
if ((fip->de_Attributes & ATTR_DIRECTORY) != 0)
memcpy(fip->de_Name, oldname, 11);
fip->de_refcnt++;
error = msdosfs_lookup_ino(fdvp, NULL, fcnp, &scn, &blkoff);
MPASS(error == 0);
error = removede(fdip, fip);
if (error != 0) {
/* XXX should downgrade to ro here, fs is corrupt */
goto unlock;
}
if (!doingdirectory) {
error = pcbmap(tdip, de_cluster(pmp, to_diroffset), 0,
&fip->de_dirclust, 0);
if (error != 0) {
/*
* XXX should downgrade to ro here,
* fs is corrupt
*/
goto unlock;
}
if (fip->de_dirclust == MSDOSFSROOT)
fip->de_diroffset = to_diroffset;
else
fip->de_diroffset = to_diroffset & pmp->pm_crbomask;
}
reinsert(fip);
/*
* If we moved a directory to a new parent directory, then we must
* fixup the ".." entry in the moved directory.
*/
if (doingdirectory && newparent) {
cn = ip->de_StartCluster;
cn = fip->de_StartCluster;
if (cn == MSDOSFSROOT) {
/* this should never happen */
panic("msdosfs_rename(): updating .. in root directory?");
} else
bn = cntobn(pmp, cn);
error = bread(pmp->pm_devvp, bn, pmp->pm_bpcluster,
NOCRED, &bp);
if (error) {
NOCRED, &bp);
if (error != 0) {
/* XXX should downgrade to ro here, fs is corrupt */
VOP_UNLOCK(fvp);
goto bad;
goto unlock;
}
dotdotp = (struct direntry *)bp->b_data + 1;
pcl = dp->de_StartCluster;
pcl = tdip->de_StartCluster;
if (FAT32(pmp) && pcl == pmp->pm_rootdirblk)
pcl = MSDOSFSROOT;
putushort(dotdotp->deStartCluster, pcl);
@ -1245,8 +1254,7 @@ abortit:
bdwrite(bp);
else if ((error = bwrite(bp)) != 0) {
/* XXX should downgrade to ro here, fs is corrupt */
VOP_UNLOCK(fvp);
goto bad;
goto unlock;
}
}
@ -1258,17 +1266,35 @@ abortit:
* namecache entries that were installed for this direntry.
*/
cache_purge(fvp);
VOP_UNLOCK(fvp);
bad:
if (xp)
vput(tvp);
unlock:
vput(fdvp);
vput(fvp);
if (tvp != NULL) {
if (tvp != tdvp)
vput(tvp);
else
vrele(tvp);
}
vput(tdvp);
out:
ip->de_flag &= ~DE_RENAME;
return (error);
releout:
vrele(tdvp);
if (tvp != NULL)
vrele(tvp);
vrele(fdvp);
vrele(fvp);
return (error);
abortit:
if (tdvp == tvp)
vrele(tdvp);
else
vput(tdvp);
if (tvp != NULL)
vput(tvp);
vrele(fdvp);
vrele(fvp);
return (error);
}
static struct {
@ -1428,7 +1454,7 @@ msdosfs_rmdir(struct vop_rmdir_args *ap)
* non-empty.)
*/
error = 0;
if (!dosdirempty(ip) || ip->de_flag & DE_RENAME) {
if (!dosdirempty(ip)) {
error = ENOTEMPTY;
goto out;
}