• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / ZODB / 8679957491

30 Mar 2024 11:42AM CUT coverage: 83.744%. Remained the same
8679957491

push

github

dataflake
- vb [ci skip]

2878 of 4051 branches covered (71.04%)

13348 of 15939 relevant lines covered (83.74%)

0.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/ZODB/scripts/fsrefs.py
1
#!/usr/bin/env python
2
##############################################################################
3
#
4
# Copyright (c) 2002 Zope Foundation and Contributors.
5
# All Rights Reserved.
6
#
7
# This software is subject to the provisions of the Zope Public License,
8
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
9
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
10
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
11
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
12
# FOR A PARTICULAR PURPOSE
13
#
14
##############################################################################
15

16
"""Check FileStorage for dangling references.
×
17

18
usage: fsrefs.py [-v] data.fs
19

20
fsrefs.py checks object sanity by trying to load the current revision of
21
every object O in the database, and also verifies that every object
22
directly reachable from each such O exists in the database.
23

24
It's hard to explain exactly what it does because it relies on undocumented
25
features in Python's cPickle module:  many of the crucial steps of loading
26
an object are taken, but application objects aren't actually created.  This
27
saves a lot of time, and allows fsrefs to be run even if the code
28
implementing the object classes isn't available.
29

30
A read-only connection to the specified FileStorage is made, but it is not
31
recommended to run fsrefs against a live FileStorage.  Because a live
32
FileStorage is mutating while fsrefs runs, it's not possible for fsrefs to
33
get a wholly consistent view of the database across the entire time fsrefs
34
is running; spurious error messages may result.
35

36
fsrefs doesn't normally produce any output.  If an object fails to load, the
37
oid of the object is given in a message saying so, and if -v was specified
38
then the traceback corresponding to the load failure is also displayed
39
(this is the only effect of the -v flag).
40

41
Three other kinds of errors are also detected, when an object O loads OK,
42
and directly refers to a persistent object P but there's a problem with P:
43

44
 - If P doesn't exist in the database, a message saying so is displayed.
45
   The unsatisifiable reference to P is often called a "dangling
46
   reference"; P is called "missing" in the error output.
47

48
 - If the current state of the database is such that P's creation has
49
   been undone, then P can't be loaded either.  This is also a kind of
50
   dangling reference, but is identified as "object creation was undone".
51

52
 - If P can't be loaded (but does exist in the database), a message saying
53
   that O refers to an object that can't be loaded is displayed.
54

55
fsrefs also (indirectly) checks that the .index file is sane, because
56
fsrefs uses the index to get its idea of what constitutes "all the objects
57
in the database".
58

59
Note these limitations:  because fsrefs only looks at the current revision
60
of objects, it does not attempt to load objects in versions, or non-current
61
revisions of objects; therefore fsrefs cannot find problems in versions or
62
in non-current revisions.
63
"""
64

65
import traceback
×
66

67
from BTrees.QQBTree import QQBTree
×
68

69
from ZODB.FileStorage import FileStorage
×
70
from ZODB.POSException import POSKeyError
×
71
from ZODB.serialize import get_refs
×
72
from ZODB.TimeStamp import TimeStamp
×
73
from ZODB.utils import get_pickle_metadata
×
74
from ZODB.utils import load_current
×
75
from ZODB.utils import oid_repr
×
76
from ZODB.utils import p64
×
77
from ZODB.utils import u64
×
78

79

80
# There's a problem with oid.  'data' is its pickle, and 'serial' its
81
# serial number.  'missing' is a list of (oid, class, reason) triples,
82
# explaining what the problem(s) is(are).
83

84

85
def report(oid, data, serial, missing):
×
86
    from_mod, from_class = get_pickle_metadata(data)
×
87
    if len(missing) > 1:
×
88
        plural = "s"
×
89
    else:
90
        plural = ""
×
91
    ts = TimeStamp(serial)
×
92
    print("oid {} {}.{}".format(hex(u64(oid)), from_mod, from_class))
×
93
    print("last updated: {}, tid={}".format(ts, hex(u64(serial))))
×
94
    print("refers to invalid object%s:" % plural)
×
95
    for oid, info, reason in missing:
×
96
        if isinstance(info, tuple):
×
97
            description = "%s.%s" % info
×
98
        else:
99
            description = str(info)
×
100
        print("\toid {} {}: {!r}".format(oid_repr(oid), reason, description))
×
101
    print()
×
102

103

104
def main(path=None):
×
105
    verbose = 0
×
106
    if path is None:
×
107
        import getopt
×
108
        import sys
×
109

110
        opts, args = getopt.getopt(sys.argv[1:], "v")
×
111
        for k, v in opts:
×
112
            if k == "-v":
×
113
                verbose += 1
×
114

115
        path, = args
×
116

117
    fs = FileStorage(path, read_only=1)
×
118

119
    # Set of oids in the index that failed to load due to POSKeyError.
120
    # This is what happens if undo is applied to the transaction creating
121
    # the object (the oid is still in the index, but its current data
122
    # record has a backpointer of 0, and POSKeyError is raised then
123
    # because of that backpointer).
124
    undone = {}
×
125

126
    # Set of oids that were present in the index but failed to load.
127
    # This does not include oids in undone.
128
    noload = {}
×
129

130
    # build {pos -> oid} index that is reverse to {oid -> pos} fs._index
131
    # we'll need this to iterate objects in order of ascending file position to
132
    # optimize disk IO.
133
    pos2oid = QQBTree()  # pos -> u64(oid)
×
134
    for oid, pos in fs._index.iteritems():
×
135
        pos2oid[pos] = u64(oid)
×
136

137
    # pass 1: load all objects listed in the index and remember those objects
138
    # that are deleted or load with an error. Iterate objects in order of
139
    # ascending file position to optimize disk IO.
140
    for oid64 in pos2oid.itervalues():
×
141
        oid = p64(oid64)
×
142
        try:
×
143
            data, serial = load_current(fs, oid)
×
144
        except (KeyboardInterrupt, SystemExit):
×
145
            raise
×
146
        except POSKeyError:
×
147
            undone[oid] = 1
×
148
        except:  # noqa: E722 do not use bare 'except'
×
149
            if verbose:
×
150
                traceback.print_exc()
×
151
            noload[oid] = 1
×
152

153
    # pass 2: go through all objects again and verify that their references do
154
    # not point to problematic object set. Iterate objects in order of
155
    # ascending file position to optimize disk IO.
156
    inactive = noload.copy()
×
157
    inactive.update(undone)
×
158
    for oid64 in pos2oid.itervalues():
×
159
        oid = p64(oid64)
×
160
        if oid in inactive:
×
161
            continue
×
162
        data, serial = load_current(fs, oid)
×
163
        refs = get_refs(data)
×
164
        missing = []  # contains 3-tuples of oid, klass-metadata, reason
×
165
        for ref, klass in refs:
×
166
            if klass is None:
×
167
                klass = '<unknown>'
×
168
            if ref not in fs._index:
×
169
                missing.append((ref, klass, "missing"))
×
170
            if ref in noload:
×
171
                missing.append((ref, klass, "failed to load"))
×
172
            if ref in undone:
×
173
                missing.append((ref, klass, "object creation was undone"))
×
174
        if missing:
×
175
            report(oid, data, serial, missing)
×
176

177

178
if __name__ == "__main__":
×
179
    main()
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc