archiver.py 234 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482
  1. import argparse
  2. import collections
  3. import configparser
  4. import faulthandler
  5. import functools
  6. import hashlib
  7. import inspect
  8. import itertools
  9. import json
  10. import logging
  11. import os
  12. import re
  13. import shlex
  14. import shutil
  15. import signal
  16. import stat
  17. import subprocess
  18. import sys
  19. import tarfile
  20. import textwrap
  21. import time
  22. import traceback
  23. from binascii import unhexlify
  24. from contextlib import contextmanager
  25. from datetime import datetime, timedelta
  26. from itertools import zip_longest
  27. from .logger import create_logger, setup_logging
  28. logger = create_logger()
  29. import msgpack
  30. import borg
  31. from . import __version__
  32. from . import helpers
  33. from . import shellpattern
  34. from .algorithms.checksums import crc32
  35. from .archive import Archive, ArchiveChecker, ArchiveRecreater, Statistics, is_special
  36. from .archive import BackupError, BackupOSError, backup_io
  37. from .cache import Cache, assert_secure, SecurityManager
  38. from .constants import * # NOQA
  39. from .compress import CompressionSpec
  40. from .crypto.key import key_creator, key_argument_names, tam_required_file, tam_required, RepoKey, PassphraseKey
  41. from .crypto.keymanager import KeyManager
  42. from .helpers import EXIT_SUCCESS, EXIT_WARNING, EXIT_ERROR
  43. from .helpers import Error, NoManifestError, set_ec
  44. from .helpers import positive_int_validator, location_validator, archivename_validator, ChunkerParams, Location
  45. from .helpers import PrefixSpec, SortBySpec, HUMAN_SORT_KEYS, FilesCacheMode
  46. from .helpers import BaseFormatter, ItemFormatter, ArchiveFormatter
  47. from .helpers import format_timedelta, format_file_size, parse_file_size, format_archive
  48. from .helpers import safe_encode, remove_surrogates, bin_to_hex, prepare_dump_dict
  49. from .helpers import interval, prune_within, prune_split
  50. from .helpers import timestamp
  51. from .helpers import get_cache_dir
  52. from .helpers import Manifest
  53. from .helpers import hardlinkable
  54. from .helpers import StableDict
  55. from .helpers import check_python, check_extension_modules
  56. from .helpers import dir_is_tagged, is_slow_msgpack, yes, sysinfo
  57. from .helpers import log_multi
  58. from .helpers import signal_handler, raising_signal_handler, SigHup, SigTerm
  59. from .helpers import ErrorIgnoringTextIOWrapper
  60. from .helpers import ProgressIndicatorPercent
  61. from .helpers import basic_json_data, json_print
  62. from .helpers import replace_placeholders
  63. from .helpers import ChunkIteratorFileWrapper
  64. from .helpers import popen_with_error_handling, prepare_subprocess_env
  65. from .helpers import dash_open
  66. from .helpers import umount
  67. from .nanorst import rst_to_terminal
  68. from .patterns import ArgparsePatternAction, ArgparseExcludeFileAction, ArgparsePatternFileAction, parse_exclude_pattern
  69. from .patterns import PatternMatcher
  70. from .item import Item
  71. from .platform import get_flags, get_process_id, SyncFile
  72. from .remote import RepositoryServer, RemoteRepository, cache_if_remote
  73. from .repository import Repository, LIST_SCAN_LIMIT, TAG_PUT, TAG_DELETE, TAG_COMMIT
  74. from .selftest import selftest
  75. from .upgrader import AtticRepositoryUpgrader, BorgRepositoryUpgrader
  76. STATS_HEADER = " Original size Compressed size Deduplicated size"
  77. def argument(args, str_or_bool):
  78. """If bool is passed, return it. If str is passed, retrieve named attribute from args."""
  79. if isinstance(str_or_bool, str):
  80. return getattr(args, str_or_bool)
  81. if isinstance(str_or_bool, (list, tuple)):
  82. return any(getattr(args, item) for item in str_or_bool)
  83. return str_or_bool
  84. def with_repository(fake=False, invert_fake=False, create=False, lock=True,
  85. exclusive=False, manifest=True, cache=False, secure=True,
  86. compatibility=None):
  87. """
  88. Method decorator for subcommand-handling methods: do_XYZ(self, args, repository, …)
  89. If a parameter (where allowed) is a str the attribute named of args is used instead.
  90. :param fake: (str or bool) use None instead of repository, don't do anything else
  91. :param create: create repository
  92. :param lock: lock repository
  93. :param exclusive: (str or bool) lock repository exclusively (for writing)
  94. :param manifest: load manifest and key, pass them as keyword arguments
  95. :param cache: open cache, pass it as keyword argument (implies manifest)
  96. :param secure: do assert_secure after loading manifest
  97. :param compatibility: mandatory if not create and (manifest or cache), specifies mandatory feature categories to check
  98. """
  99. if not create and (manifest or cache):
  100. if compatibility is None:
  101. raise AssertionError("with_repository decorator used without compatibility argument")
  102. if type(compatibility) is not tuple:
  103. raise AssertionError("with_repository decorator compatibility argument must be of type tuple")
  104. else:
  105. if compatibility is not None:
  106. raise AssertionError("with_repository called with compatibility argument but would not check" + repr(compatibility))
  107. if create:
  108. compatibility = Manifest.NO_OPERATION_CHECK
  109. def decorator(method):
  110. @functools.wraps(method)
  111. def wrapper(self, args, **kwargs):
  112. location = args.location # note: 'location' must be always present in args
  113. append_only = getattr(args, 'append_only', False)
  114. storage_quota = getattr(args, 'storage_quota', None)
  115. if argument(args, fake) ^ invert_fake:
  116. return method(self, args, repository=None, **kwargs)
  117. elif location.proto == 'ssh':
  118. repository = RemoteRepository(location, create=create, exclusive=argument(args, exclusive),
  119. lock_wait=self.lock_wait, lock=lock, append_only=append_only, args=args)
  120. else:
  121. repository = Repository(location.path, create=create, exclusive=argument(args, exclusive),
  122. lock_wait=self.lock_wait, lock=lock, append_only=append_only,
  123. storage_quota=storage_quota)
  124. with repository:
  125. if manifest or cache:
  126. kwargs['manifest'], kwargs['key'] = Manifest.load(repository, compatibility)
  127. if 'compression' in args:
  128. kwargs['key'].compressor = args.compression.compressor
  129. if secure:
  130. assert_secure(repository, kwargs['manifest'], self.lock_wait)
  131. if cache:
  132. with Cache(repository, kwargs['key'], kwargs['manifest'],
  133. do_files=getattr(args, 'cache_files', False),
  134. ignore_inode=getattr(args, 'ignore_inode', False),
  135. progress=getattr(args, 'progress', False), lock_wait=self.lock_wait,
  136. cache_mode=getattr(args, 'files_cache_mode', DEFAULT_FILES_CACHE_MODE)) as cache_:
  137. return method(self, args, repository=repository, cache=cache_, **kwargs)
  138. else:
  139. return method(self, args, repository=repository, **kwargs)
  140. return wrapper
  141. return decorator
  142. def with_archive(method):
  143. @functools.wraps(method)
  144. def wrapper(self, args, repository, key, manifest, **kwargs):
  145. archive = Archive(repository, key, manifest, args.location.archive,
  146. numeric_owner=getattr(args, 'numeric_owner', False),
  147. nobsdflags=getattr(args, 'nobsdflags', False),
  148. cache=kwargs.get('cache'),
  149. consider_part_files=args.consider_part_files, log_json=args.log_json)
  150. return method(self, args, repository=repository, manifest=manifest, key=key, archive=archive, **kwargs)
  151. return wrapper
  152. def parse_storage_quota(storage_quota):
  153. parsed = parse_file_size(storage_quota)
  154. if parsed < parse_file_size('10M'):
  155. raise argparse.ArgumentTypeError('quota is too small (%s). At least 10M are required.' % storage_quota)
  156. return parsed
  157. def get_func(args):
  158. # This works around http://bugs.python.org/issue9351
  159. # func is used at the leaf parsers of the argparse parser tree,
  160. # fallback_func at next level towards the root,
  161. # fallback2_func at the 2nd next level (which is root in our case).
  162. for name in 'func', 'fallback_func', 'fallback2_func':
  163. func = getattr(args, name, None)
  164. if func is not None:
  165. return func
  166. raise Exception('expected func attributes not found')
  167. class Archiver:
  168. def __init__(self, lock_wait=None, prog=None):
  169. self.exit_code = EXIT_SUCCESS
  170. self.lock_wait = lock_wait
  171. self.prog = prog
  172. def print_error(self, msg, *args):
  173. msg = args and msg % args or msg
  174. self.exit_code = EXIT_ERROR
  175. logger.error(msg)
  176. def print_warning(self, msg, *args):
  177. msg = args and msg % args or msg
  178. self.exit_code = EXIT_WARNING # we do not terminate here, so it is a warning
  179. logger.warning(msg)
  180. def print_file_status(self, status, path):
  181. if self.output_list and (self.output_filter is None or status in self.output_filter):
  182. if self.log_json:
  183. print(json.dumps({
  184. 'type': 'file_status',
  185. 'status': status,
  186. 'path': remove_surrogates(path),
  187. }), file=sys.stderr)
  188. else:
  189. logging.getLogger('borg.output.list').info("%1s %s", status, remove_surrogates(path))
  190. @staticmethod
  191. def compare_chunk_contents(chunks1, chunks2):
  192. """Compare two chunk iterators (like returned by :meth:`.DownloadPipeline.fetch_many`)"""
  193. end = object()
  194. alen = ai = 0
  195. blen = bi = 0
  196. while True:
  197. if not alen - ai:
  198. a = next(chunks1, end)
  199. if a is end:
  200. return not blen - bi and next(chunks2, end) is end
  201. a = memoryview(a)
  202. alen = len(a)
  203. ai = 0
  204. if not blen - bi:
  205. b = next(chunks2, end)
  206. if b is end:
  207. return not alen - ai and next(chunks1, end) is end
  208. b = memoryview(b)
  209. blen = len(b)
  210. bi = 0
  211. slicelen = min(alen - ai, blen - bi)
  212. if a[ai:ai + slicelen] != b[bi:bi + slicelen]:
  213. return False
  214. ai += slicelen
  215. bi += slicelen
  216. @staticmethod
  217. def build_matcher(inclexcl_patterns, include_paths):
  218. matcher = PatternMatcher()
  219. matcher.add_inclexcl(inclexcl_patterns)
  220. matcher.add_includepaths(include_paths)
  221. return matcher
  222. def do_serve(self, args):
  223. """Start in server mode. This command is usually not used manually."""
  224. RepositoryServer(
  225. restrict_to_paths=args.restrict_to_paths,
  226. restrict_to_repositories=args.restrict_to_repositories,
  227. append_only=args.append_only,
  228. storage_quota=args.storage_quota,
  229. ).serve()
  230. return EXIT_SUCCESS
  231. @with_repository(create=True, exclusive=True, manifest=False)
  232. def do_init(self, args, repository):
  233. """Initialize an empty repository"""
  234. path = args.location.canonical_path()
  235. logger.info('Initializing repository at "%s"' % path)
  236. try:
  237. key = key_creator(repository, args)
  238. except (EOFError, KeyboardInterrupt):
  239. repository.destroy()
  240. return EXIT_WARNING
  241. manifest = Manifest(key, repository)
  242. manifest.key = key
  243. manifest.write()
  244. repository.commit()
  245. with Cache(repository, key, manifest, warn_if_unencrypted=False):
  246. pass
  247. if key.tam_required:
  248. tam_file = tam_required_file(repository)
  249. open(tam_file, 'w').close()
  250. logger.warning(
  251. '\n'
  252. 'By default repositories initialized with this version will produce security\n'
  253. 'errors if written to with an older version (up to and including Borg 1.0.8).\n'
  254. '\n'
  255. 'If you want to use these older versions, you can disable the check by running:\n'
  256. 'borg upgrade --disable-tam %s\n'
  257. '\n'
  258. 'See https://borgbackup.readthedocs.io/en/stable/changes.html#pre-1-0-9-manifest-spoofing-vulnerability '
  259. 'for details about the security implications.', shlex.quote(path))
  260. return self.exit_code
  261. @with_repository(exclusive=True, manifest=False)
  262. def do_check(self, args, repository):
  263. """Check repository consistency"""
  264. if args.repair:
  265. msg = ("'check --repair' is an experimental feature that might result in data loss." +
  266. "\n" +
  267. "Type 'YES' if you understand this and want to continue: ")
  268. if not yes(msg, false_msg="Aborting.", invalid_msg="Invalid answer, aborting.",
  269. truish=('YES', ), retry=False,
  270. env_var_override='BORG_CHECK_I_KNOW_WHAT_I_AM_DOING'):
  271. return EXIT_ERROR
  272. if args.repo_only and any((args.verify_data, args.first, args.last, args.prefix)):
  273. self.print_error("--repository-only contradicts --first, --last, --prefix and --verify-data arguments.")
  274. return EXIT_ERROR
  275. if not args.archives_only:
  276. if not repository.check(repair=args.repair, save_space=args.save_space):
  277. return EXIT_WARNING
  278. if args.prefix:
  279. args.glob_archives = args.prefix + '*'
  280. if not args.repo_only and not ArchiveChecker().check(
  281. repository, repair=args.repair, archive=args.location.archive,
  282. first=args.first, last=args.last, sort_by=args.sort_by or 'ts', glob=args.glob_archives,
  283. verify_data=args.verify_data, save_space=args.save_space):
  284. return EXIT_WARNING
  285. return EXIT_SUCCESS
  286. @with_repository(compatibility=(Manifest.Operation.CHECK,))
  287. def do_change_passphrase(self, args, repository, manifest, key):
  288. """Change repository key file passphrase"""
  289. if not hasattr(key, 'change_passphrase'):
  290. print('This repository is not encrypted, cannot change the passphrase.')
  291. return EXIT_ERROR
  292. key.change_passphrase()
  293. logger.info('Key updated')
  294. if hasattr(key, 'find_key'):
  295. # print key location to make backing it up easier
  296. logger.info('Key location: %s', key.find_key())
  297. return EXIT_SUCCESS
  298. def do_change_passphrase_deprecated(self, args):
  299. logger.warning('"borg change-passphrase" is deprecated and will be removed in Borg 1.2.\n'
  300. 'Use "borg key change-passphrase" instead.')
  301. return self.do_change_passphrase(args)
  302. @with_repository(lock=False, exclusive=False, manifest=False, cache=False)
  303. def do_key_export(self, args, repository):
  304. """Export the repository key for backup"""
  305. manager = KeyManager(repository)
  306. manager.load_keyblob()
  307. if args.paper:
  308. manager.export_paperkey(args.path)
  309. else:
  310. if not args.path:
  311. self.print_error("output file to export key to expected")
  312. return EXIT_ERROR
  313. if args.qr:
  314. manager.export_qr(args.path)
  315. else:
  316. manager.export(args.path)
  317. return EXIT_SUCCESS
  318. @with_repository(lock=False, exclusive=False, manifest=False, cache=False)
  319. def do_key_import(self, args, repository):
  320. """Import the repository key from backup"""
  321. manager = KeyManager(repository)
  322. if args.paper:
  323. if args.path:
  324. self.print_error("with --paper import from file is not supported")
  325. return EXIT_ERROR
  326. manager.import_paperkey(args)
  327. else:
  328. if not args.path:
  329. self.print_error("input file to import key from expected")
  330. return EXIT_ERROR
  331. if args.path != '-' and not os.path.exists(args.path):
  332. self.print_error("input file does not exist: " + args.path)
  333. return EXIT_ERROR
  334. manager.import_keyfile(args)
  335. return EXIT_SUCCESS
  336. @with_repository(manifest=False)
  337. def do_migrate_to_repokey(self, args, repository):
  338. """Migrate passphrase -> repokey"""
  339. manifest_data = repository.get(Manifest.MANIFEST_ID)
  340. key_old = PassphraseKey.detect(repository, manifest_data)
  341. key_new = RepoKey(repository)
  342. key_new.target = repository
  343. key_new.repository_id = repository.id
  344. key_new.enc_key = key_old.enc_key
  345. key_new.enc_hmac_key = key_old.enc_hmac_key
  346. key_new.id_key = key_old.id_key
  347. key_new.chunk_seed = key_old.chunk_seed
  348. key_new.change_passphrase() # option to change key protection passphrase, save
  349. logger.info('Key updated')
  350. return EXIT_SUCCESS
  351. def do_benchmark_crud(self, args):
  352. """Benchmark Create, Read, Update, Delete for archives."""
  353. def measurement_run(repo, path):
  354. archive = repo + '::borg-benchmark-crud'
  355. compression = '--compression=none'
  356. # measure create perf (without files cache to always have it chunking)
  357. t_start = time.monotonic()
  358. rc = self.do_create(self.parse_args(['create', compression, '--files-cache=disabled', archive + '1', path]))
  359. t_end = time.monotonic()
  360. dt_create = t_end - t_start
  361. assert rc == 0
  362. # now build files cache
  363. rc1 = self.do_create(self.parse_args(['create', compression, archive + '2', path]))
  364. rc2 = self.do_delete(self.parse_args(['delete', archive + '2']))
  365. assert rc1 == rc2 == 0
  366. # measure a no-change update (archive1 is still present)
  367. t_start = time.monotonic()
  368. rc1 = self.do_create(self.parse_args(['create', compression, archive + '3', path]))
  369. t_end = time.monotonic()
  370. dt_update = t_end - t_start
  371. rc2 = self.do_delete(self.parse_args(['delete', archive + '3']))
  372. assert rc1 == rc2 == 0
  373. # measure extraction (dry-run: without writing result to disk)
  374. t_start = time.monotonic()
  375. rc = self.do_extract(self.parse_args(['extract', '--dry-run', archive + '1']))
  376. t_end = time.monotonic()
  377. dt_extract = t_end - t_start
  378. assert rc == 0
  379. # measure archive deletion (of LAST present archive with the data)
  380. t_start = time.monotonic()
  381. rc = self.do_delete(self.parse_args(['delete', archive + '1']))
  382. t_end = time.monotonic()
  383. dt_delete = t_end - t_start
  384. assert rc == 0
  385. return dt_create, dt_update, dt_extract, dt_delete
  386. @contextmanager
  387. def test_files(path, count, size, random):
  388. path = os.path.join(path, 'borg-test-data')
  389. os.makedirs(path)
  390. for i in range(count):
  391. fname = os.path.join(path, 'file_%d' % i)
  392. data = b'\0' * size if not random else os.urandom(size)
  393. with SyncFile(fname, binary=True) as fd: # used for posix_fadvise's sake
  394. fd.write(data)
  395. yield path
  396. shutil.rmtree(path)
  397. if '_BORG_BENCHMARK_CRUD_TEST' in os.environ:
  398. tests = [
  399. ('Z-TEST', 1, 1, False),
  400. ('R-TEST', 1, 1, True),
  401. ]
  402. else:
  403. tests = [
  404. ('Z-BIG', 10, 100000000, False),
  405. ('R-BIG', 10, 100000000, True),
  406. ('Z-MEDIUM', 1000, 1000000, False),
  407. ('R-MEDIUM', 1000, 1000000, True),
  408. ('Z-SMALL', 10000, 10000, False),
  409. ('R-SMALL', 10000, 10000, True),
  410. ]
  411. for msg, count, size, random in tests:
  412. with test_files(args.path, count, size, random) as path:
  413. dt_create, dt_update, dt_extract, dt_delete = measurement_run(args.location.canonical_path(), path)
  414. total_size_MB = count * size / 1e06
  415. file_size_formatted = format_file_size(size)
  416. content = 'random' if random else 'all-zero'
  417. fmt = '%s-%-10s %9.2f MB/s (%d * %s %s files: %.2fs)'
  418. print(fmt % ('C', msg, total_size_MB / dt_create, count, file_size_formatted, content, dt_create))
  419. print(fmt % ('R', msg, total_size_MB / dt_extract, count, file_size_formatted, content, dt_extract))
  420. print(fmt % ('U', msg, total_size_MB / dt_update, count, file_size_formatted, content, dt_update))
  421. print(fmt % ('D', msg, total_size_MB / dt_delete, count, file_size_formatted, content, dt_delete))
  422. return 0
  423. @with_repository(fake='dry_run', exclusive=True, compatibility=(Manifest.Operation.WRITE,))
  424. def do_create(self, args, repository, manifest=None, key=None):
  425. """Create new archive"""
  426. matcher = PatternMatcher(fallback=True)
  427. matcher.add_inclexcl(args.patterns)
  428. def create_inner(archive, cache):
  429. # Add cache dir to inode_skip list
  430. skip_inodes = set()
  431. try:
  432. st = os.stat(get_cache_dir())
  433. skip_inodes.add((st.st_ino, st.st_dev))
  434. except OSError:
  435. pass
  436. # Add local repository dir to inode_skip list
  437. if not args.location.host:
  438. try:
  439. st = os.stat(args.location.path)
  440. skip_inodes.add((st.st_ino, st.st_dev))
  441. except OSError:
  442. pass
  443. logger.debug('Processing files ...')
  444. for path in args.paths:
  445. if path == '-': # stdin
  446. path = args.stdin_name
  447. if not dry_run:
  448. try:
  449. status = archive.process_stdin(path, cache)
  450. except BackupOSError as e:
  451. status = 'E'
  452. self.print_warning('%s: %s', path, e)
  453. else:
  454. status = '-'
  455. self.print_file_status(status, path)
  456. continue
  457. path = os.path.normpath(path)
  458. try:
  459. st = os.stat(path, follow_symlinks=False)
  460. except OSError as e:
  461. self.print_warning('%s: %s', path, e)
  462. continue
  463. if args.one_file_system:
  464. restrict_dev = st.st_dev
  465. else:
  466. restrict_dev = None
  467. self._process(archive, cache, matcher, args.exclude_caches, args.exclude_if_present,
  468. args.keep_exclude_tags, skip_inodes, path, restrict_dev,
  469. read_special=args.read_special, dry_run=dry_run, st=st)
  470. if not dry_run:
  471. archive.save(comment=args.comment, timestamp=args.timestamp)
  472. if args.progress:
  473. archive.stats.show_progress(final=True)
  474. args.stats |= args.json
  475. if args.stats:
  476. if args.json:
  477. json_print(basic_json_data(manifest, cache=cache, extra={
  478. 'archive': archive,
  479. }))
  480. else:
  481. log_multi(DASHES,
  482. str(archive),
  483. DASHES,
  484. STATS_HEADER,
  485. str(archive.stats),
  486. str(cache),
  487. DASHES, logger=logging.getLogger('borg.output.stats'))
  488. self.output_filter = args.output_filter
  489. self.output_list = args.output_list
  490. self.exclude_nodump = args.exclude_nodump
  491. dry_run = args.dry_run
  492. t0 = datetime.utcnow()
  493. t0_monotonic = time.monotonic()
  494. if not dry_run:
  495. with Cache(repository, key, manifest, do_files=args.cache_files, progress=args.progress,
  496. lock_wait=self.lock_wait, permit_adhoc_cache=args.no_cache_sync,
  497. cache_mode=args.files_cache_mode, ignore_inode=args.ignore_inode) as cache:
  498. archive = Archive(repository, key, manifest, args.location.archive, cache=cache,
  499. create=True, checkpoint_interval=args.checkpoint_interval,
  500. numeric_owner=args.numeric_owner, noatime=args.noatime, noctime=args.noctime, nobirthtime=args.nobirthtime,
  501. nobsdflags=args.nobsdflags, progress=args.progress,
  502. chunker_params=args.chunker_params, start=t0, start_monotonic=t0_monotonic,
  503. log_json=args.log_json)
  504. create_inner(archive, cache)
  505. else:
  506. create_inner(None, None)
  507. return self.exit_code
  508. def _process(self, archive, cache, matcher, exclude_caches, exclude_if_present,
  509. keep_exclude_tags, skip_inodes, path, restrict_dev,
  510. read_special=False, dry_run=False, st=None):
  511. """
  512. Process *path* recursively according to the various parameters.
  513. *st* (if given) is a *os.stat_result* object for *path*.
  514. This should only raise on critical errors. Per-item errors must be handled within this method.
  515. """
  516. try:
  517. recurse_excluded_dir = False
  518. if matcher.match(path):
  519. if st is None:
  520. with backup_io('stat'):
  521. st = os.stat(path, follow_symlinks=False)
  522. else:
  523. self.print_file_status('x', path)
  524. # get out here as quickly as possible:
  525. # we only need to continue if we shall recurse into an excluded directory.
  526. # if we shall not recurse, then do not even touch (stat()) the item, it
  527. # could trigger an error, e.g. if access is forbidden, see #3209.
  528. if not matcher.recurse_dir:
  529. return
  530. if st is None:
  531. with backup_io('stat'):
  532. st = os.stat(path, follow_symlinks=False)
  533. recurse_excluded_dir = stat.S_ISDIR(st.st_mode)
  534. if not recurse_excluded_dir:
  535. return
  536. if (st.st_ino, st.st_dev) in skip_inodes:
  537. return
  538. # if restrict_dev is given, we do not want to recurse into a new filesystem,
  539. # but we WILL save the mountpoint directory (or more precise: the root
  540. # directory of the mounted filesystem that shadows the mountpoint dir).
  541. recurse = restrict_dev is None or st.st_dev == restrict_dev
  542. status = None
  543. if self.exclude_nodump:
  544. # Ignore if nodump flag is set
  545. with backup_io('flags'):
  546. if get_flags(path, st) & stat.UF_NODUMP:
  547. self.print_file_status('x', path)
  548. return
  549. if stat.S_ISREG(st.st_mode):
  550. if not dry_run:
  551. status = archive.process_file(path, st, cache)
  552. elif stat.S_ISDIR(st.st_mode):
  553. if recurse:
  554. tag_paths = dir_is_tagged(path, exclude_caches, exclude_if_present)
  555. if tag_paths:
  556. if keep_exclude_tags and not dry_run:
  557. archive.process_dir(path, st)
  558. for tag_path in tag_paths:
  559. self._process(archive, cache, matcher, exclude_caches, exclude_if_present,
  560. keep_exclude_tags, skip_inodes, tag_path, restrict_dev,
  561. read_special=read_special, dry_run=dry_run)
  562. self.print_file_status('x', path)
  563. return
  564. if not dry_run:
  565. if not recurse_excluded_dir:
  566. status = archive.process_dir(path, st)
  567. if recurse:
  568. with backup_io('scandir'):
  569. entries = helpers.scandir_inorder(path)
  570. for dirent in entries:
  571. normpath = os.path.normpath(dirent.path)
  572. self._process(archive, cache, matcher, exclude_caches, exclude_if_present,
  573. keep_exclude_tags, skip_inodes, normpath, restrict_dev,
  574. read_special=read_special, dry_run=dry_run)
  575. elif stat.S_ISLNK(st.st_mode):
  576. if not dry_run:
  577. if not read_special:
  578. status = archive.process_symlink(path, st)
  579. else:
  580. try:
  581. st_target = os.stat(path)
  582. except OSError:
  583. special = False
  584. else:
  585. special = is_special(st_target.st_mode)
  586. if special:
  587. status = archive.process_file(path, st_target, cache)
  588. else:
  589. status = archive.process_symlink(path, st)
  590. elif stat.S_ISFIFO(st.st_mode):
  591. if not dry_run:
  592. if not read_special:
  593. status = archive.process_fifo(path, st)
  594. else:
  595. status = archive.process_file(path, st, cache)
  596. elif stat.S_ISCHR(st.st_mode):
  597. if not dry_run:
  598. if not read_special:
  599. status = archive.process_dev(path, st, 'c')
  600. else:
  601. status = archive.process_file(path, st, cache)
  602. elif stat.S_ISBLK(st.st_mode):
  603. if not dry_run:
  604. if not read_special:
  605. status = archive.process_dev(path, st, 'b')
  606. else:
  607. status = archive.process_file(path, st, cache)
  608. elif stat.S_ISSOCK(st.st_mode):
  609. # Ignore unix sockets
  610. return
  611. elif stat.S_ISDOOR(st.st_mode):
  612. # Ignore Solaris doors
  613. return
  614. elif stat.S_ISPORT(st.st_mode):
  615. # Ignore Solaris event ports
  616. return
  617. else:
  618. self.print_warning('Unknown file type: %s', path)
  619. return
  620. except BackupOSError as e:
  621. self.print_warning('%s: %s', path, e)
  622. status = 'E'
  623. # Status output
  624. if status is None:
  625. if not dry_run:
  626. status = '?' # need to add a status code somewhere
  627. else:
  628. status = '-' # dry run, item was not backed up
  629. if not recurse_excluded_dir:
  630. self.print_file_status(status, path)
  631. @staticmethod
  632. def build_filter(matcher, peek_and_store_hardlink_masters, strip_components):
  633. if strip_components:
  634. def item_filter(item):
  635. matched = matcher.match(item.path) and os.sep.join(item.path.split(os.sep)[strip_components:])
  636. peek_and_store_hardlink_masters(item, matched)
  637. return matched
  638. else:
  639. def item_filter(item):
  640. matched = matcher.match(item.path)
  641. peek_and_store_hardlink_masters(item, matched)
  642. return matched
  643. return item_filter
  644. @with_repository(compatibility=(Manifest.Operation.READ,))
  645. @with_archive
  646. def do_extract(self, args, repository, manifest, key, archive):
  647. """Extract archive contents"""
  648. # be restrictive when restoring files, restore permissions later
  649. if sys.getfilesystemencoding() == 'ascii':
  650. logger.warning('Warning: File system encoding is "ascii", extracting non-ascii filenames will not be supported.')
  651. if sys.platform.startswith(('linux', 'freebsd', 'netbsd', 'openbsd', 'darwin', )):
  652. logger.warning('Hint: You likely need to fix your locale setup. E.g. install locales and use: LANG=en_US.UTF-8')
  653. matcher = self.build_matcher(args.patterns, args.paths)
  654. progress = args.progress
  655. output_list = args.output_list
  656. dry_run = args.dry_run
  657. stdout = args.stdout
  658. sparse = args.sparse
  659. strip_components = args.strip_components
  660. dirs = []
  661. partial_extract = not matcher.empty() or strip_components
  662. hardlink_masters = {} if partial_extract else None
  663. def peek_and_store_hardlink_masters(item, matched):
  664. if (partial_extract and not matched and hardlinkable(item.mode) and
  665. item.get('hardlink_master', True) and 'source' not in item):
  666. hardlink_masters[item.get('path')] = (item.get('chunks'), None)
  667. filter = self.build_filter(matcher, peek_and_store_hardlink_masters, strip_components)
  668. if progress:
  669. pi = ProgressIndicatorPercent(msg='%5.1f%% Extracting: %s', step=0.1, msgid='extract')
  670. pi.output('Calculating size')
  671. extracted_size = sum(item.get_size(hardlink_masters) for item in archive.iter_items(filter))
  672. pi.total = extracted_size
  673. else:
  674. pi = None
  675. for item in archive.iter_items(filter, preload=True):
  676. orig_path = item.path
  677. if strip_components:
  678. item.path = os.sep.join(orig_path.split(os.sep)[strip_components:])
  679. if not args.dry_run:
  680. while dirs and not item.path.startswith(dirs[-1].path):
  681. dir_item = dirs.pop(-1)
  682. try:
  683. archive.extract_item(dir_item, stdout=stdout)
  684. except BackupOSError as e:
  685. self.print_warning('%s: %s', remove_surrogates(dir_item.path), e)
  686. if output_list:
  687. logging.getLogger('borg.output.list').info(remove_surrogates(orig_path))
  688. try:
  689. if dry_run:
  690. archive.extract_item(item, dry_run=True, pi=pi)
  691. else:
  692. if stat.S_ISDIR(item.mode):
  693. dirs.append(item)
  694. archive.extract_item(item, stdout=stdout, restore_attrs=False)
  695. else:
  696. archive.extract_item(item, stdout=stdout, sparse=sparse, hardlink_masters=hardlink_masters,
  697. stripped_components=strip_components, original_path=orig_path, pi=pi)
  698. except (BackupOSError, BackupError) as e:
  699. self.print_warning('%s: %s', remove_surrogates(orig_path), e)
  700. if pi:
  701. pi.finish()
  702. if not args.dry_run:
  703. pi = ProgressIndicatorPercent(total=len(dirs), msg='Setting directory permissions %3.0f%%',
  704. msgid='extract.permissions')
  705. while dirs:
  706. pi.show()
  707. dir_item = dirs.pop(-1)
  708. try:
  709. archive.extract_item(dir_item, stdout=stdout)
  710. except BackupOSError as e:
  711. self.print_warning('%s: %s', remove_surrogates(dir_item.path), e)
  712. for pattern in matcher.get_unmatched_include_patterns():
  713. self.print_warning("Include pattern '%s' never matched.", pattern)
  714. if pi:
  715. # clear progress output
  716. pi.finish()
  717. return self.exit_code
  718. @with_repository(compatibility=(Manifest.Operation.READ,))
  719. @with_archive
  720. def do_export_tar(self, args, repository, manifest, key, archive):
  721. """Export archive contents as a tarball"""
  722. self.output_list = args.output_list
  723. # A quick note about the general design of tar_filter and tarfile;
  724. # The tarfile module of Python can provide some compression mechanisms
  725. # by itself, using the builtin gzip, bz2 and lzma modules (and "tarmodes"
  726. # such as "w:xz").
  727. #
  728. # Doing so would have three major drawbacks:
  729. # For one the compressor runs on the same thread as the program using the
  730. # tarfile, stealing valuable CPU time from Borg and thus reducing throughput.
  731. # Then this limits the available options - what about lz4? Brotli? zstd?
  732. # The third issue is that systems can ship more optimized versions than those
  733. # built into Python, e.g. pigz or pxz, which can use more than one thread for
  734. # compression.
  735. #
  736. # Therefore we externalize compression by using a filter program, which has
  737. # none of these drawbacks. The only issue of using an external filter is
  738. # that it has to be installed -- hardly a problem, considering that
  739. # the decompressor must be installed as well to make use of the exported tarball!
  740. filter = None
  741. if args.tar_filter == 'auto':
  742. # Note that filter remains None if tarfile is '-'.
  743. if args.tarfile.endswith('.tar.gz'):
  744. filter = 'gzip'
  745. elif args.tarfile.endswith('.tar.bz2'):
  746. filter = 'bzip2'
  747. elif args.tarfile.endswith('.tar.xz'):
  748. filter = 'xz'
  749. logger.debug('Automatically determined tar filter: %s', filter)
  750. else:
  751. filter = args.tar_filter
  752. tarstream = dash_open(args.tarfile, 'wb')
  753. tarstream_close = args.tarfile != '-'
  754. if filter:
  755. # When we put a filter between us and the final destination,
  756. # the selected output (tarstream until now) becomes the output of the filter (=filterout).
  757. # The decision whether to close that or not remains the same.
  758. filterout = tarstream
  759. filterout_close = tarstream_close
  760. env = prepare_subprocess_env(system=True)
  761. # There is no deadlock potential here (the subprocess docs warn about this), because
  762. # communication with the process is a one-way road, i.e. the process can never block
  763. # for us to do something while we block on the process for something different.
  764. filterproc = popen_with_error_handling(filter, stdin=subprocess.PIPE, stdout=filterout,
  765. log_prefix='--tar-filter: ', env=env)
  766. if not filterproc:
  767. return EXIT_ERROR
  768. # Always close the pipe, otherwise the filter process would not notice when we are done.
  769. tarstream = filterproc.stdin
  770. tarstream_close = True
  771. # The | (pipe) symbol instructs tarfile to use a streaming mode of operation
  772. # where it never seeks on the passed fileobj.
  773. tar = tarfile.open(fileobj=tarstream, mode='w|')
  774. self._export_tar(args, archive, tar)
  775. # This does not close the fileobj (tarstream) we passed to it -- a side effect of the | mode.
  776. tar.close()
  777. if tarstream_close:
  778. tarstream.close()
  779. if filter:
  780. logger.debug('Done creating tar, waiting for filter to die...')
  781. rc = filterproc.wait()
  782. if rc:
  783. logger.error('--tar-filter exited with code %d, output file is likely unusable!', rc)
  784. self.exit_code = EXIT_ERROR
  785. else:
  786. logger.debug('filter exited with code %d', rc)
  787. if filterout_close:
  788. filterout.close()
  789. return self.exit_code
  790. def _export_tar(self, args, archive, tar):
  791. matcher = self.build_matcher(args.patterns, args.paths)
  792. progress = args.progress
  793. output_list = args.output_list
  794. strip_components = args.strip_components
  795. partial_extract = not matcher.empty() or strip_components
  796. hardlink_masters = {} if partial_extract else None
  797. def peek_and_store_hardlink_masters(item, matched):
  798. if (partial_extract and not matched and hardlinkable(item.mode) and
  799. item.get('hardlink_master', True) and 'source' not in item):
  800. hardlink_masters[item.get('path')] = (item.get('chunks'), None)
  801. filter = self.build_filter(matcher, peek_and_store_hardlink_masters, strip_components)
  802. if progress:
  803. pi = ProgressIndicatorPercent(msg='%5.1f%% Processing: %s', step=0.1, msgid='extract')
  804. pi.output('Calculating size')
  805. extracted_size = sum(item.get_size(hardlink_masters) for item in archive.iter_items(filter))
  806. pi.total = extracted_size
  807. else:
  808. pi = None
  809. def item_content_stream(item):
  810. """
  811. Return a file-like object that reads from the chunks of *item*.
  812. """
  813. chunk_iterator = archive.pipeline.fetch_many([chunk_id for chunk_id, _, _ in item.chunks])
  814. if pi:
  815. info = [remove_surrogates(item.path)]
  816. return ChunkIteratorFileWrapper(chunk_iterator,
  817. lambda read_bytes: pi.show(increase=len(read_bytes), info=info))
  818. else:
  819. return ChunkIteratorFileWrapper(chunk_iterator)
  820. def item_to_tarinfo(item, original_path):
  821. """
  822. Transform a Borg *item* into a tarfile.TarInfo object.
  823. Return a tuple (tarinfo, stream), where stream may be a file-like object that represents
  824. the file contents, if any, and is None otherwise. When *tarinfo* is None, the *item*
  825. cannot be represented as a TarInfo object and should be skipped.
  826. """
  827. # If we would use the PAX (POSIX) format (which we currently don't),
  828. # we can support most things that aren't possible with classic tar
  829. # formats, including GNU tar, such as:
  830. # atime, ctime, possibly Linux capabilities (security.* xattrs)
  831. # and various additions supported by GNU tar in POSIX mode.
  832. stream = None
  833. tarinfo = tarfile.TarInfo()
  834. tarinfo.name = item.path
  835. tarinfo.mtime = item.mtime / 1e9
  836. tarinfo.mode = stat.S_IMODE(item.mode)
  837. tarinfo.uid = item.uid
  838. tarinfo.gid = item.gid
  839. tarinfo.uname = item.user or ''
  840. tarinfo.gname = item.group or ''
  841. # The linkname in tar has the same dual use the 'source' attribute of Borg items,
  842. # i.e. for symlinks it means the destination, while for hardlinks it refers to the
  843. # file.
  844. # Since hardlinks in tar have a different type code (LNKTYPE) the format might
  845. # support hardlinking arbitrary objects (including symlinks and directories), but
  846. # whether implementations actually support that is a whole different question...
  847. tarinfo.linkname = ""
  848. modebits = stat.S_IFMT(item.mode)
  849. if modebits == stat.S_IFREG:
  850. tarinfo.type = tarfile.REGTYPE
  851. if 'source' in item:
  852. source = os.sep.join(item.source.split(os.sep)[strip_components:])
  853. if hardlink_masters is None:
  854. linkname = source
  855. else:
  856. chunks, linkname = hardlink_masters.get(item.source, (None, source))
  857. if linkname:
  858. # Master was already added to the archive, add a hardlink reference to it.
  859. tarinfo.type = tarfile.LNKTYPE
  860. tarinfo.linkname = linkname
  861. elif chunks is not None:
  862. # The item which has the chunks was not put into the tar, therefore
  863. # we do that now and update hardlink_masters to reflect that.
  864. item.chunks = chunks
  865. tarinfo.size = item.get_size()
  866. stream = item_content_stream(item)
  867. hardlink_masters[item.get('source') or original_path] = (None, item.path)
  868. else:
  869. tarinfo.size = item.get_size()
  870. stream = item_content_stream(item)
  871. elif modebits == stat.S_IFDIR:
  872. tarinfo.type = tarfile.DIRTYPE
  873. elif modebits == stat.S_IFLNK:
  874. tarinfo.type = tarfile.SYMTYPE
  875. tarinfo.linkname = item.source
  876. elif modebits == stat.S_IFBLK:
  877. tarinfo.type = tarfile.BLKTYPE
  878. tarinfo.devmajor = os.major(item.rdev)
  879. tarinfo.devminor = os.minor(item.rdev)
  880. elif modebits == stat.S_IFCHR:
  881. tarinfo.type = tarfile.CHRTYPE
  882. tarinfo.devmajor = os.major(item.rdev)
  883. tarinfo.devminor = os.minor(item.rdev)
  884. elif modebits == stat.S_IFIFO:
  885. tarinfo.type = tarfile.FIFOTYPE
  886. else:
  887. self.print_warning('%s: unsupported file type %o for tar export', remove_surrogates(item.path), modebits)
  888. set_ec(EXIT_WARNING)
  889. return None, stream
  890. return tarinfo, stream
  891. for item in archive.iter_items(filter, preload=True):
  892. orig_path = item.path
  893. if strip_components:
  894. item.path = os.sep.join(orig_path.split(os.sep)[strip_components:])
  895. tarinfo, stream = item_to_tarinfo(item, orig_path)
  896. if tarinfo:
  897. if output_list:
  898. logging.getLogger('borg.output.list').info(remove_surrogates(orig_path))
  899. tar.addfile(tarinfo, stream)
  900. if pi:
  901. pi.finish()
  902. for pattern in matcher.get_unmatched_include_patterns():
  903. self.print_warning("Include pattern '%s' never matched.", pattern)
  904. return self.exit_code
  905. @with_repository(compatibility=(Manifest.Operation.READ,))
  906. @with_archive
  907. def do_diff(self, args, repository, manifest, key, archive):
  908. """Diff contents of two archives"""
  909. def fetch_and_compare_chunks(chunk_ids1, chunk_ids2, archive1, archive2):
  910. chunks1 = archive1.pipeline.fetch_many(chunk_ids1)
  911. chunks2 = archive2.pipeline.fetch_many(chunk_ids2)
  912. return self.compare_chunk_contents(chunks1, chunks2)
  913. def sum_chunk_size(item, consider_ids=None):
  914. if item.get('deleted'):
  915. size = None
  916. else:
  917. if consider_ids is not None: # consider only specific chunks
  918. size = sum(chunk.size for chunk in item.chunks if chunk.id in consider_ids)
  919. else: # consider all chunks
  920. size = item.get_size()
  921. return size
  922. def get_owner(item):
  923. if args.numeric_owner:
  924. return item.uid, item.gid
  925. else:
  926. return item.user, item.group
  927. def get_mode(item):
  928. if 'mode' in item:
  929. return stat.filemode(item.mode)
  930. else:
  931. return [None]
  932. def has_hardlink_master(item, hardlink_masters):
  933. return hardlinkable(item.mode) and item.get('source') in hardlink_masters
  934. def compare_link(item1, item2):
  935. # These are the simple link cases. For special cases, e.g. if a
  936. # regular file is replaced with a link or vice versa, it is
  937. # indicated in compare_mode instead.
  938. if item1.get('deleted'):
  939. return 'added link'
  940. elif item2.get('deleted'):
  941. return 'removed link'
  942. elif 'source' in item1 and 'source' in item2 and item1.source != item2.source:
  943. return 'changed link'
  944. def contents_changed(item1, item2):
  945. if item1.get('deleted') != item2.get('deleted'):
  946. # a deleleted/non-existing file is considered different to an existing file,
  947. # even if the latter is empty.
  948. return True
  949. if can_compare_chunk_ids:
  950. return item1.chunks != item2.chunks
  951. else:
  952. if sum_chunk_size(item1) != sum_chunk_size(item2):
  953. return True
  954. else:
  955. chunk_ids1 = [c.id for c in item1.chunks]
  956. chunk_ids2 = [c.id for c in item2.chunks]
  957. return not fetch_and_compare_chunks(chunk_ids1, chunk_ids2, archive1, archive2)
  958. def compare_content(path, item1, item2):
  959. if contents_changed(item1, item2):
  960. if item1.get('deleted'):
  961. return 'added {:>13}'.format(format_file_size(sum_chunk_size(item2)))
  962. if item2.get('deleted'):
  963. return 'removed {:>11}'.format(format_file_size(sum_chunk_size(item1)))
  964. if not can_compare_chunk_ids:
  965. return 'modified'
  966. chunk_ids1 = {c.id for c in item1.chunks}
  967. chunk_ids2 = {c.id for c in item2.chunks}
  968. added_ids = chunk_ids2 - chunk_ids1
  969. removed_ids = chunk_ids1 - chunk_ids2
  970. added = sum_chunk_size(item2, added_ids)
  971. removed = sum_chunk_size(item1, removed_ids)
  972. return '{:>9} {:>9}'.format(format_file_size(added, precision=1, sign=True),
  973. format_file_size(-removed, precision=1, sign=True))
  974. def compare_directory(item1, item2):
  975. if item2.get('deleted') and not item1.get('deleted'):
  976. return 'removed directory'
  977. elif item1.get('deleted') and not item2.get('deleted'):
  978. return 'added directory'
  979. def compare_owner(item1, item2):
  980. user1, group1 = get_owner(item1)
  981. user2, group2 = get_owner(item2)
  982. if user1 != user2 or group1 != group2:
  983. return '[{}:{} -> {}:{}]'.format(user1, group1, user2, group2)
  984. def compare_mode(item1, item2):
  985. if item1.mode != item2.mode:
  986. return '[{} -> {}]'.format(get_mode(item1), get_mode(item2))
  987. def compare_items(output, path, item1, item2, hardlink_masters, deleted=False):
  988. """
  989. Compare two items with identical paths.
  990. :param deleted: Whether one of the items has been deleted
  991. """
  992. changes = []
  993. if has_hardlink_master(item1, hardlink_masters):
  994. item1 = hardlink_masters[item1.source][0]
  995. if has_hardlink_master(item2, hardlink_masters):
  996. item2 = hardlink_masters[item2.source][1]
  997. if get_mode(item1)[0] == 'l' or get_mode(item2)[0] == 'l':
  998. changes.append(compare_link(item1, item2))
  999. if 'chunks' in item1 and 'chunks' in item2:
  1000. changes.append(compare_content(path, item1, item2))
  1001. if get_mode(item1)[0] == 'd' or get_mode(item2)[0] == 'd':
  1002. changes.append(compare_directory(item1, item2))
  1003. if not deleted:
  1004. changes.append(compare_owner(item1, item2))
  1005. changes.append(compare_mode(item1, item2))
  1006. changes = [x for x in changes if x]
  1007. if changes:
  1008. output_line = (remove_surrogates(path), ' '.join(changes))
  1009. if args.sort:
  1010. output.append(output_line)
  1011. else:
  1012. print_output(output_line)
  1013. def print_output(line):
  1014. print("{:<19} {}".format(line[1], line[0]))
  1015. def compare_archives(archive1, archive2, matcher):
  1016. def hardlink_master_seen(item):
  1017. return 'source' not in item or not hardlinkable(item.mode) or item.source in hardlink_masters
  1018. def is_hardlink_master(item):
  1019. return item.get('hardlink_master', True) and 'source' not in item
  1020. def update_hardlink_masters(item1, item2):
  1021. if is_hardlink_master(item1) or is_hardlink_master(item2):
  1022. hardlink_masters[item1.path] = (item1, item2)
  1023. def compare_or_defer(item1, item2):
  1024. update_hardlink_masters(item1, item2)
  1025. if not hardlink_master_seen(item1) or not hardlink_master_seen(item2):
  1026. deferred.append((item1, item2))
  1027. else:
  1028. compare_items(output, item1.path, item1, item2, hardlink_masters)
  1029. orphans_archive1 = collections.OrderedDict()
  1030. orphans_archive2 = collections.OrderedDict()
  1031. deferred = []
  1032. hardlink_masters = {}
  1033. output = []
  1034. for item1, item2 in zip_longest(
  1035. archive1.iter_items(lambda item: matcher.match(item.path)),
  1036. archive2.iter_items(lambda item: matcher.match(item.path)),
  1037. ):
  1038. if item1 and item2 and item1.path == item2.path:
  1039. compare_or_defer(item1, item2)
  1040. continue
  1041. if item1:
  1042. matching_orphan = orphans_archive2.pop(item1.path, None)
  1043. if matching_orphan:
  1044. compare_or_defer(item1, matching_orphan)
  1045. else:
  1046. orphans_archive1[item1.path] = item1
  1047. if item2:
  1048. matching_orphan = orphans_archive1.pop(item2.path, None)
  1049. if matching_orphan:
  1050. compare_or_defer(matching_orphan, item2)
  1051. else:
  1052. orphans_archive2[item2.path] = item2
  1053. # At this point orphans_* contain items that had no matching partner in the other archive
  1054. deleted_item = Item(
  1055. deleted=True,
  1056. chunks=[],
  1057. mode=0,
  1058. )
  1059. for added in orphans_archive2.values():
  1060. path = added.path
  1061. deleted_item.path = path
  1062. update_hardlink_masters(deleted_item, added)
  1063. compare_items(output, path, deleted_item, added, hardlink_masters, deleted=True)
  1064. for deleted in orphans_archive1.values():
  1065. path = deleted.path
  1066. deleted_item.path = path
  1067. update_hardlink_masters(deleted, deleted_item)
  1068. compare_items(output, path, deleted, deleted_item, hardlink_masters, deleted=True)
  1069. for item1, item2 in deferred:
  1070. assert hardlink_master_seen(item1)
  1071. assert hardlink_master_seen(item2)
  1072. compare_items(output, item1.path, item1, item2, hardlink_masters)
  1073. for line in sorted(output):
  1074. print_output(line)
  1075. archive1 = archive
  1076. archive2 = Archive(repository, key, manifest, args.archive2,
  1077. consider_part_files=args.consider_part_files)
  1078. can_compare_chunk_ids = archive1.metadata.get('chunker_params', False) == archive2.metadata.get(
  1079. 'chunker_params', True) or args.same_chunker_params
  1080. if not can_compare_chunk_ids:
  1081. self.print_warning('--chunker-params might be different between archives, diff will be slow.\n'
  1082. 'If you know for certain that they are the same, pass --same-chunker-params '
  1083. 'to override this check.')
  1084. matcher = self.build_matcher(args.patterns, args.paths)
  1085. compare_archives(archive1, archive2, matcher)
  1086. for pattern in matcher.get_unmatched_include_patterns():
  1087. self.print_warning("Include pattern '%s' never matched.", pattern)
  1088. return self.exit_code
  1089. @with_repository(exclusive=True, cache=True, compatibility=(Manifest.Operation.CHECK,))
  1090. @with_archive
  1091. def do_rename(self, args, repository, manifest, key, cache, archive):
  1092. """Rename an existing archive"""
  1093. name = replace_placeholders(args.name)
  1094. archive.rename(name)
  1095. manifest.write()
  1096. repository.commit()
  1097. cache.commit()
  1098. return self.exit_code
  1099. @with_repository(exclusive=True, manifest=False)
  1100. def do_delete(self, args, repository):
  1101. """Delete an existing repository or archives"""
  1102. archive_filter_specified = args.first or args.last or args.prefix or args.glob_archives
  1103. explicit_archives_specified = args.location.archive or args.archives
  1104. if archive_filter_specified and explicit_archives_specified:
  1105. self.print_error('Mixing archive filters and explicitly named archives is not supported.')
  1106. return self.exit_code
  1107. if archive_filter_specified or explicit_archives_specified:
  1108. return self._delete_archives(args, repository)
  1109. else:
  1110. return self._delete_repository(args, repository)
  1111. def _delete_archives(self, args, repository):
  1112. """Delete archives"""
  1113. dry_run = args.dry_run
  1114. manifest, key = Manifest.load(repository, (Manifest.Operation.DELETE,))
  1115. if args.location.archive or args.archives:
  1116. archives = list(args.archives)
  1117. if args.location.archive:
  1118. archives.insert(0, args.location.archive)
  1119. archive_names = tuple(archives)
  1120. else:
  1121. archive_names = tuple(x.name for x in manifest.archives.list_considering(args))
  1122. if not archive_names:
  1123. return self.exit_code
  1124. if args.forced == 2:
  1125. deleted = False
  1126. for i, archive_name in enumerate(archive_names, 1):
  1127. try:
  1128. del manifest.archives[archive_name]
  1129. except KeyError:
  1130. self.exit_code = EXIT_WARNING
  1131. logger.warning('Archive {} not found ({}/{}).'.format(archive_name, i, len(archive_names)))
  1132. else:
  1133. deleted = True
  1134. msg = 'Would delete: {} ({}/{})' if dry_run else 'Deleted archive: {} ({}/{})'
  1135. logger.info(msg.format(archive_name, i, len(archive_names)))
  1136. if dry_run:
  1137. logger.info('Finished dry-run.')
  1138. elif deleted:
  1139. manifest.write()
  1140. # note: might crash in compact() after committing the repo
  1141. repository.commit()
  1142. logger.info('Done. Run "borg check --repair" to clean up the mess.')
  1143. else:
  1144. logger.warning('Aborted.')
  1145. return self.exit_code
  1146. stats = Statistics()
  1147. with Cache(repository, key, manifest, progress=args.progress, lock_wait=self.lock_wait) as cache:
  1148. for i, archive_name in enumerate(archive_names, 1):
  1149. msg = 'Would delete archive: {} ({}/{})' if dry_run else 'Deleting archive: {} ({}/{})'
  1150. logger.info(msg.format(archive_name, i, len(archive_names)))
  1151. if not dry_run:
  1152. Archive(repository, key, manifest, archive_name, cache=cache).delete(
  1153. stats, progress=args.progress, forced=args.forced)
  1154. if not dry_run:
  1155. manifest.write()
  1156. repository.commit(save_space=args.save_space)
  1157. cache.commit()
  1158. if args.stats:
  1159. log_multi(DASHES,
  1160. STATS_HEADER,
  1161. stats.summary.format(label='Deleted data:', stats=stats),
  1162. str(cache),
  1163. DASHES, logger=logging.getLogger('borg.output.stats'))
  1164. return self.exit_code
  1165. def _delete_repository(self, args, repository):
  1166. """Delete a repository"""
  1167. dry_run = args.dry_run
  1168. if not args.cache_only:
  1169. msg = []
  1170. try:
  1171. manifest, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK)
  1172. except NoManifestError:
  1173. msg.append("You requested to completely DELETE the repository *including* all archives it may "
  1174. "contain.")
  1175. msg.append("This repository seems to have no manifest, so we can't tell anything about its "
  1176. "contents.")
  1177. else:
  1178. msg.append("You requested to completely DELETE the repository *including* all archives it "
  1179. "contains:")
  1180. for archive_info in manifest.archives.list(sort_by=['ts']):
  1181. msg.append(format_archive(archive_info))
  1182. msg.append("Type 'YES' if you understand this and want to continue: ")
  1183. msg = '\n'.join(msg)
  1184. if not yes(msg, false_msg="Aborting.", invalid_msg='Invalid answer, aborting.', truish=('YES',),
  1185. retry=False, env_var_override='BORG_DELETE_I_KNOW_WHAT_I_AM_DOING'):
  1186. self.exit_code = EXIT_ERROR
  1187. return self.exit_code
  1188. if not dry_run:
  1189. repository.destroy()
  1190. logger.info("Repository deleted.")
  1191. SecurityManager.destroy(repository)
  1192. else:
  1193. logger.info("Would delete repository.")
  1194. if not dry_run:
  1195. Cache.destroy(repository)
  1196. logger.info("Cache deleted.")
  1197. else:
  1198. logger.info("Would delete cache.")
  1199. return self.exit_code
  1200. def do_mount(self, args):
  1201. """Mount archive or an entire repository as a FUSE filesystem"""
  1202. # Perform these checks before opening the repository and asking for a passphrase.
  1203. try:
  1204. import borg.fuse
  1205. except ImportError as e:
  1206. self.print_error('borg mount not available: loading FUSE support failed [ImportError: %s]' % str(e))
  1207. return self.exit_code
  1208. if not os.path.isdir(args.mountpoint) or not os.access(args.mountpoint, os.R_OK | os.W_OK | os.X_OK):
  1209. self.print_error('%s: Mountpoint must be a writable directory' % args.mountpoint)
  1210. return self.exit_code
  1211. return self._do_mount(args)
  1212. @with_repository(compatibility=(Manifest.Operation.READ,))
  1213. def _do_mount(self, args, repository, manifest, key):
  1214. from .fuse import FuseOperations
  1215. with cache_if_remote(repository, decrypted_cache=key) as cached_repo:
  1216. operations = FuseOperations(key, repository, manifest, args, cached_repo)
  1217. logger.info("Mounting filesystem")
  1218. try:
  1219. operations.mount(args.mountpoint, args.options, args.foreground)
  1220. except RuntimeError:
  1221. # Relevant error message already printed to stderr by FUSE
  1222. self.exit_code = EXIT_ERROR
  1223. return self.exit_code
  1224. def do_umount(self, args):
  1225. """un-mount the FUSE filesystem"""
  1226. return umount(args.mountpoint)
  1227. @with_repository(compatibility=(Manifest.Operation.READ,))
  1228. def do_list(self, args, repository, manifest, key):
  1229. """List archive or repository contents"""
  1230. if args.location.archive:
  1231. if args.json:
  1232. self.print_error('The --json option is only valid for listing archives, not archive contents.')
  1233. return self.exit_code
  1234. return self._list_archive(args, repository, manifest, key)
  1235. else:
  1236. if args.json_lines:
  1237. self.print_error('The --json-lines option is only valid for listing archive contents, not archives.')
  1238. return self.exit_code
  1239. return self._list_repository(args, repository, manifest, key)
  1240. def _list_archive(self, args, repository, manifest, key):
  1241. matcher = self.build_matcher(args.patterns, args.paths)
  1242. if args.format is not None:
  1243. format = args.format
  1244. elif args.short:
  1245. format = "{path}{NL}"
  1246. else:
  1247. format = "{mode} {user:6} {group:6} {size:8} {mtime} {path}{extra}{NL}"
  1248. def _list_inner(cache):
  1249. archive = Archive(repository, key, manifest, args.location.archive, cache=cache,
  1250. consider_part_files=args.consider_part_files)
  1251. formatter = ItemFormatter(archive, format, json_lines=args.json_lines)
  1252. for item in archive.iter_items(lambda item: matcher.match(item.path)):
  1253. sys.stdout.write(formatter.format_item(item))
  1254. # Only load the cache if it will be used
  1255. if ItemFormatter.format_needs_cache(format):
  1256. with Cache(repository, key, manifest, lock_wait=self.lock_wait) as cache:
  1257. _list_inner(cache)
  1258. else:
  1259. _list_inner(cache=None)
  1260. return self.exit_code
  1261. def _list_repository(self, args, repository, manifest, key):
  1262. if args.format is not None:
  1263. format = args.format
  1264. elif args.short:
  1265. format = "{archive}{NL}"
  1266. else:
  1267. format = "{archive:<36} {time} [{id}]{NL}"
  1268. formatter = ArchiveFormatter(format, repository, manifest, key, json=args.json)
  1269. output_data = []
  1270. for archive_info in manifest.archives.list_considering(args):
  1271. if args.json:
  1272. output_data.append(formatter.get_item_data(archive_info))
  1273. else:
  1274. sys.stdout.write(formatter.format_item(archive_info))
  1275. if args.json:
  1276. json_print(basic_json_data(manifest, extra={
  1277. 'archives': output_data
  1278. }))
  1279. return self.exit_code
  1280. @with_repository(cache=True, compatibility=(Manifest.Operation.READ,))
  1281. def do_info(self, args, repository, manifest, key, cache):
  1282. """Show archive details such as disk space used"""
  1283. if any((args.location.archive, args.first, args.last, args.prefix, args.glob_archives)):
  1284. return self._info_archives(args, repository, manifest, key, cache)
  1285. else:
  1286. return self._info_repository(args, repository, manifest, key, cache)
  1287. def _info_archives(self, args, repository, manifest, key, cache):
  1288. def format_cmdline(cmdline):
  1289. return remove_surrogates(' '.join(shlex.quote(x) for x in cmdline))
  1290. if args.location.archive:
  1291. archive_names = (args.location.archive,)
  1292. else:
  1293. archive_names = tuple(x.name for x in manifest.archives.list_considering(args))
  1294. if not archive_names:
  1295. return self.exit_code
  1296. output_data = []
  1297. for i, archive_name in enumerate(archive_names, 1):
  1298. archive = Archive(repository, key, manifest, archive_name, cache=cache,
  1299. consider_part_files=args.consider_part_files)
  1300. info = archive.info()
  1301. if args.json:
  1302. output_data.append(info)
  1303. else:
  1304. info['duration'] = format_timedelta(timedelta(seconds=info['duration']))
  1305. info['command_line'] = format_cmdline(info['command_line'])
  1306. print(textwrap.dedent("""
  1307. Archive name: {name}
  1308. Archive fingerprint: {id}
  1309. Comment: {comment}
  1310. Hostname: {hostname}
  1311. Username: {username}
  1312. Time (start): {start}
  1313. Time (end): {end}
  1314. Duration: {duration}
  1315. Number of files: {stats[nfiles]}
  1316. Command line: {command_line}
  1317. Utilization of maximum supported archive size: {limits[max_archive_size]:.0%}
  1318. ------------------------------------------------------------------------------
  1319. Original size Compressed size Deduplicated size
  1320. This archive: {stats[original_size]:>20s} {stats[compressed_size]:>20s} {stats[deduplicated_size]:>20s}
  1321. {cache}
  1322. """).strip().format(cache=cache, **info))
  1323. if self.exit_code:
  1324. break
  1325. if not args.json and len(archive_names) - i:
  1326. print()
  1327. if args.json:
  1328. json_print(basic_json_data(manifest, cache=cache, extra={
  1329. 'archives': output_data,
  1330. }))
  1331. return self.exit_code
  1332. def _info_repository(self, args, repository, manifest, key, cache):
  1333. info = basic_json_data(manifest, cache=cache, extra={
  1334. 'security_dir': cache.security_manager.dir,
  1335. })
  1336. if args.json:
  1337. json_print(info)
  1338. else:
  1339. encryption = 'Encrypted: '
  1340. if key.NAME == 'plaintext':
  1341. encryption += 'No'
  1342. else:
  1343. encryption += 'Yes (%s)' % key.NAME
  1344. if key.NAME.startswith('key file'):
  1345. encryption += '\nKey file: %s' % key.find_key()
  1346. info['encryption'] = encryption
  1347. print(textwrap.dedent("""
  1348. Repository ID: {id}
  1349. Location: {location}
  1350. {encryption}
  1351. Cache: {cache.path}
  1352. Security dir: {security_dir}
  1353. """).strip().format(
  1354. id=bin_to_hex(repository.id),
  1355. location=repository._location.canonical_path(),
  1356. **info))
  1357. print(DASHES)
  1358. print(STATS_HEADER)
  1359. print(str(cache))
  1360. return self.exit_code
  1361. @with_repository(exclusive=True, compatibility=(Manifest.Operation.DELETE,))
  1362. def do_prune(self, args, repository, manifest, key):
  1363. """Prune repository archives according to specified rules"""
  1364. if not any((args.secondly, args.minutely, args.hourly, args.daily,
  1365. args.weekly, args.monthly, args.yearly, args.within)):
  1366. self.print_error('At least one of the "keep-within", "keep-last", '
  1367. '"keep-secondly", "keep-minutely", "keep-hourly", "keep-daily", '
  1368. '"keep-weekly", "keep-monthly" or "keep-yearly" settings must be specified.')
  1369. return self.exit_code
  1370. if args.prefix:
  1371. args.glob_archives = args.prefix + '*'
  1372. checkpoint_re = r'\.checkpoint(\.\d+)?'
  1373. archives_checkpoints = manifest.archives.list(glob=args.glob_archives,
  1374. match_end=r'(%s)?\Z' % checkpoint_re,
  1375. sort_by=['ts'], reverse=True)
  1376. is_checkpoint = re.compile(r'(%s)\Z' % checkpoint_re).search
  1377. checkpoints = [arch for arch in archives_checkpoints if is_checkpoint(arch.name)]
  1378. # keep the latest checkpoint, if there is no later non-checkpoint archive
  1379. if archives_checkpoints and checkpoints and archives_checkpoints[0] is checkpoints[0]:
  1380. keep_checkpoints = checkpoints[:1]
  1381. else:
  1382. keep_checkpoints = []
  1383. checkpoints = set(checkpoints)
  1384. # ignore all checkpoint archives to avoid keeping one (which is an incomplete backup)
  1385. # that is newer than a successfully completed backup - and killing the successful backup.
  1386. archives = [arch for arch in archives_checkpoints if arch not in checkpoints]
  1387. keep = []
  1388. if args.within:
  1389. keep += prune_within(archives, args.within)
  1390. if args.secondly:
  1391. keep += prune_split(archives, '%Y-%m-%d %H:%M:%S', args.secondly, keep)
  1392. if args.minutely:
  1393. keep += prune_split(archives, '%Y-%m-%d %H:%M', args.minutely, keep)
  1394. if args.hourly:
  1395. keep += prune_split(archives, '%Y-%m-%d %H', args.hourly, keep)
  1396. if args.daily:
  1397. keep += prune_split(archives, '%Y-%m-%d', args.daily, keep)
  1398. if args.weekly:
  1399. keep += prune_split(archives, '%G-%V', args.weekly, keep)
  1400. if args.monthly:
  1401. keep += prune_split(archives, '%Y-%m', args.monthly, keep)
  1402. if args.yearly:
  1403. keep += prune_split(archives, '%Y', args.yearly, keep)
  1404. to_delete = (set(archives) | checkpoints) - (set(keep) | set(keep_checkpoints))
  1405. stats = Statistics()
  1406. with Cache(repository, key, manifest, lock_wait=self.lock_wait) as cache:
  1407. list_logger = logging.getLogger('borg.output.list')
  1408. if args.output_list:
  1409. # set up counters for the progress display
  1410. to_delete_len = len(to_delete)
  1411. archives_deleted = 0
  1412. pi = ProgressIndicatorPercent(total=len(to_delete), msg='Pruning archives %3.0f%%', msgid='prune')
  1413. for archive in archives_checkpoints:
  1414. if archive in to_delete:
  1415. pi.show()
  1416. if args.dry_run:
  1417. if args.output_list:
  1418. list_logger.info('Would prune: %s' % format_archive(archive))
  1419. else:
  1420. if args.output_list:
  1421. archives_deleted += 1
  1422. list_logger.info('Pruning archive: %s (%d/%d)' % (format_archive(archive),
  1423. archives_deleted, to_delete_len))
  1424. Archive(repository, key, manifest, archive.name, cache).delete(stats, forced=args.forced)
  1425. else:
  1426. if args.output_list:
  1427. list_logger.info('Keeping archive: %s' % format_archive(archive))
  1428. pi.finish()
  1429. if to_delete and not args.dry_run:
  1430. manifest.write()
  1431. repository.commit(save_space=args.save_space)
  1432. cache.commit()
  1433. if args.stats:
  1434. log_multi(DASHES,
  1435. STATS_HEADER,
  1436. stats.summary.format(label='Deleted data:', stats=stats),
  1437. str(cache),
  1438. DASHES, logger=logging.getLogger('borg.output.stats'))
  1439. return self.exit_code
  1440. @with_repository(fake=('tam', 'disable_tam'), invert_fake=True, manifest=False, exclusive=True)
  1441. def do_upgrade(self, args, repository, manifest=None, key=None):
  1442. """upgrade a repository from a previous version"""
  1443. if args.tam:
  1444. manifest, key = Manifest.load(repository, (Manifest.Operation.CHECK,), force_tam_not_required=args.force)
  1445. if not hasattr(key, 'change_passphrase'):
  1446. print('This repository is not encrypted, cannot enable TAM.')
  1447. return EXIT_ERROR
  1448. if not manifest.tam_verified or not manifest.config.get(b'tam_required', False):
  1449. # The standard archive listing doesn't include the archive ID like in borg 1.1.x
  1450. print('Manifest contents:')
  1451. for archive_info in manifest.archives.list(sort_by=['ts']):
  1452. print(format_archive(archive_info), '[%s]' % bin_to_hex(archive_info.id))
  1453. manifest.config[b'tam_required'] = True
  1454. manifest.write()
  1455. repository.commit()
  1456. if not key.tam_required:
  1457. key.tam_required = True
  1458. key.change_passphrase(key._passphrase)
  1459. print('Key updated')
  1460. if hasattr(key, 'find_key'):
  1461. print('Key location:', key.find_key())
  1462. if not tam_required(repository):
  1463. tam_file = tam_required_file(repository)
  1464. open(tam_file, 'w').close()
  1465. print('Updated security database')
  1466. elif args.disable_tam:
  1467. manifest, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK, force_tam_not_required=True)
  1468. if tam_required(repository):
  1469. os.unlink(tam_required_file(repository))
  1470. if key.tam_required:
  1471. key.tam_required = False
  1472. key.change_passphrase(key._passphrase)
  1473. print('Key updated')
  1474. if hasattr(key, 'find_key'):
  1475. print('Key location:', key.find_key())
  1476. manifest.config[b'tam_required'] = False
  1477. manifest.write()
  1478. repository.commit()
  1479. else:
  1480. # mainly for upgrades from Attic repositories,
  1481. # but also supports borg 0.xx -> 1.0 upgrade.
  1482. repo = AtticRepositoryUpgrader(args.location.path, create=False)
  1483. try:
  1484. repo.upgrade(args.dry_run, inplace=args.inplace, progress=args.progress)
  1485. except NotImplementedError as e:
  1486. print("warning: %s" % e)
  1487. repo = BorgRepositoryUpgrader(args.location.path, create=False)
  1488. try:
  1489. repo.upgrade(args.dry_run, inplace=args.inplace, progress=args.progress)
  1490. except NotImplementedError as e:
  1491. print("warning: %s" % e)
  1492. return self.exit_code
  1493. @with_repository(cache=True, exclusive=True, compatibility=(Manifest.Operation.CHECK,))
  1494. def do_recreate(self, args, repository, manifest, key, cache):
  1495. """Re-create archives"""
  1496. msg = ("recreate is an experimental feature.\n"
  1497. "Type 'YES' if you understand this and want to continue: ")
  1498. if not yes(msg, false_msg="Aborting.", truish=('YES',),
  1499. env_var_override='BORG_RECREATE_I_KNOW_WHAT_I_AM_DOING'):
  1500. return EXIT_ERROR
  1501. matcher = self.build_matcher(args.patterns, args.paths)
  1502. self.output_list = args.output_list
  1503. self.output_filter = args.output_filter
  1504. recompress = args.recompress != 'never'
  1505. always_recompress = args.recompress == 'always'
  1506. recreater = ArchiveRecreater(repository, manifest, key, cache, matcher,
  1507. exclude_caches=args.exclude_caches, exclude_if_present=args.exclude_if_present,
  1508. keep_exclude_tags=args.keep_exclude_tags, chunker_params=args.chunker_params,
  1509. compression=args.compression, recompress=recompress, always_recompress=always_recompress,
  1510. progress=args.progress, stats=args.stats,
  1511. file_status_printer=self.print_file_status,
  1512. checkpoint_interval=args.checkpoint_interval,
  1513. dry_run=args.dry_run)
  1514. if args.location.archive:
  1515. name = args.location.archive
  1516. target = replace_placeholders(args.target) if args.target else None
  1517. if recreater.is_temporary_archive(name):
  1518. self.print_error('Refusing to work on temporary archive of prior recreate: %s', name)
  1519. return self.exit_code
  1520. if not recreater.recreate(name, args.comment, target):
  1521. self.print_error('Nothing to do. Archive was not processed.\n'
  1522. 'Specify at least one pattern, PATH, --comment, re-compression or re-chunking option.')
  1523. else:
  1524. if args.target is not None:
  1525. self.print_error('--target: Need to specify single archive')
  1526. return self.exit_code
  1527. for archive in manifest.archives.list(sort_by=['ts']):
  1528. name = archive.name
  1529. if recreater.is_temporary_archive(name):
  1530. continue
  1531. print('Processing', name)
  1532. if not recreater.recreate(name, args.comment):
  1533. logger.info('Skipped archive %s: Nothing to do. Archive was not processed.', name)
  1534. if not args.dry_run:
  1535. manifest.write()
  1536. repository.commit()
  1537. cache.commit()
  1538. return self.exit_code
  1539. @with_repository(manifest=False, exclusive=True)
  1540. def do_with_lock(self, args, repository):
  1541. """run a user specified command with the repository lock held"""
  1542. # for a new server, this will immediately take an exclusive lock.
  1543. # to support old servers, that do not have "exclusive" arg in open()
  1544. # RPC API, we also do it the old way:
  1545. # re-write manifest to start a repository transaction - this causes a
  1546. # lock upgrade to exclusive for remote (and also for local) repositories.
  1547. # by using manifest=False in the decorator, we avoid having to require
  1548. # the encryption key (and can operate just with encrypted data).
  1549. data = repository.get(Manifest.MANIFEST_ID)
  1550. repository.put(Manifest.MANIFEST_ID, data)
  1551. # usually, a 0 byte (open for writing) segment file would be visible in the filesystem here.
  1552. # we write and close this file, to rather have a valid segment file on disk, before invoking the subprocess.
  1553. # we can only do this for local repositories (with .io), though:
  1554. if hasattr(repository, 'io'):
  1555. repository.io.close_segment()
  1556. env = prepare_subprocess_env(system=True)
  1557. try:
  1558. # we exit with the return code we get from the subprocess
  1559. return subprocess.call([args.command] + args.args, env=env)
  1560. finally:
  1561. # we need to commit the "no change" operation we did to the manifest
  1562. # because it created a new segment file in the repository. if we would
  1563. # roll back, the same file would be later used otherwise (for other content).
  1564. # that would be bad if somebody uses rsync with ignore-existing (or
  1565. # any other mechanism relying on existing segment data not changing).
  1566. # see issue #1867.
  1567. repository.commit()
  1568. @with_repository(exclusive=True, manifest=False)
  1569. def do_config(self, args, repository):
  1570. """get, set, and delete values in a repository or cache config file"""
  1571. def repo_validate(section, name, value=None, check_value=True):
  1572. if section not in ['repository', ]:
  1573. raise ValueError('Invalid section')
  1574. if name in ['segments_per_dir', 'max_segment_size', 'storage_quota', ]:
  1575. if check_value:
  1576. try:
  1577. int(value)
  1578. except ValueError:
  1579. raise ValueError('Invalid value') from None
  1580. if name == 'max_segment_size':
  1581. if int(value) >= MAX_SEGMENT_SIZE_LIMIT:
  1582. raise ValueError('Invalid value: max_segment_size >= %d' % MAX_SEGMENT_SIZE_LIMIT)
  1583. elif name in ['additional_free_space', ]:
  1584. if check_value:
  1585. try:
  1586. parse_file_size(value)
  1587. except ValueError:
  1588. raise ValueError('Invalid value') from None
  1589. elif name in ['append_only', ]:
  1590. if check_value and value not in ['0', '1']:
  1591. raise ValueError('Invalid value')
  1592. elif name in ['id', ]:
  1593. if check_value:
  1594. try:
  1595. bin_id = unhexlify(value)
  1596. except:
  1597. raise ValueError('Invalid value, must be 64 hex digits') from None
  1598. if len(bin_id) != 32:
  1599. raise ValueError('Invalid value, must be 64 hex digits')
  1600. else:
  1601. raise ValueError('Invalid name')
  1602. def cache_validate(section, name, value=None, check_value=True):
  1603. if section not in ['cache', ]:
  1604. raise ValueError('Invalid section')
  1605. if name in ['previous_location', ]:
  1606. if check_value:
  1607. Location(value)
  1608. else:
  1609. raise ValueError('Invalid name')
  1610. def list_config(config):
  1611. default_values = {
  1612. 'version': '1',
  1613. 'segments_per_dir': str(DEFAULT_SEGMENTS_PER_DIR),
  1614. 'max_segment_size': str(MAX_SEGMENT_SIZE_LIMIT),
  1615. 'additional_free_space': '0',
  1616. 'storage_quota': repository.storage_quota,
  1617. 'append_only': repository.append_only
  1618. }
  1619. print('[repository]')
  1620. for key in ['version', 'segments_per_dir', 'max_segment_size',
  1621. 'storage_quota', 'additional_free_space', 'append_only',
  1622. 'id']:
  1623. value = config.get('repository', key, fallback=False)
  1624. if value is None:
  1625. value = default_values.get(key)
  1626. if value is None:
  1627. raise Error('The repository config is missing the %s key which has no default value' % key)
  1628. print('%s = %s' % (key, value))
  1629. if not args.list:
  1630. try:
  1631. section, name = args.name.split('.')
  1632. except ValueError:
  1633. section = args.cache and "cache" or "repository"
  1634. name = args.name
  1635. if args.cache:
  1636. manifest, key = Manifest.load(repository, (Manifest.Operation.WRITE,))
  1637. assert_secure(repository, manifest, self.lock_wait)
  1638. cache = Cache(repository, key, manifest, lock_wait=self.lock_wait)
  1639. try:
  1640. if args.cache:
  1641. cache.cache_config.load()
  1642. config = cache.cache_config._config
  1643. save = cache.cache_config.save
  1644. validate = cache_validate
  1645. else:
  1646. config = repository.config
  1647. save = lambda: repository.save_config(repository.path, repository.config)
  1648. validate = repo_validate
  1649. if args.delete:
  1650. validate(section, name, check_value=False)
  1651. config.remove_option(section, name)
  1652. if len(config.options(section)) == 0:
  1653. config.remove_section(section)
  1654. save()
  1655. elif args.list:
  1656. list_config(config)
  1657. elif args.value:
  1658. validate(section, name, args.value)
  1659. if section not in config.sections():
  1660. config.add_section(section)
  1661. config.set(section, name, args.value)
  1662. save()
  1663. else:
  1664. try:
  1665. print(config.get(section, name))
  1666. except (configparser.NoOptionError, configparser.NoSectionError) as e:
  1667. print(e, file=sys.stderr)
  1668. return EXIT_WARNING
  1669. return EXIT_SUCCESS
  1670. finally:
  1671. if args.cache:
  1672. cache.close()
  1673. def do_debug_info(self, args):
  1674. """display system information for debugging / bug reports"""
  1675. print(sysinfo())
  1676. # Additional debug information
  1677. print('CRC implementation:', crc32.__name__)
  1678. print('Process ID:', get_process_id())
  1679. return EXIT_SUCCESS
  1680. @with_repository(compatibility=Manifest.NO_OPERATION_CHECK)
  1681. def do_debug_dump_archive_items(self, args, repository, manifest, key):
  1682. """dump (decrypted, decompressed) archive items metadata (not: data)"""
  1683. archive = Archive(repository, key, manifest, args.location.archive,
  1684. consider_part_files=args.consider_part_files)
  1685. for i, item_id in enumerate(archive.metadata.items):
  1686. data = key.decrypt(item_id, repository.get(item_id))
  1687. filename = '%06d_%s.items' % (i, bin_to_hex(item_id))
  1688. print('Dumping', filename)
  1689. with open(filename, 'wb') as fd:
  1690. fd.write(data)
  1691. print('Done.')
  1692. return EXIT_SUCCESS
  1693. @with_repository(compatibility=Manifest.NO_OPERATION_CHECK)
  1694. def do_debug_dump_archive(self, args, repository, manifest, key):
  1695. """dump decoded archive metadata (not: data)"""
  1696. try:
  1697. archive_meta_orig = manifest.archives.get_raw_dict()[safe_encode(args.location.archive)]
  1698. except KeyError:
  1699. raise Archive.DoesNotExist(args.location.archive)
  1700. indent = 4
  1701. def do_indent(d):
  1702. return textwrap.indent(json.dumps(d, indent=indent), prefix=' ' * indent)
  1703. def output(fd):
  1704. # this outputs megabytes of data for a modest sized archive, so some manual streaming json output
  1705. fd.write('{\n')
  1706. fd.write(' "_name": ' + json.dumps(args.location.archive) + ",\n")
  1707. fd.write(' "_manifest_entry":\n')
  1708. fd.write(do_indent(prepare_dump_dict(archive_meta_orig)))
  1709. fd.write(',\n')
  1710. data = key.decrypt(archive_meta_orig[b'id'], repository.get(archive_meta_orig[b'id']))
  1711. archive_org_dict = msgpack.unpackb(data, object_hook=StableDict, unicode_errors='surrogateescape')
  1712. fd.write(' "_meta":\n')
  1713. fd.write(do_indent(prepare_dump_dict(archive_org_dict)))
  1714. fd.write(',\n')
  1715. fd.write(' "_items": [\n')
  1716. unpacker = msgpack.Unpacker(use_list=False, object_hook=StableDict)
  1717. first = True
  1718. for item_id in archive_org_dict[b'items']:
  1719. data = key.decrypt(item_id, repository.get(item_id))
  1720. unpacker.feed(data)
  1721. for item in unpacker:
  1722. item = prepare_dump_dict(item)
  1723. if first:
  1724. first = False
  1725. else:
  1726. fd.write(',\n')
  1727. fd.write(do_indent(item))
  1728. fd.write('\n')
  1729. fd.write(' ]\n}\n')
  1730. with dash_open(args.path, 'w') as fd:
  1731. output(fd)
  1732. return EXIT_SUCCESS
  1733. @with_repository(compatibility=Manifest.NO_OPERATION_CHECK)
  1734. def do_debug_dump_manifest(self, args, repository, manifest, key):
  1735. """dump decoded repository manifest"""
  1736. data = key.decrypt(None, repository.get(manifest.MANIFEST_ID))
  1737. meta = prepare_dump_dict(msgpack.fallback.unpackb(data, object_hook=StableDict, unicode_errors='surrogateescape'))
  1738. with dash_open(args.path, 'w') as fd:
  1739. json.dump(meta, fd, indent=4)
  1740. return EXIT_SUCCESS
  1741. @with_repository(manifest=False)
  1742. def do_debug_dump_repo_objs(self, args, repository):
  1743. """dump (decrypted, decompressed) repo objects, repo index MUST be current/correct"""
  1744. from .crypto.key import key_factory
  1745. def decrypt_dump(i, id, cdata, tag=None, segment=None, offset=None):
  1746. if cdata is not None:
  1747. give_id = id if id != Manifest.MANIFEST_ID else None
  1748. data = key.decrypt(give_id, cdata)
  1749. else:
  1750. data = b''
  1751. tag_str = '' if tag is None else '_' + tag
  1752. segment_str = '_' + str(segment) if segment is not None else ''
  1753. offset_str = '_' + str(offset) if offset is not None else ''
  1754. id_str = '_' + bin_to_hex(id) if id is not None else ''
  1755. filename = '%08d%s%s%s%s.obj' % (i, segment_str, offset_str, tag_str, id_str)
  1756. print('Dumping', filename)
  1757. with open(filename, 'wb') as fd:
  1758. fd.write(data)
  1759. if args.ghost:
  1760. # dump ghosty stuff from segment files: not yet committed objects, deleted / superceded objects, commit tags
  1761. # set up the key without depending on a manifest obj
  1762. for id, cdata, tag, segment, offset in repository.scan_low_level():
  1763. if tag == TAG_PUT:
  1764. key = key_factory(repository, cdata)
  1765. break
  1766. i = 0
  1767. for id, cdata, tag, segment, offset in repository.scan_low_level():
  1768. if tag == TAG_PUT:
  1769. decrypt_dump(i, id, cdata, tag='put', segment=segment, offset=offset)
  1770. elif tag == TAG_DELETE:
  1771. decrypt_dump(i, id, None, tag='del', segment=segment, offset=offset)
  1772. elif tag == TAG_COMMIT:
  1773. decrypt_dump(i, None, None, tag='commit', segment=segment, offset=offset)
  1774. i += 1
  1775. else:
  1776. # set up the key without depending on a manifest obj
  1777. ids = repository.list(limit=1, marker=None)
  1778. cdata = repository.get(ids[0])
  1779. key = key_factory(repository, cdata)
  1780. marker = None
  1781. i = 0
  1782. while True:
  1783. result = repository.scan(limit=LIST_SCAN_LIMIT, marker=marker) # must use on-disk order scanning here
  1784. if not result:
  1785. break
  1786. marker = result[-1]
  1787. for id in result:
  1788. cdata = repository.get(id)
  1789. decrypt_dump(i, id, cdata)
  1790. i += 1
  1791. print('Done.')
  1792. return EXIT_SUCCESS
  1793. @with_repository(manifest=False)
  1794. def do_debug_search_repo_objs(self, args, repository):
  1795. """search for byte sequences in repo objects, repo index MUST be current/correct"""
  1796. context = 32
  1797. def print_finding(info, wanted, data, offset):
  1798. before = data[offset - context:offset]
  1799. after = data[offset + len(wanted):offset + len(wanted) + context]
  1800. print('%s: %s %s %s == %r %r %r' % (info, before.hex(), wanted.hex(), after.hex(),
  1801. before, wanted, after))
  1802. wanted = args.wanted
  1803. try:
  1804. if wanted.startswith('hex:'):
  1805. wanted = unhexlify(wanted[4:])
  1806. elif wanted.startswith('str:'):
  1807. wanted = wanted[4:].encode('utf-8')
  1808. else:
  1809. raise ValueError('unsupported search term')
  1810. except (ValueError, UnicodeEncodeError):
  1811. wanted = None
  1812. if not wanted:
  1813. self.print_error('search term needs to be hex:123abc or str:foobar style')
  1814. return EXIT_ERROR
  1815. from .crypto.key import key_factory
  1816. # set up the key without depending on a manifest obj
  1817. ids = repository.list(limit=1, marker=None)
  1818. cdata = repository.get(ids[0])
  1819. key = key_factory(repository, cdata)
  1820. marker = None
  1821. last_data = b''
  1822. last_id = None
  1823. i = 0
  1824. while True:
  1825. result = repository.scan(limit=LIST_SCAN_LIMIT, marker=marker) # must use on-disk order scanning here
  1826. if not result:
  1827. break
  1828. marker = result[-1]
  1829. for id in result:
  1830. cdata = repository.get(id)
  1831. give_id = id if id != Manifest.MANIFEST_ID else None
  1832. data = key.decrypt(give_id, cdata)
  1833. # try to locate wanted sequence crossing the border of last_data and data
  1834. boundary_data = last_data[-(len(wanted) - 1):] + data[:len(wanted) - 1]
  1835. if wanted in boundary_data:
  1836. boundary_data = last_data[-(len(wanted) - 1 + context):] + data[:len(wanted) - 1 + context]
  1837. offset = boundary_data.find(wanted)
  1838. info = '%d %s | %s' % (i, last_id.hex(), id.hex())
  1839. print_finding(info, wanted, boundary_data, offset)
  1840. # try to locate wanted sequence in data
  1841. count = data.count(wanted)
  1842. if count:
  1843. offset = data.find(wanted) # only determine first occurance's offset
  1844. info = "%d %s #%d" % (i, id.hex(), count)
  1845. print_finding(info, wanted, data, offset)
  1846. last_id, last_data = id, data
  1847. i += 1
  1848. if i % 10000 == 0:
  1849. print('%d objects processed.' % i)
  1850. print('Done.')
  1851. return EXIT_SUCCESS
  1852. @with_repository(manifest=False)
  1853. def do_debug_get_obj(self, args, repository):
  1854. """get object contents from the repository and write it into file"""
  1855. hex_id = args.id
  1856. try:
  1857. id = unhexlify(hex_id)
  1858. except ValueError:
  1859. print("object id %s is invalid." % hex_id)
  1860. else:
  1861. try:
  1862. data = repository.get(id)
  1863. except Repository.ObjectNotFound:
  1864. print("object %s not found." % hex_id)
  1865. else:
  1866. with open(args.path, "wb") as f:
  1867. f.write(data)
  1868. print("object %s fetched." % hex_id)
  1869. return EXIT_SUCCESS
  1870. @with_repository(manifest=False, exclusive=True)
  1871. def do_debug_put_obj(self, args, repository):
  1872. """put file(s) contents into the repository"""
  1873. for path in args.paths:
  1874. with open(path, "rb") as f:
  1875. data = f.read()
  1876. h = hashlib.sha256(data) # XXX hardcoded
  1877. repository.put(h.digest(), data)
  1878. print("object %s put." % h.hexdigest())
  1879. repository.commit()
  1880. return EXIT_SUCCESS
  1881. @with_repository(manifest=False, exclusive=True)
  1882. def do_debug_delete_obj(self, args, repository):
  1883. """delete the objects with the given IDs from the repo"""
  1884. modified = False
  1885. for hex_id in args.ids:
  1886. try:
  1887. id = unhexlify(hex_id)
  1888. except ValueError:
  1889. print("object id %s is invalid." % hex_id)
  1890. else:
  1891. try:
  1892. repository.delete(id)
  1893. modified = True
  1894. print("object %s deleted." % hex_id)
  1895. except Repository.ObjectNotFound:
  1896. print("object %s not found." % hex_id)
  1897. if modified:
  1898. repository.commit()
  1899. print('Done.')
  1900. return EXIT_SUCCESS
  1901. @with_repository(manifest=False, exclusive=True, cache=True, compatibility=Manifest.NO_OPERATION_CHECK)
  1902. def do_debug_refcount_obj(self, args, repository, manifest, key, cache):
  1903. """display refcounts for the objects with the given IDs"""
  1904. for hex_id in args.ids:
  1905. try:
  1906. id = unhexlify(hex_id)
  1907. except ValueError:
  1908. print("object id %s is invalid." % hex_id)
  1909. else:
  1910. try:
  1911. refcount = cache.chunks[id][0]
  1912. print("object %s has %d referrers [info from chunks cache]." % (hex_id, refcount))
  1913. except KeyError:
  1914. print("object %s not found [info from chunks cache]." % hex_id)
  1915. return EXIT_SUCCESS
  1916. def do_debug_convert_profile(self, args):
  1917. """convert Borg profile to Python profile"""
  1918. import marshal
  1919. with args.output, args.input:
  1920. marshal.dump(msgpack.unpack(args.input, use_list=False, encoding='utf-8'), args.output)
  1921. return EXIT_SUCCESS
  1922. @with_repository(lock=False, manifest=False)
  1923. def do_break_lock(self, args, repository):
  1924. """Break the repository lock (e.g. in case it was left by a dead borg."""
  1925. repository.break_lock()
  1926. Cache.break_lock(repository)
  1927. return self.exit_code
  1928. helptext = collections.OrderedDict()
  1929. helptext['patterns'] = textwrap.dedent('''
  1930. The path/filenames used as input for the pattern matching start from the
  1931. currently active recursion root. You usually give the recursion root(s)
  1932. when invoking borg and these can be either relative or absolute paths.
  1933. So, when you give `relative/` as root, the paths going into the matcher
  1934. will look like `relative/.../file.ext`. When you give `/absolute/` as root,
  1935. they will look like `/absolute/.../file.ext`. This is meant when we talk
  1936. about "full path" below.
  1937. File patterns support these styles: fnmatch, shell, regular expressions,
  1938. path prefixes and path full-matches. By default, fnmatch is used for
  1939. ``--exclude`` patterns and shell-style is used for the experimental ``--pattern``
  1940. option.
  1941. If followed by a colon (':') the first two characters of a pattern are used as a
  1942. style selector. Explicit style selection is necessary when a
  1943. non-default style is desired or when the desired pattern starts with
  1944. two alphanumeric characters followed by a colon (i.e. `aa:something/*`).
  1945. `Fnmatch <https://docs.python.org/3/library/fnmatch.html>`_, selector `fm:`
  1946. This is the default style for ``--exclude`` and ``--exclude-from``.
  1947. These patterns use a variant of shell pattern syntax, with '\*' matching
  1948. any number of characters, '?' matching any single character, '[...]'
  1949. matching any single character specified, including ranges, and '[!...]'
  1950. matching any character not specified. For the purpose of these patterns,
  1951. the path separator ('\\' for Windows and '/' on other systems) is not
  1952. treated specially. Wrap meta-characters in brackets for a literal
  1953. match (i.e. `[?]` to match the literal character `?`). For a path
  1954. to match a pattern, the full path must match, or it must match
  1955. from the start of the full path to just before a path separator. Except
  1956. for the root path, paths will never end in the path separator when
  1957. matching is attempted. Thus, if a given pattern ends in a path
  1958. separator, a '\*' is appended before matching is attempted.
  1959. Shell-style patterns, selector `sh:`
  1960. This is the default style for ``--pattern`` and ``--patterns-from``.
  1961. Like fnmatch patterns these are similar to shell patterns. The difference
  1962. is that the pattern may include `**/` for matching zero or more directory
  1963. levels, `*` for matching zero or more arbitrary characters with the
  1964. exception of any path separator.
  1965. Regular expressions, selector `re:`
  1966. Regular expressions similar to those found in Perl are supported. Unlike
  1967. shell patterns regular expressions are not required to match the full
  1968. path and any substring match is sufficient. It is strongly recommended to
  1969. anchor patterns to the start ('^'), to the end ('$') or both. Path
  1970. separators ('\\' for Windows and '/' on other systems) in paths are
  1971. always normalized to a forward slash ('/') before applying a pattern. The
  1972. regular expression syntax is described in the `Python documentation for
  1973. the re module <https://docs.python.org/3/library/re.html>`_.
  1974. Path prefix, selector `pp:`
  1975. This pattern style is useful to match whole sub-directories. The pattern
  1976. `pp:root/somedir` matches `root/somedir` and everything therein.
  1977. Path full-match, selector `pf:`
  1978. This pattern style is (only) useful to match full paths.
  1979. This is kind of a pseudo pattern as it can not have any variable or
  1980. unspecified parts - the full path must be given.
  1981. `pf:root/file.ext` matches `root/file.txt` only.
  1982. Implementation note: this is implemented via very time-efficient O(1)
  1983. hashtable lookups (this means you can have huge amounts of such patterns
  1984. without impacting performance much).
  1985. Due to that, this kind of pattern does not respect any context or order.
  1986. If you use such a pattern to include a file, it will always be included
  1987. (if the directory recursion encounters it).
  1988. Other include/exclude patterns that would normally match will be ignored.
  1989. Same logic applies for exclude.
  1990. .. note::
  1991. `re:`, `sh:` and `fm:` patterns are all implemented on top of the Python SRE
  1992. engine. It is very easy to formulate patterns for each of these types which
  1993. requires an inordinate amount of time to match paths. If untrusted users
  1994. are able to supply patterns, ensure they cannot supply `re:` patterns.
  1995. Further, ensure that `sh:` and `fm:` patterns only contain a handful of
  1996. wildcards at most.
  1997. Exclusions can be passed via the command line option ``--exclude``. When used
  1998. from within a shell the patterns should be quoted to protect them from
  1999. expansion.
  2000. The ``--exclude-from`` option permits loading exclusion patterns from a text
  2001. file with one pattern per line. Lines empty or starting with the number sign
  2002. ('#') after removing whitespace on both ends are ignored. The optional style
  2003. selector prefix is also supported for patterns loaded from a file. Due to
  2004. whitespace removal paths with whitespace at the beginning or end can only be
  2005. excluded using regular expressions.
  2006. Examples::
  2007. # Exclude '/home/user/file.o' but not '/home/user/file.odt':
  2008. $ borg create -e '*.o' backup /
  2009. # Exclude '/home/user/junk' and '/home/user/subdir/junk' but
  2010. # not '/home/user/importantjunk' or '/etc/junk':
  2011. $ borg create -e '/home/*/junk' backup /
  2012. # Exclude the contents of '/home/user/cache' but not the directory itself:
  2013. $ borg create -e /home/user/cache/ backup /
  2014. # The file '/home/user/cache/important' is *not* backed up:
  2015. $ borg create -e /home/user/cache/ backup / /home/user/cache/important
  2016. # The contents of directories in '/home' are not backed up when their name
  2017. # ends in '.tmp'
  2018. $ borg create --exclude 're:^/home/[^/]+\.tmp/' backup /
  2019. # Load exclusions from file
  2020. $ cat >exclude.txt <<EOF
  2021. # Comment line
  2022. /home/*/junk
  2023. *.tmp
  2024. fm:aa:something/*
  2025. re:^/home/[^/]\.tmp/
  2026. sh:/home/*/.thumbnails
  2027. EOF
  2028. $ borg create --exclude-from exclude.txt backup /
  2029. .. container:: experimental
  2030. A more general and easier to use way to define filename matching patterns exists
  2031. with the experimental ``--pattern`` and ``--patterns-from`` options. Using these, you
  2032. may specify the backup roots (starting points) and patterns for inclusion/exclusion.
  2033. A root path starts with the prefix `R`, followed by a path (a plain path, not a
  2034. file pattern). An include rule starts with the prefix +, an exclude rule starts
  2035. with the prefix -, an exclude-norecurse rule starts with !, all followed by a pattern.
  2036. Inclusion patterns are useful to include paths that are contained in an excluded
  2037. path. The first matching pattern is used so if an include pattern matches before
  2038. an exclude pattern, the file is backed up. If an exclude-norecurse pattern matches
  2039. a directory, it won't recurse into it and won't discover any potential matches for
  2040. include rules below that directory.
  2041. Note that the default pattern style for ``--pattern`` and ``--patterns-from`` is
  2042. shell style (`sh:`), so those patterns behave similar to rsync include/exclude
  2043. patterns. The pattern style can be set via the `P` prefix.
  2044. Patterns (``--pattern``) and excludes (``--exclude``) from the command line are
  2045. considered first (in the order of appearance). Then patterns from ``--patterns-from``
  2046. are added. Exclusion patterns from ``--exclude-from`` files are appended last.
  2047. Examples::
  2048. # backup pics, but not the ones from 2018, except the good ones:
  2049. # note: using = is essential to avoid cmdline argument parsing issues.
  2050. borg create --pattern=+pics/2018/good --pattern=-pics/2018 repo::arch pics
  2051. # use a file with patterns:
  2052. borg create --patterns-from patterns.lst repo::arch
  2053. The patterns.lst file could look like that::
  2054. # "sh:" pattern style is the default, so the following line is not needed:
  2055. P sh
  2056. R /
  2057. # can be rebuild
  2058. - /home/*/.cache
  2059. # they're downloads for a reason
  2060. - /home/*/Downloads
  2061. # susan is a nice person
  2062. # include susans home
  2063. + /home/susan
  2064. # don't backup the other home directories
  2065. - /home/*\n\n''')
  2066. helptext['placeholders'] = textwrap.dedent('''
  2067. Repository (or Archive) URLs, ``--prefix`` and ``--remote-path`` values support these
  2068. placeholders:
  2069. {hostname}
  2070. The (short) hostname of the machine.
  2071. {fqdn}
  2072. The full name of the machine.
  2073. {reverse-fqdn}
  2074. The full name of the machine in reverse domain name notation.
  2075. {now}
  2076. The current local date and time, by default in ISO-8601 format.
  2077. You can also supply your own `format string <https://docs.python.org/3.4/library/datetime.html#strftime-and-strptime-behavior>`_, e.g. {now:%Y-%m-%d_%H:%M:%S}
  2078. {utcnow}
  2079. The current UTC date and time, by default in ISO-8601 format.
  2080. You can also supply your own `format string <https://docs.python.org/3.4/library/datetime.html#strftime-and-strptime-behavior>`_, e.g. {utcnow:%Y-%m-%d_%H:%M:%S}
  2081. {user}
  2082. The user name (or UID, if no name is available) of the user running borg.
  2083. {pid}
  2084. The current process ID.
  2085. {borgversion}
  2086. The version of borg, e.g.: 1.0.8rc1
  2087. {borgmajor}
  2088. The version of borg, only the major version, e.g.: 1
  2089. {borgminor}
  2090. The version of borg, only major and minor version, e.g.: 1.0
  2091. {borgpatch}
  2092. The version of borg, only major, minor and patch version, e.g.: 1.0.8
  2093. If literal curly braces need to be used, double them for escaping::
  2094. borg create /path/to/repo::{{literal_text}}
  2095. Examples::
  2096. borg create /path/to/repo::{hostname}-{user}-{utcnow} ...
  2097. borg create /path/to/repo::{hostname}-{now:%Y-%m-%d_%H:%M:%S} ...
  2098. borg prune --prefix '{hostname}-' ...
  2099. .. note::
  2100. systemd uses a difficult, non-standard syntax for command lines in unit files (refer to
  2101. the `systemd.unit(5)` manual page).
  2102. When invoking borg from unit files, pay particular attention to escaping,
  2103. especially when using the now/utcnow placeholders, since systemd performs its own
  2104. %-based variable replacement even in quoted text. To avoid interference from systemd,
  2105. double all percent signs (``{hostname}-{now:%Y-%m-%d_%H:%M:%S}``
  2106. becomes ``{hostname}-{now:%%Y-%%m-%%d_%%H:%%M:%%S}``).\n\n''')
  2107. helptext['compression'] = textwrap.dedent('''
  2108. It is no problem to mix different compression methods in one repo,
  2109. deduplication is done on the source data chunks (not on the compressed
  2110. or encrypted data).
  2111. If some specific chunk was once compressed and stored into the repo, creating
  2112. another backup that also uses this chunk will not change the stored chunk.
  2113. So if you use different compression specs for the backups, whichever stores a
  2114. chunk first determines its compression. See also borg recreate.
  2115. Compression is lz4 by default. If you want something else, you have to specify what you want.
  2116. Valid compression specifiers are:
  2117. none
  2118. Do not compress.
  2119. lz4
  2120. Use lz4 compression. Very high speed, very low compression. (default)
  2121. zstd[,L]
  2122. Use zstd ("zstandard") compression, a modern wide-range algorithm.
  2123. If you do not explicitely give the compression level L (ranging from 1
  2124. to 22), it will use level 3.
  2125. Archives compressed with zstd are not compatible with borg < 1.1.4.
  2126. zlib[,L]
  2127. Use zlib ("gz") compression. Medium speed, medium compression.
  2128. If you do not explicitely give the compression level L (ranging from 0
  2129. to 9), it will use level 6.
  2130. Giving level 0 (means "no compression", but still has zlib protocol
  2131. overhead) is usually pointless, you better use "none" compression.
  2132. lzma[,L]
  2133. Use lzma ("xz") compression. Low speed, high compression.
  2134. If you do not explicitely give the compression level L (ranging from 0
  2135. to 9), it will use level 6.
  2136. Giving levels above 6 is pointless and counterproductive because it does
  2137. not compress better due to the buffer size used by borg - but it wastes
  2138. lots of CPU cycles and RAM.
  2139. auto,C[,L]
  2140. Use a built-in heuristic to decide per chunk whether to compress or not.
  2141. The heuristic tries with lz4 whether the data is compressible.
  2142. For incompressible data, it will not use compression (uses "none").
  2143. For compressible data, it uses the given C[,L] compression - with C[,L]
  2144. being any valid compression specifier.
  2145. Examples::
  2146. borg create --compression lz4 REPO::ARCHIVE data
  2147. borg create --compression zstd REPO::ARCHIVE data
  2148. borg create --compression zstd,10 REPO::ARCHIVE data
  2149. borg create --compression zlib REPO::ARCHIVE data
  2150. borg create --compression zlib,1 REPO::ARCHIVE data
  2151. borg create --compression auto,lzma,6 REPO::ARCHIVE data
  2152. borg create --compression auto,lzma ...\n\n''')
  2153. def do_help(self, parser, commands, args):
  2154. if not args.topic:
  2155. parser.print_help()
  2156. elif args.topic in self.helptext:
  2157. print(rst_to_terminal(self.helptext[args.topic]))
  2158. elif args.topic in commands:
  2159. if args.epilog_only:
  2160. print(commands[args.topic].epilog)
  2161. elif args.usage_only:
  2162. commands[args.topic].epilog = None
  2163. commands[args.topic].print_help()
  2164. else:
  2165. commands[args.topic].print_help()
  2166. else:
  2167. msg_lines = []
  2168. msg_lines += ['No help available on %s.' % args.topic]
  2169. msg_lines += ['Try one of the following:']
  2170. msg_lines += [' Commands: %s' % ', '.join(sorted(commands.keys()))]
  2171. msg_lines += [' Topics: %s' % ', '.join(sorted(self.helptext.keys()))]
  2172. parser.error('\n'.join(msg_lines))
  2173. return self.exit_code
  2174. def do_subcommand_help(self, parser, args):
  2175. """display infos about subcommand"""
  2176. parser.print_help()
  2177. return EXIT_SUCCESS
  2178. do_maincommand_help = do_subcommand_help
  2179. def preprocess_args(self, args):
  2180. deprecations = [
  2181. # ('--old', '--new' or None, 'Warning: "--old" has been deprecated. Use "--new" instead.'),
  2182. ('--list-format', '--format', 'Warning: "--list-format" has been deprecated. Use "--format" instead.'),
  2183. ('--keep-tag-files', '--keep-exclude-tags', 'Warning: "--keep-tag-files" has been deprecated. Use "--keep-exclude-tags" instead.'),
  2184. ('--ignore-inode', None, 'Warning: "--ignore-inode" has been deprecated. Use "--files-cache=ctime,size" or "...=mtime,size" instead.'),
  2185. ('--no-files-cache', None, 'Warning: "--no-files-cache" has been deprecated. Use "--files-cache=disabled" instead.'),
  2186. ]
  2187. for i, arg in enumerate(args[:]):
  2188. for old_name, new_name, warning in deprecations:
  2189. if arg.startswith(old_name):
  2190. if new_name is not None:
  2191. args[i] = arg.replace(old_name, new_name)
  2192. print(warning, file=sys.stderr)
  2193. return args
  2194. class CommonOptions:
  2195. """
  2196. Support class to allow specifying common options directly after the top-level command.
  2197. Normally options can only be specified on the parser defining them, which means
  2198. that generally speaking *all* options go after all sub-commands. This is annoying
  2199. for common options in scripts, e.g. --remote-path or logging options.
  2200. This class allows adding the same set of options to both the top-level parser
  2201. and the final sub-command parsers (but not intermediary sub-commands, at least for now).
  2202. It does so by giving every option's target name ("dest") a suffix indicating its level
  2203. -- no two options in the parser hierarchy can have the same target --
  2204. then, after parsing the command line, multiple definitions are resolved.
  2205. Defaults are handled by only setting them on the top-level parser and setting
  2206. a sentinel object in all sub-parsers, which then allows to discern which parser
  2207. supplied the option.
  2208. """
  2209. def __init__(self, define_common_options, suffix_precedence):
  2210. """
  2211. *define_common_options* should be a callable taking one argument, which
  2212. will be a argparse.Parser.add_argument-like function.
  2213. *define_common_options* will be called multiple times, and should call
  2214. the passed function to define common options exactly the same way each time.
  2215. *suffix_precedence* should be a tuple of the suffixes that will be used.
  2216. It is ordered from lowest precedence to highest precedence:
  2217. An option specified on the parser belonging to index 0 is overridden if the
  2218. same option is specified on any parser with a higher index.
  2219. """
  2220. self.define_common_options = define_common_options
  2221. self.suffix_precedence = suffix_precedence
  2222. # Maps suffixes to sets of target names.
  2223. # E.g. common_options["_subcommand"] = {..., "log_level", ...}
  2224. self.common_options = dict()
  2225. # Set of options with the 'append' action.
  2226. self.append_options = set()
  2227. # This is the sentinel object that replaces all default values in parsers
  2228. # below the top-level parser.
  2229. self.default_sentinel = object()
  2230. def add_common_group(self, parser, suffix, provide_defaults=False):
  2231. """
  2232. Add common options to *parser*.
  2233. *provide_defaults* must only be True exactly once in a parser hierarchy,
  2234. at the top level, and False on all lower levels. The default is chosen
  2235. accordingly.
  2236. *suffix* indicates the suffix to use internally. It also indicates
  2237. which precedence the *parser* has for common options. See *suffix_precedence*
  2238. of __init__.
  2239. """
  2240. assert suffix in self.suffix_precedence
  2241. def add_argument(*args, **kwargs):
  2242. if 'dest' in kwargs:
  2243. kwargs.setdefault('action', 'store')
  2244. assert kwargs['action'] in ('help', 'store_const', 'store_true', 'store_false', 'store', 'append')
  2245. is_append = kwargs['action'] == 'append'
  2246. if is_append:
  2247. self.append_options.add(kwargs['dest'])
  2248. assert kwargs['default'] == [], 'The default is explicitly constructed as an empty list in resolve()'
  2249. else:
  2250. self.common_options.setdefault(suffix, set()).add(kwargs['dest'])
  2251. kwargs['dest'] += suffix
  2252. if not provide_defaults:
  2253. # Interpolate help now, in case the %(default)d (or so) is mentioned,
  2254. # to avoid producing incorrect help output.
  2255. # Assumption: Interpolated output can safely be interpolated again,
  2256. # which should always be the case.
  2257. # Note: We control all inputs.
  2258. kwargs['help'] = kwargs['help'] % kwargs
  2259. if not is_append:
  2260. kwargs['default'] = self.default_sentinel
  2261. common_group.add_argument(*args, **kwargs)
  2262. common_group = parser.add_argument_group('Common options')
  2263. self.define_common_options(add_argument)
  2264. def resolve(self, args: argparse.Namespace): # Namespace has "in" but otherwise is not like a dict.
  2265. """
  2266. Resolve the multiple definitions of each common option to the final value.
  2267. """
  2268. for suffix in self.suffix_precedence:
  2269. # From highest level to lowest level, so the "most-specific" option wins, e.g.
  2270. # "borg --debug create --info" shall result in --info being effective.
  2271. for dest in self.common_options.get(suffix, []):
  2272. # map_from is this suffix' option name, e.g. log_level_subcommand
  2273. # map_to is the target name, e.g. log_level
  2274. map_from = dest + suffix
  2275. map_to = dest
  2276. # Retrieve value; depending on the action it may not exist, but usually does
  2277. # (store_const/store_true/store_false), either because the action implied a default
  2278. # or a default is explicitly supplied.
  2279. # Note that defaults on lower levels are replaced with default_sentinel.
  2280. # Only the top level has defaults.
  2281. value = getattr(args, map_from, self.default_sentinel)
  2282. if value is not self.default_sentinel:
  2283. # value was indeed specified on this level. Transfer value to target,
  2284. # and un-clobber the args (for tidiness - you *cannot* use the suffixed
  2285. # names for other purposes, obviously).
  2286. setattr(args, map_to, value)
  2287. try:
  2288. delattr(args, map_from)
  2289. except AttributeError:
  2290. pass
  2291. # Options with an "append" action need some special treatment. Instead of
  2292. # overriding values, all specified values are merged together.
  2293. for dest in self.append_options:
  2294. option_value = []
  2295. for suffix in self.suffix_precedence:
  2296. # Find values of this suffix, if any, and add them to the final list
  2297. extend_from = dest + suffix
  2298. if extend_from in args:
  2299. values = getattr(args, extend_from)
  2300. delattr(args, extend_from)
  2301. option_value.extend(values)
  2302. setattr(args, dest, option_value)
  2303. def build_parser(self):
  2304. # You can use :ref:`xyz` in the following usage pages. However, for plain-text view,
  2305. # e.g. through "borg ... --help", define a substitution for the reference here.
  2306. # It will replace the entire :ref:`foo` verbatim.
  2307. rst_plain_text_references = {
  2308. 'a_status_oddity': '"I am seeing ‘A’ (added) status for a unchanged file!?"',
  2309. }
  2310. def process_epilog(epilog):
  2311. epilog = textwrap.dedent(epilog).splitlines()
  2312. try:
  2313. mode = borg.doc_mode
  2314. except AttributeError:
  2315. mode = 'command-line'
  2316. if mode in ('command-line', 'build_usage'):
  2317. epilog = [line for line in epilog if not line.startswith('.. man')]
  2318. epilog = '\n'.join(epilog)
  2319. if mode == 'command-line':
  2320. epilog = rst_to_terminal(epilog, rst_plain_text_references)
  2321. return epilog
  2322. def define_common_options(add_common_option):
  2323. add_common_option('-h', '--help', action='help', help='show this help message and exit')
  2324. add_common_option('--critical', dest='log_level',
  2325. action='store_const', const='critical', default='warning',
  2326. help='work on log level CRITICAL')
  2327. add_common_option('--error', dest='log_level',
  2328. action='store_const', const='error', default='warning',
  2329. help='work on log level ERROR')
  2330. add_common_option('--warning', dest='log_level',
  2331. action='store_const', const='warning', default='warning',
  2332. help='work on log level WARNING (default)')
  2333. add_common_option('--info', '-v', '--verbose', dest='log_level',
  2334. action='store_const', const='info', default='warning',
  2335. help='work on log level INFO')
  2336. add_common_option('--debug', dest='log_level',
  2337. action='store_const', const='debug', default='warning',
  2338. help='enable debug output, work on log level DEBUG')
  2339. add_common_option('--debug-topic', metavar='TOPIC', dest='debug_topics', action='append', default=[],
  2340. help='enable TOPIC debugging (can be specified multiple times). '
  2341. 'The logger path is borg.debug.<TOPIC> if TOPIC is not fully qualified.')
  2342. add_common_option('-p', '--progress', dest='progress', action='store_true',
  2343. help='show progress information')
  2344. add_common_option('--log-json', dest='log_json', action='store_true',
  2345. help='Output one JSON object per log line instead of formatted text.')
  2346. add_common_option('--lock-wait', metavar='SECONDS', dest='lock_wait', type=int, default=1,
  2347. help='wait at most SECONDS for acquiring a repository/cache lock (default: %(default)d).')
  2348. add_common_option('--show-version', dest='show_version', action='store_true',
  2349. help='show/log the borg version')
  2350. add_common_option('--show-rc', dest='show_rc', action='store_true',
  2351. help='show/log the return code (rc)')
  2352. add_common_option('--umask', metavar='M', dest='umask', type=lambda s: int(s, 8), default=UMASK_DEFAULT,
  2353. help='set umask to M (local and remote, default: %(default)04o)')
  2354. add_common_option('--remote-path', metavar='PATH', dest='remote_path',
  2355. help='use PATH as borg executable on the remote (default: "borg")')
  2356. add_common_option('--remote-ratelimit', metavar='RATE', dest='remote_ratelimit', type=int,
  2357. help='set remote network upload rate limit in kiByte/s (default: 0=unlimited)')
  2358. add_common_option('--consider-part-files', dest='consider_part_files', action='store_true',
  2359. help='treat part files like normal files (e.g. to list/extract them)')
  2360. add_common_option('--debug-profile', metavar='FILE', dest='debug_profile', default=None,
  2361. help='Write execution profile in Borg format into FILE. For local use a Python-'
  2362. 'compatible file can be generated by suffixing FILE with ".pyprof".')
  2363. def define_exclude_and_patterns(add_option, *, tag_files=False, strip_components=False):
  2364. add_option('-e', '--exclude', metavar='PATTERN', dest='patterns',
  2365. type=parse_exclude_pattern, action='append',
  2366. help='exclude paths matching PATTERN')
  2367. add_option('--exclude-from', metavar='EXCLUDEFILE', action=ArgparseExcludeFileAction,
  2368. help='read exclude patterns from EXCLUDEFILE, one per line')
  2369. add_option('--pattern', metavar='PATTERN', action=ArgparsePatternAction,
  2370. help='experimental: include/exclude paths matching PATTERN')
  2371. add_option('--patterns-from', metavar='PATTERNFILE', action=ArgparsePatternFileAction,
  2372. help='experimental: read include/exclude patterns from PATTERNFILE, one per line')
  2373. if tag_files:
  2374. add_option('--exclude-caches', dest='exclude_caches', action='store_true',
  2375. help='exclude directories that contain a CACHEDIR.TAG file '
  2376. '(http://www.brynosaurus.com/cachedir/spec.html)')
  2377. add_option('--exclude-if-present', metavar='NAME', dest='exclude_if_present',
  2378. action='append', type=str,
  2379. help='exclude directories that are tagged by containing a filesystem object with '
  2380. 'the given NAME')
  2381. add_option('--keep-exclude-tags', '--keep-tag-files', dest='keep_exclude_tags',
  2382. action='store_true',
  2383. help='if tag objects are specified with ``--exclude-if-present``, '
  2384. 'don\'t omit the tag objects themselves from the backup archive')
  2385. if strip_components:
  2386. add_option('--strip-components', metavar='NUMBER', dest='strip_components', type=int, default=0,
  2387. help='Remove the specified number of leading path elements. '
  2388. 'Paths with fewer elements will be silently skipped.')
  2389. def define_exclusion_group(subparser, **kwargs):
  2390. exclude_group = subparser.add_argument_group('Exclusion options')
  2391. define_exclude_and_patterns(exclude_group.add_argument, **kwargs)
  2392. return exclude_group
  2393. def define_archive_filters_group(subparser, *, sort_by=True, first_last=True):
  2394. filters_group = subparser.add_argument_group('Archive filters',
  2395. 'Archive filters can be applied to repository targets.')
  2396. group = filters_group.add_mutually_exclusive_group()
  2397. group.add_argument('-P', '--prefix', metavar='PREFIX', dest='prefix', type=PrefixSpec, default='',
  2398. help='only consider archive names starting with this prefix.')
  2399. group.add_argument('-a', '--glob-archives', metavar='GLOB', dest='glob_archives', default=None,
  2400. help='only consider archive names matching the glob. '
  2401. 'sh: rules apply, see "borg help patterns". '
  2402. '``--prefix`` and ``--glob-archives`` are mutually exclusive.')
  2403. if sort_by:
  2404. sort_by_default = 'timestamp'
  2405. filters_group.add_argument('--sort-by', metavar='KEYS', dest='sort_by',
  2406. type=SortBySpec, default=sort_by_default,
  2407. help='Comma-separated list of sorting keys; valid keys are: {}; default is: {}'
  2408. .format(', '.join(HUMAN_SORT_KEYS), sort_by_default))
  2409. if first_last:
  2410. group = filters_group.add_mutually_exclusive_group()
  2411. group.add_argument('--first', metavar='N', dest='first', default=0, type=positive_int_validator,
  2412. help='consider first N archives after other filters were applied')
  2413. group.add_argument('--last', metavar='N', dest='last', default=0, type=positive_int_validator,
  2414. help='consider last N archives after other filters were applied')
  2415. parser = argparse.ArgumentParser(prog=self.prog, description='Borg - Deduplicated Backups',
  2416. add_help=False)
  2417. # paths and patterns must have an empty list as default everywhere
  2418. parser.set_defaults(fallback2_func=functools.partial(self.do_maincommand_help, parser),
  2419. paths=[], patterns=[])
  2420. parser.common_options = self.CommonOptions(define_common_options,
  2421. suffix_precedence=('_maincommand', '_midcommand', '_subcommand'))
  2422. parser.add_argument('-V', '--version', action='version', version='%(prog)s ' + __version__,
  2423. help='show version number and exit')
  2424. parser.common_options.add_common_group(parser, '_maincommand', provide_defaults=True)
  2425. common_parser = argparse.ArgumentParser(add_help=False, prog=self.prog)
  2426. common_parser.set_defaults(paths=[], patterns=[])
  2427. parser.common_options.add_common_group(common_parser, '_subcommand')
  2428. mid_common_parser = argparse.ArgumentParser(add_help=False, prog=self.prog)
  2429. mid_common_parser.set_defaults(paths=[], patterns=[])
  2430. parser.common_options.add_common_group(mid_common_parser, '_midcommand')
  2431. mount_epilog = process_epilog("""
  2432. This command mounts an archive as a FUSE filesystem. This can be useful for
  2433. browsing an archive or restoring individual files. Unless the ``--foreground``
  2434. option is given the command will run in the background until the filesystem
  2435. is ``umounted``.
  2436. The command ``borgfs`` provides a wrapper for ``borg mount``. This can also be
  2437. used in fstab entries:
  2438. ``/path/to/repo /mnt/point fuse.borgfs defaults,noauto 0 0``
  2439. To allow a regular user to use fstab entries, add the ``user`` option:
  2440. ``/path/to/repo /mnt/point fuse.borgfs defaults,noauto,user 0 0``
  2441. For mount options, see the fuse(8) manual page. Additional mount options
  2442. supported by borg:
  2443. - versions: when used with a repository mount, this gives a merged, versioned
  2444. view of the files in the archives. EXPERIMENTAL, layout may change in future.
  2445. - allow_damaged_files: by default damaged files (where missing chunks were
  2446. replaced with runs of zeros by borg check ``--repair``) are not readable and
  2447. return EIO (I/O error). Set this option to read such files.
  2448. The BORG_MOUNT_DATA_CACHE_ENTRIES environment variable is meant for advanced users
  2449. to tweak the performance. It sets the number of cached data chunks; additional
  2450. memory usage can be up to ~8 MiB times this number. The default is the number
  2451. of CPU cores.
  2452. When the daemonized process receives a signal or crashes, it does not unmount.
  2453. Unmounting in these cases could cause an active rsync or similar process
  2454. to unintentionally delete data.
  2455. When running in the foreground ^C/SIGINT unmounts cleanly, but other
  2456. signals or crashes do not.
  2457. """)
  2458. if parser.prog == 'borgfs':
  2459. parser.description = self.do_mount.__doc__
  2460. parser.epilog = mount_epilog
  2461. parser.formatter_class = argparse.RawDescriptionHelpFormatter
  2462. parser.help = 'mount repository'
  2463. subparser = parser
  2464. else:
  2465. subparsers = parser.add_subparsers(title='required arguments', metavar='<command>')
  2466. subparser = subparsers.add_parser('mount', parents=[common_parser], add_help=False,
  2467. description=self.do_mount.__doc__,
  2468. epilog=mount_epilog,
  2469. formatter_class=argparse.RawDescriptionHelpFormatter,
  2470. help='mount repository')
  2471. subparser.set_defaults(func=self.do_mount)
  2472. subparser.add_argument('location', metavar='REPOSITORY_OR_ARCHIVE', type=location_validator(),
  2473. help='repository/archive to mount')
  2474. subparser.add_argument('mountpoint', metavar='MOUNTPOINT', type=str,
  2475. help='where to mount filesystem')
  2476. subparser.add_argument('-f', '--foreground', dest='foreground',
  2477. action='store_true',
  2478. help='stay in foreground, do not daemonize')
  2479. subparser.add_argument('-o', dest='options', type=str,
  2480. help='Extra mount options')
  2481. define_archive_filters_group(subparser)
  2482. subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
  2483. help='paths to extract; patterns are supported')
  2484. define_exclusion_group(subparser, strip_components=True)
  2485. if parser.prog == 'borgfs':
  2486. return parser
  2487. serve_epilog = process_epilog("""
  2488. This command starts a repository server process. This command is usually not used manually.
  2489. """)
  2490. subparser = subparsers.add_parser('serve', parents=[common_parser], add_help=False,
  2491. description=self.do_serve.__doc__, epilog=serve_epilog,
  2492. formatter_class=argparse.RawDescriptionHelpFormatter,
  2493. help='start repository server process')
  2494. subparser.set_defaults(func=self.do_serve)
  2495. subparser.add_argument('--restrict-to-path', metavar='PATH', dest='restrict_to_paths', action='append',
  2496. help='restrict repository access to PATH. '
  2497. 'Can be specified multiple times to allow the client access to several directories. '
  2498. 'Access to all sub-directories is granted implicitly; PATH doesn\'t need to directly point to a repository.')
  2499. subparser.add_argument('--restrict-to-repository', metavar='PATH', dest='restrict_to_repositories', action='append',
  2500. help='restrict repository access. Only the repository located at PATH '
  2501. '(no sub-directories are considered) is accessible. '
  2502. 'Can be specified multiple times to allow the client access to several repositories. '
  2503. 'Unlike ``--restrict-to-path`` sub-directories are not accessible; '
  2504. 'PATH needs to directly point at a repository location. '
  2505. 'PATH may be an empty directory or the last element of PATH may not exist, in which case '
  2506. 'the client may initialize a repository there.')
  2507. subparser.add_argument('--append-only', dest='append_only', action='store_true',
  2508. help='only allow appending to repository segment files')
  2509. subparser.add_argument('--storage-quota', metavar='QUOTA', dest='storage_quota',
  2510. type=parse_storage_quota, default=None,
  2511. help='Override storage quota of the repository (e.g. 5G, 1.5T). '
  2512. 'When a new repository is initialized, sets the storage quota on the new '
  2513. 'repository as well. Default: no quota.')
  2514. init_epilog = process_epilog("""
  2515. This command initializes an empty repository. A repository is a filesystem
  2516. directory containing the deduplicated data from zero or more archives.
  2517. Encryption can be enabled at repository init time. It cannot be changed later.
  2518. It is not recommended to work without encryption. Repository encryption protects
  2519. you e.g. against the case that an attacker has access to your backup repository.
  2520. But be careful with the key / the passphrase:
  2521. If you want "passphrase-only" security, use one of the repokey modes. The
  2522. key will be stored inside the repository (in its "config" file). In above
  2523. mentioned attack scenario, the attacker will have the key (but not the
  2524. passphrase).
  2525. If you want "passphrase and having-the-key" security, use one of the keyfile
  2526. modes. The key will be stored in your home directory (in .config/borg/keys).
  2527. In the attack scenario, the attacker who has just access to your repo won't
  2528. have the key (and also not the passphrase).
  2529. Make a backup copy of the key file (keyfile mode) or repo config file
  2530. (repokey mode) and keep it at a safe place, so you still have the key in
  2531. case it gets corrupted or lost. Also keep the passphrase at a safe place.
  2532. The backup that is encrypted with that key won't help you with that, of course.
  2533. Make sure you use a good passphrase. Not too short, not too simple. The real
  2534. encryption / decryption key is encrypted with / locked by your passphrase.
  2535. If an attacker gets your key, he can't unlock and use it without knowing the
  2536. passphrase.
  2537. Be careful with special or non-ascii characters in your passphrase:
  2538. - Borg processes the passphrase as unicode (and encodes it as utf-8),
  2539. so it does not have problems dealing with even the strangest characters.
  2540. - BUT: that does not necessarily apply to your OS / VM / keyboard configuration.
  2541. So better use a long passphrase made from simple ascii chars than one that
  2542. includes non-ascii stuff or characters that are hard/impossible to enter on
  2543. a different keyboard layout.
  2544. You can change your passphrase for existing repos at any time, it won't affect
  2545. the encryption/decryption key or other secrets.
  2546. Encryption modes
  2547. ++++++++++++++++
  2548. .. nanorst: inline-fill
  2549. +----------+---------------+------------------------+--------------------------+
  2550. | Hash/MAC | Not encrypted | Not encrypted, | Encrypted (AEAD w/ AES) |
  2551. | | no auth | but authenticated | and authenticated |
  2552. +----------+---------------+------------------------+--------------------------+
  2553. | SHA-256 | none | `authenticated` | repokey |
  2554. | | | | keyfile |
  2555. +----------+---------------+------------------------+--------------------------+
  2556. | BLAKE2b | n/a | `authenticated-blake2` | `repokey-blake2` |
  2557. | | | | `keyfile-blake2` |
  2558. +----------+---------------+------------------------+--------------------------+
  2559. .. nanorst: inline-replace
  2560. `Marked modes` are new in Borg 1.1 and are not backwards-compatible with Borg 1.0.x.
  2561. On modern Intel/AMD CPUs (except very cheap ones), AES is usually
  2562. hardware-accelerated.
  2563. BLAKE2b is faster than SHA256 on Intel/AMD 64-bit CPUs
  2564. (except AMD Ryzen and future CPUs with SHA extensions),
  2565. which makes `authenticated-blake2` faster than `none` and `authenticated`.
  2566. On modern ARM CPUs, NEON provides hardware acceleration for SHA256 making it faster
  2567. than BLAKE2b-256 there. NEON accelerates AES as well.
  2568. Hardware acceleration is always used automatically when available.
  2569. `repokey` and `keyfile` use AES-CTR-256 for encryption and HMAC-SHA256 for
  2570. authentication in an encrypt-then-MAC (EtM) construction. The chunk ID hash
  2571. is HMAC-SHA256 as well (with a separate key).
  2572. These modes are compatible with Borg 1.0.x.
  2573. `repokey-blake2` and `keyfile-blake2` are also authenticated encryption modes,
  2574. but use BLAKE2b-256 instead of HMAC-SHA256 for authentication. The chunk ID
  2575. hash is a keyed BLAKE2b-256 hash.
  2576. These modes are new and *not* compatible with Borg 1.0.x.
  2577. `authenticated` mode uses no encryption, but authenticates repository contents
  2578. through the same HMAC-SHA256 hash as the `repokey` and `keyfile` modes (it uses it
  2579. as the chunk ID hash). The key is stored like `repokey`.
  2580. This mode is new and *not* compatible with Borg 1.0.x.
  2581. `authenticated-blake2` is like `authenticated`, but uses the keyed BLAKE2b-256 hash
  2582. from the other blake2 modes.
  2583. This mode is new and *not* compatible with Borg 1.0.x.
  2584. `none` mode uses no encryption and no authentication. It uses SHA256 as chunk
  2585. ID hash. Not recommended, rather consider using an authenticated or
  2586. authenticated/encrypted mode. This mode has possible denial-of-service issues
  2587. when running ``borg create`` on contents controlled by an attacker.
  2588. Use it only for new repositories where no encryption is wanted **and** when compatibility
  2589. with 1.0.x is important. If compatibility with 1.0.x is not important, use
  2590. `authenticated-blake2` or `authenticated` instead.
  2591. This mode is compatible with Borg 1.0.x.
  2592. """)
  2593. subparser = subparsers.add_parser('init', parents=[common_parser], add_help=False,
  2594. description=self.do_init.__doc__, epilog=init_epilog,
  2595. formatter_class=argparse.RawDescriptionHelpFormatter,
  2596. help='initialize empty repository')
  2597. subparser.set_defaults(func=self.do_init)
  2598. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  2599. type=location_validator(archive=False),
  2600. help='repository to create')
  2601. subparser.add_argument('-e', '--encryption', metavar='MODE', dest='encryption', required=True,
  2602. choices=key_argument_names(),
  2603. help='select encryption key mode **(required)**')
  2604. subparser.add_argument('--append-only', dest='append_only', action='store_true',
  2605. help='create an append-only mode repository')
  2606. subparser.add_argument('--storage-quota', metavar='QUOTA', dest='storage_quota', default=None,
  2607. type=parse_storage_quota,
  2608. help='Set storage quota of the new repository (e.g. 5G, 1.5T). Default: no quota.')
  2609. check_epilog = process_epilog("""
  2610. The check command verifies the consistency of a repository and the corresponding archives.
  2611. First, the underlying repository data files are checked:
  2612. - For all segments the segment magic (header) is checked
  2613. - For all objects stored in the segments, all metadata (e.g. crc and size) and
  2614. all data is read. The read data is checked by size and CRC. Bit rot and other
  2615. types of accidental damage can be detected this way.
  2616. - If we are in repair mode and a integrity error is detected for a segment,
  2617. we try to recover as many objects from the segment as possible.
  2618. - In repair mode, it makes sure that the index is consistent with the data
  2619. stored in the segments.
  2620. - If you use a remote repo server via ssh:, the repo check is executed on the
  2621. repo server without causing significant network traffic.
  2622. - The repository check can be skipped using the ``--archives-only`` option.
  2623. Second, the consistency and correctness of the archive metadata is verified:
  2624. - Is the repo manifest present? If not, it is rebuilt from archive metadata
  2625. chunks (this requires reading and decrypting of all metadata and data).
  2626. - Check if archive metadata chunk is present. if not, remove archive from
  2627. manifest.
  2628. - For all files (items) in the archive, for all chunks referenced by these
  2629. files, check if chunk is present.
  2630. If a chunk is not present and we are in repair mode, replace it with a same-size
  2631. replacement chunk of zeros.
  2632. If a previously lost chunk reappears (e.g. via a later backup) and we are in
  2633. repair mode, the all-zero replacement chunk will be replaced by the correct chunk.
  2634. This requires reading of archive and file metadata, but not data.
  2635. - If we are in repair mode and we checked all the archives: delete orphaned
  2636. chunks from the repo.
  2637. - if you use a remote repo server via ssh:, the archive check is executed on
  2638. the client machine (because if encryption is enabled, the checks will require
  2639. decryption and this is always done client-side, because key access will be
  2640. required).
  2641. - The archive checks can be time consuming, they can be skipped using the
  2642. ``--repository-only`` option.
  2643. The ``--verify-data`` option will perform a full integrity verification (as opposed to
  2644. checking the CRC32 of the segment) of data, which means reading the data from the
  2645. repository, decrypting and decompressing it. This is a cryptographic verification,
  2646. which will detect (accidental) corruption. For encrypted repositories it is
  2647. tamper-resistant as well, unless the attacker has access to the keys.
  2648. It is also very slow.
  2649. """)
  2650. subparser = subparsers.add_parser('check', parents=[common_parser], add_help=False,
  2651. description=self.do_check.__doc__,
  2652. epilog=check_epilog,
  2653. formatter_class=argparse.RawDescriptionHelpFormatter,
  2654. help='verify repository')
  2655. subparser.set_defaults(func=self.do_check)
  2656. subparser.add_argument('location', metavar='REPOSITORY_OR_ARCHIVE', nargs='?', default='',
  2657. type=location_validator(),
  2658. help='repository or archive to check consistency of')
  2659. subparser.add_argument('--repository-only', dest='repo_only', action='store_true',
  2660. help='only perform repository checks')
  2661. subparser.add_argument('--archives-only', dest='archives_only', action='store_true',
  2662. help='only perform archives checks')
  2663. subparser.add_argument('--verify-data', dest='verify_data', action='store_true',
  2664. help='perform cryptographic archive data integrity verification '
  2665. '(conflicts with ``--repository-only``)')
  2666. subparser.add_argument('--repair', dest='repair', action='store_true',
  2667. help='attempt to repair any inconsistencies found')
  2668. subparser.add_argument('--save-space', dest='save_space', action='store_true',
  2669. help='work slower, but using less space')
  2670. define_archive_filters_group(subparser)
  2671. subparser = subparsers.add_parser('key', parents=[mid_common_parser], add_help=False,
  2672. description="Manage a keyfile or repokey of a repository",
  2673. epilog="",
  2674. formatter_class=argparse.RawDescriptionHelpFormatter,
  2675. help='manage repository key')
  2676. key_parsers = subparser.add_subparsers(title='required arguments', metavar='<command>')
  2677. subparser.set_defaults(fallback_func=functools.partial(self.do_subcommand_help, subparser))
  2678. key_export_epilog = process_epilog("""
  2679. If repository encryption is used, the repository is inaccessible
  2680. without the key. This command allows to backup this essential key.
  2681. Note that the backup produced does not include the passphrase itself
  2682. (i.e. the exported key stays encrypted). In order to regain access to a
  2683. repository, one needs both the exported key and the original passphrase.
  2684. There are two backup formats. The normal backup format is suitable for
  2685. digital storage as a file. The ``--paper`` backup format is optimized
  2686. for printing and typing in while importing, with per line checks to
  2687. reduce problems with manual input.
  2688. For repositories using keyfile encryption the key is saved locally
  2689. on the system that is capable of doing backups. To guard against loss
  2690. of this key, the key needs to be backed up independently of the main
  2691. data backup.
  2692. For repositories using the repokey encryption the key is saved in the
  2693. repository in the config file. A backup is thus not strictly needed,
  2694. but guards against the repository becoming inaccessible if the file
  2695. is damaged for some reason.
  2696. """)
  2697. subparser = key_parsers.add_parser('export', parents=[common_parser], add_help=False,
  2698. description=self.do_key_export.__doc__,
  2699. epilog=key_export_epilog,
  2700. formatter_class=argparse.RawDescriptionHelpFormatter,
  2701. help='export repository key for backup')
  2702. subparser.set_defaults(func=self.do_key_export)
  2703. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  2704. type=location_validator(archive=False))
  2705. subparser.add_argument('path', metavar='PATH', nargs='?', type=str,
  2706. help='where to store the backup')
  2707. subparser.add_argument('--paper', dest='paper', action='store_true',
  2708. help='Create an export suitable for printing and later type-in')
  2709. subparser.add_argument('--qr-html', dest='qr', action='store_true',
  2710. help='Create an html file suitable for printing and later type-in or qr scan')
  2711. key_import_epilog = process_epilog("""
  2712. This command allows to restore a key previously backed up with the
  2713. export command.
  2714. If the ``--paper`` option is given, the import will be an interactive
  2715. process in which each line is checked for plausibility before
  2716. proceeding to the next line. For this format PATH must not be given.
  2717. """)
  2718. subparser = key_parsers.add_parser('import', parents=[common_parser], add_help=False,
  2719. description=self.do_key_import.__doc__,
  2720. epilog=key_import_epilog,
  2721. formatter_class=argparse.RawDescriptionHelpFormatter,
  2722. help='import repository key from backup')
  2723. subparser.set_defaults(func=self.do_key_import)
  2724. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  2725. type=location_validator(archive=False))
  2726. subparser.add_argument('path', metavar='PATH', nargs='?', type=str,
  2727. help='path to the backup (\'-\' to read from stdin)')
  2728. subparser.add_argument('--paper', dest='paper', action='store_true',
  2729. help='interactively import from a backup done with ``--paper``')
  2730. change_passphrase_epilog = process_epilog("""
  2731. The key files used for repository encryption are optionally passphrase
  2732. protected. This command can be used to change this passphrase.
  2733. Please note that this command only changes the passphrase, but not any
  2734. secret protected by it (like e.g. encryption/MAC keys or chunker seed).
  2735. Thus, changing the passphrase after passphrase and borg key got compromised
  2736. does not protect future (nor past) backups to the same repository.
  2737. """)
  2738. subparser = key_parsers.add_parser('change-passphrase', parents=[common_parser], add_help=False,
  2739. description=self.do_change_passphrase.__doc__,
  2740. epilog=change_passphrase_epilog,
  2741. formatter_class=argparse.RawDescriptionHelpFormatter,
  2742. help='change repository passphrase')
  2743. subparser.set_defaults(func=self.do_change_passphrase)
  2744. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  2745. type=location_validator(archive=False))
  2746. # Borg 1.0 alias for change passphrase (without the "key" subcommand)
  2747. subparser = subparsers.add_parser('change-passphrase', parents=[common_parser], add_help=False,
  2748. description=self.do_change_passphrase.__doc__,
  2749. epilog=change_passphrase_epilog,
  2750. formatter_class=argparse.RawDescriptionHelpFormatter,
  2751. help='change repository passphrase')
  2752. subparser.set_defaults(func=self.do_change_passphrase_deprecated)
  2753. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  2754. type=location_validator(archive=False))
  2755. migrate_to_repokey_epilog = process_epilog("""
  2756. This command migrates a repository from passphrase mode (removed in Borg 1.0)
  2757. to repokey mode.
  2758. You will be first asked for the repository passphrase (to open it in passphrase
  2759. mode). This is the same passphrase as you used to use for this repo before 1.0.
  2760. It will then derive the different secrets from this passphrase.
  2761. Then you will be asked for a new passphrase (twice, for safety). This
  2762. passphrase will be used to protect the repokey (which contains these same
  2763. secrets in encrypted form). You may use the same passphrase as you used to
  2764. use, but you may also use a different one.
  2765. After migrating to repokey mode, you can change the passphrase at any time.
  2766. But please note: the secrets will always stay the same and they could always
  2767. be derived from your (old) passphrase-mode passphrase.
  2768. """)
  2769. subparser = key_parsers.add_parser('migrate-to-repokey', parents=[common_parser], add_help=False,
  2770. description=self.do_migrate_to_repokey.__doc__,
  2771. epilog=migrate_to_repokey_epilog,
  2772. formatter_class=argparse.RawDescriptionHelpFormatter,
  2773. help='migrate passphrase-mode repository to repokey')
  2774. subparser.set_defaults(func=self.do_migrate_to_repokey)
  2775. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  2776. type=location_validator(archive=False))
  2777. create_epilog = process_epilog("""
  2778. This command creates a backup archive containing all files found while recursively
  2779. traversing all paths specified. Paths are added to the archive as they are given,
  2780. that means if relative paths are desired, the command has to be run from the correct
  2781. directory.
  2782. When giving '-' as path, borg will read data from standard input and create a
  2783. file 'stdin' in the created archive from that data.
  2784. The archive will consume almost no disk space for files or parts of files that
  2785. have already been stored in other archives.
  2786. The archive name needs to be unique. It must not end in '.checkpoint' or
  2787. '.checkpoint.N' (with N being a number), because these names are used for
  2788. checkpoints and treated in special ways.
  2789. In the archive name, you may use the following placeholders:
  2790. {now}, {utcnow}, {fqdn}, {hostname}, {user} and some others.
  2791. Backup speed is increased by not reprocessing files that are already part of
  2792. existing archives and weren't modified. The detection of unmodified files is
  2793. done by comparing multiple file metadata values with previous values kept in
  2794. the files cache.
  2795. This comparison can operate in different modes as given by ``--files-cache``:
  2796. - ctime,size,inode (default)
  2797. - mtime,size,inode (default behaviour of borg versions older than 1.1.0rc4)
  2798. - ctime,size (ignore the inode number)
  2799. - mtime,size (ignore the inode number)
  2800. - rechunk,ctime (all files are considered modified - rechunk, cache ctime)
  2801. - rechunk,mtime (all files are considered modified - rechunk, cache mtime)
  2802. - disabled (disable the files cache, all files considered modified - rechunk)
  2803. inode number: better safety, but often unstable on network filesystems
  2804. Normally, detecting file modifications will take inode information into
  2805. consideration to improve the reliability of file change detection.
  2806. This is problematic for files located on sshfs and similar network file
  2807. systems which do not provide stable inode numbers, such files will always
  2808. be considered modified. You can use modes without `inode` in this case to
  2809. improve performance, but reliability of change detection might be reduced.
  2810. ctime vs. mtime: safety vs. speed
  2811. - ctime is a rather safe way to detect changes to a file (metadata and contents)
  2812. as it can not be set from userspace. But, a metadata-only change will already
  2813. update the ctime, so there might be some unnecessary chunking/hashing even
  2814. without content changes. Some filesystems do not support ctime (change time).
  2815. - mtime usually works and only updates if file contents were changed. But mtime
  2816. can be arbitrarily set from userspace, e.g. to set mtime back to the same value
  2817. it had before a content change happened. This can be used maliciously as well as
  2818. well-meant, but in both cases mtime based cache modes can be problematic.
  2819. The mount points of filesystems or filesystem snapshots should be the same for every
  2820. creation of a new archive to ensure fast operation. This is because the file cache that
  2821. is used to determine changed files quickly uses absolute filenames.
  2822. If this is not possible, consider creating a bind mount to a stable location.
  2823. The ``--progress`` option shows (from left to right) Original, Compressed and Deduplicated
  2824. (O, C and D, respectively), then the Number of files (N) processed so far, followed by
  2825. the currently processed path.
  2826. When using ``--stats``, you will get some statistics about how much data was
  2827. added - the "This Archive" deduplicated size there is most interesting as that is
  2828. how much your repository will grow. Please note that the "All archives" stats refer to
  2829. the state after creation. Also, the ``--stats`` and ``--dry-run`` options are mutually
  2830. exclusive because the data is not actually compressed and deduplicated during a dry run.
  2831. See the output of the "borg help patterns" command for more help on exclude patterns.
  2832. See the output of the "borg help placeholders" command for more help on placeholders.
  2833. .. man NOTES
  2834. The ``--exclude`` patterns are not like tar. In tar ``--exclude`` .bundler/gems will
  2835. exclude foo/.bundler/gems. In borg it will not, you need to use ``--exclude``
  2836. '\*/.bundler/gems' to get the same effect. See ``borg help patterns`` for
  2837. more information.
  2838. In addition to using ``--exclude`` patterns, it is possible to use
  2839. ``--exclude-if-present`` to specify the name of a filesystem object (e.g. a file
  2840. or folder name) which, when contained within another folder, will prevent the
  2841. containing folder from being backed up. By default, the containing folder and
  2842. all of its contents will be omitted from the backup. If, however, you wish to
  2843. only include the objects specified by ``--exclude-if-present`` in your backup,
  2844. and not include any other contents of the containing folder, this can be enabled
  2845. through using the ``--keep-exclude-tags`` option.
  2846. Item flags
  2847. ++++++++++
  2848. ``--list`` outputs a list of all files, directories and other
  2849. file system items it considered (no matter whether they had content changes
  2850. or not). For each item, it prefixes a single-letter flag that indicates type
  2851. and/or status of the item.
  2852. If you are interested only in a subset of that output, you can give e.g.
  2853. ``--filter=AME`` and it will only show regular files with A, M or E status (see
  2854. below).
  2855. A uppercase character represents the status of a regular file relative to the
  2856. "files" cache (not relative to the repo -- this is an issue if the files cache
  2857. is not used). Metadata is stored in any case and for 'A' and 'M' also new data
  2858. chunks are stored. For 'U' all data chunks refer to already existing chunks.
  2859. - 'A' = regular file, added (see also :ref:`a_status_oddity` in the FAQ)
  2860. - 'M' = regular file, modified
  2861. - 'U' = regular file, unchanged
  2862. - 'E' = regular file, an error happened while accessing/reading *this* file
  2863. A lowercase character means a file type other than a regular file,
  2864. borg usually just stores their metadata:
  2865. - 'd' = directory
  2866. - 'b' = block device
  2867. - 'c' = char device
  2868. - 'h' = regular file, hardlink (to already seen inodes)
  2869. - 's' = symlink
  2870. - 'f' = fifo
  2871. Other flags used include:
  2872. - 'i' = backup data was read from standard input (stdin)
  2873. - '-' = dry run, item was *not* backed up
  2874. - 'x' = excluded, item was *not* backed up
  2875. - '?' = missing status code (if you see this, please file a bug report!)
  2876. """)
  2877. subparser = subparsers.add_parser('create', parents=[common_parser], add_help=False,
  2878. description=self.do_create.__doc__,
  2879. epilog=create_epilog,
  2880. formatter_class=argparse.RawDescriptionHelpFormatter,
  2881. help='create backup')
  2882. subparser.set_defaults(func=self.do_create)
  2883. dryrun_group = subparser.add_mutually_exclusive_group()
  2884. dryrun_group.add_argument('-n', '--dry-run', dest='dry_run', action='store_true',
  2885. help='do not create a backup archive')
  2886. dryrun_group.add_argument('-s', '--stats', dest='stats', action='store_true',
  2887. help='print statistics for the created archive')
  2888. subparser.add_argument('--list', dest='output_list', action='store_true',
  2889. help='output verbose list of items (files, dirs, ...)')
  2890. subparser.add_argument('--filter', metavar='STATUSCHARS', dest='output_filter',
  2891. help='only display items with the given status characters (see description)')
  2892. subparser.add_argument('--json', action='store_true',
  2893. help='output stats as JSON. Implies ``--stats``.')
  2894. subparser.add_argument('--no-cache-sync', dest='no_cache_sync', action='store_true',
  2895. help='experimental: do not synchronize the cache. Implies not using the files cache.')
  2896. subparser.add_argument('--no-files-cache', dest='cache_files', action='store_false',
  2897. help='do not load/update the file metadata cache used to detect unchanged files')
  2898. subparser.add_argument('--stdin-name', metavar='NAME', dest='stdin_name', default='stdin',
  2899. help='use NAME in archive for stdin data (default: "stdin")')
  2900. exclude_group = define_exclusion_group(subparser, tag_files=True)
  2901. exclude_group.add_argument('--exclude-nodump', dest='exclude_nodump', action='store_true',
  2902. help='exclude files flagged NODUMP')
  2903. fs_group = subparser.add_argument_group('Filesystem options')
  2904. fs_group.add_argument('-x', '--one-file-system', dest='one_file_system', action='store_true',
  2905. help='stay in the same file system and do not store mount points of other file systems')
  2906. fs_group.add_argument('--numeric-owner', dest='numeric_owner', action='store_true',
  2907. help='only store numeric user and group identifiers')
  2908. fs_group.add_argument('--noatime', dest='noatime', action='store_true',
  2909. help='do not store atime into archive')
  2910. fs_group.add_argument('--noctime', dest='noctime', action='store_true',
  2911. help='do not store ctime into archive')
  2912. fs_group.add_argument('--nobirthtime', dest='nobirthtime', action='store_true',
  2913. help='do not store birthtime (creation date) into archive')
  2914. fs_group.add_argument('--nobsdflags', dest='nobsdflags', action='store_true',
  2915. help='do not read and store bsdflags (e.g. NODUMP, IMMUTABLE) into archive')
  2916. fs_group.add_argument('--ignore-inode', dest='ignore_inode', action='store_true',
  2917. help='ignore inode data in the file metadata cache used to detect unchanged files.')
  2918. fs_group.add_argument('--files-cache', metavar='MODE', dest='files_cache_mode',
  2919. type=FilesCacheMode, default=DEFAULT_FILES_CACHE_MODE_UI,
  2920. help='operate files cache in MODE. default: %s' % DEFAULT_FILES_CACHE_MODE_UI)
  2921. fs_group.add_argument('--read-special', dest='read_special', action='store_true',
  2922. help='open and read block and char device files as well as FIFOs as if they were '
  2923. 'regular files. Also follows symlinks pointing to these kinds of files.')
  2924. archive_group = subparser.add_argument_group('Archive options')
  2925. archive_group.add_argument('--comment', dest='comment', metavar='COMMENT', default='',
  2926. help='add a comment text to the archive')
  2927. archive_group.add_argument('--timestamp', metavar='TIMESTAMP', dest='timestamp',
  2928. type=timestamp, default=None,
  2929. help='manually specify the archive creation date/time (UTC, yyyy-mm-ddThh:mm:ss format). '
  2930. 'Alternatively, give a reference file/directory.')
  2931. archive_group.add_argument('-c', '--checkpoint-interval', metavar='SECONDS', dest='checkpoint_interval',
  2932. type=int, default=1800,
  2933. help='write checkpoint every SECONDS seconds (Default: 1800)')
  2934. archive_group.add_argument('--chunker-params', metavar='PARAMS', dest='chunker_params',
  2935. type=ChunkerParams, default=CHUNKER_PARAMS,
  2936. help='specify the chunker parameters (CHUNK_MIN_EXP, CHUNK_MAX_EXP, '
  2937. 'HASH_MASK_BITS, HASH_WINDOW_SIZE). default: %d,%d,%d,%d' % CHUNKER_PARAMS)
  2938. archive_group.add_argument('-C', '--compression', metavar='COMPRESSION', dest='compression',
  2939. type=CompressionSpec, default=CompressionSpec('lz4'),
  2940. help='select compression algorithm, see the output of the '
  2941. '"borg help compression" command for details.')
  2942. subparser.add_argument('location', metavar='ARCHIVE',
  2943. type=location_validator(archive=True),
  2944. help='name of archive to create (must be also a valid directory name)')
  2945. subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
  2946. help='paths to archive')
  2947. extract_epilog = process_epilog("""
  2948. This command extracts the contents of an archive. By default the entire
  2949. archive is extracted but a subset of files and directories can be selected
  2950. by passing a list of ``PATHs`` as arguments. The file selection can further
  2951. be restricted by using the ``--exclude`` option.
  2952. See the output of the "borg help patterns" command for more help on exclude patterns.
  2953. By using ``--dry-run``, you can do all extraction steps except actually writing the
  2954. output data: reading metadata and data chunks from the repo, checking the hash/hmac,
  2955. decrypting, decompressing.
  2956. ``--progress`` can be slower than no progress display, since it makes one additional
  2957. pass over the archive metadata.
  2958. .. note::
  2959. Currently, extract always writes into the current working directory ("."),
  2960. so make sure you ``cd`` to the right place before calling ``borg extract``.
  2961. """)
  2962. subparser = subparsers.add_parser('extract', parents=[common_parser], add_help=False,
  2963. description=self.do_extract.__doc__,
  2964. epilog=extract_epilog,
  2965. formatter_class=argparse.RawDescriptionHelpFormatter,
  2966. help='extract archive contents')
  2967. subparser.set_defaults(func=self.do_extract)
  2968. subparser.add_argument('--list', dest='output_list', action='store_true',
  2969. help='output verbose list of items (files, dirs, ...)')
  2970. subparser.add_argument('-n', '--dry-run', dest='dry_run', action='store_true',
  2971. help='do not actually change any files')
  2972. subparser.add_argument('--numeric-owner', dest='numeric_owner', action='store_true',
  2973. help='only obey numeric user and group identifiers')
  2974. subparser.add_argument('--nobsdflags', dest='nobsdflags', action='store_true',
  2975. help='do not extract/set bsdflags (e.g. NODUMP, IMMUTABLE)')
  2976. subparser.add_argument('--stdout', dest='stdout', action='store_true',
  2977. help='write all extracted data to stdout')
  2978. subparser.add_argument('--sparse', dest='sparse', action='store_true',
  2979. help='create holes in output sparse file from all-zero chunks')
  2980. subparser.add_argument('location', metavar='ARCHIVE',
  2981. type=location_validator(archive=True),
  2982. help='archive to extract')
  2983. subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
  2984. help='paths to extract; patterns are supported')
  2985. define_exclusion_group(subparser, strip_components=True)
  2986. export_tar_epilog = process_epilog("""
  2987. This command creates a tarball from an archive.
  2988. When giving '-' as the output FILE, Borg will write a tar stream to standard output.
  2989. By default (``--tar-filter=auto``) Borg will detect whether the FILE should be compressed
  2990. based on its file extension and pipe the tarball through an appropriate filter
  2991. before writing it to FILE:
  2992. - .tar.gz: gzip
  2993. - .tar.bz2: bzip2
  2994. - .tar.xz: xz
  2995. Alternatively a ``--tar-filter`` program may be explicitly specified. It should
  2996. read the uncompressed tar stream from stdin and write a compressed/filtered
  2997. tar stream to stdout.
  2998. The generated tarball uses the GNU tar format.
  2999. export-tar is a lossy conversion:
  3000. BSD flags, ACLs, extended attributes (xattrs), atime and ctime are not exported.
  3001. Timestamp resolution is limited to whole seconds, not the nanosecond resolution
  3002. otherwise supported by Borg.
  3003. A ``--sparse`` option (as found in borg extract) is not supported.
  3004. By default the entire archive is extracted but a subset of files and directories
  3005. can be selected by passing a list of ``PATHs`` as arguments.
  3006. The file selection can further be restricted by using the ``--exclude`` option.
  3007. See the output of the "borg help patterns" command for more help on exclude patterns.
  3008. ``--progress`` can be slower than no progress display, since it makes one additional
  3009. pass over the archive metadata.
  3010. """)
  3011. subparser = subparsers.add_parser('export-tar', parents=[common_parser], add_help=False,
  3012. description=self.do_export_tar.__doc__,
  3013. epilog=export_tar_epilog,
  3014. formatter_class=argparse.RawDescriptionHelpFormatter,
  3015. help='create tarball from archive')
  3016. subparser.set_defaults(func=self.do_export_tar)
  3017. subparser.add_argument('--tar-filter', dest='tar_filter', default='auto',
  3018. help='filter program to pipe data through')
  3019. subparser.add_argument('--list', dest='output_list', action='store_true',
  3020. help='output verbose list of items (files, dirs, ...)')
  3021. subparser.add_argument('location', metavar='ARCHIVE',
  3022. type=location_validator(archive=True),
  3023. help='archive to export')
  3024. subparser.add_argument('tarfile', metavar='FILE',
  3025. help='output tar file. "-" to write to stdout instead.')
  3026. subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
  3027. help='paths to extract; patterns are supported')
  3028. define_exclusion_group(subparser, strip_components=True)
  3029. diff_epilog = process_epilog("""
  3030. This command finds differences (file contents, user/group/mode) between archives.
  3031. A repository location and an archive name must be specified for REPO::ARCHIVE1.
  3032. ARCHIVE2 is just another archive name in same repository (no repository location
  3033. allowed).
  3034. For archives created with Borg 1.1 or newer diff automatically detects whether
  3035. the archives are created with the same chunker params. If so, only chunk IDs
  3036. are compared, which is very fast.
  3037. For archives prior to Borg 1.1 chunk contents are compared by default.
  3038. If you did not create the archives with different chunker params,
  3039. pass ``--same-chunker-params``.
  3040. Note that the chunker params changed from Borg 0.xx to 1.0.
  3041. See the output of the "borg help patterns" command for more help on exclude patterns.
  3042. """)
  3043. subparser = subparsers.add_parser('diff', parents=[common_parser], add_help=False,
  3044. description=self.do_diff.__doc__,
  3045. epilog=diff_epilog,
  3046. formatter_class=argparse.RawDescriptionHelpFormatter,
  3047. help='find differences in archive contents')
  3048. subparser.set_defaults(func=self.do_diff)
  3049. subparser.add_argument('--numeric-owner', dest='numeric_owner', action='store_true',
  3050. help='only consider numeric user and group identifiers')
  3051. subparser.add_argument('--same-chunker-params', dest='same_chunker_params', action='store_true',
  3052. help='Override check of chunker parameters.')
  3053. subparser.add_argument('--sort', dest='sort', action='store_true',
  3054. help='Sort the output lines by file path.')
  3055. subparser.add_argument('location', metavar='REPO::ARCHIVE1',
  3056. type=location_validator(archive=True),
  3057. help='repository location and ARCHIVE1 name')
  3058. subparser.add_argument('archive2', metavar='ARCHIVE2',
  3059. type=archivename_validator(),
  3060. help='ARCHIVE2 name (no repository location allowed)')
  3061. subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
  3062. help='paths of items inside the archives to compare; patterns are supported')
  3063. define_exclusion_group(subparser)
  3064. rename_epilog = process_epilog("""
  3065. This command renames an archive in the repository.
  3066. This results in a different archive ID.
  3067. """)
  3068. subparser = subparsers.add_parser('rename', parents=[common_parser], add_help=False,
  3069. description=self.do_rename.__doc__,
  3070. epilog=rename_epilog,
  3071. formatter_class=argparse.RawDescriptionHelpFormatter,
  3072. help='rename archive')
  3073. subparser.set_defaults(func=self.do_rename)
  3074. subparser.add_argument('location', metavar='ARCHIVE',
  3075. type=location_validator(archive=True),
  3076. help='archive to rename')
  3077. subparser.add_argument('name', metavar='NEWNAME',
  3078. type=archivename_validator(),
  3079. help='the new archive name to use')
  3080. delete_epilog = process_epilog("""
  3081. This command deletes an archive from the repository or the complete repository.
  3082. Disk space is reclaimed accordingly. If you delete the complete repository, the
  3083. local cache for it (if any) is also deleted.
  3084. When using ``--stats``, you will get some statistics about how much data was
  3085. deleted - the "Deleted data" deduplicated size there is most interesting as
  3086. that is how much your repository will shrink.
  3087. Please note that the "All archives" stats refer to the state after deletion.
  3088. """)
  3089. subparser = subparsers.add_parser('delete', parents=[common_parser], add_help=False,
  3090. description=self.do_delete.__doc__,
  3091. epilog=delete_epilog,
  3092. formatter_class=argparse.RawDescriptionHelpFormatter,
  3093. help='delete archive')
  3094. subparser.set_defaults(func=self.do_delete)
  3095. subparser.add_argument('-n', '--dry-run', dest='dry_run', action='store_true',
  3096. help='do not change repository')
  3097. subparser.add_argument('-s', '--stats', dest='stats', action='store_true',
  3098. help='print statistics for the deleted archive')
  3099. subparser.add_argument('--cache-only', dest='cache_only', action='store_true',
  3100. help='delete only the local cache for the given repository')
  3101. subparser.add_argument('--force', dest='forced',
  3102. action='count', default=0,
  3103. help='force deletion of corrupted archives, '
  3104. 'use ``--force --force`` in case ``--force`` does not work.')
  3105. subparser.add_argument('--save-space', dest='save_space', action='store_true',
  3106. help='work slower, but using less space')
  3107. subparser.add_argument('location', metavar='TARGET', nargs='?', default='',
  3108. type=location_validator(),
  3109. help='archive or repository to delete')
  3110. subparser.add_argument('archives', metavar='ARCHIVE', nargs='*',
  3111. help='archives to delete')
  3112. define_archive_filters_group(subparser)
  3113. list_epilog = process_epilog("""
  3114. This command lists the contents of a repository or an archive.
  3115. See the "borg help patterns" command for more help on exclude patterns.
  3116. .. man NOTES
  3117. The following keys are available for ``--format``:
  3118. """) + BaseFormatter.keys_help() + textwrap.dedent("""
  3119. Keys for listing repository archives:
  3120. """) + ArchiveFormatter.keys_help() + textwrap.dedent("""
  3121. Keys for listing archive files:
  3122. """) + ItemFormatter.keys_help()
  3123. subparser = subparsers.add_parser('list', parents=[common_parser], add_help=False,
  3124. description=self.do_list.__doc__,
  3125. epilog=list_epilog,
  3126. formatter_class=argparse.RawDescriptionHelpFormatter,
  3127. help='list archive or repository contents')
  3128. subparser.set_defaults(func=self.do_list)
  3129. subparser.add_argument('--short', dest='short', action='store_true',
  3130. help='only print file/directory names, nothing else')
  3131. subparser.add_argument('--format', '--list-format', metavar='FORMAT', dest='format',
  3132. help='specify format for file listing '
  3133. '(default: "{mode} {user:6} {group:6} {size:8d} {mtime} {path}{extra}{NL}")')
  3134. subparser.add_argument('--json', action='store_true',
  3135. help='Only valid for listing repository contents. Format output as JSON. '
  3136. 'The form of ``--format`` is ignored, '
  3137. 'but keys used in it are added to the JSON output. '
  3138. 'Some keys are always present. Note: JSON can only represent text. '
  3139. 'A "barchive" key is therefore not available.')
  3140. subparser.add_argument('--json-lines', action='store_true',
  3141. help='Only valid for listing archive contents. Format output as JSON Lines. '
  3142. 'The form of ``--format`` is ignored, '
  3143. 'but keys used in it are added to the JSON output. '
  3144. 'Some keys are always present. Note: JSON can only represent text. '
  3145. 'A "bpath" key is therefore not available.')
  3146. subparser.add_argument('location', metavar='REPOSITORY_OR_ARCHIVE', nargs='?', default='',
  3147. type=location_validator(),
  3148. help='repository/archive to list contents of')
  3149. subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
  3150. help='paths to list; patterns are supported')
  3151. define_archive_filters_group(subparser)
  3152. define_exclusion_group(subparser)
  3153. umount_epilog = process_epilog("""
  3154. This command un-mounts a FUSE filesystem that was mounted with ``borg mount``.
  3155. This is a convenience wrapper that just calls the platform-specific shell
  3156. command - usually this is either umount or fusermount -u.
  3157. """)
  3158. subparser = subparsers.add_parser('umount', parents=[common_parser], add_help=False,
  3159. description=self.do_umount.__doc__,
  3160. epilog=umount_epilog,
  3161. formatter_class=argparse.RawDescriptionHelpFormatter,
  3162. help='umount repository')
  3163. subparser.set_defaults(func=self.do_umount)
  3164. subparser.add_argument('mountpoint', metavar='MOUNTPOINT', type=str,
  3165. help='mountpoint of the filesystem to umount')
  3166. info_epilog = process_epilog("""
  3167. This command displays detailed information about the specified archive or repository.
  3168. Please note that the deduplicated sizes of the individual archives do not add
  3169. up to the deduplicated size of the repository ("all archives"), because the two
  3170. are meaning different things:
  3171. This archive / deduplicated size = amount of data stored ONLY for this archive
  3172. = unique chunks of this archive.
  3173. All archives / deduplicated size = amount of data stored in the repo
  3174. = all chunks in the repository.
  3175. Borg archives can only contain a limited amount of file metadata.
  3176. The size of an archive relative to this limit depends on a number of factors,
  3177. mainly the number of files, the lengths of paths and other metadata stored for files.
  3178. This is shown as *utilization of maximum supported archive size*.
  3179. """)
  3180. subparser = subparsers.add_parser('info', parents=[common_parser], add_help=False,
  3181. description=self.do_info.__doc__,
  3182. epilog=info_epilog,
  3183. formatter_class=argparse.RawDescriptionHelpFormatter,
  3184. help='show repository or archive information')
  3185. subparser.set_defaults(func=self.do_info)
  3186. subparser.add_argument('location', metavar='REPOSITORY_OR_ARCHIVE', nargs='?', default='',
  3187. type=location_validator(),
  3188. help='archive or repository to display information about')
  3189. subparser.add_argument('--json', action='store_true',
  3190. help='format output as JSON')
  3191. define_archive_filters_group(subparser)
  3192. break_lock_epilog = process_epilog("""
  3193. This command breaks the repository and cache locks.
  3194. Please use carefully and only while no borg process (on any machine) is
  3195. trying to access the Cache or the Repository.
  3196. """)
  3197. subparser = subparsers.add_parser('break-lock', parents=[common_parser], add_help=False,
  3198. description=self.do_break_lock.__doc__,
  3199. epilog=break_lock_epilog,
  3200. formatter_class=argparse.RawDescriptionHelpFormatter,
  3201. help='break repository and cache locks')
  3202. subparser.set_defaults(func=self.do_break_lock)
  3203. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  3204. type=location_validator(archive=False),
  3205. help='repository for which to break the locks')
  3206. prune_epilog = process_epilog("""
  3207. The prune command prunes a repository by deleting all archives not matching
  3208. any of the specified retention options. This command is normally used by
  3209. automated backup scripts wanting to keep a certain number of historic backups.
  3210. Also, prune automatically removes checkpoint archives (incomplete archives left
  3211. behind by interrupted backup runs) except if the checkpoint is the latest
  3212. archive (and thus still needed). Checkpoint archives are not considered when
  3213. comparing archive counts against the retention limits (``--keep-X``).
  3214. If a prefix is set with -P, then only archives that start with the prefix are
  3215. considered for deletion and only those archives count towards the totals
  3216. specified by the rules.
  3217. Otherwise, *all* archives in the repository are candidates for deletion!
  3218. There is no automatic distinction between archives representing different
  3219. contents. These need to be distinguished by specifying matching prefixes.
  3220. If you have multiple sequences of archives with different data sets (e.g.
  3221. from different machines) in one shared repository, use one prune call per
  3222. data set that matches only the respective archives using the -P option.
  3223. The ``--keep-within`` option takes an argument of the form "<int><char>",
  3224. where char is "H", "d", "w", "m", "y". For example, ``--keep-within 2d`` means
  3225. to keep all archives that were created within the past 48 hours.
  3226. "1m" is taken to mean "31d". The archives kept with this option do not
  3227. count towards the totals specified by any other options.
  3228. A good procedure is to thin out more and more the older your backups get.
  3229. As an example, ``--keep-daily 7`` means to keep the latest backup on each day,
  3230. up to 7 most recent days with backups (days without backups do not count).
  3231. The rules are applied from secondly to yearly, and backups selected by previous
  3232. rules do not count towards those of later rules. The time that each backup
  3233. starts is used for pruning purposes. Dates and times are interpreted in
  3234. the local timezone, and weeks go from Monday to Sunday. Specifying a
  3235. negative number of archives to keep means that there is no limit.
  3236. The ``--keep-last N`` option is doing the same as ``--keep-secondly N`` (and it will
  3237. keep the last N archives under the assumption that you do not create more than one
  3238. backup archive in the same second).
  3239. When using ``--stats``, you will get some statistics about how much data was
  3240. deleted - the "Deleted data" deduplicated size there is most interesting as
  3241. that is how much your repository will shrink.
  3242. Please note that the "All archives" stats refer to the state after pruning.
  3243. """)
  3244. subparser = subparsers.add_parser('prune', parents=[common_parser], add_help=False,
  3245. description=self.do_prune.__doc__,
  3246. epilog=prune_epilog,
  3247. formatter_class=argparse.RawDescriptionHelpFormatter,
  3248. help='prune archives')
  3249. subparser.set_defaults(func=self.do_prune)
  3250. subparser.add_argument('-n', '--dry-run', dest='dry_run', action='store_true',
  3251. help='do not change repository')
  3252. subparser.add_argument('--force', dest='forced', action='store_true',
  3253. help='force pruning of corrupted archives')
  3254. subparser.add_argument('-s', '--stats', dest='stats', action='store_true',
  3255. help='print statistics for the deleted archive')
  3256. subparser.add_argument('--list', dest='output_list', action='store_true',
  3257. help='output verbose list of archives it keeps/prunes')
  3258. subparser.add_argument('--keep-within', metavar='INTERVAL', dest='within', type=interval,
  3259. help='keep all archives within this time interval')
  3260. subparser.add_argument('--keep-last', '--keep-secondly', dest='secondly', type=int, default=0,
  3261. help='number of secondly archives to keep')
  3262. subparser.add_argument('--keep-minutely', dest='minutely', type=int, default=0,
  3263. help='number of minutely archives to keep')
  3264. subparser.add_argument('-H', '--keep-hourly', dest='hourly', type=int, default=0,
  3265. help='number of hourly archives to keep')
  3266. subparser.add_argument('-d', '--keep-daily', dest='daily', type=int, default=0,
  3267. help='number of daily archives to keep')
  3268. subparser.add_argument('-w', '--keep-weekly', dest='weekly', type=int, default=0,
  3269. help='number of weekly archives to keep')
  3270. subparser.add_argument('-m', '--keep-monthly', dest='monthly', type=int, default=0,
  3271. help='number of monthly archives to keep')
  3272. subparser.add_argument('-y', '--keep-yearly', dest='yearly', type=int, default=0,
  3273. help='number of yearly archives to keep')
  3274. define_archive_filters_group(subparser, sort_by=False, first_last=False)
  3275. subparser.add_argument('--save-space', dest='save_space', action='store_true',
  3276. help='work slower, but using less space')
  3277. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  3278. type=location_validator(archive=False),
  3279. help='repository to prune')
  3280. upgrade_epilog = process_epilog("""
  3281. Upgrade an existing, local Borg repository.
  3282. When you do not need borg upgrade
  3283. +++++++++++++++++++++++++++++++++
  3284. Not every change requires that you run ``borg upgrade``.
  3285. You do **not** need to run it when:
  3286. - moving your repository to a different place
  3287. - upgrading to another point release (like 1.0.x to 1.0.y),
  3288. except when noted otherwise in the changelog
  3289. - upgrading from 1.0.x to 1.1.x,
  3290. except when noted otherwise in the changelog
  3291. Borg 1.x.y upgrades
  3292. +++++++++++++++++++
  3293. Use ``borg upgrade --tam REPO`` to require manifest authentication
  3294. introduced with Borg 1.0.9 to address security issues. This means
  3295. that modifying the repository after doing this with a version prior
  3296. to 1.0.9 will raise a validation error, so only perform this upgrade
  3297. after updating all clients using the repository to 1.0.9 or newer.
  3298. This upgrade should be done on each client for safety reasons.
  3299. If a repository is accidentally modified with a pre-1.0.9 client after
  3300. this upgrade, use ``borg upgrade --tam --force REPO`` to remedy it.
  3301. If you routinely do this you might not want to enable this upgrade
  3302. (which will leave you exposed to the security issue). You can
  3303. reverse the upgrade by issuing ``borg upgrade --disable-tam REPO``.
  3304. See
  3305. https://borgbackup.readthedocs.io/en/stable/changes.html#pre-1-0-9-manifest-spoofing-vulnerability
  3306. for details.
  3307. Attic and Borg 0.xx to Borg 1.x
  3308. +++++++++++++++++++++++++++++++
  3309. This currently supports converting an Attic repository to Borg and also
  3310. helps with converting Borg 0.xx to 1.0.
  3311. Currently, only LOCAL repositories can be upgraded (issue #465).
  3312. Please note that ``borg create`` (since 1.0.0) uses bigger chunks by
  3313. default than old borg or attic did, so the new chunks won't deduplicate
  3314. with the old chunks in the upgraded repository.
  3315. See ``--chunker-params`` option of ``borg create`` and ``borg recreate``.
  3316. ``borg upgrade`` will change the magic strings in the repository's
  3317. segments to match the new Borg magic strings. The keyfiles found in
  3318. $ATTIC_KEYS_DIR or ~/.attic/keys/ will also be converted and
  3319. copied to $BORG_KEYS_DIR or ~/.config/borg/keys.
  3320. The cache files are converted, from $ATTIC_CACHE_DIR or
  3321. ~/.cache/attic to $BORG_CACHE_DIR or ~/.cache/borg, but the
  3322. cache layout between Borg and Attic changed, so it is possible
  3323. the first backup after the conversion takes longer than expected
  3324. due to the cache resync.
  3325. Upgrade should be able to resume if interrupted, although it
  3326. will still iterate over all segments. If you want to start
  3327. from scratch, use `borg delete` over the copied repository to
  3328. make sure the cache files are also removed:
  3329. borg delete borg
  3330. Unless ``--inplace`` is specified, the upgrade process first creates a backup
  3331. copy of the repository, in REPOSITORY.before-upgrade-DATETIME, using hardlinks.
  3332. This requires that the repository and its parent directory reside on same
  3333. filesystem so the hardlink copy can work.
  3334. This takes longer than in place upgrades, but is much safer and gives
  3335. progress information (as opposed to ``cp -al``). Once you are satisfied
  3336. with the conversion, you can safely destroy the backup copy.
  3337. WARNING: Running the upgrade in place will make the current
  3338. copy unusable with older version, with no way of going back
  3339. to previous versions. This can PERMANENTLY DAMAGE YOUR
  3340. REPOSITORY! Attic CAN NOT READ BORG REPOSITORIES, as the
  3341. magic strings have changed. You have been warned.""")
  3342. subparser = subparsers.add_parser('upgrade', parents=[common_parser], add_help=False,
  3343. description=self.do_upgrade.__doc__,
  3344. epilog=upgrade_epilog,
  3345. formatter_class=argparse.RawDescriptionHelpFormatter,
  3346. help='upgrade repository format')
  3347. subparser.set_defaults(func=self.do_upgrade)
  3348. subparser.add_argument('-n', '--dry-run', dest='dry_run', action='store_true',
  3349. help='do not change repository')
  3350. subparser.add_argument('--inplace', dest='inplace', action='store_true',
  3351. help='rewrite repository in place, with no chance of going back '
  3352. 'to older versions of the repository.')
  3353. subparser.add_argument('--force', dest='force', action='store_true',
  3354. help='Force upgrade')
  3355. subparser.add_argument('--tam', dest='tam', action='store_true',
  3356. help='Enable manifest authentication (in key and cache) (Borg 1.0.9 and later).')
  3357. subparser.add_argument('--disable-tam', dest='disable_tam', action='store_true',
  3358. help='Disable manifest authentication (in key and cache).')
  3359. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  3360. type=location_validator(archive=False),
  3361. help='path to the repository to be upgraded')
  3362. recreate_epilog = process_epilog("""
  3363. Recreate the contents of existing archives.
  3364. This is an *experimental* feature. Do *not* use this on your only backup.
  3365. ``--exclude``, ``--exclude-from``, ``--exclude-if-present``, ``--keep-exclude-tags``, and PATH
  3366. have the exact same semantics as in "borg create". If PATHs are specified the
  3367. resulting archive will only contain files from these PATHs.
  3368. Note that all paths in an archive are relative, therefore absolute patterns/paths
  3369. will *not* match (``--exclude``, ``--exclude-from``, PATHs).
  3370. ``--recompress`` allows to change the compression of existing data in archives.
  3371. Due to how Borg stores compressed size information this might display
  3372. incorrect information for archives that were not recreated at the same time.
  3373. There is no risk of data loss by this.
  3374. ``--chunker-params`` will re-chunk all files in the archive, this can be
  3375. used to have upgraded Borg 0.xx or Attic archives deduplicate with
  3376. Borg 1.x archives.
  3377. **USE WITH CAUTION.**
  3378. Depending on the PATHs and patterns given, recreate can be used to permanently
  3379. delete files from archives.
  3380. When in doubt, use ``--dry-run --verbose --list`` to see how patterns/PATHS are
  3381. interpreted.
  3382. The archive being recreated is only removed after the operation completes. The
  3383. archive that is built during the operation exists at the same time at
  3384. "<ARCHIVE>.recreate". The new archive will have a different archive ID.
  3385. With ``--target`` the original archive is not replaced, instead a new archive is created.
  3386. When rechunking space usage can be substantial, expect at least the entire
  3387. deduplicated size of the archives using the previous chunker params.
  3388. When recompressing expect approx. (throughput / checkpoint-interval) in space usage,
  3389. assuming all chunks are recompressed.
  3390. If you recently ran borg check --repair and it had to fix lost chunks with all-zero
  3391. replacement chunks, please first run another backup for the same data and re-run
  3392. borg check --repair afterwards to heal any archives that had lost chunks which are
  3393. still generated from the input data.
  3394. Important: running borg recreate to re-chunk will remove the chunks_healthy
  3395. metadata of all items with replacement chunks, so healing will not be possible
  3396. any more after re-chunking (it is also unlikely it would ever work: due to the
  3397. change of chunking parameters, the missing chunk likely will never be seen again
  3398. even if you still have the data that produced it).
  3399. """)
  3400. subparser = subparsers.add_parser('recreate', parents=[common_parser], add_help=False,
  3401. description=self.do_recreate.__doc__,
  3402. epilog=recreate_epilog,
  3403. formatter_class=argparse.RawDescriptionHelpFormatter,
  3404. help=self.do_recreate.__doc__)
  3405. subparser.set_defaults(func=self.do_recreate)
  3406. subparser.add_argument('--list', dest='output_list', action='store_true',
  3407. help='output verbose list of items (files, dirs, ...)')
  3408. subparser.add_argument('--filter', metavar='STATUSCHARS', dest='output_filter',
  3409. help='only display items with the given status characters (listed in borg create --help)')
  3410. subparser.add_argument('-n', '--dry-run', dest='dry_run', action='store_true',
  3411. help='do not change anything')
  3412. subparser.add_argument('-s', '--stats', dest='stats', action='store_true',
  3413. help='print statistics at end')
  3414. define_exclusion_group(subparser, tag_files=True)
  3415. archive_group = subparser.add_argument_group('Archive options')
  3416. archive_group.add_argument('--target', dest='target', metavar='TARGET', default=None,
  3417. type=archivename_validator(),
  3418. help='create a new archive with the name ARCHIVE, do not replace existing archive '
  3419. '(only applies for a single archive)')
  3420. archive_group.add_argument('-c', '--checkpoint-interval', dest='checkpoint_interval',
  3421. type=int, default=1800, metavar='SECONDS',
  3422. help='write checkpoint every SECONDS seconds (Default: 1800)')
  3423. archive_group.add_argument('--comment', dest='comment', metavar='COMMENT', default=None,
  3424. help='add a comment text to the archive')
  3425. archive_group.add_argument('--timestamp', metavar='TIMESTAMP', dest='timestamp',
  3426. type=timestamp, default=None,
  3427. help='manually specify the archive creation date/time (UTC, yyyy-mm-ddThh:mm:ss format). '
  3428. 'alternatively, give a reference file/directory.')
  3429. archive_group.add_argument('-C', '--compression', metavar='COMPRESSION', dest='compression',
  3430. type=CompressionSpec, default=CompressionSpec('lz4'),
  3431. help='select compression algorithm, see the output of the '
  3432. '"borg help compression" command for details.')
  3433. archive_group.add_argument('--recompress', metavar='MODE', dest='recompress', nargs='?',
  3434. default='never', const='if-different', choices=('never', 'if-different', 'always'),
  3435. help='recompress data chunks according to ``--compression``. '
  3436. 'MODE `if-different`: '
  3437. 'recompress if current compression is with a different compression algorithm '
  3438. '(the level is not considered). '
  3439. 'MODE `always`: '
  3440. 'recompress even if current compression is with the same compression algorithm '
  3441. '(use this to change the compression level). '
  3442. 'MODE `never` (default): '
  3443. 'do not recompress.')
  3444. archive_group.add_argument('--chunker-params', metavar='PARAMS', dest='chunker_params',
  3445. type=ChunkerParams, default=CHUNKER_PARAMS,
  3446. help='specify the chunker parameters (CHUNK_MIN_EXP, CHUNK_MAX_EXP, '
  3447. 'HASH_MASK_BITS, HASH_WINDOW_SIZE) or `default` to use the current defaults. '
  3448. 'default: %d,%d,%d,%d' % CHUNKER_PARAMS)
  3449. subparser.add_argument('location', metavar='REPOSITORY_OR_ARCHIVE', nargs='?', default='',
  3450. type=location_validator(),
  3451. help='repository/archive to recreate')
  3452. subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
  3453. help='paths to recreate; patterns are supported')
  3454. with_lock_epilog = process_epilog("""
  3455. This command runs a user-specified command while the repository lock is held.
  3456. It will first try to acquire the lock (make sure that no other operation is
  3457. running in the repo), then execute the given command as a subprocess and wait
  3458. for its termination, release the lock and return the user command's return
  3459. code as borg's return code.
  3460. .. note::
  3461. If you copy a repository with the lock held, the lock will be present in
  3462. the copy. Thus, before using borg on the copy from a different host,
  3463. you need to use "borg break-lock" on the copied repository, because
  3464. Borg is cautious and does not automatically remove stale locks made by a different host.
  3465. """)
  3466. subparser = subparsers.add_parser('with-lock', parents=[common_parser], add_help=False,
  3467. description=self.do_with_lock.__doc__,
  3468. epilog=with_lock_epilog,
  3469. formatter_class=argparse.RawDescriptionHelpFormatter,
  3470. help='run user command with lock held')
  3471. subparser.set_defaults(func=self.do_with_lock)
  3472. subparser.add_argument('location', metavar='REPOSITORY',
  3473. type=location_validator(archive=False),
  3474. help='repository to lock')
  3475. subparser.add_argument('command', metavar='COMMAND',
  3476. help='command to run')
  3477. subparser.add_argument('args', metavar='ARGS', nargs=argparse.REMAINDER,
  3478. help='command arguments')
  3479. config_epilog = process_epilog("""
  3480. This command gets and sets options in a local repository or cache config file.
  3481. For security reasons, this command only works on local repositories.
  3482. To delete a config value entirely, use ``--delete``. To list the values
  3483. of the configuration file or the default values, use ``--list``. To get and existing
  3484. key, pass only the key name. To set a key, pass both the key name and
  3485. the new value. Keys can be specified in the format "section.name" or
  3486. simply "name"; the section will default to "repository" and "cache" for
  3487. the repo and cache configs, respectively.
  3488. By default, borg config manipulates the repository config file. Using ``--cache``
  3489. edits the repository cache's config file instead.
  3490. """)
  3491. subparser = subparsers.add_parser('config', parents=[common_parser], add_help=False,
  3492. description=self.do_config.__doc__,
  3493. epilog=config_epilog,
  3494. formatter_class=argparse.RawDescriptionHelpFormatter,
  3495. help='get and set configuration values')
  3496. subparser.set_defaults(func=self.do_config)
  3497. subparser.add_argument('-c', '--cache', dest='cache', action='store_true',
  3498. help='get and set values from the repo cache')
  3499. group = subparser.add_mutually_exclusive_group()
  3500. group.add_argument('-d', '--delete', dest='delete', action='store_true',
  3501. help='delete the key from the config file')
  3502. group.add_argument('-l', '--list', dest='list', action='store_true',
  3503. help='list the configuration of the repo')
  3504. subparser.add_argument('location', metavar='REPOSITORY',
  3505. type=location_validator(archive=False, proto='file'),
  3506. help='repository to configure')
  3507. subparser.add_argument('name', metavar='NAME', nargs='?',
  3508. help='name of config key')
  3509. subparser.add_argument('value', metavar='VALUE', nargs='?',
  3510. help='new value for key')
  3511. subparser = subparsers.add_parser('help', parents=[common_parser], add_help=False,
  3512. description='Extra help')
  3513. subparser.add_argument('--epilog-only', dest='epilog_only', action='store_true')
  3514. subparser.add_argument('--usage-only', dest='usage_only', action='store_true')
  3515. subparser.set_defaults(func=functools.partial(self.do_help, parser, subparsers.choices))
  3516. subparser.add_argument('topic', metavar='TOPIC', type=str, nargs='?',
  3517. help='additional help on TOPIC')
  3518. debug_epilog = process_epilog("""
  3519. These commands are not intended for normal use and potentially very
  3520. dangerous if used incorrectly.
  3521. They exist to improve debugging capabilities without direct system access, e.g.
  3522. in case you ever run into some severe malfunction. Use them only if you know
  3523. what you are doing or if a trusted developer tells you what to do.""")
  3524. subparser = subparsers.add_parser('debug', parents=[mid_common_parser], add_help=False,
  3525. description='debugging command (not intended for normal use)',
  3526. epilog=debug_epilog,
  3527. formatter_class=argparse.RawDescriptionHelpFormatter,
  3528. help='debugging command (not intended for normal use)')
  3529. debug_parsers = subparser.add_subparsers(title='required arguments', metavar='<command>')
  3530. subparser.set_defaults(fallback_func=functools.partial(self.do_subcommand_help, subparser))
  3531. debug_info_epilog = process_epilog("""
  3532. This command displays some system information that might be useful for bug
  3533. reports and debugging problems. If a traceback happens, this information is
  3534. already appended at the end of the traceback.
  3535. """)
  3536. subparser = debug_parsers.add_parser('info', parents=[common_parser], add_help=False,
  3537. description=self.do_debug_info.__doc__,
  3538. epilog=debug_info_epilog,
  3539. formatter_class=argparse.RawDescriptionHelpFormatter,
  3540. help='show system infos for debugging / bug reports (debug)')
  3541. subparser.set_defaults(func=self.do_debug_info)
  3542. debug_dump_archive_items_epilog = process_epilog("""
  3543. This command dumps raw (but decrypted and decompressed) archive items (only metadata) to files.
  3544. """)
  3545. subparser = debug_parsers.add_parser('dump-archive-items', parents=[common_parser], add_help=False,
  3546. description=self.do_debug_dump_archive_items.__doc__,
  3547. epilog=debug_dump_archive_items_epilog,
  3548. formatter_class=argparse.RawDescriptionHelpFormatter,
  3549. help='dump archive items (metadata) (debug)')
  3550. subparser.set_defaults(func=self.do_debug_dump_archive_items)
  3551. subparser.add_argument('location', metavar='ARCHIVE',
  3552. type=location_validator(archive=True),
  3553. help='archive to dump')
  3554. debug_dump_archive_epilog = process_epilog("""
  3555. This command dumps all metadata of an archive in a decoded form to a file.
  3556. """)
  3557. subparser = debug_parsers.add_parser('dump-archive', parents=[common_parser], add_help=False,
  3558. description=self.do_debug_dump_archive.__doc__,
  3559. epilog=debug_dump_archive_epilog,
  3560. formatter_class=argparse.RawDescriptionHelpFormatter,
  3561. help='dump decoded archive metadata (debug)')
  3562. subparser.set_defaults(func=self.do_debug_dump_archive)
  3563. subparser.add_argument('location', metavar='ARCHIVE',
  3564. type=location_validator(archive=True),
  3565. help='archive to dump')
  3566. subparser.add_argument('path', metavar='PATH', type=str,
  3567. help='file to dump data into')
  3568. debug_dump_manifest_epilog = process_epilog("""
  3569. This command dumps manifest metadata of a repository in a decoded form to a file.
  3570. """)
  3571. subparser = debug_parsers.add_parser('dump-manifest', parents=[common_parser], add_help=False,
  3572. description=self.do_debug_dump_manifest.__doc__,
  3573. epilog=debug_dump_manifest_epilog,
  3574. formatter_class=argparse.RawDescriptionHelpFormatter,
  3575. help='dump decoded repository metadata (debug)')
  3576. subparser.set_defaults(func=self.do_debug_dump_manifest)
  3577. subparser.add_argument('location', metavar='REPOSITORY',
  3578. type=location_validator(archive=False),
  3579. help='repository to dump')
  3580. subparser.add_argument('path', metavar='PATH', type=str,
  3581. help='file to dump data into')
  3582. debug_dump_repo_objs_epilog = process_epilog("""
  3583. This command dumps raw (but decrypted and decompressed) repo objects to files.
  3584. """)
  3585. subparser = debug_parsers.add_parser('dump-repo-objs', parents=[common_parser], add_help=False,
  3586. description=self.do_debug_dump_repo_objs.__doc__,
  3587. epilog=debug_dump_repo_objs_epilog,
  3588. formatter_class=argparse.RawDescriptionHelpFormatter,
  3589. help='dump repo objects (debug)')
  3590. subparser.set_defaults(func=self.do_debug_dump_repo_objs)
  3591. subparser.add_argument('location', metavar='REPOSITORY',
  3592. type=location_validator(archive=False),
  3593. help='repo to dump')
  3594. subparser.add_argument('--ghost', dest='ghost', action='store_true',
  3595. help='dump all segment file contents, including deleted/uncommitted objects and commits.')
  3596. debug_search_repo_objs_epilog = process_epilog("""
  3597. This command searches raw (but decrypted and decompressed) repo objects for a specific bytes sequence.
  3598. """)
  3599. subparser = debug_parsers.add_parser('search-repo-objs', parents=[common_parser], add_help=False,
  3600. description=self.do_debug_search_repo_objs.__doc__,
  3601. epilog=debug_search_repo_objs_epilog,
  3602. formatter_class=argparse.RawDescriptionHelpFormatter,
  3603. help='search repo objects (debug)')
  3604. subparser.set_defaults(func=self.do_debug_search_repo_objs)
  3605. subparser.add_argument('location', metavar='REPOSITORY',
  3606. type=location_validator(archive=False),
  3607. help='repo to search')
  3608. subparser.add_argument('wanted', metavar='WANTED', type=str,
  3609. help='term to search the repo for, either 0x1234abcd hex term or a string')
  3610. debug_get_obj_epilog = process_epilog("""
  3611. This command gets an object from the repository.
  3612. """)
  3613. subparser = debug_parsers.add_parser('get-obj', parents=[common_parser], add_help=False,
  3614. description=self.do_debug_get_obj.__doc__,
  3615. epilog=debug_get_obj_epilog,
  3616. formatter_class=argparse.RawDescriptionHelpFormatter,
  3617. help='get object from repository (debug)')
  3618. subparser.set_defaults(func=self.do_debug_get_obj)
  3619. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  3620. type=location_validator(archive=False),
  3621. help='repository to use')
  3622. subparser.add_argument('id', metavar='ID', type=str,
  3623. help='hex object ID to get from the repo')
  3624. subparser.add_argument('path', metavar='PATH', type=str,
  3625. help='file to write object data into')
  3626. debug_put_obj_epilog = process_epilog("""
  3627. This command puts objects into the repository.
  3628. """)
  3629. subparser = debug_parsers.add_parser('put-obj', parents=[common_parser], add_help=False,
  3630. description=self.do_debug_put_obj.__doc__,
  3631. epilog=debug_put_obj_epilog,
  3632. formatter_class=argparse.RawDescriptionHelpFormatter,
  3633. help='put object to repository (debug)')
  3634. subparser.set_defaults(func=self.do_debug_put_obj)
  3635. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  3636. type=location_validator(archive=False),
  3637. help='repository to use')
  3638. subparser.add_argument('paths', metavar='PATH', nargs='+', type=str,
  3639. help='file(s) to read and create object(s) from')
  3640. debug_delete_obj_epilog = process_epilog("""
  3641. This command deletes objects from the repository.
  3642. """)
  3643. subparser = debug_parsers.add_parser('delete-obj', parents=[common_parser], add_help=False,
  3644. description=self.do_debug_delete_obj.__doc__,
  3645. epilog=debug_delete_obj_epilog,
  3646. formatter_class=argparse.RawDescriptionHelpFormatter,
  3647. help='delete object from repository (debug)')
  3648. subparser.set_defaults(func=self.do_debug_delete_obj)
  3649. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  3650. type=location_validator(archive=False),
  3651. help='repository to use')
  3652. subparser.add_argument('ids', metavar='IDs', nargs='+', type=str,
  3653. help='hex object ID(s) to delete from the repo')
  3654. debug_refcount_obj_epilog = process_epilog("""
  3655. This command displays the reference count for objects from the repository.
  3656. """)
  3657. subparser = debug_parsers.add_parser('refcount-obj', parents=[common_parser], add_help=False,
  3658. description=self.do_debug_refcount_obj.__doc__,
  3659. epilog=debug_refcount_obj_epilog,
  3660. formatter_class=argparse.RawDescriptionHelpFormatter,
  3661. help='show refcount for object from repository (debug)')
  3662. subparser.set_defaults(func=self.do_debug_refcount_obj)
  3663. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  3664. type=location_validator(archive=False),
  3665. help='repository to use')
  3666. subparser.add_argument('ids', metavar='IDs', nargs='+', type=str,
  3667. help='hex object ID(s) to show refcounts for')
  3668. debug_convert_profile_epilog = process_epilog("""
  3669. Convert a Borg profile to a Python cProfile compatible profile.
  3670. """)
  3671. subparser = debug_parsers.add_parser('convert-profile', parents=[common_parser], add_help=False,
  3672. description=self.do_debug_convert_profile.__doc__,
  3673. epilog=debug_convert_profile_epilog,
  3674. formatter_class=argparse.RawDescriptionHelpFormatter,
  3675. help='convert Borg profile to Python profile (debug)')
  3676. subparser.set_defaults(func=self.do_debug_convert_profile)
  3677. subparser.add_argument('input', metavar='INPUT', type=argparse.FileType('rb'),
  3678. help='Borg profile')
  3679. subparser.add_argument('output', metavar='OUTPUT', type=argparse.FileType('wb'),
  3680. help='Output file')
  3681. benchmark_epilog = process_epilog("These commands do various benchmarks.")
  3682. subparser = subparsers.add_parser('benchmark', parents=[mid_common_parser], add_help=False,
  3683. description='benchmark command',
  3684. epilog=benchmark_epilog,
  3685. formatter_class=argparse.RawDescriptionHelpFormatter,
  3686. help='benchmark command')
  3687. benchmark_parsers = subparser.add_subparsers(title='required arguments', metavar='<command>')
  3688. subparser.set_defaults(fallback_func=functools.partial(self.do_subcommand_help, subparser))
  3689. bench_crud_epilog = process_epilog("""
  3690. This command benchmarks borg CRUD (create, read, update, delete) operations.
  3691. It creates input data below the given PATH and backups this data into the given REPO.
  3692. The REPO must already exist (it could be a fresh empty repo or an existing repo, the
  3693. command will create / read / update / delete some archives named borg-test-data\* there.
  3694. Make sure you have free space there, you'll need about 1GB each (+ overhead).
  3695. If your repository is encrypted and borg needs a passphrase to unlock the key, use:
  3696. BORG_PASSPHRASE=mysecret borg benchmark crud REPO PATH
  3697. Measurements are done with different input file sizes and counts.
  3698. The file contents are very artificial (either all zero or all random),
  3699. thus the measurement results do not necessarily reflect performance with real data.
  3700. Also, due to the kind of content used, no compression is used in these benchmarks.
  3701. C- == borg create (1st archive creation, no compression, do not use files cache)
  3702. C-Z- == all-zero files. full dedup, this is primarily measuring reader/chunker/hasher.
  3703. C-R- == random files. no dedup, measuring throughput through all processing stages.
  3704. R- == borg extract (extract archive, dry-run, do everything, but do not write files to disk)
  3705. R-Z- == all zero files. Measuring heavily duplicated files.
  3706. R-R- == random files. No duplication here, measuring throughput through all processing
  3707. stages, except writing to disk.
  3708. U- == borg create (2nd archive creation of unchanged input files, measure files cache speed)
  3709. The throughput value is kind of virtual here, it does not actually read the file.
  3710. U-Z- == needs to check the 2 all-zero chunks' existence in the repo.
  3711. U-R- == needs to check existence of a lot of different chunks in the repo.
  3712. D- == borg delete archive (delete last remaining archive, measure deletion + compaction)
  3713. D-Z- == few chunks to delete / few segments to compact/remove.
  3714. D-R- == many chunks to delete / many segments to compact/remove.
  3715. Please note that there might be quite some variance in these measurements.
  3716. Try multiple measurements and having a otherwise idle machine (and network, if you use it).
  3717. """)
  3718. subparser = benchmark_parsers.add_parser('crud', parents=[common_parser], add_help=False,
  3719. description=self.do_benchmark_crud.__doc__,
  3720. epilog=bench_crud_epilog,
  3721. formatter_class=argparse.RawDescriptionHelpFormatter,
  3722. help='benchmarks borg CRUD (create, extract, update, delete).')
  3723. subparser.set_defaults(func=self.do_benchmark_crud)
  3724. subparser.add_argument('location', metavar='REPO',
  3725. type=location_validator(archive=False),
  3726. help='repo to use for benchmark (must exist)')
  3727. subparser.add_argument('path', metavar='PATH', help='path were to create benchmark input data')
  3728. return parser
  3729. def get_args(self, argv, cmd):
  3730. """usually, just returns argv, except if we deal with a ssh forced command for borg serve."""
  3731. result = self.parse_args(argv[1:])
  3732. if cmd is not None and result.func == self.do_serve:
  3733. forced_result = result
  3734. argv = shlex.split(cmd)
  3735. # Drop environment variables (do *not* interpret them) before trying to parse
  3736. # the borg command line.
  3737. argv = list(itertools.dropwhile(lambda arg: '=' in arg, argv))
  3738. result = self.parse_args(argv[1:])
  3739. if result.func != forced_result.func:
  3740. # someone is trying to execute a different borg subcommand, don't do that!
  3741. return forced_result
  3742. # we only take specific options from the forced "borg serve" command:
  3743. result.restrict_to_paths = forced_result.restrict_to_paths
  3744. result.restrict_to_repositories = forced_result.restrict_to_repositories
  3745. result.append_only = forced_result.append_only
  3746. return result
  3747. def parse_args(self, args=None):
  3748. # We can't use argparse for "serve" since we don't want it to show up in "Available commands"
  3749. if args:
  3750. args = self.preprocess_args(args)
  3751. parser = self.build_parser()
  3752. args = parser.parse_args(args or ['-h'])
  3753. parser.common_options.resolve(args)
  3754. func = get_func(args)
  3755. if func == self.do_create and not args.paths:
  3756. # need at least 1 path but args.paths may also be populated from patterns
  3757. parser.error('Need at least one PATH argument.')
  3758. return args
  3759. def prerun_checks(self, logger):
  3760. check_python()
  3761. check_extension_modules()
  3762. selftest(logger)
  3763. def _setup_implied_logging(self, args):
  3764. """ turn on INFO level logging for args that imply that they will produce output """
  3765. # map of option name to name of logger for that option
  3766. option_logger = {
  3767. 'output_list': 'borg.output.list',
  3768. 'show_version': 'borg.output.show-version',
  3769. 'show_rc': 'borg.output.show-rc',
  3770. 'stats': 'borg.output.stats',
  3771. 'progress': 'borg.output.progress',
  3772. }
  3773. for option, logger_name in option_logger.items():
  3774. option_set = args.get(option, False)
  3775. logging.getLogger(logger_name).setLevel('INFO' if option_set else 'WARN')
  3776. def _setup_topic_debugging(self, args):
  3777. """Turn on DEBUG level logging for specified --debug-topics."""
  3778. for topic in args.debug_topics:
  3779. if '.' not in topic:
  3780. topic = 'borg.debug.' + topic
  3781. logger.debug('Enabling debug topic %s', topic)
  3782. logging.getLogger(topic).setLevel('DEBUG')
  3783. def run(self, args):
  3784. os.umask(args.umask) # early, before opening files
  3785. self.lock_wait = args.lock_wait
  3786. func = get_func(args)
  3787. # do not use loggers before this!
  3788. is_serve = func == self.do_serve
  3789. setup_logging(level=args.log_level, is_serve=is_serve, json=args.log_json)
  3790. self.log_json = args.log_json
  3791. args.progress |= is_serve
  3792. self._setup_implied_logging(vars(args))
  3793. self._setup_topic_debugging(args)
  3794. if args.show_version:
  3795. logging.getLogger('borg.output.show-version').info('borgbackup version %s' % __version__)
  3796. self.prerun_checks(logger)
  3797. if is_slow_msgpack():
  3798. logger.warning("Using a pure-python msgpack! This will result in lower performance.")
  3799. if args.debug_profile:
  3800. # Import only when needed - avoids a further increase in startup time
  3801. import cProfile
  3802. import marshal
  3803. logger.debug('Writing execution profile to %s', args.debug_profile)
  3804. # Open the file early, before running the main program, to avoid
  3805. # a very late crash in case the specified path is invalid.
  3806. with open(args.debug_profile, 'wb') as fd:
  3807. profiler = cProfile.Profile()
  3808. variables = dict(locals())
  3809. profiler.enable()
  3810. try:
  3811. return set_ec(func(args))
  3812. finally:
  3813. profiler.disable()
  3814. profiler.snapshot_stats()
  3815. if args.debug_profile.endswith('.pyprof'):
  3816. marshal.dump(profiler.stats, fd)
  3817. else:
  3818. # We use msgpack here instead of the marshal module used by cProfile itself,
  3819. # because the latter is insecure. Since these files may be shared over the
  3820. # internet we don't want a format that is impossible to interpret outside
  3821. # an insecure implementation.
  3822. # See scripts/msgpack2marshal.py for a small script that turns a msgpack file
  3823. # into a marshal file that can be read by e.g. pyprof2calltree.
  3824. # For local use it's unnecessary hassle, though, that's why .pyprof makes
  3825. # it compatible (see above).
  3826. msgpack.pack(profiler.stats, fd, use_bin_type=True)
  3827. else:
  3828. return set_ec(func(args))
  3829. def sig_info_handler(sig_no, stack): # pragma: no cover
  3830. """search the stack for infos about the currently processed file and print them"""
  3831. with signal_handler(sig_no, signal.SIG_IGN):
  3832. for frame in inspect.getouterframes(stack):
  3833. func, loc = frame[3], frame[0].f_locals
  3834. if func in ('process_file', '_process', ): # create op
  3835. path = loc['path']
  3836. try:
  3837. pos = loc['fd'].tell()
  3838. total = loc['st'].st_size
  3839. except Exception:
  3840. pos, total = 0, 0
  3841. logger.info("{0} {1}/{2}".format(path, format_file_size(pos), format_file_size(total)))
  3842. break
  3843. if func in ('extract_item', ): # extract op
  3844. path = loc['item'].path
  3845. try:
  3846. pos = loc['fd'].tell()
  3847. except Exception:
  3848. pos = 0
  3849. logger.info("{0} {1}/???".format(path, format_file_size(pos)))
  3850. break
  3851. def sig_trace_handler(sig_no, stack): # pragma: no cover
  3852. print('\nReceived SIGUSR2 at %s, dumping trace...' % datetime.now().replace(microsecond=0), file=sys.stderr)
  3853. faulthandler.dump_traceback()
  3854. def main(): # pragma: no cover
  3855. # Make sure stdout and stderr have errors='replace' to avoid unicode
  3856. # issues when print()-ing unicode file names
  3857. sys.stdout = ErrorIgnoringTextIOWrapper(sys.stdout.buffer, sys.stdout.encoding, 'replace', line_buffering=True)
  3858. sys.stderr = ErrorIgnoringTextIOWrapper(sys.stderr.buffer, sys.stderr.encoding, 'replace', line_buffering=True)
  3859. # If we receive SIGINT (ctrl-c), SIGTERM (kill) or SIGHUP (kill -HUP),
  3860. # catch them and raise a proper exception that can be handled for an
  3861. # orderly exit.
  3862. # SIGHUP is important especially for systemd systems, where logind
  3863. # sends it when a session exits, in addition to any traditional use.
  3864. # Output some info if we receive SIGUSR1 or SIGINFO (ctrl-t).
  3865. # Register fault handler for SIGSEGV, SIGFPE, SIGABRT, SIGBUS and SIGILL.
  3866. faulthandler.enable()
  3867. with signal_handler('SIGINT', raising_signal_handler(KeyboardInterrupt)), \
  3868. signal_handler('SIGHUP', raising_signal_handler(SigHup)), \
  3869. signal_handler('SIGTERM', raising_signal_handler(SigTerm)), \
  3870. signal_handler('SIGUSR1', sig_info_handler), \
  3871. signal_handler('SIGUSR2', sig_trace_handler), \
  3872. signal_handler('SIGINFO', sig_info_handler):
  3873. archiver = Archiver()
  3874. msg = msgid = tb = None
  3875. tb_log_level = logging.ERROR
  3876. try:
  3877. args = archiver.get_args(sys.argv, os.environ.get('SSH_ORIGINAL_COMMAND'))
  3878. except Error as e:
  3879. msg = e.get_message()
  3880. tb_log_level = logging.ERROR if e.traceback else logging.DEBUG
  3881. tb = '%s\n%s' % (traceback.format_exc(), sysinfo())
  3882. # we might not have logging setup yet, so get out quickly
  3883. print(msg, file=sys.stderr)
  3884. if tb_log_level == logging.ERROR:
  3885. print(tb, file=sys.stderr)
  3886. sys.exit(e.exit_code)
  3887. try:
  3888. exit_code = archiver.run(args)
  3889. except Error as e:
  3890. msg = e.get_message()
  3891. msgid = type(e).__qualname__
  3892. tb_log_level = logging.ERROR if e.traceback else logging.DEBUG
  3893. tb = "%s\n%s" % (traceback.format_exc(), sysinfo())
  3894. exit_code = e.exit_code
  3895. except RemoteRepository.RPCError as e:
  3896. important = e.exception_class not in ('LockTimeout', ) and e.traceback
  3897. msgid = e.exception_class
  3898. tb_log_level = logging.ERROR if important else logging.DEBUG
  3899. if important:
  3900. msg = e.exception_full
  3901. else:
  3902. msg = e.get_message()
  3903. tb = '\n'.join('Borg server: ' + l for l in e.sysinfo.splitlines())
  3904. tb += "\n" + sysinfo()
  3905. exit_code = EXIT_ERROR
  3906. except Exception:
  3907. msg = 'Local Exception'
  3908. msgid = 'Exception'
  3909. tb_log_level = logging.ERROR
  3910. tb = '%s\n%s' % (traceback.format_exc(), sysinfo())
  3911. exit_code = EXIT_ERROR
  3912. except KeyboardInterrupt:
  3913. msg = 'Keyboard interrupt'
  3914. tb_log_level = logging.DEBUG
  3915. tb = '%s\n%s' % (traceback.format_exc(), sysinfo())
  3916. exit_code = EXIT_ERROR
  3917. except SigTerm:
  3918. msg = 'Received SIGTERM'
  3919. msgid = 'Signal.SIGTERM'
  3920. tb_log_level = logging.DEBUG
  3921. tb = '%s\n%s' % (traceback.format_exc(), sysinfo())
  3922. exit_code = EXIT_ERROR
  3923. except SigHup:
  3924. msg = 'Received SIGHUP.'
  3925. msgid = 'Signal.SIGHUP'
  3926. exit_code = EXIT_ERROR
  3927. if msg:
  3928. logger.error(msg, msgid=msgid)
  3929. if tb:
  3930. logger.log(tb_log_level, tb)
  3931. if args.show_rc:
  3932. rc_logger = logging.getLogger('borg.output.show-rc')
  3933. exit_msg = 'terminating with %s status, rc %d'
  3934. if exit_code == EXIT_SUCCESS:
  3935. rc_logger.info(exit_msg % ('success', exit_code))
  3936. elif exit_code == EXIT_WARNING:
  3937. rc_logger.warning(exit_msg % ('warning', exit_code))
  3938. elif exit_code == EXIT_ERROR:
  3939. rc_logger.error(exit_msg % ('error', exit_code))
  3940. else:
  3941. rc_logger.error(exit_msg % ('abnormal', exit_code or 666))
  3942. sys.exit(exit_code)
  3943. if __name__ == '__main__':
  3944. main()