async def restore_complete_doit(fname_or_fd, words): from main import dis # build password password = '******'.join(words) # filename already picked, taste it and maybe consider using it's data. try: fd = open(fname_or_fd, 'rb') if isinstance(fname_or_fd, str) else fname_or_fd except: await ux_show_story('Unable to open backup file. \n\n' + str(fname_or_fd)) return try: if not words: contents = fd.read() else: try: compat7z.check_file_headers(fd) except Exception as e: await ux_show_story('Unable to read backup file. Has it been touched?' '\n\nError: ' + str(e)) return dis.fullscreen("Decrypting...") try: zz = compat7z.Builder() fname, contents = zz.read_file(fd, password, progress_fcn=dis.progress_bar_show) assert fname == 'ckcc-backup.txt', "Wrong filename in archive" # simple quick sanity check assert contents[0:1] == b'#' and contents[-1:] == b'\n', "Corrupted after decrypt" except Exception as e: # assume everything here is "password wrong" errors print("pw wrong? %s" % e) await ux_show_story('Unable to decrypt backup file. Incorrect password?' '\n\nTried:\n\n' + password) return finally: fd.close() vals = {} for line in contents.decode().split('\n'): if not line: continue if line[0] == '#': continue try: k,v = line.split(' = ', 1) #print("%s = %s" % (k, v)) vals[k] = ujson.loads(v) except: print("unable to decode line: %r" % line) # but keep going! await restore_from_dict(vals)
async def verify_backup_file(fname_or_fd): # read 7z header, and measure checksums # - no password is wanted/required # - really just checking CRC32, but that's enough against truncated files from files import CardSlot, CardMissingError from actions import needs_microsd prob = None fd = None # filename already picked, open it. try: with CardSlot() as card: prob = 'Unable to open backup file.' fd = open(fname_or_fd, 'rb') if isinstance(fname_or_fd, str) else fname_or_fd prob = 'Unable to read backup file headers. Might be truncated.' compat7z.check_file_headers(fd) prob = 'Unable to verify backup file contents.' zz = compat7z.Builder() files = zz.verify_file_crc(fd, MAX_BACKUP_FILE_SIZE) assert len(files) == 1 fname, fsize = files[0] assert fname == 'ckcc-backup.txt' assert 400 < fsize < MAX_BACKUP_FILE_SIZE, 'size' except CardMissingError: await needs_microsd() return except Exception as e: await ux_show_story(prob + '\n\nError: ' + str(e)) return finally: if fd: fd.close() await ux_show_story( "Backup file CRC checks out okay.\n\nPlease note this is only a check against accidental truncation and similar. Targeted modifications can still pass this test." )
async def restore_complete_doit(fname_or_fd, words): # Open file, read it, maybe decrypt it; return string if any error # - some errors will be shown, None return in that case # - no return if successful (due to reboot) from main import dis from files import CardSlot, CardMissingError from actions import needs_microsd # build password password = '******'.join(words) prob = None try: with CardSlot() as card: # filename already picked, taste it and maybe consider using its data. try: fd = open(fname_or_fd, 'rb') if isinstance( fname_or_fd, str) else fname_or_fd except: return 'Unable to open backup file.\n\n' + str(fname_or_fd) try: if not words: contents = fd.read() else: try: compat7z.check_file_headers(fd) except Exception as e: return 'Unable to read backup file. Has it been touched?\n\nError: ' \ + str(e) dis.fullscreen("Decrypting...") try: zz = compat7z.Builder() fname, contents = zz.read_file( fd, password, MAX_BACKUP_FILE_SIZE, progress_fcn=dis.progress_bar_show) # simple quick sanity checks assert fname == 'ckcc-backup.txt' assert contents[0:1] == b'#' and contents[-1:] == b'\n' except Exception as e: # assume everything here is "password wrong" errors #print("pw wrong? %s" % e) return ( 'Unable to decrypt backup file. Incorrect password?' '\n\nTried:\n\n' + password) finally: fd.close() except CardMissingError: await needs_microsd() return vals = {} for line in contents.decode().split('\n'): if not line: continue if line[0] == '#': continue try: k, v = line.split(' = ', 1) #print("%s = %s" % (k, v)) vals[k] = ujson.loads(v) except: print("unable to decode line: %r" % line) # but keep going! # this leads to reboot if it works, else errors shown, etc. return await restore_from_dict(vals)
async def write_complete_backup(words, fname_pattern, write_sflash): # Just do the writing from main import dis, pa, settings from files import CardSlot, CardMissingError # Show progress: dis.fullscreen('Encrypting...' if words else 'Generating...') body = render_backup_contents().encode() gc.collect() if words: # NOTE: Takes a few seconds to do the key-streching, but little actual # time to do the encryption. pw = ' '.join(words) zz = compat7z.Builder(password=pw, progress_fcn=dis.progress_bar_show) zz.add_data(body) hdr, footer = zz.save('ckcc-backup.txt') filesize = len(body) + MAX_BACKUP_FILE_SIZE del body gc.collect() else: # cleartext dump zz = None filesize = len(body) + 10 if write_sflash: # for use over USB and unit testing: commit file into SPI flash from sffile import SFFile with SFFile(0, max_size=filesize, message='Saving...') as fd: await fd.erase() if zz: fd.write(hdr) fd.write(zz.body) fd.write(footer) else: fd.write(body) return fd.tell(), fd.checksum.digest() for copy in range(25): # choose a filename try: with CardSlot() as card: fname, nice = card.pick_filename(fname_pattern) # do actual write with open(fname, 'wb') as fd: if zz: fd.write(hdr) fd.write(zz.body) fd.write(footer) else: fd.write(body) except Exception as e: # includes CardMissingError import sys sys.print_exception(e) # catch any error ch = await ux_show_story( 'Failed to write! Please insert formated MicroSD card, ' 'and press OK to try again.\n\nX to cancel.\n\n\n' + str(e)) if ch == 'x': break continue if copy == 0: while 1: msg = '''Backup file written:\n\n%s\n\n\ To view or restore the file, you must have the full password.\n\n\ Insert another SD card and press 2 to make another copy.''' % (nice) ch = await ux_show_story(msg, escape='2') if ch == 'y': return if ch == '2': break else: ch = await ux_show_story('''File (#%d) written:\n\n%s\n\n\ Press OK for another copy, or press X to stop.''' % (copy + 1, nice), escape='2') if ch == 'x': break