mirror of
https://github.com/borgbackup/borg.git
synced 2025-02-22 06:01:54 +00:00
Merge pull request #1239 from ThomasWaldmann/forced-archive-delete
Forced archive delete
This commit is contained in:
commit
3811447a0e
2 changed files with 61 additions and 17 deletions
|
@ -24,6 +24,8 @@
|
||||||
from .platform import acl_get, acl_set
|
from .platform import acl_get, acl_set
|
||||||
from .chunker import Chunker
|
from .chunker import Chunker
|
||||||
from .hashindex import ChunkIndex
|
from .hashindex import ChunkIndex
|
||||||
|
from .repository import Repository
|
||||||
|
|
||||||
import msgpack
|
import msgpack
|
||||||
|
|
||||||
ITEMS_BUFFER = 1024 * 1024
|
ITEMS_BUFFER = 1024 * 1024
|
||||||
|
@ -494,7 +496,25 @@ def rename(self, name):
|
||||||
self.cache.chunk_decref(self.id, self.stats)
|
self.cache.chunk_decref(self.id, self.stats)
|
||||||
del self.manifest.archives[self.name]
|
del self.manifest.archives[self.name]
|
||||||
|
|
||||||
def delete(self, stats, progress=False):
|
def delete(self, stats, progress=False, forced=False):
|
||||||
|
class ChunksIndexError(Error):
|
||||||
|
"""Chunk ID {} missing from chunks index, corrupted chunks index - aborting transaction."""
|
||||||
|
|
||||||
|
def chunk_decref(id, stats):
|
||||||
|
nonlocal error
|
||||||
|
try:
|
||||||
|
self.cache.chunk_decref(id, stats)
|
||||||
|
except KeyError:
|
||||||
|
cid = hexlify(id).decode('ascii')
|
||||||
|
raise ChunksIndexError(cid)
|
||||||
|
except Repository.ObjectNotFound as e:
|
||||||
|
# object not in repo - strange, but we wanted to delete it anyway.
|
||||||
|
if not forced:
|
||||||
|
raise
|
||||||
|
error = True
|
||||||
|
|
||||||
|
error = False
|
||||||
|
try:
|
||||||
unpacker = msgpack.Unpacker(use_list=False)
|
unpacker = msgpack.Unpacker(use_list=False)
|
||||||
items_ids = self.metadata[b'items']
|
items_ids = self.metadata[b'items']
|
||||||
pi = ProgressIndicatorPercent(total=len(items_ids), msg="Decrementing references %3.0f%%", same_line=True)
|
pi = ProgressIndicatorPercent(total=len(items_ids), msg="Decrementing references %3.0f%%", same_line=True)
|
||||||
|
@ -502,15 +522,33 @@ def delete(self, stats, progress=False):
|
||||||
if progress:
|
if progress:
|
||||||
pi.show(i)
|
pi.show(i)
|
||||||
unpacker.feed(self.key.decrypt(items_id, data))
|
unpacker.feed(self.key.decrypt(items_id, data))
|
||||||
self.cache.chunk_decref(items_id, stats)
|
chunk_decref(items_id, stats)
|
||||||
|
try:
|
||||||
for item in unpacker:
|
for item in unpacker:
|
||||||
if b'chunks' in item:
|
if b'chunks' in item:
|
||||||
for chunk_id, size, csize in item[b'chunks']:
|
for chunk_id, size, csize in item[b'chunks']:
|
||||||
self.cache.chunk_decref(chunk_id, stats)
|
chunk_decref(chunk_id, stats)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
# if items metadata spans multiple chunks and one chunk got dropped somehow,
|
||||||
|
# it could be that unpacker yields bad types
|
||||||
|
if not forced:
|
||||||
|
raise
|
||||||
|
error = True
|
||||||
if progress:
|
if progress:
|
||||||
pi.finish()
|
pi.finish()
|
||||||
self.cache.chunk_decref(self.id, stats)
|
except (msgpack.UnpackException, Repository.ObjectNotFound):
|
||||||
|
# items metadata corrupted
|
||||||
|
if not forced:
|
||||||
|
raise
|
||||||
|
error = True
|
||||||
|
# in forced delete mode, we try hard to delete at least the manifest entry,
|
||||||
|
# if possible also the archive superblock, even if processing the items raises
|
||||||
|
# some harmless exception.
|
||||||
|
chunk_decref(self.id, stats)
|
||||||
del self.manifest.archives[self.name]
|
del self.manifest.archives[self.name]
|
||||||
|
if error:
|
||||||
|
logger.warning('forced deletion succeeded, but the deleted archive was corrupted.')
|
||||||
|
logger.warning('borg check --repair is required to free all space.')
|
||||||
|
|
||||||
def stat_attrs(self, st, path):
|
def stat_attrs(self, st, path):
|
||||||
item = {
|
item = {
|
||||||
|
|
|
@ -426,7 +426,7 @@ def do_delete(self, args, repository):
|
||||||
with Cache(repository, key, manifest, lock_wait=self.lock_wait) as cache:
|
with Cache(repository, key, manifest, lock_wait=self.lock_wait) as cache:
|
||||||
archive = Archive(repository, key, manifest, args.location.archive, cache=cache)
|
archive = Archive(repository, key, manifest, args.location.archive, cache=cache)
|
||||||
stats = Statistics()
|
stats = Statistics()
|
||||||
archive.delete(stats, progress=args.progress)
|
archive.delete(stats, progress=args.progress, forced=args.forced)
|
||||||
manifest.write()
|
manifest.write()
|
||||||
repository.commit(save_space=args.save_space)
|
repository.commit(save_space=args.save_space)
|
||||||
cache.commit()
|
cache.commit()
|
||||||
|
@ -635,7 +635,7 @@ def do_prune(self, args, repository, manifest, key):
|
||||||
else:
|
else:
|
||||||
if args.output_list:
|
if args.output_list:
|
||||||
logger.info('Pruning archive: %s' % format_archive(archive))
|
logger.info('Pruning archive: %s' % format_archive(archive))
|
||||||
Archive(repository, key, manifest, archive.name, cache).delete(stats)
|
Archive(repository, key, manifest, archive.name, cache).delete(stats, forced=args.forced)
|
||||||
if to_delete and not args.dry_run:
|
if to_delete and not args.dry_run:
|
||||||
manifest.write()
|
manifest.write()
|
||||||
repository.commit(save_space=args.save_space)
|
repository.commit(save_space=args.save_space)
|
||||||
|
@ -1230,6 +1230,9 @@ def build_parser(self, args=None, prog=None):
|
||||||
subparser.add_argument('-c', '--cache-only', dest='cache_only',
|
subparser.add_argument('-c', '--cache-only', dest='cache_only',
|
||||||
action='store_true', default=False,
|
action='store_true', default=False,
|
||||||
help='delete only the local cache for the given repository')
|
help='delete only the local cache for the given repository')
|
||||||
|
subparser.add_argument('--force', dest='forced',
|
||||||
|
action='store_true', default=False,
|
||||||
|
help='force deletion of corrupted archives')
|
||||||
subparser.add_argument('--save-space', dest='save_space', action='store_true',
|
subparser.add_argument('--save-space', dest='save_space', action='store_true',
|
||||||
default=False,
|
default=False,
|
||||||
help='work slower, but using less space')
|
help='work slower, but using less space')
|
||||||
|
@ -1342,6 +1345,9 @@ def build_parser(self, args=None, prog=None):
|
||||||
subparser.add_argument('-n', '--dry-run', dest='dry_run',
|
subparser.add_argument('-n', '--dry-run', dest='dry_run',
|
||||||
default=False, action='store_true',
|
default=False, action='store_true',
|
||||||
help='do not change repository')
|
help='do not change repository')
|
||||||
|
subparser.add_argument('--force', dest='forced',
|
||||||
|
action='store_true', default=False,
|
||||||
|
help='force pruning of corrupted archives')
|
||||||
subparser.add_argument('-s', '--stats', dest='stats',
|
subparser.add_argument('-s', '--stats', dest='stats',
|
||||||
action='store_true', default=False,
|
action='store_true', default=False,
|
||||||
help='print statistics for the deleted archive')
|
help='print statistics for the deleted archive')
|
||||||
|
|
Loading…
Reference in a new issue