blob: f3c7be6aa9e996cf42d4afc4f966222171c65d98 [file] [log] [blame]
# SPDX-License-Identifier: GPL-2.0
A living document. The basic algorithm.
TODO: (D == DONE)
0) Need to bring some sanity into the case of flags that can
be set in the secondaries at mkfs time but reset or cleared
in the primary later in the filesystem's life.
0) Clear the persistent read-only bit if set. Clear the
shared bit if set and the version number is zero. This
brings the filesystem back to a known state.
0) make sure that superblock geometry code checks the logstart
value against whether or not we have an internal log.
If we have an internal log and a logdev, that's ok.
(Maybe we just aren't using it). If we have an external
log (logstart == 0) but no logdev, that's right out.
0) write secondary superblock search code. Rewrite initial
superblock parsing code to be less complicated. Just
use variables to indicate primary, secondary, etc.,
and use a function to get the SB given a specific location
or something.
2) For inode alignment, if the SB bit is set and the
inode alignment size field in the SB is set, then
believe that the fs inodes MUST be aligned and
disallow any non-aligned inodes. Likewise, if
the SB bit isn't set (or earlier version) and
the inode alignment size field is zero, then
never set the bit even if the inodes are aligned.
Note that the bits and alignment values are
replicated in the secondary superblocks.
0) add feature specification options to parse_arguments
0) add logic to add_inode_ref(), add_inode_reached()
to detect nlink overflows in cases where the fs
(or user had indicated fs) doesn't support new nlinks.
6) check to make sure that the inodes containing btree blocks
with # recs < minrecs aren't legit -- e.g. the only
descendant of a root block.
7) inode di_size value sanity checking -- should always be less than
the biggest filebno offset mentioned in the bmaps. Doesn't
have to be equal though since we're allowed to overallocate
(it just wastes a little space). This is for both regular
files and directories (have to modify the existing directory
check).
Add tracking of largest offset in bmap scanning code. Compare
value against di_size. Should be >= di_size.
Alternatively, you could pass the inode into down through
the extent record processing layer and make the checks
there.
Add knowledge of quota inodes. size of quota inode is
always zero. We should maintain that.
8) Basic quota stuff.
Invariants
if quota feature bit is set, the quota inodes
if set, should point to disconnected, 0 len inodes.
D - if quota inodes exist, the quota bits must be
turned on. It's ok for the quota flags to be
zeroed but they should be in a legal state
(see xfs_quota.h).
D - if the quota flags are non-zero, the corresponding
quota inodes must exist.
quota inodes are never deleted, only their space
is freed.
if quotas are being downgraded, then check quota inodes
at the end of phase 3. If they haven't been cleared yet,
clear them. Regardless, then clear sb flags (quota inode
fields, quota flags, and quota bit).
5) look at verify_inode_chunk(). it's probably really broken.
9) Complicated quota stuff. Add code to bmap scan code to
track used blocks. Add another pair of AVL trees
to track user and project quota limits. Set AVL
trees up at the beginning of phase 3. Quota inodes
can be rebuilt or corrected later if damaged.
D - 0) fix directory processing. phase 3, if an entry references
a free inode, *don't* mark it used. wait for the rest of
phase 3 processing to hit that inode. If it looks like it's
in use, we'll mark in use then. If not, we'll clear it and
mark the inode map. then in phase 4, you can depend on the
inode map. should probably set the parent info in phase 4.
So we have a check_dups flag. Maybe we should change the
name of check_dir to discover_inodes. During phase 3
(discover_inodes == 1), uncertain inodes are added to list.
During phase 4 (discover_inodes == 0), they aren't. And
we never mark inodes in use from the directory code.
During phase 4, we shouldn't complain about names with
a leading '/' since we made those names in phase 3.
Have to change dino_chunks.c (parent setting), dinode.c
and dir.c.
D - 0) make sure we don't screw up filesystems with real-time inodes.
remember to initialize real-time map with all blocks XR_E_FREE.
D - 4) check contents of symlinks as well as lengths in process_symlinks()
in dinode.c. Right now, we only check lengths.
D - 1) Feature mismatches -- for quotas and attributes,
if the stuff exists in the filesystem, set the
superblock version bits.
D - 0) rewrite directory leaf block holemap comparison code.
probably should just check the leaf block hole info
against our incore bitmap. If the hole flag is not
set, then we know that there can only be one hole and
it has to be between the entry table and the top of heap.
If the hole flag is set, then it's ok if the on-disk
holemap doesn't describe everything as long as what
it does describe doesn't conflict with reality.
D - 0) rewrite setting nlinks handling -- for version 1
inodes, set both nlinks and onlinks (zero projid_lo/hi
and pad) if we have to change anything. For
version 2, I think we're ok.
D - 0) Put awareness of quota inode into mark_standalone_inodes.
D - 8) redo handling of superblocks with bad version numbers. need
to bail out (without harming) fs's that have sbs that
are newer than we are.
D - 0) How do we handle feature mismatches between fs and
superblock? For nlink, check each inode after you
know it's good. If onlinks is 0 and nlinks is > 0
and it's a version 2 inode, then it really is a version
2 inode and the nlinks flag in the SB needs to be set.
If it's a version 2 inode and the SB agrees but onlink
is non-zero, then clear onlink.
D - 3) keep cumulative counts of freeblocks, inodes, etc. to set in
the superblock at the end of phase 5. Remember that
agf freeblock counters don't include blocks used by
the non-root levels of the freespace trees but that
the sb free block counters include those.
D - 0) Do parent setting in directory code (called by phase 3).
actually, I put it in process_inode_set and propagated
the parent up to it from the process_dinode/process_dir
routines. seemed cleaner than pushing the irec down
and letting them bang on it.
D - 0) If we clear a file in phase 4, make sure that if it's
a directory that the parent info is cleared also.
D - 0) put inode tree flashover (call to add_ino_backptrs) into phase 5.
D - 0) do set/get_inode_parent functions in incore_ino.c.
also do is/set/ inode_processed.
D - 0) do a versions.c to extract feature info and set global vars
from the superblock version number and possibly feature bits
D - 0) change longform_dir_entry_check + shortform_dir_entry_check
to return a count of how many illegal '/' entries exist.
if > 0, then process_dirstack needs to call prune_dir_entry
with a hash value of 0 to delete the entries.
D - 0) add the "processed" bitfield
to the backptrs_t struct that gets attached after
phase 4.
D- ) Phase 6 !!!
D - 0) look at usage of XFS_MAKE_IPTR(). It does the right
arithmetic assuming you count your offsets from the
beginning of the buffer.
D - 0) look at references to XFS_INODES_PER_CHUNK. change the
ones that really mean sizeof(uint64_t)*NBBY to
something else (like that only defined as a constant
INOS_PER_IREC. this isn't as important since
XFS_INODES_PER_CHUNK will never chang
D - 0) look at junk_zerolen_dir_leaf_entries() to make sure it isn't hosing
the freemap since it assumed that bytes between the
end of the table and firstused didn't show up in the
freemap when they actually do.
D - 0) track down XFS_INO_TO_OFFSET() usage. I don't think I'm
using it right. (e.g. I think
it gives you the offset of an inode into a block but
on small block filesystems, I may be reading in inodes
in multiblock buffers and working from the start of
the buffer plus I'm using it to get offsets into
my ino_rec's which may not be a good idea since I
use 64-inode ino_rec's whereas the offset macro
works off blocksize).
D - 0.0) put buffer -> dirblock conversion macros into xfs kernel code
D - 0.2) put in sibling pointer checking and path fixup into
bmap (long form) scan routines in scan.c
D - 0.3) find out if bmap btrees with only root blocks are legal. I'm
betting that they're not because they'd be extent inodes
instead. If that's the case, rip some code out of
process_btinode()
Algorithm (XXX means not done yet):
Phase 1 -- get a superblock and zero log
get a superblock -- either read in primary or
find a secondary (ag header), check ag headers
To find secondary:
Go for brute force and read in the filesystem N meg
at a time looking for a superblock. as a
slight optimization, we could maybe skip
ahead some number of blocks to try and get
towards the end of the first ag.
After you find a secondary, try and find at least
other ags as a verification that the
secondary is a good superblock.
XXX - Ugh. Have to take growfs'ed filesystems into account.
The root superblock geometry info may not be right if
recovery hasn't run or it's been trashed. The old ag's
may or may not be right since the system could have crashed
during growfs or the bwrite() to the superblocks could have
failed and the buffer been reused. So we need to check
to see if another ag exists beyond the "last" ag
to see if a growfs happened. If not, then we know that
the geometry info is good and treat the fs as a non-growfs'ed
fs. If we do have inconsistencies, then the smaller geometry
is the old fs and the larger the new. We can check the
new superblocks to see if they're good. If not, then we
know the system crashed at or soon after the growfs and
we can choose to either accept the new geometry info or
trash it and truncate the fs back to the old geometry
parameters.
Cross-check geometry information in secondary sb's with
primary to ensure that it's correct.
Use sim code to allow mount filesystems *without* reading
in root inode. This sets up the xfs_mount_t structure
and allows us to use XFS_* macros that we wouldn't
otherwise be able to use.
Note, I split phase 1 and 2 into separate pieces because I want
to initialize the xfs_repair incore data structures after phase 1.
parse superblock version and feature flags and set appropriate
global vars to reflect the flags (attributes, quotas, etc.)
Workaround for the mkfs "not zeroing the superblock buffer" bug.
Determine what field is the last valid non-zero field in
the superblock. The trick here is to be able to differentiate
the last valid non-zero field in the primary superblock and
secondaries because they may not be the same. Fields in
the primary can be set as the filesystem gets upgraded but
the upgrades won't touch the secondaries. This means that
we need to find some number of secondaries and check them.
So we do the checking here and the setting in phase2.
Phase 2 -- check integrity of allocation group allocation structures
zero the log if in no modify mode
sanity check ag headers -- superblocks match, agi isn't
trashed -- the agf and agfl
don't really matter because we can
just recreate them later.
Zero part of the superblock buffer if necessary
Walk the freeblock trees to get an
initial idea of what the fs thinks is free.
Files that disagree (claim free'd blocks)
can be salvaged or deleted. If the btree is
internally inconsistent, when in doubt, mark
blocks free. If they're used, they'll be stolen
back later. don't have to check sibling pointers
for each level since we're going to regenerate
all the trees anyway.
Walk the inode allocation trees and
make sure they're ok, otherwise the sim
inode routines will probably just barf.
mark inode allocation tree blocks and ag header
blocks as used blocks. If the trees are
corrupted, this phase will generate "uncertain"
inode chunks. Those chunks go on a list and
will have to verified later. Record the blocks
that are used to detect corruption and multiply
claimed blocks. These trees will be regenerated
later. Mark the blocks containing inodes referenced
by uncorrupted inode trees as being used by inodes.
The other blocks will get marked when/if the inodes
are verified.
calculate root and realtime inode numbers from the
filesystem geometry, fix up mount structure's
incore superblock if they're wrong.
ASSUMPTION: at end of phase 2, we've got superblocks and ag headers
that are not garbage (some data in them like counters and the
freeblock and inode trees may be inconsistent but the header
is readable and otherwise makes sense).
XXX if in no_modify mode, check for blocks claimed by one freespace
btree and not the other
Phase 3 -- traverse inodes to make the inodes, bmaps and freespace maps
consistent. For each ag, use either the incore inode map or
scan the ag for inodes.
Let's use the incore inode map, now that we've made one
up in phase2. If we lose the maps, we'll locate inodes
when we traverse the directory heirarchy. If we lose both,
we could scan the disk. Ugh. Maybe make that a command-line
option that we support later.
ASSUMPTION: we know if the ag allocation btrees are intact (phase 2)
First - Walk and clear the ag unlinked lists. We'll process
the inodes later. Check and make sure that the unlinked
lists reference known inodes. If not, add to the list
of uncertain inodes.
Second, check the uncertain inode list generated in phase2 and
above and get them into the inode tree if they're good.
The incore inode cluster tree *always* has good
clusters (alignment, etc.) in it.
Third, make sure that the root inode is known. If not,
and we know the inode number from the superblock,
discover that inode and its chunk.
Then, walk the incore inode-cluster tree.
Maintain an in-core bitmap over the entire fs for block allocation.
traverse each inode, make sure inode mode field matches free/allocated
bit in the incore inode allocation tree. If there's a mismatch,
assume that the inode is in use.
- for each in-use inode, traverse each bmap/dir/attribute
map or tree. Maintain a map (extent list?) for the
current inode.
- For each block marked as used, check to see if already known
(referenced by another file or directory) and sanity
check the contents of the block as well if possible
(in the case of meta-blocks).
- if the inode claims already used blocks, mark the blocks
as multiply claimed (duplicate) and go on. the inode
will be cleared in phase 4.
- if metablocks are garbaged, clear the inode after
traversing what you can of the bmap and
proceed to next inode. We don't have to worry
about trashing the maps or trees in cleared inodes
because the blocks will show up as free in the
ag freespace trees that we set up in phase 5.
- clear the di_next_unlinked pointer -- all unlinked
but active files go bye-bye.
- All blocks start out unknown. We need the last state
in case we run into a case where we need to step
on a block to store filesystem meta-data and it
turns out later that it's referenced by some inode's
bmap. In that case, the inode loses because we've
already trashed the block. This shouldn't happen
in the first version unless some inode has a bogus
bmap referencing blocks in the ag header but the
4th state will keep us from inadvertently doing
something stupid in that case.
- If inode is allocated, mark all blocks allocated to the
current inode as allocated in the incore freespace
bitmap.
- If inode is good and a directory, scan through it to
find leaf entries and discover any unknown inodes.
For shortform, we correct what we can.
If the directory is corrupt, we try and fix it in
place. If it has zero good entries, then we blast it.
All unknown inodes get put onto the uncertain inode
list. This is safe because we only put inodes onto
the list when we're processing known inodes so the
uncertain inode list isn't in use.
We fix only one problem -- an entry that has
a mathematically invalid inode numbers in them.
If that's the case, we replace the inode number
with NULLFSINO and we'll fix up the entry in
phase 6.
That info may conflict with the inode information,
but we'll straighten out any inconsistencies there
in phase4 when we process the inodes again.
Errors involving bogus forward/back links,
zero-length entries make the directory get
trashed.
if an entry references a free inode, ignore that
fact for now. wait for the rest of phase 3
processing to hit that inode. If it looks like it's
in use, we'll mark in use then. If not, we'll
clear it and mark the inode map. then in phase
4, you can depend on the inode map.
Entries that point to non-existent or free
inodes, and extra blocks in the directory
will get fixed in place in a later pass.
Entries that point to a quota inode are
marked TBD.
If the directory internally points to the same
block twice, the directory gets blown away.
Note that processing uncertain inodes can add more inodes
to the uncertain list if they're directories. So we loop
until the uncertain list is empty.
During inode verification, if the inode blocks are unknown,
mark then as in-use by inodes.
XXX HEURISTIC -- if we blow an inode away that has space,
assume that the freespace btree is now out of wack.
If it was ok earlier, it's certain to be wrong now.
And the odds of this space free cancelling out the
existing error is so small I'm willing to ignore it.
Should probably do this via a global var and complain
about this later.
Assumption: All known inodes are now marked as in-use or free. Any
inodes that we haven't found by now are hosed (lost) since
we can't reach them via either the inode btrees or via directory
entries.
Directories are semi-clean. All '.' entries are good.
Root '..' entry is good if root inode exists. All entries
referencing non-existent inodes, free inodes, etc.
XXX verify that either quota inode is 0 or NULLFSINO or
if sb quota flag is non zero, verify that quota inode
is NULLFSINO or is referencing a used, but disconnected
inode.
XXX if in no_modify mode, check for unclaimed blocks
- Phase 4 - Check for inodes referencing duplicate blocks
At this point, all known duplicate blocks are marked in
the block map. However, some of the claimed blocks in
the bmap may in fact be free because they belong to inodes
that have to be cleared either due to being a trashed
directory or because it's the first inode to claim a
block that was then claimed later. There's a similar
problem with meta-data blocks that are referenced by
inode bmaps that are going to be freed once the inode
(or directory) gets cleared.
So at this point, we collect the duplicate blocks into
extents and put them into the duplicate extent list.
Mark the ag header blocks as in use.
We then process each inode twice -- the first time
we check to see if the inode claims a duplicate extent
and we do NOT set the block bitmap. If the inode claims
a duplicate extent, we clear the inode. Since the bitmap
hasn't been set, that automatically frees all blocks associated
with the cleared inode. If the inode is ok, process it a second
time and set the bitmap since we know that this inode will live.
The unlinked list gets cleared in every inode at this point as
well. We no longer need to preserve it since we've discovered
every inode we're going to find from it.
verify existence of root inode. if it exists, check for
existence of "lost+found". If it exists, mark the entry
to be deleted, and clear the inode. All the inodes that
were connected to the lost+found will be reconnected later.
XXX HEURISTIC -- if we blow an inode away that has space,
assume that the freespace btree is now out of wack.
If it was ok earlier, it's certain to be wrong now.
And the odds of this space free cancelling out the
existing error is so small I'm willing to ignore it.
Should probably do this via a global var and complain
about this later.
Clear the quota inodes if the inode btree says that
they're not in use. The space freed will get picked
up by phase 5.
XXX Clear the quota inodes if the filesystem is being downgraded.
- Phase 5 - Build inode allocation trees, freespace trees and
agfl's for each ag. After this, we should be able to
unmount the filesystem and remount it for real.
For each ag: (if no in no_modify mode)
scan bitmap first to figure out number of extents.
calculate space required for all trees. Start with inode trees.
Setup the btree cursor which includes the list of preallocated
blocks. As a by-product, this will delete the extents required
for the inode tree from the incore extent tree.
Calculate how many extents will be required to represent the
remaining free extent tree on disk (twice, one for bybno and
one for bycnt). You have to iterate on this because consuming
extents can alter the number of blocks required to represent
the remaining extents. If there's slop left over, you can
put it in the agfl though.
Then, manually build the trees, agi, agfs, and agfls.
XXX if in no_modify mode, scan the on-disk inode allocation
trees and compare against the incore versions. Don't have
to scan the freespace trees because we caught the problems
there in phase2 and phase3. But if we cleared any inodes
with space during phases 3 or 4, now is the time to complain.
XXX - Free duplicate extent lists. ???
Assumptions: at this point, sim code having to do with inode
creation/modification/deletion and space allocation
work because the inode maps, space maps, and bmaps
for all files in the filesystem are good. The only
structures that are screwed up are the directory contents,
which means that lookup may not work for beans, the
root inode which exists but may be completely bogus and
the link counts on all inodes which may also be bogus.
Free the bitmap, the freespace tree.
Flash the incore inode tree over from parent list to having
full backpointers.
realtime processing, if any --
(Skip to below if running in no_modify mode).
Generate the realtime bitmap from the incore realtime
extent map and slam the info into the realtime bitmap
inode. Generate summary info from the realtime extent map.
XXX if in no_modify mode, compare contents of realtime bitmap
inode to the incore realtime extent map. generate the
summary info from the incore realtime extent map.
compare against the contents of the realtime summary inode.
complain if bad.
reset superblock counters, sync version numbers
- Phase 6 - directory traversal -- check reference counts,
attach disconnected inodes, fix up bogus directories
Assumptions: all on-disk space and inode trees are structurally
sound. Incore and on-disk inode trees agree on whether
an inode is in use.
Directories are structurally sound. All hashvalues
are monotonically increasing and interior nodes are
correct so lookups work. All legal directory entries
point to inodes that are in use and exist. Shortform
directories are fine except that the links haven't been
checked for conflicts (cycles, ".." being correct, etc.).
Longform directories haven't been checked for those problems
either PLUS longform directories may still contain
entries beginning with '/'. No zero-length entries
exist (they've been deleted or converted to '/').
Root directory may or may not exist. orphange may
or may not exist. Contents of either may be completely
bogus.
Entries may point to free or non-existent inodes.
At this we point, we may need new incore structures and
may be able to trash an old one (like the filesystem
block map)
If '/' is trashed, then reinitialize it.
If no realtime inodes, make them and if necessary, slam the
summary info into the realtime summary
inode. Ditto with the realtime bitmap inode.
Make orphanage (lost+found ???).
Traverse each directory from '/' (unless it was created).
Check directory structure and each directory entry.
If the entry is bogus (points to a non-existent or
free inode, for example), mark that entry TBD. Maintain
link counts on all inodes. Currently, traversal is
depth-first.
Mark every inode reached as "reached" (includes
bumping up link counts).
If a entry points to a directory but the parent (..)
disagrees, then blow away the entry. if the directory
being pointed to winds up disconnected, it'll be moved
to the orphanage (and the link count incremented to
account for the link and the reached bit set then).
If an entry points to a directory that we've already
reached, then some entry is bad and should be blown
away. It's easiest to blow away the current entry
plus since presumably the parent entry in the
reached directory points to another directory,
then it's far more likely that the current
entry is bogus (otherwise the parent should point
at it).
If an entry points to a non-existent of free inode,
blow the entry away.
Every time a good entry is encountered update the
link count for the inode that the entry points to.
After traversal, scan incore inode map for directories not
reached. Go to first one and try and find its root
by following .. entries. Once at root, run traversal
algorithm. When algorithm terminates, move subtree
root inode to the orphanage. Repeat as necessary
until all disconnected directories are attached.
Move all disconnected inodes to orphanage.
- Phase 7: reset reference counts if required.
Now traverse the on-disk inodes again, and make sure on-disk
reference counts are correct. Reset if necessary.
SKIP all unused inodes -- that also makes us
skip the orphanage inode which we think is
unused but is really used. However, the ref counts
on that should be right so that's ok.
---
multiple TB xfs_repair
modify above to work in a couple of AGs at a time. The bitmaps
should span only the current set of AGs.
The key it scan the inode bmaps and keep a list of inodes
that span multiple AG sets and keep the list in a data structure
that's keyed off AG set # as well as inode # and also has a bit
to indicate whether or not the inode will be cleared.
Then in each AG set, when doing duplicate extent processing,
you have to process all multi-AG-set inodes that claim blocks in
the current AG set. If there's a conflict, you mark clear the
inode in the current AG and you mark the multi-AG inode as
"to be cleared".
After going through all AGs, you can clear the to-be-cleared
multi-AG-set inodes and pull them off the list.
When building up the AG freespace trees, you walk the bmaps
of all multi-AG-set inodes that are in the AG-set and include
blocks claimed in the AG by the inode as used.
This probably involves adding a phase 3-0 which would have to
check all the inodes to see which ones are multi-AG-set inodes
and set up the multi-AG-set inode data structure. Plus the
process_dinode routines may have to be altered just a bit
to do the right thing if running in tera-byte mode (call
out to routines that check the multi-AG-set inodes when
appropriate).
To make things go faster, phase 3-0 could probably run
in parallel. It should be possible to run phases 2-5
in parallel as well once the appropriate synchronization
is added to the incore routines and the static directory
leaf block bitmap is changed to be on the stack.
Phase 7 probably can be in parallel as well.
By in parallel, I mean that assuming that an AG-set
contains 4 AGs, you could run 4 threads, 1 per AG
in parallel to process the AG set.
I don't see how phase 6 can be run in parallel though.
And running Phase 8 in parallel is just silly.