|  | @@ -63,12 +63,16 @@ class RepositoryServer:  # pragma: no cover
 | 
	
		
			
				|  |  |      def serve(self):
 | 
	
		
			
				|  |  |          stdin_fd = sys.stdin.fileno()
 | 
	
		
			
				|  |  |          stdout_fd = sys.stdout.fileno()
 | 
	
		
			
				|  |  | +        stderr_fd = sys.stdout.fileno()
 | 
	
		
			
				|  |  |          # Make stdin non-blocking
 | 
	
		
			
				|  |  |          fl = fcntl.fcntl(stdin_fd, fcntl.F_GETFL)
 | 
	
		
			
				|  |  |          fcntl.fcntl(stdin_fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
 | 
	
		
			
				|  |  |          # Make stdout blocking
 | 
	
		
			
				|  |  |          fl = fcntl.fcntl(stdout_fd, fcntl.F_GETFL)
 | 
	
		
			
				|  |  |          fcntl.fcntl(stdout_fd, fcntl.F_SETFL, fl & ~os.O_NONBLOCK)
 | 
	
		
			
				|  |  | +        # Make stderr blocking
 | 
	
		
			
				|  |  | +        fl = fcntl.fcntl(stderr_fd, fcntl.F_GETFL)
 | 
	
		
			
				|  |  | +        fcntl.fcntl(stderr_fd, fcntl.F_SETFL, fl & ~os.O_NONBLOCK)
 | 
	
		
			
				|  |  |          unpacker = msgpack.Unpacker(use_list=False)
 | 
	
		
			
				|  |  |          while True:
 | 
	
		
			
				|  |  |              r, w, es = select.select([stdin_fd], [], [], 10)
 | 
	
	
		
			
				|  | @@ -91,6 +95,7 @@ class RepositoryServer:  # pragma: no cover
 | 
	
		
			
				|  |  |                              f = getattr(self.repository, method)
 | 
	
		
			
				|  |  |                          res = f(*args)
 | 
	
		
			
				|  |  |                      except BaseException as e:
 | 
	
		
			
				|  |  | +                        # XXX rather log exception
 | 
	
		
			
				|  |  |                          exc = "Remote Traceback by Borg %s%s%s" % (__version__, os.linesep, traceback.format_exc())
 | 
	
		
			
				|  |  |                          os.write(stdout_fd, msgpack.packb((1, msgid, e.__class__.__name__, exc)))
 | 
	
		
			
				|  |  |                      else:
 | 
	
	
		
			
				|  | @@ -137,13 +142,15 @@ class RemoteRepository:
 | 
	
		
			
				|  |  |          borg_cmd = self.borg_cmd(args, testing)
 | 
	
		
			
				|  |  |          if not testing:
 | 
	
		
			
				|  |  |              borg_cmd = self.ssh_cmd(location) + borg_cmd
 | 
	
		
			
				|  |  | -        self.p = Popen(borg_cmd, bufsize=0, stdin=PIPE, stdout=PIPE)
 | 
	
		
			
				|  |  | +        self.p = Popen(borg_cmd, bufsize=0, stdin=PIPE, stdout=PIPE, stderr=PIPE)
 | 
	
		
			
				|  |  |          self.stdin_fd = self.p.stdin.fileno()
 | 
	
		
			
				|  |  |          self.stdout_fd = self.p.stdout.fileno()
 | 
	
		
			
				|  |  | +        self.stderr_fd = self.p.stderr.fileno()
 | 
	
		
			
				|  |  |          fcntl.fcntl(self.stdin_fd, fcntl.F_SETFL, fcntl.fcntl(self.stdin_fd, fcntl.F_GETFL) | os.O_NONBLOCK)
 | 
	
		
			
				|  |  |          fcntl.fcntl(self.stdout_fd, fcntl.F_SETFL, fcntl.fcntl(self.stdout_fd, fcntl.F_GETFL) | os.O_NONBLOCK)
 | 
	
		
			
				|  |  | -        self.r_fds = [self.stdout_fd]
 | 
	
		
			
				|  |  | -        self.x_fds = [self.stdin_fd, self.stdout_fd]
 | 
	
		
			
				|  |  | +        fcntl.fcntl(self.stderr_fd, fcntl.F_SETFL, fcntl.fcntl(self.stderr_fd, fcntl.F_GETFL) | os.O_NONBLOCK)
 | 
	
		
			
				|  |  | +        self.r_fds = [self.stdout_fd, self.stderr_fd]
 | 
	
		
			
				|  |  | +        self.x_fds = [self.stdin_fd, self.stdout_fd, self.stderr_fd]
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          try:
 | 
	
		
			
				|  |  |              version = self.call('negotiate', RPC_PROTOCOL_VERSION)
 | 
	
	
		
			
				|  | @@ -238,19 +245,32 @@ class RemoteRepository:
 | 
	
		
			
				|  |  |              r, w, x = select.select(self.r_fds, w_fds, self.x_fds, 1)
 | 
	
		
			
				|  |  |              if x:
 | 
	
		
			
				|  |  |                  raise Exception('FD exception occurred')
 | 
	
		
			
				|  |  | -            if r:
 | 
	
		
			
				|  |  | -                data = os.read(self.stdout_fd, BUFSIZE)
 | 
	
		
			
				|  |  | -                if not data:
 | 
	
		
			
				|  |  | -                    raise ConnectionClosed()
 | 
	
		
			
				|  |  | -                self.unpacker.feed(data)
 | 
	
		
			
				|  |  | -                for unpacked in self.unpacker:
 | 
	
		
			
				|  |  | -                    if not (isinstance(unpacked, tuple) and len(unpacked) == 4):
 | 
	
		
			
				|  |  | -                        raise Exception("Unexpected RPC data format.")
 | 
	
		
			
				|  |  | -                    type, msgid, error, res = unpacked
 | 
	
		
			
				|  |  | -                    if msgid in self.ignore_responses:
 | 
	
		
			
				|  |  | -                        self.ignore_responses.remove(msgid)
 | 
	
		
			
				|  |  | -                    else:
 | 
	
		
			
				|  |  | -                        self.responses[msgid] = error, res
 | 
	
		
			
				|  |  | +            for fd in r:
 | 
	
		
			
				|  |  | +                if fd is self.stdout_fd:
 | 
	
		
			
				|  |  | +                    data = os.read(fd, BUFSIZE)
 | 
	
		
			
				|  |  | +                    if not data:
 | 
	
		
			
				|  |  | +                        raise ConnectionClosed()
 | 
	
		
			
				|  |  | +                    self.unpacker.feed(data)
 | 
	
		
			
				|  |  | +                    for unpacked in self.unpacker:
 | 
	
		
			
				|  |  | +                        if not (isinstance(unpacked, tuple) and len(unpacked) == 4):
 | 
	
		
			
				|  |  | +                            raise Exception("Unexpected RPC data format.")
 | 
	
		
			
				|  |  | +                        type, msgid, error, res = unpacked
 | 
	
		
			
				|  |  | +                        if msgid in self.ignore_responses:
 | 
	
		
			
				|  |  | +                            self.ignore_responses.remove(msgid)
 | 
	
		
			
				|  |  | +                        else:
 | 
	
		
			
				|  |  | +                            self.responses[msgid] = error, res
 | 
	
		
			
				|  |  | +                elif fd is self.stderr_fd:
 | 
	
		
			
				|  |  | +                    data = os.read(fd, 32768)
 | 
	
		
			
				|  |  | +                    if not data:
 | 
	
		
			
				|  |  | +                        raise ConnectionClosed()
 | 
	
		
			
				|  |  | +                    data = data.decode('utf-8')
 | 
	
		
			
				|  |  | +                    for line in data.splitlines():
 | 
	
		
			
				|  |  | +                        if line.startswith('$LOG '):
 | 
	
		
			
				|  |  | +                            _, level, msg = line.split(' ', 2)
 | 
	
		
			
				|  |  | +                            level = getattr(logging, level, logging.CRITICAL)  # str -> int
 | 
	
		
			
				|  |  | +                            logging.log(level, msg)
 | 
	
		
			
				|  |  | +                        else:
 | 
	
		
			
				|  |  | +                            print("Remote: " + line, file=sys.stderr)
 | 
	
		
			
				|  |  |              if w:
 | 
	
		
			
				|  |  |                  while not self.to_send and (calls or self.preload_ids) and len(waiting_for) < 100:
 | 
	
		
			
				|  |  |                      if calls:
 |