| /* |
| * Copyright (c) 2000-2002,2004-2005 Silicon Graphics, Inc. |
| * All Rights Reserved. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it would be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include <libxfs.h> |
| #include "globals.h" |
| #include "err_protos.h" |
| #include "attr_repair.h" |
| #include "dinode.h" |
| #include "bmap.h" |
| #include "protos.h" |
| #include "dir2.h" |
| |
| static int xfs_acl_valid(struct xfs_mount *mp, struct xfs_acl *daclp); |
| static int xfs_mac_valid(xfs_mac_label_t *lp); |
| |
| /* |
| * da node check/verify functions that the attribute tree relies on are first in |
| * the file before the actual attribute code. This used to be shared with the |
| * dir v1 code, but that format is no longer supported yb the userspace |
| * utilities and hence is now specific to the attribute tree implementation. |
| */ |
| #define XR_DA_LEAF_MAPSIZE XFS_ATTR_LEAF_MAPSIZE |
| |
| typedef unsigned char da_freemap_t; |
| |
| /* |
| * the cursor gets passed up and down the da btree processing |
| * routines. The interior block processing routines use the |
| * cursor to determine if the pointers to and from the preceding |
| * and succeeding sibling blocks are ok and whether the values in |
| * the current block are consistent with the entries in the parent |
| * nodes. When a block is traversed, a parent-verification routine |
| * is called to verify if the next logical entry in the next level up |
| * is consistent with the greatest hashval in the next block of the |
| * current level. The verification routine is itself recursive and |
| * calls itself if it has to traverse an interior block to get |
| * the next logical entry. The routine recurses upwards through |
| * the tree until it finds a block where it can simply step to |
| * the next entry. The hashval in that entry should be equal to |
| * the hashval being passed to it (the greatest hashval in the block |
| * that the entry points to). If that isn't true, then the tree |
| * is blown and we need to trash it, salvage and trash it, or fix it. |
| * Currently, we just trash it. |
| */ |
| typedef struct da_level_state { |
| xfs_buf_t *bp; /* block bp */ |
| #ifdef XR_DIR_TRACE |
| xfs_da_intnode_t *n; /* bp data */ |
| #endif |
| xfs_dablk_t bno; /* file block number */ |
| xfs_dahash_t hashval; /* last verified hashval */ |
| int index; /* current index in block */ |
| int dirty; /* is buffer dirty ? (1 == yes) */ |
| } da_level_state_t; |
| |
| typedef struct da_bt_cursor { |
| int active; /* highest level in tree (# levels-1) */ |
| int type; /* 0 if dir, 1 if attr */ |
| xfs_ino_t ino; |
| xfs_dablk_t greatest_bno; |
| xfs_dinode_t *dip; |
| da_level_state_t level[XFS_DA_NODE_MAXDEPTH]; |
| struct blkmap *blkmap; |
| } da_bt_cursor_t; |
| |
| |
| /* |
| * Allocate a freespace map for directory or attr leaf blocks (1 bit per byte) |
| * 1 == used, 0 == free. |
| */ |
| static da_freemap_t * |
| alloc_da_freemap(struct xfs_mount *mp) |
| { |
| return calloc(1, mp->m_sb.sb_blocksize / NBBY); |
| } |
| |
| /* |
| * Set the he range [start, stop) in the directory freemap. |
| * |
| * Returns 1 if there is a conflict or 0 if everything's good. |
| * |
| * Within a char, the lowest bit of the char represents the byte with |
| * the smallest address |
| */ |
| static int |
| set_da_freemap(xfs_mount_t *mp, da_freemap_t *map, int start, int stop) |
| { |
| const da_freemap_t mask = 0x1; |
| int i; |
| |
| if (start > stop) { |
| /* |
| * allow == relation since [x, x) claims 1 byte |
| */ |
| do_warn(_("bad range claimed [%d, %d) in da block\n"), |
| start, stop); |
| return(1); |
| } |
| |
| if (stop > mp->m_sb.sb_blocksize) { |
| do_warn( |
| _("byte range end [%d %d) in da block larger than blocksize %d\n"), |
| start, stop, mp->m_sb.sb_blocksize); |
| return(1); |
| } |
| |
| for (i = start; i < stop; i ++) { |
| if (map[i / NBBY] & (mask << i % NBBY)) { |
| do_warn(_("multiply claimed byte %d in da block\n"), i); |
| return(1); |
| } |
| map[i / NBBY] |= (mask << i % NBBY); |
| } |
| |
| return(0); |
| } |
| |
| /* |
| * walk tree from root to the left-most leaf block reading in |
| * blocks and setting up cursor. passes back file block number of the |
| * left-most leaf block if successful (bno). returns 1 if successful, |
| * 0 if unsuccessful. |
| */ |
| static int |
| traverse_int_dablock(xfs_mount_t *mp, |
| da_bt_cursor_t *da_cursor, |
| xfs_dablk_t *rbno, |
| int whichfork) |
| { |
| xfs_dablk_t bno; |
| int i; |
| xfs_da_intnode_t *node; |
| xfs_fsblock_t fsbno; |
| xfs_buf_t *bp; |
| struct xfs_da_node_entry *btree; |
| struct xfs_da3_icnode_hdr nodehdr; |
| |
| /* |
| * traverse down left-side of tree until we hit the |
| * left-most leaf block setting up the btree cursor along |
| * the way. |
| */ |
| bno = 0; |
| i = -1; |
| node = NULL; |
| da_cursor->active = 0; |
| |
| do { |
| /* |
| * read in each block along the way and set up cursor |
| */ |
| fsbno = blkmap_get(da_cursor->blkmap, bno); |
| |
| if (fsbno == NULLFSBLOCK) |
| goto error_out; |
| |
| bp = libxfs_readbuf(mp->m_dev, XFS_FSB_TO_DADDR(mp, fsbno), |
| XFS_FSB_TO_BB(mp, 1), 0, &xfs_da3_node_buf_ops); |
| if (!bp) { |
| if (whichfork == XFS_DATA_FORK) |
| do_warn( |
| _("can't read block %u (fsbno %" PRIu64 ") for directory inode %" PRIu64 "\n"), |
| bno, fsbno, da_cursor->ino); |
| else |
| do_warn( |
| _("can't read block %u (fsbno %" PRIu64 ") for attrbute fork of inode %" PRIu64 "\n"), |
| bno, fsbno, da_cursor->ino); |
| goto error_out; |
| } |
| |
| node = (xfs_da_intnode_t *)XFS_BUF_PTR(bp); |
| btree = M_DIROPS(mp)->node_tree_p(node); |
| M_DIROPS(mp)->node_hdr_from_disk(&nodehdr, node); |
| |
| if (nodehdr.magic != XFS_DA_NODE_MAGIC && |
| nodehdr.magic != XFS_DA3_NODE_MAGIC) { |
| do_warn(_("bad dir/attr magic number in inode %" PRIu64 ", " |
| "file bno = %u, fsbno = %" PRIu64 "\n"), |
| da_cursor->ino, bno, fsbno); |
| libxfs_putbuf(bp); |
| goto error_out; |
| } |
| |
| if (nodehdr.count > mp->m_attr_geo->node_ents) { |
| do_warn(_("bad record count in inode %" PRIu64 ", " |
| "count = %d, max = %d\n"), |
| da_cursor->ino, |
| nodehdr.count, |
| mp->m_attr_geo->node_ents); |
| libxfs_putbuf(bp); |
| goto error_out; |
| } |
| |
| /* |
| * maintain level counter |
| */ |
| if (i == -1) |
| i = da_cursor->active = nodehdr.level; |
| else { |
| if (nodehdr.level == i - 1) { |
| i--; |
| } else { |
| if (whichfork == XFS_DATA_FORK) |
| do_warn(_("bad directory btree for " |
| "directory inode %" PRIu64 "\n"), |
| da_cursor->ino); |
| else |
| do_warn(_("bad attribute fork btree " |
| "for inode %" PRIu64 "\n"), |
| da_cursor->ino); |
| libxfs_putbuf(bp); |
| goto error_out; |
| } |
| } |
| |
| da_cursor->level[i].hashval = be32_to_cpu(btree[0].hashval); |
| da_cursor->level[i].bp = bp; |
| da_cursor->level[i].bno = bno; |
| da_cursor->level[i].index = 0; |
| #ifdef XR_DIR_TRACE |
| da_cursor->level[i].n = XFS_BUF_TO_DA_INTNODE(bp); |
| #endif |
| |
| /* |
| * set up new bno for next level down |
| */ |
| bno = be32_to_cpu(btree[0].before); |
| } while (node != NULL && i > 1); |
| |
| /* |
| * now return block number and get out |
| */ |
| *rbno = da_cursor->level[0].bno = bno; |
| return(1); |
| |
| error_out: |
| while (i > 1 && i <= da_cursor->active) { |
| libxfs_putbuf(da_cursor->level[i].bp); |
| i++; |
| } |
| |
| return(0); |
| } |
| |
| /* |
| * blow out buffer for this level and all the rest above as well |
| * if error == 0, we are not expecting to encounter any unreleased |
| * buffers (e.g. if we do, it's a mistake). if error == 1, we're |
| * in an error-handling case so unreleased buffers may exist. |
| */ |
| static void |
| release_da_cursor_int(xfs_mount_t *mp, |
| da_bt_cursor_t *cursor, |
| int prev_level, |
| int error) |
| { |
| int level = prev_level + 1; |
| |
| if (cursor->level[level].bp != NULL) { |
| if (!error) { |
| do_warn(_("release_da_cursor_int got unexpected " |
| "non-null bp, dabno = %u\n"), |
| cursor->level[level].bno); |
| } |
| ASSERT(error != 0); |
| |
| libxfs_putbuf(cursor->level[level].bp); |
| cursor->level[level].bp = NULL; |
| } |
| |
| if (level < cursor->active) |
| release_da_cursor_int(mp, cursor, level, error); |
| |
| return; |
| } |
| |
| static void |
| release_da_cursor(xfs_mount_t *mp, |
| da_bt_cursor_t *cursor, |
| int prev_level) |
| { |
| release_da_cursor_int(mp, cursor, prev_level, 0); |
| } |
| |
| static void |
| err_release_da_cursor(xfs_mount_t *mp, |
| da_bt_cursor_t *cursor, |
| int prev_level) |
| { |
| release_da_cursor_int(mp, cursor, prev_level, 1); |
| } |
| |
| /* |
| * make sure that all entries in all blocks along the right side of |
| * of the tree are used and hashval's are consistent. level is the |
| * level of the descendent block. returns 0 if good (even if it had |
| * to be fixed up), and 1 if bad. The right edge of the tree is |
| * technically a block boundary. this routine should be used then |
| * instead of verify_da_path(). |
| */ |
| static int |
| verify_final_da_path(xfs_mount_t *mp, |
| da_bt_cursor_t *cursor, |
| const int p_level) |
| { |
| xfs_da_intnode_t *node; |
| xfs_dahash_t hashval; |
| int bad = 0; |
| int entry; |
| int this_level = p_level + 1; |
| struct xfs_da_node_entry *btree; |
| struct xfs_da3_icnode_hdr nodehdr; |
| |
| #ifdef XR_DIR_TRACE |
| fprintf(stderr, "in verify_final_da_path, this_level = %d\n", |
| this_level); |
| #endif |
| /* |
| * the index should point to the next "unprocessed" entry |
| * in the block which should be the final (rightmost) entry |
| */ |
| entry = cursor->level[this_level].index; |
| node = (xfs_da_intnode_t *)XFS_BUF_PTR(cursor->level[this_level].bp); |
| btree = M_DIROPS(mp)->node_tree_p(node); |
| M_DIROPS(mp)->node_hdr_from_disk(&nodehdr, node); |
| |
| /* |
| * check internal block consistency on this level -- ensure |
| * that all entries are used, encountered and expected hashvals |
| * match, etc. |
| */ |
| if (entry != nodehdr.count - 1) { |
| do_warn(_("directory/attribute block used/count " |
| "inconsistency - %d/%hu\n"), |
| entry, nodehdr.count); |
| bad++; |
| } |
| /* |
| * hash values monotonically increasing ??? |
| */ |
| if (cursor->level[this_level].hashval >= |
| be32_to_cpu(btree[entry].hashval)) { |
| do_warn(_("directory/attribute block hashvalue inconsistency, " |
| "expected > %u / saw %u\n"), |
| cursor->level[this_level].hashval, |
| be32_to_cpu(btree[entry].hashval)); |
| bad++; |
| } |
| if (nodehdr.forw != 0) { |
| do_warn(_("bad directory/attribute forward block pointer, " |
| "expected 0, saw %u\n"), |
| nodehdr.forw); |
| bad++; |
| } |
| if (bad) { |
| do_warn(_("bad directory block in dir ino %" PRIu64 "\n"), |
| cursor->ino); |
| return(1); |
| } |
| /* |
| * keep track of greatest block # -- that gets |
| * us the length of the directory |
| */ |
| if (cursor->level[this_level].bno > cursor->greatest_bno) |
| cursor->greatest_bno = cursor->level[this_level].bno; |
| |
| /* |
| * ok, now check descendant block number against this level |
| */ |
| if (cursor->level[p_level].bno != be32_to_cpu(btree[entry].before)) { |
| #ifdef XR_DIR_TRACE |
| fprintf(stderr, "bad directory btree pointer, child bno should " |
| "be %d, block bno is %d, hashval is %u\n", |
| be16_to_cpu(btree[entry].before), |
| cursor->level[p_level].bno, |
| cursor->level[p_level].hashval); |
| fprintf(stderr, "verify_final_da_path returns 1 (bad) #1a\n"); |
| #endif |
| return(1); |
| } |
| |
| if (cursor->level[p_level].hashval != be32_to_cpu(btree[entry].hashval)) { |
| if (!no_modify) { |
| do_warn(_("correcting bad hashval in non-leaf " |
| "dir/attr block\n\tin (level %d) in " |
| "inode %" PRIu64 ".\n"), |
| this_level, cursor->ino); |
| btree[entry].hashval = cpu_to_be32( |
| cursor->level[p_level].hashval); |
| cursor->level[this_level].dirty++; |
| } else { |
| do_warn(_("would correct bad hashval in non-leaf " |
| "dir/attr block\n\tin (level %d) in " |
| "inode %" PRIu64 ".\n"), |
| this_level, cursor->ino); |
| } |
| } |
| |
| /* |
| * Note: squirrel hashval away _before_ releasing the |
| * buffer, preventing a use-after-free problem. |
| */ |
| hashval = be32_to_cpu(btree[entry].hashval); |
| |
| /* |
| * release/write buffer |
| */ |
| ASSERT(cursor->level[this_level].dirty == 0 || |
| (cursor->level[this_level].dirty && !no_modify)); |
| |
| if (cursor->level[this_level].dirty && !no_modify) |
| libxfs_writebuf(cursor->level[this_level].bp, 0); |
| else |
| libxfs_putbuf(cursor->level[this_level].bp); |
| |
| cursor->level[this_level].bp = NULL; |
| |
| /* |
| * bail out if this is the root block (top of tree) |
| */ |
| if (this_level >= cursor->active) { |
| #ifdef XR_DIR_TRACE |
| fprintf(stderr, "verify_final_da_path returns 0 (ok)\n"); |
| #endif |
| return(0); |
| } |
| /* |
| * set hashvalue to correctly reflect the now-validated |
| * last entry in this block and continue upwards validation |
| */ |
| cursor->level[this_level].hashval = hashval; |
| return(verify_final_da_path(mp, cursor, this_level)); |
| } |
| |
| /* |
| * Verifies the path from a descendant block up to the root. |
| * Should be called when the descendant level traversal hits |
| * a block boundary before crossing the boundary (reading in a new |
| * block). |
| * |
| * the directory/attr btrees work differently to the other fs btrees. |
| * each interior block contains records that are <hashval, bno> |
| * pairs. The bno is a file bno, not a filesystem bno. The last |
| * hashvalue in the block <bno> will be <hashval>. BUT unlike |
| * the freespace btrees, the *last* value in each block gets |
| * propagated up the tree instead of the first value in each block. |
| * that is, the interior records point to child blocks and the *greatest* |
| * hash value contained by the child block is the one the block above |
| * uses as the key for the child block. |
| * |
| * level is the level of the descendent block. returns 0 if good, |
| * and 1 if bad. The descendant block may be a leaf block. |
| * |
| * the invariant here is that the values in the cursor for the |
| * levels beneath this level (this_level) and the cursor index |
| * for this level *must* be valid. |
| * |
| * that is, the hashval/bno info is accurate for all |
| * DESCENDANTS and match what the node[index] information |
| * for the current index in the cursor for this level. |
| * |
| * the index values in the cursor for the descendant level |
| * are allowed to be off by one as they will reflect the |
| * next entry at those levels to be processed. |
| * |
| * the hashvalue for the current level can't be set until |
| * we hit the last entry in the block so, it's garbage |
| * until set by this routine. |
| * |
| * bno and bp for the current block/level are always valid |
| * since they have to be set so we can get a buffer for the |
| * block. |
| */ |
| static int |
| verify_da_path(xfs_mount_t *mp, |
| da_bt_cursor_t *cursor, |
| const int p_level) |
| { |
| xfs_da_intnode_t *node; |
| xfs_da_intnode_t *newnode; |
| xfs_fsblock_t fsbno; |
| xfs_dablk_t dabno; |
| xfs_buf_t *bp; |
| int bad; |
| int entry; |
| int this_level = p_level + 1; |
| struct xfs_da_node_entry *btree; |
| struct xfs_da3_icnode_hdr nodehdr; |
| |
| /* |
| * index is currently set to point to the entry that |
| * should be processed now in this level. |
| */ |
| entry = cursor->level[this_level].index; |
| node = (xfs_da_intnode_t *)XFS_BUF_PTR(cursor->level[this_level].bp); |
| btree = M_DIROPS(mp)->node_tree_p(node); |
| M_DIROPS(mp)->node_hdr_from_disk(&nodehdr, node); |
| |
| /* |
| * if this block is out of entries, validate this |
| * block and move on to the next block. |
| * and update cursor value for said level |
| */ |
| if (entry >= nodehdr.count) { |
| /* |
| * update the hash value for this level before |
| * validating it. bno value should be ok since |
| * it was set when the block was first read in. |
| */ |
| cursor->level[this_level].hashval = |
| be32_to_cpu(btree[entry - 1].hashval); |
| |
| /* |
| * keep track of greatest block # -- that gets |
| * us the length of the directory |
| */ |
| if (cursor->level[this_level].bno > cursor->greatest_bno) |
| cursor->greatest_bno = cursor->level[this_level].bno; |
| |
| /* |
| * validate the path for the current used-up block |
| * before we trash it |
| */ |
| if (verify_da_path(mp, cursor, this_level)) |
| return(1); |
| /* |
| * ok, now get the next buffer and check sibling pointers |
| */ |
| dabno = nodehdr.forw; |
| ASSERT(dabno != 0); |
| fsbno = blkmap_get(cursor->blkmap, dabno); |
| |
| if (fsbno == NULLFSBLOCK) { |
| do_warn(_("can't get map info for block %u " |
| "of directory inode %" PRIu64 "\n"), |
| dabno, cursor->ino); |
| return(1); |
| } |
| |
| bp = libxfs_readbuf(mp->m_dev, XFS_FSB_TO_DADDR(mp, fsbno), |
| XFS_FSB_TO_BB(mp, 1), 0, &xfs_da3_node_buf_ops); |
| if (!bp) { |
| do_warn( |
| _("can't read block %u (%" PRIu64 ") for directory inode %" PRIu64 "\n"), |
| dabno, fsbno, cursor->ino); |
| return(1); |
| } |
| |
| newnode = (xfs_da_intnode_t *)XFS_BUF_PTR(bp); |
| btree = M_DIROPS(mp)->node_tree_p(node); |
| M_DIROPS(mp)->node_hdr_from_disk(&nodehdr, newnode); |
| |
| /* |
| * verify magic number and back pointer, sanity-check |
| * entry count, verify level |
| */ |
| bad = 0; |
| if (nodehdr.magic != XFS_DA_NODE_MAGIC || |
| nodehdr.magic != XFS_DA3_NODE_MAGIC) { |
| do_warn( |
| _("bad magic number %x in block %u (%" PRIu64 ") for directory inode %" PRIu64 "\n"), |
| nodehdr.magic, |
| dabno, fsbno, cursor->ino); |
| bad++; |
| } |
| if (nodehdr.back != cursor->level[this_level].bno) { |
| do_warn( |
| _("bad back pointer in block %u (%"PRIu64 ") for directory inode %" PRIu64 "\n"), |
| dabno, fsbno, cursor->ino); |
| bad++; |
| } |
| if (nodehdr.count > mp->m_attr_geo->node_ents) { |
| do_warn( |
| _("entry count %d too large in block %u (%" PRIu64 ") for directory inode %" PRIu64 "\n"), |
| nodehdr.count, |
| dabno, fsbno, cursor->ino); |
| bad++; |
| } |
| if (nodehdr.level != this_level) { |
| do_warn( |
| _("bad level %d in block %u (%" PRIu64 ") for directory inode %" PRIu64 "\n"), |
| nodehdr.level, |
| dabno, fsbno, cursor->ino); |
| bad++; |
| } |
| if (bad) { |
| #ifdef XR_DIR_TRACE |
| fprintf(stderr, "verify_da_path returns 1 (bad) #4\n"); |
| #endif |
| libxfs_putbuf(bp); |
| return(1); |
| } |
| |
| /* |
| * update cursor, write out the *current* level if |
| * required. don't write out the descendant level |
| */ |
| ASSERT(cursor->level[this_level].dirty == 0 || |
| (cursor->level[this_level].dirty && !no_modify)); |
| |
| if (cursor->level[this_level].dirty && !no_modify) |
| libxfs_writebuf(cursor->level[this_level].bp, 0); |
| else |
| libxfs_putbuf(cursor->level[this_level].bp); |
| |
| /* switch cursor to point at the new buffer we just read */ |
| cursor->level[this_level].bp = bp; |
| cursor->level[this_level].dirty = 0; |
| cursor->level[this_level].bno = dabno; |
| cursor->level[this_level].hashval = |
| be32_to_cpu(btree[0].hashval); |
| #ifdef XR_DIR_TRACE |
| cursor->level[this_level].n = newnode; |
| #endif |
| entry = cursor->level[this_level].index = 0; |
| |
| /* |
| * We want to rewrite the buffer on a CRC error seeing as it |
| * contains what appears to be a valid node block, but only if |
| * we are fixing errors. |
| */ |
| if (bp->b_error == -EFSBADCRC && !no_modify) |
| cursor->level[this_level].dirty++; |
| } |
| /* |
| * ditto for block numbers |
| */ |
| if (cursor->level[p_level].bno != be32_to_cpu(btree[entry].before)) { |
| #ifdef XR_DIR_TRACE |
| fprintf(stderr, "bad directory btree pointer, child bno " |
| "should be %d, block bno is %d, hashval is %u\n", |
| be32_to_cpu(btree[entry].before), |
| cursor->level[p_level].bno, |
| cursor->level[p_level].hashval); |
| fprintf(stderr, "verify_da_path returns 1 (bad) #1a\n"); |
| #endif |
| return(1); |
| } |
| /* |
| * ok, now validate last hashvalue in the descendant |
| * block against the hashval in the current entry |
| */ |
| if (cursor->level[p_level].hashval != |
| be32_to_cpu(btree[entry].hashval)) { |
| if (!no_modify) { |
| do_warn(_("correcting bad hashval in interior " |
| "dir/attr block\n\tin (level %d) in " |
| "inode %" PRIu64 ".\n"), |
| this_level, cursor->ino); |
| btree[entry].hashval = cpu_to_be32( |
| cursor->level[p_level].hashval); |
| cursor->level[this_level].dirty++; |
| } else { |
| do_warn(_("would correct bad hashval in interior " |
| "dir/attr block\n\tin (level %d) in " |
| "inode %" PRIu64 ".\n"), |
| this_level, cursor->ino); |
| } |
| } |
| /* |
| * increment index for this level to point to next entry |
| * (which should point to the next descendant block) |
| */ |
| cursor->level[this_level].index++; |
| #ifdef XR_DIR_TRACE |
| fprintf(stderr, "verify_da_path returns 0 (ok)\n"); |
| #endif |
| return(0); |
| } |
| |
| /* |
| * For attribute repair, there are 3 formats to worry about. First, is |
| * shortform attributes which reside in the inode. Second is the leaf |
| * form, and lastly the btree. Much of this models after the directory |
| * structure so code resembles the directory repair cases. |
| * For shortform case, if an attribute looks corrupt, it is removed. |
| * If that leaves the shortform down to 0 attributes, it's okay and |
| * will appear to just have a null attribute fork. Some checks are done |
| * for validity of the value field based on what the security needs are. |
| * Calls will be made to xfs_mac_valid or xfs_acl_valid routines if the |
| * security attributes exist. They will be cleared if invalid. |
| * No other values will be checked. The DMF folks do not have current |
| * requirements, but may in the future. |
| * |
| * For leaf block attributes, it requires more processing. One sticky |
| * point is that the attributes can be local (within the leaf) or |
| * remote (outside the leaf in other blocks). Thinking of local only |
| * if you get a bad attribute, and want to delete just one, it's a-okay |
| * if it remains large enough to still be a leaf block attribute. Otherwise, |
| * it may have to be converted to shortform. How to convert this and when |
| * is an issue. This call is happening in Phase3. Phase5 will capture empty |
| * blocks, but Phase6 allows you to use the libxfs library which knows |
| * how to handle attributes in the kernel for converting formats. What we |
| * could do is mark an attribute to be cleared now, but in phase6 somehow |
| * have it cleared for real and then the format changed to shortform if |
| * applicable. Since this requires more work than I anticipate can be |
| * accomplished for the next release, we will instead just say any bad |
| * attribute in the leaf block will make the entire attribute fork be |
| * cleared. The simplest way to do that is to ignore the leaf format, and |
| * call clear_dinode_attr to just make a shortform attribute fork with |
| * zero entries. |
| * |
| * Another issue with handling repair on leaf attributes is the remote |
| * blocks. To make sure that they look good and are not used multiple times |
| * by the attribute fork, some mechanism to keep track of all them is necessary. |
| * Do this in the future, time permitting. For now, note that there is no |
| * check for remote blocks and their allocations. |
| * |
| * For btree formatted attributes, the model can follow directories. That |
| * would mean go down the tree to the leftmost leaf. From there moving down |
| * the links and processing each. They would call back up the tree, to verify |
| * that the tree structure is okay. Any problems will result in the attribute |
| * fork being emptied and put in shortform format. |
| */ |
| |
| /* |
| * This routine just checks what security needs are for attribute values |
| * only called when root flag is set, otherwise these names could exist in |
| * in user attribute land without a conflict. |
| * If value is non-zero, then a remote attribute is being passed in |
| */ |
| static int |
| valuecheck( |
| struct xfs_mount *mp, |
| char *namevalue, |
| char *value, |
| int namelen, |
| int valuelen) |
| { |
| /* for proper alignment issues, get the structs and memmove the values */ |
| xfs_mac_label_t macl; |
| void *valuep; |
| int clearit = 0; |
| |
| if ((namelen == SGI_ACL_FILE_SIZE && |
| strncmp(namevalue, SGI_ACL_FILE, SGI_ACL_FILE_SIZE) == 0) || |
| (namelen == SGI_ACL_DEFAULT_SIZE && |
| strncmp(namevalue, SGI_ACL_DEFAULT, SGI_ACL_DEFAULT_SIZE) == 0)) { |
| if (value == NULL) { |
| valuep = malloc(valuelen); |
| if (!valuep) |
| do_error(_("No memory for ACL check!\n")); |
| memcpy(valuep, namevalue + namelen, valuelen); |
| } else |
| valuep = value; |
| |
| if (xfs_acl_valid(mp, valuep) != 0) { |
| clearit = 1; |
| do_warn( |
| _("entry contains illegal value in attribute named SGI_ACL_FILE " |
| "or SGI_ACL_DEFAULT\n")); |
| } |
| |
| if (valuep != value) |
| free(valuep); |
| |
| } else if (strncmp(namevalue, SGI_MAC_FILE, SGI_MAC_FILE_SIZE) == 0) { |
| if (value == NULL) { |
| memset(&macl, 0, sizeof(xfs_mac_label_t)); |
| memmove(&macl, namevalue+namelen, valuelen); |
| valuep = &macl; |
| } else |
| valuep = value; |
| |
| if (xfs_mac_valid((xfs_mac_label_t *)valuep) != 1) { /* 1 is valid */ |
| /* |
| * if sysconf says MAC enabled, |
| * temp = mac_from_text("msenhigh/mintlow", NULL) |
| * copy it to value, update valuelen, totsize |
| * This causes pushing up or down of all following |
| * attributes, forcing a attribute format change!! |
| * else clearit = 1; |
| */ |
| clearit = 1; |
| do_warn( |
| _("entry contains illegal value in attribute named SGI_MAC_LABEL\n")); |
| } |
| } else if (strncmp(namevalue, SGI_CAP_FILE, SGI_CAP_FILE_SIZE) == 0) { |
| if ( valuelen != sizeof(xfs_cap_set_t)) { |
| clearit = 1; |
| do_warn( |
| _("entry contains illegal value in attribute named SGI_CAP_FILE\n")); |
| } |
| } |
| |
| return(clearit); |
| } |
| |
| |
| /* |
| * this routine validates the attributes in shortform format. |
| * a non-zero return repair value means certain attributes are bogus |
| * and were cleared if possible. Warnings do not generate error conditions |
| * if you cannot modify the structures. repair is set to 1, if anything |
| * was fixed. |
| */ |
| static int |
| process_shortform_attr( |
| struct xfs_mount *mp, |
| xfs_ino_t ino, |
| xfs_dinode_t *dip, |
| int *repair) |
| { |
| xfs_attr_shortform_t *asf; |
| xfs_attr_sf_entry_t *currententry, *nextentry, *tempentry; |
| int i, junkit; |
| int currentsize, remainingspace; |
| |
| *repair = 0; |
| |
| asf = (xfs_attr_shortform_t *) XFS_DFORK_APTR(dip); |
| |
| /* Assumption: hdr.totsize is less than a leaf block and was checked |
| * by lclinode for valid sizes. Check the count though. |
| */ |
| if (asf->hdr.count == 0) |
| /* then the total size should just be the header length */ |
| if (be16_to_cpu(asf->hdr.totsize) != sizeof(xfs_attr_sf_hdr_t)) { |
| /* whoops there's a discrepancy. Clear the hdr */ |
| if (!no_modify) { |
| do_warn( |
| _("there are no attributes in the fork for inode %" PRIu64 "\n"), |
| ino); |
| asf->hdr.totsize = |
| cpu_to_be16(sizeof(xfs_attr_sf_hdr_t)); |
| *repair = 1; |
| return(1); |
| } else { |
| do_warn( |
| _("would junk the attribute fork since count is 0 for inode %" PRIu64 "\n"), |
| ino); |
| return(1); |
| } |
| } |
| |
| currentsize = sizeof(xfs_attr_sf_hdr_t); |
| remainingspace = be16_to_cpu(asf->hdr.totsize) - currentsize; |
| nextentry = &asf->list[0]; |
| for (i = 0; i < asf->hdr.count; i++) { |
| currententry = nextentry; |
| junkit = 0; |
| |
| /* don't go off the end if the hdr.count was off */ |
| if ((currentsize + (sizeof(xfs_attr_sf_entry_t) - 1)) > |
| be16_to_cpu(asf->hdr.totsize)) |
| break; /* get out and reset count and totSize */ |
| |
| /* if the namelen is 0, can't get to the rest of the entries */ |
| if (currententry->namelen == 0) { |
| do_warn(_("zero length name entry in attribute fork,")); |
| if (!no_modify) { |
| do_warn( |
| _(" truncating attributes for inode %" PRIu64 " to %d\n"), ino, i); |
| *repair = 1; |
| break; /* and then update hdr fields */ |
| } else { |
| do_warn( |
| _(" would truncate attributes for inode %" PRIu64 " to %d\n"), ino, i); |
| break; |
| } |
| } else { |
| /* It's okay to have a 0 length valuelen, but do a |
| * rough check to make sure we haven't gone outside of |
| * totsize. |
| */ |
| if (remainingspace < currententry->namelen || |
| ((remainingspace - currententry-> |
| namelen) < currententry->valuelen)) { |
| do_warn( |
| _("name or value attribute lengths are too large,\n")); |
| if (!no_modify) { |
| do_warn( |
| _(" truncating attributes for inode %" PRIu64 " to %d\n"), |
| ino, i); |
| *repair = 1; |
| break; /* and then update hdr fields */ |
| } else { |
| do_warn( |
| _(" would truncate attributes for inode %" PRIu64 " to %d\n"), |
| ino, i); |
| break; |
| } |
| } |
| } |
| |
| /* namecheck checks for / and null terminated for file names. |
| * attributes names currently follow the same rules. |
| */ |
| if (namecheck((char *)¤tentry->nameval[0], |
| currententry->namelen)) { |
| do_warn( |
| _("entry contains illegal character in shortform attribute name\n")); |
| junkit = 1; |
| } |
| |
| if (currententry->flags & XFS_ATTR_INCOMPLETE) { |
| do_warn( |
| _("entry has INCOMPLETE flag on in shortform attribute\n")); |
| junkit = 1; |
| } |
| |
| /* Only check values for root security attributes */ |
| if (currententry->flags & XFS_ATTR_ROOT) |
| junkit |= valuecheck(mp, |
| (char *)¤tentry->nameval[0], |
| NULL, currententry->namelen, |
| currententry->valuelen); |
| |
| remainingspace = remainingspace - |
| XFS_ATTR_SF_ENTSIZE(currententry); |
| |
| if (junkit) { |
| if (!no_modify) { |
| /* get rid of only this entry */ |
| do_warn( |
| _("removing attribute entry %d for inode %" PRIu64 "\n"), |
| i, ino); |
| tempentry = (xfs_attr_sf_entry_t *) |
| ((__psint_t) currententry + |
| XFS_ATTR_SF_ENTSIZE(currententry)); |
| memmove(currententry,tempentry,remainingspace); |
| asf->hdr.count -= 1; |
| i--; /* no worries, it will wrap back to 0 */ |
| *repair = 1; |
| continue; /* go back up now */ |
| } else { |
| do_warn( |
| _("would remove attribute entry %d for inode %" PRIu64 "\n"), |
| i, ino); |
| } |
| } |
| |
| /* Let's get ready for the next entry... */ |
| nextentry = (xfs_attr_sf_entry_t *)((__psint_t) nextentry + |
| XFS_ATTR_SF_ENTSIZE(currententry)); |
| currentsize = currentsize + XFS_ATTR_SF_ENTSIZE(currententry); |
| |
| } /* end the loop */ |
| |
| if (asf->hdr.count != i) { |
| if (no_modify) { |
| do_warn( |
| _("would have corrected attribute entry count in inode %" PRIu64 " from %d to %d\n"), |
| ino, asf->hdr.count, i); |
| } else { |
| do_warn( |
| _("corrected attribute entry count in inode %" PRIu64 ", was %d, now %d\n"), |
| ino, asf->hdr.count, i); |
| asf->hdr.count = i; |
| *repair = 1; |
| } |
| } |
| |
| /* ASSUMPTION: currentsize <= totsize */ |
| if (be16_to_cpu(asf->hdr.totsize) != currentsize) { |
| if (no_modify) { |
| do_warn( |
| _("would have corrected attribute totsize in inode %" PRIu64 " from %d to %d\n"), |
| ino, be16_to_cpu(asf->hdr.totsize), |
| currentsize); |
| } else { |
| do_warn( |
| _("corrected attribute entry totsize in inode %" PRIu64 ", was %d, now %d\n"), |
| ino, be16_to_cpu(asf->hdr.totsize), |
| currentsize); |
| asf->hdr.totsize = cpu_to_be16(currentsize); |
| *repair = 1; |
| } |
| } |
| |
| return(*repair); |
| } |
| |
| /* This routine brings in blocks from disk one by one and assembles them |
| * in the value buffer. If get_bmapi gets smarter later to return an extent |
| * or list of extents, that would be great. For now, we don't expect too |
| * many blocks per remote value, so one by one is sufficient. |
| */ |
| static int |
| rmtval_get(xfs_mount_t *mp, xfs_ino_t ino, blkmap_t *blkmap, |
| xfs_dablk_t blocknum, int valuelen, char* value) |
| { |
| xfs_fsblock_t bno; |
| xfs_buf_t *bp; |
| int clearit = 0, i = 0, length = 0, amountdone = 0; |
| int hdrsize = 0; |
| |
| if (xfs_sb_version_hascrc(&mp->m_sb)) |
| hdrsize = sizeof(struct xfs_attr3_rmt_hdr); |
| |
| /* ASSUMPTION: valuelen is a valid number, so use it for looping */ |
| /* Note that valuelen is not a multiple of blocksize */ |
| while (amountdone < valuelen) { |
| bno = blkmap_get(blkmap, blocknum + i); |
| if (bno == NULLFSBLOCK) { |
| do_warn( |
| _("remote block for attributes of inode %" PRIu64 " is missing\n"), ino); |
| clearit = 1; |
| break; |
| } |
| bp = libxfs_readbuf(mp->m_dev, XFS_FSB_TO_DADDR(mp, bno), |
| XFS_FSB_TO_BB(mp, 1), 0, |
| &xfs_attr3_rmt_buf_ops); |
| if (!bp) { |
| do_warn( |
| _("can't read remote block for attributes of inode %" PRIu64 "\n"), ino); |
| clearit = 1; |
| break; |
| } |
| |
| if (bp->b_error == -EFSBADCRC || bp->b_error == -EFSCORRUPTED) { |
| do_warn( |
| _("Corrupt remote block for attributes of inode %" PRIu64 "\n"), ino); |
| clearit = 1; |
| break; |
| } |
| |
| ASSERT(mp->m_sb.sb_blocksize == XFS_BUF_COUNT(bp)); |
| |
| length = MIN(XFS_BUF_COUNT(bp) - hdrsize, valuelen - amountdone); |
| memmove(value, XFS_BUF_PTR(bp) + hdrsize, length); |
| amountdone += length; |
| value += length; |
| i++; |
| libxfs_putbuf(bp); |
| } |
| return (clearit); |
| } |
| |
| /* The block is read in. The magic number and forward / backward |
| * links are checked by the caller process_leaf_attr. |
| * If any problems occur the routine returns with non-zero. In |
| * this case the next step is to clear the attribute fork, by |
| * changing it to shortform and zeroing it out. Forkoff need not |
| * be changed. |
| */ |
| |
| static int |
| process_leaf_attr_local( |
| struct xfs_mount *mp, |
| xfs_attr_leafblock_t *leaf, |
| int i, |
| xfs_attr_leaf_entry_t *entry, |
| xfs_dahash_t last_hashval, |
| xfs_dablk_t da_bno, |
| xfs_ino_t ino) |
| { |
| xfs_attr_leaf_name_local_t *local; |
| |
| local = xfs_attr3_leaf_name_local(leaf, i); |
| if (local->namelen == 0 || namecheck((char *)&local->nameval[0], |
| local->namelen)) { |
| do_warn( |
| _("attribute entry %d in attr block %u, inode %" PRIu64 " has bad name (namelen = %d)\n"), |
| i, da_bno, ino, local->namelen); |
| return -1; |
| } |
| |
| /* Check on the hash value. Checking order of values |
| * is not necessary, since one wrong clears the whole |
| * fork. If the ordering's wrong, it's caught here or |
| * the kernel code has a bug with transaction logging |
| * or attributes itself. Being paranoid, let's check |
| * ordering anyway in case both the name value and the |
| * hashvalue were wrong but matched. Unlikely, however. |
| */ |
| if (be32_to_cpu(entry->hashval) != libxfs_da_hashname( |
| &local->nameval[0], local->namelen) || |
| be32_to_cpu(entry->hashval) < last_hashval) { |
| do_warn( |
| _("bad hashvalue for attribute entry %d in attr block %u, inode %" PRIu64 "\n"), |
| i, da_bno, ino); |
| return -1; |
| } |
| |
| /* Only check values for root security attributes */ |
| if (entry->flags & XFS_ATTR_ROOT) { |
| if (valuecheck(mp, (char *)&local->nameval[0], NULL, |
| local->namelen, be16_to_cpu(local->valuelen))) { |
| do_warn( |
| _("bad security value for attribute entry %d in attr block %u, inode %" PRIu64 "\n"), |
| i, da_bno, ino); |
| return -1; |
| } |
| } |
| return xfs_attr_leaf_entsize_local(local->namelen, |
| be16_to_cpu(local->valuelen)); |
| } |
| |
| static int |
| process_leaf_attr_remote( |
| xfs_attr_leafblock_t *leaf, |
| int i, |
| xfs_attr_leaf_entry_t *entry, |
| xfs_dahash_t last_hashval, |
| xfs_dablk_t da_bno, |
| xfs_ino_t ino, |
| xfs_mount_t *mp, |
| blkmap_t *blkmap) |
| { |
| xfs_attr_leaf_name_remote_t *remotep; |
| char* value; |
| |
| remotep = xfs_attr3_leaf_name_remote(leaf, i); |
| |
| if (remotep->namelen == 0 || namecheck((char *)&remotep->name[0], |
| remotep->namelen) || |
| be32_to_cpu(entry->hashval) != |
| libxfs_da_hashname((uchar_t *)&remotep->name[0], |
| remotep->namelen) || |
| be32_to_cpu(entry->hashval) < last_hashval || |
| be32_to_cpu(remotep->valueblk) == 0) { |
| do_warn( |
| _("inconsistent remote attribute entry %d in attr block %u, ino %" PRIu64 "\n"), i, da_bno, ino); |
| return -1; |
| } |
| |
| if (!(entry->flags & XFS_ATTR_ROOT)) |
| goto out; |
| |
| value = malloc(be32_to_cpu(remotep->valuelen)); |
| if (value == NULL) { |
| do_warn( |
| _("cannot malloc enough for remotevalue attribute for inode %" PRIu64 "\n"), |
| ino); |
| do_warn(_("SKIPPING this remote attribute\n")); |
| goto out; |
| } |
| if (rmtval_get(mp, ino, blkmap, be32_to_cpu(remotep->valueblk), |
| be32_to_cpu(remotep->valuelen), value)) { |
| do_warn( |
| _("remote attribute get failed for entry %d, inode %" PRIu64 "\n"), |
| i, ino); |
| goto bad_free_out; |
| } |
| if (valuecheck(mp, (char *)&remotep->name[0], value, remotep->namelen, |
| be32_to_cpu(remotep->valuelen))) { |
| do_warn( |
| _("remote attribute value check failed for entry %d, inode %" PRIu64 "\n"), |
| i, ino); |
| goto bad_free_out; |
| } |
| free(value); |
| out: |
| return xfs_attr_leaf_entsize_remote(remotep->namelen); |
| |
| bad_free_out: |
| free(value); |
| return -1; |
| } |
| |
| static int |
| process_leaf_attr_block( |
| xfs_mount_t *mp, |
| xfs_attr_leafblock_t *leaf, |
| xfs_dablk_t da_bno, |
| xfs_ino_t ino, |
| blkmap_t *blkmap, |
| xfs_dahash_t last_hashval, |
| xfs_dahash_t *current_hashval, |
| int *repair) |
| { |
| xfs_attr_leaf_entry_t *entry; |
| int i, start, stop, clearit, usedbs, firstb, thissize; |
| da_freemap_t *attr_freemap; |
| struct xfs_attr3_icleaf_hdr leafhdr; |
| |
| xfs_attr3_leaf_hdr_from_disk(mp->m_attr_geo, &leafhdr, leaf); |
| clearit = usedbs = 0; |
| firstb = mp->m_sb.sb_blocksize; |
| stop = xfs_attr3_leaf_hdr_size(leaf); |
| |
| /* does the count look sorta valid? */ |
| if (leafhdr.count * sizeof(xfs_attr_leaf_entry_t) + stop > |
| mp->m_sb.sb_blocksize) { |
| do_warn( |
| _("bad attribute count %d in attr block %u, inode %" PRIu64 "\n"), |
| leafhdr.count, da_bno, ino); |
| return 1; |
| } |
| |
| attr_freemap = alloc_da_freemap(mp); |
| (void) set_da_freemap(mp, attr_freemap, 0, stop); |
| |
| /* go thru each entry checking for problems */ |
| for (i = 0, entry = xfs_attr3_leaf_entryp(leaf); |
| i < leafhdr.count; i++, entry++) { |
| |
| /* check if index is within some boundary. */ |
| if (be16_to_cpu(entry->nameidx) > mp->m_sb.sb_blocksize) { |
| do_warn( |
| _("bad attribute nameidx %d in attr block %u, inode %" PRIu64 "\n"), |
| be16_to_cpu(entry->nameidx), da_bno, ino); |
| clearit = 1; |
| break; |
| } |
| |
| if (entry->flags & XFS_ATTR_INCOMPLETE) { |
| /* we are inconsistent state. get rid of us */ |
| do_warn( |
| _("attribute entry #%d in attr block %u, inode %" PRIu64 " is INCOMPLETE\n"), |
| i, da_bno, ino); |
| clearit = 1; |
| break; |
| } |
| |
| /* mark the entry used */ |
| start = (__psint_t)entry - (__psint_t)leaf; |
| stop = start + sizeof(xfs_attr_leaf_entry_t); |
| if (set_da_freemap(mp, attr_freemap, start, stop)) { |
| do_warn( |
| _("attribute entry %d in attr block %u, inode %" PRIu64 " claims already used space\n"), |
| i, da_bno, ino); |
| clearit = 1; |
| break; /* got an overlap */ |
| } |
| |
| if (entry->flags & XFS_ATTR_LOCAL) |
| thissize = process_leaf_attr_local(mp, leaf, i, entry, |
| last_hashval, da_bno, ino); |
| else |
| thissize = process_leaf_attr_remote(leaf, i, entry, |
| last_hashval, da_bno, ino, |
| mp, blkmap); |
| if (thissize < 0) { |
| clearit = 1; |
| break; |
| } |
| |
| *current_hashval = last_hashval = be32_to_cpu(entry->hashval); |
| |
| if (set_da_freemap(mp, attr_freemap, be16_to_cpu(entry->nameidx), |
| be16_to_cpu(entry->nameidx) + thissize)) { |
| do_warn( |
| _("attribute entry %d in attr block %u, inode %" PRIu64 " claims used space\n"), |
| i, da_bno, ino); |
| clearit = 1; |
| break; /* got an overlap */ |
| } |
| usedbs += thissize; |
| if (be16_to_cpu(entry->nameidx) < firstb) |
| firstb = be16_to_cpu(entry->nameidx); |
| |
| } /* end the loop */ |
| |
| if (!clearit) { |
| /* verify the header information is correct */ |
| |
| /* if the holes flag is set, don't reset first_used unless it's |
| * pointing to used bytes. we're being conservative here |
| * since the block will get compacted anyhow by the kernel. |
| */ |
| |
| if ((leafhdr.holes == 0 && |
| firstb != leafhdr.firstused) || |
| leafhdr.firstused > firstb) { |
| if (!no_modify) { |
| do_warn( |
| _("- resetting first used heap value from %d to %d in " |
| "block %u of attribute fork of inode %" PRIu64 "\n"), |
| leafhdr.firstused, |
| firstb, da_bno, ino); |
| leafhdr.firstused = firstb; |
| *repair = 1; |
| } else { |
| do_warn( |
| _("- would reset first used value from %d to %d in " |
| "block %u of attribute fork of inode %" PRIu64 "\n"), |
| leafhdr.firstused, |
| firstb, da_bno, ino); |
| } |
| } |
| |
| if (usedbs != leafhdr.usedbytes) { |
| if (!no_modify) { |
| do_warn( |
| _("- resetting usedbytes cnt from %d to %d in " |
| "block %u of attribute fork of inode %" PRIu64 "\n"), |
| leafhdr.usedbytes, |
| usedbs, da_bno, ino); |
| leafhdr.usedbytes = usedbs; |
| *repair = 1; |
| } else { |
| do_warn( |
| _("- would reset usedbytes cnt from %d to %d in " |
| "block %u of attribute fork of %" PRIu64 "\n"), |
| leafhdr.usedbytes, |
| usedbs, da_bno, ino); |
| } |
| } |
| |
| /* there's a lot of work in process_leaf_dir_block to go thru |
| * checking for holes and compacting if appropiate. I don't think |
| * attributes need all that, so let's just leave the holes. If |
| * we discover later that this is a good place to do compaction |
| * we can add it then. |
| */ |
| } |
| if (*repair) |
| xfs_attr3_leaf_hdr_to_disk(mp->m_attr_geo, leaf, &leafhdr); |
| |
| free(attr_freemap); |
| return (clearit); /* and repair */ |
| } |
| |
| |
| /* |
| * returns 0 if the attribute fork is ok, 1 if it has to be junked. |
| */ |
| static int |
| process_leaf_attr_level(xfs_mount_t *mp, |
| da_bt_cursor_t *da_cursor) |
| { |
| int repair; |
| xfs_attr_leafblock_t *leaf; |
| xfs_buf_t *bp; |
| xfs_ino_t ino; |
| xfs_fsblock_t dev_bno; |
| xfs_dablk_t da_bno; |
| xfs_dablk_t prev_bno; |
| xfs_dahash_t current_hashval = 0; |
| xfs_dahash_t greatest_hashval; |
| struct xfs_attr3_icleaf_hdr leafhdr; |
| |
| da_bno = da_cursor->level[0].bno; |
| ino = da_cursor->ino; |
| prev_bno = 0; |
| |
| do { |
| repair = 0; |
| dev_bno = blkmap_get(da_cursor->blkmap, da_bno); |
| /* |
| * 0 is the root block and no block |
| * pointer can point to the root block of the btree |
| */ |
| ASSERT(da_bno != 0); |
| |
| if (dev_bno == NULLFSBLOCK) { |
| do_warn( |
| _("can't map block %u for attribute fork for inode %" PRIu64 "\n"), |
| da_bno, ino); |
| goto error_out; |
| } |
| |
| bp = libxfs_readbuf(mp->m_dev, XFS_FSB_TO_DADDR(mp, dev_bno), |
| XFS_FSB_TO_BB(mp, 1), 0, |
| &xfs_attr3_leaf_buf_ops); |
| if (!bp) { |
| do_warn( |
| _("can't read file block %u (fsbno %" PRIu64 ") for attribute fork of inode %" PRIu64 "\n"), |
| da_bno, dev_bno, ino); |
| goto error_out; |
| } |
| if (bp->b_error == -EFSBADCRC) |
| repair++; |
| |
| leaf = bp->b_addr; |
| xfs_attr3_leaf_hdr_from_disk(mp->m_attr_geo, &leafhdr, leaf); |
| |
| /* check magic number for leaf directory btree block */ |
| if (!(leafhdr.magic == XFS_ATTR_LEAF_MAGIC || |
| leafhdr.magic == XFS_ATTR3_LEAF_MAGIC)) { |
| do_warn( |
| _("bad attribute leaf magic %#x for inode %" PRIu64 "\n"), |
| leafhdr.magic, ino); |
| libxfs_putbuf(bp); |
| goto error_out; |
| } |
| |
| /* |
| * for each block, process the block, verify its path, |
| * then get next block. update cursor values along the way |
| */ |
| if (process_leaf_attr_block(mp, leaf, da_bno, ino, |
| da_cursor->blkmap, current_hashval, |
| &greatest_hashval, &repair)) { |
| libxfs_putbuf(bp); |
| goto error_out; |
| } |
| |
| /* |
| * index can be set to hdr.count so match the |
| * indexes of the interior blocks -- which at the |
| * end of the block will point to 1 after the final |
| * real entry in the block |
| */ |
| da_cursor->level[0].hashval = greatest_hashval; |
| da_cursor->level[0].bp = bp; |
| da_cursor->level[0].bno = da_bno; |
| da_cursor->level[0].index = leafhdr.count; |
| da_cursor->level[0].dirty = repair; |
| |
| if (leafhdr.back != prev_bno) { |
| do_warn( |
| _("bad sibling back pointer for block %u in attribute fork for inode %" PRIu64 "\n"), |
| da_bno, ino); |
| libxfs_putbuf(bp); |
| goto error_out; |
| } |
| |
| prev_bno = da_bno; |
| da_bno = leafhdr.forw; |
| |
| if (da_bno != 0 && verify_da_path(mp, da_cursor, 0)) { |
| libxfs_putbuf(bp); |
| goto error_out; |
| } |
| |
| current_hashval = greatest_hashval; |
| |
| if (repair && !no_modify) |
| libxfs_writebuf(bp, 0); |
| else |
| libxfs_putbuf(bp); |
| } while (da_bno != 0); |
| |
| if (verify_final_da_path(mp, da_cursor, 0)) { |
| /* |
| * verify the final path up (right-hand-side) if still ok |
| */ |
| do_warn( |
| _("bad hash path in attribute fork for inode %" PRIu64 "\n"), |
| da_cursor->ino); |
| goto error_out; |
| } |
| |
| /* releases all buffers holding interior btree blocks */ |
| release_da_cursor(mp, da_cursor, 0); |
| return(0); |
| |
| error_out: |
| /* release all buffers holding interior btree blocks */ |
| err_release_da_cursor(mp, da_cursor, 0); |
| return(1); |
| } |
| |
| |
| /* |
| * a node directory is a true btree -- where the attribute fork |
| * has gotten big enough that it is represented as a non-trivial (e.g. |
| * has more than just a block) btree. |
| * |
| * Note that if we run into any problems, we will trash the attribute fork. |
| * |
| * returns 0 if things are ok, 1 if bad |
| * Note this code has been based off process_node_dir. |
| */ |
| static int |
| process_node_attr( |
| xfs_mount_t *mp, |
| xfs_ino_t ino, |
| xfs_dinode_t *dip, |
| blkmap_t *blkmap) |
| { |
| xfs_dablk_t bno; |
| int error = 0; |
| da_bt_cursor_t da_cursor; |
| |
| /* |
| * try again -- traverse down left-side of tree until we hit |
| * the left-most leaf block setting up the btree cursor along |
| * the way. Then walk the leaf blocks left-to-right, calling |
| * a parent-verification routine each time we traverse a block. |
| */ |
| memset(&da_cursor, 0, sizeof(da_bt_cursor_t)); |
| da_cursor.active = 0; |
| da_cursor.type = 0; |
| da_cursor.ino = ino; |
| da_cursor.dip = dip; |
| da_cursor.greatest_bno = 0; |
| da_cursor.blkmap = blkmap; |
| |
| /* |
| * now process interior node. don't have any buffers held in this path. |
| */ |
| error = traverse_int_dablock(mp, &da_cursor, &bno, XFS_ATTR_FORK); |
| if (error == 0) |
| return(1); /* 0 means unsuccessful */ |
| |
| /* |
| * now pass cursor and bno into leaf-block processing routine |
| * the leaf dir level routine checks the interior paths |
| * up to the root including the final right-most path. |
| */ |
| |
| return (process_leaf_attr_level(mp, &da_cursor)); |
| } |
| |
| /* |
| * Start processing for a leaf or fuller btree. |
| * A leaf directory is one where the attribute fork is too big for |
| * the inode but is small enough to fit into one btree block |
| * outside the inode. This code is modelled after process_leaf_dir_block. |
| * |
| * returns 0 if things are ok, 1 if bad (attributes needs to be junked) |
| * repair is set, if anything was changed, but attributes can live thru it |
| */ |
| static int |
| process_longform_attr( |
| xfs_mount_t *mp, |
| xfs_ino_t ino, |
| xfs_dinode_t *dip, |
| blkmap_t *blkmap, |
| int *repair) /* out - 1 if something was fixed */ |
| { |
| xfs_attr_leafblock_t *leaf; |
| xfs_fsblock_t bno; |
| xfs_buf_t *bp; |
| xfs_dahash_t next_hashval; |
| int repairlinks = 0; |
| struct xfs_attr3_icleaf_hdr leafhdr; |
| |
| *repair = 0; |
| |
| bno = blkmap_get(blkmap, 0); |
| |
| if ( bno == NULLFSBLOCK ) { |
| if (dip->di_aformat == XFS_DINODE_FMT_EXTENTS && |
| be16_to_cpu(dip->di_anextents) == 0) |
| return(0); /* the kernel can handle this state */ |
| do_warn( |
| _("block 0 of inode %" PRIu64 " attribute fork is missing\n"), |
| ino); |
| return(1); |
| } |
| /* FIX FOR bug 653709 -- EKN */ |
| if (mp->m_sb.sb_agcount < XFS_FSB_TO_AGNO(mp, bno)) { |
| do_warn( |
| _("agno of attribute fork of inode %" PRIu64 " out of regular partition\n"), ino); |
| return(1); |
| } |
| |
| bp = libxfs_readbuf(mp->m_dev, XFS_FSB_TO_DADDR(mp, bno), |
| XFS_FSB_TO_BB(mp, 1), 0, &xfs_da3_node_buf_ops); |
| if (!bp) { |
| do_warn( |
| _("can't read block 0 of inode %" PRIu64 " attribute fork\n"), |
| ino); |
| return(1); |
| } |
| if (bp->b_error == -EFSBADCRC) |
| (*repair)++; |
| |
| /* verify leaf block */ |
| leaf = (xfs_attr_leafblock_t *)XFS_BUF_PTR(bp); |
| xfs_attr3_leaf_hdr_from_disk(mp->m_attr_geo, &leafhdr, leaf); |
| |
| /* check sibling pointers in leaf block or root block 0 before |
| * we have to release the btree block |
| */ |
| if (leafhdr.forw != 0 || leafhdr.back != 0) { |
| if (!no_modify) { |
| do_warn( |
| _("clearing forw/back pointers in block 0 for attributes in inode %" PRIu64 "\n"), |
| ino); |
| repairlinks = 1; |
| leafhdr.forw = 0; |
| leafhdr.back = 0; |
| xfs_attr3_leaf_hdr_to_disk(mp->m_attr_geo, |
| leaf, &leafhdr); |
| } else { |
| do_warn( |
| _("would clear forw/back pointers in block 0 for attributes in inode %" PRIu64 "\n"), ino); |
| } |
| } |
| |
| /* |
| * use magic number to tell us what type of attribute this is. |
| * it's possible to have a node or leaf attribute in either an |
| * extent format or btree format attribute fork. |
| */ |
| switch (leafhdr.magic) { |
| case XFS_ATTR_LEAF_MAGIC: /* leaf-form attribute */ |
| case XFS_ATTR3_LEAF_MAGIC: |
| if (process_leaf_attr_block(mp, leaf, 0, ino, blkmap, |
| 0, &next_hashval, repair)) { |
| /* the block is bad. lose the attribute fork. */ |
| libxfs_putbuf(bp); |
| return(1); |
| } |
| *repair = *repair || repairlinks; |
| break; |
| |
| case XFS_DA_NODE_MAGIC: /* btree-form attribute */ |
| case XFS_DA3_NODE_MAGIC: |
| /* must do this now, to release block 0 before the traversal */ |
| if ((*repair || repairlinks) && !no_modify) { |
| *repair = 1; |
| libxfs_writebuf(bp, 0); |
| } else |
| libxfs_putbuf(bp); |
| return (process_node_attr(mp, ino, dip, blkmap)); /* + repair */ |
| default: |
| do_warn( |
| _("bad attribute leaf magic # %#x for dir ino %" PRIu64 "\n"), |
| be16_to_cpu(leaf->hdr.info.magic), ino); |
| libxfs_putbuf(bp); |
| return(1); |
| } |
| |
| if (*repair && !no_modify) |
| libxfs_writebuf(bp, 0); |
| else |
| libxfs_putbuf(bp); |
| |
| return(0); /* repair may be set */ |
| } |
| |
| |
| static int |
| xfs_acl_from_disk( |
| struct xfs_mount *mp, |
| struct xfs_icacl **aclp, |
| struct xfs_acl *dacl) |
| { |
| struct xfs_icacl *acl; |
| struct xfs_icacl_entry *ace; |
| struct xfs_acl_entry *dace; |
| int count; |
| int i; |
| |
| count = be32_to_cpu(dacl->acl_cnt); |
| if (count > XFS_ACL_MAX_ENTRIES(mp)) { |
| do_warn(_("Too many ACL entries, count %d\n"), count); |
| *aclp = NULL; |
| return EINVAL; |
| } |
| |
| |
| acl = malloc(sizeof(struct xfs_icacl) + |
| count * sizeof(struct xfs_icacl_entry)); |
| if (!acl) { |
| do_warn(_("cannot malloc enough for ACL attribute\n")); |
| do_warn(_("SKIPPING this ACL\n")); |
| *aclp = NULL; |
| return ENOMEM; |
| } |
| |
| acl->acl_cnt = count; |
| for (i = 0; i < count; i++) { |
| ace = &acl->acl_entry[i]; |
| dace = &dacl->acl_entry[i]; |
| |
| ace->ae_tag = be32_to_cpu(dace->ae_tag); |
| ace->ae_id = be32_to_cpu(dace->ae_id); |
| ace->ae_perm = be16_to_cpu(dace->ae_perm); |
| } |
| |
| *aclp = acl; |
| return 0; |
| } |
| |
| /* |
| * returns 1 if attributes got cleared |
| * and 0 if things are ok. |
| */ |
| int |
| process_attributes( |
| xfs_mount_t *mp, |
| xfs_ino_t ino, |
| xfs_dinode_t *dip, |
| blkmap_t *blkmap, |
| int *repair) /* returned if we did repair */ |
| { |
| int err; |
| __u8 aformat = dip->di_aformat; |
| #ifdef DEBUG |
| xfs_attr_shortform_t *asf; |
| |
| asf = (xfs_attr_shortform_t *) XFS_DFORK_APTR(dip); |
| #endif |
| |
| if (aformat == XFS_DINODE_FMT_LOCAL) { |
| ASSERT(be16_to_cpu(asf->hdr.totsize) <= |
| XFS_DFORK_ASIZE(dip, mp)); |
| err = process_shortform_attr(mp, ino, dip, repair); |
| } else if (aformat == XFS_DINODE_FMT_EXTENTS || |
| aformat == XFS_DINODE_FMT_BTREE) { |
| err = process_longform_attr(mp, ino, dip, blkmap, |
| repair); |
| /* if err, convert this to shortform and clear it */ |
| /* if repair and no error, it's taken care of */ |
| } else { |
| do_warn(_("illegal attribute format %d, ino %" PRIu64 "\n"), |
| aformat, ino); |
| err = 1; |
| } |
| return (err); /* and repair */ |
| } |
| |
| /* |
| * Validate an ACL |
| */ |
| static int |
| xfs_acl_valid( |
| struct xfs_mount *mp, |
| struct xfs_acl *daclp) |
| { |
| struct xfs_icacl *aclp = NULL; |
| struct xfs_icacl_entry *entry, *e; |
| int user = 0, group = 0, other = 0, mask = 0, mask_required = 0; |
| int i, j; |
| |
| if (daclp == NULL) |
| goto acl_invalid; |
| |
| switch (xfs_acl_from_disk(mp, &aclp, daclp)) { |
| case ENOMEM: |
| return 0; |
| case EINVAL: |
| goto acl_invalid; |
| default: |
| break; |
| } |
| |
| for (i = 0; i < aclp->acl_cnt; i++) { |
| entry = &aclp->acl_entry[i]; |
| if (entry->ae_perm & ~(ACL_READ|ACL_WRITE|ACL_EXECUTE)) |
| goto acl_invalid; |
| switch (entry->ae_tag) { |
| case ACL_USER_OBJ: |
| if (user++) |
| goto acl_invalid; |
| break; |
| case ACL_GROUP_OBJ: |
| if (group++) |
| goto acl_invalid; |
| break; |
| case ACL_OTHER: |
| if (other++) |
| goto acl_invalid; |
| break; |
| case ACL_USER: |
| case ACL_GROUP: |
| for (j = i + 1; j < aclp->acl_cnt; j++) { |
| e = &aclp->acl_entry[j]; |
| if (e->ae_id == entry->ae_id && |
| e->ae_tag == entry->ae_tag) |
| goto acl_invalid; |
| } |
| mask_required++; |
| break; |
| case ACL_MASK: |
| if (mask++) |
| goto acl_invalid; |
| break; |
| default: |
| goto acl_invalid; |
| } |
| } |
| if (!user || !group || !other || (mask_required && !mask)) |
| goto acl_invalid; |
| free(aclp); |
| return 0; |
| acl_invalid: |
| free(aclp); |
| errno = EINVAL; |
| return (-1); |
| } |
| |
| /* |
| * Check a category or division set to ensure that all values are in |
| * ascending order and each division or category appears only once. |
| */ |
| static int |
| __check_setvalue(const unsigned short *list, unsigned short count) |
| { |
| unsigned short i; |
| |
| for (i = 1; i < count ; i++) |
| if (list[i] <= list[i-1]) |
| return -1; |
| return 0; |
| } |
| |
| /* |
| * xfs_mac_valid(lp) |
| * Check the validity of a MAC label. |
| */ |
| static int |
| xfs_mac_valid(xfs_mac_label_t *lp) |
| { |
| if (lp == NULL) |
| return (0); |
| |
| /* |
| * if the total category set and division set is greater than 250 |
| * report error |
| */ |
| if ((lp->ml_catcount + lp->ml_divcount) > XFS_MAC_MAX_SETS) |
| return(0); |
| |
| /* |
| * check whether the msentype value is valid, and do they have |
| * appropriate level, category association. |
| */ |
| switch (lp->ml_msen_type) { |
| case XFS_MSEN_ADMIN_LABEL: |
| case XFS_MSEN_EQUAL_LABEL: |
| case XFS_MSEN_HIGH_LABEL: |
| case XFS_MSEN_MLD_HIGH_LABEL: |
| case XFS_MSEN_LOW_LABEL: |
| case XFS_MSEN_MLD_LOW_LABEL: |
| if (lp->ml_level != 0 || lp->ml_catcount > 0 ) |
| return (0); |
| break; |
| case XFS_MSEN_TCSEC_LABEL: |
| case XFS_MSEN_MLD_LABEL: |
| if (lp->ml_catcount > 0 && |
| __check_setvalue(lp->ml_list, |
| lp->ml_catcount) == -1) |
| return (0); |
| break; |
| case XFS_MSEN_UNKNOWN_LABEL: |
| default: |
| return (0); |
| } |
| |
| /* |
| * check whether the minttype value is valid, and do they have |
| * appropriate grade, division association. |
| */ |
| switch (lp->ml_mint_type) { |
| case XFS_MINT_BIBA_LABEL: |
| if (lp->ml_divcount > 0 && |
| __check_setvalue(lp->ml_list + lp->ml_catcount, |
| lp->ml_divcount) == -1) |
| return(0); |
| break; |
| case XFS_MINT_EQUAL_LABEL: |
| case XFS_MINT_HIGH_LABEL: |
| case XFS_MINT_LOW_LABEL: |
| if (lp->ml_grade != 0 || lp->ml_divcount > 0 ) |
| return(0); |
| break; |
| default: |
| return(0); |
| } |
| |
| return (1); |
| } |