/* * Clear an Address/Length List so that it holds no pairs. */ void alenlist_clear(alenlist_t alenlist) { alenlist_chunk_t chunk, freechunk; /* * If this List is not FIXED_SIZE, free all the * extra chunks. */ if (!(alenlist->al_flags & AL_FIXED_SIZE)) { /* First, free any extension alenlist chunks */ chunk = alenlist->al_chunk.alc_next; while (chunk) { freechunk = chunk; chunk = chunk->alc_next; snia_kmem_zone_free(alenlist_chunk_zone, freechunk); DECR_COUNT(&alenlist_chunk_count); } alenlist->al_actual_size = ALEN_CHUNK_SZ; alenlist->al_chunk.alc_next = NULL; } alenlist->al_logical_size = 0; alenlist->al_last_chunk = &alenlist->al_chunk; do_cursor_init(alenlist, &alenlist->al_cursor); }
/* * Destroy an Address/Length List. */ void alenlist_destroy(alenlist_t alenlist) { if (alenlist == NULL) return; /* * Turn off FIXED_SIZE so this List can be * automatically shrunk. */ alenlist->al_flags &= ~AL_FIXED_SIZE; /* Free extension chunks first */ if (alenlist->al_chunk.alc_next) alenlist_clear(alenlist); /* Now, free the alenlist itself */ snia_kmem_zone_free(alenlist_zone, alenlist); DECR_COUNT(&alenlist_count); }
/* * xdirtrunc is called to remove all directory entries under this directory. * The files themselves are removed elsewhere. */ void xdirtrunc(struct xmemnode *dir) { register struct xdirent *xdp; size_t namelen; timestruc_t now; ASSERT(RW_WRITE_HELD(&dir->xn_rwlock)); ASSERT(dir->xn_type == VDIR); for (xdp = dir->xn_dir; xdp; xdp = dir->xn_dir) { ASSERT(xdp->xd_next != xdp); ASSERT(xdp->xd_prev != xdp); ASSERT(xdp->xd_xmemnode); ASSERT(xdp->xd_xmemnode->xn_nlink > 0); dir->xn_dir = xdp->xd_next; namelen = strlen(xdp->xd_name) + 1; DECR_COUNT(&xdp->xd_xmemnode->xn_nlink, &xdp->xd_xmemnode->xn_tlock); xmemfs_hash_out(xdp); xmem_memfree(xdp, sizeof (struct xdirent) + namelen); dir->xn_size -= (sizeof (struct xdirent) + namelen); dir->xn_dirents--; } gethrestime(&now); dir->xn_mtime = now; dir->xn_ctime = now; ASSERT(dir->xn_dir == NULL); ASSERT(dir->xn_size == 0); ASSERT(dir->xn_dirents == 0); }
/* * Free an Address/Length List cursor. */ void alenlist_cursor_destroy(alenlist_cursor_t cursorp) { DECR_COUNT(&alenlist_cursor_count); snia_kmem_zone_free(alenlist_cursor_zone, cursorp); }
static int xdirrename( struct xmemnode *fromparent, /* parent directory of source */ struct xmemnode *fromxp, /* source xmemnode */ struct xmemnode *toparent, /* parent directory of target */ char *nm, /* entry we are trying to change */ struct xmemnode *to, /* target xmemnode */ struct xdirent *where, /* target xmemnode directory entry */ struct cred *cred) /* credentials */ { int error = 0; int doingdirectory; timestruc_t now; #if defined(lint) nm = nm; #endif ASSERT(RW_WRITE_HELD(&toparent->xn_rwlock)); rw_enter(&fromxp->xn_rwlock, RW_READER); rw_enter(&to->xn_rwlock, RW_READER); /* * Check that everything is on the same filesystem. */ if (to->xn_vnode->v_vfsp != toparent->xn_vnode->v_vfsp || to->xn_vnode->v_vfsp != fromxp->xn_vnode->v_vfsp) { error = EXDEV; goto out; } /* * Short circuit rename of something to itself. */ if (fromxp == to) { error = ESAME; /* special KLUDGE error code */ goto out; } /* * Must have write permission to rewrite target entry. */ if (error = xmem_xaccess(fromparent, VWRITE, cred)) goto out; /* * If the parent directory is "sticky", then the user must own * either the parent directory or the destination of the rename, * or else must have permission to write the destination. * Otherwise the destination may not be changed (except by the * privileged users). This implements append-only directories. */ if (error = xmem_sticky_remove_access(toparent, to, cred)) 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 = (fromxp->xn_type == VDIR); if (to->xn_type == VDIR) { if (!doingdirectory) { error = EISDIR; goto out; } /* * vn_vfswlock will prevent mounts from using the directory * until we are done. */ if (vn_vfswlock(XNTOV(to))) { error = EBUSY; goto out; } if (vn_mountedvfs(XNTOV(to)) != NULL) { vn_vfsunlock(XNTOV(to)); error = EBUSY; goto out; } mutex_enter(&to->xn_tlock); if (to->xn_dirents > 2 || to->xn_nlink > 2) { mutex_exit(&to->xn_tlock); vn_vfsunlock(XNTOV(to)); error = EEXIST; /* SIGH should be ENOTEMPTY */ /* * Update atime because checking xn_dirents is * logically equivalent to reading the directory */ gethrestime(&to->xn_atime); goto out; } mutex_exit(&to->xn_tlock); } else if (doingdirectory) { error = ENOTDIR; goto out; } where->xd_xmemnode = fromxp; gethrestime(&now); toparent->xn_mtime = now; toparent->xn_ctime = now; /* * Upgrade to write lock on "to" (i.e., the target xmemnode). */ rw_exit(&to->xn_rwlock); rw_enter(&to->xn_rwlock, RW_WRITER); /* * Decrement the link count of the target xmemnode. */ DECR_COUNT(&to->xn_nlink, &to->xn_tlock); to->xn_ctime = now; if (doingdirectory) { /* * The entry for "to" no longer exists so release the vfslock. */ vn_vfsunlock(XNTOV(to)); /* * Decrement the target link count and delete all entires. */ xdirtrunc(to); ASSERT(to->xn_nlink == 0); /* * 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. */ if (fromparent != toparent) xdirfixdotdot(fromxp, fromparent, toparent); } out: rw_exit(&to->xn_rwlock); rw_exit(&fromxp->xn_rwlock); return (error); }
/* * Delete entry xp of name "nm" from dir. * Free dir entry space and decrement link count on xmemnode(s). * * Return 0 on success. */ int xdirdelete( struct xmemnode *dir, struct xmemnode *xp, char *nm, enum dr_op op, struct cred *cred) { register struct xdirent *tpdp; int error; size_t namelen; struct xmemnode *xptmp; timestruc_t now; ASSERT(RW_WRITE_HELD(&dir->xn_rwlock)); ASSERT(RW_WRITE_HELD(&xp->xn_rwlock)); ASSERT(dir->xn_type == VDIR); ASSERT(nm[0] != '\0'); /* * return error when removing . and .. */ if (nm[0] == '.') { if (nm[1] == '\0') return (EINVAL); if (nm[1] == '.' && nm[2] == '\0') return (EEXIST); /* thus in ufs */ } if (error = xmem_xaccess(dir, VEXEC|VWRITE, cred)) return (error); /* * If the parent directory is "sticky", then the user must * own the parent directory or the file in it, or else must * have permission to write the file. Otherwise it may not * be deleted (except by privileged users). Same as ufs_dirremove. */ if (error = xmem_sticky_remove_access(dir, xp, cred)) return (error); if (dir->xn_dir == NULL) return (ENOENT); tpdp = xmemfs_hash_lookup(nm, dir, 0, &xptmp); if (tpdp == NULL) { /* * If it is gone, some other thread got here first! * Return error ENOENT. */ return (ENOENT); } /* * If the xmemnode in the xdirent changed, we were probably * the victim of a concurrent rename operation. The original * is gone, so return that status (same as UFS). */ if (xp != xptmp) return (ENOENT); xmemfs_hash_out(tpdp); /* * Take tpdp out of the directory list. */ ASSERT(tpdp->xd_next != tpdp); ASSERT(tpdp->xd_prev != tpdp); if (tpdp->xd_prev) { tpdp->xd_prev->xd_next = tpdp->xd_next; } if (tpdp->xd_next) { tpdp->xd_next->xd_prev = tpdp->xd_prev; } /* * If the roving slot pointer happens to match tpdp, * point it at the previous dirent. */ if (dir->xn_dir->xd_prev == tpdp) { dir->xn_dir->xd_prev = tpdp->xd_prev; } ASSERT(tpdp->xd_next != tpdp); ASSERT(tpdp->xd_prev != tpdp); /* * tpdp points to the correct directory entry */ namelen = strlen(tpdp->xd_name) + 1; xmem_memfree(tpdp, sizeof (struct xdirent) + namelen); dir->xn_size -= (sizeof (struct xdirent) + namelen); dir->xn_dirents--; gethrestime(&now); dir->xn_mtime = now; dir->xn_ctime = now; xp->xn_ctime = now; ASSERT(xp->xn_nlink > 0); DECR_COUNT(&xp->xn_nlink, &xp->xn_tlock); if (op == DR_RMDIR && xp->xn_type == VDIR) { xdirtrunc(xp); ASSERT(xp->xn_nlink == 0); } return (0); }
/* * Enter a directory entry for 'name' and 'xp' into directory 'dir' * * Returns 0 on success. */ int xdirenter( struct xmount *xm, struct xmemnode *dir, /* target directory to make entry in */ char *name, /* name of entry */ enum de_op op, /* entry operation */ struct xmemnode *fromparent, /* source directory if rename */ struct xmemnode *xp, /* source xmemnode, if link/rename */ struct vattr *va, struct xmemnode **xpp, /* return xmemnode, if create/mkdir */ struct cred *cred) { struct xdirent *xdp; struct xmemnode *found = NULL; int error = 0; char *s; /* * xn_rwlock is held to serialize direnter and dirdeletes */ ASSERT(RW_WRITE_HELD(&dir->xn_rwlock)); ASSERT(dir->xn_type == VDIR); /* * Don't allow '/' characters in pathname component * (thus in ufs_direnter()). */ for (s = name; *s; s++) if (*s == '/') return (EACCES); ASSERT(name[0] != '\0'); /* * For link and rename lock the source entry and check the link count * to see if it has been removed while it was unlocked. */ if (op == DE_LINK || op == DE_RENAME) { mutex_enter(&xp->xn_tlock); if (xp->xn_nlink == 0) { mutex_exit(&xp->xn_tlock); return (ENOENT); } if (xp->xn_nlink == MAXLINK) { mutex_exit(&xp->xn_tlock); return (EMLINK); } xp->xn_nlink++; mutex_exit(&xp->xn_tlock); gethrestime(&xp->xn_ctime); } /* * This might be a "dangling detached directory". * it could have been removed, but a reference * to it kept in u_cwd. don't bother searching * it, and with any luck the user will get tired * of dealing with us and cd to some absolute * pathway. *sigh*, thus in ufs, too. */ if (dir->xn_nlink == 0) { error = ENOENT; goto out; } /* * If this is a rename of a directory and the parent is * different (".." must be changed), then the source * directory must not be in the directory hierarchy * above the target, as this would orphan everything * below the source directory. */ if (op == DE_RENAME) { if (xp == dir) { error = EINVAL; goto out; } if (xp->xn_type == VDIR) { if ((fromparent != dir) && (error = xdircheckpath(xp, dir, cred))) { goto out; } } } /* * Search for the entry. Return "found" if it exists. */ xdp = xmemfs_hash_lookup(name, dir, 1, &found); if (xdp) { ASSERT(found); switch (op) { case DE_CREATE: case DE_MKDIR: if (xpp) { *xpp = found; error = EEXIST; } else { xmemnode_rele(found); } break; case DE_RENAME: error = xdirrename(fromparent, xp, dir, name, found, xdp, cred); xmemnode_rele(found); break; case DE_LINK: /* * Can't link to an existing file. */ error = EEXIST; xmemnode_rele(found); break; } } else { /* * The entry does not exist. Check write permission in * directory to see if entry can be created. */ if (error = xmem_xaccess(dir, VWRITE, cred)) goto out; if (op == DE_CREATE || op == DE_MKDIR) { /* * Make new xmemnode and directory entry as required. */ error = xdirmakexnode(dir, xm, va, op, &xp, cred); if (error) goto out; } if (error = xdiraddentry(dir, xp, name, op, fromparent)) { if (op == DE_CREATE || op == DE_MKDIR) { /* * Unmake the inode we just made. */ rw_enter(&xp->xn_rwlock, RW_WRITER); if ((xp->xn_type) == VDIR) { ASSERT(xdp == NULL); /* * cleanup allocs made by xdirinit() */ xdirtrunc(xp); } mutex_enter(&xp->xn_tlock); xp->xn_nlink = 0; mutex_exit(&xp->xn_tlock); gethrestime(&xp->xn_ctime); rw_exit(&xp->xn_rwlock); xmemnode_rele(xp); xp = NULL; } } else if (xpp) { *xpp = xp; } else if (op == DE_CREATE || op == DE_MKDIR) { xmemnode_rele(xp); } } out: if (error && (op == DE_LINK || op == DE_RENAME)) { /* * Undo bumped link count. */ DECR_COUNT(&xp->xn_nlink, &xp->xn_tlock); gethrestime(&xp->xn_ctime); } return (error); }