/* * Convert inode mode to directory entry filetype
*/ unsignedchar
xfs_mode_to_ftype( int mode)
{ switch (mode & S_IFMT) { case S_IFREG: return XFS_DIR3_FT_REG_FILE; case S_IFDIR: return XFS_DIR3_FT_DIR; case S_IFCHR: return XFS_DIR3_FT_CHRDEV; case S_IFBLK: return XFS_DIR3_FT_BLKDEV; case S_IFIFO: return XFS_DIR3_FT_FIFO; case S_IFSOCK: return XFS_DIR3_FT_SOCK; case S_IFLNK: return XFS_DIR3_FT_SYMLINK; default: return XFS_DIR3_FT_UNKNOWN;
}
}
/* * ASCII case-insensitive (ie. A-Z) support for directories that was * used in IRIX.
*/
xfs_dahash_t
xfs_ascii_ci_hashname( conststruct xfs_name *name)
{
xfs_dahash_t hash; int i;
for (i = 0, hash = 0; i < name->len; i++)
hash = xfs_ascii_ci_xfrm(name->name[i]) ^ rol32(hash, 7);
return hash;
}
enum xfs_dacmp
xfs_ascii_ci_compname( struct xfs_da_args *args, constunsignedchar *name, int len)
{ enum xfs_dacmp result; int i;
if (args->namelen != len) return XFS_CMP_DIFFERENT;
result = XFS_CMP_EXACT; for (i = 0; i < len; i++) { if (args->name[i] == name[i]) continue; if (xfs_ascii_ci_xfrm(args->name[i]) !=
xfs_ascii_ci_xfrm(name[i])) return XFS_CMP_DIFFERENT;
result = XFS_CMP_CASE;
}
return result;
}
int
xfs_da_mount( struct xfs_mount *mp)
{ struct xfs_da_geometry *dageo;
/* * Initialize a directory with its "." and ".." entries.
*/ int
xfs_dir_init(
xfs_trans_t *tp,
xfs_inode_t *dp,
xfs_inode_t *pdp)
{ struct xfs_da_args *args; int error;
ASSERT(S_ISDIR(VFS_I(dp)->i_mode));
error = xfs_dir_ino_validate(tp->t_mountp, pdp->i_ino); if (error) return error;
args = kzalloc(sizeof(*args), GFP_KERNEL | __GFP_NOFAIL); if (!args) return -ENOMEM;
int
xfs_dir_createname_args( struct xfs_da_args *args)
{ int error;
if (!args->inumber)
args->op_flags |= XFS_DA_OP_JUSTCHECK;
switch (xfs_dir2_format(args, &error)) { case XFS_DIR2_FMT_SF: return xfs_dir2_sf_addname(args); case XFS_DIR2_FMT_BLOCK: return xfs_dir2_block_addname(args); case XFS_DIR2_FMT_LEAF: return xfs_dir2_leaf_addname(args); case XFS_DIR2_FMT_NODE: return xfs_dir2_node_addname(args); default: return error;
}
}
/* * Enter a name in a directory, or check for available space. * If inum is 0, only the available space test is performed.
*/ int
xfs_dir_createname( struct xfs_trans *tp, struct xfs_inode *dp, conststruct xfs_name *name,
xfs_ino_t inum, /* new entry inode number */
xfs_extlen_t total) /* bmap's total block count */
{ struct xfs_da_args *args; int rval;
ASSERT(S_ISDIR(VFS_I(dp)->i_mode));
if (inum) {
rval = xfs_dir_ino_validate(tp->t_mountp, inum); if (rval) return rval;
XFS_STATS_INC(dp->i_mount, xs_dir_create);
}
args = kzalloc(sizeof(*args), GFP_KERNEL | __GFP_NOFAIL); if (!args) return -ENOMEM;
/* * If doing a CI lookup and case-insensitive match, dup actual name into * args.value. Return EEXIST for success (ie. name found) or an error.
*/ int
xfs_dir_cilookup_result( struct xfs_da_args *args, constunsignedchar *name, int len)
{ if (args->cmpresult == XFS_CMP_DIFFERENT) return -ENOENT; if (args->cmpresult != XFS_CMP_CASE ||
!(args->op_flags & XFS_DA_OP_CILOOKUP)) return -EEXIST;
int
xfs_dir_lookup_args( struct xfs_da_args *args)
{ int error;
switch (xfs_dir2_format(args, &error)) { case XFS_DIR2_FMT_SF:
error = xfs_dir2_sf_lookup(args); break; case XFS_DIR2_FMT_BLOCK:
error = xfs_dir2_block_lookup(args); break; case XFS_DIR2_FMT_LEAF:
error = xfs_dir2_leaf_lookup(args); break; case XFS_DIR2_FMT_NODE:
error = xfs_dir2_node_lookup(args); break; default: break;
}
if (error != -EEXIST) return error; return 0;
}
/* * Lookup a name in a directory, give back the inode number. * If ci_name is not NULL, returns the actual name in ci_name if it differs * to name, or ci_name->name is set to NULL for an exact match.
*/
int
xfs_dir_lookup( struct xfs_trans *tp, struct xfs_inode *dp, conststruct xfs_name *name,
xfs_ino_t *inum, /* out: inode number */ struct xfs_name *ci_name) /* out: actual name if CI match */
{ struct xfs_da_args *args; int rval; int lock_mode;
int
xfs_dir_replace_args( struct xfs_da_args *args)
{ int error;
switch (xfs_dir2_format(args, &error)) { case XFS_DIR2_FMT_SF: return xfs_dir2_sf_replace(args); case XFS_DIR2_FMT_BLOCK: return xfs_dir2_block_replace(args); case XFS_DIR2_FMT_LEAF: return xfs_dir2_leaf_replace(args); case XFS_DIR2_FMT_NODE: return xfs_dir2_node_replace(args); default: return error;
}
}
/* * Replace the inode number of a directory entry.
*/ int
xfs_dir_replace( struct xfs_trans *tp, struct xfs_inode *dp, conststruct xfs_name *name, /* name of entry to replace */
xfs_ino_t inum, /* new inode number */
xfs_extlen_t total) /* bmap's total block count */
{ struct xfs_da_args *args; int rval;
ASSERT(S_ISDIR(VFS_I(dp)->i_mode));
rval = xfs_dir_ino_validate(tp->t_mountp, inum); if (rval) return rval;
args = kzalloc(sizeof(*args), GFP_KERNEL | __GFP_NOFAIL); if (!args) return -ENOMEM;
/* * See if this entry can be added to the directory without allocating space.
*/ int
xfs_dir_canenter( struct xfs_trans *tp, struct xfs_inode *dp, conststruct xfs_name *name) /* name of entry to add */
{ return xfs_dir_createname(tp, dp, name, 0, 0);
}
/* * Utility routines.
*/
/* * Add a block to the directory. * * This routine is for data and free blocks, not leaf/node blocks which are * handled by xfs_da_grow_inode.
*/ int
xfs_dir2_grow_inode( struct xfs_da_args *args, int space, /* v2 dir's space XFS_DIR2_xxx_SPACE */
xfs_dir2_db_t *dbp) /* out: block number added */
{ struct xfs_inode *dp = args->dp; struct xfs_mount *mp = dp->i_mount;
xfs_fileoff_t bno; /* directory offset of new block */ int count; /* count of filesystem blocks */ int error;
trace_xfs_dir2_grow_inode(args, space);
/* * Set lowest possible block in the space requested.
*/
bno = XFS_B_TO_FSBT(mp, space * XFS_DIR2_SPACE_SIZE);
count = args->geo->fsbcount;
error = xfs_da_grow_inode_int(args, &bno, count); if (error) return error;
/* * Update file's size if this is the data space and it grew.
*/ if (space == XFS_DIR2_DATA_SPACE) {
xfs_fsize_t size; /* directory file (data) size */
/* * Remove the given block from the directory. * This routine is used for data and free blocks, leaf/node are done * by xfs_da_shrink_inode.
*/ int
xfs_dir2_shrink_inode( struct xfs_da_args *args,
xfs_dir2_db_t db, struct xfs_buf *bp)
{
xfs_fileoff_t bno; /* directory file offset */
xfs_dablk_t da; /* directory file offset */ int done; /* bunmap is finished */ struct xfs_inode *dp; int error; struct xfs_mount *mp; struct xfs_trans *tp;
/* Unmap the fsblock(s). */
error = xfs_bunmapi(tp, dp, da, args->geo->fsbcount, 0, 0, &done); if (error) { /* * ENOSPC actually can happen if we're in a removename with no * space reservation, and the resulting block removal would * cause a bmap btree split or conversion from extents to btree. * This can only happen for un-fragmented directory blocks, * since you need to be punching out the middle of an extent. * In this case we need to leave the block in the file, and not * binval it. So the block has to be in a consistent empty * state and appropriately logged. We don't free up the buffer, * the caller can tell it hasn't happened since it got an error * back.
*/ return error;
}
ASSERT(done); /* * Invalidate the buffer from the transaction.
*/
xfs_trans_binval(tp, bp); /* * If it's not a data block, we're done.
*/ if (db >= xfs_dir2_byte_to_db(args->geo, XFS_DIR2_LEAF_OFFSET)) return 0; /* * If the block isn't the last one in the directory, we're done.
*/ if (dp->i_disk_size > xfs_dir2_db_off_to_byte(args->geo, db + 1, 0)) return 0;
bno = da; if ((error = xfs_bmap_last_before(tp, dp, &bno, XFS_DATA_FORK))) { /* * This can't really happen unless there's kernel corruption.
*/ return error;
} if (db == args->geo->datablk)
ASSERT(bno == 0); else
ASSERT(bno > 0); /* * Set the size to the new last block.
*/
dp->i_disk_size = XFS_FSB_TO_B(mp, bno);
xfs_trans_log_inode(tp, dp, XFS_ILOG_CORE); return 0;
}
/* Returns true if the directory entry name is valid. */ bool
xfs_dir2_namecheck( constvoid *name,
size_t length)
{ /* * MAXNAMELEN includes the trailing null, but (name/length) leave it * out, so use >= for the length check.
*/ if (length >= MAXNAMELEN) returnfalse;
/* There shouldn't be any slashes or nulls here */ return !memchr(name, '/', length) && !memchr(name, 0, length);
}
#ifdef CONFIG_XFS_LIVE_HOOKS /* * Use a static key here to reduce the overhead of directory live update hooks. * If the compiler supports jump labels, the static branch will be replaced by * a nop sled when there are no hook users. Online fsck is currently the only * caller, so this is a reasonable tradeoff. * * Note: Patching the kernel code requires taking the cpu hotplug lock. Other * parts of the kernel allocate memory with that lock held, which means that * XFS callers cannot hold any locks that might be used by memory reclaim or * writeback when calling the static_branch_{inc,dec} functions.
*/
DEFINE_STATIC_XFS_HOOK_SWITCH(xfs_dir_hooks_switch);
/* Call the specified function during a directory update. */ int
xfs_dir_hook_add( struct xfs_mount *mp, struct xfs_dir_hook *hook)
{ return xfs_hooks_add(&mp->m_dir_update_hooks, &hook->dirent_hook);
}
/* Stop calling the specified function during a directory update. */ void
xfs_dir_hook_del( struct xfs_mount *mp, struct xfs_dir_hook *hook)
{
xfs_hooks_del(&mp->m_dir_update_hooks, &hook->dirent_hook);
}
/* * Given a directory @dp, a newly allocated inode @ip, and a @name, link @ip * into @dp under the given @name. If @ip is a directory, it will be * initialized. Both inodes must have the ILOCK held and the transaction must * have sufficient blocks reserved.
*/ int
xfs_dir_create_child( struct xfs_trans *tp, unsignedint resblks, struct xfs_dir_update *du)
{ struct xfs_inode *dp = du->dp; conststruct xfs_name *name = du->name; struct xfs_inode *ip = du->ip; int error;
if (S_ISDIR(VFS_I(ip)->i_mode)) {
error = xfs_dir_init(tp, ip, dp); if (error) return error;
xfs_bumplink(tp, dp);
}
/* * If we have parent pointers, we need to add the attribute containing * the parent information now.
*/ if (du->ppargs) {
error = xfs_parent_addname(tp, du->ppargs, dp, name, ip); if (error) return error;
}
xfs_dir_update_hook(dp, ip, 1, name); return 0;
}
/* * Given a directory @dp, an existing non-directory inode @ip, and a @name, * link @ip into @dp under the given @name. Both inodes must have the ILOCK * held.
*/ int
xfs_dir_add_child( struct xfs_trans *tp, unsignedint resblks, struct xfs_dir_update *du)
{ struct xfs_inode *dp = du->dp; conststruct xfs_name *name = du->name; struct xfs_inode *ip = du->ip; struct xfs_mount *mp = tp->t_mountp; int error;
/* * If we have parent pointers, we now need to add the parent record to * the attribute fork of the inode. If this is the initial parent * attribute, we need to create it correctly, otherwise we can just add * the parent to the inode.
*/ if (du->ppargs) {
error = xfs_parent_addname(tp, du->ppargs, dp, name, ip); if (error) return error;
}
xfs_dir_update_hook(dp, ip, 1, name); return 0;
}
/* * Given a directory @dp, a child @ip, and a @name, remove the (@name, @ip) * entry from the directory. Both inodes must have the ILOCK held.
*/ int
xfs_dir_remove_child( struct xfs_trans *tp, unsignedint resblks, struct xfs_dir_update *du)
{ struct xfs_inode *dp = du->dp; conststruct xfs_name *name = du->name; struct xfs_inode *ip = du->ip; int error;
/* * If we're removing a directory perform some additional validation.
*/ if (S_ISDIR(VFS_I(ip)->i_mode)) {
ASSERT(VFS_I(ip)->i_nlink >= 2); if (VFS_I(ip)->i_nlink != 2) return -ENOTEMPTY; if (!xfs_dir_isempty(ip)) return -ENOTEMPTY;
/* Drop the link from ip's "..". */
error = xfs_droplink(tp, dp); if (error) return error;
/* Drop the "." link from ip to self. */
error = xfs_droplink(tp, ip); if (error) return error;
/* * Point the unlinked child directory's ".." entry to the root * directory to eliminate back-references to inodes that may * get freed before the child directory is closed. If the fs * gets shrunk, this can lead to dirent inode validation errors.
*/ if (dp->i_ino != tp->t_mountp->m_sb.sb_rootino) {
error = xfs_dir_replace(tp, ip, &xfs_name_dotdot,
tp->t_mountp->m_sb.sb_rootino, 0); if (error) return error;
}
} else { /* * When removing a non-directory we need to log the parent * inode here. For a directory this is done implicitly * by the xfs_droplink call for the ".." entry.
*/
xfs_trans_log_inode(tp, dp, XFS_ILOG_CORE);
}
xfs_trans_ichgtime(tp, dp, XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);
/* Drop the link from dp to ip. */
error = xfs_droplink(tp, ip); if (error) return error;
/* * Exchange the entry (@name1, @ip1) in directory @dp1 with the entry (@name2, * @ip2) in directory @dp2, and update '..' @ip1 and @ip2's entries as needed. * @ip1 and @ip2 need not be of the same type. * * All inodes must have the ILOCK held, and both entries must already exist.
*/ int
xfs_dir_exchange_children( struct xfs_trans *tp, struct xfs_dir_update *du1, struct xfs_dir_update *du2, unsignedint spaceres)
{ struct xfs_inode *dp1 = du1->dp; conststruct xfs_name *name1 = du1->name; struct xfs_inode *ip1 = du1->ip; struct xfs_inode *dp2 = du2->dp; conststruct xfs_name *name2 = du2->name; struct xfs_inode *ip2 = du2->ip; int ip1_flags = 0; int ip2_flags = 0; int dp2_flags = 0; int error;
/* Swap inode number for dirent in first parent */
error = xfs_dir_replace(tp, dp1, name1, ip2->i_ino, spaceres); if (error) return error;
/* Swap inode number for dirent in second parent */
error = xfs_dir_replace(tp, dp2, name2, ip1->i_ino, spaceres); if (error) return error;
/* * If we're renaming one or more directories across different parents, * update the respective ".." entries (and link counts) to match the new * parents.
*/ if (dp1 != dp2) {
dp2_flags = XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG;
if (S_ISDIR(VFS_I(ip2)->i_mode)) {
error = xfs_dir_replace(tp, ip2, &xfs_name_dotdot,
dp1->i_ino, spaceres); if (error) return error;
/* transfer ip2 ".." reference to dp1 */ if (!S_ISDIR(VFS_I(ip1)->i_mode)) {
error = xfs_droplink(tp, dp2); if (error) return error;
xfs_bumplink(tp, dp1);
}
/* * Although ip1 isn't changed here, userspace needs * to be warned about the change, so that applications * relying on it (like backup ones), will properly * notify the change
*/
ip1_flags |= XFS_ICHGTIME_CHG;
ip2_flags |= XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG;
}
if (S_ISDIR(VFS_I(ip1)->i_mode)) {
error = xfs_dir_replace(tp, ip1, &xfs_name_dotdot,
dp2->i_ino, spaceres); if (error) return error;
/* transfer ip1 ".." reference to dp2 */ if (!S_ISDIR(VFS_I(ip2)->i_mode)) {
error = xfs_droplink(tp, dp1); if (error) return error;
xfs_bumplink(tp, dp2);
}
/* * Although ip2 isn't changed here, userspace needs * to be warned about the change, so that applications * relying on it (like backup ones), will properly * notify the change
*/
ip1_flags |= XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG;
ip2_flags |= XFS_ICHGTIME_CHG;
}
}
if (du2->ppargs) {
error = xfs_parent_replacename(tp, du2->ppargs, dp2, name2,
dp1, name1, ip2); if (error) return error;
}
/* * Inform our hook clients that we've finished an exchange operation as * follows: removed the source and target files from their directories; * added the target to the source directory; and added the source to * the target directory. All inodes are locked, so it's ok to model a * rename this way so long as we say we deleted entries before we add * new ones.
*/
xfs_dir_update_hook(dp1, ip1, -1, name1);
xfs_dir_update_hook(dp2, ip2, -1, name2);
xfs_dir_update_hook(dp1, ip2, 1, name1);
xfs_dir_update_hook(dp2, ip1, 1, name2); return 0;
}
/* * Given an entry (@src_name, @src_ip) in directory @src_dp, make the entry * @target_name in directory @target_dp point to @src_ip and remove the * original entry, cleaning up everything left behind. * * Cleanup involves dropping a link count on @target_ip, and either removing * the (@src_name, @src_ip) entry from @src_dp or simply replacing the entry * with (@src_name, @wip) if a whiteout inode @wip is supplied. * * All inodes must have the ILOCK held. We assume that if @src_ip is a * directory then its '..' doesn't already point to @target_dp, and that @wip * is a freshly allocated whiteout.
*/ int
xfs_dir_rename_children( struct xfs_trans *tp, struct xfs_dir_update *du_src, struct xfs_dir_update *du_tgt, unsignedint spaceres, struct xfs_dir_update *du_wip)
{ struct xfs_mount *mp = tp->t_mountp; struct xfs_inode *src_dp = du_src->dp; conststruct xfs_name *src_name = du_src->name; struct xfs_inode *src_ip = du_src->ip; struct xfs_inode *target_dp = du_tgt->dp; conststruct xfs_name *target_name = du_tgt->name; struct xfs_inode *target_ip = du_tgt->ip; bool new_parent = (src_dp != target_dp); bool src_is_directory; int error;
/* * Check for expected errors before we dirty the transaction * so we can return an error without a transaction abort.
*/ if (target_ip == NULL) { /* * If there's no space reservation, check the entry will * fit before actually inserting it.
*/ if (!spaceres) {
error = xfs_dir_canenter(tp, target_dp, target_name); if (error) return error;
}
} else { /* * If target exists and it's a directory, check that whether * it can be destroyed.
*/ if (S_ISDIR(VFS_I(target_ip)->i_mode) &&
(!xfs_dir_isempty(target_ip) ||
(VFS_I(target_ip)->i_nlink > 2))) return -EEXIST;
}
/* * Directory entry creation below may acquire the AGF. Remove * the whiteout from the unlinked list first to preserve correct * AGI/AGF locking order. This dirties the transaction so failures * after this point will abort and log recovery will clean up the * mess. * * For whiteouts, we need to bump the link count on the whiteout * inode. After this point, we have a real link, clear the tmpfile * state flag from the inode so it doesn't accidentally get misused * in future.
*/ if (du_wip->ip) { struct xfs_perag *pag;
/* * Set up the target.
*/ if (target_ip == NULL) { /* * If target does not exist and the rename crosses * directories, adjust the target directory link count * to account for the ".." reference from the new entry.
*/
error = xfs_dir_createname(tp, target_dp, target_name,
src_ip->i_ino, spaceres); if (error) return error;
if (new_parent && src_is_directory) {
xfs_bumplink(tp, target_dp);
}
} else { /* target_ip != NULL */ /* * Link the source inode under the target name. * If the source inode is a directory and we are moving * it across directories, its ".." entry will be * inconsistent until we replace that down below. * * In case there is already an entry with the same * name at the destination directory, remove it first.
*/
error = xfs_dir_replace(tp, target_dp, target_name,
src_ip->i_ino, spaceres); if (error) return error;
/* * Decrement the link count on the target since the target * dir no longer points to it.
*/
error = xfs_droplink(tp, target_ip); if (error) return error;
if (src_is_directory) { /* * Drop the link from the old "." entry.
*/
error = xfs_droplink(tp, target_ip); if (error) return error;
}
} /* target_ip != NULL */
/* * Remove the source.
*/ if (new_parent && src_is_directory) { /* * Rewrite the ".." entry to point to the new * directory.
*/
error = xfs_dir_replace(tp, src_ip, &xfs_name_dotdot,
target_dp->i_ino, spaceres);
ASSERT(error != -EEXIST); if (error) return error;
}
/* * We always want to hit the ctime on the source inode. * * This isn't strictly required by the standards since the source * inode isn't really being changed, but old unix file systems did * it and some incremental backup programs won't work without it.
*/
xfs_trans_ichgtime(tp, src_ip, XFS_ICHGTIME_CHG);
xfs_trans_log_inode(tp, src_ip, XFS_ILOG_CORE);
/* * Adjust the link count on src_dp. This is necessary when * renaming a directory, either within one parent when * the target existed, or across two parent directories.
*/ if (src_is_directory && (new_parent || target_ip != NULL)) {
/* * Decrement link count on src_directory since the * entry that's moved no longer points to it.
*/
error = xfs_droplink(tp, src_dp); if (error) return error;
}
/* * For whiteouts, we only need to update the source dirent with the * inode number of the whiteout inode rather than removing it * altogether.
*/ if (du_wip->ip)
error = xfs_dir_replace(tp, src_dp, src_name, du_wip->ip->i_ino,
spaceres); else
error = xfs_dir_removename(tp, src_dp, src_name, src_ip->i_ino,
spaceres); if (error) return error;
if (du_src->ppargs) {
error = xfs_parent_replacename(tp, du_src->ppargs, src_dp,
src_name, target_dp, target_name, src_ip); if (error) return error;
}
if (du_tgt->ppargs) {
error = xfs_parent_removename(tp, du_tgt->ppargs, target_dp,
target_name, target_ip); if (error) return error;
}
/* * Inform our hook clients that we've finished a rename operation as * follows: removed the source and target files from their directories; * that we've added the source to the target directory; and finally * that we've added the whiteout, if there was one. All inodes are * locked, so it's ok to model a rename this way so long as we say we * deleted entries before we add new ones.
*/ if (target_ip)
xfs_dir_update_hook(target_dp, target_ip, -1, target_name);
xfs_dir_update_hook(src_dp, src_ip, -1, src_name);
xfs_dir_update_hook(target_dp, src_ip, 1, target_name); if (du_wip->ip)
xfs_dir_update_hook(src_dp, du_wip->ip, 1, src_name); return 0;
}
Messung V0.5
¤ Dauer der Verarbeitung: 0.8 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.