|
| 1 | +# python2 module |
| 2 | +import re |
| 3 | +import duplicity.backend |
| 4 | +import duplicity.urlparse_2_5 as urlparser |
| 5 | +import subprocess |
| 6 | +from duplicity.backends.qubesintervmbackendprivate.common import Commands, StatusCodes, write_zero_terminated_ascii, sanitize_filename |
| 7 | +from os.path import basename |
| 8 | +from shutil import copyfileobj |
| 9 | + |
| 10 | + |
| 11 | +class QubesInterVmBackend(duplicity.backend.Backend): |
| 12 | + def __init__(self, parsed_url): |
| 13 | + duplicity.backend.Backend.__init__(self, parsed_url) |
| 14 | + print(parsed_url.path) |
| 15 | + pat = re.compile("^/+([^/]+)/([^/]+)$") |
| 16 | + parts = pat.match(parsed_url.path) |
| 17 | + self.vm = parts.group(1) |
| 18 | + self.path = parts.group(2) |
| 19 | + |
| 20 | + def _call_command(self, command_letter): |
| 21 | + return _QubesInterVmBackendChannel(command_letter, self.path, self.vm); |
| 22 | + |
| 23 | + def _check_for_error_message(self, inp): |
| 24 | + response = inp.read(1) |
| 25 | + if response == StatusCodes.ERROR: |
| 26 | + raise Exception("Error messge from the other end: "+inp.read(1024).decode('ascii')) |
| 27 | + elif response == StatusCodes.OK: |
| 28 | + print("got OK code") |
| 29 | + return inp # OK |
| 30 | + elif len(response) == 0: |
| 31 | + raise Exception("Unexpected empty response") |
| 32 | + else: |
| 33 | + raise Exception("Unexpected response code: "+str(ord(response))) |
| 34 | + |
| 35 | + def _read_all(self, proc): |
| 36 | + return self._check_for_error_message(proc.stdout).read() |
| 37 | + |
| 38 | + # Methods required for Duplicity: |
| 39 | + def _list(self): |
| 40 | + with self._call_command(Commands.LIST) as proc: |
| 41 | + return map(sanitize_filename, self._read_all(proc).decode('ascii').split('\0')) |
| 42 | + |
| 43 | + def put(self, source_path, remote_filename = None): |
| 44 | + name = remote_filename or source_path.get_filename() |
| 45 | + with source_path.open("rb") as f, self._call_command(Commands.PUT) as proc: |
| 46 | + write_zero_terminated_ascii(proc.stdin, name) |
| 47 | + copyfileobj(f, proc.stdin) |
| 48 | + proc.stdin.close() # let the peer know we are finished. TODO: close automatically in _call_command? |
| 49 | + self._check_for_error_message(proc.stdout) |
| 50 | + |
| 51 | + def get(self, remote_filename, local_path): |
| 52 | + with local_path.open("w") as f, self._call_command(Commands.GET) as proc: |
| 53 | + write_zero_terminated_ascii(proc.stdin, remote_filename) |
| 54 | + proc.stdin.close() |
| 55 | + self._check_for_error_message(proc.stdout) |
| 56 | + copyfileobj(proc.stdout, f) |
| 57 | + |
| 58 | + # TODO: |
| 59 | + # def delete(self, filename_list) <-- not needed yet |
| 60 | + |
| 61 | + |
| 62 | +class _QubesInterVmBackendChannel: |
| 63 | + def __init__(self, command_letter, path, vm): |
| 64 | + self.command_letter = command_letter |
| 65 | + self.path = path |
| 66 | + self.vm = vm |
| 67 | + self.initialized = False |
| 68 | + def __enter__(self): |
| 69 | + self.proc = subprocess.Popen([ |
| 70 | + "/usr/lib/qubes/qrexec-client-vm", |
| 71 | + self.vm, |
| 72 | + "v6ak.QubesInterVmBackupStorage" |
| 73 | + ], stdin = subprocess.PIPE, stdout = subprocess.PIPE)# , stderr = subprocess.PIPE |
| 74 | + try: |
| 75 | + self.proc.stdin.write(self.command_letter) |
| 76 | + write_zero_terminated_ascii(self.proc.stdin, self.path) |
| 77 | + except: |
| 78 | + self.__exit__(*sys.exc_info()) |
| 79 | + raise |
| 80 | + self.initialized = True |
| 81 | + return self.proc |
| 82 | + def __exit__(self, type, value, traceback): |
| 83 | + def close(f): |
| 84 | + if f is not None: |
| 85 | + f.close() |
| 86 | + def check_empty(f): |
| 87 | + if f is None: return |
| 88 | + res = f.read(1) |
| 89 | + if len(res) <> 0: |
| 90 | + raise Exception("Unexpected byte "+str(ord(res))) |
| 91 | + try: |
| 92 | + if self.initialized and (type is None): # Do not perform sanity checks when an exception is thrown, as they would likely fail and hide the root of cause |
| 93 | + # Assert that nothing remains |
| 94 | + check_empty(self.proc.stdout) |
| 95 | + check_empty(self.proc.stderr) |
| 96 | + finally: |
| 97 | + close(self.proc.stdin) |
| 98 | + close(self.proc.stderr) |
| 99 | + close(self.proc.stdout) |
| 100 | + return_code = self.proc.wait() |
| 101 | + if return_code <> 0: |
| 102 | + raise Exception("process did not finish with success: "+str(return_code)) |
| 103 | + |
| 104 | +duplicity.backend.register_backend('qubesintervm', QubesInterVmBackend) |
0 commit comments