tool.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. #!/usr/bin/env python
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. tool.py: Routines and data for the mmgen-tool utility
  20. """
  21. import sys
  22. import mmgen.bitcoin as bitcoin
  23. import binascii as ba
  24. import mmgen.config as g
  25. from mmgen.crypto import *
  26. from mmgen.util import *
  27. from mmgen.tx import *
  28. def Msg(s): sys.stdout.write(s + "\n")
  29. def Msg_r(s): sys.stdout.write(s)
  30. def Vmsg(s):
  31. if g.verbose: sys.stdout.write(s + "\n")
  32. def Vmsg_r(s):
  33. if g.verbose: sys.stdout.write(s)
  34. opts = {}
  35. commands = {
  36. "strtob58": ['<string> [str]'],
  37. "hextob58": ['<hex number> [str]'],
  38. "b58tohex": ['<b58 number> [str]'],
  39. "b58randenc": [],
  40. "randhex": ['nbytes [int=32]'],
  41. "randwif": ['compressed [bool=False]'],
  42. "randpair": ['compressed [bool=False]'],
  43. "wif2hex": ['<wif> [str]', 'compressed [bool=False]'],
  44. "wif2addr": ['<wif> [str]', 'compressed [bool=False]'],
  45. "hex2wif": ['<private key in hex format> [str]', 'compressed [bool=False]'],
  46. "hexdump": ['<infile> [str]', 'cols [int=8]', 'line_nums [bool=True]'],
  47. "unhexdump": ['<infile> [str]'],
  48. "mn_rand128": ['wordlist [str="electrum"]'],
  49. "mn_rand192": ['wordlist [str="electrum"]'],
  50. "mn_rand256": ['wordlist [str="electrum"]'],
  51. "mn_stats": ['wordlist [str="electrum"]'],
  52. "mn_printlist": ['wordlist [str="electrum"]'],
  53. "id8": ['<infile> [str]'],
  54. "id6": ['<infile> [str]'],
  55. "str2id6": ['<string (spaces are ignored)> [str]'],
  56. "listaddresses":['minconf [int=1]', 'showempty [bool=False]'],
  57. "getbalance": ['minconf [int=1]'],
  58. "viewtx": ['<MMGen tx file> [str]'],
  59. "check_addrfile": ['<MMGen addr file> [str]'],
  60. "find_incog_data": ['<file or device name> [str]','<Incog ID> [str]','keep_searching [bool=False]'],
  61. "hexreverse": ['<hexadecimal string> [str]'],
  62. "sha256x2": ['<str, hexstr or filename> [str]',
  63. 'hex_input [bool=False]','file_input [bool=False]'],
  64. "hexlify": ['<string> [str]'],
  65. "hexaddr2addr": ['<btc address in hex format> [str]'],
  66. "addr2hexaddr": ['<btc address> [str]'],
  67. "pubkey2addr": ['<public key in hex format> [str]'],
  68. "pubkey2hexaddr": ['<public key in hex format> [str]'],
  69. "privhex2addr": ['<private key in hex format> [str]','compressed [bool=False]'],
  70. "encrypt": ['<infile> [str]','outfile [str=""]','hash_preset [str="3"]'],
  71. "decrypt": ['<infile> [str]','outfile [str=""]','hash_preset [str="3"]'],
  72. "rand2file": ['<outfile> [str]','<nbytes> [str]','threads [int=4]'],
  73. "bytespec": ['<bytespec> [str]'],
  74. }
  75. command_help = """
  76. Bitcoin address/key operations (compressed addresses supported):
  77. addr2hexaddr - convert Bitcoin address from base58 to hex format
  78. b58randenc - generate a random 32-byte number and convert it to base 58
  79. b58tohex - convert a base 58 number to hexadecimal
  80. hex2wif - convert a private key from hex to WIF format
  81. hexaddr2addr - convert Bitcoin address from hex to base58 format
  82. hextob58 - convert a hexadecimal number to base 58
  83. privhex2addr - generate Bitcoin address from private key in hex format
  84. pubkey2addr - convert Bitcoin public key to address
  85. pubkey2hexaddr - convert Bitcoin public key to address in hex format
  86. randpair - generate a random private key/address pair
  87. randwif - generate a random private key in WIF format
  88. strtob58 - convert a string to base 58
  89. wif2addr - generate a Bitcoin address from a key in WIF format
  90. wif2hex - convert a private key from WIF to hex format
  91. Wallet/TX operations (bitcoind must be running):
  92. getbalance - like 'bitcoind getbalance' but shows confirmed/unconfirmed,
  93. spendable/unspendable balances for individual {pnm} wallets
  94. listaddresses - list {pnm} addresses and their balances
  95. viewtx - show raw/signed {pnm} transaction in human-readable form
  96. General utilities:
  97. bytespec - convert a byte specifier such as '1GB' into a plain integer
  98. hexdump - encode data into formatted hexadecimal form (file or stdin)
  99. hexlify - display string in hexadecimal format
  100. hexreverse - reverse bytes of a hexadecimal string
  101. rand2file - write 'n' bytes of random data to specified file
  102. randhex - print 'n' bytes (default 32) of random data in hex format
  103. sha256x2 - compute a double sha256 hash of data
  104. unhexdump - decode formatted hexadecimal data (file or stdin)
  105. File encryption:
  106. encrypt - encrypt a file
  107. decrypt - decrypt a file
  108. {pnm} encryption suite:
  109. * Key: Scrypt (user-configurable hash parameters, 32-byte salt)
  110. * Enc: AES256_CTR, 16-byte rand IV, sha256 hash + 32-byte nonce + data
  111. * The encrypted file is indistinguishable from random data
  112. {pnm}-specific operations:
  113. check_addrfile - compute checksum and address list for {pnm} address file
  114. find_incog_data - Use an Incog ID to find hidden incognito wallet data
  115. id6 - generate 6-character {pnm} ID for a file (or stdin)
  116. id8 - generate 8-character {pnm} ID for a file (or stdin)
  117. str2id6 - generate 6-character {pnm} ID for a string, ignoring spaces
  118. Mnemonic operations (choose "electrum" (default), "tirosh" or "all"
  119. wordlists):
  120. mn_rand128 - generate random 128-bit mnemonic
  121. mn_rand192 - generate random 192-bit mnemonic
  122. mn_rand256 - generate random 256-bit mnemonic
  123. mn_stats - show stats for mnemonic wordlist
  124. mn_printlist - print mnemonic wordlist
  125. IMPORTANT NOTE: Though {pnm} mnemonics use the Electrum wordlist, they're
  126. computed using a different algorithm and are NOT Electrum-compatible!
  127. """.format(pnm=g.proj_name)
  128. def tool_usage(prog_name, command):
  129. print "USAGE: '%s %s%s'" % (prog_name, command,
  130. (" "+" ".join(commands[command]) if commands[command] else ""))
  131. def process_args(prog_name, command, uargs):
  132. cargs = commands[command]
  133. cargs_req = [[i.split(" [")[0],i.split(" [")[1][:-1]]
  134. for i in cargs if "=" not in i]
  135. cargs_nam = dict([[
  136. i.split(" [")[0],
  137. [i.split(" [")[1].split("=")[0], i.split(" [")[1].split("=")[1][:-1]]
  138. ] for i in cargs if "=" in i])
  139. uargs_req = [i for i in uargs if "=" not in i]
  140. uargs_nam = dict([i.split("=") for i in uargs if "=" in i])
  141. # print cargs_req; print cargs_nam; print uargs_req; print uargs_nam; sys.exit()
  142. n = len(cargs_req)
  143. if len(uargs_req) != n:
  144. tool_usage(prog_name, command)
  145. sys.exit(1)
  146. for a in uargs_nam.keys():
  147. if a not in cargs_nam.keys():
  148. print "'%s' invalid named argument" % a
  149. sys.exit(1)
  150. def test_type(arg_type,arg,name=""):
  151. try:
  152. t = type(eval(arg))
  153. assert(t == eval(arg_type))
  154. except:
  155. print "'%s': Invalid argument for argument %s ('%s' required)" % \
  156. (arg, name, arg_type)
  157. sys.exit(1)
  158. return True
  159. ret = []
  160. def normalize_arg(arg, arg_type):
  161. if arg_type == "bool":
  162. if arg.lower() in ("true","yes","1","on"): return "True"
  163. if arg.lower() in ("false","no","0","off"): return "False"
  164. return arg
  165. for i in range(len(cargs_req)):
  166. arg_type = cargs_req[i][1]
  167. arg = normalize_arg(uargs_req[i], arg_type)
  168. if arg_type == "str":
  169. ret.append('"%s"' % (arg))
  170. elif test_type(arg_type, arg, "#"+str(i+1)):
  171. ret.append('%s' % (arg))
  172. for k in uargs_nam.keys():
  173. arg_type = cargs_nam[k][0]
  174. arg = normalize_arg(uargs_nam[k], arg_type)
  175. if arg_type == "str":
  176. ret.append('%s="%s"' % (k, arg))
  177. elif test_type(arg_type, arg, "'"+k+"'"):
  178. ret.append('%s=%s' % (k, arg))
  179. return ret
  180. # Individual commands
  181. def print_convert_results(indata,enc,dec,no_recode=False):
  182. Vmsg("Input: [%s]" % indata)
  183. Vmsg_r("Encoded data: ["); Msg_r(enc); Vmsg_r("]"); Msg("")
  184. if not no_recode:
  185. Vmsg("Recoded data: [%s]" % dec)
  186. if indata != dec:
  187. Msg("WARNING! Recoded number doesn't match input stringwise!")
  188. def hexdump(infile, cols=8, line_nums=True):
  189. print pretty_hexdump(get_data_from_file(infile,dash=True),
  190. cols=cols, line_nums=line_nums)
  191. def unhexdump(infile):
  192. sys.stdout.write(decode_pretty_hexdump(get_data_from_file(infile,dash=True)))
  193. def strtob58(s):
  194. enc = bitcoin.b58encode(s)
  195. dec = bitcoin.b58decode(enc)
  196. print_convert_results(s,enc,dec)
  197. def hextob58(s,f_enc=bitcoin.b58encode, f_dec=bitcoin.b58decode):
  198. enc = f_enc(ba.unhexlify(s))
  199. dec = ba.hexlify(f_dec(enc))
  200. print_convert_results(s,enc,dec)
  201. def b58tohex(s,f_enc=bitcoin.b58decode, f_dec=bitcoin.b58encode):
  202. tmp = f_enc(s)
  203. if tmp == False: sys.exit(1)
  204. enc = ba.hexlify(tmp)
  205. dec = f_dec(ba.unhexlify(enc))
  206. print_convert_results(s,enc,dec)
  207. def b58randenc():
  208. r = get_random(32,opts)
  209. enc = bitcoin.b58encode(r)
  210. dec = bitcoin.b58decode(enc)
  211. print_convert_results(ba.hexlify(r),enc,ba.hexlify(dec))
  212. def randhex(nbytes='32'):
  213. print ba.hexlify(get_random(int(nbytes),opts))
  214. def randwif(compressed=False):
  215. r_hex = ba.hexlify(get_random(32,opts))
  216. enc = bitcoin.hextowif(r_hex,compressed)
  217. print_convert_results(r_hex,enc,"",no_recode=True)
  218. def randpair(compressed=False):
  219. r_hex = ba.hexlify(get_random(32,opts))
  220. wif = bitcoin.hextowif(r_hex,compressed)
  221. addr = bitcoin.privnum2addr(int(r_hex,16),compressed)
  222. Vmsg("Key (hex): %s" % r_hex)
  223. Vmsg_r("Key (WIF): "); Msg(wif)
  224. Vmsg_r("Addr: "); Msg(addr)
  225. def wif2addr(wif,compressed=False):
  226. s_enc = bitcoin.wiftohex(wif,compressed)
  227. if s_enc == False:
  228. Msg("Invalid address")
  229. sys.exit(1)
  230. addr = bitcoin.privnum2addr(int(s_enc,16),compressed)
  231. Vmsg_r("Addr: "); Msg(addr)
  232. from mmgen.mnemonic import *
  233. from mmgen.mn_electrum import electrum_words as el
  234. from mmgen.mn_tirosh import tirosh_words as tl
  235. wordlists = sorted(wl_checksums.keys())
  236. def get_wordlist(wordlist):
  237. wordlist = wordlist.lower()
  238. if wordlist not in wordlists:
  239. Msg('"%s": invalid wordlist. Valid choices: %s' %
  240. (wordlist,'"'+'" "'.join(wordlists)+'"'))
  241. sys.exit(1)
  242. return el if wordlist == "electrum" else tl
  243. def do_random_mn(nbytes,wordlist):
  244. r = get_random(nbytes,opts)
  245. wlists = wordlists if wordlist == "all" else [wordlist]
  246. for wl in wlists:
  247. l = get_wordlist(wl)
  248. if wl == wlists[0]: Vmsg("Seed: %s" % ba.hexlify(r))
  249. mn = get_mnemonic_from_seed(r,l.strip().split("\n"),
  250. wordlist,print_info=False)
  251. Vmsg("%s wordlist mnemonic:" % (wl.capitalize()))
  252. print " ".join(mn)
  253. def mn_rand128(wordlist="electrum"): do_random_mn(16,wordlist)
  254. def mn_rand192(wordlist="electrum"): do_random_mn(24,wordlist)
  255. def mn_rand256(wordlist="electrum"): do_random_mn(32,wordlist)
  256. def mn_stats(wordlist="electrum"):
  257. l = get_wordlist(wordlist)
  258. check_wordlist(l,wordlist)
  259. def mn_printlist(wordlist="electrum"):
  260. l = get_wordlist(wordlist)
  261. print "%s" % l.strip()
  262. def id8(infile): print make_chksum_8(get_data_from_file(infile,dash=True))
  263. def id6(infile): print make_chksum_6(get_data_from_file(infile,dash=True))
  264. def str2id6(s): print make_chksum_6("".join(s.split()))
  265. # List MMGen addresses and their balances:
  266. def listaddresses(minconf=1,showempty=False):
  267. from mmgen.tx import connect_to_bitcoind,trim_exponent,is_mmgen_addr
  268. c = connect_to_bitcoind()
  269. addrs = {}
  270. for d in c.listunspent(0):
  271. ma,comment = split2(d.account)
  272. if is_mmgen_addr(ma) and d.confirmations >= minconf:
  273. key = "_".join(ma.split(":"))
  274. if key not in addrs: addrs[key] = [0,comment]
  275. addrs[key][0] += d.amount
  276. # "bitcoind getbalance <account>" can produce a false balance
  277. # (sipa watchonly bitcoind), so use only for empty accounts
  278. if showempty:
  279. # Show accts with not enough confirmations as empty!
  280. # A feature, not a bug!
  281. for (ma,comment),bal in [(split2(a),c.getbalance(a,minconf=minconf))
  282. for a in c.listaccounts(0)]:
  283. if is_mmgen_addr(ma) and bal == 0:
  284. key = "_".join(ma.split(":"))
  285. if key not in addrs: addrs[key] = [0,comment]
  286. fs = "%-{}s %-{}s %s".format(
  287. max([len(k) for k in addrs.keys()]),
  288. max([len(str(addrs[k][1])) for k in addrs.keys()])
  289. )
  290. print fs % ("ADDRESS","COMMENT","BALANCE")
  291. def s_mmgen(ma):
  292. return "{}:{:>0{w}}".format(w=g.mmgen_idx_max_digits, *ma.split("_"))
  293. old_sid = ""
  294. for k in sorted(addrs.keys(),key=s_mmgen):
  295. sid,num = k.split("_")
  296. if old_sid and old_sid != sid: print
  297. old_sid = sid
  298. print fs % (sid+":"+num, addrs[k][1], trim_exponent(addrs[k][0]))
  299. def getbalance(minconf=1):
  300. from mmgen.tx import connect_to_bitcoind,trim_exponent,is_mmgen_addr
  301. accts = {}
  302. for d in connect_to_bitcoind().listunspent(0):
  303. ma = split2(d.account)[0]
  304. keys = ["TOTAL"]
  305. if d.spendable: keys += ["SPENDABLE"]
  306. if is_mmgen_addr(ma): keys += [ma.split(":")[0]]
  307. c = d.confirmations
  308. i = 2 if c >= minconf else 1
  309. for key in keys:
  310. if key not in accts: accts[key] = [0,0,0]
  311. for j in ([0] if c == 0 else []) + [i]:
  312. accts[key][j] += d.amount
  313. fs = "{:12} {:<%s} {:<%s} {:<}" % (16,16)
  314. mc,lbl = str(minconf),"confirms"
  315. print fs.format("Wallet","Unconfirmed",
  316. "<%s %s"%(mc,lbl),">=%s %s"%(mc,lbl))
  317. for key in sorted(accts.keys()):
  318. print fs.format(key+":", *[str(trim_exponent(a))+" BTC" for a in accts[key]])
  319. def viewtx(infile):
  320. c = connect_to_bitcoind()
  321. tx_data = get_lines_from_file(infile,"transaction data")
  322. metadata,tx_hex,inputs_data,b2m_map = parse_tx_data(tx_data,infile)
  323. view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata)
  324. def check_addrfile(infile): parse_addrs_file(infile)
  325. def hexreverse(hex_str):
  326. print ba.hexlify(decode_pretty_hexdump(hex_str)[::-1])
  327. def hexlify(s):
  328. print ba.hexlify(s)
  329. def sha256x2(s, file_input=False, hex_input=False):
  330. from hashlib import sha256
  331. if file_input: b = get_data_from_file(s)
  332. elif hex_input: b = decode_pretty_hexdump(s)
  333. else: b = s
  334. print sha256(sha256(b).digest()).hexdigest()
  335. def hexaddr2addr(hexaddr):
  336. print bitcoin.hexaddr2addr(hexaddr)
  337. def addr2hexaddr(addr):
  338. print bitcoin.verify_addr(addr,return_hex=True)
  339. def pubkey2hexaddr(pubkeyhex):
  340. print bitcoin.pubhex2hexaddr(pubkeyhex)
  341. def pubkey2addr(pubkeyhex):
  342. print bitcoin.pubhex2addr(pubkeyhex)
  343. def privhex2addr(privkeyhex,compressed=False):
  344. print bitcoin.privnum2addr(int(privkeyhex,16),compressed)
  345. def wif2hex(wif,compressed=False):
  346. print bitcoin.wiftohex(wif,compressed)
  347. def hex2wif(hexpriv,compressed=False):
  348. print bitcoin.hextowif(hexpriv,compressed)
  349. def encrypt(infile,outfile="",hash_preset=''):
  350. data = get_data_from_file(infile,"data for encryption")
  351. enc_d = mmgen_encrypt(data,hash_preset,opts)
  352. if outfile == '-':
  353. write_to_stdout(enc_d,"encrypted data",confirm=True)
  354. else:
  355. if not outfile:
  356. outfile = os.path.basename(infile) + "." + g.mmenc_ext
  357. write_to_file(outfile, enc_d, opts,"encrypted data",True,True)
  358. def decrypt(infile,outfile="",hash_preset=''):
  359. enc_d = get_data_from_file(infile,"encrypted data")
  360. dec_d = mmgen_decrypt(enc_d,hash_preset,opts)
  361. if outfile == '-':
  362. write_to_stdout(dec_d,"decrypted data",confirm=True)
  363. else:
  364. if not outfile:
  365. outfile = os.path.basename(infile)
  366. if outfile[-len(g.mmenc_ext)-1:] == "."+g.mmenc_ext:
  367. outfile = outfile[:-len(g.mmenc_ext)-1]
  368. else:
  369. outfile = outfile + ".dec"
  370. write_to_file(outfile, dec_d, opts,"decrypted data",True,True)
  371. def find_incog_data(filename,iv_id,keep_searching=False):
  372. ivsize,bsize,mod = g.aesctr_iv_len,4096,4096*8
  373. n,carry = 0," "*ivsize
  374. f = os.open(filename,os.O_RDONLY)
  375. while True:
  376. d = os.read(f,bsize)
  377. if not d: break
  378. d = carry + d
  379. for i in range(bsize):
  380. if sha256(d[i:i+ivsize]).hexdigest()[:8].upper() == iv_id:
  381. if n+i < ivsize: continue
  382. msg("\rIncog data for ID %s found at offset %s" %
  383. (iv_id,n+i-ivsize))
  384. if not keep_searching: sys.exit(0)
  385. carry = d[len(d)-ivsize:]
  386. n += bsize
  387. if not n % mod: msg_r("\rSearched: %s bytes" % n)
  388. msg("")
  389. os.close(f)
  390. # From "man dd":
  391. # c=1, w=2, b=512, kB=1000, K=1024, MB=1000*1000, M=1024*1024,
  392. # GB=1000*1000*1000, G=1024*1024*1024, and so on for T, P, E, Z, Y.
  393. def parse_nbytes(nbytes):
  394. import re
  395. m = re.match(r'([0123456789]+)(.*)',nbytes)
  396. smap = ("c",1),("w",2),("b",512),("kB",1000),("K",1024),("MB",1000*1000),\
  397. ("M",1024*1024),("GB",1000*1000*1000),("G",1024*1024*1024)
  398. if m:
  399. if m.group(2):
  400. for k,v in smap:
  401. if k == m.group(2):
  402. return int(m.group(1)) * v
  403. else:
  404. msg("Valid byte specifiers: '%s'" % "' '".join([i[0] for i in smap]))
  405. else:
  406. return int(nbytes)
  407. msg("'%s': invalid byte specifier" % nbytes)
  408. sys.exit(1)
  409. def rand2file(outfile, nbytes, threads=4):
  410. nbytes = parse_nbytes(nbytes)
  411. from Crypto import Random
  412. rh = Random.new()
  413. from Queue import Queue
  414. from threading import Thread
  415. bsize = 2**20
  416. roll = bsize * 4
  417. if 'outdir' in opts: outfile = make_full_path(opts['outdir'],outfile)
  418. f = open(outfile,"w")
  419. from Crypto.Cipher import AES
  420. from Crypto.Util import Counter
  421. key = get_random(32,opts)
  422. def encrypt_worker(wid):
  423. while True:
  424. i,d = q1.get()
  425. c = AES.new(key, AES.MODE_CTR,
  426. counter=Counter.new(g.aesctr_iv_len*8,initial_value=i))
  427. enc_data = c.encrypt(d)
  428. q2.put(enc_data)
  429. q1.task_done()
  430. def output_worker():
  431. while True:
  432. data = q2.get()
  433. f.write(data)
  434. q2.task_done()
  435. q1 = Queue()
  436. for i in range(max(1,threads-2)):
  437. t = Thread(target=encrypt_worker, args=(i,))
  438. t.daemon = True
  439. t.start()
  440. q2 = Queue()
  441. t = Thread(target=output_worker)
  442. t.daemon = True
  443. t.start()
  444. i = 1; rbytes = nbytes
  445. while rbytes > 0:
  446. d = rh.read(min(bsize,rbytes))
  447. q1.put((i,d))
  448. rbytes -= bsize
  449. i += 1
  450. if not (bsize*i) % roll:
  451. msg_r("\rRead: %s bytes" % (bsize*i))
  452. msg("\rRead: %s bytes" % nbytes)
  453. qmsg("\r%s bytes written to file '%s'" % (nbytes,outfile))
  454. q1.join()
  455. q2.join()
  456. f.close()
  457. def bytespec(s): print parse_nbytes(s)