Repository: borgbackup/borg

Issue:
borg mount crashes with empty archive
if one tries to mount a completely empty archive, borg mount crashes.

note: usually this never happens as one does not have empty archives.



Discussion:
```
(borg-env)tw@tux:~/w/borg$ borg mount -f repo::1 mnt
Local Exception.
Traceback (most recent call last):
  File "/home/tw/w/borg/borg/fuse.py", line 173, in get_item
    return self.items[inode]
KeyError: 1

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/tw/w/borg/borg/archiver.py", line 1615, in main
    exit_code = archiver.run(args)
  File "/home/tw/w/borg/borg/archiver.py", line 1552, in run
    return args.func(args)
  File "/home/tw/w/borg/borg/archiver.py", line 85, in wrapper
    return method(self, args, repository=repository, **kwargs)
  File "/home/tw/w/borg/borg/archiver.py", line 488, in do_mount
    operations.mount(args.mountpoint, args.options, args.foreground)
  File "/home/tw/w/borg/borg/fuse.py", line 100, in mount
    signal = fuse_main()
  File "/home/tw/w/borg/borg/fuse.py", line 25, in fuse_main
    return llfuse.main(workers=1)
  File "src/fuse_api.pxi", line 343, in llfuse.main (src/llfuse.c:35968)
  File "src/handlers.pxi", line 80, in llfuse.fuse_getattr (src/llfuse.c:8699)
  File "src/handlers.pxi", line 81, in llfuse.fuse_getattr (src/llfuse.c:8646)
  File "/home/tw/w/borg/borg/fuse.py", line 185, in getattr
    item = self.get_item(inode)
  File "/home/tw/w/borg/borg/fuse.py", line 175, in get_item
    return self.cache.get(inode)
  File "/home/tw/w/borg/borg/fuse.py", line 43, in get
    self.fd.seek(inode - self.offset, io.SEEK_SET)
OSError: [Errno 22] Invalid argument

Platform: Linux tux 4.6.4-tw2 #4 SMP Mon Jul 18 15:44:28 CEST 2016 x86_64 x86_64
Linux: Ubuntu 16.04 xenial
Borg: 1.0.7.dev16+ng10ecb17  Python: CPython 3.5.1+
PID: 25973  CWD: /home/tw/w/borg
sys.argv: ['/home/tw/w/borg-env/bin/borg', 'mount', '-f', 'repo::1', 'mnt']
SSH_ORIGINAL_COMMAND: None
```

Do we just want to abort early if the archive is empty?

Empty directory would be better I think. A fresh FS with no contents is also just an empty directory, just like an empty archive is a bucket of nothingness... floating in spaaaaaaaaace.

@enkore Makes sense, especially if this is used in scripts.


Resolving patch:
diff --git a/borg/fuse.py b/borg/fuse.py
--- a/borg/fuse.py
+++ b/borg/fuse.py
@@ -66,17 +66,13 @@ def __init__(self, key, repository, manifest, archive, cached_repo):
         data_cache_capacity = int(os.environ.get('BORG_MOUNT_DATA_CACHE_ENTRIES', os.cpu_count() or 1))
         logger.debug('mount data cache capacity: %d chunks', data_cache_capacity)
         self.data_cache = LRUCache(capacity=data_cache_capacity, dispose=lambda _: None)
+        self._create_dir(parent=1)  # first call, create root dir (inode == 1)
         if archive:
             self.process_archive(archive)
         else:
-            # Create root inode
-            self.parent[1] = self.allocate_inode()
-            self.items[1] = self.default_dir
             for archive_name in manifest.archives:
                 # Create archive placeholder inode
-                archive_inode = self.allocate_inode()
-                self.items[archive_inode] = self.default_dir
-                self.parent[archive_inode] = 1
+                archive_inode = self._create_dir(parent=1)
                 self.contents[1][os.fsencode(archive_name)] = archive_inode
                 self.pending_archives[archive_inode] = Archive(repository, key, manifest, archive_name)
 
@@ -106,6 +102,14 @@ def mount(self, mountpoint, mount_options, foreground=False):
         finally:
             llfuse.close(umount)
 
+    def _create_dir(self, parent):
+        """Create directory
+        """
+        ino = self.allocate_inode()
+        self.items[ino] = self.default_dir
+        self.parent[ino] = parent
+        return ino
+
     def process_archive(self, archive, prefix=[]):
         """Build fuse inode hierarchy from archive metadata
         """
@@ -129,11 +133,6 @@ def process_archive(self, archive, prefix=[]):
                 num_segments = len(segments)
                 parent = 1
                 for i, segment in enumerate(segments, 1):
-                    # Insert a default root inode if needed
-                    if self._inode_count == 0 and segment:
-                        archive_inode = self.allocate_inode()
-                        self.items[archive_inode] = self.default_dir
-                        self.parent[archive_inode] = parent
                     # Leaf segment?
                     if i == num_segments:
                         if b'source' in item and stat.S_ISREG(item[b'mode']):
@@ -149,9 +148,7 @@ def process_archive(self, archive, prefix=[]):
                     elif segment in self.contents[parent]:
                         parent = self.contents[parent][segment]
                     else:
-                        inode = self.allocate_inode()
-                        self.items[inode] = self.default_dir
-                        self.parent[inode] = parent
+                        inode = self._create_dir(parent)
                         if segment:
                             self.contents[parent][segment] = inode
                         parent = inode


Test patch: