util.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2020 The MMGen Project <mmgen@tuta.io>
  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. util.py: Low-level routines imported by other modules in the MMGen suite
  20. """
  21. import sys,os,time,stat,re
  22. from hashlib import sha256
  23. from string import hexdigits,digits
  24. from .color import *
  25. from .exception import *
  26. from .globalvars import *
  27. if g.platform == 'win':
  28. def msg_r(s):
  29. try:
  30. g.stderr.write(s)
  31. g.stderr.flush()
  32. except:
  33. os.write(2,s.encode())
  34. def Msg_r(s):
  35. try:
  36. g.stdout.write(s)
  37. g.stdout.flush()
  38. except:
  39. os.write(1,s.encode())
  40. def msg(s): msg_r(s + '\n')
  41. def Msg(s): Msg_r(s + '\n')
  42. else:
  43. def msg_r(s):
  44. g.stderr.write(s)
  45. g.stderr.flush()
  46. def Msg_r(s):
  47. g.stdout.write(s)
  48. g.stdout.flush()
  49. def msg(s): g.stderr.write(s + '\n')
  50. def Msg(s): g.stdout.write(s + '\n')
  51. def msgred(s): msg(red(s))
  52. def rmsg(s): msg(red(s))
  53. def rmsg_r(s): msg_r(red(s))
  54. def ymsg(s): msg(yellow(s))
  55. def ymsg_r(s): msg_r(yellow(s))
  56. def gmsg(s): msg(green(s))
  57. def gmsg_r(s): msg_r(green(s))
  58. def bmsg(s): msg(blue(s))
  59. def bmsg_r(s): msg_r(blue(s))
  60. def mmsg(*args):
  61. for d in args: Msg(repr(d))
  62. def mdie(*args):
  63. mmsg(*args); sys.exit(0)
  64. def die_wait(delay,ev=0,s=''):
  65. assert isinstance(delay,int)
  66. assert isinstance(ev,int)
  67. if s: msg(s)
  68. time.sleep(delay)
  69. sys.exit(ev)
  70. def die_pause(ev=0,s=''):
  71. assert isinstance(ev,int)
  72. if s: msg(s)
  73. input('Press ENTER to exit')
  74. sys.exit(ev)
  75. def die(ev=0,s=''):
  76. assert isinstance(ev,int)
  77. if s: msg(s)
  78. sys.exit(ev)
  79. def Die(ev=0,s=''):
  80. assert isinstance(ev,int)
  81. if s: Msg(s)
  82. sys.exit(ev)
  83. def rdie(ev=0,s=''): die(ev,red(s))
  84. def ydie(ev=0,s=''): die(ev,yellow(s))
  85. def pp_fmt(d):
  86. import pprint
  87. return pprint.PrettyPrinter(indent=4,compact=True).pformat(d)
  88. def pp_msg(d):
  89. msg(pp_fmt(d))
  90. def fmt(s,indent='',strip_char=None):
  91. "de-indent multiple lines of text, or indent with specified string"
  92. return indent + ('\n'+indent).join([l.strip(strip_char) for l in s.strip().splitlines()]) + '\n'
  93. def fmt_list(l,fmt='dfl',indent=''):
  94. "pretty-format a list"
  95. sep,lq,rq = {
  96. 'utf8': ("“, ”", "“", "”"),
  97. 'dfl': ("', '", "'", "'"),
  98. 'bare': (' ', '', '' ),
  99. 'no_quotes': (', ', '', '' ),
  100. 'no_spc': ("','", "'", "'"),
  101. 'min': (",", "'", "'"),
  102. 'col': ('\n'+indent, indent, '' ),
  103. }[fmt]
  104. return lq + sep.join(l) + rq
  105. CUR_HIDE = '\033[?25l'
  106. CUR_SHOW = '\033[?25h'
  107. def warn_altcoins(coinsym,trust_level):
  108. if trust_level > 3:
  109. return
  110. tl_str = (
  111. red('COMPLETELY UNTESTED'),
  112. red('LOW'),
  113. yellow('MEDIUM'),
  114. green('HIGH'),
  115. )[trust_level]
  116. m = """
  117. Support for coin {!r} is EXPERIMENTAL. The {pn} project
  118. assumes no responsibility for any loss of funds you may incur.
  119. This coin’s {pn} testing status: {}
  120. Are you sure you want to continue?
  121. """
  122. m = fmt(m).strip().format(coinsym.upper(),tl_str,pn=g.proj_name)
  123. if g.test_suite:
  124. qmsg(m)
  125. return
  126. if not keypress_confirm(m,default_yes=True):
  127. sys.exit(0)
  128. def set_for_type(val,refval,desc,invert_bool=False,src=None):
  129. if type(refval) == bool:
  130. v = str(val).lower()
  131. ret = True if v in ('true','yes','1') else False if v in ('false','no','none','0') else None
  132. if ret is not None:
  133. return not ret if invert_bool else ret
  134. else:
  135. try:
  136. return type(refval)(not val if invert_bool else val)
  137. except:
  138. pass
  139. die(1,'{!r}: invalid value for {!r}{} (must be of type {!r})'.format(
  140. val,
  141. desc,
  142. ' in {!r}'.format(src) if src else '',
  143. type(refval).__name__) )
  144. # From 'man dd':
  145. # c=1, w=2, b=512, kB=1000, K=1024, MB=1000*1000, M=1024*1024,
  146. # GB=1000*1000*1000, G=1024*1024*1024, and so on for T, P, E, Z, Y.
  147. def parse_bytespec(nbytes):
  148. smap = (('c', 1),
  149. ('w', 2),
  150. ('b', 512),
  151. ('kB', 1000),
  152. ('K', 1024),
  153. ('MB', 1000*1000),
  154. ('M', 1024*1024),
  155. ('GB', 1000*1000*1000),
  156. ('G', 1024*1024*1024),
  157. ('TB', 1000*1000*1000*1000),
  158. ('T', 1024*1024*1024*1024))
  159. import re
  160. m = re.match(r'([0123456789.]+)(.*)',nbytes)
  161. if m:
  162. if m.group(2):
  163. for k,v in smap:
  164. if k == m.group(2):
  165. from decimal import Decimal
  166. return int(Decimal(m.group(1)) * v)
  167. else:
  168. msg("Valid byte specifiers: '{}'".format("' '".join([i[0] for i in smap])))
  169. elif '.' in nbytes:
  170. raise ValueError('fractional bytes not allowed')
  171. else:
  172. return int(nbytes)
  173. die(1,"'{}': invalid byte specifier".format(nbytes))
  174. def check_or_create_dir(path):
  175. try:
  176. os.listdir(path)
  177. except:
  178. try:
  179. os.makedirs(path,0o700)
  180. except:
  181. die(2,"ERROR: unable to read or create path '{}'".format(path))
  182. from .opts import opt
  183. def qmsg(s,alt=None):
  184. if opt.quiet:
  185. if alt != None: msg(alt)
  186. else: msg(s)
  187. def qmsg_r(s,alt=None):
  188. if opt.quiet:
  189. if alt != None: msg_r(alt)
  190. else: msg_r(s)
  191. def vmsg(s,force=False):
  192. if opt.verbose or force: msg(s)
  193. def vmsg_r(s,force=False):
  194. if opt.verbose or force: msg_r(s)
  195. def Vmsg(s,force=False):
  196. if opt.verbose or force: Msg(s)
  197. def Vmsg_r(s,force=False):
  198. if opt.verbose or force: Msg_r(s)
  199. def dmsg(s):
  200. if opt.debug: msg(s)
  201. def suf(arg,suf_type='s',verb='none'):
  202. suf_types = {
  203. 'none': {
  204. 's': ('s', ''),
  205. 'es': ('es', ''),
  206. 'ies': ('ies','y'),
  207. },
  208. 'is': {
  209. 's': ('s are', ' is'),
  210. 'es': ('es are', ' is'),
  211. 'ies': ('ies are','y is'),
  212. },
  213. 'has': {
  214. 's': ('s have', ' has'),
  215. 'es': ('es have', ' has'),
  216. 'ies': ('ies have','y has'),
  217. },
  218. }
  219. if isinstance(arg,int):
  220. n = arg
  221. elif isinstance(arg,(list,tuple,set,dict)):
  222. n = len(arg)
  223. else:
  224. die(2,'{}: invalid parameter for suf()'.format(arg))
  225. return suf_types[verb][suf_type][n == 1]
  226. def get_extension(f):
  227. a,b = os.path.splitext(f)
  228. return ('',b[1:])[len(b) > 1]
  229. def remove_extension(f,e):
  230. a,b = os.path.splitext(f)
  231. return (f,a)[len(b)>1 and b[1:]==e]
  232. def make_chksum_N(s,nchars,sep=False):
  233. if isinstance(s,str): s = s.encode()
  234. if nchars%4 or not (4 <= nchars <= 64): return False
  235. s = sha256(sha256(s).digest()).hexdigest().upper()
  236. sep = ('',' ')[bool(sep)]
  237. return sep.join([s[i*4:i*4+4] for i in range(nchars//4)])
  238. def make_chksum_8(s,sep=False):
  239. from .obj import HexStr
  240. s = HexStr(sha256(sha256(s).digest()).hexdigest()[:8].upper(),case='upper')
  241. return '{} {}'.format(s[:4],s[4:]) if sep else s
  242. def make_chksum_6(s):
  243. from .obj import HexStr
  244. if isinstance(s,str): s = s.encode()
  245. return HexStr(sha256(s).hexdigest()[:6])
  246. def is_chksum_6(s): return len(s) == 6 and is_hex_str_lc(s)
  247. def make_iv_chksum(s): return sha256(s).hexdigest()[:8].upper()
  248. def splitN(s,n,sep=None): # always return an n-element list
  249. ret = s.split(sep,n-1)
  250. return ret + ['' for i in range(n-len(ret))]
  251. def split2(s,sep=None): return splitN(s,2,sep) # always return a 2-element list
  252. def split3(s,sep=None): return splitN(s,3,sep) # always return a 3-element list
  253. def split_into_cols(col_wid,s):
  254. return ' '.join([s[col_wid*i:col_wid*(i+1)] for i in range(len(s)//col_wid+1)]).rstrip()
  255. def capfirst(s): # different from str.capitalize() - doesn't downcase any uc in string
  256. return s if len(s) == 0 else s[0].upper() + s[1:]
  257. def decode_timestamp(s):
  258. # tz_save = open('/etc/timezone').read().rstrip()
  259. os.environ['TZ'] = 'UTC'
  260. ts = time.strptime(s,'%Y%m%d_%H%M%S')
  261. t = time.mktime(ts)
  262. # os.environ['TZ'] = tz_save
  263. return int(t)
  264. def make_timestamp(secs=None):
  265. t = int(secs) if secs else time.time()
  266. return '{:04d}{:02d}{:02d}_{:02d}{:02d}{:02d}'.format(*time.gmtime(t)[:6])
  267. def make_timestr(secs=None):
  268. t = int(secs) if secs else time.time()
  269. return '{}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}'.format(*time.gmtime(t)[:6])
  270. def secs_to_dhms(secs):
  271. dsecs = secs//3600
  272. return '{}{:02d}:{:02d}:{:02d}'.format(
  273. ('','{} day{}, '.format(dsecs//24,suf(dsecs//24)))[dsecs > 24],
  274. dsecs % 24, (secs//60) % 60, secs % 60)
  275. def secs_to_hms(secs):
  276. return '{:02d}:{:02d}:{:02d}'.format(secs//3600, (secs//60) % 60, secs % 60)
  277. def secs_to_ms(secs):
  278. return '{:02d}:{:02d}'.format(secs//60, secs % 60)
  279. def is_digits(s): return set(list(s)) <= set(list(digits))
  280. def is_int(s):
  281. try:
  282. int(str(s))
  283. return True
  284. except:
  285. return False
  286. def is_hex_str(s): return set(list(s.lower())) <= set(list(hexdigits.lower()))
  287. def is_hex_str_lc(s): return set(list(s)) <= set(list(hexdigits.lower()))
  288. def is_hex_str_uc(s): return set(list(s)) <= set(list(hexdigits.upper()))
  289. def is_utf8(s):
  290. return is_ascii(s,enc='utf8')
  291. def is_ascii(s,enc='ascii'):
  292. try: s.decode(enc)
  293. except: return False
  294. else: return True
  295. def match_ext(addr,ext):
  296. return addr.split('.')[-1] == ext
  297. def file_exists(f):
  298. try:
  299. os.stat(f)
  300. return True
  301. except:
  302. return False
  303. def file_is_readable(f):
  304. from stat import S_IREAD
  305. try:
  306. assert os.stat(f).st_mode & S_IREAD
  307. except:
  308. return False
  309. else:
  310. return True
  311. def get_from_brain_opt_params():
  312. l,p = opt.from_brain.split(',')
  313. return(int(l),p)
  314. def remove_whitespace(s):
  315. return s.translate(dict((ord(ws),None) for ws in '\t\r\n '))
  316. def pretty_format(s,width=80,pfx=''):
  317. out = []
  318. while(s):
  319. if len(s) <= width:
  320. out.append(s)
  321. break
  322. i = s[:width].rfind(' ')
  323. out.append(s[:i])
  324. s = s[i+1:]
  325. return pfx + ('\n'+pfx).join(out)
  326. def block_format(data,gw=2,cols=8,line_nums=None,data_is_hex=False):
  327. assert line_nums in (None,'hex','dec'),"'line_nums' must be one of None, 'hex' or 'dec'"
  328. ln_fs = '{:06x}: ' if line_nums == 'hex' else '{:06}: '
  329. bytes_per_chunk = gw
  330. if data_is_hex:
  331. gw *= 2
  332. nchunks = len(data)//gw + bool(len(data)%gw)
  333. return ''.join(
  334. ('' if (line_nums == None or i % cols) else ln_fs.format(i*bytes_per_chunk))
  335. + data[i*gw:i*gw+gw]
  336. + (' ' if (not cols or (i+1) % cols) else '\n')
  337. for i in range(nchunks)
  338. ).rstrip() + '\n'
  339. def pretty_hexdump(data,gw=2,cols=8,line_nums=None):
  340. return block_format(data.hex(),gw,cols,line_nums,data_is_hex=True)
  341. def decode_pretty_hexdump(data):
  342. from string import hexdigits
  343. pat = re.compile(fr'^[{hexdigits}]+:\s+')
  344. lines = [pat.sub('',line) for line in data.splitlines()]
  345. try:
  346. return bytes.fromhex(''.join((''.join(lines).split())))
  347. except:
  348. msg('Data not in hexdump format')
  349. return False
  350. def strip_comments(line):
  351. return re.sub(r'\s+$','',re.sub(r'#.*','',line,1))
  352. def remove_comments(lines):
  353. return [m for m in [strip_comments(l) for l in lines] if m != '']
  354. def get_hash_params(hash_preset):
  355. if hash_preset in g.hash_presets:
  356. return g.hash_presets[hash_preset] # N,p,r,buflen
  357. else: # Shouldn't be here
  358. die(3,"{}: invalid 'hash_preset' value".format(hash_preset))
  359. def compare_chksums(chk1,desc1,chk2,desc2,hdr='',die_on_fail=False,verbose=False):
  360. if not chk1 == chk2:
  361. fs = "{} ERROR: {} checksum ({}) doesn't match {} checksum ({})"
  362. m = fs.format((hdr+':\n ' if hdr else 'CHECKSUM'),desc2,chk2,desc1,chk1)
  363. if die_on_fail:
  364. die(3,m)
  365. else:
  366. vmsg(m,force=verbose)
  367. return False
  368. vmsg('{} checksum OK ({})'.format(capfirst(desc1),chk1))
  369. return True
  370. def compare_or_die(val1, desc1, val2, desc2, e='Error'):
  371. if val1 != val2:
  372. die(3,"{}: {} ({}) doesn't match {} ({})".format(e,desc2,val2,desc1,val1))
  373. dmsg('{} OK ({})'.format(capfirst(desc2),val2))
  374. return True
  375. def open_file_or_exit(filename,mode,silent=False):
  376. try:
  377. return open(filename, mode)
  378. except:
  379. op = ('writing','reading')['r' in mode]
  380. die(2,("Unable to open file '{}' for {}".format(filename,op),'')[silent])
  381. def check_file_type_and_access(fname,ftype,blkdev_ok=False):
  382. a = ((os.R_OK,'read'),(os.W_OK,'writ'))
  383. access,m = a[ftype in ('output file','output directory')]
  384. ok_types = [
  385. (stat.S_ISREG,'regular file'),
  386. (stat.S_ISLNK,'symbolic link')
  387. ]
  388. if blkdev_ok: ok_types.append((stat.S_ISBLK,'block device'))
  389. if ftype == 'output directory': ok_types = [(stat.S_ISDIR, 'output directory')]
  390. try: mode = os.stat(fname).st_mode
  391. except:
  392. raise FileNotFound("Requested {} '{}' not found".format(ftype,fname))
  393. for t in ok_types:
  394. if t[0](mode): break
  395. else:
  396. die(1,"Requested {} '{}' is not a {}".format(ftype,fname,' or '.join([t[1] for t in ok_types])))
  397. if not os.access(fname, access):
  398. die(1,"Requested {} '{}' is not {}able by you".format(ftype,fname,m))
  399. return True
  400. def check_infile(f,blkdev_ok=False):
  401. return check_file_type_and_access(f,'input file',blkdev_ok=blkdev_ok)
  402. def check_outfile(f,blkdev_ok=False):
  403. return check_file_type_and_access(f,'output file',blkdev_ok=blkdev_ok)
  404. def check_outdir(f):
  405. return check_file_type_and_access(f,'output directory')
  406. def check_wallet_extension(fn):
  407. from .wallet import Wallet
  408. if not Wallet.ext_to_type(get_extension(fn)):
  409. raise BadFileExtension("'{}': unrecognized seed source file extension".format(fn))
  410. def make_full_path(outdir,outfile):
  411. return os.path.normpath(os.path.join(outdir, os.path.basename(outfile)))
  412. def get_seed_file(cmd_args,nargs,invoked_as=None):
  413. from .filename import find_file_in_dir
  414. from .wallet import MMGenWallet
  415. wf = find_file_in_dir(MMGenWallet,g.data_dir)
  416. wd_from_opt = bool(opt.hidden_incog_input_params or opt.in_fmt) # have wallet data from opt?
  417. import mmgen.opts as opts
  418. if len(cmd_args) + (wd_from_opt or bool(wf)) < nargs:
  419. if not wf:
  420. msg('No default wallet found, and no other seed source was specified')
  421. opts.usage()
  422. elif len(cmd_args) > nargs:
  423. opts.usage()
  424. elif len(cmd_args) == nargs and wf and invoked_as != 'gen':
  425. qmsg('Warning: overriding default wallet with user-supplied wallet')
  426. if cmd_args or wf:
  427. check_infile(cmd_args[0] if cmd_args else wf)
  428. return cmd_args[0] if cmd_args else (wf,None)[wd_from_opt]
  429. def get_new_passphrase(desc,passchg=False):
  430. w = '{}passphrase for {}'.format(('','new ')[bool(passchg)], desc)
  431. if opt.passwd_file:
  432. pw = ' '.join(get_words_from_file(opt.passwd_file,w))
  433. elif opt.echo_passphrase:
  434. pw = ' '.join(get_words_from_user('Enter {}: '.format(w)))
  435. else:
  436. for i in range(g.passwd_max_tries):
  437. pw = ' '.join(get_words_from_user('Enter {}: '.format(w)))
  438. pw2 = ' '.join(get_words_from_user('Repeat passphrase: '))
  439. dmsg('Passphrases: [{}] [{}]'.format(pw,pw2))
  440. if pw == pw2:
  441. vmsg('Passphrases match'); break
  442. else: msg('Passphrases do not match. Try again.')
  443. else:
  444. die(2,'User failed to duplicate passphrase in {} attempts'.format(g.passwd_max_tries))
  445. if pw == '': qmsg('WARNING: Empty passphrase')
  446. return pw
  447. def confirm_or_raise(message,q,expect='YES',exit_msg='Exiting at user request'):
  448. m = message.strip()
  449. if m: msg(m)
  450. a = q+' ' if q[0].isupper() else 'Are you sure you want to {}?\n'.format(q)
  451. b = "Type uppercase '{}' to confirm: ".format(expect)
  452. if my_raw_input(a+b).strip() != expect:
  453. raise UserNonConfirmation(exit_msg)
  454. def write_data_to_file( outfile,data,desc='data',
  455. ask_write=False,
  456. ask_write_prompt='',
  457. ask_write_default_yes=True,
  458. ask_overwrite=True,
  459. ask_tty=True,
  460. no_tty=False,
  461. quiet=False,
  462. binary=False,
  463. ignore_opt_outdir=False,
  464. check_data=False,
  465. cmp_data=None):
  466. if quiet: ask_tty = ask_overwrite = False
  467. if opt.quiet: ask_overwrite = False
  468. if ask_write_default_yes == False or ask_write_prompt:
  469. ask_write = True
  470. def do_stdout():
  471. qmsg('Output to STDOUT requested')
  472. if g.stdin_tty:
  473. if no_tty:
  474. die(2,'Printing {} to screen is not allowed'.format(desc))
  475. if (ask_tty and not opt.quiet) or binary:
  476. confirm_or_raise('','output {} to screen'.format(desc))
  477. else:
  478. try: of = os.readlink('/proc/{}/fd/1'.format(os.getpid())) # Linux
  479. except: of = None # Windows
  480. if of:
  481. if of[:5] == 'pipe:':
  482. if no_tty:
  483. die(2,'Writing {} to pipe is not allowed'.format(desc))
  484. if ask_tty and not opt.quiet:
  485. confirm_or_raise('','output {} to pipe'.format(desc))
  486. msg('')
  487. of2,pd = os.path.relpath(of),os.path.pardir
  488. msg("Redirecting output to file '{}'".format((of2,of)[of2[:len(pd)] == pd]))
  489. else:
  490. msg('Redirecting output to file')
  491. if binary and g.platform == 'win':
  492. import msvcrt
  493. msvcrt.setmode(sys.stdout.fileno(),os.O_BINARY)
  494. # MSWin workaround. See msg_r()
  495. try:
  496. sys.stdout.write(data.decode() if isinstance(data,bytes) else data)
  497. except:
  498. os.write(1,data if isinstance(data,bytes) else data.encode())
  499. def do_file(outfile,ask_write_prompt):
  500. if opt.outdir and not ignore_opt_outdir and not os.path.isabs(outfile):
  501. outfile = make_full_path(opt.outdir,outfile)
  502. if ask_write:
  503. if not ask_write_prompt: ask_write_prompt = 'Save {}?'.format(desc)
  504. if not keypress_confirm(ask_write_prompt,
  505. default_yes=ask_write_default_yes):
  506. die(1,'{} not saved'.format(capfirst(desc)))
  507. hush = False
  508. if file_exists(outfile) and ask_overwrite:
  509. q = "File '{}' already exists\nOverwrite?".format(outfile)
  510. confirm_or_raise('',q)
  511. msg("Overwriting file '{}'".format(outfile))
  512. hush = True
  513. # not atomic, but better than nothing
  514. # if cmp_data is empty, file can be either empty or non-existent
  515. if check_data:
  516. try:
  517. d = open(outfile,('r','rb')[bool(binary)]).read()
  518. except:
  519. d = ''
  520. finally:
  521. if d != cmp_data:
  522. if g.test_suite:
  523. print_diff(cmp_data,d)
  524. m = "{} in file '{}' has been altered by some other program! Aborting file write"
  525. die(3,m.format(desc,outfile))
  526. # To maintain portability, always open files in binary mode
  527. # If 'binary' option not set, encode/decode data before writing and after reading
  528. f = open_file_or_exit(outfile,'wb')
  529. try:
  530. f.write(data if binary else data.encode())
  531. except:
  532. die(2,"Failed to write {} to file '{}'".format(desc,outfile))
  533. f.close
  534. if not (hush or quiet):
  535. msg("{} written to file '{}'".format(capfirst(desc),outfile))
  536. return True
  537. if opt.stdout or outfile in ('','-'):
  538. do_stdout()
  539. elif sys.stdin.isatty() and not sys.stdout.isatty():
  540. do_stdout()
  541. else:
  542. do_file(outfile,ask_write_prompt)
  543. def get_words_from_user(prompt):
  544. words = my_raw_input(prompt, echo=opt.echo_passphrase).split()
  545. dmsg('Sanitized input: [{}]'.format(' '.join(words)))
  546. return words
  547. def get_words_from_file(infile,desc,quiet=False):
  548. if not quiet:
  549. qmsg("Getting {} from file '{}'".format(desc,infile))
  550. f = open_file_or_exit(infile, 'rb')
  551. try: words = f.read().decode().split()
  552. except: die(1,'{} data must be UTF-8 encoded.'.format(capfirst(desc)))
  553. f.close()
  554. dmsg('Sanitized input: [{}]'.format(' '.join(words)))
  555. return words
  556. def get_words(infile,desc,prompt):
  557. if infile:
  558. return get_words_from_file(infile,desc)
  559. else:
  560. return get_words_from_user(prompt)
  561. def mmgen_decrypt_file_maybe(fn,desc='',quiet=False,silent=False):
  562. d = get_data_from_file(fn,desc,binary=True,quiet=quiet,silent=silent)
  563. have_enc_ext = get_extension(fn) == g.mmenc_ext
  564. if have_enc_ext or not is_utf8(d):
  565. m = ('Attempting to decrypt','Decrypting')[have_enc_ext]
  566. qmsg("{} {} '{}'".format(m,desc,fn))
  567. from .crypto import mmgen_decrypt_retry
  568. d = mmgen_decrypt_retry(d,desc)
  569. return d
  570. def get_lines_from_file(fn,desc='',trim_comments=False,quiet=False,silent=False):
  571. dec = mmgen_decrypt_file_maybe(fn,desc,quiet=quiet,silent=silent)
  572. ret = dec.decode().splitlines()
  573. if trim_comments: ret = remove_comments(ret)
  574. dmsg("Got {} lines from file '{}'".format(len(ret),fn))
  575. return ret
  576. def get_data_from_user(desc='data'): # user input MUST be UTF-8
  577. p = ('','Enter {}: '.format(desc))[g.stdin_tty]
  578. data = my_raw_input(p,echo=opt.echo_passphrase)
  579. dmsg('User input: [{}]'.format(data))
  580. return data
  581. def get_data_from_file(infile,desc='data',dash=False,silent=False,binary=False,quiet=False):
  582. if not opt.quiet and not silent and not quiet and desc:
  583. qmsg("Getting {} from file '{}'".format(desc,infile))
  584. if dash and infile == '-':
  585. data = os.fdopen(0,'rb').read(g.max_input_size+1)
  586. else:
  587. data = open_file_or_exit(infile,'rb',silent=silent).read(g.max_input_size+1)
  588. if not binary:
  589. data = data.decode()
  590. if len(data) == g.max_input_size + 1:
  591. raise MaxInputSizeExceeded('Too much input data! Max input data size: {} bytes'.format(g.max_input_size))
  592. return data
  593. def pwfile_reuse_warning():
  594. if 'passwd_file_used' in globals():
  595. qmsg("Reusing passphrase from file '{}' at user request".format(opt.passwd_file))
  596. return True
  597. globals()['passwd_file_used'] = True
  598. return False
  599. def get_mmgen_passphrase(desc,passchg=False):
  600. prompt ='Enter {}passphrase for {}: '.format(('','old ')[bool(passchg)],desc)
  601. if opt.passwd_file:
  602. pwfile_reuse_warning()
  603. return ' '.join(get_words_from_file(opt.passwd_file,'passphrase'))
  604. else:
  605. return ' '.join(get_words_from_user(prompt))
  606. def my_raw_input(prompt,echo=True,insert_txt='',use_readline=True):
  607. try: import readline
  608. except: use_readline = False # Windows
  609. if use_readline and sys.stdout.isatty():
  610. def st_hook(): readline.insert_text(insert_txt)
  611. readline.set_startup_hook(st_hook)
  612. else:
  613. msg_r(prompt)
  614. prompt = ''
  615. from .term import kb_hold_protect
  616. kb_hold_protect()
  617. if g.test_suite_popen_spawn:
  618. msg(prompt)
  619. sys.stderr.flush()
  620. reply = os.read(0,4096).decode()
  621. elif echo or not sys.stdin.isatty():
  622. reply = input(prompt)
  623. else:
  624. from getpass import getpass
  625. if g.platform == 'win':
  626. # MSWin hack - getpass('foo') doesn't flush stderr
  627. msg_r(prompt.strip()) # getpass('') adds a space
  628. sys.stderr.flush()
  629. reply = getpass('')
  630. else:
  631. reply = getpass(prompt)
  632. kb_hold_protect()
  633. try:
  634. return reply.strip()
  635. except:
  636. die(1,'User input must be UTF-8 encoded.')
  637. def keypress_confirm(prompt,default_yes=False,verbose=False,no_nl=False,complete_prompt=False):
  638. q = ('(y/N)','(Y/n)')[bool(default_yes)]
  639. p = prompt if complete_prompt else '{} {}: '.format(prompt,q)
  640. nl = ('\n','\r{}\r'.format(' '*len(p)))[no_nl]
  641. if opt.accept_defaults:
  642. msg(p)
  643. return default_yes
  644. from .term import get_char
  645. while True:
  646. reply = get_char(p,immed_chars='yYnN').strip('\n\r')
  647. if not reply:
  648. msg_r(nl)
  649. return True if default_yes else False
  650. elif reply in 'yYnN':
  651. msg_r(nl)
  652. return True if reply in 'yY' else False
  653. else:
  654. msg_r('\nInvalid reply\n' if verbose else '\r')
  655. def do_pager(text):
  656. pagers = ['less','more']
  657. end_msg = '\n(end of text)\n\n'
  658. # --- Non-MSYS Windows code deleted ---
  659. # raw, chop, horiz scroll 8 chars, disable buggy line chopping in MSYS
  660. os.environ['LESS'] = (('--shift 8 -RS'),('-cR -#1'))[g.platform=='win']
  661. if 'PAGER' in os.environ and os.environ['PAGER'] != pagers[0]:
  662. pagers = [os.environ['PAGER']] + pagers
  663. for pager in pagers:
  664. try:
  665. from subprocess import run
  666. m = text + ('' if pager == 'less' else end_msg)
  667. p = run([pager],input=m.encode(),check=True)
  668. msg_r('\r')
  669. except:
  670. pass
  671. else:
  672. break
  673. else:
  674. Msg(text+end_msg)
  675. def do_license_msg(immed=False):
  676. if opt.quiet or g.no_license or opt.yes or not g.stdin_tty:
  677. return
  678. p = "Press 'w' for conditions and warranty info, or 'c' to continue:"
  679. import mmgen.license as gpl
  680. msg(gpl.warning)
  681. prompt = '{} '.format(p.strip())
  682. from .term import get_char
  683. while True:
  684. reply = get_char(prompt, immed_chars=('','wc')[bool(immed)])
  685. if reply == 'w':
  686. do_pager(gpl.conditions)
  687. elif reply == 'c':
  688. msg('')
  689. break
  690. else:
  691. msg_r('\r')
  692. msg('')
  693. # TODO: these belong in protocol.py
  694. def get_coin_daemon_cfg_fn():
  695. # Use dirname() to remove 'bob' or 'alice' component
  696. cfg_dir = os.path.dirname(g.data_dir) if g.regtest else g.proto.daemon_data_dir
  697. return os.path.join(cfg_dir,g.proto.name+'.conf' )
  698. def get_coin_daemon_cfg_options(req_keys):
  699. fn = get_coin_daemon_cfg_fn()
  700. try:
  701. lines = get_lines_from_file(fn,'',silent=not opt.verbose)
  702. except:
  703. vmsg(f'Warning: {fn!r} does not exist or is unreadable')
  704. return dict((k,None) for k in req_keys)
  705. def gen():
  706. for key in req_keys:
  707. val = None
  708. for l in lines:
  709. if l.startswith(key):
  710. res = l.split('=',1)
  711. if len(res) == 2 and not ' ' in res[1].strip():
  712. val = res[1].strip()
  713. yield (key,val)
  714. return dict(gen())
  715. def format_par(s,indent=0,width=80,as_list=False):
  716. words,lines = s.split(),[]
  717. assert width >= indent + 4,'width must be >= indent + 4'
  718. while words:
  719. line = ''
  720. while len(line) <= (width-indent) and words:
  721. if line and len(line) + len(words[0]) + 1 > width-indent: break
  722. line += ('',' ')[bool(line)] + words.pop(0)
  723. lines.append(' '*indent + line)
  724. return lines if as_list else '\n'.join(lines) + '\n'
  725. # module loading magic for tx.py and tw.py
  726. def altcoin_subclass(cls,mod_id,cls_name):
  727. if cls.__name__ != cls_name: return cls
  728. mod_dir = g.proto.base_coin.lower()
  729. pname = g.proto.class_pfx if hasattr(g.proto,'class_pfx') else capfirst(g.proto.name)
  730. tname = 'Token' if g.token else ''
  731. import importlib
  732. modname = 'mmgen.altcoins.{}.{}'.format(mod_dir,mod_id)
  733. clsname = '{}{}{}'.format(pname,tname,cls_name)
  734. try:
  735. return getattr(importlib.import_module(modname),clsname)
  736. except ImportError:
  737. return cls
  738. # decorator for TrackingWallet
  739. def write_mode(orig_func):
  740. def f(self,*args,**kwargs):
  741. if self.mode != 'w':
  742. m = '{} opened in read-only mode: cannot execute method {}()'
  743. die(1,m.format(type(self).__name__,locals()['orig_func'].__name__))
  744. return orig_func(self,*args,**kwargs)
  745. return f
  746. def get_network_id(coin=None,testnet=None):
  747. if coin == None: assert testnet == None
  748. if coin != None: assert testnet != None
  749. return (coin or g.coin).lower() + ('','_tn')[testnet or g.testnet]
  750. def run_session(callback,do_rpc_init=True,proto=None,backend=None):
  751. backend = backend or opt.rpc_backend
  752. proto = proto or g.proto
  753. import asyncio
  754. async def do():
  755. if backend == 'aiohttp':
  756. import aiohttp
  757. async with aiohttp.ClientSession(
  758. headers = { 'Content-Type': 'application/json' },
  759. connector = aiohttp.TCPConnector(limit_per_host=g.aiohttp_rpc_queue_len),
  760. ) as g.session:
  761. if do_rpc_init:
  762. from .rpc import rpc_init
  763. await rpc_init(proto=proto,backend=backend)
  764. ret = await callback
  765. g.session = None
  766. return ret
  767. else:
  768. if do_rpc_init:
  769. from .rpc import rpc_init
  770. await rpc_init(proto=proto,backend=backend)
  771. return await callback
  772. # return asyncio.run(do()) # Python 3.7+
  773. return asyncio.get_event_loop().run_until_complete(do())