]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/scripts/debug.py
dump-share: emit SDMF information too
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / scripts / debug.py
1
2 # do not import any allmydata modules at this level. Do that from inside
3 # individual functions instead.
4 import os, sys, struct, time
5 from twisted.python import usage
6 from allmydata.scripts.common import BasedirMixin
7
8 class DumpOptions(usage.Options):
9     optParameters = [
10         ["filename", "f", None, "which file to dump"],
11         ]
12
13     def parseArgs(self, filename=None):
14         if filename:
15             self['filename'] = filename
16
17     def postOptions(self):
18         if not self['filename']:
19             raise usage.UsageError("<filename> parameter is required")
20
21 class DumpRootDirnodeOptions(BasedirMixin, usage.Options):
22     optParameters = [
23         ["basedir", "C", None, "the vdrive-server's base directory"],
24         ]
25
26 class DumpDirnodeOptions(BasedirMixin, usage.Options):
27     optParameters = [
28         ["uri", "u", None, "the URI of the dirnode to dump."],
29         ["basedir", "C", None, "which directory to create the introducer in"],
30         ]
31     optFlags = [
32         ["verbose", "v", "be extra noisy (show encrypted data)"],
33         ]
34     def parseArgs(self, *args):
35         if len(args) == 1:
36             self['uri'] = args[-1]
37             args = args[:-1]
38         BasedirMixin.parseArgs(self, *args)
39
40     def postOptions(self):
41         BasedirMixin.postOptions(self)
42         if not self['uri']:
43             raise usage.UsageError("<uri> parameter is required")
44
45 def dump_share(config, out=sys.stdout, err=sys.stderr):
46     from allmydata import uri, storage
47
48     # check the version, to see if we have a mutable or immutable share
49     f = open(config['filename'], "rb")
50     prefix = f.read(32)
51     f.close()
52     if prefix == storage.MutableShareFile.MAGIC:
53         return dump_mutable_share(config, out, err)
54     # otherwise assume it's immutable
55     f = storage.ShareFile(config['filename'])
56     # use a ReadBucketProxy to parse the bucket and find the uri extension
57     bp = storage.ReadBucketProxy(None)
58     offsets = bp._parse_offsets(f.read_share_data(0, 0x24))
59     seek = offsets['uri_extension']
60     length = struct.unpack(">L", f.read_share_data(seek, 4))[0]
61     seek += 4
62     data = f.read_share_data(seek, length)
63
64     unpacked = uri.unpack_extension_readable(data)
65     keys1 = ("size", "num_segments", "segment_size",
66              "needed_shares", "total_shares")
67     keys2 = ("codec_name", "codec_params", "tail_codec_params")
68     keys3 = ("plaintext_hash", "plaintext_root_hash",
69              "crypttext_hash", "crypttext_root_hash",
70              "share_root_hash")
71     display_keys = {"size": "file_size"}
72     for k in keys1:
73         if k in unpacked:
74             dk = display_keys.get(k, k)
75             print >>out, "%19s: %s" % (dk, unpacked[k])
76     print >>out
77     for k in keys2:
78         if k in unpacked:
79             dk = display_keys.get(k, k)
80             print >>out, "%19s: %s" % (dk, unpacked[k])
81     print >>out
82     for k in keys3:
83         if k in unpacked:
84             dk = display_keys.get(k, k)
85             print >>out, "%19s: %s" % (dk, unpacked[k])
86
87     leftover = set(unpacked.keys()) - set(keys1 + keys2 + keys3)
88     if leftover:
89         print >>out
90         print >>out, "LEFTOVER:"
91         for k in sorted(leftover):
92             print >>out, "%s: %s" % (k, unpacked[k])
93
94     sizes = {}
95     sizes['data'] = bp._data_size
96     sizes['validation'] = (offsets['uri_extension'] -
97                            offsets['plaintext_hash_tree'])
98     sizes['uri-extension'] = len(data)
99     print >>out
100     print >>out, "Size of data within the share:"
101     for k in sorted(sizes):
102         print >>out, "%19s: %s" % (k, sizes[k])
103
104     # display lease information too
105     leases = list(f.iter_leases())
106     if leases:
107         for i,lease in enumerate(leases):
108             (owner_num, renew_secret, cancel_secret, expiration_time) = lease
109             when = format_expiration_time(expiration_time)
110             print >>out, "Lease #%d: owner=%d, expire in %s" % (i, owner_num,
111                                                                 when)
112     else:
113         print >>out, "No leases."
114
115     print >>out
116     return 0
117
118 def format_expiration_time(expiration_time):
119     now = time.time()
120     remains = expiration_time - now
121     when = "%ds" % remains
122     if remains > 24*3600:
123         when += " (%d days)" % (remains / (24*3600))
124     elif remains > 3600:
125         when += " (%d hours)" % (remains / 3600)
126     return when
127
128
129 def dump_mutable_share(config, out, err):
130     from allmydata import storage
131     from allmydata.util import idlib
132     m = storage.MutableShareFile(config['filename'])
133     f = open(config['filename'], "rb")
134     WE, nodeid = m._read_write_enabler_and_nodeid(f)
135     num_extra_leases = m._read_num_extra_leases(f)
136     data_length = m._read_data_length(f)
137     extra_lease_offset = m._read_extra_lease_offset(f)
138     container_size = extra_lease_offset - m.DATA_OFFSET
139     leases = list(m._enumerate_leases(f))
140
141     share_type = "unknown"
142     f.seek(m.DATA_OFFSET)
143     if f.read(1) == "\x00":
144         # this slot contains an SMDF share
145         share_type = "SDMF"
146     f.close()
147
148     print >>out
149     print >>out, "Mutable slot found:"
150     print >>out, " share_type: %s" % share_type
151     print >>out, " write_enabler: %s" % idlib.b2a(WE)
152     print >>out, " WE for nodeid: %s" % idlib.nodeid_b2a(nodeid)
153     print >>out, " num_extra_leases: %d" % num_extra_leases
154     print >>out, " container_size: %d" % container_size
155     print >>out, " data_length: %d" % data_length
156     if leases:
157         for (leasenum, (oid,et,rs,cs,anid)) in leases:
158             print >>out
159             print >>out, " Lease #%d:" % leasenum
160             print >>out, "  ownerid: %d" % oid
161             when = format_expiration_time(et)
162             print >>out, "  expires in %s" % when
163             print >>out, "  renew_secret: %s" % idlib.b2a(rs)
164             print >>out, "  cancel_secret: %s" % idlib.b2a(cs)
165             print >>out, "  secrets are for nodeid: %s" % idlib.nodeid_b2a(anid)
166     else:
167         print >>out, "No leases."
168     print >>out
169
170     if share_type == "SDMF":
171         dump_SDMF_share(m.DATA_OFFSET, data_length, config, out, err)
172
173     return 0
174
175 def dump_SDMF_share(offset, length, config, out, err):
176     from allmydata import mutable
177     from allmydata.util import idlib
178
179     f = open(config['filename'], "rb")
180     f.seek(offset)
181     data = f.read(min(length, 2000))
182     f.close()
183
184     pieces = mutable.unpack_share(data)
185
186     (seqnum, root_hash, IV, k, N, segsize, datalen,
187      pubkey, signature, share_hash_chain, block_hash_tree,
188      share_data, enc_privkey) = pieces
189
190     print >>out, " SDMF contents:"
191     print >>out, "  seqnum: %d" % seqnum
192     print >>out, "  root_hash: %s" % idlib.b2a(root_hash)
193     print >>out, "  IV: %s" % idlib.b2a(IV)
194     print >>out, "  required_shares: %d" % k
195     print >>out, "  total_shares: %d" % N
196     print >>out, "  segsize: %d" % segsize
197     print >>out, "  datalen: %d" % datalen
198     share_hash_ids = ",".join([str(hid) for (hid,hash) in share_hash_chain])
199     print >>out, "  share_hash_chain: %s" % share_hash_ids
200     print >>out, "  block_hash_tree: %d nodes" % len(block_hash_tree)
201
202     print >>out
203
204
205 def dump_root_dirnode(config, out=sys.stdout, err=sys.stderr):
206     from allmydata import uri
207
208     basedir = config['basedirs'][0]
209     root_dirnode_file = os.path.join(basedir, "vdrive", "root")
210     try:
211         f = open(root_dirnode_file, "rb")
212         key = f.read()
213         rooturi = uri.DirnodeURI("fakeFURL", key)
214         print >>out, rooturi.to_string()
215         return 0
216     except EnvironmentError:
217         print >>out,  "unable to read root dirnode file from %s" % \
218               root_dirnode_file
219         return 1
220
221 def dump_directory_node(config, out=sys.stdout, err=sys.stderr):
222     from allmydata import dirnode
223     from allmydata.util import hashutil, idlib
224     from allmydata.interfaces import IDirnodeURI
225     basedir = config['basedirs'][0]
226     dir_uri = IDirnodeURI(config['uri'])
227     verbose = config['verbose']
228
229     if dir_uri.is_readonly():
230         wk, we, rk, index = \
231             hashutil.generate_dirnode_keys_from_readkey(dir_uri.readkey)
232     else:
233         wk, we, rk, index = \
234             hashutil.generate_dirnode_keys_from_writekey(dir_uri.writekey)
235
236     filename = os.path.join(basedir, "vdrive", idlib.b2a(index))
237
238     print >>out
239     print >>out, "dirnode uri: %s" % dir_uri.to_string()
240     print >>out, "filename : %s" % filename
241     print >>out, "index        : %s" % idlib.b2a(index)
242     if wk:
243         print >>out, "writekey     : %s" % idlib.b2a(wk)
244         print >>out, "write_enabler: %s" % idlib.b2a(we)
245     else:
246         print >>out, "writekey     : None"
247         print >>out, "write_enabler: None"
248     print >>out, "readkey      : %s" % idlib.b2a(rk)
249
250     print >>out
251
252     vds = dirnode.VirtualDriveServer(os.path.join(basedir, "vdrive"), False)
253     data = vds._read_from_file(index)
254     if we:
255         if we != data[0]:
256             print >>out, "ERROR: write_enabler does not match"
257
258     for (H_key, E_key, E_write, E_read) in data[1]:
259         if verbose:
260             print >>out, " H_key %s" % idlib.b2a(H_key)
261             print >>out, " E_key %s" % idlib.b2a(E_key)
262             print >>out, " E_write %s" % idlib.b2a(E_write)
263             print >>out, " E_read %s" % idlib.b2a(E_read)
264         key = dirnode.decrypt(rk, E_key)
265         print >>out, " key %s" % key
266         if hashutil.dir_name_hash(rk, key) != H_key:
267             print >>out, "  ERROR: H_key does not match"
268         if wk and E_write:
269             if len(E_write) < 14:
270                 print >>out, "  ERROR: write data is short:", idlib.b2a(E_write)
271             write = dirnode.decrypt(wk, E_write)
272             print >>out, "   write: %s" % write
273         read = dirnode.decrypt(rk, E_read)
274         print >>out, "   read: %s" % read
275         print >>out
276
277     return 0
278
279
280 subCommands = [
281     ["dump-share", None, DumpOptions,
282      "Unpack and display the contents of a share (uri_extension and leases)."],
283     ["dump-root-dirnode", None, DumpRootDirnodeOptions,
284      "Compute most of the URI for the vdrive server's root dirnode."],
285     ["dump-dirnode", None, DumpDirnodeOptions,
286      "Unpack and display the contents of a vdrive DirectoryNode."],
287     ]
288
289 dispatch = {
290     "dump-share": dump_share,
291     "dump-root-dirnode": dump_root_dirnode,
292     "dump-dirnode": dump_directory_node,
293     }