|
@@ -27,6 +27,7 @@ try:
|
|
|
from binascii import unhexlify
|
|
|
from contextlib import contextmanager
|
|
|
from datetime import datetime, timedelta
|
|
|
+ from io import TextIOWrapper
|
|
|
|
|
|
from .logger import create_logger, setup_logging
|
|
|
|
|
@@ -51,7 +52,7 @@ try:
|
|
|
from .helpers import PrefixSpec, GlobSpec, CommentSpec, SortBySpec, FilesCacheMode
|
|
|
from .helpers import BaseFormatter, ItemFormatter, ArchiveFormatter
|
|
|
from .helpers import format_timedelta, format_file_size, parse_file_size, format_archive
|
|
|
- from .helpers import safe_encode, remove_surrogates, bin_to_hex, prepare_dump_dict
|
|
|
+ from .helpers import safe_encode, remove_surrogates, bin_to_hex, prepare_dump_dict, eval_escapes
|
|
|
from .helpers import interval, prune_within, prune_split, PRUNING_PATTERNS
|
|
|
from .helpers import timestamp
|
|
|
from .helpers import get_cache_dir, os_stat
|
|
@@ -73,6 +74,7 @@ try:
|
|
|
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 .nanorst import rst_to_terminal
|
|
|
from .patterns import ArgparsePatternAction, ArgparseExcludeFileAction, ArgparsePatternFileAction, parse_exclude_pattern
|
|
|
from .patterns import PatternMatcher
|
|
@@ -533,6 +535,37 @@ class Archiver:
|
|
|
else:
|
|
|
status = '-'
|
|
|
self.print_file_status(status, path)
|
|
|
+ elif args.paths_from_command or args.paths_from_stdin:
|
|
|
+ paths_sep = eval_escapes(args.paths_delimiter) if args.paths_delimiter is not None else '\n'
|
|
|
+ if args.paths_from_command:
|
|
|
+ try:
|
|
|
+ proc = subprocess.Popen(args.paths, stdout=subprocess.PIPE)
|
|
|
+ except (FileNotFoundError, PermissionError) as e:
|
|
|
+ self.print_error('Failed to execute command: %s', e)
|
|
|
+ return self.exit_code
|
|
|
+ pipe_bin = proc.stdout
|
|
|
+ else: # args.paths_from_stdin == True
|
|
|
+ pipe_bin = sys.stdin.buffer
|
|
|
+ pipe = TextIOWrapper(pipe_bin, errors='surrogateescape')
|
|
|
+ for path in iter_separated(pipe, paths_sep):
|
|
|
+ try:
|
|
|
+ with backup_io('stat'):
|
|
|
+ st = os_stat(path=path, parent_fd=None, name=None, follow_symlinks=False)
|
|
|
+ status = self._process_any(path=path, parent_fd=None, name=None, st=st, fso=fso,
|
|
|
+ cache=cache, read_special=args.read_special, dry_run=dry_run)
|
|
|
+ except (BackupOSError, BackupError) as e:
|
|
|
+ self.print_warning('%s: %s', path, e)
|
|
|
+ status = 'E'
|
|
|
+ if status == 'C':
|
|
|
+ self.print_warning('%s: file changed while we backed it up', path)
|
|
|
+ if status is None:
|
|
|
+ status = '?'
|
|
|
+ self.print_file_status(status, path)
|
|
|
+ if args.paths_from_command:
|
|
|
+ rc = proc.wait()
|
|
|
+ if rc != 0:
|
|
|
+ self.print_error('Command %r exited with status %d', args.paths[0], rc)
|
|
|
+ return self.exit_code
|
|
|
else:
|
|
|
for path in args.paths:
|
|
|
if path == '-': # stdin
|
|
@@ -3277,6 +3310,13 @@ class Archiver:
|
|
|
subparser.add_argument('--content-from-command', action='store_true',
|
|
|
help='interpret PATH as command and store its stdout. See also section Reading from'
|
|
|
' stdin below.')
|
|
|
+ subparser.add_argument('--paths-from-stdin', action='store_true',
|
|
|
+ help='read DELIM-separated list of paths to backup from stdin. Will not '
|
|
|
+ 'recurse into directories.')
|
|
|
+ subparser.add_argument('--paths-from-command', action='store_true',
|
|
|
+ help='interpret PATH as command and treat its output as ``--paths-from-stdin``')
|
|
|
+ subparser.add_argument('--paths-delimiter', metavar='DELIM',
|
|
|
+ help='set path delimiter for ``--paths-from-stdin`` and ``--paths-from-command`` (default: \\n) ')
|
|
|
|
|
|
exclude_group = define_exclusion_group(subparser, tag_files=True)
|
|
|
exclude_group.add_argument('--exclude-nodump', dest='exclude_nodump', action='store_true',
|
|
@@ -4522,10 +4562,12 @@ class Archiver:
|
|
|
args = parser.parse_args(args or ['-h'])
|
|
|
parser.common_options.resolve(args)
|
|
|
func = get_func(args)
|
|
|
+ if func == self.do_create and args.paths and args.paths_from_stdin:
|
|
|
+ parser.error('Must not pass PATH with ``--paths-from-stdin``.')
|
|
|
if func == self.do_create and not args.paths:
|
|
|
- if args.content_from_command:
|
|
|
+ if args.content_from_command or args.paths_from_command:
|
|
|
parser.error('No command given.')
|
|
|
- else:
|
|
|
+ elif not args.paths_from_stdin:
|
|
|
# need at least 1 path but args.paths may also be populated from patterns
|
|
|
parser.error('Need at least one PATH argument.')
|
|
|
if not getattr(args, 'lock', True): # Option --bypass-lock sets args.lock = False
|