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 hints = [str(k), str(segsize)]
373 u = MDMFVerifierURI(storage_index, fingerprint, hints)
374 verify_cap = u.to_string()
375 print >>out, " verify-cap:", quote_output(verify_cap, quotemarks=False)
377 if options['offsets']:
378 # NOTE: this offset-calculation code is fragile, and needs to be
379 # merged with MutableShareFile's internals.
382 print >>out, " Section Offsets:"
383 def printoffset(name, value, shift=0):
384 print >>out, "%s%.20s: %s (0x%x)" % (" "*shift, name, value, value)
385 printoffset("first lease", m.HEADER_SIZE, 2)
386 printoffset("share data", m.DATA_OFFSET, 2)
387 o_seqnum = m.DATA_OFFSET + struct.calcsize(">B")
388 printoffset("seqnum", o_seqnum, 4)
389 o_root_hash = m.DATA_OFFSET + struct.calcsize(">BQ")
390 printoffset("root_hash", o_root_hash, 4)
391 for k in ["enc_privkey", "share_hash_chain", "signature",
392 "verification_key", "verification_key_end",
393 "share_data", "block_hash_tree", "EOF"]:
394 name = {"share_data": "block data",
395 "verification_key": "pubkey",
396 "verification_key_end": "end of pubkey",
397 "EOF": "end of share data"}.get(k,k)
398 offset = m.DATA_OFFSET + offsets[k]
399 printoffset(name, offset, 4)
400 f = open(options['filename'], "rb")
401 printoffset("extra leases", m._read_extra_lease_offset(f) + 4, 2)
408 class DumpCapOptions(usage.Options):
409 def getSynopsis(self):
410 return "Usage: tahoe debug dump-cap [options] FILECAP"
413 None, "Specify the storage server nodeid (ASCII), to construct WE and secrets."],
414 ["client-secret", "c", None,
415 "Specify the client's base secret (ASCII), to construct secrets."],
416 ["client-dir", "d", None,
417 "Specify the client's base directory, from which a -c secret will be read."],
419 def parseArgs(self, cap):
422 def getUsage(self, width=None):
423 t = usage.Options.getUsage(self, width)
425 Print information about the given cap-string (aka: URI, file-cap, dir-cap,
426 read-cap, write-cap). The URI string is parsed and unpacked. This prints the
427 type of the cap, its storage index, and any derived keys.
429 tahoe debug dump-cap URI:SSK-Verifier:4vozh77tsrw7mdhnj7qvp5ky74:q7f3dwz76sjys4kqfdt3ocur2pay3a6rftnkqmi2uxu3vqsdsofq
431 This may be useful to determine if a read-cap and a write-cap refer to the
432 same time, or to extract the storage-index from a file-cap (to then use with
435 If additional information is provided (storage server nodeid and/or client
436 base secret), this command will compute the shared secrets used for the
437 write-enabler and for lease-renewal.
442 def dump_cap(options):
443 from allmydata import uri
444 from allmydata.util import base32
445 from base64 import b32decode
446 import urlparse, urllib
451 if options['nodeid']:
452 nodeid = b32decode(options['nodeid'].upper())
454 if options['client-secret']:
455 secret = base32.a2b(options['client-secret'])
456 elif options['client-dir']:
457 secretfile = os.path.join(options['client-dir'], "private", "secret")
459 secret = base32.a2b(open(secretfile, "r").read().strip())
460 except EnvironmentError:
463 if cap.startswith("http"):
464 scheme, netloc, path, params, query, fragment = urlparse.urlparse(cap)
465 assert path.startswith("/uri/")
466 cap = urllib.unquote(path[len("/uri/"):])
468 u = uri.from_string(cap)
471 dump_uri_instance(u, nodeid, secret, out)
473 def _dump_secrets(storage_index, secret, nodeid, out):
474 from allmydata.util import hashutil
475 from allmydata.util import base32
478 crs = hashutil.my_renewal_secret_hash(secret)
479 print >>out, " client renewal secret:", base32.b2a(crs)
480 frs = hashutil.file_renewal_secret_hash(crs, storage_index)
481 print >>out, " file renewal secret:", base32.b2a(frs)
483 renew = hashutil.bucket_renewal_secret_hash(frs, nodeid)
484 print >>out, " lease renewal secret:", base32.b2a(renew)
485 ccs = hashutil.my_cancel_secret_hash(secret)
486 print >>out, " client cancel secret:", base32.b2a(ccs)
487 fcs = hashutil.file_cancel_secret_hash(ccs, storage_index)
488 print >>out, " file cancel secret:", base32.b2a(fcs)
490 cancel = hashutil.bucket_cancel_secret_hash(fcs, nodeid)
491 print >>out, " lease cancel secret:", base32.b2a(cancel)
493 def dump_uri_instance(u, nodeid, secret, out, show_header=True):
494 from allmydata import uri
495 from allmydata.storage.server import si_b2a
496 from allmydata.util import base32, hashutil
497 from allmydata.util.encodingutil import quote_output
499 if isinstance(u, uri.CHKFileURI):
501 print >>out, "CHK File:"
502 print >>out, " key:", base32.b2a(u.key)
503 print >>out, " UEB hash:", base32.b2a(u.uri_extension_hash)
504 print >>out, " size:", u.size
505 print >>out, " k/N: %d/%d" % (u.needed_shares, u.total_shares)
506 print >>out, " storage index:", si_b2a(u.get_storage_index())
507 _dump_secrets(u.get_storage_index(), secret, nodeid, out)
508 elif isinstance(u, uri.CHKFileVerifierURI):
510 print >>out, "CHK Verifier URI:"
511 print >>out, " UEB hash:", base32.b2a(u.uri_extension_hash)
512 print >>out, " size:", u.size
513 print >>out, " k/N: %d/%d" % (u.needed_shares, u.total_shares)
514 print >>out, " storage index:", si_b2a(u.get_storage_index())
516 elif isinstance(u, uri.LiteralFileURI):
518 print >>out, "Literal File URI:"
519 print >>out, " data:", quote_output(u.data)
521 elif isinstance(u, uri.WriteableSSKFileURI): # SDMF
523 print >>out, "SDMF Writeable URI:"
524 print >>out, " writekey:", base32.b2a(u.writekey)
525 print >>out, " readkey:", base32.b2a(u.readkey)
526 print >>out, " storage index:", si_b2a(u.get_storage_index())
527 print >>out, " fingerprint:", base32.b2a(u.fingerprint)
530 we = hashutil.ssk_write_enabler_hash(u.writekey, nodeid)
531 print >>out, " write_enabler:", base32.b2a(we)
533 _dump_secrets(u.get_storage_index(), secret, nodeid, out)
534 elif isinstance(u, uri.ReadonlySSKFileURI):
536 print >>out, "SDMF Read-only URI:"
537 print >>out, " readkey:", base32.b2a(u.readkey)
538 print >>out, " storage index:", si_b2a(u.get_storage_index())
539 print >>out, " fingerprint:", base32.b2a(u.fingerprint)
540 elif isinstance(u, uri.SSKVerifierURI):
542 print >>out, "SDMF Verifier URI:"
543 print >>out, " storage index:", si_b2a(u.get_storage_index())
544 print >>out, " fingerprint:", base32.b2a(u.fingerprint)
546 elif isinstance(u, uri.WriteableMDMFFileURI): # MDMF
548 print >>out, "MDMF Writeable URI:"
549 print >>out, " writekey:", base32.b2a(u.writekey)
550 print >>out, " readkey:", base32.b2a(u.readkey)
551 print >>out, " storage index:", si_b2a(u.get_storage_index())
552 print >>out, " fingerprint:", base32.b2a(u.fingerprint)
555 we = hashutil.ssk_write_enabler_hash(u.writekey, nodeid)
556 print >>out, " write_enabler:", base32.b2a(we)
558 _dump_secrets(u.get_storage_index(), secret, nodeid, out)
559 elif isinstance(u, uri.ReadonlyMDMFFileURI):
561 print >>out, "MDMF Read-only URI:"
562 print >>out, " readkey:", base32.b2a(u.readkey)
563 print >>out, " storage index:", si_b2a(u.get_storage_index())
564 print >>out, " fingerprint:", base32.b2a(u.fingerprint)
565 elif isinstance(u, uri.MDMFVerifierURI):
567 print >>out, "MDMF Verifier URI:"
568 print >>out, " storage index:", si_b2a(u.get_storage_index())
569 print >>out, " fingerprint:", base32.b2a(u.fingerprint)
572 elif isinstance(u, uri.ImmutableDirectoryURI): # CHK-based directory
574 print >>out, "CHK Directory URI:"
575 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
576 elif isinstance(u, uri.ImmutableDirectoryURIVerifier):
578 print >>out, "CHK Directory Verifier URI:"
579 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
581 elif isinstance(u, uri.DirectoryURI): # SDMF-based directory
583 print >>out, "Directory Writeable URI:"
584 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
585 elif isinstance(u, uri.ReadonlyDirectoryURI):
587 print >>out, "Directory Read-only URI:"
588 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
589 elif isinstance(u, uri.DirectoryURIVerifier):
591 print >>out, "Directory Verifier URI:"
592 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
594 elif isinstance(u, uri.MDMFDirectoryURI): # MDMF-based directory
596 print >>out, "Directory Writeable URI:"
597 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
598 elif isinstance(u, uri.ReadonlyMDMFDirectoryURI):
600 print >>out, "Directory Read-only URI:"
601 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
602 elif isinstance(u, uri.MDMFDirectoryURIVerifier):
604 print >>out, "Directory Verifier URI:"
605 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
608 print >>out, "unknown cap type"
610 class FindSharesOptions(usage.Options):
611 def getSynopsis(self):
612 return "Usage: tahoe debug find-shares STORAGE_INDEX NODEDIRS.."
614 def parseArgs(self, storage_index_s, *nodedirs):
615 from allmydata.util.encodingutil import argv_to_abspath
616 self.si_s = storage_index_s
617 self.nodedirs = map(argv_to_abspath, nodedirs)
619 def getUsage(self, width=None):
620 t = usage.Options.getUsage(self, width)
622 Locate all shares for the given storage index. This command looks through one
623 or more node directories to find the shares. It returns a list of filenames,
624 one per line, for each share file found.
626 tahoe debug find-shares 4vozh77tsrw7mdhnj7qvp5ky74 testgrid/node-*
628 It may be useful during testing, when running a test grid in which all the
629 nodes are on a local disk. The share files thus located can be counted,
630 examined (with dump-share), or corrupted/deleted to test checker/repairer.
634 def find_shares(options):
635 """Given a storage index and a list of node directories, emit a list of
636 all matching shares to stdout, one per line. For example:
638 find-shares.py 44kai1tui348689nrw8fjegc8c ~/testnet/node-*
642 /home/warner/testnet/node-1/storage/shares/44k/44kai1tui348689nrw8fjegc8c/5
643 /home/warner/testnet/node-1/storage/shares/44k/44kai1tui348689nrw8fjegc8c/9
644 /home/warner/testnet/node-2/storage/shares/44k/44kai1tui348689nrw8fjegc8c/2
646 from allmydata.storage.server import si_a2b, storage_index_to_dir
647 from allmydata.util.encodingutil import listdir_unicode
650 sharedir = storage_index_to_dir(si_a2b(options.si_s))
651 for d in options.nodedirs:
652 d = os.path.join(d, "storage/shares", sharedir)
653 if os.path.exists(d):
654 for shnum in listdir_unicode(d):
655 print >>out, os.path.join(d, shnum)
660 class CatalogSharesOptions(usage.Options):
664 def parseArgs(self, *nodedirs):
665 from allmydata.util.encodingutil import argv_to_abspath
666 self.nodedirs = map(argv_to_abspath, nodedirs)
668 raise usage.UsageError("must specify at least one node directory")
670 def getSynopsis(self):
671 return "Usage: tahoe debug catalog-shares NODEDIRS.."
673 def getUsage(self, width=None):
674 t = usage.Options.getUsage(self, width)
676 Locate all shares in the given node directories, and emit a one-line summary
677 of each share. Run it like this:
679 tahoe debug catalog-shares testgrid/node-* >allshares.txt
681 The lines it emits will look like the following:
683 CHK $SI $k/$N $filesize $UEB_hash $expiration $abspath_sharefile
684 SDMF $SI $k/$N $filesize $seqnum/$roothash $expiration $abspath_sharefile
685 UNKNOWN $abspath_sharefile
687 This command can be used to build up a catalog of shares from many storage
688 servers and then sort the results to compare all shares for the same file. If
689 you see shares with the same SI but different parameters/filesize/UEB_hash,
690 then something is wrong. The misc/find-share/anomalies.py script may be
695 def call(c, *args, **kwargs):
696 # take advantage of the fact that ImmediateReadBucketProxy returns
697 # Deferreds that are already fired
700 d = defer.maybeDeferred(c, *args, **kwargs)
701 d.addCallbacks(results.append, failures.append)
703 failures[0].raiseException()
706 def describe_share(abs_sharefile, si_s, shnum_s, now, out):
707 from allmydata import uri
708 from allmydata.storage.mutable import MutableShareFile
709 from allmydata.storage.immutable import ShareFile
710 from allmydata.mutable.layout import unpack_share
711 from allmydata.mutable.common import NeedMoreDataError
712 from allmydata.immutable.layout import ReadBucketProxy
713 from allmydata.util import base32
714 from allmydata.util.encodingutil import quote_output
717 f = open(abs_sharefile, "rb")
720 if prefix == MutableShareFile.MAGIC:
722 m = MutableShareFile(abs_sharefile)
723 WE, nodeid = m._read_write_enabler_and_nodeid(f)
724 data_length = m._read_data_length(f)
725 expiration_time = min( [lease.expiration_time
726 for (i,lease) in m._enumerate_leases(f)] )
727 expiration = max(0, expiration_time - now)
729 share_type = "unknown"
730 f.seek(m.DATA_OFFSET)
731 if f.read(1) == "\x00":
732 # this slot contains an SMDF share
735 if share_type == "SDMF":
736 f.seek(m.DATA_OFFSET)
737 data = f.read(min(data_length, 2000))
740 pieces = unpack_share(data)
741 except NeedMoreDataError, e:
742 # retry once with the larger size
743 size = e.needed_bytes
744 f.seek(m.DATA_OFFSET)
745 data = f.read(min(data_length, size))
746 pieces = unpack_share(data)
747 (seqnum, root_hash, IV, k, N, segsize, datalen,
748 pubkey, signature, share_hash_chain, block_hash_tree,
749 share_data, enc_privkey) = pieces
751 print >>out, "SDMF %s %d/%d %d #%d:%s %d %s" % \
752 (si_s, k, N, datalen,
753 seqnum, base32.b2a(root_hash),
754 expiration, quote_output(abs_sharefile))
756 print >>out, "UNKNOWN mutable %s" % quote_output(abs_sharefile)
758 elif struct.unpack(">L", prefix[:4]) == (1,):
761 class ImmediateReadBucketProxy(ReadBucketProxy):
762 def __init__(self, sf):
764 ReadBucketProxy.__init__(self, None, None, "")
766 return "<ImmediateReadBucketProxy>"
767 def _read(self, offset, size):
768 return defer.succeed(sf.read_share_data(offset, size))
770 # use a ReadBucketProxy to parse the bucket and find the uri extension
771 sf = ShareFile(abs_sharefile)
772 bp = ImmediateReadBucketProxy(sf)
774 expiration_time = min( [lease.expiration_time
775 for lease in sf.get_leases()] )
776 expiration = max(0, expiration_time - now)
778 UEB_data = call(bp.get_uri_extension)
779 unpacked = uri.unpack_extension_readable(UEB_data)
781 k = unpacked["needed_shares"]
782 N = unpacked["total_shares"]
783 filesize = unpacked["size"]
784 ueb_hash = unpacked["UEB_hash"]
786 print >>out, "CHK %s %d/%d %d %s %d %s" % (si_s, k, N, filesize,
787 ueb_hash, expiration,
788 quote_output(abs_sharefile))
791 print >>out, "UNKNOWN really-unknown %s" % quote_output(abs_sharefile)
795 def catalog_shares(options):
796 from allmydata.util.encodingutil import listdir_unicode, quote_output
801 for d in options.nodedirs:
802 d = os.path.join(d, "storage/shares")
804 abbrevs = listdir_unicode(d)
805 except EnvironmentError:
806 # ignore nodes that have storage turned off altogether
809 for abbrevdir in sorted(abbrevs):
810 if abbrevdir == "incoming":
812 abbrevdir = os.path.join(d, abbrevdir)
813 # this tool may get run against bad disks, so we can't assume
814 # that listdir_unicode will always succeed. Try to catalog as much
817 sharedirs = listdir_unicode(abbrevdir)
818 for si_s in sorted(sharedirs):
819 si_dir = os.path.join(abbrevdir, si_s)
820 catalog_shares_one_abbrevdir(si_s, si_dir, now, out,err)
822 print >>err, "Error processing %s" % quote_output(abbrevdir)
823 failure.Failure().printTraceback(err)
833 def catalog_shares_one_abbrevdir(si_s, si_dir, now, out, err):
834 from allmydata.util.encodingutil import listdir_unicode, quote_output
837 for shnum_s in sorted(listdir_unicode(si_dir), key=_as_number):
838 abs_sharefile = os.path.join(si_dir, shnum_s)
839 assert os.path.isfile(abs_sharefile)
841 describe_share(abs_sharefile, si_s, shnum_s, now,
844 print >>err, "Error processing %s" % quote_output(abs_sharefile)
845 failure.Failure().printTraceback(err)
847 print >>err, "Error processing %s" % quote_output(si_dir)
848 failure.Failure().printTraceback(err)
850 class CorruptShareOptions(usage.Options):
851 def getSynopsis(self):
852 return "Usage: tahoe debug corrupt-share SHARE_FILENAME"
855 ["offset", "o", "block-random", "Specify which bit to flip."],
858 def getUsage(self, width=None):
859 t = usage.Options.getUsage(self, width)
861 Corrupt the given share by flipping a bit. This will cause a
862 verifying/downloading client to log an integrity-check failure incident, and
863 downloads will proceed with a different share.
865 The --offset parameter controls which bit should be flipped. The default is
866 to flip a single random bit of the block data.
868 tahoe debug corrupt-share testgrid/node-3/storage/shares/4v/4vozh77tsrw7mdhnj7qvp5ky74/0
870 Obviously, this command should not be used in normal operation.
873 def parseArgs(self, filename):
874 self['filename'] = filename
876 def corrupt_share(options):
878 from allmydata.storage.mutable import MutableShareFile
879 from allmydata.storage.immutable import ShareFile
880 from allmydata.mutable.layout import unpack_header
881 from allmydata.immutable.layout import ReadBucketProxy
883 fn = options['filename']
884 assert options["offset"] == "block-random", "other offsets not implemented"
885 # first, what kind of share is it?
887 def flip_bit(start, end):
888 offset = random.randrange(start, end)
889 bit = random.randrange(0, 8)
890 print >>out, "[%d..%d): %d.b%d" % (start, end, offset, bit)
894 d = chr(ord(d) ^ 0x01)
902 if prefix == MutableShareFile.MAGIC:
904 m = MutableShareFile(fn)
906 f.seek(m.DATA_OFFSET)
908 # make sure this slot contains an SMDF share
909 assert data[0] == "\x00", "non-SDMF mutable shares not supported"
912 (version, ig_seqnum, ig_roothash, ig_IV, ig_k, ig_N, ig_segsize,
913 ig_datalen, offsets) = unpack_header(data)
915 assert version == 0, "we only handle v0 SDMF files"
916 start = m.DATA_OFFSET + offsets["share_data"]
917 end = m.DATA_OFFSET + offsets["enc_privkey"]
920 # otherwise assume it's immutable
922 bp = ReadBucketProxy(None, None, '')
923 offsets = bp._parse_offsets(f.read_share_data(0, 0x24))
924 start = f._data_offset + offsets["data"]
925 end = f._data_offset + offsets["plaintext_hash_tree"]
930 class ReplOptions(usage.Options):
931 def getSynopsis(self):
932 return "Usage: tahoe debug repl"
936 return code.interact()
939 DEFAULT_TESTSUITE = 'allmydata'
941 class TrialOptions(twisted_trial.Options):
942 def getSynopsis(self):
943 return "Usage: tahoe debug trial [options] [[file|package|module|TestCase|testmethod]...]"
945 def parseOptions(self, all_subargs, *a, **kw):
946 self.trial_args = list(all_subargs)
948 # any output from the option parsing will be printed twice, but that's harmless
949 return twisted_trial.Options.parseOptions(self, all_subargs, *a, **kw)
951 def parseArgs(self, *nonoption_args):
952 if not nonoption_args:
953 self.trial_args.append(DEFAULT_TESTSUITE)
955 def getUsage(self, width=None):
956 t = twisted_trial.Options.getUsage(self, width)
958 The 'tahoe debug trial' command uses the correct imports for this instance of
959 Tahoe-LAFS. The default test suite is '%s'.
960 """ % (DEFAULT_TESTSUITE,)
964 sys.argv = ['trial'] + config.trial_args
966 # This does not return.
970 class DebugCommand(usage.Options):
972 ["dump-share", None, DumpOptions,
973 "Unpack and display the contents of a share (uri_extension and leases)."],
974 ["dump-cap", None, DumpCapOptions, "Unpack a read-cap or write-cap."],
975 ["find-shares", None, FindSharesOptions, "Locate sharefiles in node dirs."],
976 ["catalog-shares", None, CatalogSharesOptions, "Describe all shares in node dirs."],
977 ["corrupt-share", None, CorruptShareOptions, "Corrupt a share by flipping a bit."],
978 ["repl", None, ReplOptions, "Open a Python interpreter."],
979 ["trial", None, TrialOptions, "Run tests using Twisted Trial with the right imports."],
981 def postOptions(self):
982 if not hasattr(self, 'subOptions'):
983 raise usage.UsageError("must specify a subcommand")
984 def getSynopsis(self):
985 return "Usage: tahoe debug SUBCOMMAND"
986 def getUsage(self, width=None):
987 #t = usage.Options.getUsage(self, width)
990 tahoe debug dump-share Unpack and display the contents of a share.
991 tahoe debug dump-cap Unpack a read-cap or write-cap.
992 tahoe debug find-shares Locate sharefiles in node directories.
993 tahoe debug catalog-shares Describe all shares in node dirs.
994 tahoe debug corrupt-share Corrupt a share by flipping a bit.
995 tahoe debug repl Open a Python interpreter.
996 tahoe debug trial Run tests using Twisted Trial with the right imports.
998 Please run e.g. 'tahoe debug dump-share --help' for more details on each
1001 # See ticket #1441 for why we print different information when
1002 # run via /usr/bin/tahoe. Note that argv[0] is the full path.
1003 if sys.argv[0] == '/usr/bin/tahoe':
1005 To get branch coverage for the Tahoe test suite (on the installed copy of
1006 Tahoe), install the 'python-coverage' package and then use:
1008 python-coverage run --branch /usr/bin/tahoe debug trial
1012 Another debugging feature is that bin%stahoe allows executing an arbitrary
1013 "runner" command (typically an installed Python script, such as 'coverage'),
1014 with the Tahoe libraries on the PYTHONPATH. The runner command name is
1015 prefixed with '@', and any occurrences of '@tahoe' in its arguments are
1016 replaced by the full path to the tahoe script.
1018 For example, if 'coverage' is installed and on the PATH, you can use:
1020 bin%stahoe @coverage run --branch @tahoe debug trial
1022 to get branch coverage for the Tahoe test suite. Or, to run python with
1023 the -3 option that warns about Python 3 incompatibilities:
1025 bin%stahoe @python -3 @tahoe command [options]
1026 """ % (os.sep, os.sep, os.sep)
1030 "dump-share": dump_share,
1031 "dump-cap": dump_cap,
1032 "find-shares": find_shares,
1033 "catalog-shares": catalog_shares,
1034 "corrupt-share": corrupt_share,
1040 def do_debug(options):
1041 so = options.subOptions
1042 so.stdout = options.stdout
1043 so.stderr = options.stderr
1044 f = subDispatch[options.subCommand]
1049 ["debug", None, DebugCommand, "debug subcommands: use 'tahoe debug' for a list."],