Repository: borgbackup/borg

Issue:
IndexError: tuple index out of range (1.1.0 server)
1.0.6 client talking to 1.1.0 server:
```
Creating a backup at Fri Oct 20 04:30:02 CEST 2017 on x ...
pyimod03_importers Remote: Local Exception
pyimod03_importers Remote: Traceback (most recent call last):
Remote:   File "borg/archiver.py", line 4024, in main
Remote:   File "borg/archiver.py", line 3952, in run
Remote:   File "borg/archiver.py", line 238, in do_serve
Remote:   File "borg/remote.py", line 227, in serve
Remote:   File "borg/remote.py", line 183, in positional_to_named
Remote:   File "borg/remote.py", line 183, in <dictcomp>
Remote: IndexError: tuple index out of range
Remote: 
Remote: Platform: FreeBSD x.rsync.net 10.3-RELEASE FreeBSD 10.3-RELEASE #0: Wed Aug 30 20:40:07 CEST 2017     root@x.rsync.net:/usr/src/sys/amd64/compile/x_10.3 amd64 
Remote: Borg: 1.1.0  Python: CPython 3.5.2
Remote: PID: 88751  CWD: /data1/home/x
Remote: sys.argv: ['/usr/local/bin/borg1/borg1', 'serve', '--restrict-to-path', 'x']
Remote: SSH_ORIGINAL_COMMAND: None
Remote: 
Connection closed by remote host
terminating with error status, rc 2
```


Discussion:
```
    def positional_to_named(self, method, argv):
        """Translate from positional protocol to named protocol."""
        return {name: argv[pos] for pos, name in enumerate(compatMap[method])}
```

So, the tuple in the error msg refers to `argv` and it seems there is some `method` where `compatMap[method]` has more elements than the `argv` in this case:

```
compatMap = {
    'check': ('repair', 'save_space', ),
    'commit': ('save_space', ),
    'rollback': (),
    'destroy': (),
    '__len__': (),
    'list': ('limit', 'marker', ),
    'put': ('id', 'data', ),
    'get': ('id', ),
    'delete': ('id', ),
    'save_key': ('keydata', ),
    'load_key': (),
    'break_lock': (),
    'negotiate': ('client_data', ),
    'open': ('path', 'create', 'lock_wait', 'lock', 'exclusive', 'append_only', ),
    'get_free_nonce': (),
    'commit_nonce_reservation': ('next_unreserved', 'start_nonce', ),
}
```
Upgrading the client borg from 1.0.6 to 1.0.11 fixes the issue.
1.0.6 client does a 4 args open():
```
self.id = self.call('open', self.location.path, create, lock_wait, lock)
```

clients >= 1.0.7 do a 6 args open() first, then fall back to 4 args open() when receiving a TypeError from server:
```
self.id = self.call('open', self.location.path, create, lock_wait, lock, exclusive, append_only)
self.id = self.call('open', self.location.path, create, lock_wait, lock)
```


Resolving patch:
diff --git a/src/borg/remote.py b/src/borg/remote.py
--- a/src/borg/remote.py
+++ b/src/borg/remote.py
@@ -179,7 +179,15 @@ def __init__(self, restrict_to_paths, restrict_to_repositories, append_only, sto
 
     def positional_to_named(self, method, argv):
         """Translate from positional protocol to named protocol."""
-        return {name: argv[pos] for pos, name in enumerate(compatMap[method])}
+        try:
+            return {name: argv[pos] for pos, name in enumerate(compatMap[method])}
+        except IndexError:
+            if method == 'open' and len(argv) == 4:
+                # borg clients < 1.0.7 use open() with 4 args
+                mapping = compatMap[method][:4]
+            else:
+                raise
+            return {name: argv[pos] for pos, name in enumerate(mapping)}
 
     def filter_args(self, f, kwargs):
         """Remove unknown named parameters from call, because client did (implicitly) say it's ok."""


Test patch: