Преглед на файлове

Merge pull request #8767 from ThomasWaldmann/backports-1.2

Backports to 1.2-maint
TW преди 1 месец
родител
ревизия
9aaa74c7d3
променени са 3 файла, в които са добавени 37 реда и са изтрити 10 реда
  1. 11 0
      docs/faq.rst
  2. 18 9
      src/borg/archiver.py
  3. 8 1
      src/borg/helpers/yes.py

+ 11 - 0
docs/faq.rst

@@ -478,6 +478,17 @@ will only be applied to new chunks, not existing chunks. So it's safe
 to change them.
 to change them.
 
 
 
 
+Why is backing up an unmodified FAT filesystem slow on Linux?
+-------------------------------------------------------------
+
+By default, the files cache used by BorgBackup considers the inode of files.
+When an inode number changes compared to the last backup, it hashes the file
+again. The ``vfat`` kernel driver does not produce stable inode numbers by
+default.  One way to achieve stable inode numbering is mounting the filesystem
+using ``nfs=nostale_ro``. Doing so implies mounting the filesystem read-only.
+Another option is to not consider inode numbers in the files cache by passing
+``--files-cache=ctime,size``.
+
 Security
 Security
 ########
 ########
 
 

+ 18 - 9
src/borg/archiver.py

@@ -208,7 +208,7 @@ def with_archive(method):
 
 
 def parse_storage_quota(storage_quota):
 def parse_storage_quota(storage_quota):
     parsed = parse_file_size(storage_quota)
     parsed = parse_file_size(storage_quota)
-    if parsed < parse_file_size('10M'):
+    if parsed != 0 and parsed < parse_file_size('10M'):
         raise argparse.ArgumentTypeError('quota is too small (%s). At least 10M are required.' % storage_quota)
         raise argparse.ArgumentTypeError('quota is too small (%s). At least 10M are required.' % storage_quota)
     return parsed
     return parsed
 
 
@@ -1913,11 +1913,12 @@ class Archiver:
     @with_repository(manifest=False, exclusive=True)
     @with_repository(manifest=False, exclusive=True)
     def do_compact(self, args, repository):
     def do_compact(self, args, repository):
         """compact segment files in the repository"""
         """compact segment files in the repository"""
-        # see the comment in do_with_lock about why we do it like this:
-        data = repository.get(Manifest.MANIFEST_ID)
-        repository.put(Manifest.MANIFEST_ID, data)
-        threshold = args.threshold / 100
-        repository.commit(compact=True, threshold=threshold, cleanup_commits=args.cleanup_commits)
+        if not args.dry_run:  # support --dry-run to simplify scripting
+            # see the comment in do_with_lock about why we do it like this:
+            data = repository.get(Manifest.MANIFEST_ID)
+            repository.put(Manifest.MANIFEST_ID, data)
+            threshold = args.threshold / 100
+            repository.commit(compact=True, threshold=threshold, cleanup_commits=args.cleanup_commits)
         return EXIT_SUCCESS
         return EXIT_SUCCESS
 
 
     @with_repository(exclusive=True, manifest=False)
     @with_repository(exclusive=True, manifest=False)
@@ -1940,8 +1941,15 @@ class Archiver:
                     except ValueError:
                     except ValueError:
                         raise ValueError('Invalid value') from None
                         raise ValueError('Invalid value') from None
                     if name == 'storage_quota':
                     if name == 'storage_quota':
-                        if parse_file_size(value) < parse_file_size('10M'):
+                        wanted = parse_file_size(value)
+                        minimum = parse_file_size('10M')
+                        if wanted != 0 and wanted < minimum:
                             raise ValueError('Invalid value: storage_quota < 10M')
                             raise ValueError('Invalid value: storage_quota < 10M')
