blob: cbbce60188d6baf61a74a00b01a30be32866054d [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2000-2002,2005 Silicon Graphics, Inc.
* All Rights Reserved.
*/
#include "libxfs.h"
#include "avl.h"
#include "globals.h"
#include "incore.h"
#include "err_protos.h"
#include "dinode.h"
#include "dir2.h"
#include "bmap.h"
#include "da_util.h"
#include "prefetch.h"
#include "progress.h"
/*
* Known bad inode list. These are seen when the leaf and node
* block linkages are incorrect.
*/
typedef struct dir2_bad {
xfs_ino_t ino;
struct dir2_bad *next;
} dir2_bad_t;
static dir2_bad_t *dir2_bad_list;
static void
dir2_add_badlist(
xfs_ino_t ino)
{
dir2_bad_t *l;
if ((l = malloc(sizeof(dir2_bad_t))) == NULL) {
do_error(
_("malloc failed (%zu bytes) dir2_add_badlist:ino %" PRIu64 "\n"),
sizeof(dir2_bad_t), ino);
exit(1);
}
l->next = dir2_bad_list;
dir2_bad_list = l;
l->ino = ino;
}
int
dir2_is_badino(
xfs_ino_t ino)
{
dir2_bad_t *l;
for (l = dir2_bad_list; l; l = l->next)
if (l->ino == ino)
return 1;
return 0;
}
/*
* Fix up a shortform directory which was in long form (i8count set)
* and is now in short form (i8count clear).
* Return pointer to the end of the data when done.
*/
void
process_sf_dir2_fixi8(
struct xfs_mount *mp,
struct xfs_dir2_sf_hdr *sfp,
xfs_dir2_sf_entry_t **next_sfep)
{
xfs_ino_t ino;
struct xfs_dir2_sf_hdr *newsfp;
xfs_dir2_sf_entry_t *newsfep;
struct xfs_dir2_sf_hdr *oldsfp;
xfs_dir2_sf_entry_t *oldsfep;
int oldsize;
newsfp = sfp;
oldsize = (intptr_t)*next_sfep - (intptr_t)sfp;
oldsfp = malloc(oldsize);
if (oldsfp == NULL) {
do_error(_("couldn't malloc dir2 shortform copy\n"));
exit(1);
}
memmove(oldsfp, newsfp, oldsize);
newsfp->count = oldsfp->count;
newsfp->i8count = 0;
ino = libxfs_dir2_sf_get_parent_ino(sfp);
libxfs_dir2_sf_put_parent_ino(newsfp, ino);
oldsfep = xfs_dir2_sf_firstentry(oldsfp);
newsfep = xfs_dir2_sf_firstentry(newsfp);
while ((int)((char *)oldsfep - (char *)oldsfp) < oldsize) {
newsfep->namelen = oldsfep->namelen;
xfs_dir2_sf_put_offset(newsfep,
xfs_dir2_sf_get_offset(oldsfep));
memmove(newsfep->name, oldsfep->name, newsfep->namelen);
ino = libxfs_dir2_sf_get_ino(mp, oldsfp, oldsfep);
libxfs_dir2_sf_put_ino(mp, newsfp, newsfep, ino);
oldsfep = libxfs_dir2_sf_nextentry(mp, oldsfp, oldsfep);
newsfep = libxfs_dir2_sf_nextentry(mp, newsfp, newsfep);
}
*next_sfep = newsfep;
free(oldsfp);
}
/*
* Regenerate legal (minimal) offsets for the shortform directory.
*/
static void
process_sf_dir2_fixoff(
xfs_mount_t *mp,
xfs_dinode_t *dip)
{
int i;
int offset;
xfs_dir2_sf_entry_t *sfep;
struct xfs_dir2_sf_hdr *sfp;
sfp = (struct xfs_dir2_sf_hdr *)XFS_DFORK_DPTR(dip);
sfep = xfs_dir2_sf_firstentry(sfp);
offset = mp->m_dir_geo->data_first_offset;
for (i = 0; i < sfp->count; i++) {
xfs_dir2_sf_put_offset(sfep, offset);
offset += libxfs_dir2_data_entsize(mp, sfep->namelen);
sfep = libxfs_dir2_sf_nextentry(mp, sfp, sfep);
}
}
/*
* this routine performs inode discovery and tries to fix things
* in place. available redundancy -- inode data size should match
* used directory space in inode.
* a non-zero return value means the directory is bogus and should be blasted.
*/
/* ARGSUSED */
static int
process_sf_dir2(
xfs_mount_t *mp,
xfs_ino_t ino,
xfs_dinode_t *dip,
int ino_discovery,
int *dino_dirty, /* out - 1 if dinode buffer dirty */
char *dirname, /* directory pathname */
xfs_ino_t *parent, /* out - NULLFSINO if entry not exist */
int *repair) /* out - 1 if dir was fixed up */
{
int bad_offset;
int bad_sfnamelen;
int i;
int i8;
int64_t ino_dir_size;
int ino_off;
ino_tree_node_t *irec_p;
int junkit;
char *junkreason = NULL;
xfs_ino_t lino;
int max_size;
char name[MAXNAMELEN + 1];
int namelen;
xfs_dir2_sf_entry_t *next_sfep;
int num_entries;
int offset;
struct xfs_dir2_sf_hdr *sfp;
xfs_dir2_sf_entry_t *sfep;
int tmp_elen;
int tmp_len;
xfs_dir2_sf_entry_t *tmp_sfep;
xfs_ino_t zero = 0;
sfp = (struct xfs_dir2_sf_hdr *)XFS_DFORK_DPTR(dip);
max_size = XFS_DFORK_DSIZE(dip, mp);
num_entries = sfp->count;
ino_dir_size = be64_to_cpu(dip->di_size);
offset = mp->m_dir_geo->data_first_offset;
bad_offset = *repair = 0;
ASSERT(ino_dir_size <= max_size);
/*
* Initialize i8 based on size of parent inode number.
*/
i8 = (libxfs_dir2_sf_get_parent_ino(sfp) > XFS_DIR2_MAX_SHORT_INUM);
/*
* check for bad entry count
*/
if (num_entries * libxfs_dir2_sf_entsize(mp, sfp, 1) +
xfs_dir2_sf_hdr_size(0) > max_size || num_entries == 0)
num_entries = 0xFF;
/*
* run through entries, stop at first bad entry, don't need
* to check for .. since that's encoded in its own field
*/
next_sfep = xfs_dir2_sf_firstentry(sfp);
for (i = 0;
i < num_entries && ino_dir_size > (char *)next_sfep - (char *)sfp;
i++) {
tmp_sfep = NULL;
sfep = next_sfep;
junkit = 0;
bad_sfnamelen = 0;
lino = libxfs_dir2_sf_get_ino(mp, sfp, sfep);
/*
* if entry points to self, junk it since only '.' or '..'
* should do that and shortform dirs don't contain either
* entry. if inode number is invalid, trash entry.
* if entry points to special inodes, trash it.
* if inode is unknown but number is valid,
* add it to the list of uncertain inodes. don't
* have to worry about an entry pointing to a
* deleted lost+found inode because the entry was
* deleted at the same time that the inode was cleared.
*/
if (lino == ino) {
junkit = 1;
junkreason = _("current");
} else if (!libxfs_verify_dir_ino(mp, lino)) {
junkit = 1;
junkreason = _("invalid");
} else if (lino == mp->m_sb.sb_rbmino) {
junkit = 1;
junkreason = _("realtime bitmap");
} else if (lino == mp->m_sb.sb_rsumino) {
junkit = 1;
junkreason = _("realtime summary");
} else if (lino == mp->m_sb.sb_uquotino) {
junkit = 1;
junkreason = _("user quota");
} else if (lino == mp->m_sb.sb_gquotino) {
junkit = 1;
junkreason = _("group quota");
} else if (lino == mp->m_sb.sb_pquotino) {
junkit = 1;
junkreason = _("project quota");
} else if ((irec_p = find_inode_rec(mp,
XFS_INO_TO_AGNO(mp, lino),
XFS_INO_TO_AGINO(mp, lino))) != NULL) {
/*
* if inode is marked free and we're in inode
* discovery mode, leave the entry alone for now.
* if the inode turns out to be used, we'll figure
* that out when we scan it. If the inode really
* is free, we'll hit this code again in phase 4
* after we've finished inode discovery and blow
* out the entry then.
*/
ino_off = XFS_INO_TO_AGINO(mp, lino) -
irec_p->ino_startnum;
ASSERT(is_inode_confirmed(irec_p, ino_off));
if (is_inode_free(irec_p, ino_off) && !ino_discovery) {
junkit = 1;
junkreason = _("free");
}
} else if (ino_discovery) {
/*
* put the inode on the uncertain list. we'll
* pull the inode off the list and check it later.
* if the inode turns out be bogus, we'll delete
* this entry in phase 6.
*/
add_inode_uncertain(mp, lino, 0);
} else {
/*
* blow the entry out. we know about all
* undiscovered entries now (past inode discovery
* phase) so this is clearly a bogus entry.
*/
junkit = 1;
junkreason = _("non-existent");
}
namelen = sfep->namelen;
if (junkit)
do_warn(
_("entry \"%*.*s\" in shortform directory %" PRIu64 " references %s inode %" PRIu64 "\n"),
namelen, namelen, sfep->name, ino, junkreason,
lino);
/* is dir namelen 0 or does this entry extend past dir size? */
if (namelen == 0) {
junkreason = _("is zero length");
bad_sfnamelen = 1;
} else if ((intptr_t) sfep - (intptr_t) sfp +
libxfs_dir2_sf_entsize(mp, sfp, sfep->namelen)
> ino_dir_size) {
junkreason = _("extends past end of dir");
bad_sfnamelen = 1;
}
if (bad_sfnamelen) {
do_warn(
_("entry #%d %s in shortform dir %" PRIu64),
i, junkreason, ino);
if (!no_modify)
do_warn(_(", junking %d entries\n"),
num_entries - i);
else
do_warn(_(", would junk %d entries\n"),
num_entries - i);
/*
* don't process the rest of the directory,
* break out of processing loop
*/
break;
}
/*
* check for illegal chars in name.
* no need to check for bad length because
* the length value is stored in a byte
* so it can't be too big, it can only wrap
*/
if (!libxfs_dir2_namecheck(sfep->name, namelen)) {
/*
* junk entry
*/
do_warn(
_("entry contains illegal character in shortform dir %" PRIu64 "\n"),
ino);
junkit = 1;
}
if (xfs_dir2_sf_get_offset(sfep) < offset) {
do_warn(
_("entry contains offset out of order in shortform dir %" PRIu64 "\n"),
ino);
bad_offset = 1;
}
offset = xfs_dir2_sf_get_offset(sfep) +
libxfs_dir2_data_entsize(mp, namelen);
/*
* junk the entry by copying up the rest of the
* fork over the current entry and decrementing
* the entry count. if we're in no_modify mode,
* just issue the warning instead. then continue
* the loop with the next_sfep pointer set to the
* correct place in the fork and other counters
* properly set to reflect the deletion if it
* happened.
*/
if (junkit) {
memmove(name, sfep->name, namelen);
name[namelen] = '\0';
if (!no_modify) {
tmp_elen = libxfs_dir2_sf_entsize(mp, sfp,
sfep->namelen);
be64_add_cpu(&dip->di_size, -tmp_elen);
ino_dir_size -= tmp_elen;
tmp_sfep = (xfs_dir2_sf_entry_t *)
((intptr_t) sfep + tmp_elen);
tmp_len = max_size - ((intptr_t) tmp_sfep
- (intptr_t) sfp);
memmove(sfep, tmp_sfep, tmp_len);
sfp->count -= 1;
num_entries--;
memset((void *) ((intptr_t) sfep + tmp_len), 0,
tmp_elen);
/*
* reset the tmp value to the current
* pointer so we'll process the entry
* we just moved up
*/
tmp_sfep = sfep;
/*
* WARNING: drop the index i by one
* so it matches the decremented count
* for accurate comparisons later
*/
i--;
*dino_dirty = 1;
*repair = 1;
do_warn(
_("junking entry \"%s\" in directory inode %" PRIu64 "\n"),
name, ino);
} else {
do_warn(
_("would have junked entry \"%s\" in directory inode %" PRIu64 "\n"),
name, ino);
}
} else if (lino > XFS_DIR2_MAX_SHORT_INUM)
i8++;
/*
* go onto next entry unless we've just junked an
* entry in which the current entry pointer points
* to an unprocessed entry. have to take into zero-len
* entries into account in no modify mode since we
* calculate size based on next_sfep.
*/
next_sfep = (tmp_sfep == NULL)
? (xfs_dir2_sf_entry_t *) ((intptr_t) sfep
+ ((!bad_sfnamelen)
? libxfs_dir2_sf_entsize(mp, sfp, sfep->namelen)
: libxfs_dir2_sf_entsize(mp, sfp, namelen)))
: tmp_sfep;
}
/* sync up sizes and entry counts */
if (sfp->count != i) {
if (no_modify) {
do_warn(
_("would have corrected entry count in directory %" PRIu64 " from %d to %d\n"),
ino, sfp->count, i);
} else {
do_warn(
_("corrected entry count in directory %" PRIu64 ", was %d, now %d\n"),
ino, sfp->count, i);
sfp->count = i;
*dino_dirty = 1;
*repair = 1;
}
}
if (sfp->i8count != i8) {
if (no_modify) {
do_warn(
_("would have corrected i8 count in directory %" PRIu64 " from %d to %d\n"),
ino, sfp->i8count, i8);
} else {
do_warn(
_("corrected i8 count in directory %" PRIu64 ", was %d, now %d\n"),
ino, sfp->i8count, i8);
if (i8 == 0)
process_sf_dir2_fixi8(mp, sfp, &next_sfep);
else
sfp->i8count = i8;
*dino_dirty = 1;
*repair = 1;
}
}
if ((intptr_t)next_sfep - (intptr_t)sfp != ino_dir_size) {
if (no_modify) {
do_warn(
_("would have corrected directory %" PRIu64 " size from %" PRId64 " to %" PRIdPTR "\n"),
ino, ino_dir_size,
(intptr_t)next_sfep - (intptr_t)sfp);
} else {
do_warn(
_("corrected directory %" PRIu64 " size, was %" PRId64 ", now %" PRIdPTR "\n"),
ino, ino_dir_size,
(intptr_t)next_sfep - (intptr_t)sfp);
dip->di_size = cpu_to_be64(
(intptr_t)next_sfep - (intptr_t)sfp);
*dino_dirty = 1;
*repair = 1;
}
}
if (offset + (sfp->count + 2) * sizeof(xfs_dir2_leaf_entry_t) +
sizeof(xfs_dir2_block_tail_t) > mp->m_dir_geo->blksize) {
do_warn(_("directory %" PRIu64 " offsets too high\n"), ino);
bad_offset = 1;
}
if (bad_offset) {
if (no_modify) {
do_warn(
_("would have corrected entry offsets in directory %" PRIu64 "\n"),
ino);
} else {
do_warn(
_("corrected entry offsets in directory %" PRIu64 "\n"),
ino);
process_sf_dir2_fixoff(mp, dip);
*dino_dirty = 1;
*repair = 1;
}
}
/*
* check parent (..) entry
*/
*parent = libxfs_dir2_sf_get_parent_ino(sfp);
/*
* if parent entry is bogus, null it out. we'll fix it later .
* If the validation fails for the root inode we fix it in
* the next else case.
*/
if (!libxfs_verify_dir_ino(mp, *parent) && ino != mp->m_sb.sb_rootino) {
do_warn(
_("bogus .. inode number (%" PRIu64 ") in directory inode %" PRIu64 ", "),
*parent, ino);
*parent = NULLFSINO;
if (!no_modify) {
do_warn(_("clearing inode number\n"));
libxfs_dir2_sf_put_parent_ino(sfp, zero);
*dino_dirty = 1;
*repair = 1;
} else {
do_warn(_("would clear inode number\n"));
}
} else if (ino == mp->m_sb.sb_rootino && ino != *parent) {
/*
* root directories must have .. == .
*/
if (!no_modify) {
do_warn(
_("corrected root directory %" PRIu64 " .. entry, was %" PRIu64 ", now %" PRIu64 "\n"),
ino, *parent, ino);
*parent = ino;
libxfs_dir2_sf_put_parent_ino(sfp, ino);
*dino_dirty = 1;
*repair = 1;
} else {
do_warn(
_("would have corrected root directory %" PRIu64 " .. entry from %" PRIu64" to %" PRIu64 "\n"),
ino, *parent, ino);
}
} else if (ino == *parent && ino != mp->m_sb.sb_rootino) {
/*
* likewise, non-root directories can't have .. pointing
* to .
*/
*parent = NULLFSINO;
do_warn(
_("bad .. entry in directory inode %" PRIu64 ", points to self, "),
ino);
if (!no_modify) {
do_warn(_("clearing inode number\n"));
libxfs_dir2_sf_put_parent_ino(sfp, zero);
*dino_dirty = 1;
*repair = 1;
} else {
do_warn(_("would clear inode number\n"));
}
}
return(0);
}
/*
* Process one directory data block.
*/
/* ARGSUSED */
static int
process_dir2_data(
xfs_mount_t *mp,
xfs_ino_t ino,
xfs_dinode_t *dip,
int ino_discovery,
char *dirname, /* directory pathname */
xfs_ino_t *parent, /* out - NULLFSINO if entry not exist */
struct xfs_buf *bp,
int *dot, /* out - 1 if there is a dot, else 0 */
int *dotdot, /* out - 1 if there's a dotdot, else 0 */
xfs_dablk_t da_bno,
char *endptr,
int *dirty)
{
int badbest;
xfs_dir2_data_free_t *bf;
int clearino;
char *clearreason = NULL;
struct xfs_dir2_data_hdr *d;
xfs_dir2_data_entry_t *dep;
xfs_dir2_data_free_t *dfp;
xfs_dir2_data_unused_t *dup;
int freeseen;
int i;
int ino_off;
ino_tree_node_t *irec_p;
int junkit;
int lastfree;
int nm_illegal;
char *ptr;
xfs_ino_t ent_ino;
d = bp->b_addr;
bf = libxfs_dir2_data_bestfree_p(mp, d);
ptr = (char *)d + mp->m_dir_geo->data_entry_offset;
badbest = lastfree = freeseen = 0;
if (be16_to_cpu(bf[0].length) == 0) {
badbest |= be16_to_cpu(bf[0].offset) != 0;
freeseen |= 1 << 0;
}
if (be16_to_cpu(bf[1].length) == 0) {
badbest |= be16_to_cpu(bf[1].offset) != 0;
freeseen |= 1 << 1;
}
if (be16_to_cpu(bf[2].length) == 0) {
badbest |= be16_to_cpu(bf[2].offset) != 0;
freeseen |= 1 << 2;
}
badbest |= be16_to_cpu(bf[0].length) < be16_to_cpu(bf[1].length);
badbest |= be16_to_cpu(bf[1].length) < be16_to_cpu(bf[2].length);
while (ptr < endptr) {
dup = (xfs_dir2_data_unused_t *)ptr;
/*
* If it's unused, look for the space in the bestfree table.
* If we find it, account for that, else make sure it doesn't
* need to be there.
*/
if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
if (ptr + be16_to_cpu(dup->length) > endptr ||
be16_to_cpu(dup->length) == 0 ||
(be16_to_cpu(dup->length) & (XFS_DIR2_DATA_ALIGN - 1)))
break;
if (be16_to_cpu(*xfs_dir2_data_unused_tag_p(dup)) !=
(char *)dup - (char *)d)
break;
badbest |= lastfree != 0;
dfp = xfs_dir2_data_freefind(d, bf, dup);
if (dfp) {
i = dfp - bf;
badbest |= (freeseen & (1 << i)) != 0;
freeseen |= 1 << i;
} else
badbest |= be16_to_cpu(dup->length) >
be16_to_cpu(bf[2].length);
ptr += be16_to_cpu(dup->length);
lastfree = 1;
continue;
}
dep = (xfs_dir2_data_entry_t *)ptr;
if (ptr + libxfs_dir2_data_entsize(mp, dep->namelen) > endptr)
break;
if (be16_to_cpu(*libxfs_dir2_data_entry_tag_p(mp, dep)) !=
(char *)dep - (char *)d)
break;
ptr += libxfs_dir2_data_entsize(mp, dep->namelen);
lastfree = 0;
}
/*
* Dropped out before we processed everything, give up.
* Phase 6 will kill this block if we don't kill the inode.
*/
if (ptr != endptr) {
do_warn(_("corrupt block %u in directory inode %" PRIu64 "\n"),
da_bno, ino);
if (!no_modify)
do_warn(_("\twill junk block\n"));
else
do_warn(_("\twould junk block\n"));
return 1;
}
ptr = (char *)d + mp->m_dir_geo->data_entry_offset;
/*
* Process the entries now.
*/
while (ptr < endptr) {
dup = (xfs_dir2_data_unused_t *)ptr;
if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
ptr += be16_to_cpu(dup->length);
continue;
}
dep = (xfs_dir2_data_entry_t *)ptr;
ent_ino = be64_to_cpu(dep->inumber);
clearino = 1;
clearreason = NULL;
/*
* We may have to blow out an entry because of bad inode
* numbers. Do NOT touch the name until after we've computed
* the hashvalue and done a namecheck() on the name.
*
* Conditions must either set clearino to zero or set
* clearreason why it's being cleared.
*/
if (!ino_discovery && dep->name[0] == '/') {
/*
* Don't do a damned thing. We already found this
* (or did it ourselves) during phase 3.
*/
clearino = 0;
} else if (!libxfs_verify_dir_ino(mp, ent_ino)) {
/*
* Bad inode number. Clear the inode number and the
* entry will get removed later. We don't trash the
* directory since it's still structurally intact.
*/
clearreason = _("invalid");
} else if (ent_ino == mp->m_sb.sb_rbmino) {
clearreason = _("realtime bitmap");
} else if (ent_ino == mp->m_sb.sb_rsumino) {
clearreason = _("realtime summary");
} else if (ent_ino == mp->m_sb.sb_uquotino) {
clearreason = _("user quota");
} else if (ent_ino == mp->m_sb.sb_gquotino) {
clearreason = _("group quota");
} else if (ent_ino == mp->m_sb.sb_pquotino) {
clearreason = _("project quota");
} else {
irec_p = find_inode_rec(mp,
XFS_INO_TO_AGNO(mp, ent_ino),
XFS_INO_TO_AGINO(mp, ent_ino));
if (irec_p == NULL) {
if (ino_discovery) {
add_inode_uncertain(mp, ent_ino, 0);
clearino = 0;
} else
clearreason = _("non-existent");
} else {
/*
* Inode recs should have only confirmed
* inodes in them.
*/
ino_off = XFS_INO_TO_AGINO(mp, ent_ino)
- irec_p->ino_startnum;
ASSERT(is_inode_confirmed(irec_p, ino_off));
/*
* If inode is marked free and we're in inode
* discovery mode, leave the entry alone for
* now. If the inode turns out to be used,
* we'll figure that out when we scan it.
* If the inode really is free, we'll hit this
* code again in phase 4 after we've finished
* inode discovery and blow out the entry then.
*/
if (!ino_discovery && is_inode_free(irec_p,
ino_off))
clearreason = _("free");
else
clearino = 0;
}
}
ASSERT((clearino == 0 && clearreason == NULL) ||
(clearino != 0 && clearreason != NULL));
if (clearino)
do_warn(
_("entry \"%*.*s\" at block %d offset %" PRIdPTR " in directory inode %" PRIu64
" references %s inode %" PRIu64 "\n"),
dep->namelen, dep->namelen, dep->name,
da_bno, (intptr_t)ptr - (intptr_t)d, ino,
clearreason, ent_ino);
/*
* We have a special dot & dotdot fixer-upper below which can
* sort out the proper inode number, so don't clear it.
*/
if ((dep->namelen == 1 && dep->name[0] == '.') ||
(dep->namelen == 2 &&
dep->name[0] == '.' && dep->name[1] == '.')) {
clearino = 0;
clearreason = NULL;
}
/*
* If the name length is 0 (illegal) make it 1 and blast
* the entry.
*/
if (dep->namelen == 0) {
do_warn(
_("entry at block %u offset %" PRIdPTR " in directory inode %" PRIu64
"has 0 namelength\n"),
da_bno, (intptr_t)ptr - (intptr_t)d, ino);
if (!no_modify)
dep->namelen = 1;
clearino = 1;
}
/*
* If needed to clear the inode number, do it now.
*/
if (clearino) {
if (!no_modify) {
do_warn(
_("\tclearing inode number in entry at offset %" PRIdPTR "...\n"),
(intptr_t)ptr - (intptr_t)d);
dep->name[0] = '/';
*dirty = 1;
} else {
do_warn(
_("\twould clear inode number in entry at offset %" PRIdPTR "...\n"),
(intptr_t)ptr - (intptr_t)d);
}
}
/*
* Only complain about illegal names in phase 3 (when inode
* discovery is turned on). Otherwise, we'd complain a lot
* during phase 4.
*/
junkit = dep->name[0] == '/';
nm_illegal = !libxfs_dir2_namecheck(dep->name, dep->namelen);
if (ino_discovery && nm_illegal) {
do_warn(
_("entry at block %u offset %" PRIdPTR " in directory inode %" PRIu64 " has illegal name \"%*.*s\": "),
da_bno, (intptr_t)ptr - (intptr_t)d, ino,
dep->namelen, dep->namelen, dep->name);
junkit = 1;
}
/*
* Ensure we write back bad entries for later processing
*/
if (!no_modify && dep->name[0] == '/') {
*dirty = 1;
junkit = 0;
}
/*
* Special .. entry processing.
*/
if (dep->namelen == 2 &&
dep->name[0] == '.' && dep->name[1] == '.') {
if (!*dotdot) {
(*dotdot)++;
*parent = ent_ino;
/*
* What if .. == .? Legal only in the root
* inode. Blow out entry and set parent to
* NULLFSINO otherwise.
*/
if (ino == ent_ino &&
ino != mp->m_sb.sb_rootino) {
*parent = NULLFSINO;
do_warn(
_("bad .. entry in directory inode %" PRIu64 ", points to self: "),
ino);
junkit = 1;
}
/*
* We have to make sure that . == .. in the
* root inode.
*/
else if (ino != ent_ino &&
ino == mp->m_sb.sb_rootino) {
do_warn(
_("bad .. entry in root directory inode %" PRIu64 ", was %" PRIu64 ": "),
ino, ent_ino);
if (!no_modify) {
do_warn(_("correcting\n"));
dep->inumber = cpu_to_be64(ino);
*dirty = 1;
} else {
do_warn(_("would correct\n"));
}
*parent = ino;
}
/*
* Make sure our parent directory doesn't point
* off into space.
*/
if (!junkit &&
*parent != NULLFSINO &&
!libxfs_verify_ino(mp, *parent)) {
do_warn(
_("bad .. entry in directory inode %" PRIu64 ", was %" PRIu64 ": "),
ino, *parent);
if (!no_modify) {
do_warn(_("correcting\n"));
} else {
do_warn(_("would correct\n"));
}
*parent = NULLFSINO;
}
}
/*
* Can't fix the directory unless we know which ..
* entry is the right one. Both have valid inode
* numbers or we wouldn't be here. So since both
* seem equally valid, trash this one.
*/
else {
do_warn(
_("multiple .. entries in directory inode %" PRIu64 ": "),
ino);
junkit = 1;
}
}
/*
* Special . entry processing.
*/
else if (dep->namelen == 1 && dep->name[0] == '.') {
if (!*dot) {
(*dot)++;
if (ent_ino != ino) {
do_warn(
_("bad . entry in directory inode %" PRIu64 ", was %" PRIu64 ": "),
ino, ent_ino);
if (!no_modify) {
do_warn(_("correcting\n"));
dep->inumber = cpu_to_be64(ino);
*dirty = 1;
} else {
do_warn(_("would correct\n"));
}
}
} else {
do_warn(
_("multiple . entries in directory inode %" PRIu64 ": "),
ino);
junkit = 1;
}
}
/*
* All other entries -- make sure only . references self.
*/
else if (ent_ino == ino) {
do_warn(
_("entry \"%*.*s\" in directory inode %" PRIu64 " points to self: "),
dep->namelen, dep->namelen, dep->name, ino);
junkit = 1;
}
/*
* Clear junked entries.
*/
if (junkit) {
if (!no_modify) {
dep->name[0] = '/';
*dirty = 1;
do_warn(_("clearing entry\n"));
} else {
do_warn(_("would clear entry\n"));
}
}
/*
* Advance to the next entry.
*/
ptr += libxfs_dir2_data_entsize(mp, dep->namelen);
}
/*
* Check the bestfree table.
*/
if (freeseen != 7 || badbest) {
do_warn(
_("bad bestfree table in block %u in directory inode %" PRIu64 ": "),
da_bno, ino);
if (!no_modify) {
do_warn(_("repairing table\n"));
libxfs_dir2_data_freescan(mp, d, &i);
*dirty = 1;
} else {
do_warn(_("would repair table\n"));
}
}
return 0;
}
/*
* Process a block-format directory.
*/
/* ARGSUSED */
static int
process_block_dir2(
xfs_mount_t *mp,
xfs_ino_t ino,
xfs_dinode_t *dip,
int ino_discovery,
int *dino_dirty, /* out - 1 if dinode buffer dirty */
char *dirname, /* directory pathname */
xfs_ino_t *parent, /* out - NULLFSINO if entry not exist */
blkmap_t *blkmap,
int *dot, /* out - 1 if there is a dot, else 0 */
int *dotdot, /* out - 1 if there's a dotdot, else 0 */
int *repair) /* out - 1 if something was fixed */
{
struct xfs_dir2_data_hdr *block;
xfs_dir2_leaf_entry_t *blp;
bmap_ext_t *bmp;
struct xfs_buf *bp;
xfs_dir2_block_tail_t *btp;
int nex;
int rval;
bmap_ext_t lbmp;
int dirty = 0;
*repair = *dot = *dotdot = 0;
*parent = NULLFSINO;
nex = blkmap_getn(blkmap, mp->m_dir_geo->datablk,
mp->m_dir_geo->fsbcount, &bmp, &lbmp);
if (nex == 0) {
do_warn(
_("block %u for directory inode %" PRIu64 " is missing\n"),
mp->m_dir_geo->datablk, ino);
return 1;
}
bp = da_read_buf(mp, nex, bmp, &xfs_dir3_block_buf_ops);
if (bmp != &lbmp)
free(bmp);
if (bp == NULL) {
do_warn(
_("can't read block %u for directory inode %" PRIu64 "\n"),
mp->m_dir_geo->datablk, ino);
return 1;
}
/*
* Verify the block
*/
block = bp->b_addr;
if (!(be32_to_cpu(block->magic) == XFS_DIR2_BLOCK_MAGIC ||
be32_to_cpu(block->magic) == XFS_DIR3_BLOCK_MAGIC))
do_warn(
_("bad directory block magic # %#x in block %u for directory inode %" PRIu64 "\n"),
be32_to_cpu(block->magic), mp->m_dir_geo->datablk, ino);
/*
* process the data area
* this also checks & fixes the bestfree
*/
btp = xfs_dir2_block_tail_p(mp->m_dir_geo, block);
blp = xfs_dir2_block_leaf_p(btp);
/*
* Don't let this go past the end of the block.
*/
if ((char *)blp > (char *)btp)
blp = (xfs_dir2_leaf_entry_t *)btp;
rval = process_dir2_data(mp, ino, dip, ino_discovery, dirname, parent,
bp, dot, dotdot, mp->m_dir_geo->datablk, (char *)blp, &dirty);
/* If block looks ok but CRC didn't match, make sure to recompute it. */
if (!rval && bp->b_error == -EFSBADCRC)
dirty = 1;
if (dirty && !no_modify) {
*repair = 1;
libxfs_buf_mark_dirty(bp);
libxfs_buf_relse(bp);
} else
libxfs_buf_relse(bp);
return rval;
}
/*
* Validates leaf contents, node format directories only.
* magic number and sibling pointers checked by caller.
* Returns 0 if block is ok, 1 if the block is bad.
* Looking for: out of order hash values, bad stale counts.
*/
static int
process_leaf_block_dir2(
xfs_mount_t *mp,
xfs_dir2_leaf_t *leaf,
xfs_dablk_t da_bno,
xfs_ino_t ino,
xfs_dahash_t last_hashval,
xfs_dahash_t *next_hashval)
{
int i;
int stale;
struct xfs_dir2_leaf_entry *ents;
struct xfs_dir3_icleaf_hdr leafhdr;
libxfs_dir2_leaf_hdr_from_disk(mp, &leafhdr, leaf);
ents = leafhdr.ents;
for (i = stale = 0; i < leafhdr.count; i++) {
if ((char *)&ents[i] >= (char *)leaf + mp->m_dir_geo->blksize) {
do_warn(
_("bad entry count in block %u of directory inode %" PRIu64 "\n"),
da_bno, ino);
return 1;
}
if (be32_to_cpu(ents[i].address) == XFS_DIR2_NULL_DATAPTR)
stale++;
else if (be32_to_cpu(ents[i].hashval) < last_hashval) {
do_warn(
_("bad hash ordering in block %u of directory inode %" PRIu64 "\n"),
da_bno, ino);
return 1;
}
*next_hashval = last_hashval = be32_to_cpu(ents[i].hashval);
}
if (stale != leafhdr.stale) {
do_warn(
_("bad stale count in block %u of directory inode %" PRIu64 "\n"),
da_bno, ino);
return 1;
}
return 0;
}
/*
* Returns 0 if the directory is ok, 1 if it has to be rebuilt.
*/
static int
process_leaf_level_dir2(
xfs_mount_t *mp,
da_bt_cursor_t *da_cursor,
int *repair)
{
bmap_ext_t *bmp;
struct xfs_buf *bp;
int buf_dirty;
xfs_dahash_t current_hashval;
xfs_dablk_t da_bno;
xfs_dahash_t greatest_hashval;
xfs_ino_t ino;
xfs_dir2_leaf_t *leaf;
int nex;
xfs_dablk_t prev_bno;
bmap_ext_t lbmp;
struct xfs_dir3_icleaf_hdr leafhdr;
da_bno = da_cursor->level[0].bno;
ino = da_cursor->ino;
prev_bno = 0;
bmp = NULL;
current_hashval = 0;
greatest_hashval = 0;
buf_dirty = 0;
do {
nex = blkmap_getn(da_cursor->blkmap, da_bno,
mp->m_dir_geo->fsbcount, &bmp, &lbmp);
/*
* Directory code uses 0 as the NULL block pointer since 0
* is the root block and no directory block pointer can point
* to the root block of the btree.
*/
ASSERT(da_bno != 0);
if (nex == 0) {
do_warn(
_("can't map block %u for directory inode %" PRIu64 "\n"),
da_bno, ino);
goto error_out;
}
bp = da_read_buf(mp, nex, bmp, &xfs_dir3_leafn_buf_ops);
if (bmp != &lbmp)
free(bmp);
bmp = NULL;
if (bp == NULL) {
do_warn(
_("can't read file block %u for directory inode %" PRIu64 "\n"),
da_bno, ino);
goto error_out;
}
leaf = bp->b_addr;
libxfs_dir2_leaf_hdr_from_disk(mp, &leafhdr, leaf);
/*
* Check magic number for leaf directory btree block.
*/
if (!(leafhdr.magic == XFS_DIR2_LEAFN_MAGIC ||
leafhdr.magic == XFS_DIR3_LEAFN_MAGIC)) {
do_warn(
_("bad directory leaf magic # %#x for directory inode %" PRIu64 " block %u\n"),
leafhdr.magic, ino, da_bno);
libxfs_buf_relse(bp);
goto error_out;
}
buf_dirty = 0;
/*
* For each block, process the block, verify its path,
* then get next block. Update cursor values along the way.
*/
if (process_leaf_block_dir2(mp, leaf, da_bno, ino,
current_hashval, &greatest_hashval)) {
libxfs_buf_relse(bp);
goto error_out;
}
/*
* Index can be set to hdr.count so match the indices 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 = buf_dirty;
if (leafhdr.back != prev_bno) {
do_warn(
_("bad sibling back pointer for block %u in directory inode %" PRIu64 "\n"),
da_bno, ino);
libxfs_buf_relse(bp);
goto error_out;
}
prev_bno = da_bno;
da_bno = leafhdr.forw;
if (da_bno != 0) {
if (verify_da_path(mp, da_cursor, 0, XFS_DATA_FORK)) {
libxfs_buf_relse(bp);
goto error_out;
}
}
current_hashval = greatest_hashval;
/*
* If block looks ok but CRC didn't match, make sure to
* recompute it.
*/
if (!no_modify && bp->b_error == -EFSBADCRC)
buf_dirty = 1;
ASSERT(buf_dirty == 0 || (buf_dirty && !no_modify));
if (buf_dirty && !no_modify) {
*repair = 1;
libxfs_buf_mark_dirty(bp);
libxfs_buf_relse(bp);
} else
libxfs_buf_relse(bp);
} while (da_bno != 0);
if (verify_final_da_path(mp, da_cursor, 0, XFS_DATA_FORK)) {
/*
* Verify the final path up (right-hand-side) if still ok.
*/
do_warn(_("bad hash path in directory %" PRIu64 "\n"), ino);
goto error_out;
}
/*
* Redundant but just for testing.
*/
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);
if (bmp && (bmp != &lbmp))
free(bmp);
return 1;
}
/*
* Return 1 if the directory's leaf/node space is corrupted and
* needs to be rebuilt, 0 if it's ok.
*/
static int
process_node_dir2(
xfs_mount_t *mp,
xfs_ino_t ino,
xfs_dinode_t *dip,
blkmap_t *blkmap,
int *repair)
{
xfs_dablk_t bno;
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_cursor));
da_cursor.ino = ino;
da_cursor.dip = dip;
da_cursor.blkmap = blkmap;
/*
* Now process interior node.
*/
if (traverse_int_dablock(mp, &da_cursor, &bno, XFS_DATA_FORK) == 0)
return 1;
/*
* Skip directories with a root marked XFS_DIR2_LEAFN_MAGIC
*
* Be careful here: If any level of the da cursor was filled out then
* the directory has a da btree containing an invalid before pointer to
* dblock 0, and we should move on to rebuilding the directory. If no
* levels in the da cursor got filled out, then we just have a single
* leafn block and we're done.
*/
if (bno == 0) {
if (da_cursor.active > 0) {
err_release_da_cursor(mp, &da_cursor, 0);
return 1;
} else {
release_da_cursor(mp, &da_cursor, 0);
return 0;
}
} else {
/*
* 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_level_dir2(mp, &da_cursor, repair);
}
}
/*
* Process leaf and node directories.
* Process the data blocks then, if it's a node directory, check
* the consistency of those blocks.
*/
static int
process_leaf_node_dir2(
xfs_mount_t *mp,
xfs_ino_t ino,
xfs_dinode_t *dip,
int ino_discovery,
char *dirname, /* directory pathname */
xfs_ino_t *parent, /* out - NULLFSINO if entry not exist */
blkmap_t *blkmap,
int *dot, /* out - 1 if there is a dot, else 0 */
int *dotdot, /* out - 1 if there's a dotdot, else 0 */
int *repair, /* out - 1 if something was fixed */
int isnode) /* node directory not leaf */
{
bmap_ext_t *bmp;
struct xfs_buf *bp;
struct xfs_dir2_data_hdr *data;
xfs_fileoff_t dbno;
int good;
int i;
xfs_fileoff_t ndbno;
int nex;
int t;
bmap_ext_t lbmp;
int dirty = 0;
*repair = *dot = *dotdot = good = 0;
*parent = NULLFSINO;
ndbno = NULLFILEOFF;
while ((dbno = blkmap_next_off(blkmap, ndbno, &t)) < mp->m_dir_geo->leafblk) {
nex = blkmap_getn(blkmap, dbno, mp->m_dir_geo->fsbcount, &bmp, &lbmp);
/* Advance through map to last dfs block in this dir block */
ndbno = dbno;
while (ndbno < dbno + mp->m_dir_geo->fsbcount - 1) {
ndbno = blkmap_next_off(blkmap, ndbno, &t);
}
if (nex == 0) {
do_warn(
_("block %" PRIu64 " for directory inode %" PRIu64 " is missing\n"),
dbno, ino);
continue;
}
bp = da_read_buf(mp, nex, bmp, &xfs_dir3_data_buf_ops);
if (bmp != &lbmp)
free(bmp);
if (bp == NULL) {
do_warn(
_("can't read block %" PRIu64 " for directory inode %" PRIu64 "\n"),
dbno, ino);
continue;
}
data = bp->b_addr;
if (!(be32_to_cpu(data->magic) == XFS_DIR2_DATA_MAGIC ||
be32_to_cpu(data->magic) == XFS_DIR3_DATA_MAGIC))
do_warn(
_("bad directory block magic # %#x in block %" PRIu64 " for directory inode %" PRIu64 "\n"),
be32_to_cpu(data->magic), dbno, ino);
i = process_dir2_data(mp, ino, dip, ino_discovery, dirname,
parent, bp, dot, dotdot, (xfs_dablk_t)dbno,
(char *)data + mp->m_dir_geo->blksize, &dirty);
if (i == 0) {
good++;
/* Maybe just CRC is wrong. Make sure we correct it. */
if (bp->b_error == -EFSBADCRC)
dirty = 1;
}
if (dirty && !no_modify) {
*repair = 1;
libxfs_buf_mark_dirty(bp);
libxfs_buf_relse(bp);
} else
libxfs_buf_relse(bp);
}
if (good == 0)
return 1;
if (!isnode)
return 0;
if (dir2_is_badino(ino))
return 0;
if (process_node_dir2(mp, ino, dip, blkmap, repair))
dir2_add_badlist(ino);
return 0;
}
/*
* Returns 1 if things are bad (directory needs to be junked)
* and 0 if things are ok. If ino_discovery is 1, add unknown
* inodes to uncertain inode list.
*/
int
process_dir2(
xfs_mount_t *mp,
xfs_ino_t ino,
xfs_dinode_t *dip,
int ino_discovery,
int *dino_dirty,
char *dirname,
xfs_ino_t *parent,
blkmap_t *blkmap)
{
int dot;
int dotdot;
xfs_fileoff_t last;
int repair;
int res;
*parent = NULLFSINO;
dot = dotdot = 0;
last = 0;
/*
* branch off depending on the type of inode. This routine
* is only called ONCE so all the subordinate routines will
* fix '.' and junk '..' if they're bogus.
*/
if (blkmap)
last = blkmap_last_off(blkmap);
if (be64_to_cpu(dip->di_size) <= XFS_DFORK_DSIZE(dip, mp) &&
dip->di_format == XFS_DINODE_FMT_LOCAL) {
dot = dotdot = 1;
res = process_sf_dir2(mp, ino, dip, ino_discovery, dino_dirty,
dirname, parent, &repair);
} else if (last == mp->m_dir_geo->fsbcount &&
(dip->di_format == XFS_DINODE_FMT_EXTENTS ||
dip->di_format == XFS_DINODE_FMT_BTREE)) {
res = process_block_dir2(mp, ino, dip, ino_discovery,
dino_dirty, dirname, parent, blkmap, &dot, &dotdot,
&repair);
} else if (last >= mp->m_dir_geo->leafblk + mp->m_dir_geo->fsbcount &&
(dip->di_format == XFS_DINODE_FMT_EXTENTS ||
dip->di_format == XFS_DINODE_FMT_BTREE)) {
res = process_leaf_node_dir2(mp, ino, dip, ino_discovery,
dirname, parent, blkmap, &dot, &dotdot, &repair,
last > mp->m_dir_geo->leafblk + mp->m_dir_geo->fsbcount);
} else {
do_warn(_("bad size/format for directory %" PRIu64 "\n"), ino);
return 1;
}
/*
* bad . entries in all directories will be fixed up in phase 6
*/
if (dot == 0) {
do_warn(_("no . entry for directory %" PRIu64 "\n"), ino);
}
/*
* shortform dirs always have a .. entry. .. for all longform
* directories will get fixed in phase 6. .. for other shortform
* dirs also get fixed there. .. for a shortform root was
* fixed in place since we know what it should be
*/
if (dotdot == 0 && ino != mp->m_sb.sb_rootino) {
do_warn(_("no .. entry for directory %" PRIu64 "\n"), ino);
} else if (dotdot == 0 && ino == mp->m_sb.sb_rootino) {
do_warn(_("no .. entry for root directory %" PRIu64 "\n"), ino);
need_root_dotdot = 1;
}
ASSERT((ino != mp->m_sb.sb_rootino && ino != *parent) ||
(ino == mp->m_sb.sb_rootino &&
(ino == *parent || need_root_dotdot == 1)));
return res;
}