/* * Unlink zp from dl, and mark zp for reaping if this was the last link. * Can fail if zp is a mount point (EBUSY) or a non-empty directory (EEXIST). * If 'reaped_ptr' is NULL, we put reaped znodes on the delete queue. * If it's non-NULL, we use it to indicate whether the znode needs reaping, * and it's the caller's job to do it. */ int zfs_link_destroy(zfs_dirlock_t *dl, znode_t *zp, dmu_tx_t *tx, int flag, int *reaped_ptr) { znode_t *dzp = dl->dl_dzp; vnode_t *vp = ZTOV(zp); int zp_is_dir = (vp->v_type == VDIR); int reaped = 0; int error; dnlc_remove(ZTOV(dzp), dl->dl_name); if (!(flag & ZRENAMING)) { dmu_buf_will_dirty(zp->z_dbuf, tx); if (vn_vfswlock(vp)) /* prevent new mounts on zp */ return (EBUSY); if (vn_ismntpt(vp)) { /* don't remove mount point */ vn_vfsunlock(vp); return (EBUSY); } mutex_enter(&zp->z_lock); if (zp_is_dir && !zfs_dirempty(zp)) { /* dir not empty */ mutex_exit(&zp->z_lock); vn_vfsunlock(vp); return (EEXIST); } ASSERT(zp->z_phys->zp_links > zp_is_dir); if (--zp->z_phys->zp_links == zp_is_dir) { zp->z_reap = 1; zp->z_phys->zp_links = 0; reaped = 1; } else { zfs_time_stamper_locked(zp, STATE_CHANGED, tx); } mutex_exit(&zp->z_lock); vn_vfsunlock(vp); } dmu_buf_will_dirty(dzp->z_dbuf, tx); mutex_enter(&dzp->z_lock); dzp->z_phys->zp_size--; /* one dirent removed */ dzp->z_phys->zp_links -= zp_is_dir; /* ".." link from zp */ zfs_time_stamper_locked(dzp, CONTENT_MODIFIED, tx); mutex_exit(&dzp->z_lock); error = zap_remove(zp->z_zfsvfs->z_os, dzp->z_id, dl->dl_name, tx); ASSERT(error == 0); if (reaped_ptr != NULL) *reaped_ptr = reaped; else if (reaped) zfs_dq_add(zp, tx); return (0); }
/* * ------ samqfs_notify_dnlc_remove - * Look for a dnlc/dcache entry of pip/comp * and delete it from the dnlc/dcache. */ void samqfs_notify_dnlc_remove( sam_node_t *pip, /* parent inode */ char *comp) /* name to be deleted from the dcache */ { #ifdef sun (void) dnlc_remove(SAM_ITOV(pip), comp); #endif /* sun */ #ifdef linux struct inode *pli; int length; struct dentry *dir, *de; struct qstr qstr; if (pip == NULL) { return; } if (comp == NULL) { return; } pli = SAM_SITOLI(pip); length = strlen(comp); if ((length == 0) || (length > NAME_MAX)) { return; } /* * Find the dcache entry of the parent. */ dir = d_find_alias(pli); if (dir) { /* * Find the dcache entry of the component. */ qstr.name = comp; qstr.len = length; qstr.hash = rfs_full_name_hash(comp, length); de = d_lookup(dir, &qstr); if (de) { rfs_d_drop(de); dput(de); } dput(dir); } #endif /* linux */ }
/* * Unlink zp from dl, and mark zp for deletion if this was the last link. * Can fail if zp is a mount point (EBUSY) or a non-empty directory (EEXIST). * If 'unlinkedp' is NULL, we put unlinked znodes on the unlinked list. * If it's non-NULL, we use it to indicate whether the znode needs deletion, * and it's the caller's job to do it. */ int zfs_link_destroy(zfs_dirlock_t *dl, znode_t *zp, dmu_tx_t *tx, int flag, boolean_t *unlinkedp) { znode_t *dzp = dl->dl_dzp; zfsvfs_t *zfsvfs = dzp->z_zfsvfs; vnode_t *vp = ZTOV(zp); int zp_is_dir = (vp->v_type == VDIR); boolean_t unlinked = B_FALSE; sa_bulk_attr_t bulk[5]; uint64_t mtime[2], ctime[2]; int count = 0; int error; dnlc_remove(ZTOV(dzp), dl->dl_name); if (!(flag & ZRENAMING)) { #ifdef HAVE_ZPL if (vn_vfswlock(vp)) /* prevent new mounts on zp */ return (EBUSY); if (vn_ismntpt(vp)) { /* don't remove mount point */ vn_vfsunlock(vp); return (EBUSY); } #endif mutex_enter(&zp->z_lock); if (zp_is_dir && !zfs_dirempty(zp)) { mutex_exit(&zp->z_lock); #ifdef HAVE_ZPL vn_vfsunlock(vp); #endif return (EEXIST); } /* * If we get here, we are going to try to remove the object. * First try removing the name from the directory; if that * fails, return the error. */ error = zfs_dropname(dl, zp, dzp, tx, flag); if (error != 0) { mutex_exit(&zp->z_lock); #ifdef HAVE_ZPL vn_vfsunlock(vp); #endif return (error); } if (zp->z_links <= zp_is_dir) { #ifdef HAVE_ZPL zfs_panic_recover("zfs: link count on %s is %u, " "should be at least %u", zp->z_vnode->v_path ? zp->z_vnode->v_path : "<unknown>", (int)zp->z_links, zp_is_dir + 1); #endif zp->z_links = zp_is_dir + 1; } if (--zp->z_links == zp_is_dir) { zp->z_unlinked = B_TRUE; zp->z_links = 0; unlinked = B_TRUE; } else { SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), NULL, &ctime, sizeof (ctime)); SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), NULL, &zp->z_pflags, sizeof (zp->z_pflags)); zfs_tstamp_update_setup(zp, STATE_CHANGED, mtime, ctime, B_TRUE); } SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_LINKS(zfsvfs), NULL, &zp->z_links, sizeof (zp->z_links)); error = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx); count = 0; ASSERT(error == 0); mutex_exit(&zp->z_lock); #ifdef HAVE_ZPL vn_vfsunlock(vp); #endif } else { error = zfs_dropname(dl, zp, dzp, tx, flag); if (error != 0) return (error); } mutex_enter(&dzp->z_lock); dzp->z_size--; /* one dirent removed */ dzp->z_links -= zp_is_dir; /* ".." link from zp */ SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_LINKS(zfsvfs), NULL, &dzp->z_links, sizeof (dzp->z_links)); SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_SIZE(zfsvfs), NULL, &dzp->z_size, sizeof (dzp->z_size)); SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), NULL, ctime, sizeof (ctime)); SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MTIME(zfsvfs), NULL, mtime, sizeof (mtime)); SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), NULL, &dzp->z_pflags, sizeof (dzp->z_pflags)); zfs_tstamp_update_setup(dzp, CONTENT_MODIFIED, mtime, ctime, B_TRUE); error = sa_bulk_update(dzp->z_sa_hdl, bulk, count, tx); ASSERT(error == 0); mutex_exit(&dzp->z_lock); if (unlinkedp != NULL) *unlinkedp = unlinked; else if (unlinked) zfs_unlinked_add(zp, tx); return (0); }
/* * Unlink zp from dl, and mark zp for deletion if this was the last link. Can * fail if zp is a mount point (EBUSY) or a non-empty directory (ENOTEMPTY). * If 'unlinkedp' is NULL, we put unlinked znodes on the unlinked list. * If it's non-NULL, we use it to indicate whether the znode needs deletion, * and it's the caller's job to do it. */ int zfs_link_destroy(zfs_dirlock_t *dl, znode_t *zp, dmu_tx_t *tx, int flag, boolean_t *unlinkedp) { znode_t *dzp = dl->dl_dzp; zfsvfs_t *zfsvfs = ZTOZSB(dzp); int zp_is_dir = S_ISDIR(ZTOI(zp)->i_mode); boolean_t unlinked = B_FALSE; sa_bulk_attr_t bulk[5]; uint64_t mtime[2], ctime[2]; uint64_t links; int count = 0; int error; #ifdef HAVE_DNLC dnlc_remove(ZTOI(dzp), dl->dl_name); #endif /* HAVE_DNLC */ if (!(flag & ZRENAMING)) { mutex_enter(&zp->z_lock); if (zp_is_dir && !zfs_dirempty(zp)) { mutex_exit(&zp->z_lock); return (SET_ERROR(ENOTEMPTY)); } /* * If we get here, we are going to try to remove the object. * First try removing the name from the directory; if that * fails, return the error. */ error = zfs_dropname(dl, zp, dzp, tx, flag); if (error != 0) { mutex_exit(&zp->z_lock); return (error); } if (ZTOI(zp)->i_nlink <= zp_is_dir) { zfs_panic_recover("zfs: link count on %lu is %u, " "should be at least %u", zp->z_id, (int)ZTOI(zp)->i_nlink, zp_is_dir + 1); set_nlink(ZTOI(zp), zp_is_dir + 1); } drop_nlink(ZTOI(zp)); if (ZTOI(zp)->i_nlink == zp_is_dir) { zp->z_unlinked = B_TRUE; clear_nlink(ZTOI(zp)); unlinked = B_TRUE; } else { SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), NULL, &ctime, sizeof (ctime)); SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), NULL, &zp->z_pflags, sizeof (zp->z_pflags)); zfs_tstamp_update_setup(zp, STATE_CHANGED, mtime, ctime); } links = ZTOI(zp)->i_nlink; SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_LINKS(zfsvfs), NULL, &links, sizeof (links)); error = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx); count = 0; ASSERT(error == 0); mutex_exit(&zp->z_lock); } else { error = zfs_dropname(dl, zp, dzp, tx, flag); if (error != 0) return (error); } mutex_enter(&dzp->z_lock); dzp->z_size--; /* one dirent removed */ if (zp_is_dir) drop_nlink(ZTOI(dzp)); /* ".." link from zp */ links = ZTOI(dzp)->i_nlink; SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_LINKS(zfsvfs), NULL, &links, sizeof (links)); SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_SIZE(zfsvfs), NULL, &dzp->z_size, sizeof (dzp->z_size)); SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), NULL, ctime, sizeof (ctime)); SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MTIME(zfsvfs), NULL, mtime, sizeof (mtime)); SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), NULL, &dzp->z_pflags, sizeof (dzp->z_pflags)); zfs_tstamp_update_setup(dzp, CONTENT_MODIFIED, mtime, ctime); error = sa_bulk_update(dzp->z_sa_hdl, bulk, count, tx); ASSERT(error == 0); mutex_exit(&dzp->z_lock); if (unlinkedp != NULL) *unlinkedp = unlinked; else if (unlinked) zfs_unlinked_add(zp, tx); return (0); }
/* * Unlink zp from dl, and mark zp for deletion if this was the last link. * Can fail if zp is a mount point (EBUSY) or a non-empty directory (EEXIST). * If 'unlinkedp' is NULL, we put unlinked znodes on the unlinked list. * If it's non-NULL, we use it to indicate whether the znode needs deletion, * and it's the caller's job to do it. */ int zfs_link_destroy(zfs_dirlock_t *dl, znode_t *zp, dmu_tx_t *tx, int flag, boolean_t *unlinkedp) { znode_t *dzp = dl->dl_dzp; vnode_t *vp = ZTOV(zp); int zp_is_dir = (vp->v_type == VDIR); boolean_t unlinked = B_FALSE; int error; dnlc_remove(ZTOV(dzp), dl->dl_name); if (!(flag & ZRENAMING)) { dmu_buf_will_dirty(zp->z_dbuf, tx); if (vn_vfswlock(vp)) /* prevent new mounts on zp */ return (EBUSY); if (vn_ismntpt(vp)) { /* don't remove mount point */ vn_vfsunlock(vp); return (EBUSY); } mutex_enter(&zp->z_lock); if (zp_is_dir && !zfs_dirempty(zp)) { /* dir not empty */ mutex_exit(&zp->z_lock); vn_vfsunlock(vp); return (ENOTEMPTY); } if (zp->z_phys->zp_links <= zp_is_dir) { zfs_panic_recover("zfs: link count on vnode %p is %u, " "should be at least %u", zp->z_vnode, (int)zp->z_phys->zp_links, zp_is_dir + 1); zp->z_phys->zp_links = zp_is_dir + 1; } if (--zp->z_phys->zp_links == zp_is_dir) { zp->z_unlinked = B_TRUE; zp->z_phys->zp_links = 0; unlinked = B_TRUE; } else { zfs_time_stamper_locked(zp, STATE_CHANGED, tx); } mutex_exit(&zp->z_lock); vn_vfsunlock(vp); } dmu_buf_will_dirty(dzp->z_dbuf, tx); mutex_enter(&dzp->z_lock); dzp->z_phys->zp_size--; /* one dirent removed */ dzp->z_phys->zp_links -= zp_is_dir; /* ".." link from zp */ zfs_time_stamper_locked(dzp, CONTENT_MODIFIED, tx); mutex_exit(&dzp->z_lock); if (zp->z_zfsvfs->z_norm) { if (((zp->z_zfsvfs->z_case == ZFS_CASE_INSENSITIVE) && (flag & ZCIEXACT)) || ((zp->z_zfsvfs->z_case == ZFS_CASE_MIXED) && !(flag & ZCILOOK))) error = zap_remove_norm(zp->z_zfsvfs->z_os, dzp->z_id, dl->dl_name, MT_EXACT, tx); else error = zap_remove_norm(zp->z_zfsvfs->z_os, dzp->z_id, dl->dl_name, MT_FIRST, tx); } else { error = zap_remove(zp->z_zfsvfs->z_os, dzp->z_id, dl->dl_name, tx); } ASSERT(error == 0); if (unlinkedp != NULL) *unlinkedp = unlinked; else if (unlinked) zfs_unlinked_add(zp, tx); return (0); }
/* * Unlink zp from dl, and mark zp for deletion if this was the last link. * Can fail if zp is a mount point (EBUSY) or a non-empty directory (EEXIST). * If 'unlinkedp' is NULL, we put unlinked znodes on the unlinked list. * If it's non-NULL, we use it to indicate whether the znode needs deletion, * and it's the caller's job to do it. */ int zfs_link_destroy(zfs_dirlock_t *dl, znode_t *zp, dmu_tx_t *tx, int flag, boolean_t *unlinkedp) { znode_t *dzp = dl->dl_dzp; vnode_t *vp = ZTOV(zp); #ifdef __APPLE__ int zp_is_dir = S_ISDIR(zp->z_phys->zp_mode); #else int zp_is_dir = (vp->v_type == VDIR); #endif boolean_t unlinked = B_FALSE; int error; #ifndef __APPLE__ dnlc_remove(ZTOV(dzp), dl->dl_name); #endif if (!(flag & ZRENAMING)) { dmu_buf_will_dirty(zp->z_dbuf, tx); #ifdef __APPLE__ if (vp) { #endif /* __APPLE__ */ if (vn_vfswlock(vp)) /* prevent new mounts on zp */ return (EBUSY); if (vn_ismntpt(vp)) { /* don't remove mount point */ vn_vfsunlock(vp); return (EBUSY); } #ifdef __APPLE__ } /* if (vp) */ #endif /* __APPLE__ */ mutex_enter(&zp->z_lock); if (zp_is_dir && !zfs_dirempty(zp)) { /* dir not empty */ mutex_exit(&zp->z_lock); #ifdef __APPLE__ return (ENOTEMPTY); #else vn_vfsunlock(vp); return (EEXIST); #endif } if (zp->z_phys->zp_links <= zp_is_dir) { #ifndef __APPLE__ zfs_panic_recover("zfs: link count on %s is %u, " "should be at least %u", zp->z_vnode->v_path ? zp->z_vnode->v_path : "<unknown>", (int)zp->z_phys->zp_links, zp_is_dir + 1); #endif zp->z_phys->zp_links = zp_is_dir + 1; } if (--zp->z_phys->zp_links == zp_is_dir) { zp->z_unlinked = B_TRUE; zp->z_phys->zp_links = 0; unlinked = B_TRUE; } else { zfs_time_stamper_locked(zp, STATE_CHANGED, tx); } mutex_exit(&zp->z_lock); #ifndef __APPLE__ vn_vfsunlock(vp); #endif } dmu_buf_will_dirty(dzp->z_dbuf, tx); mutex_enter(&dzp->z_lock); dzp->z_phys->zp_size--; /* one dirent removed */ dzp->z_phys->zp_links -= zp_is_dir; /* ".." link from zp */ zfs_time_stamper_locked(dzp, CONTENT_MODIFIED, tx); mutex_exit(&dzp->z_lock); error = zap_remove(zp->z_zfsvfs->z_os, dzp->z_id, dl->dl_name, tx); ASSERT(error == 0); if (unlinkedp != NULL) *unlinkedp = unlinked; else if (unlinked) zfs_unlinked_add(zp, tx); return (0); }
/* * Locking i_contents in this * function seems to be really weird */ int ud_dirremove( struct ud_inode *dp, char *namep, struct ud_inode *oip, struct vnode *cdir, enum dr_op op, struct cred *cr, caller_context_t *ctp) { struct udf_vfs *udf_vfsp; int32_t namelen, err = 0; struct slot slot; struct ud_inode *ip; mode_t mode; struct file_id *fid; uint8_t *buf = NULL; uint32_t tbno; ud_printf("ud_dirremove\n"); ASSERT(RW_WRITE_HELD(&dp->i_rwlock)); udf_vfsp = dp->i_udf; namelen = (int)strlen(namep); if (namelen == 0) { cmn_err(CE_WARN, "name length == 0 in ud_dirremove"); return (EINVAL); } /* * return err when removing . and .. */ if (namep[0] == '.') { if (namelen == 1) { return (EINVAL); } else if (namelen == 2 && namep[1] == '.') { return (EEXIST); /* SIGH should be ENOTEMPTY */ } } ASSERT(RW_WRITE_HELD(&dp->i_rwlock)); /* * Check accessibility of directory. */ if (dp->i_type != VDIR) { return (ENOTDIR); } ip = NULL; slot.status = FOUND; /* don't need to look for empty slot */ slot.offset = 0; slot.size = 0; slot.fbp = NULL; slot.ep = NULL; slot.endoff = 0; /* * Execute access is required to search the directory. * Access for write is interpreted as allowing * deletion of files in the directory. */ if (err = ud_iaccess(dp, IEXEC|IWRITE, cr)) { return (err); } buf = (uint8_t *)kmem_zalloc(udf_vfsp->udf_lbsize, KM_SLEEP); rw_enter(&dp->i_contents, RW_WRITER); if (err = ud_dircheckforname(dp, namep, namelen, &slot, &ip, buf, cr)) { goto out_novfs; } if (ip == NULL) { err = ENOENT; goto out_novfs; } if (oip && oip != ip) { err = ENOENT; goto out_novfs; } if ((mode = ip->i_type) == VDIR) { /* * vn_vfswlock() prevents races between mount and rmdir. */ if (vn_vfswlock(ITOV(ip))) { err = EBUSY; goto out_novfs; } if (vn_mountedvfs(ITOV(ip)) != NULL && op != DR_RENAME) { err = EBUSY; goto out; } /* * If we are removing a directory, get a lock on it. * If the directory is empty, it will stay empty until * we can remove it. */ rw_enter(&ip->i_rwlock, RW_READER); } /* We must be holding i_contents */ rw_enter(&ip->i_contents, RW_READER); if (err = ud_sticky_remove_access(dp, ip, cr)) { rw_exit(&ip->i_contents); if (mode == VDIR) { rw_exit(&ip->i_rwlock); } goto out; } if (op == DR_RMDIR) { /* * For rmdir(2), some special checks are required. * (a) Don't remove any alias of the parent (e.g. "."). * (b) Don't remove the current directory. * (c) Make sure the entry is (still) a directory. * (d) Make sure the directory is empty. */ if (dp == ip || ITOV(ip) == cdir) { err = EINVAL; } else if (ip->i_type != VDIR) { err = ENOTDIR; } else if ((ip->i_nlink != 1) || (!ud_dirempty(ip, dp->i_uniqid, cr))) { /* * Directories do not have an * entry for "." so only one link * will be there */ err = EEXIST; /* SIGH should be ENOTEMPTY */ } if (err) { rw_exit(&ip->i_contents); if (mode == VDIR) { rw_exit(&ip->i_rwlock); } goto out; } } else if (op == DR_REMOVE) { /* * unlink(2) requires a different check: allow only * privileged processes to unlink a directory. */ struct vnode *vp = ITOV(ip); if (vp->v_type == VDIR && secpolicy_fs_linkdir(cr, vp->v_vfsp)) { err = EPERM; rw_exit(&ip->i_contents); rw_exit(&ip->i_rwlock); goto out; } } rw_exit(&ip->i_contents); /* * Remove the cache'd entry, if any. */ dnlc_remove(ITOV(dp), namep); /* * We can collapse all the directory * entries that are deleted into one big entry * but the better way is to * defer it till next directory entry * creation. where we can do this * in a more efficient way */ fid = slot.ep; /* * If this is the last entry * just truncate the file instead * of marking it deleted */ if ((slot.offset + FID_LEN(fid)) == dp->i_size) { fbrelse(slot.fbp, S_OTHER); if ((err = ud_itrunc(dp, slot.offset, 0, cr)) != 0) { goto out; } } else { fid->fid_flags |= FID_DELETED; if ((err = ud_ip_off2bno(dp, slot.offset, &tbno)) != 0) { goto out; } ud_make_tag(dp->i_udf, &fid->fid_tag, UD_FILE_ID_DESC, tbno, FID_LEN(fid)); err = ud_write_fid(dp, &slot, buf); } slot.fbp = NULL; /* * If we were removing a directory, it is 'gone' now so we can * unlock it. */ if (mode == VDIR) { rw_exit(&ip->i_rwlock); } mutex_enter(&dp->i_tlock); dp->i_flag |= IUPD|ICHG; mutex_exit(&dp->i_tlock); mutex_enter(&ip->i_tlock); ip->i_flag |= ICHG; mutex_exit(&ip->i_tlock); if (err != 0) { goto out; } rw_enter(&ip->i_contents, RW_WRITER); /* * Now dispose of the inode. */ if (ip->i_nlink > 0) { if ((op == DR_RMDIR) && (ip->i_type == VDIR)) { /* * Decrement by 1 because there is no "." * Clear the inode, but there may be other hard * links so don't free the inode. * Decrement the dp linkcount because we're * trashing the ".." entry. */ ip->i_nlink --; dp->i_nlink--; dnlc_remove(ITOV(ip), "."); dnlc_remove(ITOV(ip), ".."); /* * (void) ud_itrunc(ip, 0, 0, cr); */ } else { ip->i_nlink--; } } ITIMES_NOLOCK(dp); ITIMES_NOLOCK(ip); rw_exit(&ip->i_contents); out: if (mode == VDIR) { vn_vfsunlock(ITOV(ip)); } out_novfs: ASSERT(RW_WRITE_HELD(&dp->i_contents)); if (slot.fbp != NULL) { fbrelse(slot.fbp, S_OTHER); } rw_exit(&dp->i_contents); if (ip) { /* * If no errors, send any events after locks are dropped, * but before the VN_RELE(). */ if (err == 0) { if (op == DR_REMOVE) { vnevent_remove(ITOV(ip), ITOV(dp), namep, ctp); } else if (op == DR_RMDIR) { vnevent_rmdir(ITOV(ip), ITOV(dp), namep, ctp); } } VN_RELE(ITOV(ip)); } kmem_free(buf, udf_vfsp->udf_lbsize); return (err); }
/* * Fix the FID_PARENT entry of the child directory so that it points * to the new parent directory instead of the old one. Routine * assumes that dp is a directory and that all the inodes are on * the same file system. */ int ud_dirfixdotdot(struct ud_inode *dp, struct ud_inode *opdp, struct ud_inode *npdp) { int32_t err = 0; struct fbuf *fbp; struct file_id *fid; uint32_t loc, dummy, tbno; ud_printf("ud_dirfixdotdot\n"); ASSERT(opdp->i_type == VDIR); ASSERT(npdp->i_type == VDIR); ASSERT(RW_WRITE_HELD(&npdp->i_rwlock)); err = fbread(ITOV(dp), (offset_t)0, dp->i_udf->udf_lbsize, S_WRITE, &fbp); if (err || dp->i_nlink == 0 || dp->i_size < sizeof (struct file_id)) { goto bad; } if ((err = ud_ip_off2bno(dp, 0, &tbno)) != 0) { goto bad; } fid = (struct file_id *)fbp->fb_addr; if ((ud_verify_tag_and_desc(&fid->fid_tag, UD_FILE_ID_DESC, tbno, 1, dp->i_udf->udf_lbsize) != 0) || ((fid->fid_flags & (FID_DIR | FID_PARENT)) != (FID_DIR | FID_PARENT))) { err = ENOTDIR; goto bad; } loc = ud_xlate_to_daddr(dp->i_udf, SWAP_16(fid->fid_icb.lad_ext_prn), SWAP_32(fid->fid_icb.lad_ext_loc), 1, &dummy); ASSERT(dummy == 1); if (loc == npdp->i_icb_lbano) { goto bad; } /* * Increment the link count in the new parent inode and force it out. */ if (npdp->i_nlink == MAXLINK) { err = EMLINK; goto bad; } npdp->i_nlink++; mutex_enter(&npdp->i_tlock); npdp->i_flag |= ICHG; mutex_exit(&npdp->i_tlock); ud_iupdat(npdp, 1); /* * Rewrite the child FID_PARENT entry and force it out. */ dnlc_remove(ITOV(dp), ".."); fid->fid_icb.lad_ext_loc = SWAP_32(npdp->i_icb_block); fid->fid_icb.lad_ext_prn = SWAP_16(npdp->i_icb_prn); ud_make_tag(npdp->i_udf, &fid->fid_tag, UD_FILE_ID_DESC, tbno, FID_LEN(fid)); dnlc_enter(ITOV(dp), "..", ITOV(npdp)); err = ud_fbwrite(fbp, dp); fbp = NULL; if (err != 0) { goto bad; } /* * Decrement the link count of the old parent inode and force * it out. If opdp is NULL, then this is a new directory link; * it has no parent, so we need not do anything. */ if (opdp != NULL) { rw_enter(&opdp->i_contents, RW_WRITER); if (opdp->i_nlink != 0) { opdp->i_nlink--; mutex_enter(&opdp->i_tlock); opdp->i_flag |= ICHG; mutex_exit(&opdp->i_tlock); ud_iupdat(opdp, 1); } rw_exit(&opdp->i_contents); } return (0); bad: if (fbp) { fbrelse(fbp, S_OTHER); } return (err); }
int ud_dirrename(struct ud_inode *sdp, struct ud_inode *sip, struct ud_inode *tdp, struct ud_inode *tip, char *namep, uint8_t *buf, struct slot *slotp, struct cred *cr) { int32_t error = 0, doingdirectory; struct file_id *fid; ud_printf("ud_dirrename\n"); ASSERT(sdp->i_udf != NULL); ASSERT(MUTEX_HELD(&sdp->i_udf->udf_rename_lck)); ASSERT(RW_WRITE_HELD(&tdp->i_rwlock)); ASSERT(buf); ASSERT(slotp->ep); fid = slotp->ep; /* * Short circuit rename of something to itself. */ if (sip->i_icb_lbano == tip->i_icb_lbano) { return (ESAME); /* special KLUDGE error code */ } /* * Everything is protected under the vfs_rename_lock so the ordering * of i_contents locks doesn't matter here. */ rw_enter(&sip->i_contents, RW_READER); rw_enter(&tip->i_contents, RW_READER); /* * Check that everything is on the same filesystem. */ if ((ITOV(tip)->v_vfsp != ITOV(tdp)->v_vfsp) || (ITOV(tip)->v_vfsp != ITOV(sip)->v_vfsp)) { error = EXDEV; /* XXX archaic */ goto out; } /* * Must have write permission to rewrite target entry. */ if ((error = ud_iaccess(tdp, IWRITE, cr)) != 0 || (error = ud_sticky_remove_access(tdp, tip, cr)) != 0) goto out; /* * Ensure source and target are compatible (both directories * or both not directories). If target is a directory it must * be empty and have no links to it; in addition it must not * be a mount point, and both the source and target must be * writable. */ doingdirectory = (sip->i_type == VDIR); if (tip->i_type == VDIR) { if (!doingdirectory) { error = EISDIR; goto out; } /* * vn_vfswlock will prevent mounts from using the directory * until we are done. */ if (vn_vfswlock(ITOV(tip))) { error = EBUSY; goto out; } if (vn_mountedvfs(ITOV(tip)) != NULL) { vn_vfsunlock(ITOV(tip)); error = EBUSY; goto out; } if (!ud_dirempty(tip, tdp->i_uniqid, cr) || tip->i_nlink > 2) { vn_vfsunlock(ITOV(tip)); error = EEXIST; /* SIGH should be ENOTEMPTY */ goto out; } } else if (doingdirectory) { error = ENOTDIR; goto out; } /* * Rewrite the inode pointer for target name entry * from the target inode (ip) to the source inode (sip). * This prevents the target entry from disappearing * during a crash. Mark the directory inode to reflect the changes. */ dnlc_remove(ITOV(tdp), namep); fid->fid_icb.lad_ext_prn = SWAP_16(sip->i_icb_prn); fid->fid_icb.lad_ext_loc = SWAP_32(sip->i_icb_block); dnlc_enter(ITOV(tdp), namep, ITOV(sip)); ud_make_tag(tdp->i_udf, &fid->fid_tag, UD_FILE_ID_DESC, SWAP_32(fid->fid_tag.tag_loc), FID_LEN(fid)); error = ud_write_fid(tdp, slotp, buf); if (error) { if (doingdirectory) { vn_vfsunlock(ITOV(tip)); } goto out; } /* * Upgrade to write lock on tip */ rw_exit(&tip->i_contents); rw_enter(&tip->i_contents, RW_WRITER); mutex_enter(&tdp->i_tlock); tdp->i_flag |= IUPD|ICHG; mutex_exit(&tdp->i_tlock); /* * Decrement the link count of the target inode. * Fix the ".." entry in sip to point to dp. * This is done after the new entry is on the disk. */ tip->i_nlink--; mutex_enter(&tip->i_tlock); tip->i_flag |= ICHG; mutex_exit(&tip->i_tlock); if (doingdirectory) { /* * The entry for tip no longer exists so I can unlock the * vfslock. */ vn_vfsunlock(ITOV(tip)); /* * Decrement target link count once more if it was a directory. */ if (tip->i_nlink != 0) { cmn_err(CE_WARN, "ud_direnter: target directory link count != 0"); rw_exit(&tip->i_contents); rw_exit(&sip->i_contents); return (EINVAL); } /* * Renaming a directory with the parent different * requires that ".." be rewritten. The window is * still there for ".." to be inconsistent, but this * is unavoidable, and a lot shorter than when it was * done in a user process. We decrement the link * count in the new parent as appropriate to reflect * the just-removed target. If the parent is the * same, this is appropriate since the original * directory is going away. If the new parent is * different, dirfixdotdot() will bump the link count * back. */ tdp->i_nlink--; mutex_enter(&tdp->i_tlock); tdp->i_flag |= ICHG; mutex_exit(&tdp->i_tlock); ITIMES_NOLOCK(tdp); if (sdp != tdp) { rw_exit(&tip->i_contents); rw_exit(&sip->i_contents); error = ud_dirfixdotdot(sip, sdp, tdp); return (error); } } out: rw_exit(&tip->i_contents); rw_exit(&sip->i_contents); return (error); }