+                    elif name == 'additional_free_space':
+                        wanted = parse_file_size(value)
+                        minimum = parse_file_size('10M')
+                        if wanted != 0 and wanted < minimum:
+                            raise ValueError('Invalid value: additional_free_space < 10M')
                     elif name == 'max_segment_size':
                     elif name == 'max_segment_size':
                         if parse_file_size(value) >= MAX_SEGMENT_SIZE_LIMIT:
                         if parse_file_size(value) >= MAX_SEGMENT_SIZE_LIMIT:
                             raise ValueError('Invalid value: max_segment_size >= %d' % MAX_SEGMENT_SIZE_LIMIT)
                             raise ValueError('Invalid value: max_segment_size >= %d' % MAX_SEGMENT_SIZE_LIMIT)
@@ -3188,8 +3196,8 @@ class Archiver:
         Unmounting in these cases could cause an active rsync or similar process
         Unmounting in these cases could cause an active rsync or similar process
         to unintentionally delete data.
         to unintentionally delete data.
 
 
-        When running in the foreground ^C/SIGINT unmounts cleanly, but other
-        signals or crashes do not.
+        When running in the foreground, ^C/SIGINT cleanly unmounts the filesystem,
+        but other signals or crashes do not.
         """)
         """)
 
 
         if parser.prog == 'borgfs':
         if parser.prog == 'borgfs':
@@ -3456,6 +3464,7 @@ class Archiver:
         subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
         subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
                                type=location_validator(archive=False),
                                type=location_validator(archive=False),
                                help='repository to compact')
                                help='repository to compact')
+        subparser.add_argument('-n', '--dry-run', dest='dry_run', action='store_true', help='do nothing')
         subparser.add_argument('--cleanup-commits', dest='cleanup_commits', action='store_true',
         subparser.add_argument('--cleanup-commits', dest='cleanup_commits', action='store_true',
                                help='cleanup commit-only 17-byte segment files')
                                help='cleanup commit-only 17-byte segment files')
         subparser.add_argument('--threshold', metavar='PERCENT', dest='threshold',
         subparser.add_argument('--threshold', metavar='PERCENT', dest='threshold',

+ 8 - 1
src/borg/helpers/yes.py

@@ -8,6 +8,9 @@ FALSISH = ('No', 'NO', 'no', 'N', 'n', '0', )
 TRUISH = ('Yes', 'YES', 'yes', 'Y', 'y', '1', )
 TRUISH = ('Yes', 'YES', 'yes', 'Y', 'y', '1', )
 DEFAULTISH = ('Default', 'DEFAULT', 'default', 'D', 'd', '', )
 DEFAULTISH = ('Default', 'DEFAULT', 'default', 'D', 'd', '', )
 
 
+ERROR = "error"
+assert ERROR not in TRUISH + FALSISH + DEFAULTISH
+
 
 
 def yes(msg=None, false_msg=None, true_msg=None, default_msg=None,
 def yes(msg=None, false_msg=None, true_msg=None, default_msg=None,
         retry_msg=None, invalid_msg=None, env_msg='{} (from {})',
         retry_msg=None, invalid_msg=None, env_msg='{} (from {})',
@@ -77,10 +80,14 @@ def yes(msg=None, false_msg=None, true_msg=None, default_msg=None,
             if not prompt:
             if not prompt:
                 return default
                 return default
             try:
             try:
-                answer = input()
+                answer = input()  # this may raise UnicodeDecodeError, #6984
+                if answer == ERROR:  # for testing purposes
+                    raise UnicodeDecodeError("?", b"?", 0, 1, "?")  # args don't matter
             except EOFError:
             except EOFError:
                 # avoid defaultish[0], defaultish could be empty
                 # avoid defaultish[0], defaultish could be empty
                 answer = truish[0] if default else falsish[0]
                 answer = truish[0] if default else falsish[0]
+            except UnicodeDecodeError:
+                answer = ERROR
         if answer in defaultish:
         if answer in defaultish:
             if default_msg:
             if default_msg:
                 output(default_msg, 'accepted_default')
                 output(default_msg, 'accepted_default')