scripts: Ignore errors with compat-disabled gstate

The gbmap introduces quite a bit of complexity with how it interacts
with config: block_count => gbmap weight, and wcompat => gbmap enabled.
On one hand this means fewer sources of truth, on the other hand it
makes the gbmap logic cross subsystems and a bit messy.

To avoid trying to parse a bunch of disabled/garbage gstate, this adds
wcompat/rcompat checks to our Gstate class, exposed via __bool__.

This also means we actually need to parse wcompat/rcompat/ocompat flags,
but that wasn't to difficult (though currently only supports 32-bits).

---

I added conditional repr logic for the grm and gbmap, but didn't bother
with the gcksum. The gcksum is used too many other places in these
scripts to expect a nice rendering when disabled.
This commit is contained in:
Christopher Haster
2025-10-11 14:19:20 -05:00
parent b5a94f3397
commit 67d3c6ea69
3 changed files with 234 additions and 15 deletions

View File

@ -80,6 +80,24 @@ TAG_NOTE = 0x3100 ## 0x3100 v-11 ---1 ---- ----
TAG_ECKSUM = 0x3200 ## 0x3200 v-11 --1- ---- ----
TAG_GCKSUMDELTA = 0x3300 ## 0x3300 v-11 --11 ---- ----
RCOMPAT_NONSTANDARD = 0x00000001 # Non-standard filesystem format
RCOMPAT_WRONLY = 0x00000002 # Reading is disallowed
RCOMPAT_BMOSS = 0x00000010 # Files may use inlined data
RCOMPAT_BSPROUT = 0x00000020 # Files may use block pointers
RCOMPAT_BSHRUB = 0x00000040 # Files may use inlined btrees
RCOMPAT_BTREE = 0x00000080 # Files may use btrees
RCOMPAT_MMOSS = 0x00000100 # May use an inlined mdir
RCOMPAT_MSPROUT = 0x00000200 # May use an mdir pointer
RCOMPAT_MSHRUB = 0x00000400 # May use an inlined mtree
RCOMPAT_MTREE = 0x00000800 # May use an mdir btree
RCOMPAT_GRM = 0x00001000 # Global-remove in use
WCOMPAT_NONSTANDARD = 0x00000001 # Non-standard filesystem format
WCOMPAT_RDONLY = 0x00000002 # Writing is disallowed
WCOMPAT_DIR = 0x00000010 # Directory file types in use
WCOMPAT_GCKSUM = 0x00001000 # Global-checksum in use
WCOMPAT_GBMAP = 0x00002000 # Global on-disk block-map in use
# assign chars/colors to specific filesystem objects
CHARS = {
@ -2544,6 +2562,13 @@ class Config:
class Rcompat(Config):
tag = TAG_RCOMPAT
def __init__(self, mroot, tag, rattr):
super().__init__(mroot, tag, rattr)
self.flags = fromle32(self.data)
def __int__(self):
return self.flags
def repr(self):
return 'rcompat 0x%s' % (
''.join('%02x' % f for f in reversed(self.data)))
@ -2551,6 +2576,13 @@ class Config:
class Wcompat(Config):
tag = TAG_WCOMPAT
def __init__(self, mroot, tag, rattr):
super().__init__(mroot, tag, rattr)
self.flags = fromle32(self.data)
def __int__(self):
return self.flags
def repr(self):
return 'wcompat 0x%s' % (
''.join('%02x' % f for f in reversed(self.data)))
@ -2558,6 +2590,13 @@ class Config:
class Ocompat(Config):
tag = TAG_OCOMPAT
def __init__(self, mroot, tag, rattr):
super().__init__(mroot, tag, rattr)
self.flags = fromle32(self.data)
def __int__(self):
return self.flags
def repr(self):
return 'ocompat 0x%s' % (
''.join('%02x' % f for f in reversed(self.data)))
@ -2707,6 +2746,9 @@ class Gstate:
class Gstate:
tag = None
mask = None
rcompat = None
wcompat = None
ocompat = None
def __init__(self, mtree, config, tag, gdeltas):
# replace tag with what we find
@ -2723,11 +2765,31 @@ class Gstate:
fillvalue=0))
self.data = data
# check compat flags while we can access config
if self.rcompat is not None:
self.rcompat = self.rcompat & (
int(config.rcompat) if config.rcompat is not None
else 0)
if self.wcompat is not None:
self.wcompat = self.wcompat & (
int(config.wcompat) if config.wcompat is not None
else 0)
if self.ocompat is not None:
self.ocompat = self.ocompat & (
int(config.ocompat) if config.ocompat is not None
else 0)
@property
def blocks(self):
return tuple(it.chain.from_iterable(
gdelta.blocks for _, gdelta in self.gdeltas))
# true unless compat flags are missing
def __bool__(self):
return (self.rcompat != 0
and self.wcompat != 0
and self.ocompat != 0)
@property
def size(self):
return len(self.data)
@ -2762,6 +2824,7 @@ class Gstate:
# the global-checksum, cubed
class Gcksum(Gstate):
tag = TAG_GCKSUMDELTA
wcompat = WCOMPAT_GCKSUM
def __init__(self, mtree, config, tag, gdeltas):
super().__init__(mtree, config, tag, gdeltas)
@ -2776,6 +2839,7 @@ class Gstate:
# any global-removes
class Grm(Gstate):
tag = TAG_GRMDELTA
rcompat = RCOMPAT_GRM
def __init__(self, mtree, config, tag, gdeltas):
super().__init__(mtree, config, tag, gdeltas)
@ -2794,11 +2858,16 @@ class Gstate:
self.queue = queue
def repr(self):
return 'grm [%s]' % ', '.join(mid.repr() for mid in self.queue)
if self:
return 'grm [%s]' % ', '.join(
mid.repr() for mid in self.queue)
else:
return 'grm (unused)'
# the global block map
class Gbmap(Gstate):
tag = TAG_GBMAPDELTA
wcompat = WCOMPAT_GBMAP
def __init__(self, mtree, config, tag, gdeltas):
super().__init__(mtree, config, tag, gdeltas)
@ -2813,9 +2882,12 @@ class Gstate:
cksum)
def repr(self):
return 'gbmap %s 0x%x %d' % (
self.btree.addr(),
self.window, self.known)
if self:
return 'gbmap %s 0x%x %d' % (
self.btree.addr(),
self.window, self.known)
else:
return 'gbmap (unused)'
# keep track of known gstate
_known = [g for g in Gstate.__subclasses__() if g.tag is not None]
@ -3325,7 +3397,7 @@ class Lfs3:
# traverse any gstate
if not mtree_only and gstate:
for gstate_ in self.gstate:
if getattr(gstate_, 'btree', None) is None:
if not gstate_ or getattr(gstate_, 'btree', None) is None:
continue
for r in gstate_.btree.traverse(

View File

@ -78,6 +78,24 @@ TAG_NOTE = 0x3100 ## 0x3100 v-11 ---1 ---- ----
TAG_ECKSUM = 0x3200 ## 0x3200 v-11 --1- ---- ----
TAG_GCKSUMDELTA = 0x3300 ## 0x3300 v-11 --11 ---- ----
RCOMPAT_NONSTANDARD = 0x00000001 # Non-standard filesystem format
RCOMPAT_WRONLY = 0x00000002 # Reading is disallowed
RCOMPAT_BMOSS = 0x00000010 # Files may use inlined data
RCOMPAT_BSPROUT = 0x00000020 # Files may use block pointers
RCOMPAT_BSHRUB = 0x00000040 # Files may use inlined btrees
RCOMPAT_BTREE = 0x00000080 # Files may use btrees
RCOMPAT_MMOSS = 0x00000100 # May use an inlined mdir
RCOMPAT_MSPROUT = 0x00000200 # May use an mdir pointer
RCOMPAT_MSHRUB = 0x00000400 # May use an inlined mtree
RCOMPAT_MTREE = 0x00000800 # May use an mdir btree
RCOMPAT_GRM = 0x00001000 # Global-remove in use
WCOMPAT_NONSTANDARD = 0x00000001 # Non-standard filesystem format
WCOMPAT_RDONLY = 0x00000002 # Writing is disallowed
WCOMPAT_DIR = 0x00000010 # Directory file types in use
WCOMPAT_GCKSUM = 0x00001000 # Global-checksum in use
WCOMPAT_GBMAP = 0x00002000 # Global on-disk block-map in use
# assign colors to specific filesystem objects
@ -2574,6 +2592,13 @@ class Config:
class Rcompat(Config):
tag = TAG_RCOMPAT
def __init__(self, mroot, tag, rattr):
super().__init__(mroot, tag, rattr)
self.flags = fromle32(self.data)
def __int__(self):
return self.flags
def repr(self):
return 'rcompat 0x%s' % (
''.join('%02x' % f for f in reversed(self.data)))
@ -2581,6 +2606,13 @@ class Config:
class Wcompat(Config):
tag = TAG_WCOMPAT
def __init__(self, mroot, tag, rattr):
super().__init__(mroot, tag, rattr)
self.flags = fromle32(self.data)
def __int__(self):
return self.flags
def repr(self):
return 'wcompat 0x%s' % (
''.join('%02x' % f for f in reversed(self.data)))
@ -2588,6 +2620,13 @@ class Config:
class Ocompat(Config):
tag = TAG_OCOMPAT
def __init__(self, mroot, tag, rattr):
super().__init__(mroot, tag, rattr)
self.flags = fromle32(self.data)
def __int__(self):
return self.flags
def repr(self):
return 'ocompat 0x%s' % (
''.join('%02x' % f for f in reversed(self.data)))
@ -2737,6 +2776,9 @@ class Gstate:
class Gstate:
tag = None
mask = None
rcompat = None
wcompat = None
ocompat = None
def __init__(self, mtree, config, tag, gdeltas):
# replace tag with what we find
@ -2753,11 +2795,31 @@ class Gstate:
fillvalue=0))
self.data = data
# check compat flags while we can access config
if self.rcompat is not None:
self.rcompat = self.rcompat & (
int(config.rcompat) if config.rcompat is not None
else 0)
if self.wcompat is not None:
self.wcompat = self.wcompat & (
int(config.wcompat) if config.wcompat is not None
else 0)
if self.ocompat is not None:
self.ocompat = self.ocompat & (
int(config.ocompat) if config.ocompat is not None
else 0)
@property
def blocks(self):
return tuple(it.chain.from_iterable(
gdelta.blocks for _, gdelta in self.gdeltas))
# true unless compat flags are missing
def __bool__(self):
return (self.rcompat != 0
and self.wcompat != 0
and self.ocompat != 0)
@property
def size(self):
return len(self.data)
@ -2792,6 +2854,7 @@ class Gstate:
# the global-checksum, cubed
class Gcksum(Gstate):
tag = TAG_GCKSUMDELTA
wcompat = WCOMPAT_GCKSUM
def __init__(self, mtree, config, tag, gdeltas):
super().__init__(mtree, config, tag, gdeltas)
@ -2806,6 +2869,7 @@ class Gstate:
# any global-removes
class Grm(Gstate):
tag = TAG_GRMDELTA
rcompat = RCOMPAT_GRM
def __init__(self, mtree, config, tag, gdeltas):
super().__init__(mtree, config, tag, gdeltas)
@ -2824,11 +2888,16 @@ class Gstate:
self.queue = queue
def repr(self):
return 'grm [%s]' % ', '.join(mid.repr() for mid in self.queue)
if self:
return 'grm [%s]' % ', '.join(
mid.repr() for mid in self.queue)
else:
return 'grm (unused)'
# the global block map
class Gbmap(Gstate):
tag = TAG_GBMAPDELTA
wcompat = WCOMPAT_GBMAP
def __init__(self, mtree, config, tag, gdeltas):
super().__init__(mtree, config, tag, gdeltas)
@ -2843,9 +2912,12 @@ class Gstate:
cksum)
def repr(self):
return 'gbmap %s 0x%x %d' % (
self.btree.addr(),
self.window, self.known)
if self:
return 'gbmap %s 0x%x %d' % (
self.btree.addr(),
self.window, self.known)
else:
return 'gbmap (unused)'
# keep track of known gstate
_known = [g for g in Gstate.__subclasses__() if g.tag is not None]
@ -3355,7 +3427,7 @@ class Lfs3:
# traverse any gstate
if not mtree_only and gstate:
for gstate_ in self.gstate:
if getattr(gstate_, 'btree', None) is None:
if not gstate_ or getattr(gstate_, 'btree', None) is None:
continue
for r in gstate_.btree.traverse(

View File

@ -70,6 +70,24 @@ TAG_NOTE = 0x3100 ## 0x3100 v-11 ---1 ---- ----
TAG_ECKSUM = 0x3200 ## 0x3200 v-11 --1- ---- ----
TAG_GCKSUMDELTA = 0x3300 ## 0x3300 v-11 --11 ---- ----
RCOMPAT_NONSTANDARD = 0x00000001 # Non-standard filesystem format
RCOMPAT_WRONLY = 0x00000002 # Reading is disallowed
RCOMPAT_BMOSS = 0x00000010 # Files may use inlined data
RCOMPAT_BSPROUT = 0x00000020 # Files may use block pointers
RCOMPAT_BSHRUB = 0x00000040 # Files may use inlined btrees
RCOMPAT_BTREE = 0x00000080 # Files may use btrees
RCOMPAT_MMOSS = 0x00000100 # May use an inlined mdir
RCOMPAT_MSPROUT = 0x00000200 # May use an mdir pointer
RCOMPAT_MSHRUB = 0x00000400 # May use an inlined mtree
RCOMPAT_MTREE = 0x00000800 # May use an mdir btree
RCOMPAT_GRM = 0x00001000 # Global-remove in use
WCOMPAT_NONSTANDARD = 0x00000001 # Non-standard filesystem format
WCOMPAT_RDONLY = 0x00000002 # Writing is disallowed
WCOMPAT_DIR = 0x00000010 # Directory file types in use
WCOMPAT_GCKSUM = 0x00001000 # Global-checksum in use
WCOMPAT_GBMAP = 0x00002000 # Global on-disk block-map in use
# some ways of block geometry representations
# 512 -> 512
@ -2469,6 +2487,13 @@ class Config:
class Rcompat(Config):
tag = TAG_RCOMPAT
def __init__(self, mroot, tag, rattr):
super().__init__(mroot, tag, rattr)
self.flags = fromle32(self.data)
def __int__(self):
return self.flags
def repr(self):
return 'rcompat 0x%s' % (
''.join('%02x' % f for f in reversed(self.data)))
@ -2476,6 +2501,13 @@ class Config:
class Wcompat(Config):
tag = TAG_WCOMPAT
def __init__(self, mroot, tag, rattr):
super().__init__(mroot, tag, rattr)
self.flags = fromle32(self.data)
def __int__(self):
return self.flags
def repr(self):
return 'wcompat 0x%s' % (
''.join('%02x' % f for f in reversed(self.data)))
@ -2483,6 +2515,13 @@ class Config:
class Ocompat(Config):
tag = TAG_OCOMPAT
def __init__(self, mroot, tag, rattr):
super().__init__(mroot, tag, rattr)
self.flags = fromle32(self.data)
def __int__(self):
return self.flags
def repr(self):
return 'ocompat 0x%s' % (
''.join('%02x' % f for f in reversed(self.data)))
@ -2632,6 +2671,9 @@ class Gstate:
class Gstate:
tag = None
mask = None
rcompat = None
wcompat = None
ocompat = None
def __init__(self, mtree, config, tag, gdeltas):
# replace tag with what we find
@ -2648,11 +2690,31 @@ class Gstate:
fillvalue=0))
self.data = data
# check compat flags while we can access config
if self.rcompat is not None:
self.rcompat = self.rcompat & (
int(config.rcompat) if config.rcompat is not None
else 0)
if self.wcompat is not None:
self.wcompat = self.wcompat & (
int(config.wcompat) if config.wcompat is not None
else 0)
if self.ocompat is not None:
self.ocompat = self.ocompat & (
int(config.ocompat) if config.ocompat is not None
else 0)
@property
def blocks(self):
return tuple(it.chain.from_iterable(
gdelta.blocks for _, gdelta in self.gdeltas))
# true unless compat flags are missing
def __bool__(self):
return (self.rcompat != 0
and self.wcompat != 0
and self.ocompat != 0)
@property
def size(self):
return len(self.data)
@ -2687,6 +2749,7 @@ class Gstate:
# the global-checksum, cubed
class Gcksum(Gstate):
tag = TAG_GCKSUMDELTA
wcompat = WCOMPAT_GCKSUM
def __init__(self, mtree, config, tag, gdeltas):
super().__init__(mtree, config, tag, gdeltas)
@ -2701,6 +2764,7 @@ class Gstate:
# any global-removes
class Grm(Gstate):
tag = TAG_GRMDELTA
rcompat = RCOMPAT_GRM
def __init__(self, mtree, config, tag, gdeltas):
super().__init__(mtree, config, tag, gdeltas)
@ -2719,11 +2783,16 @@ class Gstate:
self.queue = queue
def repr(self):
return 'grm [%s]' % ', '.join(mid.repr() for mid in self.queue)
if self:
return 'grm [%s]' % ', '.join(
mid.repr() for mid in self.queue)
else:
return 'grm (unused)'
# the global block map
class Gbmap(Gstate):
tag = TAG_GBMAPDELTA
wcompat = WCOMPAT_GBMAP
def __init__(self, mtree, config, tag, gdeltas):
super().__init__(mtree, config, tag, gdeltas)
@ -2738,9 +2807,12 @@ class Gstate:
cksum)
def repr(self):
return 'gbmap %s 0x%x %d' % (
self.btree.addr(),
self.window, self.known)
if self:
return 'gbmap %s 0x%x %d' % (
self.btree.addr(),
self.window, self.known)
else:
return 'gbmap (unused)'
# keep track of known gstate
_known = [g for g in Gstate.__subclasses__() if g.tag is not None]
@ -3250,7 +3322,7 @@ class Lfs3:
# traverse any gstate
if not mtree_only and gstate:
for gstate_ in self.gstate:
if getattr(gstate_, 'btree', None) is None:
if not gstate_ or getattr(gstate_, 'btree', None) is None:
continue
for r in gstate_.btree.traverse(
@ -4141,6 +4213,9 @@ def dbg_gstate(lfs, *,
# print gstate structures
def dbg_gstruct(gstate):
# not in use?
if not gstate:
return
# no tree?
if getattr(gstate, 'btree', None) is None:
return