2 # do not import any allmydata modules at this level. Do that from inside
3 # individual functions instead.
4 import struct, time, os, sys
5 from twisted.python import usage, failure
6 from twisted.internet import defer
7 from twisted.scripts import trial as twisted_trial
10 class DumpOptions(usage.Options):
11 def getSynopsis(self):
12 return "Usage: tahoe debug dump-share SHARE_FILENAME"
15 ["offsets", None, "Display a table of section offsets."],
16 ["leases-only", None, "Dump leases but not CHK contents."],
19 def getUsage(self, width=None):
20 t = usage.Options.getUsage(self, width)
22 Print lots of information about the given share, by parsing the share's
23 contents. This includes share type, lease information, encoding parameters,
24 hash-tree roots, public keys, and segment sizes. This command also emits a
25 verify-cap for the file that uses the share.
27 tahoe debug dump-share testgrid/node-3/storage/shares/4v/4vozh77tsrw7mdhnj7qvp5ky74/0
32 def parseArgs(self, filename):
33 from allmydata.util.encodingutil import argv_to_abspath
34 self['filename'] = argv_to_abspath(filename)
36 def dump_share(options):
37 from allmydata.storage.mutable import MutableShareFile
38 from allmydata.util.encodingutil import quote_output
42 # check the version, to see if we have a mutable or immutable share
43 print >>out, "share filename: %s" % quote_output(options['filename'])
45 f = open(options['filename'], "rb")
48 if prefix == MutableShareFile.MAGIC:
49 return dump_mutable_share(options)
50 # otherwise assume it's immutable
51 return dump_immutable_share(options)
53 def dump_immutable_share(options):
54 from allmydata.storage.immutable import ShareFile
57 f = ShareFile(options['filename'])
58 if not options["leases-only"]:
59 dump_immutable_chk_share(f, out, options)
60 dump_immutable_lease_info(f, out)
64 def dump_immutable_chk_share(f, out, options):
65 from allmydata import uri
66 from allmydata.util import base32
67 from allmydata.immutable.layout import ReadBucketProxy
68 from allmydata.util.encodingutil import quote_output, to_str
70 # use a ReadBucketProxy to parse the bucket and find the uri extension
71 bp = ReadBucketProxy(None, None, '')
72 offsets = bp._parse_offsets(f.read_share_data(0, 0x44))
73 print >>out, "%20s: %d" % ("version", bp._version)
74 seek = offsets['uri_extension']
75 length = struct.unpack(bp._fieldstruct,
76 f.read_share_data(seek, bp._fieldsize))[0]
78 UEB_data = f.read_share_data(seek, length)
80 unpacked = uri.unpack_extension_readable(UEB_data)
81 keys1 = ("size", "num_segments", "segment_size",
82 "needed_shares", "total_shares")
83 keys2 = ("codec_name", "codec_params", "tail_codec_params")
84 keys3 = ("plaintext_hash", "plaintext_root_hash",
85 "crypttext_hash", "crypttext_root_hash",
86 "share_root_hash", "UEB_hash")
87 display_keys = {"size": "file_size"}
90 dk = display_keys.get(k, k)
91 print >>out, "%20s: %s" % (dk, unpacked[k])
95 dk = display_keys.get(k, k)
96 print >>out, "%20s: %s" % (dk, unpacked[k])
100 dk = display_keys.get(k, k)
101 print >>out, "%20s: %s" % (dk, unpacked[k])
103 leftover = set(unpacked.keys()) - set(keys1 + keys2 + keys3)
106 print >>out, "LEFTOVER:"
107 for k in sorted(leftover):
108 print >>out, "%20s: %s" % (k, unpacked[k])
110 # the storage index isn't stored in the share itself, so we depend upon
111 # knowing the parent directory name to get it
112 pieces = options['filename'].split(os.sep)
114 piece = to_str(pieces[-2])
115 if base32.could_be_base32_encoded(piece):
116 storage_index = base32.a2b(piece)
117 uri_extension_hash = base32.a2b(unpacked["UEB_hash"])
118 u = uri.CHKFileVerifierURI(storage_index, uri_extension_hash,
119 unpacked["needed_shares"],
120 unpacked["total_shares"], unpacked["size"])
121 verify_cap = u.to_string()
122 print >>out, "%20s: %s" % ("verify-cap", quote_output(verify_cap, quotemarks=False))
125 sizes['data'] = (offsets['plaintext_hash_tree'] -
127 sizes['validation'] = (offsets['uri_extension'] -
128 offsets['plaintext_hash_tree'])
129 sizes['uri-extension'] = len(UEB_data)
131 print >>out, " Size of data within the share:"
132 for k in sorted(sizes):
133 print >>out, "%20s: %s" % (k, sizes[k])
135 if options['offsets']:
137 print >>out, " Section Offsets:"
138 print >>out, "%20s: %s" % ("share data", f._data_offset)
139 for k in ["data", "plaintext_hash_tree", "crypttext_hash_tree",
140 "block_hashes", "share_hashes", "uri_extension"]:
141 name = {"data": "block data"}.get(k,k)
142 offset = f._data_offset + offsets[k]
143 print >>out, " %20s: %s (0x%x)" % (name, offset, offset)
144 print >>out, "%20s: %s" % ("leases", f._lease_offset)
146 def dump_immutable_lease_info(f, out):
147 # display lease information too
149 leases = list(f.get_leases())
151 for i,lease in enumerate(leases):
152 when = format_expiration_time(lease.expiration_time)
153 print >>out, " Lease #%d: owner=%d, expire in %s" \
154 % (i, lease.owner_num, when)
156 print >>out, " No leases."
158 def format_expiration_time(expiration_time):
160 remains = expiration_time - now
161 when = "%ds" % remains
162 if remains > 24*3600:
163 when += " (%d days)" % (remains / (24*3600))
165 when += " (%d hours)" % (remains / 3600)
169 def dump_mutable_share(options):
170 from allmydata.storage.mutable import MutableShareFile
171 from allmydata.util import base32, idlib
173 m = MutableShareFile(options['filename'])
174 f = open(options['filename'], "rb")
175 WE, nodeid = m._read_write_enabler_and_nodeid(f)
176 num_extra_leases = m._read_num_extra_leases(f)
177 data_length = m._read_data_length(f)
178 extra_lease_offset = m._read_extra_lease_offset(f)
179 container_size = extra_lease_offset - m.DATA_OFFSET
180 leases = list(m._enumerate_leases(f))
182 share_type = "unknown"
183 f.seek(m.DATA_OFFSET)
185 if version == "\x00":
186 # this slot contains an SMDF share
188 elif version == "\x01":
193 print >>out, "Mutable slot found:"
194 print >>out, " share_type: %s" % share_type
195 print >>out, " write_enabler: %s" % base32.b2a(WE)
196 print >>out, " WE for nodeid: %s" % idlib.nodeid_b2a(nodeid)
197 print >>out, " num_extra_leases: %d" % num_extra_leases
198 print >>out, " container_size: %d" % container_size
199 print >>out, " data_length: %d" % data_length
201 for (leasenum, lease) in leases:
203 print >>out, " Lease #%d:" % leasenum
204 print >>out, " ownerid: %d" % lease.owner_num
205 when = format_expiration_time(lease.expiration_time)
206 print >>out, " expires in %s" % when
207 print >>out, " renew_secret: %s" % base32.b2a(lease.renew_secret)
208 print >>out, " cancel_secret: %s" % base32.b2a(lease.cancel_secret)
209 print >>out, " secrets are for nodeid: %s" % idlib.nodeid_b2a(lease.nodeid)
211 print >>out, "No leases."
214 if share_type == "SDMF":
215 dump_SDMF_share(m, data_length, options)
216 elif share_type == "MDMF":
217 dump_MDMF_share(m, data_length, options)
221 def dump_SDMF_share(m, length, options):
222 from allmydata.mutable.layout import unpack_share, unpack_header
223 from allmydata.mutable.common import NeedMoreDataError
224 from allmydata.util import base32, hashutil
225 from allmydata.uri import SSKVerifierURI
226 from allmydata.util.encodingutil import quote_output, to_str
228 offset = m.DATA_OFFSET
232 f = open(options['filename'], "rb")
234 data = f.read(min(length, 2000))
238 pieces = unpack_share(data)
239 except NeedMoreDataError, e:
240 # retry once with the larger size
241 size = e.needed_bytes
242 f = open(options['filename'], "rb")
244 data = f.read(min(length, size))
246 pieces = unpack_share(data)
248 (seqnum, root_hash, IV, k, N, segsize, datalen,
249 pubkey, signature, share_hash_chain, block_hash_tree,
250 share_data, enc_privkey) = pieces
251 (ig_version, ig_seqnum, ig_roothash, ig_IV, ig_k, ig_N, ig_segsize,
252 ig_datalen, offsets) = unpack_header(data)
254 print >>out, " SDMF contents:"
255 print >>out, " seqnum: %d" % seqnum
256 print >>out, " root_hash: %s" % base32.b2a(root_hash)
257 print >>out, " IV: %s" % base32.b2a(IV)
258 print >>out, " required_shares: %d" % k
259 print >>out, " total_shares: %d" % N
260 print >>out, " segsize: %d" % segsize
261 print >>out, " datalen: %d" % datalen
262 print >>out, " enc_privkey: %d bytes" % len(enc_privkey)
263 print >>out, " pubkey: %d bytes" % len(pubkey)
264 print >>out, " signature: %d bytes" % len(signature)
265 share_hash_ids = ",".join(sorted([str(hid)
266 for hid in share_hash_chain.keys()]))
267 print >>out, " share_hash_chain: %s" % share_hash_ids
268 print >>out, " block_hash_tree: %d nodes" % len(block_hash_tree)
270 # the storage index isn't stored in the share itself, so we depend upon
271 # knowing the parent directory name to get it
272 pieces = options['filename'].split(os.sep)
274 piece = to_str(pieces[-2])
275 if base32.could_be_base32_encoded(piece):
276 storage_index = base32.a2b(piece)
277 fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey)
278 u = SSKVerifierURI(storage_index, fingerprint)
279 verify_cap = u.to_string()
280 print >>out, " verify-cap:", quote_output(verify_cap, quotemarks=False)
282 if options['offsets']:
283 # NOTE: this offset-calculation code is fragile, and needs to be
284 # merged with MutableShareFile's internals.
286 print >>out, " Section Offsets:"
287 def printoffset(name, value, shift=0):
288 print >>out, "%s%20s: %s (0x%x)" % (" "*shift, name, value, value)
289 printoffset("first lease", m.HEADER_SIZE)
290 printoffset("share data", m.DATA_OFFSET)
291 o_seqnum = m.DATA_OFFSET + struct.calcsize(">B")
292 printoffset("seqnum", o_seqnum, 2)
293 o_root_hash = m.DATA_OFFSET + struct.calcsize(">BQ")
294 printoffset("root_hash", o_root_hash, 2)
295 for k in ["signature", "share_hash_chain", "block_hash_tree",
297 "enc_privkey", "EOF"]:
298 name = {"share_data": "block data",
299 "EOF": "end of share data"}.get(k,k)
300 offset = m.DATA_OFFSET + offsets[k]
301 printoffset(name, offset, 2)
302 f = open(options['filename'], "rb")
303 printoffset("extra leases", m._read_extra_lease_offset(f) + 4)
308 def dump_MDMF_share(m, length, options):
309 from allmydata.mutable.layout import MDMFSlotReadProxy
310 from allmydata.util import base32, hashutil
311 from allmydata.uri import MDMFVerifierURI
312 from allmydata.util.encodingutil import quote_output, to_str
314 offset = m.DATA_OFFSET
317 f = open(options['filename'], "rb")
318 storage_index = None; shnum = 0
320 class ShareDumper(MDMFSlotReadProxy):
321 def _read(self, readvs, force_remote=False, queue=False):
323 for (where,length) in readvs:
325 data.append(f.read(length))
326 return defer.succeed({shnum: data})
328 p = ShareDumper(None, storage_index, shnum)
331 # these methods return Deferreds, but we happen to know that they run
332 # synchronously when not actually talking to a remote server
334 d.addCallback(stash.append)
337 verinfo = extract(p.get_verinfo)
338 encprivkey = extract(p.get_encprivkey)
339 signature = extract(p.get_signature)
340 pubkey = extract(p.get_verification_key)
341 block_hash_tree = extract(p.get_blockhashes)
342 share_hash_chain = extract(p.get_sharehashes)
345 (seqnum, root_hash, salt_to_use, segsize, datalen, k, N, prefix,
348 print >>out, " MDMF contents:"
349 print >>out, " seqnum: %d" % seqnum
350 print >>out, " root_hash: %s" % base32.b2a(root_hash)
351 #print >>out, " IV: %s" % base32.b2a(IV)
352 print >>out, " required_shares: %d" % k
353 print >>out, " total_shares: %d" % N
354 print >>out, " segsize: %d" % segsize
355 print >>out, " datalen: %d" % datalen
356 print >>out, " enc_privkey: %d bytes" % len(encprivkey)
357 print >>out, " pubkey: %d bytes" % len(pubkey)
358 print >>out, " signature: %d bytes" % len(signature)
359 share_hash_ids = ",".join([str(hid)
360 for hid in sorted(share_hash_chain.keys())])
361 print >>out, " share_hash_chain: %s" % share_hash_ids
362 print >>out, " block_hash_tree: %d nodes" % len(block_hash_tree)
364 # the storage index isn't stored in the share itself, so we depend upon
365 # knowing the parent directory name to get it
366 pieces = options['filename'].split(os.sep)
368 piece = to_str(pieces[-2])
369 if base32.could_be_base32_encoded(piece):
370 storage_index = base32.a2b(piece)
371 fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey)
372 u = MDMFVerifierURI(storage_index, fingerprint)
373 verify_cap = u.to_string()
374 print >>out, " verify-cap:", quote_output(verify_cap, quotemarks=False)
376 if options['offsets']:
377 # NOTE: this offset-calculation code is fragile, and needs to be
378 # merged with MutableShareFile's internals.
381 print >>out, " Section Offsets:"
382 def printoffset(name, value, shift=0):
383 print >>out, "%s%.20s: %s (0x%x)" % (" "*shift, name, value, value)
384 printoffset("first lease", m.HEADER_SIZE, 2)
385 printoffset("share data", m.DATA_OFFSET, 2)
386 o_seqnum = m.DATA_OFFSET + struct.calcsize(">B")
387 printoffset("seqnum", o_seqnum, 4)
388 o_root_hash = m.DATA_OFFSET + struct.calcsize(">BQ")
389 printoffset("root_hash", o_root_hash, 4)
390 for k in ["enc_privkey", "share_hash_chain", "signature",
391 "verification_key", "verification_key_end",
392 "share_data", "block_hash_tree", "EOF"]:
393 name = {"share_data": "block data",
394 "verification_key": "pubkey",
395 "verification_key_end": "end of pubkey",
396 "EOF": "end of share data"}.get(k,k)
397 offset = m.DATA_OFFSET + offsets[k]
398 printoffset(name, offset, 4)
399 f = open(options['filename'], "rb")
400 printoffset("extra leases", m._read_extra_lease_offset(f) + 4, 2)
407 class DumpCapOptions(usage.Options):
408 def getSynopsis(self):
409 return "Usage: tahoe debug dump-cap [options] FILECAP"
412 None, "Specify the storage server nodeid (ASCII), to construct WE and secrets."],
413 ["client-secret", "c", None,
414 "Specify the client's base secret (ASCII), to construct secrets."],
415 ["client-dir", "d", None,
416 "Specify the client's base directory, from which a -c secret will be read."],
418 def parseArgs(self, cap):
421 def getUsage(self, width=None):
422 t = usage.Options.getUsage(self, width)
424 Print information about the given cap-string (aka: URI, file-cap, dir-cap,
425 read-cap, write-cap). The URI string is parsed and unpacked. This prints the
426 type of the cap, its storage index, and any derived keys.
428 tahoe debug dump-cap URI:SSK-Verifier:4vozh77tsrw7mdhnj7qvp5ky74:q7f3dwz76sjys4kqfdt3ocur2pay3a6rftnkqmi2uxu3vqsdsofq
430 This may be useful to determine if a read-cap and a write-cap refer to the
431 same time, or to extract the storage-index from a file-cap (to then use with
434 If additional information is provided (storage server nodeid and/or client
435 base secret), this command will compute the shared secrets used for the
436 write-enabler and for lease-renewal.
441 def dump_cap(options):
442 from allmydata import uri
443 from allmydata.util import base32
444 from base64 import b32decode
445 import urlparse, urllib
450 if options['nodeid']:
451 nodeid = b32decode(options['nodeid'].upper())
453 if options['client-secret']:
454 secret = base32.a2b(options['client-secret'])
455 elif options['client-dir']:
456 secretfile = os.path.join(options['client-dir'], "private", "secret")
458 secret = base32.a2b(open(secretfile, "r").read().strip())
459 except EnvironmentError:
462 if cap.startswith("http"):
463 scheme, netloc, path, params, query, fragment = urlparse.urlparse(cap)
464 assert path.startswith("/uri/")
465 cap = urllib.unquote(path[len("/uri/"):])
467 u = uri.from_string(cap)
470 dump_uri_instance(u, nodeid, secret, out)
472 def _dump_secrets(storage_index, secret, nodeid, out):
473 from allmydata.util import hashutil
474 from allmydata.util import base32
477 crs = hashutil.my_renewal_secret_hash(secret)
478 print >>out, " client renewal secret:", base32.b2a(crs)
479 frs = hashutil.file_renewal_secret_hash(crs, storage_index)
480 print >>out, " file renewal secret:", base32.b2a(frs)
482 renew = hashutil.bucket_renewal_secret_hash(frs, nodeid)
483 print >>out, " lease renewal secret:", base32.b2a(renew)
484 ccs = hashutil.my_cancel_secret_hash(secret)
485 print >>out, " client cancel secret:", base32.b2a(ccs)
486 fcs = hashutil.file_cancel_secret_hash(ccs, storage_index)
487 print >>out, " file cancel secret:", base32.b2a(fcs)
489 cancel = hashutil.bucket_cancel_secret_hash(fcs, nodeid)
490 print >>out, " lease cancel secret:", base32.b2a(cancel)
492 def dump_uri_instance(u, nodeid, secret, out, show_header=True):
493 from allmydata import uri
494 from allmydata.storage.server import si_b2a
495 from allmydata.util import base32, hashutil
496 from allmydata.util.encodingutil import quote_output
498 if isinstance(u, uri.CHKFileURI):
500 print >>out, "CHK File:"
501 print >>out, " key:", base32.b2a(u.key)
502 print >>out, " UEB hash:", base32.b2a(u.uri_extension_hash)
503 print >>out, " size:", u.size
504 print >>out, " k/N: %d/%d" % (u.needed_shares, u.total_shares)
505 print >>out, " storage index:", si_b2a(u.get_storage_index())
506 _dump_secrets(u.get_storage_index(), secret, nodeid, out)
507 elif isinstance(u, uri.CHKFileVerifierURI):
509 print >>out, "CHK Verifier URI:"
510 print >>out, " UEB hash:", base32.b2a(u.uri_extension_hash)
511 print >>out, " size:", u.size
512 print >>out, " k/N: %d/%d" % (u.needed_shares, u.total_shares)
513 print >>out, " storage index:", si_b2a(u.get_storage_index())
515 elif isinstance(u, uri.LiteralFileURI):
517 print >>out, "Literal File URI:"
518 print >>out, " data:", quote_output(u.data)
520 elif isinstance(u, uri.WriteableSSKFileURI): # SDMF
522 print >>out, "SDMF Writeable URI:"
523 print >>out, " writekey:", base32.b2a(u.writekey)
524 print >>out, " readkey:", base32.b2a(u.readkey)
525 print >>out, " storage index:", si_b2a(u.get_storage_index())
526 print >>out, " fingerprint:", base32.b2a(u.fingerprint)
529 we = hashutil.ssk_write_enabler_hash(u.writekey, nodeid)
530 print >>out, " write_enabler:", base32.b2a(we)
532 _dump_secrets(u.get_storage_index(), secret, nodeid, out)
533 elif isinstance(u, uri.ReadonlySSKFileURI):
535 print >>out, "SDMF Read-only URI:"
536 print >>out, " readkey:", base32.b2a(u.readkey)
537 print >>out, " storage index:", si_b2a(u.get_storage_index())
538 print >>out, " fingerprint:", base32.b2a(u.fingerprint)
539 elif isinstance(u, uri.SSKVerifierURI):
541 print >>out, "SDMF Verifier URI:"
542 print >>out, " storage index:", si_b2a(u.get_storage_index())
543 print >>out, " fingerprint:", base32.b2a(u.fingerprint)
545 elif isinstance(u, uri.WriteableMDMFFileURI): # MDMF
547 print >>out, "MDMF Writeable URI:"
548 print >>out, " writekey:", base32.b2a(u.writekey)
549 print >>out, " readkey:", base32.b2a(u.readkey)
550 print >>out, " storage index:", si_b2a(u.get_storage_index())
551 print >>out, " fingerprint:", base32.b2a(u.fingerprint)
554 we = hashutil.ssk_write_enabler_hash(u.writekey, nodeid)
555 print >>out, " write_enabler:", base32.b2a(we)
557 _dump_secrets(u.get_storage_index(), secret, nodeid, out)
558 elif isinstance(u, uri.ReadonlyMDMFFileURI):
560 print >>out, "MDMF Read-only URI:"
561 print >>out, " readkey:", base32.b2a(u.readkey)
562 print >>out, " storage index:", si_b2a(u.get_storage_index())
563 print >>out, " fingerprint:", base32.b2a(u.fingerprint)
564 elif isinstance(u, uri.MDMFVerifierURI):
566 print >>out, "MDMF Verifier URI:"
567 print >>out, " storage index:", si_b2a(u.get_storage_index())
568 print >>out, " fingerprint:", base32.b2a(u.fingerprint)
571 elif isinstance(u, uri.ImmutableDirectoryURI): # CHK-based directory
573 print >>out, "CHK Directory URI:"
574 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
575 elif isinstance(u, uri.ImmutableDirectoryURIVerifier):
577 print >>out, "CHK Directory Verifier URI:"
578 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
580 elif isinstance(u, uri.DirectoryURI): # SDMF-based directory
582 print >>out, "Directory Writeable URI:"
583 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
584 elif isinstance(u, uri.ReadonlyDirectoryURI):
586 print >>out, "Directory Read-only URI:"
587 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
588 elif isinstance(u, uri.DirectoryURIVerifier):
590 print >>out, "Directory Verifier URI:"
591 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
593 elif isinstance(u, uri.MDMFDirectoryURI): # MDMF-based directory
595 print >>out, "Directory Writeable URI:"
596 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
597 elif isinstance(u, uri.ReadonlyMDMFDirectoryURI):
599 print >>out, "Directory Read-only URI:"
600 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
601 elif isinstance(u, uri.MDMFDirectoryURIVerifier):
603 print >>out, "Directory Verifier URI:"
604 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
607 print >>out, "unknown cap type"
609 class FindSharesOptions(usage.Options):
610 def getSynopsis(self):
611 return "Usage: tahoe debug find-shares STORAGE_INDEX NODEDIRS.."
613 def parseArgs(self, storage_index_s, *nodedirs):
614 from allmydata.util.encodingutil import argv_to_abspath
615 self.si_s = storage_index_s
616 self.nodedirs = map(argv_to_abspath, nodedirs)
618 def getUsage(self, width=None):
619 t = usage.Options.getUsage(self, width)
621 Locate all shares for the given storage index. This command looks through one
622 or more node directories to find the shares. It returns a list of filenames,
623 one per line, for each share file found.
625 tahoe debug find-shares 4vozh77tsrw7mdhnj7qvp5ky74 testgrid/node-*
627 It may be useful during testing, when running a test grid in which all the
628 nodes are on a local disk. The share files thus located can be counted,
629 examined (with dump-share), or corrupted/deleted to test checker/repairer.
633 def find_shares(options):
634 """Given a storage index and a list of node directories, emit a list of
635 all matching shares to stdout, one per line. For example:
637 find-shares.py 44kai1tui348689nrw8fjegc8c ~/testnet/node-*
641 /home/warner/testnet/node-1/storage/shares/44k/44kai1tui348689nrw8fjegc8c/5
642 /home/warner/testnet/node-1/storage/shares/44k/44kai1tui348689nrw8fjegc8c/9
643 /home/warner/testnet/node-2/storage/shares/44k/44kai1tui348689nrw8fjegc8c/2
645 from allmydata.storage.server import si_a2b, storage_index_to_dir
646 from allmydata.util.encodingutil import listdir_unicode
649 sharedir = storage_index_to_dir(si_a2b(options.si_s))
650 for d in options.nodedirs:
651 d = os.path.join(d, "storage/shares", sharedir)
652 if os.path.exists(d):
653 for shnum in listdir_unicode(d):
654 print >>out, os.path.join(d, shnum)
659 class CatalogSharesOptions(usage.Options):
663 def parseArgs(self, *nodedirs):
664 from allmydata.util.encodingutil import argv_to_abspath
665 self.nodedirs = map(argv_to_abspath, nodedirs)
667 raise usage.UsageError("must specify at least one node directory")
669 def getSynopsis(self):
670 return "Usage: tahoe debug catalog-shares NODEDIRS.."
672 def getUsage(self, width=None):
673 t = usage.Options.getUsage(self, width)
675 Locate all shares in the given node directories, and emit a one-line summary
676 of each share. Run it like this:
678 tahoe debug catalog-shares testgrid/node-* >allshares.txt
680 The lines it emits will look like the following:
682 CHK $SI $k/$N $filesize $UEB_hash $expiration $abspath_sharefile
683 SDMF $SI $k/$N $filesize $seqnum/$roothash $expiration $abspath_sharefile
684 UNKNOWN $abspath_sharefile
686 This command can be used to build up a catalog of shares from many storage
687 servers and then sort the results to compare all shares for the same file. If
688 you see shares with the same SI but different parameters/filesize/UEB_hash,
689 then something is wrong. The misc/find-share/anomalies.py script may be
694 def call(c, *args, **kwargs):
695 # take advantage of the fact that ImmediateReadBucketProxy returns
696 # Deferreds that are already fired
699 d = defer.maybeDeferred(c, *args, **kwargs)
700 d.addCallbacks(results.append, failures.append)
702 failures[0].raiseException()
705 def describe_share(abs_sharefile, si_s, shnum_s, now, out):
706 from allmydata import uri
707 from allmydata.storage.mutable import MutableShareFile
708 from allmydata.storage.immutable import ShareFile
709 from allmydata.mutable.layout import unpack_share
710 from allmydata.mutable.common import NeedMoreDataError
711 from allmydata.immutable.layout import ReadBucketProxy
712 from allmydata.util import base32
713 from allmydata.util.encodingutil import quote_output
716 f = open(abs_sharefile, "rb")
719 if prefix == MutableShareFile.MAGIC:
721 m = MutableShareFile(abs_sharefile)
722 WE, nodeid = m._read_write_enabler_and_nodeid(f)
723 data_length = m._read_data_length(f)
724 expiration_time = min( [lease.expiration_time
725 for (i,lease) in m._enumerate_leases(f)] )
726 expiration = max(0, expiration_time - now)
728 share_type = "unknown"
729 f.seek(m.DATA_OFFSET)
731 if version == "\x00":
732 # this slot contains an SMDF share
734 elif version == "\x01":
737 if share_type == "SDMF":
738 f.seek(m.DATA_OFFSET)
739 data = f.read(min(data_length, 2000))
742 pieces = unpack_share(data)
743 except NeedMoreDataError, e:
744 # retry once with the larger size
745 size = e.needed_bytes
746 f.seek(m.DATA_OFFSET)
747 data = f.read(min(data_length, size))
748 pieces = unpack_share(data)
749 (seqnum, root_hash, IV, k, N, segsize, datalen,
750 pubkey, signature, share_hash_chain, block_hash_tree,
751 share_data, enc_privkey) = pieces
753 print >>out, "SDMF %s %d/%d %d #%d:%s %d %s" % \
754 (si_s, k, N, datalen,
755 seqnum, base32.b2a(root_hash),
756 expiration, quote_output(abs_sharefile))
757 elif share_type == "MDMF":
758 from allmydata.mutable.layout import MDMFSlotReadProxy
760 # TODO: factor this out with dump_MDMF_share()
761 class ShareDumper(MDMFSlotReadProxy):
762 def _read(self, readvs, force_remote=False, queue=False):
764 for (where,length) in readvs:
765 f.seek(m.DATA_OFFSET+where)
766 data.append(f.read(length))
767 return defer.succeed({fake_shnum: data})
769 p = ShareDumper(None, "fake-si", fake_shnum)
772 # these methods return Deferreds, but we happen to know that
773 # they run synchronously when not actually talking to a
776 d.addCallback(stash.append)
779 verinfo = extract(p.get_verinfo)
780 (seqnum, root_hash, salt_to_use, segsize, datalen, k, N, prefix,
782 print >>out, "MDMF %s %d/%d %d #%d:%s %d %s" % \
783 (si_s, k, N, datalen,
784 seqnum, base32.b2a(root_hash),
785 expiration, quote_output(abs_sharefile))
787 print >>out, "UNKNOWN mutable %s" % quote_output(abs_sharefile)
789 elif struct.unpack(">L", prefix[:4]) == (1,):
792 class ImmediateReadBucketProxy(ReadBucketProxy):
793 def __init__(self, sf):
795 ReadBucketProxy.__init__(self, None, None, "")
797 return "<ImmediateReadBucketProxy>"
798 def _read(self, offset, size):
799 return defer.succeed(sf.read_share_data(offset, size))
801 # use a ReadBucketProxy to parse the bucket and find the uri extension
802 sf = ShareFile(abs_sharefile)
803 bp = ImmediateReadBucketProxy(sf)
805 expiration_time = min( [lease.expiration_time
806 for lease in sf.get_leases()] )
807 expiration = max(0, expiration_time - now)
809 UEB_data = call(bp.get_uri_extension)
810 unpacked = uri.unpack_extension_readable(UEB_data)
812 k = unpacked["needed_shares"]
813 N = unpacked["total_shares"]
814 filesize = unpacked["size"]
815 ueb_hash = unpacked["UEB_hash"]
817 print >>out, "CHK %s %d/%d %d %s %d %s" % (si_s, k, N, filesize,
818 ueb_hash, expiration,
819 quote_output(abs_sharefile))
822 print >>out, "UNKNOWN really-unknown %s" % quote_output(abs_sharefile)
826 def catalog_shares(options):
827 from allmydata.util.encodingutil import listdir_unicode, quote_output
832 for d in options.nodedirs:
833 d = os.path.join(d, "storage/shares")
835 abbrevs = listdir_unicode(d)
836 except EnvironmentError:
837 # ignore nodes that have storage turned off altogether
840 for abbrevdir in sorted(abbrevs):
841 if abbrevdir == "incoming":
843 abbrevdir = os.path.join(d, abbrevdir)
844 # this tool may get run against bad disks, so we can't assume
845 # that listdir_unicode will always succeed. Try to catalog as much
848 sharedirs = listdir_unicode(abbrevdir)
849 for si_s in sorted(sharedirs):
850 si_dir = os.path.join(abbrevdir, si_s)
851 catalog_shares_one_abbrevdir(si_s, si_dir, now, out,err)
853 print >>err, "Error processing %s" % quote_output(abbrevdir)
854 failure.Failure().printTraceback(err)
864 def catalog_shares_one_abbrevdir(si_s, si_dir, now, out, err):
865 from allmydata.util.encodingutil import listdir_unicode, quote_output
868 for shnum_s in sorted(listdir_unicode(si_dir), key=_as_number):
869 abs_sharefile = os.path.join(si_dir, shnum_s)
870 assert os.path.isfile(abs_sharefile)
872 describe_share(abs_sharefile, si_s, shnum_s, now,
875 print >>err, "Error processing %s" % quote_output(abs_sharefile)
876 failure.Failure().printTraceback(err)
878 print >>err, "Error processing %s" % quote_output(si_dir)
879 failure.Failure().printTraceback(err)
881 class CorruptShareOptions(usage.Options):
882 def getSynopsis(self):
883 return "Usage: tahoe debug corrupt-share SHARE_FILENAME"
886 ["offset", "o", "block-random", "Specify which bit to flip."],
889 def getUsage(self, width=None):
890 t = usage.Options.getUsage(self, width)
892 Corrupt the given share by flipping a bit. This will cause a
893 verifying/downloading client to log an integrity-check failure incident, and
894 downloads will proceed with a different share.
896 The --offset parameter controls which bit should be flipped. The default is
897 to flip a single random bit of the block data.
899 tahoe debug corrupt-share testgrid/node-3/storage/shares/4v/4vozh77tsrw7mdhnj7qvp5ky74/0
901 Obviously, this command should not be used in normal operation.
904 def parseArgs(self, filename):
905 self['filename'] = filename
907 def corrupt_share(options):
909 from allmydata.storage.mutable import MutableShareFile
910 from allmydata.storage.immutable import ShareFile
911 from allmydata.mutable.layout import unpack_header
912 from allmydata.immutable.layout import ReadBucketProxy
914 fn = options['filename']
915 assert options["offset"] == "block-random", "other offsets not implemented"
916 # first, what kind of share is it?
918 def flip_bit(start, end):
919 offset = random.randrange(start, end)
920 bit = random.randrange(0, 8)
921 print >>out, "[%d..%d): %d.b%d" % (start, end, offset, bit)
925 d = chr(ord(d) ^ 0x01)
933 if prefix == MutableShareFile.MAGIC:
935 m = MutableShareFile(fn)
937 f.seek(m.DATA_OFFSET)
939 # make sure this slot contains an SMDF share
940 assert data[0] == "\x00", "non-SDMF mutable shares not supported"
943 (version, ig_seqnum, ig_roothash, ig_IV, ig_k, ig_N, ig_segsize,
944 ig_datalen, offsets) = unpack_header(data)
946 assert version == 0, "we only handle v0 SDMF files"
947 start = m.DATA_OFFSET + offsets["share_data"]
948 end = m.DATA_OFFSET + offsets["enc_privkey"]
951 # otherwise assume it's immutable
953 bp = ReadBucketProxy(None, None, '')
954 offsets = bp._parse_offsets(f.read_share_data(0, 0x24))
955 start = f._data_offset + offsets["data"]
956 end = f._data_offset + offsets["plaintext_hash_tree"]
961 class ReplOptions(usage.Options):
962 def getSynopsis(self):
963 return "Usage: tahoe debug repl"
967 return code.interact()
970 DEFAULT_TESTSUITE = 'allmydata'
972 class TrialOptions(twisted_trial.Options):
973 def getSynopsis(self):
974 return "Usage: tahoe debug trial [options] [[file|package|module|TestCase|testmethod]...]"
976 def parseOptions(self, all_subargs, *a, **kw):
977 self.trial_args = list(all_subargs)
979 # any output from the option parsing will be printed twice, but that's harmless
980 return twisted_trial.Options.parseOptions(self, all_subargs, *a, **kw)
982 def parseArgs(self, *nonoption_args):
983 if not nonoption_args:
984 self.trial_args.append(DEFAULT_TESTSUITE)
986 def getUsage(self, width=None):
987 t = twisted_trial.Options.getUsage(self, width)
989 The 'tahoe debug trial' command uses the correct imports for this instance of
990 Tahoe-LAFS. The default test suite is '%s'.
991 """ % (DEFAULT_TESTSUITE,)
995 sys.argv = ['trial'] + config.trial_args
997 # This does not return.
1001 class DebugCommand(usage.Options):
1003 ["dump-share", None, DumpOptions,
1004 "Unpack and display the contents of a share (uri_extension and leases)."],
1005 ["dump-cap", None, DumpCapOptions, "Unpack a read-cap or write-cap."],
1006 ["find-shares", None, FindSharesOptions, "Locate sharefiles in node dirs."],
1007 ["catalog-shares", None, CatalogSharesOptions, "Describe all shares in node dirs."],
1008 ["corrupt-share", None, CorruptShareOptions, "Corrupt a share by flipping a bit."],
1009 ["repl", None, ReplOptions, "Open a Python interpreter."],
1010 ["trial", None, TrialOptions, "Run tests using Twisted Trial with the right imports."],
1012 def postOptions(self):
1013 if not hasattr(self, 'subOptions'):
1014 raise usage.UsageError("must specify a subcommand")
1015 def getSynopsis(self):
1016 return "Usage: tahoe debug SUBCOMMAND"
1017 def getUsage(self, width=None):
1018 #t = usage.Options.getUsage(self, width)
1021 tahoe debug dump-share Unpack and display the contents of a share.
1022 tahoe debug dump-cap Unpack a read-cap or write-cap.
1023 tahoe debug find-shares Locate sharefiles in node directories.
1024 tahoe debug catalog-shares Describe all shares in node dirs.
1025 tahoe debug corrupt-share Corrupt a share by flipping a bit.
1026 tahoe debug repl Open a Python interpreter.
1027 tahoe debug trial Run tests using Twisted Trial with the right imports.
1029 Please run e.g. 'tahoe debug dump-share --help' for more details on each
1032 # See ticket #1441 for why we print different information when
1033 # run via /usr/bin/tahoe. Note that argv[0] is the full path.
1034 if sys.argv[0] == '/usr/bin/tahoe':
1036 To get branch coverage for the Tahoe test suite (on the installed copy of
1037 Tahoe), install the 'python-coverage' package and then use:
1039 python-coverage run --branch /usr/bin/tahoe debug trial
1043 Another debugging feature is that bin%stahoe allows executing an arbitrary
1044 "runner" command (typically an installed Python script, such as 'coverage'),
1045 with the Tahoe libraries on the PYTHONPATH. The runner command name is
1046 prefixed with '@', and any occurrences of '@tahoe' in its arguments are
1047 replaced by the full path to the tahoe script.
1049 For example, if 'coverage' is installed and on the PATH, you can use:
1051 bin%stahoe @coverage run --branch @tahoe debug trial
1053 to get branch coverage for the Tahoe test suite. Or, to run python with
1054 the -3 option that warns about Python 3 incompatibilities:
1056 bin%stahoe @python -3 @tahoe command [options]
1057 """ % (os.sep, os.sep, os.sep)
1061 "dump-share": dump_share,
1062 "dump-cap": dump_cap,
1063 "find-shares": find_shares,
1064 "catalog-shares": catalog_shares,
1065 "corrupt-share": corrupt_share,
1071 def do_debug(options):
1072 so = options.subOptions
1073 so.stdout = options.stdout
1074 so.stderr = options.stderr
1075 f = subDispatch[options.subCommand]
1080 ["debug", None, DebugCommand, "debug subcommands: use 'tahoe debug' for a list."],