blob: f3332b3d141f59b0608e16c3c5b97b7be8dcddac [file] [log] [blame]
/*
* 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 *)&currententry->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 *)&currententry->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);
}