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
8 from foolscap.logging import cli as foolscap_cli
9 from allmydata.scripts.common import BaseOptions
12 class DumpOptions(BaseOptions):
13 def getSynopsis(self):
14 return "Usage: tahoe [global-options] debug dump-share SHARE_FILENAME"
17 ["offsets", None, "Display a table of section offsets."],
18 ["leases-only", None, "Dump leases but not CHK contents."],
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
30 def parseArgs(self, filename):
31 from allmydata.util.encodingutil import argv_to_abspath
32 self['filename'] = argv_to_abspath(filename)
34 def dump_share(options):
35 from allmydata.storage.mutable import MutableShareFile
36 from allmydata.util.encodingutil import quote_output
40 # check the version, to see if we have a mutable or immutable share
41 print >>out, "share filename: %s" % quote_output(options['filename'])
43 f = open(options['filename'], "rb")
46 if prefix == MutableShareFile.MAGIC:
47 return dump_mutable_share(options)
48 # otherwise assume it's immutable
49 return dump_immutable_share(options)
51 def dump_immutable_share(options):
52 from allmydata.storage.immutable import ShareFile
55 f = ShareFile(options['filename'])
56 if not options["leases-only"]:
57 dump_immutable_chk_share(f, out, options)
58 dump_immutable_lease_info(f, out)
62 def dump_immutable_chk_share(f, out, options):
63 from allmydata import uri
64 from allmydata.util import base32
65 from allmydata.immutable.layout import ReadBucketProxy
66 from allmydata.util.encodingutil import quote_output, to_str
68 # use a ReadBucketProxy to parse the bucket and find the uri extension
69 bp = ReadBucketProxy(None, None, '')
70 offsets = bp._parse_offsets(f.read_share_data(0, 0x44))
71 print >>out, "%20s: %d" % ("version", bp._version)
72 seek = offsets['uri_extension']
73 length = struct.unpack(bp._fieldstruct,
74 f.read_share_data(seek, bp._fieldsize))[0]
76 UEB_data = f.read_share_data(seek, length)
78 unpacked = uri.unpack_extension_readable(UEB_data)
79 keys1 = ("size", "num_segments", "segment_size",
80 "needed_shares", "total_shares")
81 keys2 = ("codec_name", "codec_params", "tail_codec_params")
82 keys3 = ("plaintext_hash", "plaintext_root_hash",
83 "crypttext_hash", "crypttext_root_hash",
84 "share_root_hash", "UEB_hash")
85 display_keys = {"size": "file_size"}
88 dk = display_keys.get(k, k)
89 print >>out, "%20s: %s" % (dk, unpacked[k])
93 dk = display_keys.get(k, k)
94 print >>out, "%20s: %s" % (dk, unpacked[k])
98 dk = display_keys.get(k, k)
99 print >>out, "%20s: %s" % (dk, unpacked[k])
101 leftover = set(unpacked.keys()) - set(keys1 + keys2 + keys3)
104 print >>out, "LEFTOVER:"
105 for k in sorted(leftover):
106 print >>out, "%20s: %s" % (k, unpacked[k])
108 # the storage index isn't stored in the share itself, so we depend upon
109 # knowing the parent directory name to get it
110 pieces = options['filename'].split(os.sep)
112 piece = to_str(pieces[-2])
113 if base32.could_be_base32_encoded(piece):
114 storage_index = base32.a2b(piece)
115 uri_extension_hash = base32.a2b(unpacked["UEB_hash"])
116 u = uri.CHKFileVerifierURI(storage_index, uri_extension_hash,
117 unpacked["needed_shares"],
118 unpacked["total_shares"], unpacked["size"])
119 verify_cap = u.to_string()
120 print >>out, "%20s: %s" % ("verify-cap", quote_output(verify_cap, quotemarks=False))
123 sizes['data'] = (offsets['plaintext_hash_tree'] -
125 sizes['validation'] = (offsets['uri_extension'] -
126 offsets['plaintext_hash_tree'])
127 sizes['uri-extension'] = len(UEB_data)
129 print >>out, " Size of data within the share:"
130 for k in sorted(sizes):
131 print >>out, "%20s: %s" % (k, sizes[k])
133 if options['offsets']:
135 print >>out, " Section Offsets:"
136 print >>out, "%20s: %s" % ("share data", f._data_offset)
137 for k in ["data", "plaintext_hash_tree", "crypttext_hash_tree",
138 "block_hashes", "share_hashes", "uri_extension"]:
139 name = {"data": "block data"}.get(k,k)
140 offset = f._data_offset + offsets[k]
141 print >>out, " %20s: %s (0x%x)" % (name, offset, offset)
142 print >>out, "%20s: %s" % ("leases", f._lease_offset)
144 def dump_immutable_lease_info(f, out):
145 # display lease information too
147 leases = list(f.get_leases())
149 for i,lease in enumerate(leases):
150 when = format_expiration_time(lease.expiration_time)
151 print >>out, " Lease #%d: owner=%d, expire in %s" \
152 % (i, lease.owner_num, when)
154 print >>out, " No leases."
156 def format_expiration_time(expiration_time):
158 remains = expiration_time - now
159 when = "%ds" % remains
160 if remains > 24*3600:
161 when += " (%d days)" % (remains / (24*3600))
163 when += " (%d hours)" % (remains / 3600)
167 def dump_mutable_share(options):
168 from allmydata.storage.mutable import MutableShareFile
169 from allmydata.util import base32, idlib
171 m = MutableShareFile(options['filename'])
172 f = open(options['filename'], "rb")
173 WE, nodeid = m._read_write_enabler_and_nodeid(f)
174 num_extra_leases = m._read_num_extra_leases(f)
175 data_length = m._read_data_length(f)
176 extra_lease_offset = m._read_extra_lease_offset(f)
177 container_size = extra_lease_offset - m.DATA_OFFSET
178 leases = list(m._enumerate_leases(f))
180 share_type = "unknown"
181 f.seek(m.DATA_OFFSET)
183 if version == "\x00":
184 # this slot contains an SMDF share
186 elif version == "\x01":
191 print >>out, "Mutable slot found:"
192 print >>out, " share_type: %s" % share_type
193 print >>out, " write_enabler: %s" % base32.b2a(WE)
194 print >>out, " WE for nodeid: %s" % idlib.nodeid_b2a(nodeid)
195 print >>out, " num_extra_leases: %d" % num_extra_leases
196 print >>out, " container_size: %d" % container_size
197 print >>out, " data_length: %d" % data_length
199 for (leasenum, lease) in leases:
201 print >>out, " Lease #%d:" % leasenum
202 print >>out, " ownerid: %d" % lease.owner_num
203 when = format_expiration_time(lease.expiration_time)
204 print >>out, " expires in %s" % when
205 print >>out, " renew_secret: %s" % base32.b2a(lease.renew_secret)
206 print >>out, " cancel_secret: %s" % base32.b2a(lease.cancel_secret)
207 print >>out, " secrets are for nodeid: %s" % idlib.nodeid_b2a(lease.nodeid)
209 print >>out, "No leases."
212 if share_type == "SDMF":
213 dump_SDMF_share(m, data_length, options)
214 elif share_type == "MDMF":
215 dump_MDMF_share(m, data_length, options)
219 def dump_SDMF_share(m, length, options):
220 from allmydata.mutable.layout import unpack_share, unpack_header
221 from allmydata.mutable.common import NeedMoreDataError
222 from allmydata.util import base32, hashutil
223 from allmydata.uri import SSKVerifierURI
224 from allmydata.util.encodingutil import quote_output, to_str
226 offset = m.DATA_OFFSET
230 f = open(options['filename'], "rb")
232 data = f.read(min(length, 2000))
236 pieces = unpack_share(data)
237 except NeedMoreDataError, e:
238 # retry once with the larger size
239 size = e.needed_bytes
240 f = open(options['filename'], "rb")
242 data = f.read(min(length, size))
244 pieces = unpack_share(data)
246 (seqnum, root_hash, IV, k, N, segsize, datalen,
247 pubkey, signature, share_hash_chain, block_hash_tree,
248 share_data, enc_privkey) = pieces
249 (ig_version, ig_seqnum, ig_roothash, ig_IV, ig_k, ig_N, ig_segsize,
250 ig_datalen, offsets) = unpack_header(data)
252 print >>out, " SDMF contents:"
253 print >>out, " seqnum: %d" % seqnum
254 print >>out, " root_hash: %s" % base32.b2a(root_hash)
255 print >>out, " IV: %s" % base32.b2a(IV)
256 print >>out, " required_shares: %d" % k
257 print >>out, " total_shares: %d" % N
258 print >>out, " segsize: %d" % segsize
259 print >>out, " datalen: %d" % datalen
260 print >>out, " enc_privkey: %d bytes" % len(enc_privkey)
261 print >>out, " pubkey: %d bytes" % len(pubkey)
262 print >>out, " signature: %d bytes" % len(signature)
263 share_hash_ids = ",".join(sorted([str(hid)
264 for hid in share_hash_chain.keys()]))
265 print >>out, " share_hash_chain: %s" % share_hash_ids
266 print >>out, " block_hash_tree: %d nodes" % len(block_hash_tree)
268 # the storage index isn't stored in the share itself, so we depend upon
269 # knowing the parent directory name to get it
270 pieces = options['filename'].split(os.sep)
272 piece = to_str(pieces[-2])
273 if base32.could_be_base32_encoded(piece):
274 storage_index = base32.a2b(piece)
275 fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey)
276 u = SSKVerifierURI(storage_index, fingerprint)
277 verify_cap = u.to_string()
278 print >>out, " verify-cap:", quote_output(verify_cap, quotemarks=False)
280 if options['offsets']:
281 # NOTE: this offset-calculation code is fragile, and needs to be
282 # merged with MutableShareFile's internals.
284 print >>out, " Section Offsets:"
285 def printoffset(name, value, shift=0):
286 print >>out, "%s%20s: %s (0x%x)" % (" "*shift, name, value, value)
287 printoffset("first lease", m.HEADER_SIZE)
288 printoffset("share data", m.DATA_OFFSET)
289 o_seqnum = m.DATA_OFFSET + struct.calcsize(">B")
290 printoffset("seqnum", o_seqnum, 2)
291 o_root_hash = m.DATA_OFFSET + struct.calcsize(">BQ")
292 printoffset("root_hash", o_root_hash, 2)
293 for k in ["signature", "share_hash_chain", "block_hash_tree",
295 "enc_privkey", "EOF"]:
296 name = {"share_data": "block data",
297 "EOF": "end of share data"}.get(k,k)
298 offset = m.DATA_OFFSET + offsets[k]
299 printoffset(name, offset, 2)
300 f = open(options['filename'], "rb")
301 printoffset("extra leases", m._read_extra_lease_offset(f) + 4)
306 def dump_MDMF_share(m, length, options):
307 from allmydata.mutable.layout import MDMFSlotReadProxy
308 from allmydata.util import base32, hashutil
309 from allmydata.uri import MDMFVerifierURI
310 from allmydata.util.encodingutil import quote_output, to_str
312 offset = m.DATA_OFFSET
315 f = open(options['filename'], "rb")
316 storage_index = None; shnum = 0
318 class ShareDumper(MDMFSlotReadProxy):
319 def _read(self, readvs, force_remote=False, queue=False):
321 for (where,length) in readvs:
323 data.append(f.read(length))
324 return defer.succeed({shnum: data})
326 p = ShareDumper(None, storage_index, shnum)
329 # these methods return Deferreds, but we happen to know that they run
330 # synchronously when not actually talking to a remote server
332 d.addCallback(stash.append)
335 verinfo = extract(p.get_verinfo)
336 encprivkey = extract(p.get_encprivkey)
337 signature = extract(p.get_signature)
338 pubkey = extract(p.get_verification_key)
339 block_hash_tree = extract(p.get_blockhashes)
340 share_hash_chain = extract(p.get_sharehashes)
343 (seqnum, root_hash, salt_to_use, segsize, datalen, k, N, prefix,
346 print >>out, " MDMF contents:"
347 print >>out, " seqnum: %d" % seqnum
348 print >>out, " root_hash: %s" % base32.b2a(root_hash)
349 #print >>out, " IV: %s" % base32.b2a(IV)
350 print >>out, " required_shares: %d" % k
351 print >>out, " total_shares: %d" % N
352 print >>out, " segsize: %d" % segsize
353 print >>out, " datalen: %d" % datalen
354 print >>out, " enc_privkey: %d bytes" % len(encprivkey)
355 print >>out, " pubkey: %d bytes" % len(pubkey)
356 print >>out, " signature: %d bytes" % len(signature)
357 share_hash_ids = ",".join([str(hid)
358 for hid in sorted(share_hash_chain.keys())])
359 print >>out, " share_hash_chain: %s" % share_hash_ids
360 print >>out, " block_hash_tree: %d nodes" % len(block_hash_tree)
362 # the storage index isn't stored in the share itself, so we depend upon
363 # knowing the parent directory name to get it
364 pieces = options['filename'].split(os.sep)
366 piece = to_str(pieces[-2])
367 if base32.could_be_base32_encoded(piece):
368 storage_index = base32.a2b(piece)
369 fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey)
370 u = MDMFVerifierURI(storage_index, fingerprint)
371 verify_cap = u.to_string()
372 print >>out, " verify-cap:", quote_output(verify_cap, quotemarks=False)
374 if options['offsets']:
375 # NOTE: this offset-calculation code is fragile, and needs to be
376 # merged with MutableShareFile's internals.
379 print >>out, " Section Offsets:"
380 def printoffset(name, value, shift=0):
381 print >>out, "%s%.20s: %s (0x%x)" % (" "*shift, name, value, value)
382 printoffset("first lease", m.HEADER_SIZE, 2)
383 printoffset("share data", m.DATA_OFFSET, 2)
384 o_seqnum = m.DATA_OFFSET + struct.calcsize(">B")
385 printoffset("seqnum", o_seqnum, 4)
386 o_root_hash = m.DATA_OFFSET + struct.calcsize(">BQ")
387 printoffset("root_hash", o_root_hash, 4)
388 for k in ["enc_privkey", "share_hash_chain", "signature",
389 "verification_key", "verification_key_end",
390 "share_data", "block_hash_tree", "EOF"]:
391 name = {"share_data": "block data",
392 "verification_key": "pubkey",
393 "verification_key_end": "end of pubkey",
394 "EOF": "end of share data"}.get(k,k)
395 offset = m.DATA_OFFSET + offsets[k]
396 printoffset(name, offset, 4)
397 f = open(options['filename'], "rb")
398 printoffset("extra leases", m._read_extra_lease_offset(f) + 4, 2)
405 class DumpCapOptions(BaseOptions):
406 def getSynopsis(self):
407 return "Usage: tahoe [global-options] debug dump-cap [options] FILECAP"
410 None, "Specify the storage server nodeid (ASCII), to construct WE and secrets."],
411 ["client-secret", "c", None,
412 "Specify the client's base secret (ASCII), to construct secrets."],
413 ["client-dir", "d", None,
414 "Specify the client's base directory, from which a -c secret will be read."],
416 def parseArgs(self, cap):
420 Print information about the given cap-string (aka: URI, file-cap, dir-cap,
421 read-cap, write-cap). The URI string is parsed and unpacked. This prints the
422 type of the cap, its storage index, and any derived keys.
424 tahoe debug dump-cap URI:SSK-Verifier:4vozh77tsrw7mdhnj7qvp5ky74:q7f3dwz76sjys4kqfdt3ocur2pay3a6rftnkqmi2uxu3vqsdsofq
426 This may be useful to determine if a read-cap and a write-cap refer to the
427 same time, or to extract the storage-index from a file-cap (to then use with
430 If additional information is provided (storage server nodeid and/or client
431 base secret), this command will compute the shared secrets used for the
432 write-enabler and for lease-renewal.
436 def dump_cap(options):
437 from allmydata import uri
438 from allmydata.util import base32
439 from base64 import b32decode
440 import urlparse, urllib
445 if options['nodeid']:
446 nodeid = b32decode(options['nodeid'].upper())
448 if options['client-secret']:
449 secret = base32.a2b(options['client-secret'])
450 elif options['client-dir']:
451 secretfile = os.path.join(options['client-dir'], "private", "secret")
453 secret = base32.a2b(open(secretfile, "r").read().strip())
454 except EnvironmentError:
457 if cap.startswith("http"):
458 scheme, netloc, path, params, query, fragment = urlparse.urlparse(cap)
459 assert path.startswith("/uri/")
460 cap = urllib.unquote(path[len("/uri/"):])
462 u = uri.from_string(cap)
465 dump_uri_instance(u, nodeid, secret, out)
467 def _dump_secrets(storage_index, secret, nodeid, out):
468 from allmydata.util import hashutil
469 from allmydata.util import base32
472 crs = hashutil.my_renewal_secret_hash(secret)
473 print >>out, " client renewal secret:", base32.b2a(crs)
474 frs = hashutil.file_renewal_secret_hash(crs, storage_index)
475 print >>out, " file renewal secret:", base32.b2a(frs)
477 renew = hashutil.bucket_renewal_secret_hash(frs, nodeid)
478 print >>out, " lease renewal secret:", base32.b2a(renew)
479 ccs = hashutil.my_cancel_secret_hash(secret)
480 print >>out, " client cancel secret:", base32.b2a(ccs)
481 fcs = hashutil.file_cancel_secret_hash(ccs, storage_index)
482 print >>out, " file cancel secret:", base32.b2a(fcs)
484 cancel = hashutil.bucket_cancel_secret_hash(fcs, nodeid)
485 print >>out, " lease cancel secret:", base32.b2a(cancel)
487 def dump_uri_instance(u, nodeid, secret, out, show_header=True):
488 from allmydata import uri
489 from allmydata.storage.server import si_b2a
490 from allmydata.util import base32, hashutil
491 from allmydata.util.encodingutil import quote_output
493 if isinstance(u, uri.CHKFileURI):
495 print >>out, "CHK File:"
496 print >>out, " key:", base32.b2a(u.key)
497 print >>out, " UEB hash:", base32.b2a(u.uri_extension_hash)
498 print >>out, " size:", u.size
499 print >>out, " k/N: %d/%d" % (u.needed_shares, u.total_shares)
500 print >>out, " storage index:", si_b2a(u.get_storage_index())
501 _dump_secrets(u.get_storage_index(), secret, nodeid, out)
502 elif isinstance(u, uri.CHKFileVerifierURI):
504 print >>out, "CHK Verifier URI:"
505 print >>out, " UEB hash:", base32.b2a(u.uri_extension_hash)
506 print >>out, " size:", u.size
507 print >>out, " k/N: %d/%d" % (u.needed_shares, u.total_shares)
508 print >>out, " storage index:", si_b2a(u.get_storage_index())
510 elif isinstance(u, uri.LiteralFileURI):
512 print >>out, "Literal File URI:"
513 print >>out, " data:", quote_output(u.data)
515 elif isinstance(u, uri.WriteableSSKFileURI): # SDMF
517 print >>out, "SDMF Writeable URI:"
518 print >>out, " writekey:", base32.b2a(u.writekey)
519 print >>out, " readkey:", base32.b2a(u.readkey)
520 print >>out, " storage index:", si_b2a(u.get_storage_index())
521 print >>out, " fingerprint:", base32.b2a(u.fingerprint)
524 we = hashutil.ssk_write_enabler_hash(u.writekey, nodeid)
525 print >>out, " write_enabler:", base32.b2a(we)
527 _dump_secrets(u.get_storage_index(), secret, nodeid, out)
528 elif isinstance(u, uri.ReadonlySSKFileURI):
530 print >>out, "SDMF Read-only URI:"
531 print >>out, " readkey:", base32.b2a(u.readkey)
532 print >>out, " storage index:", si_b2a(u.get_storage_index())
533 print >>out, " fingerprint:", base32.b2a(u.fingerprint)
534 elif isinstance(u, uri.SSKVerifierURI):
536 print >>out, "SDMF Verifier URI:"
537 print >>out, " storage index:", si_b2a(u.get_storage_index())
538 print >>out, " fingerprint:", base32.b2a(u.fingerprint)
540 elif isinstance(u, uri.WriteableMDMFFileURI): # MDMF
542 print >>out, "MDMF Writeable URI:"
543 print >>out, " writekey:", base32.b2a(u.writekey)
544 print >>out, " readkey:", base32.b2a(u.readkey)
545 print >>out, " storage index:", si_b2a(u.get_storage_index())
546 print >>out, " fingerprint:", base32.b2a(u.fingerprint)
549 we = hashutil.ssk_write_enabler_hash(u.writekey, nodeid)
550 print >>out, " write_enabler:", base32.b2a(we)
552 _dump_secrets(u.get_storage_index(), secret, nodeid, out)
553 elif isinstance(u, uri.ReadonlyMDMFFileURI):
555 print >>out, "MDMF Read-only URI:"
556 print >>out, " readkey:", base32.b2a(u.readkey)
557 print >>out, " storage index:", si_b2a(u.get_storage_index())
558 print >>out, " fingerprint:", base32.b2a(u.fingerprint)
559 elif isinstance(u, uri.MDMFVerifierURI):
561 print >>out, "MDMF Verifier URI:"
562 print >>out, " storage index:", si_b2a(u.get_storage_index())
563 print >>out, " fingerprint:", base32.b2a(u.fingerprint)
566 elif isinstance(u, uri.ImmutableDirectoryURI): # CHK-based directory
568 print >>out, "CHK Directory URI:"
569 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
570 elif isinstance(u, uri.ImmutableDirectoryURIVerifier):
572 print >>out, "CHK Directory Verifier URI:"
573 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
575 elif isinstance(u, uri.DirectoryURI): # SDMF-based directory
577 print >>out, "Directory Writeable URI:"
578 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
579 elif isinstance(u, uri.ReadonlyDirectoryURI):
581 print >>out, "Directory Read-only URI:"
582 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
583 elif isinstance(u, uri.DirectoryURIVerifier):
585 print >>out, "Directory Verifier URI:"
586 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
588 elif isinstance(u, uri.MDMFDirectoryURI): # MDMF-based directory
590 print >>out, "Directory Writeable URI:"
591 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
592 elif isinstance(u, uri.ReadonlyMDMFDirectoryURI):
594 print >>out, "Directory Read-only URI:"
595 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
596 elif isinstance(u, uri.MDMFDirectoryURIVerifier):
598 print >>out, "Directory Verifier URI:"
599 dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
602 print >>out, "unknown cap type"
604 class FindSharesOptions(BaseOptions):
605 def getSynopsis(self):
606 return "Usage: tahoe [global-options] debug find-shares STORAGE_INDEX NODEDIRS.."
608 def parseArgs(self, storage_index_s, *nodedirs):
609 from allmydata.util.encodingutil import argv_to_abspath
610 self.si_s = storage_index_s
611 self.nodedirs = map(argv_to_abspath, nodedirs)
614 Locate all shares for the given storage index. This command looks through one
615 or more node directories to find the shares. It returns a list of filenames,
616 one per line, for each share file found.
618 tahoe debug find-shares 4vozh77tsrw7mdhnj7qvp5ky74 testgrid/node-*
620 It may be useful during testing, when running a test grid in which all the
621 nodes are on a local disk. The share files thus located can be counted,
622 examined (with dump-share), or corrupted/deleted to test checker/repairer.
625 def find_shares(options):
626 """Given a storage index and a list of node directories, emit a list of
627 all matching shares to stdout, one per line. For example:
629 find-shares.py 44kai1tui348689nrw8fjegc8c ~/testnet/node-*
633 /home/warner/testnet/node-1/storage/shares/44k/44kai1tui348689nrw8fjegc8c/5
634 /home/warner/testnet/node-1/storage/shares/44k/44kai1tui348689nrw8fjegc8c/9
635 /home/warner/testnet/node-2/storage/shares/44k/44kai1tui348689nrw8fjegc8c/2
637 from allmydata.storage.server import si_a2b, storage_index_to_dir
638 from allmydata.util.encodingutil import listdir_unicode, quote_local_unicode_path
641 sharedir = storage_index_to_dir(si_a2b(options.si_s))
642 for d in options.nodedirs:
643 d = os.path.join(d, "storage", "shares", sharedir)
644 if os.path.exists(d):
645 for shnum in listdir_unicode(d):
646 print >>out, quote_local_unicode_path(os.path.join(d, shnum), quotemarks=False)
651 class CatalogSharesOptions(BaseOptions):
652 def parseArgs(self, *nodedirs):
653 from allmydata.util.encodingutil import argv_to_abspath
654 self.nodedirs = map(argv_to_abspath, nodedirs)
656 raise usage.UsageError("must specify at least one node directory")
658 def getSynopsis(self):
659 return "Usage: tahoe [global-options] debug catalog-shares NODEDIRS.."
662 Locate all shares in the given node directories, and emit a one-line summary
663 of each share. Run it like this:
665 tahoe debug catalog-shares testgrid/node-* >allshares.txt
667 The lines it emits will look like the following:
669 CHK $SI $k/$N $filesize $UEB_hash $expiration $abspath_sharefile
670 SDMF $SI $k/$N $filesize $seqnum/$roothash $expiration $abspath_sharefile
671 UNKNOWN $abspath_sharefile
673 This command can be used to build up a catalog of shares from many storage
674 servers and then sort the results to compare all shares for the same file. If
675 you see shares with the same SI but different parameters/filesize/UEB_hash,
676 then something is wrong. The misc/find-share/anomalies.py script may be
680 def call(c, *args, **kwargs):
681 # take advantage of the fact that ImmediateReadBucketProxy returns
682 # Deferreds that are already fired
685 d = defer.maybeDeferred(c, *args, **kwargs)
686 d.addCallbacks(results.append, failures.append)
688 failures[0].raiseException()
691 def describe_share(abs_sharefile, si_s, shnum_s, now, out):
692 from allmydata import uri
693 from allmydata.storage.mutable import MutableShareFile
694 from allmydata.storage.immutable import ShareFile
695 from allmydata.mutable.layout import unpack_share
696 from allmydata.mutable.common import NeedMoreDataError
697 from allmydata.immutable.layout import ReadBucketProxy
698 from allmydata.util import base32
699 from allmydata.util.encodingutil import quote_output
702 f = open(abs_sharefile, "rb")
705 if prefix == MutableShareFile.MAGIC:
707 m = MutableShareFile(abs_sharefile)
708 WE, nodeid = m._read_write_enabler_and_nodeid(f)
709 data_length = m._read_data_length(f)
710 expiration_time = min( [lease.expiration_time
711 for (i,lease) in m._enumerate_leases(f)] )
712 expiration = max(0, expiration_time - now)
714 share_type = "unknown"
715 f.seek(m.DATA_OFFSET)
717 if version == "\x00":
718 # this slot contains an SMDF share
720 elif version == "\x01":
723 if share_type == "SDMF":
724 f.seek(m.DATA_OFFSET)
725 data = f.read(min(data_length, 2000))
728 pieces = unpack_share(data)
729 except NeedMoreDataError, e:
730 # retry once with the larger size
731 size = e.needed_bytes
732 f.seek(m.DATA_OFFSET)
733 data = f.read(min(data_length, size))
734 pieces = unpack_share(data)
735 (seqnum, root_hash, IV, k, N, segsize, datalen,
736 pubkey, signature, share_hash_chain, block_hash_tree,
737 share_data, enc_privkey) = pieces
739 print >>out, "SDMF %s %d/%d %d #%d:%s %d %s" % \
740 (si_s, k, N, datalen,
741 seqnum, base32.b2a(root_hash),
742 expiration, quote_output(abs_sharefile))
743 elif share_type == "MDMF":
744 from allmydata.mutable.layout import MDMFSlotReadProxy
746 # TODO: factor this out with dump_MDMF_share()
747 class ShareDumper(MDMFSlotReadProxy):
748 def _read(self, readvs, force_remote=False, queue=False):
750 for (where,length) in readvs:
751 f.seek(m.DATA_OFFSET+where)
752 data.append(f.read(length))
753 return defer.succeed({fake_shnum: data})
755 p = ShareDumper(None, "fake-si", fake_shnum)
758 # these methods return Deferreds, but we happen to know that
759 # they run synchronously when not actually talking to a
762 d.addCallback(stash.append)
765 verinfo = extract(p.get_verinfo)
766 (seqnum, root_hash, salt_to_use, segsize, datalen, k, N, prefix,
768 print >>out, "MDMF %s %d/%d %d #%d:%s %d %s" % \
769 (si_s, k, N, datalen,
770 seqnum, base32.b2a(root_hash),
771 expiration, quote_output(abs_sharefile))
773 print >>out, "UNKNOWN mutable %s" % quote_output(abs_sharefile)
775 elif struct.unpack(">L", prefix[:4]) == (1,):
778 class ImmediateReadBucketProxy(ReadBucketProxy):
779 def __init__(self, sf):
781 ReadBucketProxy.__init__(self, None, None, "")
783 return "<ImmediateReadBucketProxy>"
784 def _read(self, offset, size):
785 return defer.succeed(sf.read_share_data(offset, size))
787 # use a ReadBucketProxy to parse the bucket and find the uri extension
788 sf = ShareFile(abs_sharefile)
789 bp = ImmediateReadBucketProxy(sf)
791 expiration_time = min( [lease.expiration_time
792 for lease in sf.get_leases()] )
793 expiration = max(0, expiration_time - now)
795 UEB_data = call(bp.get_uri_extension)
796 unpacked = uri.unpack_extension_readable(UEB_data)
798 k = unpacked["needed_shares"]
799 N = unpacked["total_shares"]
800 filesize = unpacked["size"]
801 ueb_hash = unpacked["UEB_hash"]
803 print >>out, "CHK %s %d/%d %d %s %d %s" % (si_s, k, N, filesize,
804 ueb_hash, expiration,
805 quote_output(abs_sharefile))
808 print >>out, "UNKNOWN really-unknown %s" % quote_output(abs_sharefile)
812 def catalog_shares(options):
813 from allmydata.util.encodingutil import listdir_unicode, quote_output
818 for d in options.nodedirs:
819 d = os.path.join(d, "storage", "shares")
821 abbrevs = listdir_unicode(d)
822 except EnvironmentError:
823 # ignore nodes that have storage turned off altogether
826 for abbrevdir in sorted(abbrevs):
827 if abbrevdir == "incoming":
829 abbrevdir = os.path.join(d, abbrevdir)
830 # this tool may get run against bad disks, so we can't assume
831 # that listdir_unicode will always succeed. Try to catalog as much
834 sharedirs = listdir_unicode(abbrevdir)
835 for si_s in sorted(sharedirs):
836 si_dir = os.path.join(abbrevdir, si_s)
837 catalog_shares_one_abbrevdir(si_s, si_dir, now, out,err)
839 print >>err, "Error processing %s" % quote_output(abbrevdir)
840 failure.Failure().printTraceback(err)
850 def catalog_shares_one_abbrevdir(si_s, si_dir, now, out, err):
851 from allmydata.util.encodingutil import listdir_unicode, quote_output
854 for shnum_s in sorted(listdir_unicode(si_dir), key=_as_number):
855 abs_sharefile = os.path.join(si_dir, shnum_s)
856 assert os.path.isfile(abs_sharefile)
858 describe_share(abs_sharefile, si_s, shnum_s, now,
861 print >>err, "Error processing %s" % quote_output(abs_sharefile)
862 failure.Failure().printTraceback(err)
864 print >>err, "Error processing %s" % quote_output(si_dir)
865 failure.Failure().printTraceback(err)
867 class CorruptShareOptions(BaseOptions):
868 def getSynopsis(self):
869 return "Usage: tahoe [global-options] debug corrupt-share SHARE_FILENAME"
872 ["offset", "o", "block-random", "Specify which bit to flip."],
876 Corrupt the given share by flipping a bit. This will cause a
877 verifying/downloading client to log an integrity-check failure incident, and
878 downloads will proceed with a different share.
880 The --offset parameter controls which bit should be flipped. The default is
881 to flip a single random bit of the block data.
883 tahoe debug corrupt-share testgrid/node-3/storage/shares/4v/4vozh77tsrw7mdhnj7qvp5ky74/0
885 Obviously, this command should not be used in normal operation.
887 def parseArgs(self, filename):
888 self['filename'] = filename
890 def corrupt_share(options):
892 from allmydata.storage.mutable import MutableShareFile
893 from allmydata.storage.immutable import ShareFile
894 from allmydata.mutable.layout import unpack_header
895 from allmydata.immutable.layout import ReadBucketProxy
897 fn = options['filename']
898 assert options["offset"] == "block-random", "other offsets not implemented"
899 # first, what kind of share is it?
901 def flip_bit(start, end):
902 offset = random.randrange(start, end)
903 bit = random.randrange(0, 8)
904 print >>out, "[%d..%d): %d.b%d" % (start, end, offset, bit)
908 d = chr(ord(d) ^ 0x01)
916 if prefix == MutableShareFile.MAGIC:
918 m = MutableShareFile(fn)
920 f.seek(m.DATA_OFFSET)
922 # make sure this slot contains an SMDF share
923 assert data[0] == "\x00", "non-SDMF mutable shares not supported"
926 (version, ig_seqnum, ig_roothash, ig_IV, ig_k, ig_N, ig_segsize,
927 ig_datalen, offsets) = unpack_header(data)
929 assert version == 0, "we only handle v0 SDMF files"
930 start = m.DATA_OFFSET + offsets["share_data"]
931 end = m.DATA_OFFSET + offsets["enc_privkey"]
934 # otherwise assume it's immutable
936 bp = ReadBucketProxy(None, None, '')
937 offsets = bp._parse_offsets(f.read_share_data(0, 0x24))
938 start = f._data_offset + offsets["data"]
939 end = f._data_offset + offsets["plaintext_hash_tree"]
944 class ReplOptions(BaseOptions):
945 def getSynopsis(self):
946 return "Usage: tahoe [global-options] debug repl"
950 return code.interact()
953 DEFAULT_TESTSUITE = 'allmydata'
955 class TrialOptions(twisted_trial.Options):
956 def getSynopsis(self):
957 return "Usage: tahoe [global-options] debug trial [options] [[file|package|module|TestCase|testmethod]...]"
959 def parseOptions(self, all_subargs, *a, **kw):
960 self.trial_args = list(all_subargs)
962 # any output from the option parsing will be printed twice, but that's harmless
963 return twisted_trial.Options.parseOptions(self, all_subargs, *a, **kw)
965 def parseArgs(self, *nonoption_args):
966 if not nonoption_args:
967 self.trial_args.append(DEFAULT_TESTSUITE)
969 longdesc = twisted_trial.Options.longdesc + "\n\n" + (
970 "The 'tahoe debug trial' command uses the correct imports for this "
971 "instance of Tahoe-LAFS. The default test suite is '%s'."
975 sys.argv = ['trial'] + config.trial_args
977 from allmydata._version import full_version
978 if full_version.endswith("-dirty"):
980 print >>sys.stderr, "WARNING: the source tree has been modified since the last commit."
981 print >>sys.stderr, "(It is usually preferable to commit, then test, then amend the commit(s)"
982 print >>sys.stderr, "if the tests fail.)"
985 # This does not return.
989 def fixOptionsClass( (subcmd, shortcut, OptionsClass, desc) ):
990 class FixedOptionsClass(OptionsClass):
991 def getSynopsis(self):
992 t = OptionsClass.getSynopsis(self)
993 i = t.find("Usage: flogtool ")
995 return "Usage: tahoe [global-options] debug flogtool " + t[i+len("Usage: flogtool "):]
997 return "Usage: tahoe [global-options] debug flogtool %s [options]" % (subcmd,)
998 return (subcmd, shortcut, FixedOptionsClass, desc)
1000 class FlogtoolOptions(foolscap_cli.Options):
1002 super(FlogtoolOptions, self).__init__()
1003 self.subCommands = map(fixOptionsClass, self.subCommands)
1005 def getSynopsis(self):
1006 return "Usage: tahoe [global-options] debug flogtool COMMAND [flogtool-options]"
1008 def parseOptions(self, all_subargs, *a, **kw):
1009 self.flogtool_args = list(all_subargs)
1010 return super(FlogtoolOptions, self).parseOptions(self.flogtool_args, *a, **kw)
1012 def getUsage(self, width=None):
1013 t = super(FlogtoolOptions, self).getUsage(width)
1015 The 'tahoe debug flogtool' command uses the correct imports for this instance
1018 Please run 'tahoe debug flogtool COMMAND --help' for more details on each
1027 def flogtool(config):
1028 sys.argv = ['flogtool'] + config.flogtool_args
1029 return foolscap_cli.run_flogtool()
1032 class DebugCommand(BaseOptions):
1034 ["dump-share", None, DumpOptions,
1035 "Unpack and display the contents of a share (uri_extension and leases)."],
1036 ["dump-cap", None, DumpCapOptions, "Unpack a read-cap or write-cap."],
1037 ["find-shares", None, FindSharesOptions, "Locate sharefiles in node dirs."],
1038 ["catalog-shares", None, CatalogSharesOptions, "Describe all shares in node dirs."],
1039 ["corrupt-share", None, CorruptShareOptions, "Corrupt a share by flipping a bit."],
1040 ["repl", None, ReplOptions, "Open a Python interpreter."],
1041 ["trial", None, TrialOptions, "Run tests using Twisted Trial with the right imports."],
1042 ["flogtool", None, FlogtoolOptions, "Utilities to access log files."],
1044 def postOptions(self):
1045 if not hasattr(self, 'subOptions'):
1046 raise usage.UsageError("must specify a subcommand")
1047 synopsis = "COMMAND"
1049 def getUsage(self, width=None):
1050 t = BaseOptions.getUsage(self, width)
1053 Please run e.g. 'tahoe debug dump-share --help' for more details on each
1056 # See ticket #1441 for why we print different information when
1057 # run via /usr/bin/tahoe. Note that argv[0] is the full path.
1058 if sys.argv[0] == '/usr/bin/tahoe':
1060 To get branch coverage for the Tahoe test suite (on the installed copy of
1061 Tahoe), install the 'python-coverage' package and then use:
1063 python-coverage run --branch /usr/bin/tahoe debug trial
1067 Another debugging feature is that bin%stahoe allows executing an arbitrary
1068 "runner" command (typically an installed Python script, such as 'coverage'),
1069 with the Tahoe libraries on the PYTHONPATH. The runner command name is
1070 prefixed with '@', and any occurrences of '@tahoe' in its arguments are
1071 replaced by the full path to the tahoe script.
1073 For example, if 'coverage' is installed and on the PATH, you can use:
1075 bin%stahoe @coverage run --branch @tahoe debug trial
1077 to get branch coverage for the Tahoe test suite. Or, to run python with
1078 the -3 option that warns about Python 3 incompatibilities:
1080 bin%stahoe @python -3 @tahoe command [options]
1081 """ % (os.sep, os.sep, os.sep)
1085 "dump-share": dump_share,
1086 "dump-cap": dump_cap,
1087 "find-shares": find_shares,
1088 "catalog-shares": catalog_shares,
1089 "corrupt-share": corrupt_share,
1092 "flogtool": flogtool,
1096 def do_debug(options):
1097 so = options.subOptions
1098 so.stdout = options.stdout
1099 so.stderr = options.stderr
1100 f = subDispatch[options.subCommand]
1105 ["debug", None, DebugCommand, "debug subcommands: use 'tahoe debug' for a list."],