| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2000-2006 Silicon Graphics, Inc. |
| * All Rights Reserved. |
| */ |
| #include <linux/iversion.h> |
| #include "xfs.h" |
| #include "xfs_fs.h" |
| #include "xfs_shared.h" |
| #include "xfs_format.h" |
| #include "xfs_log_format.h" |
| #include "xfs_trans_resv.h" |
| #include "xfs_sb.h" |
| #include "xfs_mount.h" |
| #include "xfs_inode.h" |
| #include "xfs_inode_util.h" |
| #include "xfs_trans.h" |
| #include "xfs_ialloc.h" |
| #include "xfs_health.h" |
| #include "xfs_bmap.h" |
| #include "xfs_error.h" |
| #include "xfs_trace.h" |
| #include "xfs_ag.h" |
| #include "xfs_iunlink_item.h" |
| #include "xfs_inode_item.h" |
| |
| uint16_t |
| xfs_flags2diflags( |
| struct xfs_inode *ip, |
| unsigned int xflags) |
| { |
| /* can't set PREALLOC this way, just preserve it */ |
| uint16_t di_flags = |
| (ip->i_diflags & XFS_DIFLAG_PREALLOC); |
| |
| if (xflags & FS_XFLAG_IMMUTABLE) |
| di_flags |= XFS_DIFLAG_IMMUTABLE; |
| if (xflags & FS_XFLAG_APPEND) |
| di_flags |= XFS_DIFLAG_APPEND; |
| if (xflags & FS_XFLAG_SYNC) |
| di_flags |= XFS_DIFLAG_SYNC; |
| if (xflags & FS_XFLAG_NOATIME) |
| di_flags |= XFS_DIFLAG_NOATIME; |
| if (xflags & FS_XFLAG_NODUMP) |
| di_flags |= XFS_DIFLAG_NODUMP; |
| if (xflags & FS_XFLAG_NODEFRAG) |
| di_flags |= XFS_DIFLAG_NODEFRAG; |
| if (xflags & FS_XFLAG_FILESTREAM) |
| di_flags |= XFS_DIFLAG_FILESTREAM; |
| if (S_ISDIR(VFS_I(ip)->i_mode)) { |
| if (xflags & FS_XFLAG_RTINHERIT) |
| di_flags |= XFS_DIFLAG_RTINHERIT; |
| if (xflags & FS_XFLAG_NOSYMLINKS) |
| di_flags |= XFS_DIFLAG_NOSYMLINKS; |
| if (xflags & FS_XFLAG_EXTSZINHERIT) |
| di_flags |= XFS_DIFLAG_EXTSZINHERIT; |
| if (xflags & FS_XFLAG_PROJINHERIT) |
| di_flags |= XFS_DIFLAG_PROJINHERIT; |
| } else if (S_ISREG(VFS_I(ip)->i_mode)) { |
| if (xflags & FS_XFLAG_REALTIME) |
| di_flags |= XFS_DIFLAG_REALTIME; |
| if (xflags & FS_XFLAG_EXTSIZE) |
| di_flags |= XFS_DIFLAG_EXTSIZE; |
| } |
| |
| return di_flags; |
| } |
| |
| uint64_t |
| xfs_flags2diflags2( |
| struct xfs_inode *ip, |
| unsigned int xflags) |
| { |
| uint64_t di_flags2 = |
| (ip->i_diflags2 & (XFS_DIFLAG2_REFLINK | |
| XFS_DIFLAG2_BIGTIME | |
| XFS_DIFLAG2_NREXT64)); |
| |
| if (xflags & FS_XFLAG_DAX) |
| di_flags2 |= XFS_DIFLAG2_DAX; |
| if (xflags & FS_XFLAG_COWEXTSIZE) |
| di_flags2 |= XFS_DIFLAG2_COWEXTSIZE; |
| |
| return di_flags2; |
| } |
| |
| uint32_t |
| xfs_ip2xflags( |
| struct xfs_inode *ip) |
| { |
| uint32_t flags = 0; |
| |
| if (ip->i_diflags & XFS_DIFLAG_ANY) { |
| if (ip->i_diflags & XFS_DIFLAG_REALTIME) |
| flags |= FS_XFLAG_REALTIME; |
| if (ip->i_diflags & XFS_DIFLAG_PREALLOC) |
| flags |= FS_XFLAG_PREALLOC; |
| if (ip->i_diflags & XFS_DIFLAG_IMMUTABLE) |
| flags |= FS_XFLAG_IMMUTABLE; |
| if (ip->i_diflags & XFS_DIFLAG_APPEND) |
| flags |= FS_XFLAG_APPEND; |
| if (ip->i_diflags & XFS_DIFLAG_SYNC) |
| flags |= FS_XFLAG_SYNC; |
| if (ip->i_diflags & XFS_DIFLAG_NOATIME) |
| flags |= FS_XFLAG_NOATIME; |
| if (ip->i_diflags & XFS_DIFLAG_NODUMP) |
| flags |= FS_XFLAG_NODUMP; |
| if (ip->i_diflags & XFS_DIFLAG_RTINHERIT) |
| flags |= FS_XFLAG_RTINHERIT; |
| if (ip->i_diflags & XFS_DIFLAG_PROJINHERIT) |
| flags |= FS_XFLAG_PROJINHERIT; |
| if (ip->i_diflags & XFS_DIFLAG_NOSYMLINKS) |
| flags |= FS_XFLAG_NOSYMLINKS; |
| if (ip->i_diflags & XFS_DIFLAG_EXTSIZE) |
| flags |= FS_XFLAG_EXTSIZE; |
| if (ip->i_diflags & XFS_DIFLAG_EXTSZINHERIT) |
| flags |= FS_XFLAG_EXTSZINHERIT; |
| if (ip->i_diflags & XFS_DIFLAG_NODEFRAG) |
| flags |= FS_XFLAG_NODEFRAG; |
| if (ip->i_diflags & XFS_DIFLAG_FILESTREAM) |
| flags |= FS_XFLAG_FILESTREAM; |
| } |
| |
| if (ip->i_diflags2 & XFS_DIFLAG2_ANY) { |
| if (ip->i_diflags2 & XFS_DIFLAG2_DAX) |
| flags |= FS_XFLAG_DAX; |
| if (ip->i_diflags2 & XFS_DIFLAG2_COWEXTSIZE) |
| flags |= FS_XFLAG_COWEXTSIZE; |
| } |
| |
| if (xfs_inode_has_attr_fork(ip)) |
| flags |= FS_XFLAG_HASATTR; |
| return flags; |
| } |
| |
| prid_t |
| xfs_get_initial_prid(struct xfs_inode *dp) |
| { |
| if (dp->i_diflags & XFS_DIFLAG_PROJINHERIT) |
| return dp->i_projid; |
| |
| /* Assign to the root project by default. */ |
| return 0; |
| } |
| |
| /* Propagate di_flags from a parent inode to a child inode. */ |
| static inline void |
| xfs_inode_inherit_flags( |
| struct xfs_inode *ip, |
| const struct xfs_inode *pip) |
| { |
| unsigned int di_flags = 0; |
| xfs_failaddr_t failaddr; |
| umode_t mode = VFS_I(ip)->i_mode; |
| |
| if (S_ISDIR(mode)) { |
| if (pip->i_diflags & XFS_DIFLAG_RTINHERIT) |
| di_flags |= XFS_DIFLAG_RTINHERIT; |
| if (pip->i_diflags & XFS_DIFLAG_EXTSZINHERIT) { |
| di_flags |= XFS_DIFLAG_EXTSZINHERIT; |
| ip->i_extsize = pip->i_extsize; |
| } |
| if (pip->i_diflags & XFS_DIFLAG_PROJINHERIT) |
| di_flags |= XFS_DIFLAG_PROJINHERIT; |
| } else if (S_ISREG(mode)) { |
| if ((pip->i_diflags & XFS_DIFLAG_RTINHERIT) && |
| xfs_has_realtime(ip->i_mount)) |
| di_flags |= XFS_DIFLAG_REALTIME; |
| if (pip->i_diflags & XFS_DIFLAG_EXTSZINHERIT) { |
| di_flags |= XFS_DIFLAG_EXTSIZE; |
| ip->i_extsize = pip->i_extsize; |
| } |
| } |
| if ((pip->i_diflags & XFS_DIFLAG_NOATIME) && |
| xfs_inherit_noatime) |
| di_flags |= XFS_DIFLAG_NOATIME; |
| if ((pip->i_diflags & XFS_DIFLAG_NODUMP) && |
| xfs_inherit_nodump) |
| di_flags |= XFS_DIFLAG_NODUMP; |
| if ((pip->i_diflags & XFS_DIFLAG_SYNC) && |
| xfs_inherit_sync) |
| di_flags |= XFS_DIFLAG_SYNC; |
| if ((pip->i_diflags & XFS_DIFLAG_NOSYMLINKS) && |
| xfs_inherit_nosymlinks) |
| di_flags |= XFS_DIFLAG_NOSYMLINKS; |
| if ((pip->i_diflags & XFS_DIFLAG_NODEFRAG) && |
| xfs_inherit_nodefrag) |
| di_flags |= XFS_DIFLAG_NODEFRAG; |
| if (pip->i_diflags & XFS_DIFLAG_FILESTREAM) |
| di_flags |= XFS_DIFLAG_FILESTREAM; |
| |
| ip->i_diflags |= di_flags; |
| |
| /* |
| * Inode verifiers on older kernels only check that the extent size |
| * hint is an integer multiple of the rt extent size on realtime files. |
| * They did not check the hint alignment on a directory with both |
| * rtinherit and extszinherit flags set. If the misaligned hint is |
| * propagated from a directory into a new realtime file, new file |
| * allocations will fail due to math errors in the rt allocator and/or |
| * trip the verifiers. Validate the hint settings in the new file so |
| * that we don't let broken hints propagate. |
| */ |
| failaddr = xfs_inode_validate_extsize(ip->i_mount, ip->i_extsize, |
| VFS_I(ip)->i_mode, ip->i_diflags); |
| if (failaddr) { |
| ip->i_diflags &= ~(XFS_DIFLAG_EXTSIZE | |
| XFS_DIFLAG_EXTSZINHERIT); |
| ip->i_extsize = 0; |
| } |
| } |
| |
| /* Propagate di_flags2 from a parent inode to a child inode. */ |
| static inline void |
| xfs_inode_inherit_flags2( |
| struct xfs_inode *ip, |
| const struct xfs_inode *pip) |
| { |
| xfs_failaddr_t failaddr; |
| |
| if (pip->i_diflags2 & XFS_DIFLAG2_COWEXTSIZE) { |
| ip->i_diflags2 |= XFS_DIFLAG2_COWEXTSIZE; |
| ip->i_cowextsize = pip->i_cowextsize; |
| } |
| if (pip->i_diflags2 & XFS_DIFLAG2_DAX) |
| ip->i_diflags2 |= XFS_DIFLAG2_DAX; |
| |
| /* Don't let invalid cowextsize hints propagate. */ |
| failaddr = xfs_inode_validate_cowextsize(ip->i_mount, ip->i_cowextsize, |
| VFS_I(ip)->i_mode, ip->i_diflags, ip->i_diflags2); |
| if (failaddr) { |
| ip->i_diflags2 &= ~XFS_DIFLAG2_COWEXTSIZE; |
| ip->i_cowextsize = 0; |
| } |
| } |
| |
| /* |
| * If we need to create attributes immediately after allocating the inode, |
| * initialise an empty attribute fork right now. We use the default fork offset |
| * for attributes here as we don't know exactly what size or how many |
| * attributes we might be adding. We can do this safely here because we know |
| * the data fork is completely empty and this saves us from needing to run a |
| * separate transaction to set the fork offset in the immediate future. |
| * |
| * If we have parent pointers and the caller hasn't told us that the file will |
| * never be linked into a directory tree, we /must/ create the attr fork. |
| */ |
| static inline bool |
| xfs_icreate_want_attrfork( |
| struct xfs_mount *mp, |
| const struct xfs_icreate_args *args) |
| { |
| if (args->flags & XFS_ICREATE_INIT_XATTRS) |
| return true; |
| |
| if (!(args->flags & XFS_ICREATE_UNLINKABLE) && xfs_has_parent(mp)) |
| return true; |
| |
| return false; |
| } |
| |
| /* Initialise an inode's attributes. */ |
| void |
| xfs_inode_init( |
| struct xfs_trans *tp, |
| const struct xfs_icreate_args *args, |
| struct xfs_inode *ip) |
| { |
| struct xfs_inode *pip = args->pip; |
| struct inode *dir = pip ? VFS_I(pip) : NULL; |
| struct xfs_mount *mp = tp->t_mountp; |
| struct inode *inode = VFS_I(ip); |
| unsigned int flags; |
| int times = XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG | |
| XFS_ICHGTIME_ACCESS; |
| |
| if (args->flags & XFS_ICREATE_TMPFILE) |
| set_nlink(inode, 0); |
| else if (S_ISDIR(args->mode)) |
| set_nlink(inode, 2); |
| else |
| set_nlink(inode, 1); |
| inode->i_rdev = args->rdev; |
| |
| if (!args->idmap || pip == NULL) { |
| /* creating a tree root, sb rooted, or detached file */ |
| inode->i_uid = GLOBAL_ROOT_UID; |
| inode->i_gid = GLOBAL_ROOT_GID; |
| ip->i_projid = 0; |
| inode->i_mode = args->mode; |
| } else { |
| /* creating a child in the directory tree */ |
| if (dir && !(dir->i_mode & S_ISGID) && xfs_has_grpid(mp)) { |
| inode_fsuid_set(inode, args->idmap); |
| inode->i_gid = dir->i_gid; |
| inode->i_mode = args->mode; |
| } else { |
| inode_init_owner(args->idmap, inode, dir, args->mode); |
| } |
| |
| /* |
| * If the group ID of the new file does not match the effective |
| * group ID or one of the supplementary group IDs, the S_ISGID |
| * bit is cleared (and only if the irix_sgid_inherit |
| * compatibility variable is set). |
| */ |
| if (irix_sgid_inherit && (inode->i_mode & S_ISGID) && |
| !vfsgid_in_group_p(i_gid_into_vfsgid(args->idmap, inode))) |
| inode->i_mode &= ~S_ISGID; |
| |
| ip->i_projid = pip ? xfs_get_initial_prid(pip) : 0; |
| } |
| |
| ip->i_disk_size = 0; |
| ip->i_df.if_nextents = 0; |
| ASSERT(ip->i_nblocks == 0); |
| |
| ip->i_extsize = 0; |
| ip->i_diflags = 0; |
| |
| if (xfs_has_v3inodes(mp)) { |
| inode_set_iversion(inode, 1); |
| ip->i_cowextsize = 0; |
| times |= XFS_ICHGTIME_CREATE; |
| } |
| |
| xfs_trans_ichgtime(tp, ip, times); |
| |
| flags = XFS_ILOG_CORE; |
| switch (args->mode & S_IFMT) { |
| case S_IFIFO: |
| case S_IFCHR: |
| case S_IFBLK: |
| case S_IFSOCK: |
| ip->i_df.if_format = XFS_DINODE_FMT_DEV; |
| flags |= XFS_ILOG_DEV; |
| break; |
| case S_IFREG: |
| case S_IFDIR: |
| if (pip && (pip->i_diflags & XFS_DIFLAG_ANY)) |
| xfs_inode_inherit_flags(ip, pip); |
| if (pip && (pip->i_diflags2 & XFS_DIFLAG2_ANY)) |
| xfs_inode_inherit_flags2(ip, pip); |
| fallthrough; |
| case S_IFLNK: |
| ip->i_df.if_format = XFS_DINODE_FMT_EXTENTS; |
| ip->i_df.if_bytes = 0; |
| ip->i_df.if_data = NULL; |
| break; |
| default: |
| ASSERT(0); |
| } |
| |
| if (xfs_icreate_want_attrfork(mp, args)) { |
| ip->i_forkoff = xfs_default_attroffset(ip) >> 3; |
| xfs_ifork_init_attr(ip, XFS_DINODE_FMT_EXTENTS, 0); |
| |
| if (!xfs_has_attr(mp)) { |
| spin_lock(&mp->m_sb_lock); |
| xfs_add_attr(mp); |
| spin_unlock(&mp->m_sb_lock); |
| xfs_log_sb(tp); |
| } |
| } |
| |
| xfs_trans_log_inode(tp, ip, flags); |
| } |
| |
| /* |
| * In-Core Unlinked List Lookups |
| * ============================= |
| * |
| * Every inode is supposed to be reachable from some other piece of metadata |
| * with the exception of the root directory. Inodes with a connection to a |
| * file descriptor but not linked from anywhere in the on-disk directory tree |
| * are collectively known as unlinked inodes, though the filesystem itself |
| * maintains links to these inodes so that on-disk metadata are consistent. |
| * |
| * XFS implements a per-AG on-disk hash table of unlinked inodes. The AGI |
| * header contains a number of buckets that point to an inode, and each inode |
| * record has a pointer to the next inode in the hash chain. This |
| * singly-linked list causes scaling problems in the iunlink remove function |
| * because we must walk that list to find the inode that points to the inode |
| * being removed from the unlinked hash bucket list. |
| * |
| * Hence we keep an in-memory double linked list to link each inode on an |
| * unlinked list. Because there are 64 unlinked lists per AGI, keeping pointer |
| * based lists would require having 64 list heads in the perag, one for each |
| * list. This is expensive in terms of memory (think millions of AGs) and cache |
| * misses on lookups. Instead, use the fact that inodes on the unlinked list |
| * must be referenced at the VFS level to keep them on the list and hence we |
| * have an existence guarantee for inodes on the unlinked list. |
| * |
| * Given we have an existence guarantee, we can use lockless inode cache lookups |
| * to resolve aginos to xfs inodes. This means we only need 8 bytes per inode |
| * for the double linked unlinked list, and we don't need any extra locking to |
| * keep the list safe as all manipulations are done under the AGI buffer lock. |
| * Keeping the list up to date does not require memory allocation, just finding |
| * the XFS inode and updating the next/prev unlinked list aginos. |
| */ |
| |
| /* |
| * Update the prev pointer of the next agino. Returns -ENOLINK if the inode |
| * is not in cache. |
| */ |
| static int |
| xfs_iunlink_update_backref( |
| struct xfs_perag *pag, |
| xfs_agino_t prev_agino, |
| xfs_agino_t next_agino) |
| { |
| struct xfs_inode *ip; |
| |
| /* No update necessary if we are at the end of the list. */ |
| if (next_agino == NULLAGINO) |
| return 0; |
| |
| ip = xfs_iunlink_lookup(pag, next_agino); |
| if (!ip) |
| return -ENOLINK; |
| |
| ip->i_prev_unlinked = prev_agino; |
| return 0; |
| } |
| |
| /* |
| * Point the AGI unlinked bucket at an inode and log the results. The caller |
| * is responsible for validating the old value. |
| */ |
| STATIC int |
| xfs_iunlink_update_bucket( |
| struct xfs_trans *tp, |
| struct xfs_perag *pag, |
| struct xfs_buf *agibp, |
| unsigned int bucket_index, |
| xfs_agino_t new_agino) |
| { |
| struct xfs_agi *agi = agibp->b_addr; |
| xfs_agino_t old_value; |
| int offset; |
| |
| ASSERT(xfs_verify_agino_or_null(pag, new_agino)); |
| |
| old_value = be32_to_cpu(agi->agi_unlinked[bucket_index]); |
| trace_xfs_iunlink_update_bucket(tp->t_mountp, pag->pag_agno, bucket_index, |
| old_value, new_agino); |
| |
| /* |
| * We should never find the head of the list already set to the value |
| * passed in because either we're adding or removing ourselves from the |
| * head of the list. |
| */ |
| if (old_value == new_agino) { |
| xfs_buf_mark_corrupt(agibp); |
| xfs_ag_mark_sick(pag, XFS_SICK_AG_AGI); |
| return -EFSCORRUPTED; |
| } |
| |
| agi->agi_unlinked[bucket_index] = cpu_to_be32(new_agino); |
| offset = offsetof(struct xfs_agi, agi_unlinked) + |
| (sizeof(xfs_agino_t) * bucket_index); |
| xfs_trans_log_buf(tp, agibp, offset, offset + sizeof(xfs_agino_t) - 1); |
| return 0; |
| } |
| |
| static int |
| xfs_iunlink_insert_inode( |
| struct xfs_trans *tp, |
| struct xfs_perag *pag, |
| struct xfs_buf *agibp, |
| struct xfs_inode *ip) |
| { |
| struct xfs_mount *mp = tp->t_mountp; |
| struct xfs_agi *agi = agibp->b_addr; |
| xfs_agino_t next_agino; |
| xfs_agino_t agino = XFS_INO_TO_AGINO(mp, ip->i_ino); |
| short bucket_index = agino % XFS_AGI_UNLINKED_BUCKETS; |
| int error; |
| |
| /* |
| * Get the index into the agi hash table for the list this inode will |
| * go on. Make sure the pointer isn't garbage and that this inode |
| * isn't already on the list. |
| */ |
| next_agino = be32_to_cpu(agi->agi_unlinked[bucket_index]); |
| if (next_agino == agino || |
| !xfs_verify_agino_or_null(pag, next_agino)) { |
| xfs_buf_mark_corrupt(agibp); |
| xfs_ag_mark_sick(pag, XFS_SICK_AG_AGI); |
| return -EFSCORRUPTED; |
| } |
| |
| /* |
| * Update the prev pointer in the next inode to point back to this |
| * inode. |
| */ |
| error = xfs_iunlink_update_backref(pag, agino, next_agino); |
| if (error == -ENOLINK) |
| error = xfs_iunlink_reload_next(tp, agibp, agino, next_agino); |
| if (error) |
| return error; |
| |
| if (next_agino != NULLAGINO) { |
| /* |
| * There is already another inode in the bucket, so point this |
| * inode to the current head of the list. |
| */ |
| error = xfs_iunlink_log_inode(tp, ip, pag, next_agino); |
| if (error) |
| return error; |
| ip->i_next_unlinked = next_agino; |
| } |
| |
| /* Point the head of the list to point to this inode. */ |
| ip->i_prev_unlinked = NULLAGINO; |
| return xfs_iunlink_update_bucket(tp, pag, agibp, bucket_index, agino); |
| } |
| |
| /* |
| * This is called when the inode's link count has gone to 0 or we are creating |
| * a tmpfile via O_TMPFILE. The inode @ip must have nlink == 0. |
| * |
| * We place the on-disk inode on a list in the AGI. It will be pulled from this |
| * list when the inode is freed. |
| */ |
| int |
| xfs_iunlink( |
| struct xfs_trans *tp, |
| struct xfs_inode *ip) |
| { |
| struct xfs_mount *mp = tp->t_mountp; |
| struct xfs_perag *pag; |
| struct xfs_buf *agibp; |
| int error; |
| |
| ASSERT(VFS_I(ip)->i_nlink == 0); |
| ASSERT(VFS_I(ip)->i_mode != 0); |
| trace_xfs_iunlink(ip); |
| |
| pag = xfs_perag_get(mp, XFS_INO_TO_AGNO(mp, ip->i_ino)); |
| |
| /* Get the agi buffer first. It ensures lock ordering on the list. */ |
| error = xfs_read_agi(pag, tp, 0, &agibp); |
| if (error) |
| goto out; |
| |
| error = xfs_iunlink_insert_inode(tp, pag, agibp, ip); |
| out: |
| xfs_perag_put(pag); |
| return error; |
| } |
| |
| static int |
| xfs_iunlink_remove_inode( |
| struct xfs_trans *tp, |
| struct xfs_perag *pag, |
| struct xfs_buf *agibp, |
| struct xfs_inode *ip) |
| { |
| struct xfs_mount *mp = tp->t_mountp; |
| struct xfs_agi *agi = agibp->b_addr; |
| xfs_agino_t agino = XFS_INO_TO_AGINO(mp, ip->i_ino); |
| xfs_agino_t head_agino; |
| short bucket_index = agino % XFS_AGI_UNLINKED_BUCKETS; |
| int error; |
| |
| trace_xfs_iunlink_remove(ip); |
| |
| /* |
| * Get the index into the agi hash table for the list this inode will |
| * go on. Make sure the head pointer isn't garbage. |
| */ |
| head_agino = be32_to_cpu(agi->agi_unlinked[bucket_index]); |
| if (!xfs_verify_agino(pag, head_agino)) { |
| XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp, |
| agi, sizeof(*agi)); |
| xfs_ag_mark_sick(pag, XFS_SICK_AG_AGI); |
| return -EFSCORRUPTED; |
| } |
| |
| /* |
| * Set our inode's next_unlinked pointer to NULL and then return |
| * the old pointer value so that we can update whatever was previous |
| * to us in the list to point to whatever was next in the list. |
| */ |
| error = xfs_iunlink_log_inode(tp, ip, pag, NULLAGINO); |
| if (error) |
| return error; |
| |
| /* |
| * Update the prev pointer in the next inode to point back to previous |
| * inode in the chain. |
| */ |
| error = xfs_iunlink_update_backref(pag, ip->i_prev_unlinked, |
| ip->i_next_unlinked); |
| if (error == -ENOLINK) |
| error = xfs_iunlink_reload_next(tp, agibp, ip->i_prev_unlinked, |
| ip->i_next_unlinked); |
| if (error) |
| return error; |
| |
| if (head_agino != agino) { |
| struct xfs_inode *prev_ip; |
| |
| prev_ip = xfs_iunlink_lookup(pag, ip->i_prev_unlinked); |
| if (!prev_ip) { |
| xfs_inode_mark_sick(ip, XFS_SICK_INO_CORE); |
| return -EFSCORRUPTED; |
| } |
| |
| error = xfs_iunlink_log_inode(tp, prev_ip, pag, |
| ip->i_next_unlinked); |
| prev_ip->i_next_unlinked = ip->i_next_unlinked; |
| } else { |
| /* Point the head of the list to the next unlinked inode. */ |
| error = xfs_iunlink_update_bucket(tp, pag, agibp, bucket_index, |
| ip->i_next_unlinked); |
| } |
| |
| ip->i_next_unlinked = NULLAGINO; |
| ip->i_prev_unlinked = 0; |
| return error; |
| } |
| |
| /* |
| * Pull the on-disk inode from the AGI unlinked list. |
| */ |
| int |
| xfs_iunlink_remove( |
| struct xfs_trans *tp, |
| struct xfs_perag *pag, |
| struct xfs_inode *ip) |
| { |
| struct xfs_buf *agibp; |
| int error; |
| |
| trace_xfs_iunlink_remove(ip); |
| |
| /* Get the agi buffer first. It ensures lock ordering on the list. */ |
| error = xfs_read_agi(pag, tp, 0, &agibp); |
| if (error) |
| return error; |
| |
| return xfs_iunlink_remove_inode(tp, pag, agibp, ip); |
| } |
| |
| /* |
| * Decrement the link count on an inode & log the change. If this causes the |
| * link count to go to zero, move the inode to AGI unlinked list so that it can |
| * be freed when the last active reference goes away via xfs_inactive(). |
| */ |
| int |
| xfs_droplink( |
| struct xfs_trans *tp, |
| struct xfs_inode *ip) |
| { |
| struct inode *inode = VFS_I(ip); |
| |
| xfs_trans_ichgtime(tp, ip, XFS_ICHGTIME_CHG); |
| |
| if (inode->i_nlink == 0) { |
| xfs_info_ratelimited(tp->t_mountp, |
| "Inode 0x%llx link count dropped below zero. Pinning link count.", |
| ip->i_ino); |
| set_nlink(inode, XFS_NLINK_PINNED); |
| } |
| if (inode->i_nlink != XFS_NLINK_PINNED) |
| drop_nlink(inode); |
| |
| xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); |
| |
| if (inode->i_nlink) |
| return 0; |
| |
| return xfs_iunlink(tp, ip); |
| } |
| |
| /* |
| * Increment the link count on an inode & log the change. |
| */ |
| void |
| xfs_bumplink( |
| struct xfs_trans *tp, |
| struct xfs_inode *ip) |
| { |
| struct inode *inode = VFS_I(ip); |
| |
| xfs_trans_ichgtime(tp, ip, XFS_ICHGTIME_CHG); |
| |
| if (inode->i_nlink == XFS_NLINK_PINNED - 1) |
| xfs_info_ratelimited(tp->t_mountp, |
| "Inode 0x%llx link count exceeded maximum. Pinning link count.", |
| ip->i_ino); |
| if (inode->i_nlink != XFS_NLINK_PINNED) |
| inc_nlink(inode); |
| |
| xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); |
| } |
| |
| /* Free an inode in the ondisk index and zero it out. */ |
| int |
| xfs_inode_uninit( |
| struct xfs_trans *tp, |
| struct xfs_perag *pag, |
| struct xfs_inode *ip, |
| struct xfs_icluster *xic) |
| { |
| struct xfs_mount *mp = ip->i_mount; |
| int error; |
| |
| /* |
| * Free the inode first so that we guarantee that the AGI lock is going |
| * to be taken before we remove the inode from the unlinked list. This |
| * makes the AGI lock -> unlinked list modification order the same as |
| * used in O_TMPFILE creation. |
| */ |
| error = xfs_difree(tp, pag, ip->i_ino, xic); |
| if (error) |
| return error; |
| |
| error = xfs_iunlink_remove(tp, pag, ip); |
| if (error) |
| return error; |
| |
| /* |
| * Free any local-format data sitting around before we reset the |
| * data fork to extents format. Note that the attr fork data has |
| * already been freed by xfs_attr_inactive. |
| */ |
| if (ip->i_df.if_format == XFS_DINODE_FMT_LOCAL) { |
| kfree(ip->i_df.if_data); |
| ip->i_df.if_data = NULL; |
| ip->i_df.if_bytes = 0; |
| } |
| |
| VFS_I(ip)->i_mode = 0; /* mark incore inode as free */ |
| ip->i_diflags = 0; |
| ip->i_diflags2 = mp->m_ino_geo.new_diflags2; |
| ip->i_forkoff = 0; /* mark the attr fork not in use */ |
| ip->i_df.if_format = XFS_DINODE_FMT_EXTENTS; |
| |
| /* |
| * Bump the generation count so no one will be confused |
| * by reincarnations of this inode. |
| */ |
| VFS_I(ip)->i_generation++; |
| xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); |
| return 0; |
| } |