|
@@ -6,11 +6,16 @@ import os
|
|
|
import stat
|
|
|
import tempfile
|
|
|
import time
|
|
|
-from .archive import Archive
|
|
|
-from .helpers import daemonize, bigint_to_int
|
|
|
from distutils.version import LooseVersion
|
|
|
+
|
|
|
import msgpack
|
|
|
|
|
|
+from .archive import Archive
|
|
|
+from .helpers import daemonize, bigint_to_int
|
|
|
+from .logger import create_logger
|
|
|
+logger = create_logger()
|
|
|
+
|
|
|
+
|
|
|
# Does this version of llfuse support ns precision?
|
|
|
have_fuse_xtime_ns = hasattr(llfuse.EntryAttributes, 'st_mtime_ns')
|
|
|
|
|
@@ -42,6 +47,9 @@ class ItemCache:
|
|
|
class FuseOperations(llfuse.Operations):
|
|
|
"""Export archive as a fuse filesystem
|
|
|
"""
|
|
|
+
|
|
|
+ allow_damaged_files = False
|
|
|
+
|
|
|
def __init__(self, key, repository, manifest, archive, cached_repo):
|
|
|
super().__init__()
|
|
|
self._inode_count = 0
|
|
@@ -68,6 +76,32 @@ class FuseOperations(llfuse.Operations):
|
|
|
self.contents[1][os.fsencode(archive_name)] = archive_inode
|
|
|
self.pending_archives[archive_inode] = Archive(repository, key, manifest, archive_name)
|
|
|
|
|
|
+ def mount(self, mountpoint, mount_options, foreground=False):
|
|
|
+ """Mount filesystem on *mountpoint* with *mount_options*."""
|
|
|
+ options = ['fsname=borgfs', 'ro']
|
|
|
+ if mount_options:
|
|
|
+ options.extend(mount_options.split(','))
|
|
|
+ try:
|
|
|
+ options.remove('allow_damaged_files')
|
|
|
+ self.allow_damaged_files = True
|
|
|
+ except ValueError:
|
|
|
+ pass
|
|
|
+ llfuse.init(self, mountpoint, options)
|
|
|
+ if not foreground:
|
|
|
+ daemonize()
|
|
|
+
|
|
|
+ # If the file system crashes, we do not want to umount because in that
|
|
|
+ # case the mountpoint suddenly appears to become empty. This can have
|
|
|
+ # nasty consequences, imagine the user has e.g. an active rsync mirror
|
|
|
+ # job - seeing the mountpoint empty, rsync would delete everything in the
|
|
|
+ # mirror.
|
|
|
+ umount = False
|
|
|
+ try:
|
|
|
+ signal = fuse_main()
|
|
|
+ umount = (signal is None) # no crash and no signal -> umount request
|
|
|
+ finally:
|
|
|
+ llfuse.close(umount)
|
|
|
+
|
|
|
def process_archive(self, archive, prefix=[]):
|
|
|
"""Build fuse inode hierarchy from archive metadata
|
|
|
"""
|
|
@@ -225,6 +259,15 @@ class FuseOperations(llfuse.Operations):
|
|
|
return self.getattr(inode)
|
|
|
|
|
|
def open(self, inode, flags, ctx=None):
|
|
|
+ if not self.allow_damaged_files:
|
|
|
+ item = self.get_item(inode)
|
|
|
+ if b'chunks_healthy' in item:
|
|
|
+ # Processed archive items don't carry the path anymore; for converting the inode
|
|
|
+ # to the path we'd either have to store the inverse of the current structure,
|
|
|
+ # or search the entire archive. So we just don't print it. It's easy to correlate anyway.
|
|
|
+ logger.warning('File has damaged (all-zero) chunks. Try running borg check --repair. '
|
|
|
+ 'Mount with allow_damaged_files to read damaged files.')
|
|
|
+ raise llfuse.FUSEError(errno.EIO)
|
|
|
return inode
|
|
|
|
|
|
def opendir(self, inode, ctx=None):
|
|
@@ -256,23 +299,3 @@ class FuseOperations(llfuse.Operations):
|
|
|
def readlink(self, inode, ctx=None):
|
|
|
item = self.get_item(inode)
|
|
|
return os.fsencode(item[b'source'])
|
|
|
-
|
|
|
- def mount(self, mountpoint, extra_options, foreground=False):
|
|
|
- options = ['fsname=borgfs', 'ro']
|
|
|
- if extra_options:
|
|
|
- options.extend(extra_options.split(','))
|
|
|
- llfuse.init(self, mountpoint, options)
|
|
|
- if not foreground:
|
|
|
- daemonize()
|
|
|
-
|
|
|
- # If the file system crashes, we do not want to umount because in that
|
|
|
- # case the mountpoint suddenly appears to become empty. This can have
|
|
|
- # nasty consequences, imagine the user has e.g. an active rsync mirror
|
|
|
- # job - seeing the mountpoint empty, rsync would delete everything in the
|
|
|
- # mirror.
|
|
|
- umount = False
|
|
|
- try:
|
|
|
- signal = fuse_main()
|
|
|
- umount = (signal is None) # no crash and no signal -> umount request
|
|
|
- finally:
|
|
|
- llfuse.close(umount)
|