We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN 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.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `borg/fuse.py`
Content:
```
1 from collections import defaultdict
2 import errno
3 import io
4 import llfuse
5 import os
6 import stat
7 import tempfile
8 import time
9 from distutils.version import LooseVersion
10 
11 import msgpack
12 
13 from .archive import Archive
14 from .helpers import daemonize, bigint_to_int
15 from .logger import create_logger
16 from .lrucache import LRUCache
17 logger = create_logger()
18 
19 
20 # Does this version of llfuse support ns precision?
21 have_fuse_xtime_ns = hasattr(llfuse.EntryAttributes, 'st_mtime_ns')
22 
23 fuse_version = LooseVersion(getattr(llfuse, '__version__', '0.1'))
24 if fuse_version >= '0.42':
25     def fuse_main():
26         return llfuse.main(workers=1)
27 else:
28     def fuse_main():
29         llfuse.main(single=True)
30         return None
31 
32 
33 class ItemCache:
34     def __init__(self):
35         self.fd = tempfile.TemporaryFile(prefix='borg-tmp')
36         self.offset = 1000000
37 
38     def add(self, item):
39         pos = self.fd.seek(0, io.SEEK_END)
40         self.fd.write(msgpack.packb(item))
41         return pos + self.offset
42 
43     def get(self, inode):
44         self.fd.seek(inode - self.offset, io.SEEK_SET)
45         return next(msgpack.Unpacker(self.fd, read_size=1024))
46 
47 
48 class FuseOperations(llfuse.Operations):
49     """Export archive as a fuse filesystem
50     """
51 
52     allow_damaged_files = False
53 
54     def __init__(self, key, repository, manifest, archive, cached_repo):
55         super().__init__()
56         self._inode_count = 0
57         self.key = key
58         self.repository = cached_repo
59         self.items = {}
60         self.parent = {}
61         self.contents = defaultdict(dict)
62         self.default_dir = {b'mode': 0o40755, b'mtime': int(time.time() * 1e9), b'uid': os.getuid(), b'gid': os.getgid()}
63         self.pending_archives = {}
64         self.accounted_chunks = {}
65         self.cache = ItemCache()
66         data_cache_capacity = int(os.environ.get('BORG_MOUNT_DATA_CACHE_ENTRIES', os.cpu_count() or 1))
67         logger.debug('mount data cache capacity: %d chunks', data_cache_capacity)
68         self.data_cache = LRUCache(capacity=data_cache_capacity, dispose=lambda _: None)
69         if archive:
70             self.process_archive(archive)
71         else:
72             # Create root inode
73             self.parent[1] = self.allocate_inode()
74             self.items[1] = self.default_dir
75             for archive_name in manifest.archives:
76                 # Create archive placeholder inode
77                 archive_inode = self.allocate_inode()
78                 self.items[archive_inode] = self.default_dir
79                 self.parent[archive_inode] = 1
80                 self.contents[1][os.fsencode(archive_name)] = archive_inode
81                 self.pending_archives[archive_inode] = Archive(repository, key, manifest, archive_name)
82 
83     def mount(self, mountpoint, mount_options, foreground=False):
84         """Mount filesystem on *mountpoint* with *mount_options*."""
85         options = ['fsname=borgfs', 'ro']
86         if mount_options:
87             options.extend(mount_options.split(','))
88         try:
89             options.remove('allow_damaged_files')
90             self.allow_damaged_files = True
91         except ValueError:
92             pass
93         llfuse.init(self, mountpoint, options)
94         if not foreground:
95             daemonize()
96 
97         # If the file system crashes, we do not want to umount because in that
98         # case the mountpoint suddenly appears to become empty. This can have
99         # nasty consequences, imagine the user has e.g. an active rsync mirror
100         # job - seeing the mountpoint empty, rsync would delete everything in the
101         # mirror.
102         umount = False
103         try:
104             signal = fuse_main()
105             umount = (signal is None)  # no crash and no signal -> umount request
106         finally:
107             llfuse.close(umount)
108 
109     def process_archive(self, archive, prefix=[]):
110         """Build fuse inode hierarchy from archive metadata
111         """
112         unpacker = msgpack.Unpacker()
113         for key, chunk in zip(archive.metadata[b'items'], self.repository.get_many(archive.metadata[b'items'])):
114             data = self.key.decrypt(key, chunk)
115             unpacker.feed(data)
116             for item in unpacker:
117                 try:
118                     # This can happen if an archive was created with a command line like
119                     # $ borg create ... dir1/file dir1
120                     # In this case the code below will have created a default_dir inode for dir1 already.
121                     inode = self._find_inode(item[b'path'], prefix)
122                 except KeyError:
123                     pass
124                 else:
125                     self.items[inode] = item
126                     continue
127                 segments = prefix + os.fsencode(os.path.normpath(item[b'path'])).split(b'/')
128                 del item[b'path']
129                 num_segments = len(segments)
130                 parent = 1
131                 for i, segment in enumerate(segments, 1):
132                     # Insert a default root inode if needed
133                     if self._inode_count == 0 and segment:
134                         archive_inode = self.allocate_inode()
135                         self.items[archive_inode] = self.default_dir
136                         self.parent[archive_inode] = parent
137                     # Leaf segment?
138                     if i == num_segments:
139                         if b'source' in item and stat.S_ISREG(item[b'mode']):
140                             inode = self._find_inode(item[b'source'], prefix)
141                             item = self.cache.get(inode)
142                             item[b'nlink'] = item.get(b'nlink', 1) + 1
143                             self.items[inode] = item
144                         else:
145                             inode = self.cache.add(item)
146                         self.parent[inode] = parent
147                         if segment:
148                             self.contents[parent][segment] = inode
149                     elif segment in self.contents[parent]:
150                         parent = self.contents[parent][segment]
151                     else:
152                         inode = self.allocate_inode()
153                         self.items[inode] = self.default_dir
154                         self.parent[inode] = parent
155                         if segment:
156                             self.contents[parent][segment] = inode
157                         parent = inode
158 
159     def allocate_inode(self):
160         self._inode_count += 1
161         return self._inode_count
162 
163     def statfs(self, ctx=None):
164         stat_ = llfuse.StatvfsData()
165         stat_.f_bsize = 512
166         stat_.f_frsize = 512
167         stat_.f_blocks = 0
168         stat_.f_bfree = 0
169         stat_.f_bavail = 0
170         stat_.f_files = 0
171         stat_.f_ffree = 0
172         stat_.f_favail = 0
173         return stat_
174 
175     def get_item(self, inode):
176         try:
177             return self.items[inode]
178         except KeyError:
179             return self.cache.get(inode)
180 
181     def _find_inode(self, path, prefix=[]):
182         segments = prefix + os.fsencode(os.path.normpath(path)).split(b'/')
183         inode = 1
184         for segment in segments:
185             inode = self.contents[inode][segment]
186         return inode
187 
188     def getattr(self, inode, ctx=None):
189         item = self.get_item(inode)
190         size = 0
191         dsize = 0
192         try:
193             for key, chunksize, _ in item[b'chunks']:
194                 size += chunksize
195                 if self.accounted_chunks.get(key, inode) == inode:
196                     self.accounted_chunks[key] = inode
197                     dsize += chunksize
198         except KeyError:
199             pass
200         entry = llfuse.EntryAttributes()
201         entry.st_ino = inode
202         entry.generation = 0
203         entry.entry_timeout = 300
204         entry.attr_timeout = 300
205         entry.st_mode = item[b'mode']
206         entry.st_nlink = item.get(b'nlink', 1)
207         entry.st_uid = item[b'uid']
208         entry.st_gid = item[b'gid']
209         entry.st_rdev = item.get(b'rdev', 0)
210         entry.st_size = size
211         entry.st_blksize = 512
212         entry.st_blocks = dsize / 512
213         # note: older archives only have mtime (not atime nor ctime)
214         if have_fuse_xtime_ns:
215             entry.st_mtime_ns = bigint_to_int(item[b'mtime'])
216             if b'atime' in item:
217                 entry.st_atime_ns = bigint_to_int(item[b'atime'])
218             else:
219                 entry.st_atime_ns = bigint_to_int(item[b'mtime'])
220             if b'ctime' in item:
221                 entry.st_ctime_ns = bigint_to_int(item[b'ctime'])
222             else:
223                 entry.st_ctime_ns = bigint_to_int(item[b'mtime'])
224         else:
225             entry.st_mtime = bigint_to_int(item[b'mtime']) / 1e9
226             if b'atime' in item:
227                 entry.st_atime = bigint_to_int(item[b'atime']) / 1e9
228             else:
229                 entry.st_atime = bigint_to_int(item[b'mtime']) / 1e9
230             if b'ctime' in item:
231                 entry.st_ctime = bigint_to_int(item[b'ctime']) / 1e9
232             else:
233                 entry.st_ctime = bigint_to_int(item[b'mtime']) / 1e9
234         return entry
235 
236     def listxattr(self, inode, ctx=None):
237         item = self.get_item(inode)
238         return item.get(b'xattrs', {}).keys()
239 
240     def getxattr(self, inode, name, ctx=None):
241         item = self.get_item(inode)
242         try:
243             return item.get(b'xattrs', {})[name]
244         except KeyError:
245             raise llfuse.FUSEError(llfuse.ENOATTR) from None
246 
247     def _load_pending_archive(self, inode):
248         # Check if this is an archive we need to load
249         archive = self.pending_archives.pop(inode, None)
250         if archive:
251             self.process_archive(archive, [os.fsencode(archive.name)])
252 
253     def lookup(self, parent_inode, name, ctx=None):
254         self._load_pending_archive(parent_inode)
255         if name == b'.':
256             inode = parent_inode
257         elif name == b'..':
258             inode = self.parent[parent_inode]
259         else:
260             inode = self.contents[parent_inode].get(name)
261             if not inode:
262                 raise llfuse.FUSEError(errno.ENOENT)
263         return self.getattr(inode)
264 
265     def open(self, inode, flags, ctx=None):
266         if not self.allow_damaged_files:
267             item = self.get_item(inode)
268             if b'chunks_healthy' in item:
269                 # Processed archive items don't carry the path anymore; for converting the inode
270                 # to the path we'd either have to store the inverse of the current structure,
271                 # or search the entire archive. So we just don't print it. It's easy to correlate anyway.
272                 logger.warning('File has damaged (all-zero) chunks. Try running borg check --repair. '
273                                'Mount with allow_damaged_files to read damaged files.')
274                 raise llfuse.FUSEError(errno.EIO)
275         return inode
276 
277     def opendir(self, inode, ctx=None):
278         self._load_pending_archive(inode)
279         return inode
280 
281     def read(self, fh, offset, size):
282         parts = []
283         item = self.get_item(fh)
284         for id, s, csize in item[b'chunks']:
285             if s < offset:
286                 offset -= s
287                 continue
288             n = min(size, s - offset)
289             if id in self.data_cache:
290                 data = self.data_cache[id]
291                 if offset + n == len(data):
292                     # evict fully read chunk from cache
293                     del self.data_cache[id]
294             else:
295                 data = self.key.decrypt(id, self.repository.get(id))
296                 if offset + n < len(data):
297                     # chunk was only partially read, cache it
298                     self.data_cache[id] = data
299             parts.append(data[offset:offset + n])
300             offset = 0
301             size -= n
302             if not size:
303                 break
304         return b''.join(parts)
305 
306     def readdir(self, fh, off):
307         entries = [(b'.', fh), (b'..', self.parent[fh])]
308         entries.extend(self.contents[fh].items())
309         for i, (name, inode) in enumerate(entries[off:], off):
310             yield name, self.getattr(inode), i + 1
311 
312     def readlink(self, inode, ctx=None):
313         item = self.get_item(inode)
314         return os.fsencode(item[b'source'])
315
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/borg/fuse.py b/borg/fuse.py
--- a/borg/fuse.py
+++ b/borg/fuse.py
@@ -66,17 +66,13 @@
         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 @@
         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 @@
                 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 @@
                     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

{"golden_diff": "diff --git a/borg/fuse.py b/borg/fuse.py\n--- a/borg/fuse.py\n+++ b/borg/fuse.py\n@@ -66,17 +66,13 @@\n         data_cache_capacity = int(os.environ.get('BORG_MOUNT_DATA_CACHE_ENTRIES', os.cpu_count() or 1))\n         logger.debug('mount data cache capacity: %d chunks', data_cache_capacity)\n         self.data_cache = LRUCache(capacity=data_cache_capacity, dispose=lambda _: None)\n+        self._create_dir(parent=1)  # first call, create root dir (inode == 1)\n         if archive:\n             self.process_archive(archive)\n         else:\n-            # Create root inode\n-            self.parent[1] = self.allocate_inode()\n-            self.items[1] = self.default_dir\n             for archive_name in manifest.archives:\n                 # Create archive placeholder inode\n-                archive_inode = self.allocate_inode()\n-                self.items[archive_inode] = self.default_dir\n-                self.parent[archive_inode] = 1\n+                archive_inode = self._create_dir(parent=1)\n                 self.contents[1][os.fsencode(archive_name)] = archive_inode\n                 self.pending_archives[archive_inode] = Archive(repository, key, manifest, archive_name)\n \n@@ -106,6 +102,14 @@\n         finally:\n             llfuse.close(umount)\n \n+    def _create_dir(self, parent):\n+        \"\"\"Create directory\n+        \"\"\"\n+        ino = self.allocate_inode()\n+        self.items[ino] = self.default_dir\n+        self.parent[ino] = parent\n+        return ino\n+\n     def process_archive(self, archive, prefix=[]):\n         \"\"\"Build fuse inode hierarchy from archive metadata\n         \"\"\"\n@@ -129,11 +133,6 @@\n                 num_segments = len(segments)\n                 parent = 1\n                 for i, segment in enumerate(segments, 1):\n-                    # Insert a default root inode if needed\n-                    if self._inode_count == 0 and segment:\n-                        archive_inode = self.allocate_inode()\n-                        self.items[archive_inode] = self.default_dir\n-                        self.parent[archive_inode] = parent\n                     # Leaf segment?\n                     if i == num_segments:\n                         if b'source' in item and stat.S_ISREG(item[b'mode']):\n@@ -149,9 +148,7 @@\n                     elif segment in self.contents[parent]:\n                         parent = self.contents[parent][segment]\n                     else:\n-                        inode = self.allocate_inode()\n-                        self.items[inode] = self.default_dir\n-                        self.parent[inode] = parent\n+                        inode = self._create_dir(parent)\n                         if segment:\n                             self.contents[parent][segment] = inode\n                         parent = inode\n", "issue": "borg mount crashes with empty archive\nif one tries to mount a completely empty archive, borg mount crashes.\n\nnote: usually this never happens as one does not have empty archives.\n\n", "before_files": [{"content": "from collections import defaultdict\nimport errno\nimport io\nimport llfuse\nimport os\nimport stat\nimport tempfile\nimport time\nfrom distutils.version import LooseVersion\n\nimport msgpack\n\nfrom .archive import Archive\nfrom .helpers import daemonize, bigint_to_int\nfrom .logger import create_logger\nfrom .lrucache import LRUCache\nlogger = create_logger()\n\n\n# Does this version of llfuse support ns precision?\nhave_fuse_xtime_ns = hasattr(llfuse.EntryAttributes, 'st_mtime_ns')\n\nfuse_version = LooseVersion(getattr(llfuse, '__version__', '0.1'))\nif fuse_version >= '0.42':\n    def fuse_main():\n        return llfuse.main(workers=1)\nelse:\n    def fuse_main():\n        llfuse.main(single=True)\n        return None\n\n\nclass ItemCache:\n    def __init__(self):\n        self.fd = tempfile.TemporaryFile(prefix='borg-tmp')\n        self.offset = 1000000\n\n    def add(self, item):\n        pos = self.fd.seek(0, io.SEEK_END)\n        self.fd.write(msgpack.packb(item))\n        return pos + self.offset\n\n    def get(self, inode):\n        self.fd.seek(inode - self.offset, io.SEEK_SET)\n        return next(msgpack.Unpacker(self.fd, read_size=1024))\n\n\nclass FuseOperations(llfuse.Operations):\n    \"\"\"Export archive as a fuse filesystem\n    \"\"\"\n\n    allow_damaged_files = False\n\n    def __init__(self, key, repository, manifest, archive, cached_repo):\n        super().__init__()\n        self._inode_count = 0\n        self.key = key\n        self.repository = cached_repo\n        self.items = {}\n        self.parent = {}\n        self.contents = defaultdict(dict)\n        self.default_dir = {b'mode': 0o40755, b'mtime': int(time.time() * 1e9), b'uid': os.getuid(), b'gid': os.getgid()}\n        self.pending_archives = {}\n        self.accounted_chunks = {}\n        self.cache = ItemCache()\n        data_cache_capacity = int(os.environ.get('BORG_MOUNT_DATA_CACHE_ENTRIES', os.cpu_count() or 1))\n        logger.debug('mount data cache capacity: %d chunks', data_cache_capacity)\n        self.data_cache = LRUCache(capacity=data_cache_capacity, dispose=lambda _: None)\n        if archive:\n            self.process_archive(archive)\n        else:\n            # Create root inode\n            self.parent[1] = self.allocate_inode()\n            self.items[1] = self.default_dir\n            for archive_name in manifest.archives:\n                # Create archive placeholder inode\n                archive_inode = self.allocate_inode()\n                self.items[archive_inode] = self.default_dir\n                self.parent[archive_inode] = 1\n                self.contents[1][os.fsencode(archive_name)] = archive_inode\n                self.pending_archives[archive_inode] = Archive(repository, key, manifest, archive_name)\n\n    def mount(self, mountpoint, mount_options, foreground=False):\n        \"\"\"Mount filesystem on *mountpoint* with *mount_options*.\"\"\"\n        options = ['fsname=borgfs', 'ro']\n        if mount_options:\n            options.extend(mount_options.split(','))\n        try:\n            options.remove('allow_damaged_files')\n            self.allow_damaged_files = True\n        except ValueError:\n            pass\n        llfuse.init(self, mountpoint, options)\n        if not foreground:\n            daemonize()\n\n        # If the file system crashes, we do not want to umount because in that\n        # case the mountpoint suddenly appears to become empty. This can have\n        # nasty consequences, imagine the user has e.g. an active rsync mirror\n        # job - seeing the mountpoint empty, rsync would delete everything in the\n        # mirror.\n        umount = False\n        try:\n            signal = fuse_main()\n            umount = (signal is None)  # no crash and no signal -> umount request\n        finally:\n            llfuse.close(umount)\n\n    def process_archive(self, archive, prefix=[]):\n        \"\"\"Build fuse inode hierarchy from archive metadata\n        \"\"\"\n        unpacker = msgpack.Unpacker()\n        for key, chunk in zip(archive.metadata[b'items'], self.repository.get_many(archive.metadata[b'items'])):\n            data = self.key.decrypt(key, chunk)\n            unpacker.feed(data)\n            for item in unpacker:\n                try:\n                    # This can happen if an archive was created with a command line like\n                    # $ borg create ... dir1/file dir1\n                    # In this case the code below will have created a default_dir inode for dir1 already.\n                    inode = self._find_inode(item[b'path'], prefix)\n                except KeyError:\n                    pass\n                else:\n                    self.items[inode] = item\n                    continue\n                segments = prefix + os.fsencode(os.path.normpath(item[b'path'])).split(b'/')\n                del item[b'path']\n                num_segments = len(segments)\n                parent = 1\n                for i, segment in enumerate(segments, 1):\n                    # Insert a default root inode if needed\n                    if self._inode_count == 0 and segment:\n                        archive_inode = self.allocate_inode()\n                        self.items[archive_inode] = self.default_dir\n                        self.parent[archive_inode] = parent\n                    # Leaf segment?\n                    if i == num_segments:\n                        if b'source' in item and stat.S_ISREG(item[b'mode']):\n                            inode = self._find_inode(item[b'source'], prefix)\n                            item = self.cache.get(inode)\n                            item[b'nlink'] = item.get(b'nlink', 1) + 1\n                            self.items[inode] = item\n                        else:\n                            inode = self.cache.add(item)\n                        self.parent[inode] = parent\n                        if segment:\n                            self.contents[parent][segment] = inode\n                    elif segment in self.contents[parent]:\n                        parent = self.contents[parent][segment]\n                    else:\n                        inode = self.allocate_inode()\n                        self.items[inode] = self.default_dir\n                        self.parent[inode] = parent\n                        if segment:\n                            self.contents[parent][segment] = inode\n                        parent = inode\n\n    def allocate_inode(self):\n        self._inode_count += 1\n        return self._inode_count\n\n    def statfs(self, ctx=None):\n        stat_ = llfuse.StatvfsData()\n        stat_.f_bsize = 512\n        stat_.f_frsize = 512\n        stat_.f_blocks = 0\n        stat_.f_bfree = 0\n        stat_.f_bavail = 0\n        stat_.f_files = 0\n        stat_.f_ffree = 0\n        stat_.f_favail = 0\n        return stat_\n\n    def get_item(self, inode):\n        try:\n            return self.items[inode]\n        except KeyError:\n            return self.cache.get(inode)\n\n    def _find_inode(self, path, prefix=[]):\n        segments = prefix + os.fsencode(os.path.normpath(path)).split(b'/')\n        inode = 1\n        for segment in segments:\n            inode = self.contents[inode][segment]\n        return inode\n\n    def getattr(self, inode, ctx=None):\n        item = self.get_item(inode)\n        size = 0\n        dsize = 0\n        try:\n            for key, chunksize, _ in item[b'chunks']:\n                size += chunksize\n                if self.accounted_chunks.get(key, inode) == inode:\n                    self.accounted_chunks[key] = inode\n                    dsize += chunksize\n        except KeyError:\n            pass\n        entry = llfuse.EntryAttributes()\n        entry.st_ino = inode\n        entry.generation = 0\n        entry.entry_timeout = 300\n        entry.attr_timeout = 300\n        entry.st_mode = item[b'mode']\n        entry.st_nlink = item.get(b'nlink', 1)\n        entry.st_uid = item[b'uid']\n        entry.st_gid = item[b'gid']\n        entry.st_rdev = item.get(b'rdev', 0)\n        entry.st_size = size\n        entry.st_blksize = 512\n        entry.st_blocks = dsize / 512\n        # note: older archives only have mtime (not atime nor ctime)\n        if have_fuse_xtime_ns:\n            entry.st_mtime_ns = bigint_to_int(item[b'mtime'])\n            if b'atime' in item:\n                entry.st_atime_ns = bigint_to_int(item[b'atime'])\n            else:\n                entry.st_atime_ns = bigint_to_int(item[b'mtime'])\n            if b'ctime' in item:\n                entry.st_ctime_ns = bigint_to_int(item[b'ctime'])\n            else:\n                entry.st_ctime_ns = bigint_to_int(item[b'mtime'])\n        else:\n            entry.st_mtime = bigint_to_int(item[b'mtime']) / 1e9\n            if b'atime' in item:\n                entry.st_atime = bigint_to_int(item[b'atime']) / 1e9\n            else:\n                entry.st_atime = bigint_to_int(item[b'mtime']) / 1e9\n            if b'ctime' in item:\n                entry.st_ctime = bigint_to_int(item[b'ctime']) / 1e9\n            else:\n                entry.st_ctime = bigint_to_int(item[b'mtime']) / 1e9\n        return entry\n\n    def listxattr(self, inode, ctx=None):\n        item = self.get_item(inode)\n        return item.get(b'xattrs', {}).keys()\n\n    def getxattr(self, inode, name, ctx=None):\n        item = self.get_item(inode)\n        try:\n            return item.get(b'xattrs', {})[name]\n        except KeyError:\n            raise llfuse.FUSEError(llfuse.ENOATTR) from None\n\n    def _load_pending_archive(self, inode):\n        # Check if this is an archive we need to load\n        archive = self.pending_archives.pop(inode, None)\n        if archive:\n            self.process_archive(archive, [os.fsencode(archive.name)])\n\n    def lookup(self, parent_inode, name, ctx=None):\n        self._load_pending_archive(parent_inode)\n        if name == b'.':\n            inode = parent_inode\n        elif name == b'..':\n            inode = self.parent[parent_inode]\n        else:\n            inode = self.contents[parent_inode].get(name)\n            if not inode:\n                raise llfuse.FUSEError(errno.ENOENT)\n        return self.getattr(inode)\n\n    def open(self, inode, flags, ctx=None):\n        if not self.allow_damaged_files:\n            item = self.get_item(inode)\n            if b'chunks_healthy' in item:\n                # Processed archive items don't carry the path anymore; for converting the inode\n                # to the path we'd either have to store the inverse of the current structure,\n                # or search the entire archive. So we just don't print it. It's easy to correlate anyway.\n                logger.warning('File has damaged (all-zero) chunks. Try running borg check --repair. '\n                               'Mount with allow_damaged_files to read damaged files.')\n                raise llfuse.FUSEError(errno.EIO)\n        return inode\n\n    def opendir(self, inode, ctx=None):\n        self._load_pending_archive(inode)\n        return inode\n\n    def read(self, fh, offset, size):\n        parts = []\n        item = self.get_item(fh)\n        for id, s, csize in item[b'chunks']:\n            if s < offset:\n                offset -= s\n                continue\n            n = min(size, s - offset)\n            if id in self.data_cache:\n                data = self.data_cache[id]\n                if offset + n == len(data):\n                    # evict fully read chunk from cache\n                    del self.data_cache[id]\n            else:\n                data = self.key.decrypt(id, self.repository.get(id))\n                if offset + n < len(data):\n                    # chunk was only partially read, cache it\n                    self.data_cache[id] = data\n            parts.append(data[offset:offset + n])\n            offset = 0\n            size -= n\n            if not size:\n                break\n        return b''.join(parts)\n\n    def readdir(self, fh, off):\n        entries = [(b'.', fh), (b'..', self.parent[fh])]\n        entries.extend(self.contents[fh].items())\n        for i, (name, inode) in enumerate(entries[off:], off):\n            yield name, self.getattr(inode), i + 1\n\n    def readlink(self, inode, ctx=None):\n        item = self.get_item(inode)\n        return os.fsencode(item[b'source'])\n", "path": "borg/fuse.py"}], "after_files": [{"content": "from collections import defaultdict\nimport errno\nimport io\nimport llfuse\nimport os\nimport stat\nimport tempfile\nimport time\nfrom distutils.version import LooseVersion\n\nimport msgpack\n\nfrom .archive import Archive\nfrom .helpers import daemonize, bigint_to_int\nfrom .logger import create_logger\nfrom .lrucache import LRUCache\nlogger = create_logger()\n\n\n# Does this version of llfuse support ns precision?\nhave_fuse_xtime_ns = hasattr(llfuse.EntryAttributes, 'st_mtime_ns')\n\nfuse_version = LooseVersion(getattr(llfuse, '__version__', '0.1'))\nif fuse_version >= '0.42':\n    def fuse_main():\n        return llfuse.main(workers=1)\nelse:\n    def fuse_main():\n        llfuse.main(single=True)\n        return None\n\n\nclass ItemCache:\n    def __init__(self):\n        self.fd = tempfile.TemporaryFile(prefix='borg-tmp')\n        self.offset = 1000000\n\n    def add(self, item):\n        pos = self.fd.seek(0, io.SEEK_END)\n        self.fd.write(msgpack.packb(item))\n        return pos + self.offset\n\n    def get(self, inode):\n        self.fd.seek(inode - self.offset, io.SEEK_SET)\n        return next(msgpack.Unpacker(self.fd, read_size=1024))\n\n\nclass FuseOperations(llfuse.Operations):\n    \"\"\"Export archive as a fuse filesystem\n    \"\"\"\n\n    allow_damaged_files = False\n\n    def __init__(self, key, repository, manifest, archive, cached_repo):\n        super().__init__()\n        self._inode_count = 0\n        self.key = key\n        self.repository = cached_repo\n        self.items = {}\n        self.parent = {}\n        self.contents = defaultdict(dict)\n        self.default_dir = {b'mode': 0o40755, b'mtime': int(time.time() * 1e9), b'uid': os.getuid(), b'gid': os.getgid()}\n        self.pending_archives = {}\n        self.accounted_chunks = {}\n        self.cache = ItemCache()\n        data_cache_capacity = int(os.environ.get('BORG_MOUNT_DATA_CACHE_ENTRIES', os.cpu_count() or 1))\n        logger.debug('mount data cache capacity: %d chunks', data_cache_capacity)\n        self.data_cache = LRUCache(capacity=data_cache_capacity, dispose=lambda _: None)\n        self._create_dir(parent=1)  # first call, create root dir (inode == 1)\n        if archive:\n            self.process_archive(archive)\n        else:\n            for archive_name in manifest.archives:\n                # Create archive placeholder inode\n                archive_inode = self._create_dir(parent=1)\n                self.contents[1][os.fsencode(archive_name)] = archive_inode\n                self.pending_archives[archive_inode] = Archive(repository, key, manifest, archive_name)\n\n    def mount(self, mountpoint, mount_options, foreground=False):\n        \"\"\"Mount filesystem on *mountpoint* with *mount_options*.\"\"\"\n        options = ['fsname=borgfs', 'ro']\n        if mount_options:\n            options.extend(mount_options.split(','))\n        try:\n            options.remove('allow_damaged_files')\n            self.allow_damaged_files = True\n        except ValueError:\n            pass\n        llfuse.init(self, mountpoint, options)\n        if not foreground:\n            daemonize()\n\n        # If the file system crashes, we do not want to umount because in that\n        # case the mountpoint suddenly appears to become empty. This can have\n        # nasty consequences, imagine the user has e.g. an active rsync mirror\n        # job - seeing the mountpoint empty, rsync would delete everything in the\n        # mirror.\n        umount = False\n        try:\n            signal = fuse_main()\n            umount = (signal is None)  # no crash and no signal -> umount request\n        finally:\n            llfuse.close(umount)\n\n    def _create_dir(self, parent):\n        \"\"\"Create directory\n        \"\"\"\n        ino = self.allocate_inode()\n        self.items[ino] = self.default_dir\n        self.parent[ino] = parent\n        return ino\n\n    def process_archive(self, archive, prefix=[]):\n        \"\"\"Build fuse inode hierarchy from archive metadata\n        \"\"\"\n        unpacker = msgpack.Unpacker()\n        for key, chunk in zip(archive.metadata[b'items'], self.repository.get_many(archive.metadata[b'items'])):\n            data = self.key.decrypt(key, chunk)\n            unpacker.feed(data)\n            for item in unpacker:\n                try:\n                    # This can happen if an archive was created with a command line like\n                    # $ borg create ... dir1/file dir1\n                    # In this case the code below will have created a default_dir inode for dir1 already.\n                    inode = self._find_inode(item[b'path'], prefix)\n                except KeyError:\n                    pass\n                else:\n                    self.items[inode] = item\n                    continue\n                segments = prefix + os.fsencode(os.path.normpath(item[b'path'])).split(b'/')\n                del item[b'path']\n                num_segments = len(segments)\n                parent = 1\n                for i, segment in enumerate(segments, 1):\n                    # Leaf segment?\n                    if i == num_segments:\n                        if b'source' in item and stat.S_ISREG(item[b'mode']):\n                            inode = self._find_inode(item[b'source'], prefix)\n                            item = self.cache.get(inode)\n                            item[b'nlink'] = item.get(b'nlink', 1) + 1\n                            self.items[inode] = item\n                        else:\n                            inode = self.cache.add(item)\n                        self.parent[inode] = parent\n                        if segment:\n                            self.contents[parent][segment] = inode\n                    elif segment in self.contents[parent]:\n                        parent = self.contents[parent][segment]\n                    else:\n                        inode = self._create_dir(parent)\n                        if segment:\n                            self.contents[parent][segment] = inode\n                        parent = inode\n\n    def allocate_inode(self):\n        self._inode_count += 1\n        return self._inode_count\n\n    def statfs(self, ctx=None):\n        stat_ = llfuse.StatvfsData()\n        stat_.f_bsize = 512\n        stat_.f_frsize = 512\n        stat_.f_blocks = 0\n        stat_.f_bfree = 0\n        stat_.f_bavail = 0\n        stat_.f_files = 0\n        stat_.f_ffree = 0\n        stat_.f_favail = 0\n        return stat_\n\n    def get_item(self, inode):\n        try:\n            return self.items[inode]\n        except KeyError:\n            return self.cache.get(inode)\n\n    def _find_inode(self, path, prefix=[]):\n        segments = prefix + os.fsencode(os.path.normpath(path)).split(b'/')\n        inode = 1\n        for segment in segments:\n            inode = self.contents[inode][segment]\n        return inode\n\n    def getattr(self, inode, ctx=None):\n        item = self.get_item(inode)\n        size = 0\n        dsize = 0\n        try:\n            for key, chunksize, _ in item[b'chunks']:\n                size += chunksize\n                if self.accounted_chunks.get(key, inode) == inode:\n                    self.accounted_chunks[key] = inode\n                    dsize += chunksize\n        except KeyError:\n            pass\n        entry = llfuse.EntryAttributes()\n        entry.st_ino = inode\n        entry.generation = 0\n        entry.entry_timeout = 300\n        entry.attr_timeout = 300\n        entry.st_mode = item[b'mode']\n        entry.st_nlink = item.get(b'nlink', 1)\n        entry.st_uid = item[b'uid']\n        entry.st_gid = item[b'gid']\n        entry.st_rdev = item.get(b'rdev', 0)\n        entry.st_size = size\n        entry.st_blksize = 512\n        entry.st_blocks = dsize / 512\n        # note: older archives only have mtime (not atime nor ctime)\n        if have_fuse_xtime_ns:\n            entry.st_mtime_ns = bigint_to_int(item[b'mtime'])\n            if b'atime' in item:\n                entry.st_atime_ns = bigint_to_int(item[b'atime'])\n            else:\n                entry.st_atime_ns = bigint_to_int(item[b'mtime'])\n            if b'ctime' in item:\n                entry.st_ctime_ns = bigint_to_int(item[b'ctime'])\n            else:\n                entry.st_ctime_ns = bigint_to_int(item[b'mtime'])\n        else:\n            entry.st_mtime = bigint_to_int(item[b'mtime']) / 1e9\n            if b'atime' in item:\n                entry.st_atime = bigint_to_int(item[b'atime']) / 1e9\n            else:\n                entry.st_atime = bigint_to_int(item[b'mtime']) / 1e9\n            if b'ctime' in item:\n                entry.st_ctime = bigint_to_int(item[b'ctime']) / 1e9\n            else:\n                entry.st_ctime = bigint_to_int(item[b'mtime']) / 1e9\n        return entry\n\n    def listxattr(self, inode, ctx=None):\n        item = self.get_item(inode)\n        return item.get(b'xattrs', {}).keys()\n\n    def getxattr(self, inode, name, ctx=None):\n        item = self.get_item(inode)\n        try:\n            return item.get(b'xattrs', {})[name]\n        except KeyError:\n            raise llfuse.FUSEError(llfuse.ENOATTR) from None\n\n    def _load_pending_archive(self, inode):\n        # Check if this is an archive we need to load\n        archive = self.pending_archives.pop(inode, None)\n        if archive:\n            self.process_archive(archive, [os.fsencode(archive.name)])\n\n    def lookup(self, parent_inode, name, ctx=None):\n        self._load_pending_archive(parent_inode)\n        if name == b'.':\n            inode = parent_inode\n        elif name == b'..':\n            inode = self.parent[parent_inode]\n        else:\n            inode = self.contents[parent_inode].get(name)\n            if not inode:\n                raise llfuse.FUSEError(errno.ENOENT)\n        return self.getattr(inode)\n\n    def open(self, inode, flags, ctx=None):\n        if not self.allow_damaged_files:\n            item = self.get_item(inode)\n            if b'chunks_healthy' in item:\n                # Processed archive items don't carry the path anymore; for converting the inode\n                # to the path we'd either have to store the inverse of the current structure,\n                # or search the entire archive. So we just don't print it. It's easy to correlate anyway.\n                logger.warning('File has damaged (all-zero) chunks. Try running borg check --repair. '\n                               'Mount with allow_damaged_files to read damaged files.')\n                raise llfuse.FUSEError(errno.EIO)\n        return inode\n\n    def opendir(self, inode, ctx=None):\n        self._load_pending_archive(inode)\n        return inode\n\n    def read(self, fh, offset, size):\n        parts = []\n        item = self.get_item(fh)\n        for id, s, csize in item[b'chunks']:\n            if s < offset:\n                offset -= s\n                continue\n            n = min(size, s - offset)\n            if id in self.data_cache:\n                data = self.data_cache[id]\n                if offset + n == len(data):\n                    # evict fully read chunk from cache\n                    del self.data_cache[id]\n            else:\n                data = self.key.decrypt(id, self.repository.get(id))\n                if offset + n < len(data):\n                    # chunk was only partially read, cache it\n                    self.data_cache[id] = data\n            parts.append(data[offset:offset + n])\n            offset = 0\n            size -= n\n            if not size:\n                break\n        return b''.join(parts)\n\n    def readdir(self, fh, off):\n        entries = [(b'.', fh), (b'..', self.parent[fh])]\n        entries.extend(self.contents[fh].items())\n        for i, (name, inode) in enumerate(entries[off:], off):\n            yield name, self.getattr(inode), i + 1\n\n    def readlink(self, inode, ctx=None):\n        item = self.get_item(inode)\n        return os.fsencode(item[b'source'])\n", "path": "borg/fuse.py"}]}