source file: /home/buildslave/tahoe/edgy/build/src/allmydata/scripts/debug.py
file stats: 496 lines, 450 executed: 90.7% covered
1.
2. # do not import any allmydata modules at this level. Do that from inside
3. # individual functions instead.
4. import struct, time, os
5. from twisted.python import usage
6.
7. class DumpOptions(usage.Options):
8. def getSynopsis(self):
9. return "Usage: tahoe debug dump-share SHARE_FILENAME"
10.
11. optFlags = [
12. ["offsets", None, "Display a table of section offsets"],
13. ]
14.
15. def getUsage(self, width=None):
16. t = usage.Options.getUsage(self, width)
17. t += """
18. Print lots of information about the given share, by parsing the share's
19. contents. This includes share type, lease information, encoding parameters,
20. hash-tree roots, public keys, and segment sizes. This command also emits a
21. verify-cap for the file that uses the share.
22.
23. tahoe debug dump-share testgrid/node-3/storage/shares/4v/4vozh77tsrw7mdhnj7qvp5ky74/0
24.
25. """
26. return t
27.
28. def parseArgs(self, filename):
29. self['filename'] = filename
30.
31. def dump_share(options):
32. from allmydata import storage
33.
34. out = options.stdout
35.
36. # check the version, to see if we have a mutable or immutable share
37. print >>out, "share filename: %s" % options['filename']
38.
39. f = open(options['filename'], "rb")
40. prefix = f.read(32)
41. f.close()
42. if prefix == storage.MutableShareFile.MAGIC:
43. return dump_mutable_share(options)
44. # otherwise assume it's immutable
45. return dump_immutable_share(options)
46.
47. def dump_immutable_share(options):
48. from allmydata import uri, storage
49. from allmydata.util import base32
50.
51. out = options.stdout
52. f = storage.ShareFile(options['filename'])
53. # use a ReadBucketProxy to parse the bucket and find the uri extension
54. bp = storage.ReadBucketProxy(None)
55. offsets = bp._parse_offsets(f.read_share_data(0, 0x24))
56. seek = offsets['uri_extension']
57. length = struct.unpack(">L", f.read_share_data(seek, 4))[0]
58. seek += 4
59. UEB_data = f.read_share_data(seek, length)
60.
61. unpacked = uri.unpack_extension_readable(UEB_data)
62. keys1 = ("size", "num_segments", "segment_size",
63. "needed_shares", "total_shares")
64. keys2 = ("codec_name", "codec_params", "tail_codec_params")
65. keys3 = ("plaintext_hash", "plaintext_root_hash",
66. "crypttext_hash", "crypttext_root_hash",
67. "share_root_hash", "UEB_hash")
68. display_keys = {"size": "file_size"}
69. for k in keys1:
70. if k in unpacked:
71. dk = display_keys.get(k, k)
72. print >>out, "%20s: %s" % (dk, unpacked[k])
73. print >>out
74. for k in keys2:
75. if k in unpacked:
76. dk = display_keys.get(k, k)
77. print >>out, "%20s: %s" % (dk, unpacked[k])
78. print >>out
79. for k in keys3:
80. if k in unpacked:
81. dk = display_keys.get(k, k)
82. print >>out, "%20s: %s" % (dk, unpacked[k])
83.
84. leftover = set(unpacked.keys()) - set(keys1 + keys2 + keys3)
85. if leftover:
86. print >>out
87. print >>out, "LEFTOVER:"
88. for k in sorted(leftover):
89. print >>out, "%20s: %s" % (k, unpacked[k])
90.
91. # the storage index isn't stored in the share itself, so we depend upon
92. # knowing the parent directory name to get it
93. pieces = options['filename'].split(os.sep)
94. if len(pieces) >= 2 and base32.could_be_base32_encoded(pieces[-2]):
95. storage_index = base32.a2b(pieces[-2])
96. uri_extension_hash = base32.a2b(unpacked["UEB_hash"])
97. u = uri.CHKFileVerifierURI(storage_index, uri_extension_hash,
98. unpacked["needed_shares"],
99. unpacked["total_shares"], unpacked["size"])
100. verify_cap = u.to_string()
101. print >>out, "%20s: %s" % ("verify-cap", verify_cap)
102.
103. sizes = {}
104. sizes['data'] = bp._data_size
105. sizes['validation'] = (offsets['uri_extension'] -
106. offsets['plaintext_hash_tree'])
107. sizes['uri-extension'] = len(UEB_data)
108. print >>out
109. print >>out, " Size of data within the share:"
110. for k in sorted(sizes):
111. print >>out, "%20s: %s" % (k, sizes[k])
112.
113. if options['offsets']:
114. print >>out
115. print >>out, " Section Offsets:"
116. print >>out, "%20s: %s" % ("share data", f._data_offset)
117. for k in ["data", "plaintext_hash_tree", "crypttext_hash_tree",
118. "block_hashes", "share_hashes", "uri_extension"]:
119. name = {"data": "block data"}.get(k,k)
120. offset = f._data_offset + offsets[k]
121. print >>out, " %20s: %s (0x%x)" % (name, offset, offset)
122. print >>out, "%20s: %s" % ("leases", f._lease_offset)
123.
124.
125. # display lease information too
126. print >>out
127. leases = list(f.iter_leases())
128. if leases:
129. for i,lease in enumerate(leases):
130. when = format_expiration_time(lease.expiration_time)
131. print >>out, " Lease #%d: owner=%d, expire in %s" \
132. % (i, lease.owner_num, when)
133. else:
134. print >>out, " No leases."
135.
136. print >>out
137. return 0
138.
139. def format_expiration_time(expiration_time):
140. now = time.time()
141. remains = expiration_time - now
142. when = "%ds" % remains
143. if remains > 24*3600:
144. when += " (%d days)" % (remains / (24*3600))
145. elif remains > 3600:
146. when += " (%d hours)" % (remains / 3600)
147. return when
148.
149.
150. def dump_mutable_share(options):
151. from allmydata import storage
152. from allmydata.util import base32, idlib
153. out = options.stdout
154. m = storage.MutableShareFile(options['filename'])
155. f = open(options['filename'], "rb")
156. WE, nodeid = m._read_write_enabler_and_nodeid(f)
157. num_extra_leases = m._read_num_extra_leases(f)
158. data_length = m._read_data_length(f)
159. extra_lease_offset = m._read_extra_lease_offset(f)
160. container_size = extra_lease_offset - m.DATA_OFFSET
161. leases = list(m._enumerate_leases(f))
162.
163. share_type = "unknown"
164. f.seek(m.DATA_OFFSET)
165. if f.read(1) == "\x00":
166. # this slot contains an SMDF share
167. share_type = "SDMF"
168. f.close()
169.
170. print >>out
171. print >>out, "Mutable slot found:"
172. print >>out, " share_type: %s" % share_type
173. print >>out, " write_enabler: %s" % base32.b2a(WE)
174. print >>out, " WE for nodeid: %s" % idlib.nodeid_b2a(nodeid)
175. print >>out, " num_extra_leases: %d" % num_extra_leases
176. print >>out, " container_size: %d" % container_size
177. print >>out, " data_length: %d" % data_length
178. if leases:
179. for (leasenum, lease) in leases:
180. print >>out
181. print >>out, " Lease #%d:" % leasenum
182. print >>out, " ownerid: %d" % lease.owner_num
183. when = format_expiration_time(lease.expiration_time)
184. print >>out, " expires in %s" % when
185. print >>out, " renew_secret: %s" % base32.b2a(lease.renew_secret)
186. print >>out, " cancel_secret: %s" % base32.b2a(lease.cancel_secret)
187. print >>out, " secrets are for nodeid: %s" % idlib.nodeid_b2a(lease.nodeid)
188. else:
189. print >>out, "No leases."
190. print >>out
191.
192. if share_type == "SDMF":
193. dump_SDMF_share(m, data_length, options)
194.
195. return 0
196.
197. def dump_SDMF_share(m, length, options):
198. from allmydata.mutable.layout import unpack_share, unpack_header
199. from allmydata.mutable.common import NeedMoreDataError
200. from allmydata.util import base32, hashutil
201. from allmydata.uri import SSKVerifierURI
202.
203. offset = m.DATA_OFFSET
204.
205. out = options.stdout
206.
207. f = open(options['filename'], "rb")
208. f.seek(offset)
209. data = f.read(min(length, 2000))
210. f.close()
211.
212. try:
213. pieces = unpack_share(data)
214. except NeedMoreDataError, e:
215. # retry once with the larger size
216. size = e.needed_bytes
217. f = open(options['filename'], "rb")
218. f.seek(offset)
219. data = f.read(min(length, size))
220. f.close()
221. pieces = unpack_share(data)
222.
223. (seqnum, root_hash, IV, k, N, segsize, datalen,
224. pubkey, signature, share_hash_chain, block_hash_tree,
225. share_data, enc_privkey) = pieces
226. (ig_version, ig_seqnum, ig_roothash, ig_IV, ig_k, ig_N, ig_segsize,
227. ig_datalen, offsets) = unpack_header(data)
228.
229. print >>out, " SDMF contents:"
230. print >>out, " seqnum: %d" % seqnum
231. print >>out, " root_hash: %s" % base32.b2a(root_hash)
232. print >>out, " IV: %s" % base32.b2a(IV)
233. print >>out, " required_shares: %d" % k
234. print >>out, " total_shares: %d" % N
235. print >>out, " segsize: %d" % segsize
236. print >>out, " datalen: %d" % datalen
237. print >>out, " enc_privkey: %d bytes" % len(enc_privkey)
238. print >>out, " pubkey: %d bytes" % len(pubkey)
239. print >>out, " signature: %d bytes" % len(signature)
240. share_hash_ids = ",".join(sorted([str(hid)
241. for hid in share_hash_chain.keys()]))
242. print >>out, " share_hash_chain: %s" % share_hash_ids
243. print >>out, " block_hash_tree: %d nodes" % len(block_hash_tree)
244.
245. # the storage index isn't stored in the share itself, so we depend upon
246. # knowing the parent directory name to get it
247. pieces = options['filename'].split(os.sep)
248. if len(pieces) >= 2 and base32.could_be_base32_encoded(pieces[-2]):
249. storage_index = base32.a2b(pieces[-2])
250. fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey)
251. u = SSKVerifierURI(storage_index, fingerprint)
252. verify_cap = u.to_string()
253. print >>out, " verify-cap:", verify_cap
254.
255. if options['offsets']:
256. # NOTE: this offset-calculation code is fragile, and needs to be
257. # merged with MutableShareFile's internals.
258. print >>out
259. print >>out, " Section Offsets:"
260. def printoffset(name, value, shift=0):
261. print >>out, "%s%20s: %s (0x%x)" % (" "*shift, name, value, value)
262. printoffset("first lease", m.HEADER_SIZE)
263. printoffset("share data", m.DATA_OFFSET)
264. o_seqnum = m.DATA_OFFSET + struct.calcsize(">B")
265. printoffset("seqnum", o_seqnum, 2)
266. o_root_hash = m.DATA_OFFSET + struct.calcsize(">BQ")
267. printoffset("root_hash", o_root_hash, 2)
268. for k in ["signature", "share_hash_chain", "block_hash_tree",
269. "share_data",
270. "enc_privkey", "EOF"]:
271. name = {"share_data": "block data",
272. "EOF": "end of share data"}.get(k,k)
273. offset = m.DATA_OFFSET + offsets[k]
274. printoffset(name, offset, 2)
275. f = open(options['filename'], "rb")
276. printoffset("extra leases", m._read_extra_lease_offset(f) + 4)
277. f.close()
278.
279. print >>out
280.
281.
282.
283. class DumpCapOptions(usage.Options):
284. def getSynopsis(self):
285. return "Usage: tahoe debug dump-cap [options] FILECAP"
286. optParameters = [
287. ["nodeid", "n",
288. None, "storage server nodeid (ascii), to construct WE and secrets."],
289. ["client-secret", "c", None,
290. "client's base secret (ascii), to construct secrets"],
291. ["client-dir", "d", None,
292. "client's base directory, from which a -c secret will be read"],
293. ]
294. def parseArgs(self, cap):
295. self.cap = cap
296.
297. def getUsage(self, width=None):
298. t = usage.Options.getUsage(self, width)
299. t += """
300. Print information about the given cap-string (aka: URI, file-cap, dir-cap,
301. read-cap, write-cap). The URI string is parsed and unpacked. This prints the
302. type of the cap, its storage index, and any derived keys.
303.
304. tahoe debug dump-cap URI:SSK-Verifier:4vozh77tsrw7mdhnj7qvp5ky74:q7f3dwz76sjys4kqfdt3ocur2pay3a6rftnkqmi2uxu3vqsdsofq
305.
306. This may be useful to determine if a read-cap and a write-cap refer to the
307. same time, or to extract the storage-index from a file-cap (to then use with
308. find-shares)
309.
310. If additional information is provided (storage server nodeid and/or client
311. base secret), this command will compute the shared secrets used for the
312. write-enabler and for lease-renewal.
313. """
314. return t
315.
316.
317. def dump_cap(options):
318. from allmydata import uri
319. from allmydata.util import base32
320. from base64 import b32decode
321. import urlparse, urllib
322.
323. out = options.stdout
324. cap = options.cap
325. nodeid = None
326. if options['nodeid']:
327. nodeid = b32decode(options['nodeid'].upper())
328. secret = None
329. if options['client-secret']:
330. secret = base32.a2b(options['client-secret'])
331. elif options['client-dir']:
332. secretfile = os.path.join(options['client-dir'], "private", "secret")
333. try:
334. secret = base32.a2b(open(secretfile, "r").read().strip())
335. except EnvironmentError:
336. pass
337.
338. if cap.startswith("http"):
339. scheme, netloc, path, params, query, fragment = urlparse.urlparse(cap)
340. assert path.startswith("/uri/")
341. cap = urllib.unquote(path[len("/uri/"):])
342.
343. u = uri.from_string(cap)
344.
345. print >>out
346. dump_uri_instance(u, nodeid, secret, out)
347.
348. def _dump_secrets(storage_index, secret, nodeid, out):
349. from allmydata.util import hashutil
350. from allmydata.util import base32
351.
352. if secret:
353. crs = hashutil.my_renewal_secret_hash(secret)
354. print >>out, " client renewal secret:", base32.b2a(crs)
355. frs = hashutil.file_renewal_secret_hash(crs, storage_index)
356. print >>out, " file renewal secret:", base32.b2a(frs)
357. if nodeid:
358. renew = hashutil.bucket_renewal_secret_hash(frs, nodeid)
359. print >>out, " lease renewal secret:", base32.b2a(renew)
360. ccs = hashutil.my_cancel_secret_hash(secret)
361. print >>out, " client cancel secret:", base32.b2a(ccs)
362. fcs = hashutil.file_cancel_secret_hash(ccs, storage_index)
363. print >>out, " file cancel secret:", base32.b2a(fcs)
364. if nodeid:
365. cancel = hashutil.bucket_cancel_secret_hash(fcs, nodeid)
366. print >>out, " lease cancel secret:", base32.b2a(cancel)
367.
368. def dump_uri_instance(u, nodeid, secret, out, show_header=True):
369. from allmydata import storage, uri
370. from allmydata.util import base32, hashutil
371.
372. if isinstance(u, uri.CHKFileURI):
373. if show_header:
374. print >>out, "CHK File:"
375. print >>out, " key:", base32.b2a(u.key)
376. print >>out, " UEB hash:", base32.b2a(u.uri_extension_hash)
377. print >>out, " size:", u.size
378. print >>out, " k/N: %d/%d" % (u.needed_shares, u.total_shares)
379. print >>out, " storage index:", storage.si_b2a(u.storage_index)
380. _dump_secrets(u.storage_index, secret, nodeid, out)
381. elif isinstance(u, uri.CHKFileVerifierURI):
382. if show_header:
383. print >>out, "CHK Verifier URI:"
384. print >>out, " UEB hash:", base32.b2a(u.uri_extension_hash)
385. print >>out, " size:", u.size
386. print >>out, " k/N: %d/%d" % (u.needed_shares, u.total_shares)
387. print >>out, " storage index:", storage.si_b2a(u.storage_index)
388.
389. elif isinstance(u, uri.LiteralFileURI):
390. if show_header:
391. print >>out, "Literal File URI:"
392. print >>out, " data:", u.data
393.
394. elif isinstance(u, uri.WriteableSSKFileURI):
395. if show_header:
396. print >>out, "SSK Writeable URI:"
397. print >>out, " writekey:", base32.b2a(u.writekey)
398. print >>out, " readkey:", base32.b2a(u.readkey)
399. print >>out, " storage index:", storage.si_b2a(u.storage_index)
400. print >>out, " fingerprint:", base32.b2a(u.fingerprint)
401. print >>out
402. if nodeid:
403. we = hashutil.ssk_write_enabler_hash(u.writekey, nodeid)
404. print >>out, " write_enabler:", base32.b2a(we)
405. print >>out
406. _dump_secrets(u.storage_index, secret, nodeid, out)
407.
408. elif isinstance(u, uri.ReadonlySSKFileURI):
409. if show_header:
410. print >>out, "SSK Read-only URI:"
411. print >>out, " readkey:", base32.b2a(u.readkey)
412. print >>out, " storage index:", storage.si_b2a(u.storage_index)
413. print >>out, " fingerprint:", base32.b2a(u.fingerprint)
414. elif isinstance(u, uri.SSKVerifierURI):
415. if show_header:
416. print >>out, "SSK Verifier URI:"
417. print >>out, " storage index:", storage.si_b2a(u.storage_index)
418. print >>out, " fingerprint:", base32.b2a(u.fingerprint)
419.
420. elif isinstance(u, uri.NewDirectoryURI):
421. if show_header:
422. print >>out, "Directory Writeable URI:"
423. dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
424. elif isinstance(u, uri.ReadonlyNewDirectoryURI):
425. if show_header:
426. print >>out, "Directory Read-only URI:"
427. dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
428. elif isinstance(u, uri.NewDirectoryURIVerifier):
429. if show_header:
430. print >>out, "Directory Verifier URI:"
431. dump_uri_instance(u._filenode_uri, nodeid, secret, out, False)
432. else:
433. print >>out, "unknown cap type"
434.
435. class FindSharesOptions(usage.Options):
436. def getSynopsis(self):
437. return "Usage: tahoe debug find-shares STORAGE_INDEX NODEDIRS.."
438. def parseArgs(self, storage_index_s, *nodedirs):
439. self.si_s = storage_index_s
440. self.nodedirs = nodedirs
441. def getUsage(self, width=None):
442. t = usage.Options.getUsage(self, width)
443. t += """
444. Locate all shares for the given storage index. This command looks through one
445. or more node directories to find the shares. It returns a list of filenames,
446. one per line, for each share file found.
447.
448. tahoe debug find-shares 4vozh77tsrw7mdhnj7qvp5ky74 testgrid/node-*
449.
450. It may be useful during testing, when running a test grid in which all the
451. nodes are on a local disk. The share files thus located can be counted,
452. examined (with dump-share), or corrupted/deleted to test checker/repairer.
453. """
454. return t
455.
456. def find_shares(options):
457. """Given a storage index and a list of node directories, emit a list of
458. all matching shares to stdout, one per line. For example:
459.
460. find-shares.py 44kai1tui348689nrw8fjegc8c ~/testnet/node-*
461.
462. gives:
463.
464. /home/warner/testnet/node-1/storage/shares/44k/44kai1tui348689nrw8fjegc8c/5
465. /home/warner/testnet/node-1/storage/shares/44k/44kai1tui348689nrw8fjegc8c/9
466. /home/warner/testnet/node-2/storage/shares/44k/44kai1tui348689nrw8fjegc8c/2
467. """
468. from allmydata import storage
469.
470. out = options.stdout
471. sharedir = storage.storage_index_to_dir(storage.si_a2b(options.si_s))
472. for d in options.nodedirs:
473. d = os.path.join(os.path.expanduser(d), "storage/shares", sharedir)
474. if os.path.exists(d):
475. for shnum in os.listdir(d):
476. print >>out, os.path.join(d, shnum)
477.
478. return 0
479.
480.
481. class CatalogSharesOptions(usage.Options):
482. """
483.
484. """
485. def parseArgs(self, *nodedirs):
486. self.nodedirs = nodedirs
487. if not nodedirs:
488. raise usage.UsageError("must specify at least one node directory")
489.
490. def getSynopsis(self):
491. return "Usage: tahoe debug catalog-shares NODEDIRS.."
492.
493. def getUsage(self, width=None):
494. t = usage.Options.getUsage(self, width)
495. t += """
496. Locate all shares in the given node directories, and emit a one-line summary
497. of each share. Run it like this:
498.
499. tahoe debug catalog-shares testgrid/node-* >allshares.txt
500.
501. The lines it emits will look like the following:
502.
503. CHK $SI $k/$N $filesize $UEB_hash $expiration $abspath_sharefile
504. SDMF $SI $k/$N $filesize $seqnum/$roothash $expiration $abspath_sharefile
505. UNKNOWN $abspath_sharefile
506.
507. This command can be used to build up a catalog of shares from many storage
508. servers and then sort the results to compare all shares for the same file. If
509. you see shares with the same SI but different parameters/filesize/UEB_hash,
510. then something is wrong. The misc/find-share/anomalies.py script may be
511. useful for purpose.
512. """
513. return t
514.
515. def describe_share(abs_sharefile, si_s, shnum_s, now, out):
516. from allmydata import uri, storage
517. from allmydata.mutable.layout import unpack_share
518. from allmydata.mutable.common import NeedMoreDataError
519. from allmydata.util import base32
520. import struct
521.
522. f = open(abs_sharefile, "rb")
523. prefix = f.read(32)
524.
525. if prefix == storage.MutableShareFile.MAGIC:
526. # mutable share
527. m = storage.MutableShareFile(abs_sharefile)
528. WE, nodeid = m._read_write_enabler_and_nodeid(f)
529. num_extra_leases = m._read_num_extra_leases(f)
530. data_length = m._read_data_length(f)
531. extra_lease_offset = m._read_extra_lease_offset(f)
532. container_size = extra_lease_offset - m.DATA_OFFSET
533. leases = list(m._enumerate_leases(f))
534. expiration_time = min( [lease[1].expiration_time
535. for lease in leases] )
536. expiration = max(0, expiration_time - now)
537.
538. share_type = "unknown"
539. f.seek(m.DATA_OFFSET)
540. if f.read(1) == "\x00":
541. # this slot contains an SMDF share
542. share_type = "SDMF"
543.
544. if share_type == "SDMF":
545. f.seek(m.DATA_OFFSET)
546. data = f.read(min(data_length, 2000))
547.
548. try:
549. pieces = unpack_share(data)
550. except NeedMoreDataError, e:
551. # retry once with the larger size
552. size = e.needed_bytes
553. f.seek(m.DATA_OFFSET)
554. data = f.read(min(data_length, size))
555. pieces = unpack_share(data)
556. (seqnum, root_hash, IV, k, N, segsize, datalen,
557. pubkey, signature, share_hash_chain, block_hash_tree,
558. share_data, enc_privkey) = pieces
559.
560. print >>out, "SDMF %s %d/%d %d #%d:%s %d %s" % \
561. (si_s, k, N, datalen,
562. seqnum, base32.b2a(root_hash),
563. expiration, abs_sharefile)
564. else:
565. print >>out, "UNKNOWN mutable %s" % (abs_sharefile,)
566.
567. elif struct.unpack(">L", prefix[:4]) == (1,):
568. # immutable
569.
570. sf = storage.ShareFile(abs_sharefile)
571. # use a ReadBucketProxy to parse the bucket and find the uri extension
572. bp = storage.ReadBucketProxy(None)
573. offsets = bp._parse_offsets(sf.read_share_data(0, 0x24))
574. seek = offsets['uri_extension']
575. length = struct.unpack(">L", sf.read_share_data(seek, 4))[0]
576. seek += 4
577. UEB_data = sf.read_share_data(seek, length)
578. expiration_time = min( [lease.expiration_time
579. for lease in sf.iter_leases()] )
580. expiration = max(0, expiration_time - now)
581.
582. unpacked = uri.unpack_extension_readable(UEB_data)
583. k = unpacked["needed_shares"]
584. N = unpacked["total_shares"]
585. filesize = unpacked["size"]
586. ueb_hash = unpacked["UEB_hash"]
587.
588. print >>out, "CHK %s %d/%d %d %s %d %s" % (si_s, k, N, filesize,
589. ueb_hash, expiration,
590. abs_sharefile)
591.
592. else:
593. print >>out, "UNKNOWN really-unknown %s" % (abs_sharefile,)
594.
595. f.close()
596.
597.
598. def catalog_shares(options):
599. out = options.stdout
600. now = time.time()
601. for d in options.nodedirs:
602. d = os.path.join(os.path.expanduser(d), "storage/shares")
603. try:
604. abbrevs = os.listdir(d)
605. except EnvironmentError:
606. # ignore nodes that have storage turned off altogether
607. pass
608. else:
609. for abbrevdir in abbrevs:
610. if abbrevdir == "incoming":
611. continue
612. abbrevdir = os.path.join(d, abbrevdir)
613. for si_s in os.listdir(abbrevdir):
614. si_dir = os.path.join(abbrevdir, si_s)
615. for shnum_s in os.listdir(si_dir):
616. abs_sharefile = os.path.join(si_dir, shnum_s)
617. abs_sharefile = os.path.abspath(abs_sharefile)
618. assert os.path.isfile(abs_sharefile)
619. describe_share(abs_sharefile, si_s, shnum_s, now, out)
620. return 0
621.
622. class CorruptShareOptions(usage.Options):
623. def getSynopsis(self):
624. return "Usage: tahoe debug corrupt-share SHARE_FILENAME"
625.
626. optParameters = [
627. ["offset", "o", "block-random", "Which bit to flip."],
628. ]
629.
630. def getUsage(self, width=None):
631. t = usage.Options.getUsage(self, width)
632. t += """
633. Corrupt the given share by flipping a bit. This will cause a
634. verifying/downloading client to log an integrity-check failure incident, and
635. downloads will proceed with a different share.
636.
637. The --offset parameter controls which bit should be flipped. The default is
638. to flip a single random bit of the block data.
639.
640. tahoe debug corrupt-share testgrid/node-3/storage/shares/4v/4vozh77tsrw7mdhnj7qvp5ky74/0
641.
642. Obviously, this command should not be used in normal operation.
643. """
644. return t
645. def parseArgs(self, filename):
646. self['filename'] = filename
647.
648. def corrupt_share(options):
649. import random
650. from allmydata import storage
651. from allmydata.mutable.layout import unpack_header
652. out = options.stdout
653. fn = options['filename']
654. assert options["offset"] == "block-random", "other offsets not implemented"
655. # first, what kind of share is it?
656.
657. def flip_bit(start, end):
658. offset = random.randrange(start, end)
659. bit = random.randrange(0, 8)
660. print >>out, "[%d..%d): %d.b%d" % (start, end, offset, bit)
661. f = open(fn, "rb+")
662. f.seek(offset)
663. d = f.read(1)
664. d = chr(ord(d) ^ 0x01)
665. f.seek(offset)
666. f.write(d)
667. f.close()
668.
669. f = open(fn, "rb")
670. prefix = f.read(32)
671. f.close()
672. if prefix == storage.MutableShareFile.MAGIC:
673. # mutable
674. m = storage.MutableShareFile(fn)
675. f = open(fn, "rb")
676. f.seek(m.DATA_OFFSET)
677. data = f.read(2000)
678. # make sure this slot contains an SMDF share
679. assert data[0] == "\x00", "non-SDMF mutable shares not supported"
680. f.close()
681.
682. (version, ig_seqnum, ig_roothash, ig_IV, ig_k, ig_N, ig_segsize,
683. ig_datalen, offsets) = unpack_header(data)
684.
685. assert version == 0, "we only handle v0 SDMF files"
686. start = m.DATA_OFFSET + offsets["share_data"]
687. end = m.DATA_OFFSET + offsets["enc_privkey"]
688. flip_bit(start, end)
689. else:
690. # otherwise assume it's immutable
691. f = storage.ShareFile(fn)
692. bp = storage.ReadBucketProxy(None)
693. offsets = bp._parse_offsets(f.read_share_data(0, 0x24))
694. start = f._data_offset + offsets["data"]
695. end = f._data_offset + offsets["plaintext_hash_tree"]
696. flip_bit(start, end)
697.
698.
699.
700. class ReplOptions(usage.Options):
701. pass
702.
703. def repl(options):
704. import code
705. return code.interact()
706.
707.
708. class DebugCommand(usage.Options):
709. subCommands = [
710. ["dump-share", None, DumpOptions,
711. "Unpack and display the contents of a share (uri_extension and leases)."],
712. ["dump-cap", None, DumpCapOptions, "Unpack a read-cap or write-cap"],
713. ["find-shares", None, FindSharesOptions, "Locate sharefiles in node dirs"],
714. ["catalog-shares", None, CatalogSharesOptions, "Describe shares in node dirs"],
715. ["corrupt-share", None, CorruptShareOptions, "Corrupt a share"],
716. ["repl", None, ReplOptions, "Open a python interpreter"],
717. ]
718. def postOptions(self):
719. if not hasattr(self, 'subOptions'):
720. raise usage.UsageError("must specify a subcommand")
721. def getSynopsis(self):
722. return "Usage: tahoe debug SUBCOMMAND"
723. def getUsage(self, width=None):
724. #t = usage.Options.getUsage(self, width)
725. t = """
726. Subcommands:
727. tahoe debug dump-share Unpack and display the contents of a share
728. tahoe debug dump-cap Unpack a read-cap or write-cap
729. tahoe debug find-shares Locate sharefiles in node directories
730. tahoe debug catalog-shares Describe all shares in node dirs
731. tahoe debug corrupt-share Corrupt a share by flipping a bit.
732.
733. Please run e.g. 'tahoe debug dump-share --help' for more details on each
734. subcommand.
735. """
736. return t
737.
738. subDispatch = {
739. "dump-share": dump_share,
740. "dump-cap": dump_cap,
741. "find-shares": find_shares,
742. "catalog-shares": catalog_shares,
743. "corrupt-share": corrupt_share,
744. "repl": repl,
745. }
746.
747.
748. def do_debug(options):
749. so = options.subOptions
750. so.stdout = options.stdout
751. so.stderr = options.stderr
752. f = subDispatch[options.subCommand]
753. return f(so)
754.
755.
756. subCommands = [
757. ["debug", None, DebugCommand, "debug subcommands: use 'tahoe debug' for a list"],
758. ]
759.
760. dispatch = {
761. "debug": do_debug,
762. }