archiver.py 116 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192
  1. from binascii import unhexlify
  2. from datetime import datetime
  3. from itertools import zip_longest
  4. from operator import attrgetter
  5. import argparse
  6. import collections
  7. import functools
  8. import hashlib
  9. import inspect
  10. import io
  11. import logging
  12. import os
  13. import re
  14. import shlex
  15. import signal
  16. import stat
  17. import subprocess
  18. import sys
  19. import textwrap
  20. import traceback
  21. from . import __version__
  22. from .helpers import Error, location_validator, archivename_validator, format_time, format_file_size, \
  23. parse_pattern, PathPrefixPattern, to_localtime, timestamp, \
  24. get_cache_dir, prune_within, prune_split, bin_to_hex, safe_encode, \
  25. Manifest, remove_surrogates, update_excludes, format_archive, check_extension_modules, \
  26. dir_is_tagged, ChunkerParams, CompressionSpec, is_slow_msgpack, yes, sysinfo, \
  27. log_multi, PatternMatcher, ItemFormatter
  28. from .logger import create_logger, setup_logging
  29. logger = create_logger()
  30. from . import helpers
  31. from .compress import Compressor, COMPR_BUFFER
  32. from .upgrader import AtticRepositoryUpgrader, BorgRepositoryUpgrader
  33. from .repository import Repository
  34. from .cache import Cache
  35. from .constants import * # NOQA
  36. from .key import key_creator, RepoKey, PassphraseKey
  37. from .archive import Archive, ArchiveChecker, ArchiveRecreater, Statistics
  38. from .remote import RepositoryServer, RemoteRepository, cache_if_remote
  39. from .selftest import selftest
  40. from .hashindex import ChunkIndexEntry
  41. from .platform import get_flags
  42. def argument(args, str_or_bool):
  43. """If bool is passed, return it. If str is passed, retrieve named attribute from args."""
  44. if isinstance(str_or_bool, str):
  45. return getattr(args, str_or_bool)
  46. return str_or_bool
  47. def with_repository(fake=False, create=False, lock=True, exclusive=False, manifest=True, cache=False):
  48. """
  49. Method decorator for subcommand-handling methods: do_XYZ(self, args, repository, …)
  50. If a parameter (where allowed) is a str the attribute named of args is used instead.
  51. :param fake: (str or bool) use None instead of repository, don't do anything else
  52. :param create: create repository
  53. :param lock: lock repository
  54. :param exclusive: (str or bool) lock repository exclusively (for writing)
  55. :param manifest: load manifest and key, pass them as keyword arguments
  56. :param cache: open cache, pass it as keyword argument (implies manifest)
  57. """
  58. def decorator(method):
  59. @functools.wraps(method)
  60. def wrapper(self, args, **kwargs):
  61. location = args.location # note: 'location' must be always present in args
  62. if argument(args, fake):
  63. return method(self, args, repository=None, **kwargs)
  64. elif location.proto == 'ssh':
  65. repository = RemoteRepository(location, create=create, lock_wait=self.lock_wait, lock=lock, args=args)
  66. else:
  67. repository = Repository(location.path, create=create, exclusive=argument(args, exclusive),
  68. lock_wait=self.lock_wait, lock=lock)
  69. with repository:
  70. if manifest or cache:
  71. kwargs['manifest'], kwargs['key'] = Manifest.load(repository)
  72. if cache:
  73. with Cache(repository, kwargs['key'], kwargs['manifest'],
  74. do_files=getattr(args, 'cache_files', False), lock_wait=self.lock_wait) as cache_:
  75. return method(self, args, repository=repository, cache=cache_, **kwargs)
  76. else:
  77. return method(self, args, repository=repository, **kwargs)
  78. return wrapper
  79. return decorator
  80. def with_archive(method):
  81. @functools.wraps(method)
  82. def wrapper(self, args, repository, key, manifest, **kwargs):
  83. archive = Archive(repository, key, manifest, args.location.archive,
  84. numeric_owner=getattr(args, 'numeric_owner', False), cache=kwargs.get('cache'))
  85. return method(self, args, repository=repository, manifest=manifest, key=key, archive=archive, **kwargs)
  86. return wrapper
  87. class Archiver:
  88. def __init__(self, lock_wait=None, prog=None):
  89. self.exit_code = EXIT_SUCCESS
  90. self.lock_wait = lock_wait
  91. self.parser = self.build_parser(prog)
  92. def print_error(self, msg, *args):
  93. msg = args and msg % args or msg
  94. self.exit_code = EXIT_ERROR
  95. logger.error(msg)
  96. def print_warning(self, msg, *args):
  97. msg = args and msg % args or msg
  98. self.exit_code = EXIT_WARNING # we do not terminate here, so it is a warning
  99. logger.warning(msg)
  100. def print_file_status(self, status, path):
  101. if self.output_list and (self.output_filter is None or status in self.output_filter):
  102. logging.getLogger('borg.output.list').info("%1s %s", status, remove_surrogates(path))
  103. @staticmethod
  104. def compare_chunk_contents(chunks1, chunks2):
  105. """Compare two chunk iterators (like returned by :meth:`.DownloadPipeline.fetch_many`)"""
  106. end = object()
  107. alen = ai = 0
  108. blen = bi = 0
  109. while True:
  110. if not alen - ai:
  111. a = next(chunks1, end)
  112. if a is end:
  113. return not blen - bi and next(chunks2, end) is end
  114. a = memoryview(a.data)
  115. alen = len(a)
  116. ai = 0
  117. if not blen - bi:
  118. b = next(chunks2, end)
  119. if b is end:
  120. return not alen - ai and next(chunks1, end) is end
  121. b = memoryview(b.data)
  122. blen = len(b)
  123. bi = 0
  124. slicelen = min(alen - ai, blen - bi)
  125. if a[ai:ai + slicelen] != b[bi:bi + slicelen]:
  126. return False
  127. ai += slicelen
  128. bi += slicelen
  129. @staticmethod
  130. def build_matcher(excludes, paths):
  131. matcher = PatternMatcher()
  132. if excludes:
  133. matcher.add(excludes, False)
  134. include_patterns = []
  135. if paths:
  136. include_patterns.extend(parse_pattern(i, PathPrefixPattern) for i in paths)
  137. matcher.add(include_patterns, True)
  138. matcher.fallback = not include_patterns
  139. return matcher, include_patterns
  140. def do_serve(self, args):
  141. """Start in server mode. This command is usually not used manually.
  142. """
  143. return RepositoryServer(restrict_to_paths=args.restrict_to_paths).serve()
  144. @with_repository(create=True, exclusive=True, manifest=False)
  145. def do_init(self, args, repository):
  146. """Initialize an empty repository"""
  147. logger.info('Initializing repository at "%s"' % args.location.canonical_path())
  148. try:
  149. key = key_creator(repository, args)
  150. except (EOFError, KeyboardInterrupt):
  151. repository.destroy()
  152. return EXIT_WARNING
  153. manifest = Manifest(key, repository)
  154. manifest.key = key
  155. manifest.write()
  156. repository.commit()
  157. with Cache(repository, key, manifest, warn_if_unencrypted=False):
  158. pass
  159. return self.exit_code
  160. @with_repository(exclusive='repair', manifest=False)
  161. def do_check(self, args, repository):
  162. """Check repository consistency"""
  163. if args.repair:
  164. msg = ("'check --repair' is an experimental feature that might result in data loss." +
  165. "\n" +
  166. "Type 'YES' if you understand this and want to continue: ")
  167. if not yes(msg, false_msg="Aborting.", truish=('YES', ),
  168. env_var_override='BORG_CHECK_I_KNOW_WHAT_I_AM_DOING'):
  169. return EXIT_ERROR
  170. if args.repo_only and args.verify_data:
  171. self.print_error("--repository-only and --verify-data contradict each other. Please select one.")
  172. return EXIT_ERROR
  173. if not args.archives_only:
  174. if not repository.check(repair=args.repair, save_space=args.save_space):
  175. return EXIT_WARNING
  176. if not args.repo_only and not ArchiveChecker().check(
  177. repository, repair=args.repair, archive=args.location.archive,
  178. last=args.last, prefix=args.prefix, verify_data=args.verify_data,
  179. save_space=args.save_space):
  180. return EXIT_WARNING
  181. return EXIT_SUCCESS
  182. @with_repository()
  183. def do_change_passphrase(self, args, repository, manifest, key):
  184. """Change repository key file passphrase"""
  185. key.change_passphrase()
  186. return EXIT_SUCCESS
  187. @with_repository(manifest=False)
  188. def do_migrate_to_repokey(self, args, repository):
  189. """Migrate passphrase -> repokey"""
  190. manifest_data = repository.get(Manifest.MANIFEST_ID)
  191. key_old = PassphraseKey.detect(repository, manifest_data)
  192. key_new = RepoKey(repository)
  193. key_new.target = repository
  194. key_new.repository_id = repository.id
  195. key_new.enc_key = key_old.enc_key
  196. key_new.enc_hmac_key = key_old.enc_hmac_key
  197. key_new.id_key = key_old.id_key
  198. key_new.chunk_seed = key_old.chunk_seed
  199. key_new.change_passphrase() # option to change key protection passphrase, save
  200. return EXIT_SUCCESS
  201. @with_repository(fake='dry_run')
  202. def do_create(self, args, repository, manifest=None, key=None):
  203. """Create new archive"""
  204. matcher = PatternMatcher(fallback=True)
  205. if args.excludes:
  206. matcher.add(args.excludes, False)
  207. def create_inner(archive, cache):
  208. # Add cache dir to inode_skip list
  209. skip_inodes = set()
  210. try:
  211. st = os.stat(get_cache_dir())
  212. skip_inodes.add((st.st_ino, st.st_dev))
  213. except OSError:
  214. pass
  215. # Add local repository dir to inode_skip list
  216. if not args.location.host:
  217. try:
  218. st = os.stat(args.location.path)
  219. skip_inodes.add((st.st_ino, st.st_dev))
  220. except OSError:
  221. pass
  222. for path in args.paths:
  223. if path == '-': # stdin
  224. path = 'stdin'
  225. if not dry_run:
  226. try:
  227. status = archive.process_stdin(path, cache)
  228. except OSError as e:
  229. status = 'E'
  230. self.print_warning('%s: %s', path, e)
  231. else:
  232. status = '-'
  233. self.print_file_status(status, path)
  234. continue
  235. path = os.path.normpath(path)
  236. try:
  237. st = os.lstat(path)
  238. except OSError as e:
  239. self.print_warning('%s: %s', path, e)
  240. continue
  241. if args.one_file_system:
  242. restrict_dev = st.st_dev
  243. else:
  244. restrict_dev = None
  245. self._process(archive, cache, matcher, args.exclude_caches, args.exclude_if_present,
  246. args.keep_tag_files, skip_inodes, path, restrict_dev,
  247. read_special=args.read_special, dry_run=dry_run, st=st)
  248. if not dry_run:
  249. archive.save(comment=args.comment, timestamp=args.timestamp)
  250. if args.progress:
  251. archive.stats.show_progress(final=True)
  252. if args.stats:
  253. archive.end = datetime.utcnow()
  254. log_multi(DASHES,
  255. str(archive),
  256. DASHES,
  257. str(archive.stats),
  258. str(cache),
  259. DASHES, logger=logging.getLogger('borg.output.stats'))
  260. self.output_filter = args.output_filter
  261. self.output_list = args.output_list
  262. self.ignore_inode = args.ignore_inode
  263. dry_run = args.dry_run
  264. t0 = datetime.utcnow()
  265. if not dry_run:
  266. with Cache(repository, key, manifest, do_files=args.cache_files, lock_wait=self.lock_wait) as cache:
  267. archive = Archive(repository, key, manifest, args.location.archive, cache=cache,
  268. create=True, checkpoint_interval=args.checkpoint_interval,
  269. numeric_owner=args.numeric_owner, progress=args.progress,
  270. chunker_params=args.chunker_params, start=t0,
  271. compression=args.compression, compression_files=args.compression_files)
  272. create_inner(archive, cache)
  273. else:
  274. create_inner(None, None)
  275. return self.exit_code
  276. def _process(self, archive, cache, matcher, exclude_caches, exclude_if_present,
  277. keep_tag_files, skip_inodes, path, restrict_dev,
  278. read_special=False, dry_run=False, st=None):
  279. if not matcher.match(path):
  280. self.print_file_status('x', path)
  281. return
  282. if st is None:
  283. try:
  284. st = os.lstat(path)
  285. except OSError as e:
  286. self.print_warning('%s: %s', path, e)
  287. return
  288. if (st.st_ino, st.st_dev) in skip_inodes:
  289. return
  290. # Entering a new filesystem?
  291. if restrict_dev is not None and st.st_dev != restrict_dev:
  292. return
  293. status = None
  294. # Ignore if nodump flag is set
  295. if get_flags(path, st) & stat.UF_NODUMP:
  296. self.print_file_status('x', path)
  297. return
  298. if stat.S_ISREG(st.st_mode) or read_special and not stat.S_ISDIR(st.st_mode):
  299. if not dry_run:
  300. try:
  301. status = archive.process_file(path, st, cache, self.ignore_inode)
  302. except OSError as e:
  303. status = 'E'
  304. self.print_warning('%s: %s', path, e)
  305. elif stat.S_ISDIR(st.st_mode):
  306. tag_paths = dir_is_tagged(path, exclude_caches, exclude_if_present)
  307. if tag_paths:
  308. if keep_tag_files and not dry_run:
  309. archive.process_dir(path, st)
  310. for tag_path in tag_paths:
  311. self._process(archive, cache, matcher, exclude_caches, exclude_if_present,
  312. keep_tag_files, skip_inodes, tag_path, restrict_dev,
  313. read_special=read_special, dry_run=dry_run)
  314. return
  315. if not dry_run:
  316. status = archive.process_dir(path, st)
  317. try:
  318. entries = helpers.scandir_inorder(path)
  319. except OSError as e:
  320. status = 'E'
  321. self.print_warning('%s: %s', path, e)
  322. else:
  323. for dirent in entries:
  324. normpath = os.path.normpath(dirent.path)
  325. self._process(archive, cache, matcher, exclude_caches, exclude_if_present,
  326. keep_tag_files, skip_inodes, normpath, restrict_dev,
  327. read_special=read_special, dry_run=dry_run)
  328. elif stat.S_ISLNK(st.st_mode):
  329. if not dry_run:
  330. status = archive.process_symlink(path, st)
  331. elif stat.S_ISFIFO(st.st_mode):
  332. if not dry_run:
  333. status = archive.process_fifo(path, st)
  334. elif stat.S_ISCHR(st.st_mode) or stat.S_ISBLK(st.st_mode):
  335. if not dry_run:
  336. status = archive.process_dev(path, st)
  337. elif stat.S_ISSOCK(st.st_mode):
  338. # Ignore unix sockets
  339. return
  340. elif stat.S_ISDOOR(st.st_mode):
  341. # Ignore Solaris doors
  342. return
  343. elif stat.S_ISPORT(st.st_mode):
  344. # Ignore Solaris event ports
  345. return
  346. else:
  347. self.print_warning('Unknown file type: %s', path)
  348. return
  349. # Status output
  350. if status is None:
  351. if not dry_run:
  352. status = '?' # need to add a status code somewhere
  353. else:
  354. status = '-' # dry run, item was not backed up
  355. self.print_file_status(status, path)
  356. @with_repository()
  357. @with_archive
  358. def do_extract(self, args, repository, manifest, key, archive):
  359. """Extract archive contents"""
  360. # be restrictive when restoring files, restore permissions later
  361. if sys.getfilesystemencoding() == 'ascii':
  362. logger.warning('Warning: File system encoding is "ascii", extracting non-ascii filenames will not be supported.')
  363. if sys.platform.startswith(('linux', 'freebsd', 'netbsd', 'openbsd', 'darwin', )):
  364. logger.warning('Hint: You likely need to fix your locale setup. E.g. install locales and use: LANG=en_US.UTF-8')
  365. matcher, include_patterns = self.build_matcher(args.excludes, args.paths)
  366. output_list = args.output_list
  367. dry_run = args.dry_run
  368. stdout = args.stdout
  369. sparse = args.sparse
  370. strip_components = args.strip_components
  371. dirs = []
  372. partial_extract = not matcher.empty() or strip_components
  373. hardlink_masters = {} if partial_extract else None
  374. def item_is_hardlink_master(item):
  375. return (partial_extract and stat.S_ISREG(item[b'mode']) and
  376. item.get(b'hardlink_master', True) and b'source' not in item)
  377. for item in archive.iter_items(preload=True,
  378. filter=lambda item: item_is_hardlink_master(item) or matcher.match(item[b'path'])):
  379. orig_path = item[b'path']
  380. if item_is_hardlink_master(item):
  381. hardlink_masters[orig_path] = (item.get(b'chunks'), None)
  382. if not matcher.match(item[b'path']):
  383. continue
  384. if strip_components:
  385. item[b'path'] = os.sep.join(orig_path.split(os.sep)[strip_components:])
  386. if not item[b'path']:
  387. continue
  388. if not args.dry_run:
  389. while dirs and not item[b'path'].startswith(dirs[-1][b'path']):
  390. archive.extract_item(dirs.pop(-1), stdout=stdout)
  391. if output_list:
  392. logging.getLogger('borg.output.list').info(remove_surrogates(orig_path))
  393. try:
  394. if dry_run:
  395. archive.extract_item(item, dry_run=True)
  396. else:
  397. if stat.S_ISDIR(item[b'mode']):
  398. dirs.append(item)
  399. archive.extract_item(item, restore_attrs=False)
  400. else:
  401. archive.extract_item(item, stdout=stdout, sparse=sparse, hardlink_masters=hardlink_masters,
  402. original_path=orig_path)
  403. except OSError as e:
  404. self.print_warning('%s: %s', remove_surrogates(orig_path), e)
  405. if not args.dry_run:
  406. while dirs:
  407. archive.extract_item(dirs.pop(-1))
  408. for pattern in include_patterns:
  409. if pattern.match_count == 0:
  410. self.print_warning("Include pattern '%s' never matched.", pattern)
  411. return self.exit_code
  412. @with_repository()
  413. @with_archive
  414. def do_diff(self, args, repository, manifest, key, archive):
  415. """Diff contents of two archives"""
  416. def fetch_and_compare_chunks(chunk_ids1, chunk_ids2, archive1, archive2):
  417. chunks1 = archive1.pipeline.fetch_many(chunk_ids1)
  418. chunks2 = archive2.pipeline.fetch_many(chunk_ids2)
  419. return self.compare_chunk_contents(chunks1, chunks2)
  420. def sum_chunk_size(item, consider_ids=None):
  421. if item.get(b'deleted'):
  422. return None
  423. else:
  424. return sum(c.size for c in item[b'chunks']
  425. if consider_ids is None or c.id in consider_ids)
  426. def get_owner(item):
  427. if args.numeric_owner:
  428. return item[b'uid'], item[b'gid']
  429. else:
  430. return item[b'user'], item[b'group']
  431. def get_mode(item):
  432. if b'mode' in item:
  433. return stat.filemode(item[b'mode'])
  434. else:
  435. return [None]
  436. def has_hardlink_master(item, hardlink_masters):
  437. return stat.S_ISREG(item[b'mode']) and item.get(b'source') in hardlink_masters
  438. def compare_link(item1, item2):
  439. # These are the simple link cases. For special cases, e.g. if a
  440. # regular file is replaced with a link or vice versa, it is
  441. # indicated in compare_mode instead.
  442. if item1.get(b'deleted'):
  443. return 'added link'
  444. elif item2.get(b'deleted'):
  445. return 'removed link'
  446. elif b'source' in item1 and b'source' in item2 and item1[b'source'] != item2[b'source']:
  447. return 'changed link'
  448. def contents_changed(item1, item2):
  449. if can_compare_chunk_ids:
  450. return item1[b'chunks'] != item2[b'chunks']
  451. else:
  452. if sum_chunk_size(item1) != sum_chunk_size(item2):
  453. return True
  454. else:
  455. chunk_ids1 = [c.id for c in item1[b'chunks']]
  456. chunk_ids2 = [c.id for c in item2[b'chunks']]
  457. return not fetch_and_compare_chunks(chunk_ids1, chunk_ids2, archive1, archive2)
  458. def compare_content(path, item1, item2):
  459. if contents_changed(item1, item2):
  460. if item1.get(b'deleted'):
  461. return ('added {:>13}'.format(format_file_size(sum_chunk_size(item2))))
  462. elif item2.get(b'deleted'):
  463. return ('removed {:>11}'.format(format_file_size(sum_chunk_size(item1))))
  464. else:
  465. chunk_ids1 = {c.id for c in item1[b'chunks']}
  466. chunk_ids2 = {c.id for c in item2[b'chunks']}
  467. added_ids = chunk_ids2 - chunk_ids1
  468. removed_ids = chunk_ids1 - chunk_ids2
  469. added = sum_chunk_size(item2, added_ids)
  470. removed = sum_chunk_size(item1, removed_ids)
  471. return ('{:>9} {:>9}'.format(format_file_size(added, precision=1, sign=True),
  472. format_file_size(-removed, precision=1, sign=True)))
  473. def compare_directory(item1, item2):
  474. if item2.get(b'deleted') and not item1.get(b'deleted'):
  475. return 'removed directory'
  476. elif item1.get(b'deleted') and not item2.get(b'deleted'):
  477. return 'added directory'
  478. def compare_owner(item1, item2):
  479. user1, group1 = get_owner(item1)
  480. user2, group2 = get_owner(item2)
  481. if user1 != user2 or group1 != group2:
  482. return '[{}:{} -> {}:{}]'.format(user1, group1, user2, group2)
  483. def compare_mode(item1, item2):
  484. if item1[b'mode'] != item2[b'mode']:
  485. return '[{} -> {}]'.format(get_mode(item1), get_mode(item2))
  486. def compare_items(output, path, item1, item2, hardlink_masters, deleted=False):
  487. """
  488. Compare two items with identical paths.
  489. :param deleted: Whether one of the items has been deleted
  490. """
  491. changes = []
  492. if has_hardlink_master(item1, hardlink_masters):
  493. item1 = hardlink_masters[item1[b'source']][0]
  494. if has_hardlink_master(item2, hardlink_masters):
  495. item2 = hardlink_masters[item2[b'source']][1]
  496. if get_mode(item1)[0] == 'l' or get_mode(item2)[0] == 'l':
  497. changes.append(compare_link(item1, item2))
  498. if b'chunks' in item1 and b'chunks' in item2:
  499. changes.append(compare_content(path, item1, item2))
  500. if get_mode(item1)[0] == 'd' or get_mode(item2)[0] == 'd':
  501. changes.append(compare_directory(item1, item2))
  502. if not deleted:
  503. changes.append(compare_owner(item1, item2))
  504. changes.append(compare_mode(item1, item2))
  505. changes = [x for x in changes if x]
  506. if changes:
  507. output_line = (remove_surrogates(path), ' '.join(changes))
  508. if args.sort:
  509. output.append(output_line)
  510. else:
  511. print_output(output_line)
  512. def print_output(line):
  513. print("{:<19} {}".format(line[1], line[0]))
  514. def compare_archives(archive1, archive2, matcher):
  515. def hardlink_master_seen(item):
  516. return b'source' not in item or not stat.S_ISREG(item[b'mode']) or item[b'source'] in hardlink_masters
  517. def is_hardlink_master(item):
  518. return item.get(b'hardlink_master', True) and b'source' not in item
  519. def update_hardlink_masters(item1, item2):
  520. if is_hardlink_master(item1) or is_hardlink_master(item2):
  521. hardlink_masters[item1[b'path']] = (item1, item2)
  522. def compare_or_defer(item1, item2):
  523. update_hardlink_masters(item1, item2)
  524. if not hardlink_master_seen(item1) or not hardlink_master_seen(item2):
  525. deferred.append((item1, item2))
  526. else:
  527. compare_items(output, item1[b'path'], item1, item2, hardlink_masters)
  528. orphans_archive1 = collections.OrderedDict()
  529. orphans_archive2 = collections.OrderedDict()
  530. deferred = []
  531. hardlink_masters = {}
  532. output = []
  533. for item1, item2 in zip_longest(
  534. archive1.iter_items(lambda item: matcher.match(item[b'path'])),
  535. archive2.iter_items(lambda item: matcher.match(item[b'path'])),
  536. ):
  537. if item1 and item2 and item1[b'path'] == item2[b'path']:
  538. compare_or_defer(item1, item2)
  539. continue
  540. if item1:
  541. matching_orphan = orphans_archive2.pop(item1[b'path'], None)
  542. if matching_orphan:
  543. compare_or_defer(item1, matching_orphan)
  544. else:
  545. orphans_archive1[item1[b'path']] = item1
  546. if item2:
  547. matching_orphan = orphans_archive1.pop(item2[b'path'], None)
  548. if matching_orphan:
  549. compare_or_defer(matching_orphan, item2)
  550. else:
  551. orphans_archive2[item2[b'path']] = item2
  552. # At this point orphans_* contain items that had no matching partner in the other archive
  553. deleted_item = {
  554. b'deleted': True,
  555. b'chunks': [],
  556. b'mode': 0,
  557. }
  558. for added in orphans_archive2.values():
  559. path = added[b'path']
  560. deleted_item[b'path'] = path
  561. update_hardlink_masters(deleted_item, added)
  562. compare_items(output, path, deleted_item, added, hardlink_masters, deleted=True)
  563. for deleted in orphans_archive1.values():
  564. path = deleted[b'path']
  565. deleted_item[b'path'] = path
  566. update_hardlink_masters(deleted, deleted_item)
  567. compare_items(output, path, deleted, deleted_item, hardlink_masters, deleted=True)
  568. for item1, item2 in deferred:
  569. assert hardlink_master_seen(item1)
  570. assert hardlink_master_seen(item2)
  571. compare_items(output, item1[b'path'], item1, item2, hardlink_masters)
  572. for line in sorted(output):
  573. print_output(line)
  574. archive1 = archive
  575. archive2 = Archive(repository, key, manifest, args.archive2)
  576. can_compare_chunk_ids = archive1.metadata.get(b'chunker_params', False) == archive2.metadata.get(
  577. b'chunker_params', True) or args.same_chunker_params
  578. if not can_compare_chunk_ids:
  579. self.print_warning('--chunker-params might be different between archives, diff will be slow.\n'
  580. 'If you know for certain that they are the same, pass --same-chunker-params '
  581. 'to override this check.')
  582. matcher, include_patterns = self.build_matcher(args.excludes, args.paths)
  583. compare_archives(archive1, archive2, matcher)
  584. for pattern in include_patterns:
  585. if pattern.match_count == 0:
  586. self.print_warning("Include pattern '%s' never matched.", pattern)
  587. return self.exit_code
  588. @with_repository(exclusive=True, cache=True)
  589. @with_archive
  590. def do_rename(self, args, repository, manifest, key, cache, archive):
  591. """Rename an existing archive"""
  592. archive.rename(args.name)
  593. manifest.write()
  594. repository.commit()
  595. cache.commit()
  596. return self.exit_code
  597. @with_repository(exclusive=True)
  598. def do_delete(self, args, repository, manifest, key):
  599. """Delete an existing repository or archive"""
  600. if args.location.archive:
  601. with Cache(repository, key, manifest, lock_wait=self.lock_wait) as cache:
  602. archive = Archive(repository, key, manifest, args.location.archive, cache=cache)
  603. stats = Statistics()
  604. archive.delete(stats, progress=args.progress)
  605. manifest.write()
  606. repository.commit(save_space=args.save_space)
  607. cache.commit()
  608. logger.info("Archive deleted.")
  609. if args.stats:
  610. log_multi(DASHES,
  611. stats.summary.format(label='Deleted data:', stats=stats),
  612. str(cache),
  613. DASHES, logger=logging.getLogger('borg.output.stats'))
  614. else:
  615. if not args.cache_only:
  616. msg = []
  617. msg.append("You requested to completely DELETE the repository *including* all archives it contains:")
  618. for archive_info in manifest.list_archive_infos(sort_by='ts'):
  619. msg.append(format_archive(archive_info))
  620. msg.append("Type 'YES' if you understand this and want to continue: ")
  621. msg = '\n'.join(msg)
  622. if not yes(msg, false_msg="Aborting.", truish=('YES', ),
  623. env_var_override='BORG_DELETE_I_KNOW_WHAT_I_AM_DOING'):
  624. self.exit_code = EXIT_ERROR
  625. return self.exit_code
  626. repository.destroy()
  627. logger.info("Repository deleted.")
  628. Cache.destroy(repository)
  629. logger.info("Cache deleted.")
  630. return self.exit_code
  631. @with_repository()
  632. def do_mount(self, args, repository, manifest, key):
  633. """Mount archive or an entire repository as a FUSE filesystem"""
  634. try:
  635. from .fuse import FuseOperations
  636. except ImportError as e:
  637. self.print_error('Loading fuse support failed [ImportError: %s]' % str(e))
  638. return self.exit_code
  639. if not os.path.isdir(args.mountpoint) or not os.access(args.mountpoint, os.R_OK | os.W_OK | os.X_OK):
  640. self.print_error('%s: Mountpoint must be a writable directory' % args.mountpoint)
  641. return self.exit_code
  642. with cache_if_remote(repository) as cached_repo:
  643. if args.location.archive:
  644. archive = Archive(repository, key, manifest, args.location.archive)
  645. else:
  646. archive = None
  647. operations = FuseOperations(key, repository, manifest, archive, cached_repo)
  648. logger.info("Mounting filesystem")
  649. try:
  650. operations.mount(args.mountpoint, args.options, args.foreground)
  651. except RuntimeError:
  652. # Relevant error message already printed to stderr by fuse
  653. self.exit_code = EXIT_ERROR
  654. return self.exit_code
  655. @with_repository()
  656. def do_list(self, args, repository, manifest, key):
  657. """List archive or repository contents"""
  658. if args.location.archive:
  659. matcher, _ = self.build_matcher(args.excludes, args.paths)
  660. with Cache(repository, key, manifest, lock_wait=self.lock_wait) as cache:
  661. archive = Archive(repository, key, manifest, args.location.archive, cache=cache)
  662. if args.format:
  663. format = args.format
  664. elif args.short:
  665. format = "{path}{NL}"
  666. else:
  667. format = "{mode} {user:6} {group:6} {size:8} {isomtime} {path}{extra}{NL}"
  668. formatter = ItemFormatter(archive, format)
  669. if not hasattr(sys.stdout, 'buffer'):
  670. # This is a shim for supporting unit tests replacing sys.stdout with e.g. StringIO,
  671. # which doesn't have an underlying buffer (= lower file object).
  672. def write(bytestring):
  673. sys.stdout.write(bytestring.decode('utf-8', errors='replace'))
  674. else:
  675. write = sys.stdout.buffer.write
  676. for item in archive.iter_items(lambda item: matcher.match(item[b'path'])):
  677. write(safe_encode(formatter.format_item(item)))
  678. else:
  679. for archive_info in manifest.list_archive_infos(sort_by='ts'):
  680. if args.prefix and not archive_info.name.startswith(args.prefix):
  681. continue
  682. if args.short:
  683. print(archive_info.name)
  684. else:
  685. print(format_archive(archive_info))
  686. return self.exit_code
  687. @with_repository(cache=True)
  688. @with_archive
  689. def do_info(self, args, repository, manifest, key, archive, cache):
  690. """Show archive details such as disk space used"""
  691. def format_cmdline(cmdline):
  692. return remove_surrogates(' '.join(shlex.quote(x) for x in cmdline))
  693. stats = archive.calc_stats(cache)
  694. print('Archive name: %s' % archive.name)
  695. print('Archive fingerprint: %s' % archive.fpr)
  696. print('Comment: %s' % archive.metadata.get(b'comment', ''))
  697. print('Hostname: %s' % archive.metadata[b'hostname'])
  698. print('Username: %s' % archive.metadata[b'username'])
  699. print('Time (start): %s' % format_time(to_localtime(archive.ts)))
  700. print('Time (end): %s' % format_time(to_localtime(archive.ts_end)))
  701. print('Duration: %s' % archive.duration_from_meta)
  702. print('Number of files: %d' % stats.nfiles)
  703. print('Command line: %s' % format_cmdline(archive.metadata[b'cmdline']))
  704. print(DASHES)
  705. print(str(stats))
  706. print(str(cache))
  707. return self.exit_code
  708. @with_repository()
  709. def do_prune(self, args, repository, manifest, key):
  710. """Prune repository archives according to specified rules"""
  711. if not any((args.secondly, args.minutely, args.hourly, args.daily,
  712. args.weekly, args.monthly, args.yearly, args.within)):
  713. self.print_error('At least one of the "keep-within", "keep-last", '
  714. '"keep-secondly", "keep-minutely", "keep-hourly", "keep-daily", '
  715. '"keep-weekly", "keep-monthly" or "keep-yearly" settings must be specified.')
  716. return self.exit_code
  717. archives_checkpoints = manifest.list_archive_infos(sort_by='ts', reverse=True) # just a ArchiveInfo list
  718. if args.prefix:
  719. archives_checkpoints = [arch for arch in archives_checkpoints if arch.name.startswith(args.prefix)]
  720. is_checkpoint = re.compile(r'\.checkpoint(\.\d+)?$').search
  721. checkpoints = [arch for arch in archives_checkpoints if is_checkpoint(arch.name)]
  722. # keep the latest checkpoint, if there is no later non-checkpoint archive
  723. if archives_checkpoints and checkpoints and archives_checkpoints[0] is checkpoints[0]:
  724. keep_checkpoints = checkpoints[:1]
  725. else:
  726. keep_checkpoints = []
  727. checkpoints = set(checkpoints)
  728. # ignore all checkpoint archives to avoid keeping one (which is an incomplete backup)
  729. # that is newer than a successfully completed backup - and killing the successful backup.
  730. archives = [arch for arch in archives_checkpoints if arch not in checkpoints]
  731. keep = []
  732. if args.within:
  733. keep += prune_within(archives, args.within)
  734. if args.secondly:
  735. keep += prune_split(archives, '%Y-%m-%d %H:%M:%S', args.secondly, keep)
  736. if args.minutely:
  737. keep += prune_split(archives, '%Y-%m-%d %H:%M', args.minutely, keep)
  738. if args.hourly:
  739. keep += prune_split(archives, '%Y-%m-%d %H', args.hourly, keep)
  740. if args.daily:
  741. keep += prune_split(archives, '%Y-%m-%d', args.daily, keep)
  742. if args.weekly:
  743. keep += prune_split(archives, '%G-%V', args.weekly, keep)
  744. if args.monthly:
  745. keep += prune_split(archives, '%Y-%m', args.monthly, keep)
  746. if args.yearly:
  747. keep += prune_split(archives, '%Y', args.yearly, keep)
  748. to_delete = (set(archives) | checkpoints) - (set(keep) | set(keep_checkpoints))
  749. stats = Statistics()
  750. with Cache(repository, key, manifest, do_files=args.cache_files, lock_wait=self.lock_wait) as cache:
  751. list_logger = logging.getLogger('borg.output.list')
  752. for archive in archives_checkpoints:
  753. if archive in to_delete:
  754. if args.dry_run:
  755. if args.output_list:
  756. list_logger.info('Would prune: %s' % format_archive(archive))
  757. else:
  758. if args.output_list:
  759. list_logger.info('Pruning archive: %s' % format_archive(archive))
  760. Archive(repository, key, manifest, archive.name, cache).delete(stats)
  761. else:
  762. if args.output_list:
  763. list_logger.info('Keeping archive: %s' % format_archive(archive))
  764. if to_delete and not args.dry_run:
  765. manifest.write()
  766. repository.commit(save_space=args.save_space)
  767. cache.commit()
  768. if args.stats:
  769. log_multi(DASHES,
  770. stats.summary.format(label='Deleted data:', stats=stats),
  771. str(cache),
  772. DASHES, logger=logging.getLogger('borg.output.stats'))
  773. return self.exit_code
  774. def do_upgrade(self, args):
  775. """upgrade a repository from a previous version"""
  776. # mainly for upgrades from Attic repositories,
  777. # but also supports borg 0.xx -> 1.0 upgrade.
  778. repo = AtticRepositoryUpgrader(args.location.path, create=False)
  779. try:
  780. repo.upgrade(args.dry_run, inplace=args.inplace, progress=args.progress)
  781. except NotImplementedError as e:
  782. print("warning: %s" % e)
  783. repo = BorgRepositoryUpgrader(args.location.path, create=False)
  784. try:
  785. repo.upgrade(args.dry_run, inplace=args.inplace, progress=args.progress)
  786. except NotImplementedError as e:
  787. print("warning: %s" % e)
  788. return self.exit_code
  789. @with_repository(cache=True, exclusive=True)
  790. def do_recreate(self, args, repository, manifest, key, cache):
  791. """Re-create archives"""
  792. def interrupt(signal_num, stack_frame):
  793. if recreater.interrupt:
  794. print("\nReceived signal, again. I'm not deaf.", file=sys.stderr)
  795. else:
  796. print("\nReceived signal, will exit cleanly.", file=sys.stderr)
  797. recreater.interrupt = True
  798. msg = ("recreate is an experimental feature.\n"
  799. "Type 'YES' if you understand this and want to continue: ")
  800. if not yes(msg, false_msg="Aborting.", truish=('YES',),
  801. env_var_override='BORG_RECREATE_I_KNOW_WHAT_I_AM_DOING'):
  802. return EXIT_ERROR
  803. matcher, include_patterns = self.build_matcher(args.excludes, args.paths)
  804. self.output_list = args.output_list
  805. self.output_filter = args.output_filter
  806. recreater = ArchiveRecreater(repository, manifest, key, cache, matcher,
  807. exclude_caches=args.exclude_caches, exclude_if_present=args.exclude_if_present,
  808. keep_tag_files=args.keep_tag_files, chunker_params=args.chunker_params,
  809. compression=args.compression, compression_files=args.compression_files,
  810. progress=args.progress, stats=args.stats,
  811. file_status_printer=self.print_file_status,
  812. dry_run=args.dry_run)
  813. signal.signal(signal.SIGTERM, interrupt)
  814. signal.signal(signal.SIGINT, interrupt)
  815. if args.location.archive:
  816. name = args.location.archive
  817. if recreater.is_temporary_archive(name):
  818. self.print_error('Refusing to work on temporary archive of prior recreate: %s', name)
  819. return self.exit_code
  820. recreater.recreate(name, args.comment)
  821. else:
  822. for archive in manifest.list_archive_infos(sort_by='ts'):
  823. name = archive.name
  824. if recreater.is_temporary_archive(name):
  825. continue
  826. print('Processing', name)
  827. if not recreater.recreate(name, args.comment):
  828. break
  829. manifest.write()
  830. repository.commit()
  831. cache.commit()
  832. return self.exit_code
  833. @with_repository(manifest=False)
  834. def do_with_lock(self, args, repository):
  835. """run a user specified command with the repository lock held"""
  836. # re-write manifest to start a repository transaction - this causes a
  837. # lock upgrade to exclusive for remote (and also for local) repositories.
  838. # by using manifest=False in the decorator, we avoid having to require
  839. # the encryption key (and can operate just with encrypted data).
  840. data = repository.get(Manifest.MANIFEST_ID)
  841. repository.put(Manifest.MANIFEST_ID, data)
  842. try:
  843. # we exit with the return code we get from the subprocess
  844. return subprocess.call([args.command] + args.args)
  845. finally:
  846. repository.rollback()
  847. @with_repository()
  848. def do_debug_dump_archive_items(self, args, repository, manifest, key):
  849. """dump (decrypted, decompressed) archive items metadata (not: data)"""
  850. archive = Archive(repository, key, manifest, args.location.archive)
  851. for i, item_id in enumerate(archive.metadata[b'items']):
  852. _, data = key.decrypt(item_id, repository.get(item_id))
  853. filename = '%06d_%s.items' % (i, bin_to_hex(item_id))
  854. print('Dumping', filename)
  855. with open(filename, 'wb') as fd:
  856. fd.write(data)
  857. print('Done.')
  858. return EXIT_SUCCESS
  859. @with_repository(manifest=False)
  860. def do_debug_get_obj(self, args, repository):
  861. """get object contents from the repository and write it into file"""
  862. hex_id = args.id
  863. try:
  864. id = unhexlify(hex_id)
  865. except ValueError:
  866. print("object id %s is invalid." % hex_id)
  867. else:
  868. try:
  869. data = repository.get(id)
  870. except repository.ObjectNotFound:
  871. print("object %s not found." % hex_id)
  872. else:
  873. with open(args.path, "wb") as f:
  874. f.write(data)
  875. print("object %s fetched." % hex_id)
  876. return EXIT_SUCCESS
  877. @with_repository(manifest=False)
  878. def do_debug_put_obj(self, args, repository):
  879. """put file(s) contents into the repository"""
  880. for path in args.paths:
  881. with open(path, "rb") as f:
  882. data = f.read()
  883. h = hashlib.sha256(data) # XXX hardcoded
  884. repository.put(h.digest(), data)
  885. print("object %s put." % h.hexdigest())
  886. repository.commit()
  887. return EXIT_SUCCESS
  888. @with_repository(manifest=False)
  889. def do_debug_delete_obj(self, args, repository):
  890. """delete the objects with the given IDs from the repo"""
  891. modified = False
  892. for hex_id in args.ids:
  893. try:
  894. id = unhexlify(hex_id)
  895. except ValueError:
  896. print("object id %s is invalid." % hex_id)
  897. else:
  898. try:
  899. repository.delete(id)
  900. modified = True
  901. print("object %s deleted." % hex_id)
  902. except repository.ObjectNotFound:
  903. print("object %s not found." % hex_id)
  904. if modified:
  905. repository.commit()
  906. print('Done.')
  907. return EXIT_SUCCESS
  908. @with_repository(lock=False, manifest=False)
  909. def do_break_lock(self, args, repository):
  910. """Break the repository lock (e.g. in case it was left by a dead borg."""
  911. repository.break_lock()
  912. Cache.break_lock(repository)
  913. return self.exit_code
  914. helptext = {}
  915. helptext['patterns'] = textwrap.dedent('''
  916. Exclusion patterns support four separate styles, fnmatch, shell, regular
  917. expressions and path prefixes. If followed by a colon (':') the first two
  918. characters of a pattern are used as a style selector. Explicit style
  919. selection is necessary when a non-default style is desired or when the
  920. desired pattern starts with two alphanumeric characters followed by a colon
  921. (i.e. `aa:something/*`).
  922. `Fnmatch <https://docs.python.org/3/library/fnmatch.html>`_, selector `fm:`
  923. These patterns use a variant of shell pattern syntax, with '*' matching
  924. any number of characters, '?' matching any single character, '[...]'
  925. matching any single character specified, including ranges, and '[!...]'
  926. matching any character not specified. For the purpose of these patterns,
  927. the path separator ('\\' for Windows and '/' on other systems) is not
  928. treated specially. Wrap meta-characters in brackets for a literal match
  929. (i.e. `[?]` to match the literal character `?`). For a path to match
  930. a pattern, it must completely match from start to end, or must match from
  931. the start to just before a path separator. Except for the root path,
  932. paths will never end in the path separator when matching is attempted.
  933. Thus, if a given pattern ends in a path separator, a '*' is appended
  934. before matching is attempted.
  935. Shell-style patterns, selector `sh:`
  936. Like fnmatch patterns these are similar to shell patterns. The difference
  937. is that the pattern may include `**/` for matching zero or more directory
  938. levels, `*` for matching zero or more arbitrary characters with the
  939. exception of any path separator.
  940. Regular expressions, selector `re:`
  941. Regular expressions similar to those found in Perl are supported. Unlike
  942. shell patterns regular expressions are not required to match the complete
  943. path and any substring match is sufficient. It is strongly recommended to
  944. anchor patterns to the start ('^'), to the end ('$') or both. Path
  945. separators ('\\' for Windows and '/' on other systems) in paths are
  946. always normalized to a forward slash ('/') before applying a pattern. The
  947. regular expression syntax is described in the `Python documentation for
  948. the re module <https://docs.python.org/3/library/re.html>`_.
  949. Prefix path, selector `pp:`
  950. This pattern style is useful to match whole sub-directories. The pattern
  951. `pp:/data/bar` matches `/data/bar` and everything therein.
  952. Exclusions can be passed via the command line option `--exclude`. When used
  953. from within a shell the patterns should be quoted to protect them from
  954. expansion.
  955. The `--exclude-from` option permits loading exclusion patterns from a text
  956. file with one pattern per line. Lines empty or starting with the number sign
  957. ('#') after removing whitespace on both ends are ignored. The optional style
  958. selector prefix is also supported for patterns loaded from a file. Due to
  959. whitespace removal paths with whitespace at the beginning or end can only be
  960. excluded using regular expressions.
  961. Examples:
  962. # Exclude '/home/user/file.o' but not '/home/user/file.odt':
  963. $ borg create -e '*.o' backup /
  964. # Exclude '/home/user/junk' and '/home/user/subdir/junk' but
  965. # not '/home/user/importantjunk' or '/etc/junk':
  966. $ borg create -e '/home/*/junk' backup /
  967. # Exclude the contents of '/home/user/cache' but not the directory itself:
  968. $ borg create -e /home/user/cache/ backup /
  969. # The file '/home/user/cache/important' is *not* backed up:
  970. $ borg create -e /home/user/cache/ backup / /home/user/cache/important
  971. # The contents of directories in '/home' are not backed up when their name
  972. # ends in '.tmp'
  973. $ borg create --exclude 're:^/home/[^/]+\.tmp/' backup /
  974. # Load exclusions from file
  975. $ cat >exclude.txt <<EOF
  976. # Comment line
  977. /home/*/junk
  978. *.tmp
  979. fm:aa:something/*
  980. re:^/home/[^/]\.tmp/
  981. sh:/home/*/.thumbnails
  982. EOF
  983. $ borg create --exclude-from exclude.txt backup /
  984. ''')
  985. def do_help(self, parser, commands, args):
  986. if not args.topic:
  987. parser.print_help()
  988. elif args.topic in self.helptext:
  989. print(self.helptext[args.topic])
  990. elif args.topic in commands:
  991. if args.epilog_only:
  992. print(commands[args.topic].epilog)
  993. elif args.usage_only:
  994. commands[args.topic].epilog = None
  995. commands[args.topic].print_help()
  996. else:
  997. commands[args.topic].print_help()
  998. else:
  999. parser.error('No help available on %s' % (args.topic,))
  1000. return self.exit_code
  1001. def preprocess_args(self, args):
  1002. deprecations = [
  1003. # ('--old', '--new', 'Warning: "--old" has been deprecated. Use "--new" instead.'),
  1004. ('--list-format', '--format', 'Warning: "--list-format" has been deprecated. Use "--format" instead.'),
  1005. ]
  1006. for i, arg in enumerate(args[:]):
  1007. for old_name, new_name, warning in deprecations:
  1008. if arg.startswith(old_name):
  1009. args[i] = arg.replace(old_name, new_name)
  1010. self.print_warning(warning)
  1011. return args
  1012. def build_parser(self, prog=None):
  1013. common_parser = argparse.ArgumentParser(add_help=False, prog=prog)
  1014. common_group = common_parser.add_argument_group('Common options')
  1015. common_group.add_argument('-h', '--help', action='help', help='show this help message and exit')
  1016. common_group.add_argument('--critical', dest='log_level',
  1017. action='store_const', const='critical', default='warning',
  1018. help='work on log level CRITICAL')
  1019. common_group.add_argument('--error', dest='log_level',
  1020. action='store_const', const='error', default='warning',
  1021. help='work on log level ERROR')
  1022. common_group.add_argument('--warning', dest='log_level',
  1023. action='store_const', const='warning', default='warning',
  1024. help='work on log level WARNING (default)')
  1025. common_group.add_argument('--info', '-v', '--verbose', dest='log_level',
  1026. action='store_const', const='info', default='warning',
  1027. help='work on log level INFO')
  1028. common_group.add_argument('--debug', dest='log_level',
  1029. action='store_const', const='debug', default='warning',
  1030. help='enable debug output, work on log level DEBUG')
  1031. common_group.add_argument('--lock-wait', dest='lock_wait', type=int, metavar='N', default=1,
  1032. help='wait for the lock, but max. N seconds (default: %(default)d).')
  1033. common_group.add_argument('--show-version', dest='show_version', action='store_true', default=False,
  1034. help='show/log the borg version')
  1035. common_group.add_argument('--show-rc', dest='show_rc', action='store_true', default=False,
  1036. help='show/log the return code (rc)')
  1037. common_group.add_argument('--no-files-cache', dest='cache_files', action='store_false',
  1038. help='do not load/update the file metadata cache used to detect unchanged files')
  1039. common_group.add_argument('--umask', dest='umask', type=lambda s: int(s, 8), default=UMASK_DEFAULT, metavar='M',
  1040. help='set umask to M (local and remote, default: %(default)04o)')
  1041. common_group.add_argument('--remote-path', dest='remote_path', default='borg', metavar='PATH',
  1042. help='set remote path to executable (default: "%(default)s")')
  1043. parser = argparse.ArgumentParser(prog=prog, description='Borg - Deduplicated Backups')
  1044. parser.add_argument('-V', '--version', action='version', version='%(prog)s ' + __version__,
  1045. help='show version number and exit')
  1046. subparsers = parser.add_subparsers(title='required arguments', metavar='<command>')
  1047. serve_epilog = textwrap.dedent("""
  1048. This command starts a repository server process. This command is usually not used manually.
  1049. """)
  1050. subparser = subparsers.add_parser('serve', parents=[common_parser], add_help=False,
  1051. description=self.do_serve.__doc__, epilog=serve_epilog,
  1052. formatter_class=argparse.RawDescriptionHelpFormatter,
  1053. help='start repository server process')
  1054. subparser.set_defaults(func=self.do_serve)
  1055. subparser.add_argument('--restrict-to-path', dest='restrict_to_paths', action='append',
  1056. metavar='PATH', help='restrict repository access to PATH')
  1057. init_epilog = textwrap.dedent("""
  1058. This command initializes an empty repository. A repository is a filesystem
  1059. directory containing the deduplicated data from zero or more archives.
  1060. Encryption can be enabled at repository init time (the default).
  1061. It is not recommended to disable encryption. Repository encryption protects you
  1062. e.g. against the case that an attacker has access to your backup repository.
  1063. But be careful with the key / the passphrase:
  1064. If you want "passphrase-only" security, use the repokey mode. The key will
  1065. be stored inside the repository (in its "config" file). In above mentioned
  1066. attack scenario, the attacker will have the key (but not the passphrase).
  1067. If you want "passphrase and having-the-key" security, use the keyfile mode.
  1068. The key will be stored in your home directory (in .config/borg/keys). In
  1069. the attack scenario, the attacker who has just access to your repo won't have
  1070. the key (and also not the passphrase).
  1071. Make a backup copy of the key file (keyfile mode) or repo config file
  1072. (repokey mode) and keep it at a safe place, so you still have the key in
  1073. case it gets corrupted or lost. Also keep the passphrase at a safe place.
  1074. The backup that is encrypted with that key won't help you with that, of course.
  1075. Make sure you use a good passphrase. Not too short, not too simple. The real
  1076. encryption / decryption key is encrypted with / locked by your passphrase.
  1077. If an attacker gets your key, he can't unlock and use it without knowing the
  1078. passphrase.
  1079. Be careful with special or non-ascii characters in your passphrase:
  1080. - Borg processes the passphrase as unicode (and encodes it as utf-8),
  1081. so it does not have problems dealing with even the strangest characters.
  1082. - BUT: that does not necessarily apply to your OS / VM / keyboard configuration.
  1083. So better use a long passphrase made from simple ascii chars than one that
  1084. includes non-ascii stuff or characters that are hard/impossible to enter on
  1085. a different keyboard layout.
  1086. You can change your passphrase for existing repos at any time, it won't affect
  1087. the encryption/decryption key or other secrets.
  1088. When encrypting, AES-CTR-256 is used for encryption, and HMAC-SHA256 for
  1089. authentication. Hardware acceleration will be used automatically.
  1090. """)
  1091. subparser = subparsers.add_parser('init', parents=[common_parser], add_help=False,
  1092. description=self.do_init.__doc__, epilog=init_epilog,
  1093. formatter_class=argparse.RawDescriptionHelpFormatter,
  1094. help='initialize empty repository')
  1095. subparser.set_defaults(func=self.do_init)
  1096. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  1097. type=location_validator(archive=False),
  1098. help='repository to create')
  1099. subparser.add_argument('-e', '--encryption', dest='encryption',
  1100. choices=('none', 'keyfile', 'repokey'), default='repokey',
  1101. help='select encryption key mode (default: "%(default)s")')
  1102. check_epilog = textwrap.dedent("""
  1103. The check command verifies the consistency of a repository and the corresponding archives.
  1104. First, the underlying repository data files are checked:
  1105. - For all segments the segment magic (header) is checked
  1106. - For all objects stored in the segments, all metadata (e.g. crc and size) and
  1107. all data is read. The read data is checked by size and CRC. Bit rot and other
  1108. types of accidental damage can be detected this way.
  1109. - If we are in repair mode and a integrity error is detected for a segment,
  1110. we try to recover as many objects from the segment as possible.
  1111. - In repair mode, it makes sure that the index is consistent with the data
  1112. stored in the segments.
  1113. - If you use a remote repo server via ssh:, the repo check is executed on the
  1114. repo server without causing significant network traffic.
  1115. - The repository check can be skipped using the --archives-only option.
  1116. Second, the consistency and correctness of the archive metadata is verified:
  1117. - Is the repo manifest present? If not, it is rebuilt from archive metadata
  1118. chunks (this requires reading and decrypting of all metadata and data).
  1119. - Check if archive metadata chunk is present. if not, remove archive from
  1120. manifest.
  1121. - For all files (items) in the archive, for all chunks referenced by these
  1122. files, check if chunk is present (if not and we are in repair mode, replace
  1123. it with a same-size chunk of zeros). This requires reading of archive and
  1124. file metadata, but not data.
  1125. - If we are in repair mode and we checked all the archives: delete orphaned
  1126. chunks from the repo.
  1127. - if you use a remote repo server via ssh:, the archive check is executed on
  1128. the client machine (because if encryption is enabled, the checks will require
  1129. decryption and this is always done client-side, because key access will be
  1130. required).
  1131. - The archive checks can be time consuming, they can be skipped using the
  1132. --repository-only option.
  1133. The --verify-data option will perform a full integrity verification (as opposed to
  1134. checking the CRC32 of the segment) of data, which means reading the data from the
  1135. repository, decrypting and decompressing it. This is a cryptographic verification,
  1136. which will detect (accidental) corruption. For encrypted repositories it is
  1137. tamper-resistant as well, unless the attacker has access to the keys.
  1138. It is also very slow.
  1139. --verify-data only verifies data used by the archives specified with --last,
  1140. --prefix or an explicitly named archive. If none of these are passed,
  1141. all data in the repository is verified.
  1142. """)
  1143. subparser = subparsers.add_parser('check', parents=[common_parser], add_help=False,
  1144. description=self.do_check.__doc__,
  1145. epilog=check_epilog,
  1146. formatter_class=argparse.RawDescriptionHelpFormatter,
  1147. help='verify repository')
  1148. subparser.set_defaults(func=self.do_check)
  1149. subparser.add_argument('location', metavar='REPOSITORY_OR_ARCHIVE', nargs='?', default='',
  1150. type=location_validator(),
  1151. help='repository or archive to check consistency of')
  1152. subparser.add_argument('--repository-only', dest='repo_only', action='store_true',
  1153. default=False,
  1154. help='only perform repository checks')
  1155. subparser.add_argument('--archives-only', dest='archives_only', action='store_true',
  1156. default=False,
  1157. help='only perform archives checks')
  1158. subparser.add_argument('--verify-data', dest='verify_data', action='store_true',
  1159. default=False,
  1160. help='perform cryptographic archive data integrity verification '
  1161. '(conflicts with --repository-only)')
  1162. subparser.add_argument('--repair', dest='repair', action='store_true',
  1163. default=False,
  1164. help='attempt to repair any inconsistencies found')
  1165. subparser.add_argument('--save-space', dest='save_space', action='store_true',
  1166. default=False,
  1167. help='work slower, but using less space')
  1168. subparser.add_argument('--last', dest='last',
  1169. type=int, default=None, metavar='N',
  1170. help='only check last N archives (Default: all)')
  1171. subparser.add_argument('-P', '--prefix', dest='prefix', type=str,
  1172. help='only consider archive names starting with this prefix')
  1173. subparser.add_argument('-p', '--progress', dest='progress',
  1174. action='store_true', default=False,
  1175. help="""show progress display while checking""")
  1176. change_passphrase_epilog = textwrap.dedent("""
  1177. The key files used for repository encryption are optionally passphrase
  1178. protected. This command can be used to change this passphrase.
  1179. """)
  1180. subparser = subparsers.add_parser('change-passphrase', parents=[common_parser], add_help=False,
  1181. description=self.do_change_passphrase.__doc__,
  1182. epilog=change_passphrase_epilog,
  1183. formatter_class=argparse.RawDescriptionHelpFormatter,
  1184. help='change repository passphrase')
  1185. subparser.set_defaults(func=self.do_change_passphrase)
  1186. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  1187. type=location_validator(archive=False))
  1188. migrate_to_repokey_epilog = textwrap.dedent("""
  1189. This command migrates a repository from passphrase mode (not supported any
  1190. more) to repokey mode.
  1191. You will be first asked for the repository passphrase (to open it in passphrase
  1192. mode). This is the same passphrase as you used to use for this repo before 1.0.
  1193. It will then derive the different secrets from this passphrase.
  1194. Then you will be asked for a new passphrase (twice, for safety). This
  1195. passphrase will be used to protect the repokey (which contains these same
  1196. secrets in encrypted form). You may use the same passphrase as you used to
  1197. use, but you may also use a different one.
  1198. After migrating to repokey mode, you can change the passphrase at any time.
  1199. But please note: the secrets will always stay the same and they could always
  1200. be derived from your (old) passphrase-mode passphrase.
  1201. """)
  1202. subparser = subparsers.add_parser('migrate-to-repokey', parents=[common_parser], add_help=False,
  1203. description=self.do_migrate_to_repokey.__doc__,
  1204. epilog=migrate_to_repokey_epilog,
  1205. formatter_class=argparse.RawDescriptionHelpFormatter,
  1206. help='migrate passphrase-mode repository to repokey')
  1207. subparser.set_defaults(func=self.do_migrate_to_repokey)
  1208. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  1209. type=location_validator(archive=False))
  1210. create_epilog = textwrap.dedent("""
  1211. This command creates a backup archive containing all files found while recursively
  1212. traversing all paths specified. The archive will consume almost no disk space for
  1213. files or parts of files that have already been stored in other archives.
  1214. The archive name needs to be unique. It must not end in '.checkpoint' or
  1215. '.checkpoint.N' (with N being a number), because these names are used for
  1216. checkpoints and treated in special ways.
  1217. In the archive name, you may use the following format tags:
  1218. {now}, {utcnow}, {fqdn}, {hostname}, {user}, {pid}
  1219. To speed up pulling backups over sshfs and similar network file systems which do
  1220. not provide correct inode information the --ignore-inode flag can be used. This
  1221. potentially decreases reliability of change detection, while avoiding always reading
  1222. all files on these file systems.
  1223. See the output of the "borg help patterns" command for more help on exclude patterns.
  1224. """)
  1225. subparser = subparsers.add_parser('create', parents=[common_parser], add_help=False,
  1226. description=self.do_create.__doc__,
  1227. epilog=create_epilog,
  1228. formatter_class=argparse.RawDescriptionHelpFormatter,
  1229. help='create backup')
  1230. subparser.set_defaults(func=self.do_create)
  1231. subparser.add_argument('-n', '--dry-run', dest='dry_run',
  1232. action='store_true', default=False,
  1233. help='do not create a backup archive')
  1234. subparser.add_argument('-s', '--stats', dest='stats',
  1235. action='store_true', default=False,
  1236. help='print statistics for the created archive')
  1237. subparser.add_argument('-p', '--progress', dest='progress',
  1238. action='store_true', default=False,
  1239. help='show progress display while creating the archive, showing Original, '
  1240. 'Compressed and Deduplicated sizes, followed by the Number of files seen '
  1241. 'and the path being processed, default: %(default)s')
  1242. subparser.add_argument('--list', dest='output_list',
  1243. action='store_true', default=False,
  1244. help='output verbose list of items (files, dirs, ...)')
  1245. subparser.add_argument('--filter', dest='output_filter', metavar='STATUSCHARS',
  1246. help='only display items with the given status characters')
  1247. exclude_group = subparser.add_argument_group('Exclusion options')
  1248. exclude_group.add_argument('-e', '--exclude', dest='excludes',
  1249. type=parse_pattern, action='append',
  1250. metavar="PATTERN", help='exclude paths matching PATTERN')
  1251. exclude_group.add_argument('--exclude-from', dest='exclude_files',
  1252. type=argparse.FileType('r'), action='append',
  1253. metavar='EXCLUDEFILE', help='read exclude patterns from EXCLUDEFILE, one per line')
  1254. exclude_group.add_argument('--exclude-caches', dest='exclude_caches',
  1255. action='store_true', default=False,
  1256. help='exclude directories that contain a CACHEDIR.TAG file ('
  1257. 'http://www.brynosaurus.com/cachedir/spec.html)')
  1258. exclude_group.add_argument('--exclude-if-present', dest='exclude_if_present',
  1259. metavar='FILENAME', action='append', type=str,
  1260. help='exclude directories that contain the specified file')
  1261. exclude_group.add_argument('--keep-tag-files', dest='keep_tag_files',
  1262. action='store_true', default=False,
  1263. help='keep tag files of excluded caches/directories')
  1264. fs_group = subparser.add_argument_group('Filesystem options')
  1265. fs_group.add_argument('-x', '--one-file-system', dest='one_file_system',
  1266. action='store_true', default=False,
  1267. help='stay in same file system, do not cross mount points')
  1268. fs_group.add_argument('--numeric-owner', dest='numeric_owner',
  1269. action='store_true', default=False,
  1270. help='only store numeric user and group identifiers')
  1271. fs_group.add_argument('--ignore-inode', dest='ignore_inode',
  1272. action='store_true', default=False,
  1273. help='ignore inode data in the file metadata cache used to detect unchanged files.')
  1274. fs_group.add_argument('--read-special', dest='read_special',
  1275. action='store_true', default=False,
  1276. help='open and read special files as if they were regular files')
  1277. archive_group = subparser.add_argument_group('Archive options')
  1278. archive_group.add_argument('--comment', dest='comment', metavar='COMMENT', default='',
  1279. help='add a comment text to the archive')
  1280. archive_group.add_argument('--timestamp', dest='timestamp',
  1281. type=timestamp, default=None,
  1282. metavar='yyyy-mm-ddThh:mm:ss',
  1283. help='manually specify the archive creation date/time (UTC). '
  1284. 'alternatively, give a reference file/directory.')
  1285. archive_group.add_argument('-c', '--checkpoint-interval', dest='checkpoint_interval',
  1286. type=int, default=300, metavar='SECONDS',
  1287. help='write checkpoint every SECONDS seconds (Default: 300)')
  1288. archive_group.add_argument('--chunker-params', dest='chunker_params',
  1289. type=ChunkerParams, default=CHUNKER_PARAMS,
  1290. metavar='CHUNK_MIN_EXP,CHUNK_MAX_EXP,HASH_MASK_BITS,HASH_WINDOW_SIZE',
  1291. help='specify the chunker parameters. default: %d,%d,%d,%d' % CHUNKER_PARAMS)
  1292. archive_group.add_argument('-C', '--compression', dest='compression',
  1293. type=CompressionSpec, default=dict(name='none'), metavar='COMPRESSION',
  1294. help='select compression algorithm (and level):\n'
  1295. 'none == no compression (default),\n'
  1296. 'auto,C[,L] == built-in heuristic decides between none or C[,L] - with C[,L]\n'
  1297. ' being any valid compression algorithm (and optional level),\n'
  1298. 'lz4 == lz4,\n'
  1299. 'zlib == zlib (default level 6),\n'
  1300. 'zlib,0 .. zlib,9 == zlib (with level 0..9),\n'
  1301. 'lzma == lzma (default level 6),\n'
  1302. 'lzma,0 .. lzma,9 == lzma (with level 0..9).')
  1303. archive_group.add_argument('--compression-from', dest='compression_files',
  1304. type=argparse.FileType('r'), action='append',
  1305. metavar='COMPRESSIONCONFIG', help='read compression patterns from COMPRESSIONCONFIG, one per line')
  1306. subparser.add_argument('location', metavar='ARCHIVE',
  1307. type=location_validator(archive=True),
  1308. help='name of archive to create (must be also a valid directory name)')
  1309. subparser.add_argument('paths', metavar='PATH', nargs='+', type=str,
  1310. help='paths to archive')
  1311. extract_epilog = textwrap.dedent("""
  1312. This command extracts the contents of an archive. By default the entire
  1313. archive is extracted but a subset of files and directories can be selected
  1314. by passing a list of ``PATHs`` as arguments. The file selection can further
  1315. be restricted by using the ``--exclude`` option.
  1316. See the output of the "borg help patterns" command for more help on exclude patterns.
  1317. By using ``--dry-run``, you can do all extraction steps except actually writing the
  1318. output data: reading metadata and data chunks from the repo, checking the hash/hmac,
  1319. decrypting, decompressing.
  1320. """)
  1321. subparser = subparsers.add_parser('extract', parents=[common_parser], add_help=False,
  1322. description=self.do_extract.__doc__,
  1323. epilog=extract_epilog,
  1324. formatter_class=argparse.RawDescriptionHelpFormatter,
  1325. help='extract archive contents')
  1326. subparser.set_defaults(func=self.do_extract)
  1327. subparser.add_argument('--list', dest='output_list',
  1328. action='store_true', default=False,
  1329. help='output verbose list of items (files, dirs, ...)')
  1330. subparser.add_argument('-n', '--dry-run', dest='dry_run',
  1331. default=False, action='store_true',
  1332. help='do not actually change any files')
  1333. subparser.add_argument('-e', '--exclude', dest='excludes',
  1334. type=parse_pattern, action='append',
  1335. metavar="PATTERN", help='exclude paths matching PATTERN')
  1336. subparser.add_argument('--exclude-from', dest='exclude_files',
  1337. type=argparse.FileType('r'), action='append',
  1338. metavar='EXCLUDEFILE', help='read exclude patterns from EXCLUDEFILE, one per line')
  1339. subparser.add_argument('--numeric-owner', dest='numeric_owner',
  1340. action='store_true', default=False,
  1341. help='only obey numeric user and group identifiers')
  1342. subparser.add_argument('--strip-components', dest='strip_components',
  1343. type=int, default=0, metavar='NUMBER',
  1344. help='Remove the specified number of leading path elements. Pathnames with fewer elements will be silently skipped.')
  1345. subparser.add_argument('--stdout', dest='stdout',
  1346. action='store_true', default=False,
  1347. help='write all extracted data to stdout')
  1348. subparser.add_argument('--sparse', dest='sparse',
  1349. action='store_true', default=False,
  1350. help='create holes in output sparse file from all-zero chunks')
  1351. subparser.add_argument('location', metavar='ARCHIVE',
  1352. type=location_validator(archive=True),
  1353. help='archive to extract')
  1354. subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
  1355. help='paths to extract; patterns are supported')
  1356. diff_epilog = textwrap.dedent("""
  1357. This command finds differences in files (contents, user, group, mode) between archives.
  1358. Both archives need to be in the same repository, and a repository location may only
  1359. be specified for ARCHIVE1.
  1360. For archives created with Borg 1.1 or newer diff automatically detects whether
  1361. the archives are created with the same chunker params. If so, only chunk IDs
  1362. are compared, which is very fast.
  1363. For archives prior to Borg 1.1 chunk contents are compared by default.
  1364. If you did not create the archives with different chunker params,
  1365. pass --same-chunker-params.
  1366. Note that the chunker params changed from Borg 0.xx to 1.0.
  1367. See the output of the "borg help patterns" command for more help on exclude patterns.
  1368. """)
  1369. subparser = subparsers.add_parser('diff', parents=[common_parser], add_help=False,
  1370. description=self.do_diff.__doc__,
  1371. epilog=diff_epilog,
  1372. formatter_class=argparse.RawDescriptionHelpFormatter,
  1373. help='find differences in archive contents')
  1374. subparser.set_defaults(func=self.do_diff)
  1375. subparser.add_argument('-e', '--exclude', dest='excludes',
  1376. type=parse_pattern, action='append',
  1377. metavar="PATTERN", help='exclude paths matching PATTERN')
  1378. subparser.add_argument('--exclude-from', dest='exclude_files',
  1379. type=argparse.FileType('r'), action='append',
  1380. metavar='EXCLUDEFILE', help='read exclude patterns from EXCLUDEFILE, one per line')
  1381. subparser.add_argument('--numeric-owner', dest='numeric_owner',
  1382. action='store_true', default=False,
  1383. help='only consider numeric user and group identifiers')
  1384. subparser.add_argument('--same-chunker-params', dest='same_chunker_params',
  1385. action='store_true', default=False,
  1386. help='Override check of chunker parameters.')
  1387. subparser.add_argument('--sort', dest='sort',
  1388. action='store_true', default=False,
  1389. help='Sort the output lines by file path.')
  1390. subparser.add_argument('location', metavar='ARCHIVE1',
  1391. type=location_validator(archive=True),
  1392. help='archive')
  1393. subparser.add_argument('archive2', metavar='ARCHIVE2',
  1394. type=archivename_validator(),
  1395. help='archive to compare with ARCHIVE1 (no repository location)')
  1396. subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
  1397. help='paths to compare; patterns are supported')
  1398. rename_epilog = textwrap.dedent("""
  1399. This command renames an archive in the repository.
  1400. This results in a different archive ID.
  1401. """)
  1402. subparser = subparsers.add_parser('rename', parents=[common_parser], add_help=False,
  1403. description=self.do_rename.__doc__,
  1404. epilog=rename_epilog,
  1405. formatter_class=argparse.RawDescriptionHelpFormatter,
  1406. help='rename archive')
  1407. subparser.set_defaults(func=self.do_rename)
  1408. subparser.add_argument('location', metavar='ARCHIVE',
  1409. type=location_validator(archive=True),
  1410. help='archive to rename')
  1411. subparser.add_argument('name', metavar='NEWNAME',
  1412. type=archivename_validator(),
  1413. help='the new archive name to use')
  1414. delete_epilog = textwrap.dedent("""
  1415. This command deletes an archive from the repository or the complete repository.
  1416. Disk space is reclaimed accordingly. If you delete the complete repository, the
  1417. local cache for it (if any) is also deleted.
  1418. """)
  1419. subparser = subparsers.add_parser('delete', parents=[common_parser], add_help=False,
  1420. description=self.do_delete.__doc__,
  1421. epilog=delete_epilog,
  1422. formatter_class=argparse.RawDescriptionHelpFormatter,
  1423. help='delete archive')
  1424. subparser.set_defaults(func=self.do_delete)
  1425. subparser.add_argument('-p', '--progress', dest='progress',
  1426. action='store_true', default=False,
  1427. help="""show progress display while deleting a single archive""")
  1428. subparser.add_argument('-s', '--stats', dest='stats',
  1429. action='store_true', default=False,
  1430. help='print statistics for the deleted archive')
  1431. subparser.add_argument('-c', '--cache-only', dest='cache_only',
  1432. action='store_true', default=False,
  1433. help='delete only the local cache for the given repository')
  1434. subparser.add_argument('--save-space', dest='save_space', action='store_true',
  1435. default=False,
  1436. help='work slower, but using less space')
  1437. subparser.add_argument('location', metavar='TARGET', nargs='?', default='',
  1438. type=location_validator(),
  1439. help='archive or repository to delete')
  1440. list_epilog = textwrap.dedent("""
  1441. This command lists the contents of a repository or an archive.
  1442. See the "borg help patterns" command for more help on exclude patterns.
  1443. The following keys are available for --format when listing files:
  1444. """) + ItemFormatter.keys_help()
  1445. subparser = subparsers.add_parser('list', parents=[common_parser], add_help=False,
  1446. description=self.do_list.__doc__,
  1447. epilog=list_epilog,
  1448. formatter_class=argparse.RawDescriptionHelpFormatter,
  1449. help='list archive or repository contents')
  1450. subparser.set_defaults(func=self.do_list)
  1451. subparser.add_argument('--short', dest='short',
  1452. action='store_true', default=False,
  1453. help='only print file/directory names, nothing else')
  1454. subparser.add_argument('--format', '--list-format', dest='format', type=str,
  1455. help="""specify format for file listing
  1456. (default: "{mode} {user:6} {group:6} {size:8d} {isomtime} {path}{extra}{NL}")""")
  1457. subparser.add_argument('-P', '--prefix', dest='prefix', type=str,
  1458. help='only consider archive names starting with this prefix')
  1459. subparser.add_argument('-e', '--exclude', dest='excludes',
  1460. type=parse_pattern, action='append',
  1461. metavar="PATTERN", help='exclude paths matching PATTERN')
  1462. subparser.add_argument('--exclude-from', dest='exclude_files',
  1463. type=argparse.FileType('r'), action='append',
  1464. metavar='EXCLUDEFILE', help='read exclude patterns from EXCLUDEFILE, one per line')
  1465. subparser.add_argument('location', metavar='REPOSITORY_OR_ARCHIVE', nargs='?', default='',
  1466. type=location_validator(),
  1467. help='repository/archive to list contents of')
  1468. subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
  1469. help='paths to list; patterns are supported')
  1470. mount_epilog = textwrap.dedent("""
  1471. This command mounts an archive as a FUSE filesystem. This can be useful for
  1472. browsing an archive or restoring individual files. Unless the ``--foreground``
  1473. option is given the command will run in the background until the filesystem
  1474. is ``umounted``.
  1475. The command ``borgfs`` provides a wrapper for ``borg mount``. This can also be
  1476. used in fstab entries:
  1477. ``/path/to/repo /mnt/point fuse.borgfs defaults,noauto 0 0``
  1478. To allow a regular user to use fstab entries, add the ``user`` option:
  1479. ``/path/to/repo /mnt/point fuse.borgfs defaults,noauto,user 0 0``
  1480. The BORG_MOUNT_DATA_CACHE_ENTRIES environment variable is meant for advanced users
  1481. to tweak the performance. It sets the number of cached data chunks; additional
  1482. memory usage can be up to ~8 MiB times this number. The default is the number
  1483. of CPU cores.
  1484. """)
  1485. subparser = subparsers.add_parser('mount', parents=[common_parser], add_help=False,
  1486. description=self.do_mount.__doc__,
  1487. epilog=mount_epilog,
  1488. formatter_class=argparse.RawDescriptionHelpFormatter,
  1489. help='mount repository')
  1490. subparser.set_defaults(func=self.do_mount)
  1491. subparser.add_argument('location', metavar='REPOSITORY_OR_ARCHIVE', type=location_validator(),
  1492. help='repository/archive to mount')
  1493. subparser.add_argument('mountpoint', metavar='MOUNTPOINT', type=str,
  1494. help='where to mount filesystem')
  1495. subparser.add_argument('-f', '--foreground', dest='foreground',
  1496. action='store_true', default=False,
  1497. help='stay in foreground, do not daemonize')
  1498. subparser.add_argument('-o', dest='options', type=str,
  1499. help='Extra mount options')
  1500. info_epilog = textwrap.dedent("""
  1501. This command displays some detailed information about the specified archive.
  1502. The "This archive" line refers exclusively to this archive:
  1503. "Deduplicated size" is the size of the unique chunks stored only for this
  1504. archive. Non-unique / common chunks show up under "All archives".
  1505. """)
  1506. subparser = subparsers.add_parser('info', parents=[common_parser], add_help=False,
  1507. description=self.do_info.__doc__,
  1508. epilog=info_epilog,
  1509. formatter_class=argparse.RawDescriptionHelpFormatter,
  1510. help='show archive information')
  1511. subparser.set_defaults(func=self.do_info)
  1512. subparser.add_argument('location', metavar='ARCHIVE',
  1513. type=location_validator(archive=True),
  1514. help='archive to display information about')
  1515. break_lock_epilog = textwrap.dedent("""
  1516. This command breaks the repository and cache locks.
  1517. Please use carefully and only while no borg process (on any machine) is
  1518. trying to access the Cache or the Repository.
  1519. """)
  1520. subparser = subparsers.add_parser('break-lock', parents=[common_parser], add_help=False,
  1521. description=self.do_break_lock.__doc__,
  1522. epilog=break_lock_epilog,
  1523. formatter_class=argparse.RawDescriptionHelpFormatter,
  1524. help='break repository and cache locks')
  1525. subparser.set_defaults(func=self.do_break_lock)
  1526. subparser.add_argument('location', metavar='REPOSITORY',
  1527. type=location_validator(archive=False),
  1528. help='repository for which to break the locks')
  1529. prune_epilog = textwrap.dedent("""
  1530. The prune command prunes a repository by deleting archives not matching
  1531. any of the specified retention options. This command is normally used by
  1532. automated backup scripts wanting to keep a certain number of historic backups.
  1533. Also, prune automatically removes checkpoint archives (incomplete archives left
  1534. behind by interrupted backup runs) except if the checkpoint is the latest
  1535. archive (and thus still needed). Checkpoint archives are not considered when
  1536. comparing archive counts against the retention limits (--keep-*).
  1537. If a prefix is set with -P, then only archives that start with the prefix are
  1538. considered for deletion and only those archives count towards the totals
  1539. specified by the rules.
  1540. Otherwise, *all* archives in the repository are candidates for deletion!
  1541. If you have multiple sequences of archives with different data sets (e.g.
  1542. from different machines) in one shared repository, use one prune call per
  1543. data set that matches only the respective archives using the -P option.
  1544. The "--keep-within" option takes an argument of the form "<int><char>",
  1545. where char is "H", "d", "w", "m", "y". For example, "--keep-within 2d" means
  1546. to keep all archives that were created within the past 48 hours.
  1547. "1m" is taken to mean "31d". The archives kept with this option do not
  1548. count towards the totals specified by any other options.
  1549. A good procedure is to thin out more and more the older your backups get.
  1550. As an example, "--keep-daily 7" means to keep the latest backup on each day,
  1551. up to 7 most recent days with backups (days without backups do not count).
  1552. The rules are applied from secondly to yearly, and backups selected by previous
  1553. rules do not count towards those of later rules. The time that each backup
  1554. completes is used for pruning purposes. Dates and times are interpreted in
  1555. the local timezone, and weeks go from Monday to Sunday. Specifying a
  1556. negative number of archives to keep means that there is no limit.
  1557. The "--keep-last N" option is doing the same as "--keep-secondly N" (and it will
  1558. keep the last N archives under the assumption that you do not create more than one
  1559. backup archive in the same second).
  1560. """)
  1561. subparser = subparsers.add_parser('prune', parents=[common_parser], add_help=False,
  1562. description=self.do_prune.__doc__,
  1563. epilog=prune_epilog,
  1564. formatter_class=argparse.RawDescriptionHelpFormatter,
  1565. help='prune archives')
  1566. subparser.set_defaults(func=self.do_prune)
  1567. subparser.add_argument('-n', '--dry-run', dest='dry_run',
  1568. default=False, action='store_true',
  1569. help='do not change repository')
  1570. subparser.add_argument('-s', '--stats', dest='stats',
  1571. action='store_true', default=False,
  1572. help='print statistics for the deleted archive')
  1573. subparser.add_argument('--list', dest='output_list',
  1574. action='store_true', default=False,
  1575. help='output verbose list of archives it keeps/prunes')
  1576. subparser.add_argument('--keep-within', dest='within', type=str, metavar='WITHIN',
  1577. help='keep all archives within this time interval')
  1578. subparser.add_argument('--keep-last', '--keep-secondly', dest='secondly', type=int, default=0,
  1579. help='number of secondly archives to keep')
  1580. subparser.add_argument('--keep-minutely', dest='minutely', type=int, default=0,
  1581. help='number of minutely archives to keep')
  1582. subparser.add_argument('-H', '--keep-hourly', dest='hourly', type=int, default=0,
  1583. help='number of hourly archives to keep')
  1584. subparser.add_argument('-d', '--keep-daily', dest='daily', type=int, default=0,
  1585. help='number of daily archives to keep')
  1586. subparser.add_argument('-w', '--keep-weekly', dest='weekly', type=int, default=0,
  1587. help='number of weekly archives to keep')
  1588. subparser.add_argument('-m', '--keep-monthly', dest='monthly', type=int, default=0,
  1589. help='number of monthly archives to keep')
  1590. subparser.add_argument('-y', '--keep-yearly', dest='yearly', type=int, default=0,
  1591. help='number of yearly archives to keep')
  1592. subparser.add_argument('-P', '--prefix', dest='prefix', type=str,
  1593. help='only consider archive names starting with this prefix')
  1594. subparser.add_argument('--save-space', dest='save_space', action='store_true',
  1595. default=False,
  1596. help='work slower, but using less space')
  1597. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  1598. type=location_validator(archive=False),
  1599. help='repository to prune')
  1600. upgrade_epilog = textwrap.dedent("""
  1601. Upgrade an existing Borg repository.
  1602. This currently supports converting an Attic repository to Borg and also
  1603. helps with converting Borg 0.xx to 1.0.
  1604. Currently, only LOCAL repositories can be upgraded (issue #465).
  1605. It will change the magic strings in the repository's segments
  1606. to match the new Borg magic strings. The keyfiles found in
  1607. $ATTIC_KEYS_DIR or ~/.attic/keys/ will also be converted and
  1608. copied to $BORG_KEYS_DIR or ~/.config/borg/keys.
  1609. The cache files are converted, from $ATTIC_CACHE_DIR or
  1610. ~/.cache/attic to $BORG_CACHE_DIR or ~/.cache/borg, but the
  1611. cache layout between Borg and Attic changed, so it is possible
  1612. the first backup after the conversion takes longer than expected
  1613. due to the cache resync.
  1614. Upgrade should be able to resume if interrupted, although it
  1615. will still iterate over all segments. If you want to start
  1616. from scratch, use `borg delete` over the copied repository to
  1617. make sure the cache files are also removed:
  1618. borg delete borg
  1619. Unless ``--inplace`` is specified, the upgrade process first
  1620. creates a backup copy of the repository, in
  1621. REPOSITORY.upgrade-DATETIME, using hardlinks. This takes
  1622. longer than in place upgrades, but is much safer and gives
  1623. progress information (as opposed to ``cp -al``). Once you are
  1624. satisfied with the conversion, you can safely destroy the
  1625. backup copy.
  1626. WARNING: Running the upgrade in place will make the current
  1627. copy unusable with older version, with no way of going back
  1628. to previous versions. This can PERMANENTLY DAMAGE YOUR
  1629. REPOSITORY! Attic CAN NOT READ BORG REPOSITORIES, as the
  1630. magic strings have changed. You have been warned.""")
  1631. subparser = subparsers.add_parser('upgrade', parents=[common_parser], add_help=False,
  1632. description=self.do_upgrade.__doc__,
  1633. epilog=upgrade_epilog,
  1634. formatter_class=argparse.RawDescriptionHelpFormatter,
  1635. help='upgrade repository format')
  1636. subparser.set_defaults(func=self.do_upgrade)
  1637. subparser.add_argument('-p', '--progress', dest='progress',
  1638. action='store_true', default=False,
  1639. help="""show progress display while upgrading the repository""")
  1640. subparser.add_argument('-n', '--dry-run', dest='dry_run',
  1641. default=False, action='store_true',
  1642. help='do not change repository')
  1643. subparser.add_argument('-i', '--inplace', dest='inplace',
  1644. default=False, action='store_true',
  1645. help="""rewrite repository in place, with no chance of going back to older
  1646. versions of the repository.""")
  1647. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  1648. type=location_validator(archive=False),
  1649. help='path to the repository to be upgraded')
  1650. recreate_epilog = textwrap.dedent("""
  1651. Recreate the contents of existing archives.
  1652. --exclude, --exclude-from and PATH have the exact same semantics
  1653. as in "borg create". If PATHs are specified the resulting archive
  1654. will only contain files from these PATHs.
  1655. --compression: all chunks seen will be stored using the given method.
  1656. Due to how Borg stores compressed size information this might display
  1657. incorrect information for archives that were not recreated at the same time.
  1658. There is no risk of data loss by this.
  1659. --chunker-params will re-chunk all files in the archive, this can be
  1660. used to have upgraded Borg 0.xx or Attic archives deduplicate with
  1661. Borg 1.x archives.
  1662. borg recreate is signal safe. Send either SIGINT (Ctrl-C on most terminals) or
  1663. SIGTERM to request termination.
  1664. Use the *exact same* command line to resume the operation later - changing excludes
  1665. or paths will lead to inconsistencies (changed excludes will only apply to newly
  1666. processed files/dirs). Changing compression leads to incorrect size information
  1667. (which does not cause any data loss, but can be misleading).
  1668. Changing chunker params between invocations might lead to data loss.
  1669. USE WITH CAUTION.
  1670. Depending on the PATHs and patterns given, recreate can be used to permanently
  1671. delete files from archives.
  1672. When in doubt, use "--dry-run --verbose --list" to see how patterns/PATHS are
  1673. interpreted.
  1674. The archive being recreated is only removed after the operation completes. The
  1675. archive that is built during the operation exists at the same time at
  1676. "<ARCHIVE>.recreate". The new archive will have a different archive ID.
  1677. When rechunking space usage can be substantial, expect at least the entire
  1678. deduplicated size of the archives using the previous chunker params.
  1679. When recompressing approximately 1 % of the repository size or 512 MB
  1680. (whichever is greater) of additional space is used.
  1681. """)
  1682. subparser = subparsers.add_parser('recreate', parents=[common_parser], add_help=False,
  1683. description=self.do_recreate.__doc__,
  1684. epilog=recreate_epilog,
  1685. formatter_class=argparse.RawDescriptionHelpFormatter,
  1686. help=self.do_recreate.__doc__)
  1687. subparser.set_defaults(func=self.do_recreate)
  1688. subparser.add_argument('--list', dest='output_list',
  1689. action='store_true', default=False,
  1690. help='output verbose list of items (files, dirs, ...)')
  1691. subparser.add_argument('--filter', dest='output_filter', metavar='STATUSCHARS',
  1692. help='only display items with the given status characters')
  1693. subparser.add_argument('-p', '--progress', dest='progress',
  1694. action='store_true', default=False,
  1695. help='show progress display while recreating archives')
  1696. subparser.add_argument('-n', '--dry-run', dest='dry_run',
  1697. action='store_true', default=False,
  1698. help='do not change anything')
  1699. subparser.add_argument('-s', '--stats', dest='stats',
  1700. action='store_true', default=False,
  1701. help='print statistics at end')
  1702. exclude_group = subparser.add_argument_group('Exclusion options')
  1703. exclude_group.add_argument('-e', '--exclude', dest='excludes',
  1704. type=parse_pattern, action='append',
  1705. metavar="PATTERN", help='exclude paths matching PATTERN')
  1706. exclude_group.add_argument('--exclude-from', dest='exclude_files',
  1707. type=argparse.FileType('r'), action='append',
  1708. metavar='EXCLUDEFILE', help='read exclude patterns from EXCLUDEFILE, one per line')
  1709. exclude_group.add_argument('--exclude-caches', dest='exclude_caches',
  1710. action='store_true', default=False,
  1711. help='exclude directories that contain a CACHEDIR.TAG file ('
  1712. 'http://www.brynosaurus.com/cachedir/spec.html)')
  1713. exclude_group.add_argument('--exclude-if-present', dest='exclude_if_present',
  1714. metavar='FILENAME', action='append', type=str,
  1715. help='exclude directories that contain the specified file')
  1716. exclude_group.add_argument('--keep-tag-files', dest='keep_tag_files',
  1717. action='store_true', default=False,
  1718. help='keep tag files of excluded caches/directories')
  1719. archive_group = subparser.add_argument_group('Archive options')
  1720. archive_group.add_argument('--comment', dest='comment', metavar='COMMENT', default=None,
  1721. help='add a comment text to the archive')
  1722. archive_group.add_argument('--timestamp', dest='timestamp',
  1723. type=timestamp, default=None,
  1724. metavar='yyyy-mm-ddThh:mm:ss',
  1725. help='manually specify the archive creation date/time (UTC). '
  1726. 'alternatively, give a reference file/directory.')
  1727. archive_group.add_argument('-C', '--compression', dest='compression',
  1728. type=CompressionSpec, default=None, metavar='COMPRESSION',
  1729. help='select compression algorithm (and level):\n'
  1730. 'none == no compression (default),\n'
  1731. 'auto,C[,L] == built-in heuristic decides between none or C[,L] - with C[,L]\n'
  1732. ' being any valid compression algorithm (and optional level),\n'
  1733. 'lz4 == lz4,\n'
  1734. 'zlib == zlib (default level 6),\n'
  1735. 'zlib,0 .. zlib,9 == zlib (with level 0..9),\n'
  1736. 'lzma == lzma (default level 6),\n'
  1737. 'lzma,0 .. lzma,9 == lzma (with level 0..9).')
  1738. archive_group.add_argument('--compression-from', dest='compression_files',
  1739. type=argparse.FileType('r'), action='append',
  1740. metavar='COMPRESSIONCONFIG', help='read compression patterns from COMPRESSIONCONFIG, one per line')
  1741. archive_group.add_argument('--chunker-params', dest='chunker_params',
  1742. type=ChunkerParams, default=None,
  1743. metavar='CHUNK_MIN_EXP,CHUNK_MAX_EXP,HASH_MASK_BITS,HASH_WINDOW_SIZE',
  1744. help='specify the chunker parameters (or "default").')
  1745. subparser.add_argument('location', metavar='REPOSITORY_OR_ARCHIVE', nargs='?', default='',
  1746. type=location_validator(),
  1747. help='repository/archive to recreate')
  1748. subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
  1749. help='paths to recreate; patterns are supported')
  1750. with_lock_epilog = textwrap.dedent("""
  1751. This command runs a user-specified command while the repository lock is held.
  1752. It will first try to acquire the lock (make sure that no other operation is
  1753. running in the repo), then execute the given command as a subprocess and wait
  1754. for its termination, release the lock and return the user command's return
  1755. code as borg's return code.
  1756. Note: if you copy a repository with the lock held, the lock will be present in
  1757. the copy, obviously. Thus, before using borg on the copy, you need to
  1758. use "borg break-lock" on it.
  1759. """)
  1760. subparser = subparsers.add_parser('with-lock', parents=[common_parser], add_help=False,
  1761. description=self.do_with_lock.__doc__,
  1762. epilog=with_lock_epilog,
  1763. formatter_class=argparse.RawDescriptionHelpFormatter,
  1764. help='run user command with lock held')
  1765. subparser.set_defaults(func=self.do_with_lock)
  1766. subparser.add_argument('location', metavar='REPOSITORY',
  1767. type=location_validator(archive=False),
  1768. help='repository to lock')
  1769. subparser.add_argument('command', metavar='COMMAND',
  1770. help='command to run')
  1771. subparser.add_argument('args', metavar='ARGS', nargs=argparse.REMAINDER,
  1772. help='command arguments')
  1773. subparser = subparsers.add_parser('help', parents=[common_parser], add_help=False,
  1774. description='Extra help')
  1775. subparser.add_argument('--epilog-only', dest='epilog_only',
  1776. action='store_true', default=False)
  1777. subparser.add_argument('--usage-only', dest='usage_only',
  1778. action='store_true', default=False)
  1779. subparser.set_defaults(func=functools.partial(self.do_help, parser, subparsers.choices))
  1780. subparser.add_argument('topic', metavar='TOPIC', type=str, nargs='?',
  1781. help='additional help on TOPIC')
  1782. debug_dump_archive_items_epilog = textwrap.dedent("""
  1783. This command dumps raw (but decrypted and decompressed) archive items (only metadata) to files.
  1784. """)
  1785. subparser = subparsers.add_parser('debug-dump-archive-items', parents=[common_parser], add_help=False,
  1786. description=self.do_debug_dump_archive_items.__doc__,
  1787. epilog=debug_dump_archive_items_epilog,
  1788. formatter_class=argparse.RawDescriptionHelpFormatter,
  1789. help='dump archive items (metadata) (debug)')
  1790. subparser.set_defaults(func=self.do_debug_dump_archive_items)
  1791. subparser.add_argument('location', metavar='ARCHIVE',
  1792. type=location_validator(archive=True),
  1793. help='archive to dump')
  1794. debug_get_obj_epilog = textwrap.dedent("""
  1795. This command gets an object from the repository.
  1796. """)
  1797. subparser = subparsers.add_parser('debug-get-obj', parents=[common_parser], add_help=False,
  1798. description=self.do_debug_get_obj.__doc__,
  1799. epilog=debug_get_obj_epilog,
  1800. formatter_class=argparse.RawDescriptionHelpFormatter,
  1801. help='get object from repository (debug)')
  1802. subparser.set_defaults(func=self.do_debug_get_obj)
  1803. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  1804. type=location_validator(archive=False),
  1805. help='repository to use')
  1806. subparser.add_argument('id', metavar='ID', type=str,
  1807. help='hex object ID to get from the repo')
  1808. subparser.add_argument('path', metavar='PATH', type=str,
  1809. help='file to write object data into')
  1810. debug_put_obj_epilog = textwrap.dedent("""
  1811. This command puts objects into the repository.
  1812. """)
  1813. subparser = subparsers.add_parser('debug-put-obj', parents=[common_parser], add_help=False,
  1814. description=self.do_debug_put_obj.__doc__,
  1815. epilog=debug_put_obj_epilog,
  1816. formatter_class=argparse.RawDescriptionHelpFormatter,
  1817. help='put object to repository (debug)')
  1818. subparser.set_defaults(func=self.do_debug_put_obj)
  1819. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  1820. type=location_validator(archive=False),
  1821. help='repository to use')
  1822. subparser.add_argument('paths', metavar='PATH', nargs='+', type=str,
  1823. help='file(s) to read and create object(s) from')
  1824. debug_delete_obj_epilog = textwrap.dedent("""
  1825. This command deletes objects from the repository.
  1826. """)
  1827. subparser = subparsers.add_parser('debug-delete-obj', parents=[common_parser], add_help=False,
  1828. description=self.do_debug_delete_obj.__doc__,
  1829. epilog=debug_delete_obj_epilog,
  1830. formatter_class=argparse.RawDescriptionHelpFormatter,
  1831. help='delete object from repository (debug)')
  1832. subparser.set_defaults(func=self.do_debug_delete_obj)
  1833. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  1834. type=location_validator(archive=False),
  1835. help='repository to use')
  1836. subparser.add_argument('ids', metavar='IDs', nargs='+', type=str,
  1837. help='hex object ID(s) to delete from the repo')
  1838. return parser
  1839. def get_args(self, argv, cmd):
  1840. """usually, just returns argv, except if we deal with a ssh forced command for borg serve."""
  1841. result = self.parse_args(argv[1:])
  1842. if cmd is not None and result.func == self.do_serve:
  1843. forced_result = result
  1844. argv = shlex.split(cmd)
  1845. result = self.parse_args(argv[1:])
  1846. if result.func != forced_result.func:
  1847. # someone is trying to execute a different borg subcommand, don't do that!
  1848. return forced_result
  1849. # the only thing we take from the forced "borg serve" ssh command is --restrict-to-path
  1850. result.restrict_to_paths = forced_result.restrict_to_paths
  1851. return result
  1852. def parse_args(self, args=None):
  1853. # We can't use argparse for "serve" since we don't want it to show up in "Available commands"
  1854. if args:
  1855. args = self.preprocess_args(args)
  1856. args = self.parser.parse_args(args or ['-h'])
  1857. update_excludes(args)
  1858. return args
  1859. def prerun_checks(self, logger):
  1860. check_extension_modules()
  1861. selftest(logger)
  1862. def _setup_implied_logging(self, args):
  1863. """ turn on INFO level logging for args that imply that they will produce output """
  1864. # map of option name to name of logger for that option
  1865. option_logger = {
  1866. 'output_list': 'borg.output.list',
  1867. 'show_version': 'borg.output.show-version',
  1868. 'show_rc': 'borg.output.show-rc',
  1869. 'stats': 'borg.output.stats',
  1870. 'progress': 'borg.output.progress',
  1871. }
  1872. for option, logger_name in option_logger.items():
  1873. if args.get(option, False):
  1874. logging.getLogger(logger_name).setLevel('INFO')
  1875. def run(self, args):
  1876. os.umask(args.umask) # early, before opening files
  1877. self.lock_wait = args.lock_wait
  1878. setup_logging(level=args.log_level, is_serve=args.func == self.do_serve) # do not use loggers before this!
  1879. self._setup_implied_logging(vars(args))
  1880. if args.show_version:
  1881. logging.getLogger('borg.output.show-version').info('borgbackup version %s' % __version__)
  1882. self.prerun_checks(logger)
  1883. if is_slow_msgpack():
  1884. logger.warning("Using a pure-python msgpack! This will result in lower performance.")
  1885. return args.func(args)
  1886. def sig_info_handler(signum, stack): # pragma: no cover
  1887. """search the stack for infos about the currently processed file and print them"""
  1888. for frame in inspect.getouterframes(stack):
  1889. func, loc = frame[3], frame[0].f_locals
  1890. if func in ('process_file', '_process', ): # create op
  1891. path = loc['path']
  1892. try:
  1893. pos = loc['fd'].tell()
  1894. total = loc['st'].st_size
  1895. except Exception:
  1896. pos, total = 0, 0
  1897. logger.info("{0} {1}/{2}".format(path, format_file_size(pos), format_file_size(total)))
  1898. break
  1899. if func in ('extract_item', ): # extract op
  1900. path = loc['item'][b'path']
  1901. try:
  1902. pos = loc['fd'].tell()
  1903. except Exception:
  1904. pos = 0
  1905. logger.info("{0} {1}/???".format(path, format_file_size(pos)))
  1906. break
  1907. class SIGTERMReceived(BaseException):
  1908. pass
  1909. def sig_term_handler(signum, stack):
  1910. raise SIGTERMReceived
  1911. def setup_signal_handlers(): # pragma: no cover
  1912. sigs = []
  1913. if hasattr(signal, 'SIGUSR1'):
  1914. sigs.append(signal.SIGUSR1) # kill -USR1 pid
  1915. if hasattr(signal, 'SIGINFO'):
  1916. sigs.append(signal.SIGINFO) # kill -INFO pid (or ctrl-t)
  1917. for sig in sigs:
  1918. signal.signal(sig, sig_info_handler)
  1919. signal.signal(signal.SIGTERM, sig_term_handler)
  1920. def main(): # pragma: no cover
  1921. # provide 'borg mount' behaviour when the main script/executable is named borgfs
  1922. if os.path.basename(sys.argv[0]) == "borgfs":
  1923. sys.argv.insert(1, "mount")
  1924. # Make sure stdout and stderr have errors='replace') to avoid unicode
  1925. # issues when print()-ing unicode file names
  1926. sys.stdout = io.TextIOWrapper(sys.stdout.buffer, sys.stdout.encoding, 'replace', line_buffering=True)
  1927. sys.stderr = io.TextIOWrapper(sys.stderr.buffer, sys.stderr.encoding, 'replace', line_buffering=True)
  1928. setup_signal_handlers()
  1929. archiver = Archiver()
  1930. msg = None
  1931. args = archiver.get_args(sys.argv, os.environ.get('SSH_ORIGINAL_COMMAND'))
  1932. try:
  1933. exit_code = archiver.run(args)
  1934. except Error as e:
  1935. msg = e.get_message()
  1936. if e.traceback:
  1937. msg += "\n%s\n%s" % (traceback.format_exc(), sysinfo())
  1938. exit_code = e.exit_code
  1939. except RemoteRepository.RPCError as e:
  1940. msg = '%s\n%s' % (str(e), sysinfo())
  1941. exit_code = EXIT_ERROR
  1942. except Exception:
  1943. msg = 'Local Exception.\n%s\n%s' % (traceback.format_exc(), sysinfo())
  1944. exit_code = EXIT_ERROR
  1945. except KeyboardInterrupt:
  1946. msg = 'Keyboard interrupt.\n%s\n%s' % (traceback.format_exc(), sysinfo())
  1947. exit_code = EXIT_ERROR
  1948. except SIGTERMReceived:
  1949. msg = 'Received SIGTERM.'
  1950. exit_code = EXIT_ERROR
  1951. if msg:
  1952. logger.error(msg)
  1953. if args.show_rc:
  1954. rc_logger = logging.getLogger('borg.output.show-rc')
  1955. exit_msg = 'terminating with %s status, rc %d'
  1956. if exit_code == EXIT_SUCCESS:
  1957. rc_logger.info(exit_msg % ('success', exit_code))
  1958. elif exit_code == EXIT_WARNING:
  1959. rc_logger.warning(exit_msg % ('warning', exit_code))
  1960. elif exit_code == EXIT_ERROR:
  1961. rc_logger.error(exit_msg % ('error', exit_code))
  1962. else:
  1963. rc_logger.error(exit_msg % ('abnormal', exit_code or 666))
  1964. sys.exit(exit_code)
  1965. if __name__ == '__main__':
  1966. main()