|
@@ -5,7 +5,6 @@ import traceback
|
|
|
|
|
|
try:
|
|
|
import argparse
|
|
|
- import base64
|
|
|
import collections
|
|
|
import configparser
|
|
|
import faulthandler
|
|
@@ -21,7 +20,6 @@ try:
|
|
|
import signal
|
|
|
import stat
|
|
|
import subprocess
|
|
|
- import tarfile
|
|
|
import textwrap
|
|
|
import time
|
|
|
from binascii import unhexlify
|
|
@@ -38,7 +36,7 @@ try:
|
|
|
from .. import helpers
|
|
|
from ..archive import Archive, ArchiveChecker, ArchiveRecreater, Statistics, is_special
|
|
|
from ..archive import BackupError, BackupOSError, backup_io, OsOpen, stat_update_check
|
|
|
- from ..archive import FilesystemObjectProcessors, TarfileObjectProcessors, MetadataCollector, ChunksProcessor
|
|
|
+ from ..archive import FilesystemObjectProcessors, MetadataCollector, ChunksProcessor
|
|
|
from ..cache import Cache, assert_secure, SecurityManager
|
|
|
from ..constants import * # NOQA
|
|
|
from ..compress import CompressionSpec
|
|
@@ -65,22 +63,13 @@ try:
|
|
|
from ..helpers import ErrorIgnoringTextIOWrapper
|
|
|
from ..helpers import ProgressIndicatorPercent
|
|
|
from ..helpers import basic_json_data, json_print
|
|
|
- from ..helpers import ChunkIteratorFileWrapper
|
|
|
- from ..helpers import prepare_subprocess_env, create_filter_process
|
|
|
- from ..helpers import dash_open
|
|
|
+ from ..helpers import prepare_subprocess_env
|
|
|
from ..helpers import umount
|
|
|
from ..helpers import flags_root, flags_dir, flags_special_follow, flags_special
|
|
|
from ..helpers import msgpack
|
|
|
from ..helpers import sig_int
|
|
|
from ..helpers import iter_separated
|
|
|
- from ..helpers import get_tar_filter
|
|
|
from ..nanorst import rst_to_terminal
|
|
|
- from ..patterns import (
|
|
|
- ArgparsePatternAction,
|
|
|
- ArgparseExcludeFileAction,
|
|
|
- ArgparsePatternFileAction,
|
|
|
- parse_exclude_pattern,
|
|
|
- )
|
|
|
from ..patterns import PatternMatcher
|
|
|
from ..item import Item
|
|
|
from ..platform import get_flags, SyncFile
|
|
@@ -122,9 +111,10 @@ def get_func(args):
|
|
|
|
|
|
|
|
|
from .debug import DebugMixIn
|
|
|
+from .tar import TarMixIn
|
|
|
|
|
|
|
|
|
-class Archiver(DebugMixIn):
|
|
|
+class Archiver(DebugMixIn, TarMixIn):
|
|
|
def __init__(self, lock_wait=None, prog=None):
|
|
|
self.exit_code = EXIT_SUCCESS
|
|
|
self.lock_wait = lock_wait
|
|
@@ -1196,194 +1186,6 @@ class Archiver(DebugMixIn):
|
|
|
pi.finish()
|
|
|
return self.exit_code
|
|
|
|
|
|
- @with_repository(compatibility=(Manifest.Operation.READ,))
|
|
|
- @with_archive
|
|
|
- def do_export_tar(self, args, repository, manifest, key, archive):
|
|
|
- """Export archive contents as a tarball"""
|
|
|
- self.output_list = args.output_list
|
|
|
-
|
|
|
- # A quick note about the general design of tar_filter and tarfile;
|
|
|
- # The tarfile module of Python can provide some compression mechanisms
|
|
|
- # by itself, using the builtin gzip, bz2 and lzma modules (and "tarmodes"
|
|
|
- # such as "w:xz").
|
|
|
- #
|
|
|
- # Doing so would have three major drawbacks:
|
|
|
- # For one the compressor runs on the same thread as the program using the
|
|
|
- # tarfile, stealing valuable CPU time from Borg and thus reducing throughput.
|
|
|
- # Then this limits the available options - what about lz4? Brotli? zstd?
|
|
|
- # The third issue is that systems can ship more optimized versions than those
|
|
|
- # built into Python, e.g. pigz or pxz, which can use more than one thread for
|
|
|
- # compression.
|
|
|
- #
|
|
|
- # Therefore we externalize compression by using a filter program, which has
|
|
|
- # none of these drawbacks. The only issue of using an external filter is
|
|
|
- # that it has to be installed -- hardly a problem, considering that
|
|
|
- # the decompressor must be installed as well to make use of the exported tarball!
|
|
|
-
|
|
|
- filter = get_tar_filter(args.tarfile, decompress=False) if args.tar_filter == "auto" else args.tar_filter
|
|
|
-
|
|
|
- tarstream = dash_open(args.tarfile, "wb")
|
|
|
- tarstream_close = args.tarfile != "-"
|
|
|
-
|
|
|
- with create_filter_process(filter, stream=tarstream, stream_close=tarstream_close, inbound=False) as _stream:
|
|
|
- self._export_tar(args, archive, _stream)
|
|
|
-
|
|
|
- return self.exit_code
|
|
|
-
|
|
|
- def _export_tar(self, args, archive, tarstream):
|
|
|
- matcher = self.build_matcher(args.patterns, args.paths)
|
|
|
-
|
|
|
- progress = args.progress
|
|
|
- output_list = args.output_list
|
|
|
- strip_components = args.strip_components
|
|
|
- hlm = HardLinkManager(id_type=bytes, info_type=str) # hlid -> path
|
|
|
-
|
|
|
- filter = self.build_filter(matcher, strip_components)
|
|
|
-
|
|
|
- # The | (pipe) symbol instructs tarfile to use a streaming mode of operation
|
|
|
- # where it never seeks on the passed fileobj.
|
|
|
- tar_format = dict(GNU=tarfile.GNU_FORMAT, PAX=tarfile.PAX_FORMAT, BORG=tarfile.PAX_FORMAT)[args.tar_format]
|
|
|
- tar = tarfile.open(fileobj=tarstream, mode="w|", format=tar_format)
|
|
|
-
|
|
|
- if progress:
|
|
|
- pi = ProgressIndicatorPercent(msg="%5.1f%% Processing: %s", step=0.1, msgid="extract")
|
|
|
- pi.output("Calculating size")
|
|
|
- extracted_size = sum(item.get_size() for item in archive.iter_items(filter))
|
|
|
- pi.total = extracted_size
|
|
|
- else:
|
|
|
- pi = None
|
|
|
-
|
|
|
- def item_content_stream(item):
|
|
|
- """
|
|
|
- Return a file-like object that reads from the chunks of *item*.
|
|
|
- """
|
|
|
- chunk_iterator = archive.pipeline.fetch_many([chunk_id for chunk_id, _ in item.chunks], is_preloaded=True)
|
|
|
- if pi:
|
|
|
- info = [remove_surrogates(item.path)]
|
|
|
- return ChunkIteratorFileWrapper(
|
|
|
- chunk_iterator, lambda read_bytes: pi.show(increase=len(read_bytes), info=info)
|
|
|
- )
|
|
|
- else:
|
|
|
- return ChunkIteratorFileWrapper(chunk_iterator)
|
|
|
-
|
|
|
- def item_to_tarinfo(item, original_path):
|
|
|
- """
|
|
|
- Transform a Borg *item* into a tarfile.TarInfo object.
|
|
|
-
|
|
|
- Return a tuple (tarinfo, stream), where stream may be a file-like object that represents
|
|
|
- the file contents, if any, and is None otherwise. When *tarinfo* is None, the *item*
|
|
|
- cannot be represented as a TarInfo object and should be skipped.
|
|
|
- """
|
|
|
- stream = None
|
|
|
- tarinfo = tarfile.TarInfo()
|
|
|
- tarinfo.name = item.path
|
|
|
- tarinfo.mtime = item.mtime / 1e9
|
|
|
- tarinfo.mode = stat.S_IMODE(item.mode)
|
|
|
- tarinfo.uid = item.uid
|
|
|
- tarinfo.gid = item.gid
|
|
|
- tarinfo.uname = item.get("user", "")
|
|
|
- tarinfo.gname = item.get("group", "")
|
|
|
- # The linkname in tar has 2 uses:
|
|
|
- # for symlinks it means the destination, while for hardlinks it refers to the file.
|
|
|
- # Since hardlinks in tar have a different type code (LNKTYPE) the format might
|
|
|
- # support hardlinking arbitrary objects (including symlinks and directories), but
|
|
|
- # whether implementations actually support that is a whole different question...
|
|
|
- tarinfo.linkname = ""
|
|
|
-
|
|
|
- modebits = stat.S_IFMT(item.mode)
|
|
|
- if modebits == stat.S_IFREG:
|
|
|
- tarinfo.type = tarfile.REGTYPE
|
|
|
- if "hlid" in item:
|
|
|
- linkname = hlm.retrieve(id=item.hlid)
|
|
|
- if linkname is not None:
|
|
|
- # the first hardlink was already added to the archive, add a tar-hardlink reference to it.
|
|
|
- tarinfo.type = tarfile.LNKTYPE
|
|
|
- tarinfo.linkname = linkname
|
|
|
- else:
|
|
|
- tarinfo.size = item.get_size()
|
|
|
- stream = item_content_stream(item)
|
|
|
- hlm.remember(id=item.hlid, info=item.path)
|
|
|
- else:
|
|
|
- tarinfo.size = item.get_size()
|
|
|
- stream = item_content_stream(item)
|
|
|
- elif modebits == stat.S_IFDIR:
|
|
|
- tarinfo.type = tarfile.DIRTYPE
|
|
|
- elif modebits == stat.S_IFLNK:
|
|
|
- tarinfo.type = tarfile.SYMTYPE
|
|
|
- tarinfo.linkname = item.source
|
|
|
- elif modebits == stat.S_IFBLK:
|
|
|
- tarinfo.type = tarfile.BLKTYPE
|
|
|
- tarinfo.devmajor = os.major(item.rdev)
|
|
|
- tarinfo.devminor = os.minor(item.rdev)
|
|
|
- elif modebits == stat.S_IFCHR:
|
|
|
- tarinfo.type = tarfile.CHRTYPE
|
|
|
- tarinfo.devmajor = os.major(item.rdev)
|
|
|
- tarinfo.devminor = os.minor(item.rdev)
|
|
|
- elif modebits == stat.S_IFIFO:
|
|
|
- tarinfo.type = tarfile.FIFOTYPE
|
|
|
- else:
|
|
|
- self.print_warning(
|
|
|
- "%s: unsupported file type %o for tar export", remove_surrogates(item.path), modebits
|
|
|
- )
|
|
|
- set_ec(EXIT_WARNING)
|
|
|
- return None, stream
|
|
|
- return tarinfo, stream
|
|
|
-
|
|
|
- def item_to_paxheaders(format, item):
|
|
|
- """
|
|
|
- Transform (parts of) a Borg *item* into a pax_headers dict.
|
|
|
- """
|
|
|
- # PAX format
|
|
|
- # ----------
|
|
|
- # When using the PAX (POSIX) format, we can support some things that aren't possible
|
|
|
- # with classic tar formats, including GNU tar, such as:
|
|
|
- # - atime, ctime (DONE)
|
|
|
- # - possibly Linux capabilities, security.* xattrs (TODO)
|
|
|
- # - various additions supported by GNU tar in POSIX mode (TODO)
|
|
|
- #
|
|
|
- # BORG format
|
|
|
- # -----------
|
|
|
- # This is based on PAX, but additionally adds BORG.* pax headers.
|
|
|
- # Additionally to the standard tar / PAX metadata and data, it transfers
|
|
|
- # ALL borg item metadata in a BORG specific way.
|
|
|
- #
|
|
|
- ph = {}
|
|
|
- # note: for mtime this is a bit redundant as it is already done by tarfile module,
|
|
|
- # but we just do it in our way to be consistent for sure.
|
|
|
- for name in "atime", "ctime", "mtime":
|
|
|
- if hasattr(item, name):
|
|
|
- ns = getattr(item, name)
|
|
|
- ph[name] = str(ns / 1e9)
|
|
|
- if format == "BORG": # BORG format additions
|
|
|
- ph["BORG.item.version"] = "1"
|
|
|
- # BORG.item.meta - just serialize all metadata we have:
|
|
|
- meta_bin = msgpack.packb(item.as_dict())
|
|
|
- meta_text = base64.b64encode(meta_bin).decode()
|
|
|
- ph["BORG.item.meta"] = meta_text
|
|
|
- return ph
|
|
|
-
|
|
|
- for item in archive.iter_items(filter, preload=True):
|
|
|
- orig_path = item.path
|
|
|
- if strip_components:
|
|
|
- item.path = os.sep.join(orig_path.split(os.sep)[strip_components:])
|
|
|
- tarinfo, stream = item_to_tarinfo(item, orig_path)
|
|
|
- if tarinfo:
|
|
|
- if args.tar_format in ("BORG", "PAX"):
|
|
|
- tarinfo.pax_headers = item_to_paxheaders(args.tar_format, item)
|
|
|
- if output_list:
|
|
|
- logging.getLogger("borg.output.list").info(remove_surrogates(orig_path))
|
|
|
- tar.addfile(tarinfo, stream)
|
|
|
-
|
|
|
- if pi:
|
|
|
- pi.finish()
|
|
|
-
|
|
|
- # This does not close the fileobj (tarstream) we passed to it -- a side effect of the | mode.
|
|
|
- tar.close()
|
|
|
-
|
|
|
- for pattern in matcher.get_unmatched_include_patterns():
|
|
|
- self.print_warning("Include pattern '%s' never matched.", pattern)
|
|
|
- return self.exit_code
|
|
|
-
|
|
|
@with_repository(compatibility=(Manifest.Operation.READ,))
|
|
|
@with_archive
|
|
|
def do_diff(self, args, repository, manifest, key, archive):
|
|
@@ -1908,102 +1710,6 @@ class Archiver(DebugMixIn):
|
|
|
cache.commit()
|
|
|
return self.exit_code
|
|
|
|
|
|
- @with_repository(cache=True, exclusive=True, compatibility=(Manifest.Operation.WRITE,))
|
|
|
- def do_import_tar(self, args, repository, manifest, key, cache):
|
|
|
- """Create a backup archive from a tarball"""
|
|
|
- self.output_filter = args.output_filter
|
|
|
- self.output_list = args.output_list
|
|
|
-
|
|
|
- filter = get_tar_filter(args.tarfile, decompress=True) if args.tar_filter == "auto" else args.tar_filter
|
|
|
-
|
|
|
- tarstream = dash_open(args.tarfile, "rb")
|
|
|
- tarstream_close = args.tarfile != "-"
|
|
|
-
|
|
|
- with create_filter_process(filter, stream=tarstream, stream_close=tarstream_close, inbound=True) as _stream:
|
|
|
- self._import_tar(args, repository, manifest, key, cache, _stream)
|
|
|
-
|
|
|
- return self.exit_code
|
|
|
-
|
|
|
- def _import_tar(self, args, repository, manifest, key, cache, tarstream):
|
|
|
- t0 = datetime.utcnow()
|
|
|
- t0_monotonic = time.monotonic()
|
|
|
-
|
|
|
- archive = Archive(
|
|
|
- repository,
|
|
|
- key,
|
|
|
- manifest,
|
|
|
- args.name,
|
|
|
- cache=cache,
|
|
|
- create=True,
|
|
|
- checkpoint_interval=args.checkpoint_interval,
|
|
|
- progress=args.progress,
|
|
|
- chunker_params=args.chunker_params,
|
|
|
- start=t0,
|
|
|
- start_monotonic=t0_monotonic,
|
|
|
- log_json=args.log_json,
|
|
|
- )
|
|
|
- cp = ChunksProcessor(
|
|
|
- cache=cache,
|
|
|
- key=key,
|
|
|
- add_item=archive.add_item,
|
|
|
- write_checkpoint=archive.write_checkpoint,
|
|
|
- checkpoint_interval=args.checkpoint_interval,
|
|
|
- rechunkify=False,
|
|
|
- )
|
|
|
- tfo = TarfileObjectProcessors(
|
|
|
- cache=cache,
|
|
|
- key=key,
|
|
|
- process_file_chunks=cp.process_file_chunks,
|
|
|
- add_item=archive.add_item,
|
|
|
- chunker_params=args.chunker_params,
|
|
|
- show_progress=args.progress,
|
|
|
- log_json=args.log_json,
|
|
|
- iec=args.iec,
|
|
|
- file_status_printer=self.print_file_status,
|
|
|
- )
|
|
|
-
|
|
|
- tar = tarfile.open(fileobj=tarstream, mode="r|")
|
|
|
-
|
|
|
- while True:
|
|
|
- tarinfo = tar.next()
|
|
|
- if not tarinfo:
|
|
|
- break
|
|
|
- if tarinfo.isreg():
|
|
|
- status = tfo.process_file(tarinfo=tarinfo, status="A", type=stat.S_IFREG, tar=tar)
|
|
|
- archive.stats.nfiles += 1
|
|
|
- elif tarinfo.isdir():
|
|
|
- status = tfo.process_dir(tarinfo=tarinfo, status="d", type=stat.S_IFDIR)
|
|
|
- elif tarinfo.issym():
|
|
|
- status = tfo.process_symlink(tarinfo=tarinfo, status="s", type=stat.S_IFLNK)
|
|
|
- elif tarinfo.islnk():
|
|
|
- # tar uses a hardlink model like: the first instance of a hardlink is stored as a regular file,
|
|
|
- # later instances are special entries referencing back to the first instance.
|
|
|
- status = tfo.process_hardlink(tarinfo=tarinfo, status="h", type=stat.S_IFREG)
|
|
|
- elif tarinfo.isblk():
|
|
|
- status = tfo.process_dev(tarinfo=tarinfo, status="b", type=stat.S_IFBLK)
|
|
|
- elif tarinfo.ischr():
|
|
|
- status = tfo.process_dev(tarinfo=tarinfo, status="c", type=stat.S_IFCHR)
|
|
|
- elif tarinfo.isfifo():
|
|
|
- status = tfo.process_fifo(tarinfo=tarinfo, status="f", type=stat.S_IFIFO)
|
|
|
- else:
|
|
|
- status = "E"
|
|
|
- self.print_warning("%s: Unsupported tarinfo type %s", tarinfo.name, tarinfo.type)
|
|
|
- self.print_file_status(status, tarinfo.name)
|
|
|
-
|
|
|
- # This does not close the fileobj (tarstream) we passed to it -- a side effect of the | mode.
|
|
|
- tar.close()
|
|
|
-
|
|
|
- if args.progress:
|
|
|
- archive.stats.show_progress(final=True)
|
|
|
- archive.stats += tfo.stats
|
|
|
- archive.save(comment=args.comment, timestamp=args.timestamp)
|
|
|
- args.stats |= args.json
|
|
|
- if args.stats:
|
|
|
- if args.json:
|
|
|
- json_print(basic_json_data(archive.manifest, cache=archive.cache, extra={"archive": archive}))
|
|
|
- else:
|
|
|
- log_multi(str(archive), str(archive.stats), logger=logging.getLogger("borg.output.stats"))
|
|
|
-
|
|
|
@with_repository(manifest=False, exclusive=True)
|
|
|
def do_with_lock(self, args, repository):
|
|
|
"""run a user specified command with the repository lock held"""
|
|
@@ -2740,6 +2446,7 @@ class Archiver(DebugMixIn):
|
|
|
def build_parser(self):
|
|
|
|
|
|
from .common import process_epilog
|
|
|
+ from .common import define_exclusion_group
|
|
|
|
|
|
def define_common_options(add_common_option):
|
|
|
add_common_option("-h", "--help", action="help", help="show this help message and exit")
|
|
@@ -2881,75 +2588,6 @@ class Archiver(DebugMixIn):
|
|
|
help="repository to use",
|
|
|
)
|
|
|
|
|
|
- def define_exclude_and_patterns(add_option, *, tag_files=False, strip_components=False):
|
|
|
- add_option(
|
|
|
- "-e",
|
|
|
- "--exclude",
|
|
|
- metavar="PATTERN",
|
|
|
- dest="patterns",
|
|
|
- type=parse_exclude_pattern,
|
|
|
- action="append",
|
|
|
- help="exclude paths matching PATTERN",
|
|
|
- )
|
|
|
- add_option(
|
|
|
- "--exclude-from",
|
|
|
- metavar="EXCLUDEFILE",
|
|
|
- action=ArgparseExcludeFileAction,
|
|
|
- help="read exclude patterns from EXCLUDEFILE, one per line",
|
|
|
- )
|
|
|
- add_option(
|
|
|
- "--pattern",
|
|
|
- metavar="PATTERN",
|
|
|
- action=ArgparsePatternAction,
|
|
|
- help="include/exclude paths matching PATTERN",
|
|
|
- )
|
|
|
- add_option(
|
|
|
- "--patterns-from",
|
|
|
- metavar="PATTERNFILE",
|
|
|
- action=ArgparsePatternFileAction,
|
|
|
- help="read include/exclude patterns from PATTERNFILE, one per line",
|
|
|
- )
|
|
|
-
|
|
|
- if tag_files:
|
|
|
- add_option(
|
|
|
- "--exclude-caches",
|
|
|
- dest="exclude_caches",
|
|
|
- action="store_true",
|
|
|
- help="exclude directories that contain a CACHEDIR.TAG file "
|
|
|
- "(http://www.bford.info/cachedir/spec.html)",
|
|
|
- )
|
|
|
- add_option(
|
|
|
- "--exclude-if-present",
|
|
|
- metavar="NAME",
|
|
|
- dest="exclude_if_present",
|
|
|
- action="append",
|
|
|
- type=str,
|
|
|
- help="exclude directories that are tagged by containing a filesystem object with " "the given NAME",
|
|
|
- )
|
|
|
- add_option(
|
|
|
- "--keep-exclude-tags",
|
|
|
- dest="keep_exclude_tags",
|
|
|
- action="store_true",
|
|
|
- help="if tag objects are specified with ``--exclude-if-present``, "
|
|
|
- "don't omit the tag objects themselves from the backup archive",
|
|
|
- )
|
|
|
-
|
|
|
- if strip_components:
|
|
|
- add_option(
|
|
|
- "--strip-components",
|
|
|
- metavar="NUMBER",
|
|
|
- dest="strip_components",
|
|
|
- type=int,
|
|
|
- default=0,
|
|
|
- help="Remove the specified number of leading path elements. "
|
|
|
- "Paths with fewer elements will be silently skipped.",
|
|
|
- )
|
|
|
-
|
|
|
- def define_exclusion_group(subparser, **kwargs):
|
|
|
- exclude_group = subparser.add_argument_group("Exclusion options")
|
|
|
- define_exclude_and_patterns(exclude_group.add_argument, **kwargs)
|
|
|
- return exclude_group
|
|
|
-
|
|
|
def define_archive_filters_group(subparser, *, sort_by=True, first_last=True):
|
|
|
filters_group = subparser.add_argument_group(
|
|
|
"Archive filters", "Archive filters can be applied to repository targets."
|
|
@@ -4044,83 +3682,6 @@ class Archiver(DebugMixIn):
|
|
|
)
|
|
|
define_exclusion_group(subparser)
|
|
|
|
|
|
- # borg export-tar
|
|
|
- export_tar_epilog = process_epilog(
|
|
|
- """
|
|
|
- This command creates a tarball from an archive.
|
|
|
-
|
|
|
- When giving '-' as the output FILE, Borg will write a tar stream to standard output.
|
|
|
-
|
|
|
- By default (``--tar-filter=auto``) Borg will detect whether the FILE should be compressed
|
|
|
- based on its file extension and pipe the tarball through an appropriate filter
|
|
|
- before writing it to FILE:
|
|
|
-
|
|
|
- - .tar.gz or .tgz: gzip
|
|
|
- - .tar.bz2 or .tbz: bzip2
|
|
|
- - .tar.xz or .txz: xz
|
|
|
- - .tar.zstd: zstd
|
|
|
- - .tar.lz4: lz4
|
|
|
-
|
|
|
- Alternatively, a ``--tar-filter`` program may be explicitly specified. It should
|
|
|
- read the uncompressed tar stream from stdin and write a compressed/filtered
|
|
|
- tar stream to stdout.
|
|
|
-
|
|
|
- Depending on the ``-tar-format`` option, these formats are created:
|
|
|
-
|
|
|
- +--------------+---------------------------+----------------------------+
|
|
|
- | --tar-format | Specification | Metadata |
|
|
|
- +--------------+---------------------------+----------------------------+
|
|
|
- | BORG | BORG specific, like PAX | all as supported by borg |
|
|
|
- +--------------+---------------------------+----------------------------+
|
|
|
- | PAX | POSIX.1-2001 (pax) format | GNU + atime/ctime/mtime ns |
|
|
|
- +--------------+---------------------------+----------------------------+
|
|
|
- | GNU | GNU tar format | mtime s, no atime/ctime, |
|
|
|
- | | | no ACLs/xattrs/bsdflags |
|
|
|
- +--------------+---------------------------+----------------------------+
|
|
|
-
|
|
|
- A ``--sparse`` option (as found in borg extract) is not supported.
|
|
|
-
|
|
|
- By default the entire archive is extracted but a subset of files and directories
|
|
|
- can be selected by passing a list of ``PATHs`` as arguments.
|
|
|
- The file selection can further be restricted by using the ``--exclude`` option.
|
|
|
-
|
|
|
- For more help on include/exclude patterns, see the :ref:`borg_patterns` command output.
|
|
|
-
|
|
|
- ``--progress`` can be slower than no progress display, since it makes one additional
|
|
|
- pass over the archive metadata.
|
|
|
- """
|
|
|
- )
|
|
|
- subparser = subparsers.add_parser(
|
|
|
- "export-tar",
|
|
|
- parents=[common_parser],
|
|
|
- add_help=False,
|
|
|
- description=self.do_export_tar.__doc__,
|
|
|
- epilog=export_tar_epilog,
|
|
|
- formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
|
- help="create tarball from archive",
|
|
|
- )
|
|
|
- subparser.set_defaults(func=self.do_export_tar)
|
|
|
- subparser.add_argument(
|
|
|
- "--tar-filter", dest="tar_filter", default="auto", help="filter program to pipe data through"
|
|
|
- )
|
|
|
- subparser.add_argument(
|
|
|
- "--list", dest="output_list", action="store_true", help="output verbose list of items (files, dirs, ...)"
|
|
|
- )
|
|
|
- subparser.add_argument(
|
|
|
- "--tar-format",
|
|
|
- metavar="FMT",
|
|
|
- dest="tar_format",
|
|
|
- default="GNU",
|
|
|
- choices=("BORG", "PAX", "GNU"),
|
|
|
- help="select tar format: BORG, PAX or GNU",
|
|
|
- )
|
|
|
- subparser.add_argument("name", metavar="NAME", type=NameSpec, help="specify the archive name")
|
|
|
- subparser.add_argument("tarfile", metavar="FILE", help='output tar file. "-" to write to stdout instead.')
|
|
|
- subparser.add_argument(
|
|
|
- "paths", metavar="PATH", nargs="*", type=str, help="paths to extract; patterns are supported"
|
|
|
- )
|
|
|
- define_exclusion_group(subparser, strip_components=True)
|
|
|
-
|
|
|
# borg extract
|
|
|
extract_epilog = process_epilog(
|
|
|
"""
|
|
@@ -5167,129 +4728,8 @@ class Archiver(DebugMixIn):
|
|
|
subparser.add_argument("command", metavar="COMMAND", help="command to run")
|
|
|
subparser.add_argument("args", metavar="ARGS", nargs=argparse.REMAINDER, help="command arguments")
|
|
|
|
|
|
- # borg import-tar
|
|
|
- import_tar_epilog = process_epilog(
|
|
|
- """
|
|
|
- This command creates a backup archive from a tarball.
|
|
|
-
|
|
|
- When giving '-' as path, Borg will read a tar stream from standard input.
|
|
|
-
|
|
|
- By default (--tar-filter=auto) Borg will detect whether the file is compressed
|
|
|
- based on its file extension and pipe the file through an appropriate filter:
|
|
|
-
|
|
|
- - .tar.gz or .tgz: gzip -d
|
|
|
- - .tar.bz2 or .tbz: bzip2 -d
|
|
|
- - .tar.xz or .txz: xz -d
|
|
|
- - .tar.zstd: zstd -d
|
|
|
- - .tar.lz4: lz4 -d
|
|
|
-
|
|
|
- Alternatively, a --tar-filter program may be explicitly specified. It should
|
|
|
- read compressed data from stdin and output an uncompressed tar stream on
|
|
|
- stdout.
|
|
|
+ self.build_parser_tar(subparsers, common_parser, mid_common_parser)
|
|
|
|
|
|
- Most documentation of borg create applies. Note that this command does not
|
|
|
- support excluding files.
|
|
|
-
|
|
|
- A ``--sparse`` option (as found in borg create) is not supported.
|
|
|
-
|
|
|
- About tar formats and metadata conservation or loss, please see ``borg export-tar``.
|
|
|
-
|
|
|
- import-tar reads these tar formats:
|
|
|
-
|
|
|
- - BORG: borg specific (PAX-based)
|
|
|
- - PAX: POSIX.1-2001
|
|
|
- - GNU: GNU tar
|
|
|
- - POSIX.1-1988 (ustar)
|
|
|
- - UNIX V7 tar
|
|
|
- - SunOS tar with extended attributes
|
|
|
-
|
|
|
- """
|
|
|
- )
|
|
|
- subparser = subparsers.add_parser(
|
|
|
- "import-tar",
|
|
|
- parents=[common_parser],
|
|
|
- add_help=False,
|
|
|
- description=self.do_import_tar.__doc__,
|
|
|
- epilog=import_tar_epilog,
|
|
|
- formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
|
- help=self.do_import_tar.__doc__,
|
|
|
- )
|
|
|
- subparser.set_defaults(func=self.do_import_tar)
|
|
|
- subparser.add_argument(
|
|
|
- "--tar-filter",
|
|
|
- dest="tar_filter",
|
|
|
- default="auto",
|
|
|
- action=Highlander,
|
|
|
- help="filter program to pipe data through",
|
|
|
- )
|
|
|
- subparser.add_argument(
|
|
|
- "-s",
|
|
|
- "--stats",
|
|
|
- dest="stats",
|
|
|
- action="store_true",
|
|
|
- default=False,
|
|
|
- help="print statistics for the created archive",
|
|
|
- )
|
|
|
- subparser.add_argument(
|
|
|
- "--list",
|
|
|
- dest="output_list",
|
|
|
- action="store_true",
|
|
|
- default=False,
|
|
|
- help="output verbose list of items (files, dirs, ...)",
|
|
|
- )
|
|
|
- subparser.add_argument(
|
|
|
- "--filter",
|
|
|
- dest="output_filter",
|
|
|
- metavar="STATUSCHARS",
|
|
|
- action=Highlander,
|
|
|
- help="only display items with the given status characters",
|
|
|
- )
|
|
|
- subparser.add_argument("--json", action="store_true", help="output stats as JSON (implies --stats)")
|
|
|
-
|
|
|
- archive_group = subparser.add_argument_group("Archive options")
|
|
|
- archive_group.add_argument(
|
|
|
- "--comment", dest="comment", metavar="COMMENT", default="", help="add a comment text to the archive"
|
|
|
- )
|
|
|
- archive_group.add_argument(
|
|
|
- "--timestamp",
|
|
|
- dest="timestamp",
|
|
|
- type=timestamp,
|
|
|
- default=None,
|
|
|
- metavar="TIMESTAMP",
|
|
|
- help="manually specify the archive creation date/time (UTC, yyyy-mm-ddThh:mm:ss format). "
|
|
|
- "alternatively, give a reference file/directory.",
|
|
|
- )
|
|
|
- archive_group.add_argument(
|
|
|
- "-c",
|
|
|
- "--checkpoint-interval",
|
|
|
- dest="checkpoint_interval",
|
|
|
- type=int,
|
|
|
- default=1800,
|
|
|
- metavar="SECONDS",
|
|
|
- help="write checkpoint every SECONDS seconds (Default: 1800)",
|
|
|
- )
|
|
|
- archive_group.add_argument(
|
|
|
- "--chunker-params",
|
|
|
- dest="chunker_params",
|
|
|
- action=Highlander,
|
|
|
- type=ChunkerParams,
|
|
|
- default=CHUNKER_PARAMS,
|
|
|
- metavar="PARAMS",
|
|
|
- help="specify the chunker parameters (ALGO, CHUNK_MIN_EXP, CHUNK_MAX_EXP, "
|
|
|
- "HASH_MASK_BITS, HASH_WINDOW_SIZE). default: %s,%d,%d,%d,%d" % CHUNKER_PARAMS,
|
|
|
- )
|
|
|
- archive_group.add_argument(
|
|
|
- "-C",
|
|
|
- "--compression",
|
|
|
- metavar="COMPRESSION",
|
|
|
- dest="compression",
|
|
|
- type=CompressionSpec,
|
|
|
- default=CompressionSpec("lz4"),
|
|
|
- help="select compression algorithm, see the output of the " '"borg help compression" command for details.',
|
|
|
- )
|
|
|
-
|
|
|
- subparser.add_argument("name", metavar="NAME", type=NameSpec, help="specify the archive name")
|
|
|
- subparser.add_argument("tarfile", metavar="TARFILE", help='input tar file. "-" to read from stdin instead.')
|
|
|
return parser
|
|
|
|
|
|
def get_args(self, argv, cmd):
|