archiver.py 300 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558
  1. # borg cli interface / toplevel archiver code
  2. import sys
  3. import traceback
  4. try:
  5. import argparse
  6. import base64
  7. import collections
  8. import configparser
  9. import faulthandler
  10. import functools
  11. import hashlib
  12. import inspect
  13. import itertools
  14. import json
  15. import logging
  16. import os
  17. import re
  18. import shlex
  19. import shutil
  20. import signal
  21. import stat
  22. import subprocess
  23. import tarfile
  24. import textwrap
  25. import time
  26. from binascii import unhexlify, hexlify
  27. from contextlib import contextmanager
  28. from datetime import datetime, timedelta
  29. from io import TextIOWrapper
  30. from .logger import create_logger, setup_logging
  31. logger = create_logger()
  32. import borg
  33. from . import __version__
  34. from . import helpers
  35. from .checksums import crc32
  36. from .archive import Archive, ArchiveChecker, ArchiveRecreater, Statistics, is_special
  37. from .archive import BackupError, BackupOSError, backup_io, OsOpen, stat_update_check
  38. from .archive import FilesystemObjectProcessors, TarfileObjectProcessors, MetadataCollector, ChunksProcessor
  39. from .archive import has_link
  40. from .cache import Cache, assert_secure, SecurityManager
  41. from .constants import * # NOQA
  42. from .compress import CompressionSpec, ZLIB, ZLIB_legacy
  43. from .crypto.key import key_creator, key_argument_names, tam_required_file, tam_required
  44. from .crypto.key import RepoKey, KeyfileKey, Blake2RepoKey, Blake2KeyfileKey, FlexiKey
  45. from .crypto.keymanager import KeyManager
  46. from .helpers import EXIT_SUCCESS, EXIT_WARNING, EXIT_ERROR, EXIT_SIGNAL_BASE
  47. from .helpers import Error, NoManifestError, set_ec
  48. from .helpers import positive_int_validator, location_validator, archivename_validator, ChunkerParams, Location
  49. from .helpers import PrefixSpec, GlobSpec, CommentSpec, SortBySpec, FilesCacheMode
  50. from .helpers import BaseFormatter, ItemFormatter, ArchiveFormatter
  51. from .helpers import format_timedelta, format_file_size, parse_file_size, format_archive
  52. from .helpers import safe_encode, remove_surrogates, bin_to_hex, prepare_dump_dict, eval_escapes
  53. from .helpers import interval, prune_within, prune_split, PRUNING_PATTERNS
  54. from .helpers import timestamp
  55. from .helpers import get_cache_dir, os_stat
  56. from .helpers import Manifest, AI_HUMAN_SORT_KEYS
  57. from .helpers import HardLinkManager
  58. from .helpers import StableDict
  59. from .helpers import check_python, check_extension_modules
  60. from .helpers import dir_is_tagged, is_slow_msgpack, is_supported_msgpack, yes, sysinfo
  61. from .helpers import log_multi
  62. from .helpers import signal_handler, raising_signal_handler, SigHup, SigTerm
  63. from .helpers import ErrorIgnoringTextIOWrapper
  64. from .helpers import ProgressIndicatorPercent
  65. from .helpers import basic_json_data, json_print
  66. from .helpers import replace_placeholders
  67. from .helpers import ChunkIteratorFileWrapper
  68. from .helpers import popen_with_error_handling, prepare_subprocess_env, create_filter_process
  69. from .helpers import dash_open
  70. from .helpers import umount
  71. from .helpers import flags_root, flags_dir, flags_special_follow, flags_special
  72. from .helpers import msgpack
  73. from .helpers import sig_int
  74. from .helpers import iter_separated
  75. from .helpers import get_tar_filter
  76. from .nanorst import rst_to_terminal
  77. from .patterns import ArgparsePatternAction, ArgparseExcludeFileAction, ArgparsePatternFileAction, parse_exclude_pattern
  78. from .patterns import PatternMatcher
  79. from .item import Item
  80. from .platform import get_flags, get_process_id, SyncFile
  81. from .platform import uid2user, gid2group
  82. from .remote import RepositoryServer, RemoteRepository, cache_if_remote
  83. from .repository import Repository, LIST_SCAN_LIMIT, TAG_PUT, TAG_DELETE, TAG_COMMIT
  84. from .selftest import selftest
  85. from .upgrader import BorgRepositoryUpgrader
  86. except BaseException:
  87. # an unhandled exception in the try-block would cause the borg cli command to exit with rc 1 due to python's
  88. # default behavior, see issue #4424.
  89. # as borg defines rc 1 as WARNING, this would be a mismatch, because a crash should be an ERROR (rc 2).
  90. traceback.print_exc()
  91. sys.exit(2) # == EXIT_ERROR
  92. assert EXIT_ERROR == 2, "EXIT_ERROR is not 2, as expected - fix assert AND exception handler right above this line."
  93. STATS_HEADER = " Original size Compressed size Deduplicated size"
  94. PURE_PYTHON_MSGPACK_WARNING = "Using a pure-python msgpack! This will result in lower performance."
  95. def argument(args, str_or_bool):
  96. """If bool is passed, return it. If str is passed, retrieve named attribute from args."""
  97. if isinstance(str_or_bool, str):
  98. return getattr(args, str_or_bool)
  99. if isinstance(str_or_bool, (list, tuple)):
  100. return any(getattr(args, item) for item in str_or_bool)
  101. return str_or_bool
  102. def get_repository(location, *, create, exclusive, lock_wait, lock, append_only,
  103. make_parent_dirs, storage_quota, args):
  104. if location.proto == 'ssh':
  105. repository = RemoteRepository(location.omit_archive(), create=create, exclusive=exclusive,
  106. lock_wait=lock_wait, lock=lock, append_only=append_only,
  107. make_parent_dirs=make_parent_dirs, args=args)
  108. else:
  109. repository = Repository(location.path, create=create, exclusive=exclusive,
  110. lock_wait=lock_wait, lock=lock, append_only=append_only,
  111. make_parent_dirs=make_parent_dirs, storage_quota=storage_quota)
  112. return repository
  113. def compat_check(*, create, manifest, key, cache, compatibility, decorator_name):
  114. if not create and (manifest or key or cache):
  115. if compatibility is None:
  116. raise AssertionError(f"{decorator_name} decorator used without compatibility argument")
  117. if type(compatibility) is not tuple:
  118. raise AssertionError(f"{decorator_name} decorator compatibility argument must be of type tuple")
  119. else:
  120. if compatibility is not None:
  121. raise AssertionError(f"{decorator_name} called with compatibility argument, "
  122. f"but would not check {compatibility!r}")
  123. if create:
  124. compatibility = Manifest.NO_OPERATION_CHECK
  125. return compatibility
  126. def with_repository(fake=False, invert_fake=False, create=False, lock=True,
  127. exclusive=False, manifest=True, cache=False, secure=True,
  128. compatibility=None):
  129. """
  130. Method decorator for subcommand-handling methods: do_XYZ(self, args, repository, …)
  131. If a parameter (where allowed) is a str the attribute named of args is used instead.
  132. :param fake: (str or bool) use None instead of repository, don't do anything else
  133. :param create: create repository
  134. :param lock: lock repository
  135. :param exclusive: (str or bool) lock repository exclusively (for writing)
  136. :param manifest: load manifest and key, pass them as keyword arguments
  137. :param cache: open cache, pass it as keyword argument (implies manifest)
  138. :param secure: do assert_secure after loading manifest
  139. :param compatibility: mandatory if not create and (manifest or cache), specifies mandatory feature categories to check
  140. """
  141. # Note: with_repository decorator does not have a "key" argument (yet?)
  142. compatibility = compat_check(create=create, manifest=manifest, key=manifest, cache=cache,
  143. compatibility=compatibility, decorator_name='with_repository')
  144. # To process the `--bypass-lock` option if specified, we need to
  145. # modify `lock` inside `wrapper`. Therefore we cannot use the
  146. # `nonlocal` statement to access `lock` as modifications would also
  147. # affect the scope outside of `wrapper`. Subsequent calls would
  148. # only see the overwritten value of `lock`, not the original one.
  149. # The solution is to define a place holder variable `_lock` to
  150. # propagate the value into `wrapper`.
  151. _lock = lock
  152. def decorator(method):
  153. @functools.wraps(method)
  154. def wrapper(self, args, **kwargs):
  155. lock = getattr(args, 'lock', _lock)
  156. location = args.location # note: 'location' must be always present in args
  157. append_only = getattr(args, 'append_only', False)
  158. storage_quota = getattr(args, 'storage_quota', None)
  159. make_parent_dirs = getattr(args, 'make_parent_dirs', False)
  160. if argument(args, fake) ^ invert_fake:
  161. return method(self, args, repository=None, **kwargs)
  162. repository = get_repository(location, create=create, exclusive=argument(args, exclusive),
  163. lock_wait=self.lock_wait, lock=lock, append_only=append_only,
  164. make_parent_dirs=make_parent_dirs, storage_quota=storage_quota,
  165. args=args)
  166. with repository:
  167. if manifest or cache:
  168. kwargs['manifest'], kwargs['key'] = Manifest.load(repository, compatibility)
  169. if 'compression' in args:
  170. kwargs['key'].compressor = args.compression.compressor
  171. if secure:
  172. assert_secure(repository, kwargs['manifest'], self.lock_wait)
  173. if cache:
  174. with Cache(repository, kwargs['key'], kwargs['manifest'],
  175. progress=getattr(args, 'progress', False), lock_wait=self.lock_wait,
  176. cache_mode=getattr(args, 'files_cache_mode', DEFAULT_FILES_CACHE_MODE),
  177. consider_part_files=getattr(args, 'consider_part_files', False),
  178. iec=getattr(args, 'iec', False)) as cache_:
  179. return method(self, args, repository=repository, cache=cache_, **kwargs)
  180. else:
  181. return method(self, args, repository=repository, **kwargs)
  182. return wrapper
  183. return decorator
  184. def with_other_repository(manifest=False, key=False, cache=False, compatibility=None):
  185. """
  186. this is a simplified version of "with_repository", just for the "other location".
  187. the repository at the "other location" is intended to get used as a **source** (== read operations).
  188. """
  189. compatibility = compat_check(create=False, manifest=manifest, key=key, cache=cache,
  190. compatibility=compatibility, decorator_name='with_other_repository')
  191. def decorator(method):
  192. @functools.wraps(method)
  193. def wrapper(self, args, **kwargs):
  194. location = getattr(args, 'other_location', None)
  195. if location is None: # nothing to do
  196. return method(self, args, **kwargs)
  197. repository = get_repository(location, create=False, exclusive=True,
  198. lock_wait=self.lock_wait, lock=True, append_only=False,
  199. make_parent_dirs=False, storage_quota=None,
  200. args=args)
  201. with repository:
  202. kwargs['other_repository'] = repository
  203. if manifest or key or cache:
  204. manifest_, key_ = Manifest.load(repository, compatibility)
  205. assert_secure(repository, manifest_, self.lock_wait)
  206. if manifest:
  207. kwargs['other_manifest'] = manifest_
  208. if key:
  209. kwargs['other_key'] = key_
  210. if cache:
  211. with Cache(repository, key_, manifest_,
  212. progress=False, lock_wait=self.lock_wait,
  213. cache_mode=getattr(args, 'files_cache_mode', DEFAULT_FILES_CACHE_MODE),
  214. consider_part_files=getattr(args, 'consider_part_files', False),
  215. iec=getattr(args, 'iec', False)) as cache_:
  216. kwargs['other_cache'] = cache_
  217. return method(self, args, **kwargs)
  218. else:
  219. return method(self, args, **kwargs)
  220. return wrapper
  221. return decorator
  222. def with_archive(method):
  223. @functools.wraps(method)
  224. def wrapper(self, args, repository, key, manifest, **kwargs):
  225. archive = Archive(repository, key, manifest, args.location.archive,
  226. numeric_ids=getattr(args, 'numeric_ids', False),
  227. noflags=getattr(args, 'nobsdflags', False) or getattr(args, 'noflags', False),
  228. noacls=getattr(args, 'noacls', False),
  229. noxattrs=getattr(args, 'noxattrs', False),
  230. cache=kwargs.get('cache'),
  231. consider_part_files=args.consider_part_files, log_json=args.log_json, iec=args.iec)
  232. return method(self, args, repository=repository, manifest=manifest, key=key, archive=archive, **kwargs)
  233. return wrapper
  234. def parse_storage_quota(storage_quota):
  235. parsed = parse_file_size(storage_quota)
  236. if parsed < parse_file_size('10M'):
  237. raise argparse.ArgumentTypeError('quota is too small (%s). At least 10M are required.' % storage_quota)
  238. return parsed
  239. def get_func(args):
  240. # This works around https://bugs.python.org/issue9351
  241. # func is used at the leaf parsers of the argparse parser tree,
  242. # fallback_func at next level towards the root,
  243. # fallback2_func at the 2nd next level (which is root in our case).
  244. for name in 'func', 'fallback_func', 'fallback2_func':
  245. func = getattr(args, name, None)
  246. if func is not None:
  247. return func
  248. raise Exception('expected func attributes not found')
  249. class Highlander(argparse.Action):
  250. """make sure some option is only given once"""
  251. def __call__(self, parser, namespace, values, option_string=None):
  252. if getattr(namespace, self.dest, None) != self.default:
  253. raise argparse.ArgumentError(self, 'There can be only one.')
  254. setattr(namespace, self.dest, values)
  255. class Archiver:
  256. def __init__(self, lock_wait=None, prog=None):
  257. self.exit_code = EXIT_SUCCESS
  258. self.lock_wait = lock_wait
  259. self.prog = prog
  260. def print_error(self, msg, *args):
  261. msg = args and msg % args or msg
  262. self.exit_code = EXIT_ERROR
  263. logger.error(msg)
  264. def print_warning(self, msg, *args):
  265. msg = args and msg % args or msg
  266. self.exit_code = EXIT_WARNING # we do not terminate here, so it is a warning
  267. logger.warning(msg)
  268. def print_file_status(self, status, path):
  269. # if we get called with status == None, the final file status was already printed
  270. if self.output_list and status is not None and (self.output_filter is None or status in self.output_filter):
  271. if self.log_json:
  272. print(json.dumps({
  273. 'type': 'file_status',
  274. 'status': status,
  275. 'path': remove_surrogates(path),
  276. }), file=sys.stderr)
  277. else:
  278. logging.getLogger('borg.output.list').info("%1s %s", status, remove_surrogates(path))
  279. @staticmethod
  280. def build_matcher(inclexcl_patterns, include_paths):
  281. matcher = PatternMatcher()
  282. matcher.add_inclexcl(inclexcl_patterns)
  283. matcher.add_includepaths(include_paths)
  284. return matcher
  285. def do_serve(self, args):
  286. """Start in server mode. This command is usually not used manually."""
  287. RepositoryServer(
  288. restrict_to_paths=args.restrict_to_paths,
  289. restrict_to_repositories=args.restrict_to_repositories,
  290. append_only=args.append_only,
  291. storage_quota=args.storage_quota,
  292. ).serve()
  293. return EXIT_SUCCESS
  294. @with_other_repository(manifest=True, key=True, compatibility=(Manifest.Operation.READ,))
  295. @with_repository(exclusive=True, manifest=True, cache=True, compatibility=(Manifest.Operation.WRITE,))
  296. def do_transfer(self, args, *,
  297. repository, manifest, key, cache,
  298. other_repository=None, other_manifest=None, other_key=None):
  299. """archives transfer from other repository"""
  300. ITEM_KEY_WHITELIST = {'path', 'source', 'rdev', 'chunks', 'chunks_healthy', 'hlid',
  301. 'mode', 'user', 'group', 'uid', 'gid', 'mtime', 'atime', 'ctime', 'birthtime', 'size',
  302. 'xattrs', 'bsdflags', 'acl_nfs4', 'acl_access', 'acl_default', 'acl_extended',
  303. 'part'}
  304. def upgrade_item(item):
  305. """upgrade item as needed, get rid of legacy crap"""
  306. if hlm.borg1_hardlink_master(item):
  307. item._dict['hlid'] = hlid = hlm.hardlink_id(item._dict['path'])
  308. hlm.remember(id=hlid, info=(item._dict.get('chunks'), item._dict.get('chunks_healthy')))
  309. elif hlm.borg1_hardlink_slave(item):
  310. item._dict['hlid'] = hlid = hlm.hardlink_id(item._dict['source'])
  311. chunks, chunks_healthy = hlm.retrieve(id=hlid, default=(None, None))
  312. if chunks is not None:
  313. item._dict['chunks'] = chunks
  314. for chunk_id, _, _ in chunks:
  315. cache.chunk_incref(chunk_id, archive.stats)
  316. if chunks_healthy is not None:
  317. item._dict['chunks_healthy'] = chunks
  318. item._dict.pop('source') # not used for hardlinks any more, replaced by hlid
  319. for attr in 'atime', 'ctime', 'mtime', 'birthtime':
  320. if attr in item:
  321. ns = getattr(item, attr) # decode (bigint or Timestamp) --> int ns
  322. setattr(item, attr, ns) # encode int ns --> msgpack.Timestamp only, no bigint any more
  323. # make sure we only have desired stuff in the new item. specifically, make sure to get rid of:
  324. # - 'acl' remnants of bug in attic <= 0.13
  325. # - 'hardlink_master' (superseded by hlid)
  326. new_item_dict = {key: value for key, value in item.as_dict().items() if key in ITEM_KEY_WHITELIST}
  327. new_item = Item(internal_dict=new_item_dict)
  328. new_item.get_size(memorize=True) # if not already present: compute+remember size for items with chunks
  329. assert all(key in new_item for key in REQUIRED_ITEM_KEYS)
  330. return new_item
  331. def upgrade_compressed_chunk(chunk):
  332. if ZLIB_legacy.detect(chunk):
  333. chunk = ZLIB.ID + chunk # get rid of the attic legacy: prepend separate type bytes for zlib
  334. return chunk
  335. dry_run = args.dry_run
  336. args.consider_checkpoints = True
  337. archive_names = tuple(x.name for x in other_manifest.archives.list_considering(args))
  338. if not archive_names:
  339. return EXIT_SUCCESS
  340. for name in archive_names:
  341. transfer_size = 0
  342. present_size = 0
  343. if name in manifest.archives and not dry_run:
  344. print(f"{name}: archive is already present in destination repo, skipping.")
  345. else:
  346. if not dry_run:
  347. print(f"{name}: copying archive to destination repo...")
  348. hlm = HardLinkManager(id_type=bytes, info_type=tuple) # hlid -> (chunks, chunks_healthy)
  349. other_archive = Archive(other_repository, other_key, other_manifest, name)
  350. archive = Archive(repository, key, manifest, name, cache=cache, create=True) if not dry_run else None
  351. for item in other_archive.iter_items():
  352. if 'chunks' in item:
  353. chunks = []
  354. for chunk_id, size, _ in item.chunks:
  355. refcount = cache.seen_chunk(chunk_id, size)
  356. if refcount == 0: # target repo does not yet have this chunk
  357. if not dry_run:
  358. cdata = other_repository.get(chunk_id)
  359. # keep compressed payload same, avoid decompression / recompression
  360. data = other_key.decrypt(chunk_id, cdata, decompress=False)
  361. data = upgrade_compressed_chunk(data)
  362. chunk_entry = cache.add_chunk(chunk_id, data, archive.stats, wait=False,
  363. compress=False, size=size)
  364. cache.repository.async_response(wait=False)
  365. chunks.append(chunk_entry)
  366. transfer_size += size
  367. else:
  368. if not dry_run:
  369. chunk_entry = cache.chunk_incref(chunk_id, archive.stats)
  370. chunks.append(chunk_entry)
  371. present_size += size
  372. if not dry_run:
  373. item.chunks = chunks # overwrite! IDs and sizes are same, csizes are likely different
  374. archive.stats.nfiles += 1
  375. if not dry_run:
  376. archive.add_item(upgrade_item(item))
  377. if not dry_run:
  378. additional_metadata = {}
  379. # keep all metadata except archive version and stats. also do not keep
  380. # recreate_source_id, recreate_args, recreate_partial_chunks which were used only in 1.1.0b1 .. b2.
  381. for attr in ('cmdline', 'hostname', 'username', 'time', 'time_end', 'comment',
  382. 'chunker_params', 'recreate_cmdline'):
  383. if hasattr(other_archive.metadata, attr):
  384. additional_metadata[attr] = getattr(other_archive.metadata, attr)
  385. archive.save(stats=archive.stats, additional_metadata=additional_metadata)
  386. print(f"{name}: finished. "
  387. f"transfer_size: {format_file_size(transfer_size)} "
  388. f"present_size: {format_file_size(present_size)}")
  389. else:
  390. print(f"{name}: completed" if transfer_size == 0 else
  391. f"{name}: incomplete, "
  392. f"transfer_size: {format_file_size(transfer_size)} "
  393. f"present_size: {format_file_size(present_size)}")
  394. return EXIT_SUCCESS
  395. @with_repository(create=True, exclusive=True, manifest=False)
  396. @with_other_repository(key=True, compatibility=(Manifest.Operation.READ, ))
  397. def do_init(self, args, repository, *, other_repository=None, other_key=None):
  398. """Initialize an empty repository"""
  399. path = args.location.canonical_path()
  400. logger.info('Initializing repository at "%s"' % path)
  401. try:
  402. key = key_creator(repository, args, other_key=other_key)
  403. except (EOFError, KeyboardInterrupt):
  404. repository.destroy()
  405. return EXIT_WARNING
  406. manifest = Manifest(key, repository)
  407. manifest.key = key
  408. manifest.write()
  409. repository.commit(compact=False)
  410. with Cache(repository, key, manifest, warn_if_unencrypted=False):
  411. pass
  412. if key.tam_required:
  413. tam_file = tam_required_file(repository)
  414. open(tam_file, 'w').close()
  415. logger.warning(
  416. '\n'
  417. 'By default repositories initialized with this version will produce security\n'
  418. 'errors if written to with an older version (up to and including Borg 1.0.8).\n'
  419. '\n'
  420. 'If you want to use these older versions, you can disable the check by running:\n'
  421. 'borg upgrade --disable-tam %s\n'
  422. '\n'
  423. 'See https://borgbackup.readthedocs.io/en/stable/changes.html#pre-1-0-9-manifest-spoofing-vulnerability '
  424. 'for details about the security implications.', shlex.quote(path))
  425. if key.NAME != 'plaintext':
  426. logger.warning(
  427. '\n'
  428. 'IMPORTANT: you will need both KEY AND PASSPHRASE to access this repo!\n'
  429. 'If you used a repokey mode, the key is stored in the repo, but you should back it up separately.\n'
  430. 'Use "borg key export" to export the key, optionally in printable format.\n'
  431. 'Write down the passphrase. Store both at safe place(s).\n')
  432. return self.exit_code
  433. @with_repository(exclusive=True, manifest=False)
  434. def do_check(self, args, repository):
  435. """Check repository consistency"""
  436. if args.repair:
  437. msg = ("This is a potentially dangerous function.\n"
  438. "check --repair might lead to data loss (for kinds of corruption it is not\n"
  439. "capable of dealing with). BE VERY CAREFUL!\n"
  440. "\n"
  441. "Type 'YES' if you understand this and want to continue: ")
  442. if not yes(msg, false_msg="Aborting.", invalid_msg="Invalid answer, aborting.",
  443. truish=('YES', ), retry=False,
  444. env_var_override='BORG_CHECK_I_KNOW_WHAT_I_AM_DOING'):
  445. return EXIT_ERROR
  446. if args.repo_only and any(
  447. (args.verify_data, args.first, args.last, args.prefix is not None, args.glob_archives)):
  448. self.print_error("--repository-only contradicts --first, --last, --prefix and --verify-data arguments.")
  449. return EXIT_ERROR
  450. if args.repair and args.max_duration:
  451. self.print_error("--repair does not allow --max-duration argument.")
  452. return EXIT_ERROR
  453. if args.max_duration and not args.repo_only:
  454. # when doing a partial repo check, we can only check crc32 checksums in segment files,
  455. # we can't build a fresh repo index in memory to verify the on-disk index against it.
  456. # thus, we should not do an archives check based on a unknown-quality on-disk repo index.
  457. # also, there is no max_duration support in the archives check code anyway.
  458. self.print_error("--repository-only is required for --max-duration support.")
  459. return EXIT_ERROR
  460. if not args.archives_only:
  461. if not repository.check(repair=args.repair, save_space=args.save_space, max_duration=args.max_duration):
  462. return EXIT_WARNING
  463. if args.prefix is not None:
  464. args.glob_archives = args.prefix + '*'
  465. if not args.repo_only and not ArchiveChecker().check(
  466. repository, repair=args.repair, archive=args.location.archive,
  467. first=args.first, last=args.last, sort_by=args.sort_by or 'ts', glob=args.glob_archives,
  468. verify_data=args.verify_data, save_space=args.save_space):
  469. return EXIT_WARNING
  470. return EXIT_SUCCESS
  471. @with_repository(compatibility=(Manifest.Operation.CHECK,))
  472. def do_change_passphrase(self, args, repository, manifest, key):
  473. """Change repository key file passphrase"""
  474. if not hasattr(key, 'change_passphrase'):
  475. print('This repository is not encrypted, cannot change the passphrase.')
  476. return EXIT_ERROR
  477. key.change_passphrase()
  478. logger.info('Key updated')
  479. if hasattr(key, 'find_key'):
  480. # print key location to make backing it up easier
  481. logger.info('Key location: %s', key.find_key())
  482. return EXIT_SUCCESS
  483. @with_repository(exclusive=True, manifest=True, cache=True, compatibility=(Manifest.Operation.CHECK,))
  484. def do_change_location(self, args, repository, manifest, key, cache):
  485. """Change repository key location"""
  486. if not hasattr(key, 'change_passphrase'):
  487. print('This repository is not encrypted, cannot change the key location.')
  488. return EXIT_ERROR
  489. if args.key_mode == 'keyfile':
  490. if isinstance(key, RepoKey):
  491. key_new = KeyfileKey(repository)
  492. elif isinstance(key, Blake2RepoKey):
  493. key_new = Blake2KeyfileKey(repository)
  494. elif isinstance(key, (KeyfileKey, Blake2KeyfileKey)):
  495. print(f"Location already is {args.key_mode}")
  496. return EXIT_SUCCESS
  497. else:
  498. raise Error("Unsupported key type")
  499. if args.key_mode == 'repokey':
  500. if isinstance(key, KeyfileKey):
  501. key_new = RepoKey(repository)
  502. elif isinstance(key, Blake2KeyfileKey):
  503. key_new = Blake2RepoKey(repository)
  504. elif isinstance(key, (RepoKey, Blake2RepoKey)):
  505. print(f"Location already is {args.key_mode}")
  506. return EXIT_SUCCESS
  507. else:
  508. raise Error("Unsupported key type")
  509. for name in ('repository_id', 'enc_key', 'enc_hmac_key', 'id_key', 'chunk_seed',
  510. 'tam_required', 'nonce_manager', 'cipher'):
  511. value = getattr(key, name)
  512. setattr(key_new, name, value)
  513. key_new.target = key_new.get_new_target(args)
  514. # save with same passphrase and algorithm
  515. key_new.save(key_new.target, key._passphrase, create=True, algorithm=key._encrypted_key_algorithm)
  516. # rewrite the manifest with the new key, so that the key-type byte of the manifest changes
  517. manifest.key = key_new
  518. manifest.write()
  519. repository.commit(compact=False)
  520. # we need to rewrite cache config and security key-type info,
  521. # so that the cached key-type will match the repo key-type.
  522. cache.begin_txn() # need to start a cache transaction, otherwise commit() does nothing.
  523. cache.key = key_new
  524. cache.commit()
  525. loc = key_new.find_key() if hasattr(key_new, 'find_key') else None
  526. if args.keep:
  527. logger.info(f'Key copied to {loc}')
  528. else:
  529. key.remove(key.target) # remove key from current location
  530. logger.info(f'Key moved to {loc}')
  531. return EXIT_SUCCESS
  532. @with_repository(exclusive=True, compatibility=(Manifest.Operation.CHECK,))
  533. def do_change_algorithm(self, args, repository, manifest, key):
  534. """Change repository key algorithm"""
  535. if not hasattr(key, 'change_passphrase'):
  536. print('This repository is not encrypted, cannot change the algorithm.')
  537. return EXIT_ERROR
  538. key.save(key.target, key._passphrase, algorithm=KEY_ALGORITHMS[args.algorithm])
  539. return EXIT_SUCCESS
  540. @with_repository(lock=False, exclusive=False, manifest=False, cache=False)
  541. def do_key_export(self, args, repository):
  542. """Export the repository key for backup"""
  543. manager = KeyManager(repository)
  544. manager.load_keyblob()
  545. if args.paper:
  546. manager.export_paperkey(args.path)
  547. else:
  548. try:
  549. if args.qr:
  550. manager.export_qr(args.path)
  551. else:
  552. manager.export(args.path)
  553. except IsADirectoryError:
  554. self.print_error(f"'{args.path}' must be a file, not a directory")
  555. return EXIT_ERROR
  556. return EXIT_SUCCESS
  557. @with_repository(lock=False, exclusive=False, manifest=False, cache=False)
  558. def do_key_import(self, args, repository):
  559. """Import the repository key from backup"""
  560. manager = KeyManager(repository)
  561. if args.paper:
  562. if args.path:
  563. self.print_error("with --paper import from file is not supported")
  564. return EXIT_ERROR
  565. manager.import_paperkey(args)
  566. else:
  567. if not args.path:
  568. self.print_error("input file to import key from expected")
  569. return EXIT_ERROR
  570. if args.path != '-' and not os.path.exists(args.path):
  571. self.print_error("input file does not exist: " + args.path)
  572. return EXIT_ERROR
  573. manager.import_keyfile(args)
  574. return EXIT_SUCCESS
  575. def do_benchmark_crud(self, args):
  576. """Benchmark Create, Read, Update, Delete for archives."""
  577. def measurement_run(repo, path):
  578. archive = repo + '::borg-benchmark-crud'
  579. compression = '--compression=none'
  580. # measure create perf (without files cache to always have it chunking)
  581. t_start = time.monotonic()
  582. rc = self.do_create(self.parse_args(['create', compression, '--files-cache=disabled', archive + '1', path]))
  583. t_end = time.monotonic()
  584. dt_create = t_end - t_start
  585. assert rc == 0
  586. # now build files cache
  587. rc1 = self.do_create(self.parse_args(['create', compression, archive + '2', path]))
  588. rc2 = self.do_delete(self.parse_args(['delete', archive + '2']))
  589. assert rc1 == rc2 == 0
  590. # measure a no-change update (archive1 is still present)
  591. t_start = time.monotonic()
  592. rc1 = self.do_create(self.parse_args(['create', compression, archive + '3', path]))
  593. t_end = time.monotonic()
  594. dt_update = t_end - t_start
  595. rc2 = self.do_delete(self.parse_args(['delete', archive + '3']))
  596. assert rc1 == rc2 == 0
  597. # measure extraction (dry-run: without writing result to disk)
  598. t_start = time.monotonic()
  599. rc = self.do_extract(self.parse_args(['extract', '--dry-run', archive + '1']))
  600. t_end = time.monotonic()
  601. dt_extract = t_end - t_start
  602. assert rc == 0
  603. # measure archive deletion (of LAST present archive with the data)
  604. t_start = time.monotonic()
  605. rc = self.do_delete(self.parse_args(['delete', archive + '1']))
  606. t_end = time.monotonic()
  607. dt_delete = t_end - t_start
  608. assert rc == 0
  609. return dt_create, dt_update, dt_extract, dt_delete
  610. @contextmanager
  611. def test_files(path, count, size, random):
  612. try:
  613. path = os.path.join(path, 'borg-test-data')
  614. os.makedirs(path)
  615. z_buff = None if random else memoryview(zeros)[:size] if size <= len(zeros) else b'\0' * size
  616. for i in range(count):
  617. fname = os.path.join(path, 'file_%d' % i)
  618. data = z_buff if not random else os.urandom(size)
  619. with SyncFile(fname, binary=True) as fd: # used for posix_fadvise's sake
  620. fd.write(data)
  621. yield path
  622. finally:
  623. shutil.rmtree(path)
  624. if '_BORG_BENCHMARK_CRUD_TEST' in os.environ:
  625. tests = [
  626. ('Z-TEST', 1, 1, False),
  627. ('R-TEST', 1, 1, True),
  628. ]
  629. else:
  630. tests = [
  631. ('Z-BIG', 10, 100000000, False),
  632. ('R-BIG', 10, 100000000, True),
  633. ('Z-MEDIUM', 1000, 1000000, False),
  634. ('R-MEDIUM', 1000, 1000000, True),
  635. ('Z-SMALL', 10000, 10000, False),
  636. ('R-SMALL', 10000, 10000, True),
  637. ]
  638. for msg, count, size, random in tests:
  639. with test_files(args.path, count, size, random) as path:
  640. dt_create, dt_update, dt_extract, dt_delete = measurement_run(args.location.canonical_path(), path)
  641. total_size_MB = count * size / 1e06
  642. file_size_formatted = format_file_size(size)
  643. content = 'random' if random else 'all-zero'
  644. fmt = '%s-%-10s %9.2f MB/s (%d * %s %s files: %.2fs)'
  645. print(fmt % ('C', msg, total_size_MB / dt_create, count, file_size_formatted, content, dt_create))
  646. print(fmt % ('R', msg, total_size_MB / dt_extract, count, file_size_formatted, content, dt_extract))
  647. print(fmt % ('U', msg, total_size_MB / dt_update, count, file_size_formatted, content, dt_update))
  648. print(fmt % ('D', msg, total_size_MB / dt_delete, count, file_size_formatted, content, dt_delete))
  649. return 0
  650. def do_benchmark_cpu(self, args):
  651. """Benchmark CPU bound operations."""
  652. from timeit import timeit
  653. random_10M = os.urandom(10*1000*1000)
  654. key_256 = os.urandom(32)
  655. key_128 = os.urandom(16)
  656. key_96 = os.urandom(12)
  657. import io
  658. from borg.chunker import get_chunker
  659. print("Chunkers =======================================================")
  660. size = "1GB"
  661. def chunkit(chunker_name, *args, **kwargs):
  662. with io.BytesIO(random_10M) as data_file:
  663. ch = get_chunker(chunker_name, *args, **kwargs)
  664. for _ in ch.chunkify(fd=data_file):
  665. pass
  666. for spec, func in [
  667. ("buzhash,19,23,21,4095", lambda: chunkit("buzhash", 19, 23, 21, 4095, seed=0)),
  668. ("fixed,1048576", lambda: chunkit("fixed", 1048576, sparse=False)),
  669. ]:
  670. print(f"{spec:<24} {size:<10} {timeit(func, number=100):.3f}s")
  671. import zlib
  672. from borg.checksums import crc32, deflate_crc32, xxh64
  673. print("Non-cryptographic checksums / hashes ===========================")
  674. size = "1GB"
  675. tests = [
  676. ("xxh64", lambda: xxh64(random_10M)),
  677. ]
  678. if crc32 is zlib.crc32:
  679. tests.insert(0, ("crc32 (zlib, used)", lambda: crc32(random_10M)))
  680. tests.insert(1, ("crc32 (libdeflate)", lambda: deflate_crc32(random_10M)))
  681. elif crc32 is deflate_crc32:
  682. tests.insert(0, ("crc32 (libdeflate, used)", lambda: crc32(random_10M)))
  683. tests.insert(1, ("crc32 (zlib)", lambda: zlib.crc32(random_10M)))
  684. else:
  685. raise Error("crc32 benchmarking code missing")
  686. for spec, func in tests:
  687. print(f"{spec:<24} {size:<10} {timeit(func, number=100):.3f}s")
  688. from borg.crypto.low_level import hmac_sha256, blake2b_256
  689. print("Cryptographic hashes / MACs ====================================")
  690. size = "1GB"
  691. for spec, func in [
  692. ("hmac-sha256", lambda: hmac_sha256(key_256, random_10M)),
  693. ("blake2b-256", lambda: blake2b_256(key_256, random_10M)),
  694. ]:
  695. print(f"{spec:<24} {size:<10} {timeit(func, number=100):.3f}s")
  696. from borg.crypto.low_level import AES256_CTR_BLAKE2b, AES256_CTR_HMAC_SHA256
  697. from borg.crypto.low_level import AES256_OCB, CHACHA20_POLY1305
  698. print("Encryption =====================================================")
  699. size = "1GB"
  700. tests = [
  701. ("aes-256-ctr-hmac-sha256", lambda: AES256_CTR_HMAC_SHA256(
  702. key_256, key_256, iv=key_128, header_len=1, aad_offset=1).encrypt(random_10M, header=b'X')),
  703. ("aes-256-ctr-blake2b", lambda: AES256_CTR_BLAKE2b(
  704. key_256*4, key_256, iv=key_128, header_len=1, aad_offset=1).encrypt(random_10M, header=b'X')),
  705. ("aes-256-ocb", lambda: AES256_OCB(
  706. key_256, iv=key_96, header_len=1, aad_offset=1).encrypt(random_10M, header=b'X')),
  707. ("chacha20-poly1305", lambda: CHACHA20_POLY1305(
  708. key_256, iv=key_96, header_len=1, aad_offset=1).encrypt(random_10M, header=b'X')),
  709. ]
  710. for spec, func in tests:
  711. print(f"{spec:<24} {size:<10} {timeit(func, number=100):.3f}s")
  712. print("KDFs (slow is GOOD, use argon2!) ===============================")
  713. count = 5
  714. for spec, func in [
  715. ("pbkdf2", lambda: FlexiKey.pbkdf2('mypassphrase', b'salt'*8, PBKDF2_ITERATIONS, 32)),
  716. ("argon2", lambda: FlexiKey.argon2('mypassphrase', 64, b'S' * ARGON2_SALT_BYTES, **ARGON2_ARGS)),
  717. ]:
  718. print(f"{spec:<24} {count:<10} {timeit(func, number=count):.3f}s")
  719. from borg.compress import CompressionSpec
  720. print("Compression ====================================================")
  721. for spec in [
  722. 'lz4',
  723. 'zstd,1',
  724. 'zstd,3',
  725. 'zstd,5',
  726. 'zstd,10',
  727. 'zstd,16',
  728. 'zstd,22',
  729. 'zlib,0',
  730. 'zlib,6',
  731. 'zlib,9',
  732. 'lzma,0',
  733. 'lzma,6',
  734. 'lzma,9',
  735. ]:
  736. compressor = CompressionSpec(spec).compressor
  737. size = "0.1GB"
  738. print(f"{spec:<12} {size:<10} {timeit(lambda: compressor.compress(random_10M), number=10):.3f}s")
  739. print("msgpack ========================================================")
  740. item = Item(path="/foo/bar/baz", mode=660, mtime=1234567)
  741. items = [item.as_dict(), ] * 1000
  742. size = "100k Items"
  743. spec = "msgpack"
  744. print(f"{spec:<12} {size:<10} {timeit(lambda: msgpack.packb(items), number=100):.3f}s")
  745. return 0
  746. @with_repository(fake='dry_run', exclusive=True, compatibility=(Manifest.Operation.WRITE,))
  747. def do_create(self, args, repository, manifest=None, key=None):
  748. """Create new archive"""
  749. matcher = PatternMatcher(fallback=True)
  750. matcher.add_inclexcl(args.patterns)
  751. def create_inner(archive, cache, fso):
  752. # Add cache dir to inode_skip list
  753. skip_inodes = set()
  754. try:
  755. st = os.stat(get_cache_dir())
  756. skip_inodes.add((st.st_ino, st.st_dev))
  757. except OSError:
  758. pass
  759. # Add local repository dir to inode_skip list
  760. if not args.location.host:
  761. try:
  762. st = os.stat(args.location.path)
  763. skip_inodes.add((st.st_ino, st.st_dev))
  764. except OSError:
  765. pass
  766. logger.debug('Processing files ...')
  767. if args.content_from_command:
  768. path = args.stdin_name
  769. mode = args.stdin_mode
  770. user = args.stdin_user
  771. group = args.stdin_group
  772. if not dry_run:
  773. try:
  774. try:
  775. proc = subprocess.Popen(args.paths, stdout=subprocess.PIPE)
  776. except (FileNotFoundError, PermissionError) as e:
  777. self.print_error('Failed to execute command: %s', e)
  778. return self.exit_code
  779. status = fso.process_pipe(path=path, cache=cache, fd=proc.stdout, mode=mode, user=user, group=group)
  780. rc = proc.wait()
  781. if rc != 0:
  782. self.print_error('Command %r exited with status %d', args.paths[0], rc)
  783. return self.exit_code
  784. except BackupOSError as e:
  785. self.print_error('%s: %s', path, e)
  786. return self.exit_code
  787. else:
  788. status = '-'
  789. self.print_file_status(status, path)
  790. elif args.paths_from_command or args.paths_from_stdin:
  791. paths_sep = eval_escapes(args.paths_delimiter) if args.paths_delimiter is not None else '\n'
  792. if args.paths_from_command:
  793. try:
  794. proc = subprocess.Popen(args.paths, stdout=subprocess.PIPE)
  795. except (FileNotFoundError, PermissionError) as e:
  796. self.print_error('Failed to execute command: %s', e)
  797. return self.exit_code
  798. pipe_bin = proc.stdout
  799. else: # args.paths_from_stdin == True
  800. pipe_bin = sys.stdin.buffer
  801. pipe = TextIOWrapper(pipe_bin, errors='surrogateescape')
  802. for path in iter_separated(pipe, paths_sep):
  803. try:
  804. with backup_io('stat'):
  805. st = os_stat(path=path, parent_fd=None, name=None, follow_symlinks=False)
  806. status = self._process_any(path=path, parent_fd=None, name=None, st=st, fso=fso,
  807. cache=cache, read_special=args.read_special, dry_run=dry_run)
  808. except (BackupOSError, BackupError) as e:
  809. self.print_warning('%s: %s', path, e)
  810. status = 'E'
  811. if status == 'C':
  812. self.print_warning('%s: file changed while we backed it up', path)
  813. self.print_file_status(status, path)
  814. if args.paths_from_command:
  815. rc = proc.wait()
  816. if rc != 0:
  817. self.print_error('Command %r exited with status %d', args.paths[0], rc)
  818. return self.exit_code
  819. else:
  820. for path in args.paths:
  821. if path == '-': # stdin
  822. path = args.stdin_name
  823. mode = args.stdin_mode
  824. user = args.stdin_user
  825. group = args.stdin_group
  826. if not dry_run:
  827. try:
  828. status = fso.process_pipe(path=path, cache=cache, fd=sys.stdin.buffer, mode=mode, user=user, group=group)
  829. except BackupOSError as e:
  830. status = 'E'
  831. self.print_warning('%s: %s', path, e)
  832. else:
  833. status = '-'
  834. self.print_file_status(status, path)
  835. continue
  836. path = os.path.normpath(path)
  837. parent_dir = os.path.dirname(path) or '.'
  838. name = os.path.basename(path)
  839. try:
  840. # note: for path == '/': name == '' and parent_dir == '/'.
  841. # the empty name will trigger a fall-back to path-based processing in os_stat and os_open.
  842. with OsOpen(path=parent_dir, flags=flags_root, noatime=True, op='open_root') as parent_fd:
  843. try:
  844. st = os_stat(path=path, parent_fd=parent_fd, name=name, follow_symlinks=False)
  845. except OSError as e:
  846. self.print_warning('%s: %s', path, e)
  847. continue
  848. if args.one_file_system:
  849. restrict_dev = st.st_dev
  850. else:
  851. restrict_dev = None
  852. self._rec_walk(path=path, parent_fd=parent_fd, name=name,
  853. fso=fso, cache=cache, matcher=matcher,
  854. exclude_caches=args.exclude_caches, exclude_if_present=args.exclude_if_present,
  855. keep_exclude_tags=args.keep_exclude_tags, skip_inodes=skip_inodes,
  856. restrict_dev=restrict_dev, read_special=args.read_special, dry_run=dry_run)
  857. # if we get back here, we've finished recursing into <path>,
  858. # we do not ever want to get back in there (even if path is given twice as recursion root)
  859. skip_inodes.add((st.st_ino, st.st_dev))
  860. except (BackupOSError, BackupError) as e:
  861. # this comes from OsOpen, self._rec_walk has own exception handler
  862. self.print_warning('%s: %s', path, e)
  863. continue
  864. if not dry_run:
  865. if args.progress:
  866. archive.stats.show_progress(final=True)
  867. archive.stats += fso.stats
  868. if sig_int:
  869. # do not save the archive if the user ctrl-c-ed - it is valid, but incomplete.
  870. # we already have a checkpoint archive in this case.
  871. self.print_error("Got Ctrl-C / SIGINT.")
  872. else:
  873. archive.save(comment=args.comment, timestamp=args.timestamp, stats=archive.stats)
  874. args.stats |= args.json
  875. if args.stats:
  876. if args.json:
  877. json_print(basic_json_data(manifest, cache=cache, extra={
  878. 'archive': archive,
  879. }))
  880. else:
  881. log_multi(DASHES,
  882. str(archive),
  883. DASHES,
  884. STATS_HEADER,
  885. str(archive.stats),
  886. str(cache),
  887. DASHES, logger=logging.getLogger('borg.output.stats'))
  888. self.output_filter = args.output_filter
  889. self.output_list = args.output_list
  890. self.noflags = args.nobsdflags or args.noflags
  891. self.noacls = args.noacls
  892. self.noxattrs = args.noxattrs
  893. self.exclude_nodump = args.exclude_nodump
  894. dry_run = args.dry_run
  895. t0 = datetime.utcnow()
  896. t0_monotonic = time.monotonic()
  897. logger.info('Creating archive at "%s"' % args.location.processed)
  898. if not dry_run:
  899. with Cache(repository, key, manifest, progress=args.progress,
  900. lock_wait=self.lock_wait, permit_adhoc_cache=args.no_cache_sync,
  901. cache_mode=args.files_cache_mode, iec=args.iec) as cache:
  902. archive = Archive(repository, key, manifest, args.location.archive, cache=cache,
  903. create=True, checkpoint_interval=args.checkpoint_interval,
  904. numeric_ids=args.numeric_ids, noatime=not args.atime, noctime=args.noctime,
  905. progress=args.progress,
  906. chunker_params=args.chunker_params, start=t0, start_monotonic=t0_monotonic,
  907. log_json=args.log_json, iec=args.iec)
  908. metadata_collector = MetadataCollector(noatime=not args.atime, noctime=args.noctime,
  909. noflags=args.nobsdflags or args.noflags, noacls=args.noacls, noxattrs=args.noxattrs,
  910. numeric_ids=args.numeric_ids, nobirthtime=args.nobirthtime)
  911. cp = ChunksProcessor(cache=cache, key=key,
  912. add_item=archive.add_item, write_checkpoint=archive.write_checkpoint,
  913. checkpoint_interval=args.checkpoint_interval, rechunkify=False)
  914. fso = FilesystemObjectProcessors(metadata_collector=metadata_collector, cache=cache, key=key,
  915. process_file_chunks=cp.process_file_chunks, add_item=archive.add_item,
  916. chunker_params=args.chunker_params, show_progress=args.progress, sparse=args.sparse,
  917. log_json=args.log_json, iec=args.iec, file_status_printer=self.print_file_status)
  918. create_inner(archive, cache, fso)
  919. else:
  920. create_inner(None, None, None)
  921. return self.exit_code
  922. def _process_any(self, *, path, parent_fd, name, st, fso, cache, read_special, dry_run):
  923. """
  924. Call the right method on the given FilesystemObjectProcessor.
  925. """
  926. if dry_run:
  927. return '-'
  928. elif stat.S_ISREG(st.st_mode):
  929. return fso.process_file(path=path, parent_fd=parent_fd, name=name, st=st, cache=cache)
  930. elif stat.S_ISDIR(st.st_mode):
  931. return fso.process_dir(path=path, parent_fd=parent_fd, name=name, st=st)
  932. elif stat.S_ISLNK(st.st_mode):
  933. if not read_special:
  934. return fso.process_symlink(path=path, parent_fd=parent_fd, name=name, st=st)
  935. else:
  936. try:
  937. st_target = os_stat(path=path, parent_fd=parent_fd, name=name, follow_symlinks=True)
  938. except OSError:
  939. special = False
  940. else:
  941. special = is_special(st_target.st_mode)
  942. if special:
  943. return fso.process_file(path=path, parent_fd=parent_fd, name=name, st=st_target,
  944. cache=cache, flags=flags_special_follow)
  945. else:
  946. return fso.process_symlink(path=path, parent_fd=parent_fd, name=name, st=st)
  947. elif stat.S_ISFIFO(st.st_mode):
  948. if not read_special:
  949. return fso.process_fifo(path=path, parent_fd=parent_fd, name=name, st=st)
  950. else:
  951. return fso.process_file(path=path, parent_fd=parent_fd, name=name, st=st,
  952. cache=cache, flags=flags_special)
  953. elif stat.S_ISCHR(st.st_mode):
  954. if not read_special:
  955. return fso.process_dev(path=path, parent_fd=parent_fd, name=name, st=st, dev_type='c')
  956. else:
  957. return fso.process_file(path=path, parent_fd=parent_fd, name=name, st=st,
  958. cache=cache, flags=flags_special)
  959. elif stat.S_ISBLK(st.st_mode):
  960. if not read_special:
  961. return fso.process_dev(path=path, parent_fd=parent_fd, name=name, st=st, dev_type='b')
  962. else:
  963. return fso.process_file(path=path, parent_fd=parent_fd, name=name, st=st,
  964. cache=cache, flags=flags_special)
  965. elif stat.S_ISSOCK(st.st_mode):
  966. # Ignore unix sockets
  967. return
  968. elif stat.S_ISDOOR(st.st_mode):
  969. # Ignore Solaris doors
  970. return
  971. elif stat.S_ISPORT(st.st_mode):
  972. # Ignore Solaris event ports
  973. return
  974. else:
  975. self.print_warning('Unknown file type: %s', path)
  976. return
  977. def _rec_walk(self, *, path, parent_fd, name, fso, cache, matcher,
  978. exclude_caches, exclude_if_present, keep_exclude_tags,
  979. skip_inodes, restrict_dev, read_special, dry_run):
  980. """
  981. Process *path* (or, preferably, parent_fd/name) recursively according to the various parameters.
  982. This should only raise on critical errors. Per-item errors must be handled within this method.
  983. """
  984. if sig_int and sig_int.action_done():
  985. # the user says "get out of here!" and we have already completed the desired action.
  986. return
  987. status = None
  988. try:
  989. recurse_excluded_dir = False
  990. if matcher.match(path):
  991. with backup_io('stat'):
  992. st = os_stat(path=path, parent_fd=parent_fd, name=name, follow_symlinks=False)
  993. else:
  994. self.print_file_status('x', path)
  995. # get out here as quickly as possible:
  996. # we only need to continue if we shall recurse into an excluded directory.
  997. # if we shall not recurse, then do not even touch (stat()) the item, it
  998. # could trigger an error, e.g. if access is forbidden, see #3209.
  999. if not matcher.recurse_dir:
  1000. return
  1001. recurse_excluded_dir = True
  1002. with backup_io('stat'):
  1003. st = os_stat(path=path, parent_fd=parent_fd, name=name, follow_symlinks=False)
  1004. if not stat.S_ISDIR(st.st_mode):
  1005. return
  1006. if (st.st_ino, st.st_dev) in skip_inodes:
  1007. return
  1008. # if restrict_dev is given, we do not want to recurse into a new filesystem,
  1009. # but we WILL save the mountpoint directory (or more precise: the root
  1010. # directory of the mounted filesystem that shadows the mountpoint dir).
  1011. recurse = restrict_dev is None or st.st_dev == restrict_dev
  1012. if self.exclude_nodump:
  1013. # Ignore if nodump flag is set
  1014. with backup_io('flags'):
  1015. if get_flags(path=path, st=st) & stat.UF_NODUMP:
  1016. self.print_file_status('x', path)
  1017. return
  1018. if not stat.S_ISDIR(st.st_mode):
  1019. # directories cannot go in this branch because they can be excluded based on tag
  1020. # files they might contain
  1021. status = self._process_any(path=path, parent_fd=parent_fd, name=name, st=st, fso=fso, cache=cache,
  1022. read_special=read_special, dry_run=dry_run)
  1023. else:
  1024. with OsOpen(path=path, parent_fd=parent_fd, name=name, flags=flags_dir,
  1025. noatime=True, op='dir_open') as child_fd:
  1026. # child_fd is None for directories on windows, in that case a race condition check is not possible.
  1027. if child_fd is not None:
  1028. with backup_io('fstat'):
  1029. st = stat_update_check(st, os.fstat(child_fd))
  1030. if recurse:
  1031. tag_names = dir_is_tagged(path, exclude_caches, exclude_if_present)
  1032. if tag_names:
  1033. # if we are already recursing in an excluded dir, we do not need to do anything else than
  1034. # returning (we do not need to archive or recurse into tagged directories), see #3991:
  1035. if not recurse_excluded_dir:
  1036. if keep_exclude_tags:
  1037. if not dry_run:
  1038. fso.process_dir_with_fd(path=path, fd=child_fd, st=st)
  1039. for tag_name in tag_names:
  1040. tag_path = os.path.join(path, tag_name)
  1041. self._rec_walk(
  1042. path=tag_path, parent_fd=child_fd, name=tag_name, fso=fso, cache=cache,
  1043. matcher=matcher, exclude_caches=exclude_caches, exclude_if_present=exclude_if_present,
  1044. keep_exclude_tags=keep_exclude_tags, skip_inodes=skip_inodes,
  1045. restrict_dev=restrict_dev, read_special=read_special, dry_run=dry_run)
  1046. self.print_file_status('x', path)
  1047. return
  1048. if not recurse_excluded_dir and not dry_run:
  1049. status = fso.process_dir_with_fd(path=path, fd=child_fd, st=st)
  1050. if recurse:
  1051. with backup_io('scandir'):
  1052. entries = helpers.scandir_inorder(path=path, fd=child_fd)
  1053. for dirent in entries:
  1054. normpath = os.path.normpath(os.path.join(path, dirent.name))
  1055. self._rec_walk(
  1056. path=normpath, parent_fd=child_fd, name=dirent.name, fso=fso, cache=cache, matcher=matcher,
  1057. exclude_caches=exclude_caches, exclude_if_present=exclude_if_present,
  1058. keep_exclude_tags=keep_exclude_tags, skip_inodes=skip_inodes, restrict_dev=restrict_dev,
  1059. read_special=read_special, dry_run=dry_run)
  1060. except (BackupOSError, BackupError) as e:
  1061. self.print_warning('%s: %s', path, e)
  1062. status = 'E'
  1063. if status == 'C':
  1064. self.print_warning('%s: file changed while we backed it up', path)
  1065. if not recurse_excluded_dir:
  1066. self.print_file_status(status, path)
  1067. @staticmethod
  1068. def build_filter(matcher, strip_components):
  1069. if strip_components:
  1070. def item_filter(item):
  1071. matched = matcher.match(item.path) and os.sep.join(item.path.split(os.sep)[strip_components:])
  1072. return matched
  1073. else:
  1074. def item_filter(item):
  1075. matched = matcher.match(item.path)
  1076. return matched
  1077. return item_filter
  1078. @with_repository(compatibility=(Manifest.Operation.READ,))
  1079. @with_archive
  1080. def do_extract(self, args, repository, manifest, key, archive):
  1081. """Extract archive contents"""
  1082. # be restrictive when restoring files, restore permissions later
  1083. if sys.getfilesystemencoding() == 'ascii':
  1084. logger.warning('Warning: File system encoding is "ascii", extracting non-ascii filenames will not be supported.')
  1085. if sys.platform.startswith(('linux', 'freebsd', 'netbsd', 'openbsd', 'darwin', )):
  1086. logger.warning('Hint: You likely need to fix your locale setup. E.g. install locales and use: LANG=en_US.UTF-8')
  1087. matcher = self.build_matcher(args.patterns, args.paths)
  1088. progress = args.progress
  1089. output_list = args.output_list
  1090. dry_run = args.dry_run
  1091. stdout = args.stdout
  1092. sparse = args.sparse
  1093. strip_components = args.strip_components
  1094. dirs = []
  1095. hlm = HardLinkManager(id_type=bytes, info_type=str) # hlid -> path
  1096. filter = self.build_filter(matcher, strip_components)
  1097. if progress:
  1098. pi = ProgressIndicatorPercent(msg='%5.1f%% Extracting: %s', step=0.1, msgid='extract')
  1099. pi.output('Calculating total archive size for the progress indicator (might take long for large archives)')
  1100. extracted_size = sum(item.get_size() for item in archive.iter_items(filter))
  1101. pi.total = extracted_size
  1102. else:
  1103. pi = None
  1104. for item in archive.iter_items(filter, preload=True):
  1105. orig_path = item.path
  1106. if strip_components:
  1107. item.path = os.sep.join(orig_path.split(os.sep)[strip_components:])
  1108. if not args.dry_run:
  1109. while dirs and not item.path.startswith(dirs[-1].path):
  1110. dir_item = dirs.pop(-1)
  1111. try:
  1112. archive.extract_item(dir_item, stdout=stdout)
  1113. except BackupOSError as e:
  1114. self.print_warning('%s: %s', remove_surrogates(dir_item.path), e)
  1115. if output_list:
  1116. logging.getLogger('borg.output.list').info(remove_surrogates(item.path))
  1117. try:
  1118. if dry_run:
  1119. archive.extract_item(item, dry_run=True, hlm=hlm, pi=pi)
  1120. else:
  1121. if stat.S_ISDIR(item.mode):
  1122. dirs.append(item)
  1123. archive.extract_item(item, stdout=stdout, restore_attrs=False)
  1124. else:
  1125. archive.extract_item(item, stdout=stdout, sparse=sparse, hlm=hlm,
  1126. stripped_components=strip_components, original_path=orig_path, pi=pi)
  1127. except (BackupOSError, BackupError) as e:
  1128. self.print_warning('%s: %s', remove_surrogates(orig_path), e)
  1129. if pi:
  1130. pi.finish()
  1131. if not args.dry_run:
  1132. pi = ProgressIndicatorPercent(total=len(dirs), msg='Setting directory permissions %3.0f%%',
  1133. msgid='extract.permissions')
  1134. while dirs:
  1135. pi.show()
  1136. dir_item = dirs.pop(-1)
  1137. try:
  1138. archive.extract_item(dir_item, stdout=stdout)
  1139. except BackupOSError as e:
  1140. self.print_warning('%s: %s', remove_surrogates(dir_item.path), e)
  1141. for pattern in matcher.get_unmatched_include_patterns():
  1142. self.print_warning("Include pattern '%s' never matched.", pattern)
  1143. if pi:
  1144. # clear progress output
  1145. pi.finish()
  1146. return self.exit_code
  1147. @with_repository(compatibility=(Manifest.Operation.READ,))
  1148. @with_archive
  1149. def do_export_tar(self, args, repository, manifest, key, archive):
  1150. """Export archive contents as a tarball"""
  1151. self.output_list = args.output_list
  1152. # A quick note about the general design of tar_filter and tarfile;
  1153. # The tarfile module of Python can provide some compression mechanisms
  1154. # by itself, using the builtin gzip, bz2 and lzma modules (and "tarmodes"
  1155. # such as "w:xz").
  1156. #
  1157. # Doing so would have three major drawbacks:
  1158. # For one the compressor runs on the same thread as the program using the
  1159. # tarfile, stealing valuable CPU time from Borg and thus reducing throughput.
  1160. # Then this limits the available options - what about lz4? Brotli? zstd?
  1161. # The third issue is that systems can ship more optimized versions than those
  1162. # built into Python, e.g. pigz or pxz, which can use more than one thread for
  1163. # compression.
  1164. #
  1165. # Therefore we externalize compression by using a filter program, which has
  1166. # none of these drawbacks. The only issue of using an external filter is
  1167. # that it has to be installed -- hardly a problem, considering that
  1168. # the decompressor must be installed as well to make use of the exported tarball!
  1169. filter = get_tar_filter(args.tarfile, decompress=False) if args.tar_filter == 'auto' else args.tar_filter
  1170. tarstream = dash_open(args.tarfile, 'wb')
  1171. tarstream_close = args.tarfile != '-'
  1172. with create_filter_process(filter, stream=tarstream, stream_close=tarstream_close, inbound=False) as _stream:
  1173. self._export_tar(args, archive, _stream)
  1174. return self.exit_code
  1175. def _export_tar(self, args, archive, tarstream):
  1176. matcher = self.build_matcher(args.patterns, args.paths)
  1177. progress = args.progress
  1178. output_list = args.output_list
  1179. strip_components = args.strip_components
  1180. hlm = HardLinkManager(id_type=bytes, info_type=str) # hlid -> path
  1181. filter = self.build_filter(matcher, strip_components)
  1182. # The | (pipe) symbol instructs tarfile to use a streaming mode of operation
  1183. # where it never seeks on the passed fileobj.
  1184. tar_format = dict(GNU=tarfile.GNU_FORMAT, PAX=tarfile.PAX_FORMAT, BORG=tarfile.PAX_FORMAT)[args.tar_format]
  1185. tar = tarfile.open(fileobj=tarstream, mode='w|', format=tar_format)
  1186. if progress:
  1187. pi = ProgressIndicatorPercent(msg='%5.1f%% Processing: %s', step=0.1, msgid='extract')
  1188. pi.output('Calculating size')
  1189. extracted_size = sum(item.get_size() for item in archive.iter_items(filter))
  1190. pi.total = extracted_size
  1191. else:
  1192. pi = None
  1193. def item_content_stream(item):
  1194. """
  1195. Return a file-like object that reads from the chunks of *item*.
  1196. """
  1197. chunk_iterator = archive.pipeline.fetch_many([chunk_id for chunk_id, _, _ in item.chunks],
  1198. is_preloaded=True)
  1199. if pi:
  1200. info = [remove_surrogates(item.path)]
  1201. return ChunkIteratorFileWrapper(chunk_iterator,
  1202. lambda read_bytes: pi.show(increase=len(read_bytes), info=info))
  1203. else:
  1204. return ChunkIteratorFileWrapper(chunk_iterator)
  1205. def item_to_tarinfo(item, original_path):
  1206. """
  1207. Transform a Borg *item* into a tarfile.TarInfo object.
  1208. Return a tuple (tarinfo, stream), where stream may be a file-like object that represents
  1209. the file contents, if any, and is None otherwise. When *tarinfo* is None, the *item*
  1210. cannot be represented as a TarInfo object and should be skipped.
  1211. """
  1212. stream = None
  1213. tarinfo = tarfile.TarInfo()
  1214. tarinfo.name = item.path
  1215. tarinfo.mtime = item.mtime / 1e9
  1216. tarinfo.mode = stat.S_IMODE(item.mode)
  1217. tarinfo.uid = item.uid
  1218. tarinfo.gid = item.gid
  1219. tarinfo.uname = item.user or ''
  1220. tarinfo.gname = item.group or ''
  1221. # The linkname in tar has 2 uses:
  1222. # for symlinks it means the destination, while for hardlinks it refers to the file.
  1223. # Since hardlinks in tar have a different type code (LNKTYPE) the format might
  1224. # support hardlinking arbitrary objects (including symlinks and directories), but
  1225. # whether implementations actually support that is a whole different question...
  1226. tarinfo.linkname = ""
  1227. modebits = stat.S_IFMT(item.mode)
  1228. if modebits == stat.S_IFREG:
  1229. tarinfo.type = tarfile.REGTYPE
  1230. if 'hlid' in item:
  1231. linkname = hlm.retrieve(id=item.hlid)
  1232. if linkname is not None:
  1233. # the first hardlink was already added to the archive, add a tar-hardlink reference to it.
  1234. tarinfo.type = tarfile.LNKTYPE
  1235. tarinfo.linkname = linkname
  1236. else:
  1237. tarinfo.size = item.get_size()
  1238. stream = item_content_stream(item)
  1239. hlm.remember(id=item.hlid, info=item.path)
  1240. else:
  1241. tarinfo.size = item.get_size()
  1242. stream = item_content_stream(item)
  1243. elif modebits == stat.S_IFDIR:
  1244. tarinfo.type = tarfile.DIRTYPE
  1245. elif modebits == stat.S_IFLNK:
  1246. tarinfo.type = tarfile.SYMTYPE
  1247. tarinfo.linkname = item.source
  1248. elif modebits == stat.S_IFBLK:
  1249. tarinfo.type = tarfile.BLKTYPE
  1250. tarinfo.devmajor = os.major(item.rdev)
  1251. tarinfo.devminor = os.minor(item.rdev)
  1252. elif modebits == stat.S_IFCHR:
  1253. tarinfo.type = tarfile.CHRTYPE
  1254. tarinfo.devmajor = os.major(item.rdev)
  1255. tarinfo.devminor = os.minor(item.rdev)
  1256. elif modebits == stat.S_IFIFO:
  1257. tarinfo.type = tarfile.FIFOTYPE
  1258. else:
  1259. self.print_warning('%s: unsupported file type %o for tar export', remove_surrogates(item.path), modebits)
  1260. set_ec(EXIT_WARNING)
  1261. return None, stream
  1262. return tarinfo, stream
  1263. def item_to_paxheaders(format, item):
  1264. """
  1265. Transform (parts of) a Borg *item* into a pax_headers dict.
  1266. """
  1267. # PAX format
  1268. # ----------
  1269. # When using the PAX (POSIX) format, we can support some things that aren't possible
  1270. # with classic tar formats, including GNU tar, such as:
  1271. # - atime, ctime (DONE)
  1272. # - possibly Linux capabilities, security.* xattrs (TODO)
  1273. # - various additions supported by GNU tar in POSIX mode (TODO)
  1274. #
  1275. # BORG format
  1276. # -----------
  1277. # This is based on PAX, but additionally adds BORG.* pax headers.
  1278. # Additionally to the standard tar / PAX metadata and data, it transfers
  1279. # ALL borg item metadata in a BORG specific way.
  1280. #
  1281. ph = {}
  1282. # note: for mtime this is a bit redundant as it is already done by tarfile module,
  1283. # but we just do it in our way to be consistent for sure.
  1284. for name in 'atime', 'ctime', 'mtime':
  1285. if hasattr(item, name):
  1286. ns = getattr(item, name)
  1287. ph[name] = str(ns / 1e9)
  1288. if format == 'BORG': # BORG format additions
  1289. ph['BORG.item.version'] = '1'
  1290. # BORG.item.meta - just serialize all metadata we have:
  1291. meta_bin = msgpack.packb(item.as_dict())
  1292. meta_text = base64.b64encode(meta_bin).decode()
  1293. ph['BORG.item.meta'] = meta_text
  1294. return ph
  1295. for item in archive.iter_items(filter, preload=True):
  1296. orig_path = item.path
  1297. if strip_components:
  1298. item.path = os.sep.join(orig_path.split(os.sep)[strip_components:])
  1299. tarinfo, stream = item_to_tarinfo(item, orig_path)
  1300. if tarinfo:
  1301. if args.tar_format in ('BORG', 'PAX'):
  1302. tarinfo.pax_headers = item_to_paxheaders(args.tar_format, item)
  1303. if output_list:
  1304. logging.getLogger('borg.output.list').info(remove_surrogates(orig_path))
  1305. tar.addfile(tarinfo, stream)
  1306. if pi:
  1307. pi.finish()
  1308. # This does not close the fileobj (tarstream) we passed to it -- a side effect of the | mode.
  1309. tar.close()
  1310. for pattern in matcher.get_unmatched_include_patterns():
  1311. self.print_warning("Include pattern '%s' never matched.", pattern)
  1312. return self.exit_code
  1313. @with_repository(compatibility=(Manifest.Operation.READ,))
  1314. @with_archive
  1315. def do_diff(self, args, repository, manifest, key, archive):
  1316. """Diff contents of two archives"""
  1317. def print_json_output(diff, path):
  1318. print(json.dumps({"path": path, "changes": [j for j, str in diff]}))
  1319. def print_text_output(diff, path):
  1320. print("{:<19} {}".format(' '.join([str for j, str in diff]), path))
  1321. print_output = print_json_output if args.json_lines else print_text_output
  1322. archive1 = archive
  1323. archive2 = Archive(repository, key, manifest, args.archive2,
  1324. consider_part_files=args.consider_part_files)
  1325. can_compare_chunk_ids = archive1.metadata.get('chunker_params', False) == archive2.metadata.get(
  1326. 'chunker_params', True) or args.same_chunker_params
  1327. if not can_compare_chunk_ids:
  1328. self.print_warning('--chunker-params might be different between archives, diff will be slow.\n'
  1329. 'If you know for certain that they are the same, pass --same-chunker-params '
  1330. 'to override this check.')
  1331. matcher = self.build_matcher(args.patterns, args.paths)
  1332. diffs = Archive.compare_archives_iter(archive1, archive2, matcher, can_compare_chunk_ids=can_compare_chunk_ids)
  1333. # Conversion to string and filtering for diff.equal to save memory if sorting
  1334. diffs = ((path, diff.changes()) for path, diff in diffs if not diff.equal)
  1335. if args.sort:
  1336. diffs = sorted(diffs)
  1337. for path, diff in diffs:
  1338. print_output(diff, path)
  1339. for pattern in matcher.get_unmatched_include_patterns():
  1340. self.print_warning("Include pattern '%s' never matched.", pattern)
  1341. return self.exit_code
  1342. @with_repository(exclusive=True, cache=True, compatibility=(Manifest.Operation.CHECK,))
  1343. @with_archive
  1344. def do_rename(self, args, repository, manifest, key, cache, archive):
  1345. """Rename an existing archive"""
  1346. archive.rename(args.name)
  1347. manifest.write()
  1348. repository.commit(compact=False)
  1349. cache.commit()
  1350. return self.exit_code
  1351. @with_repository(exclusive=True, manifest=False)
  1352. def do_delete(self, args, repository):
  1353. """Delete an existing repository or archives"""
  1354. archive_filter_specified = any((args.first, args.last, args.prefix is not None, args.glob_archives))
  1355. explicit_archives_specified = args.location.archive or args.archives
  1356. self.output_list = args.output_list
  1357. if archive_filter_specified and explicit_archives_specified:
  1358. self.print_error('Mixing archive filters and explicitly named archives is not supported.')
  1359. return self.exit_code
  1360. if archive_filter_specified or explicit_archives_specified:
  1361. return self._delete_archives(args, repository)
  1362. else:
  1363. return self._delete_repository(args, repository)
  1364. def _delete_archives(self, args, repository):
  1365. """Delete archives"""
  1366. dry_run = args.dry_run
  1367. manifest, key = Manifest.load(repository, (Manifest.Operation.DELETE,))
  1368. if args.location.archive or args.archives:
  1369. archives = list(args.archives)
  1370. if args.location.archive:
  1371. archives.insert(0, args.location.archive)
  1372. archive_names = tuple(archives)
  1373. else:
  1374. args.consider_checkpoints = True
  1375. archive_names = tuple(x.name for x in manifest.archives.list_considering(args))
  1376. if not archive_names:
  1377. return self.exit_code
  1378. if args.forced == 2:
  1379. deleted = False
  1380. logger_list = logging.getLogger('borg.output.list')
  1381. for i, archive_name in enumerate(archive_names, 1):
  1382. try:
  1383. current_archive = manifest.archives.pop(archive_name)
  1384. except KeyError:
  1385. self.exit_code = EXIT_WARNING
  1386. logger.warning(f'Archive {archive_name} not found ({i}/{len(archive_names)}).')
  1387. else:
  1388. deleted = True
  1389. if self.output_list:
  1390. msg = 'Would delete: {} ({}/{})' if dry_run else 'Deleted archive: {} ({}/{})'
  1391. logger_list.info(msg.format(format_archive(current_archive),
  1392. i, len(archive_names)))
  1393. if dry_run:
  1394. logger.info('Finished dry-run.')
  1395. elif deleted:
  1396. manifest.write()
  1397. # note: might crash in compact() after committing the repo
  1398. repository.commit(compact=False)
  1399. logger.warning('Done. Run "borg check --repair" to clean up the mess.')
  1400. else:
  1401. logger.warning('Aborted.')
  1402. return self.exit_code
  1403. stats = Statistics(iec=args.iec)
  1404. with Cache(repository, key, manifest, progress=args.progress, lock_wait=self.lock_wait, iec=args.iec) as cache:
  1405. msg_delete = 'Would delete archive: {} ({}/{})' if dry_run else 'Deleting archive: {} ({}/{})'
  1406. msg_not_found = 'Archive {} not found ({}/{}).'
  1407. logger_list = logging.getLogger('borg.output.list')
  1408. delete_count = 0
  1409. for i, archive_name in enumerate(archive_names, 1):
  1410. try:
  1411. archive_info = manifest.archives[archive_name]
  1412. except KeyError:
  1413. logger.warning(msg_not_found.format(archive_name, i, len(archive_names)))
  1414. else:
  1415. if self.output_list:
  1416. logger_list.info(msg_delete.format(format_archive(archive_info), i, len(archive_names)))
  1417. if not dry_run:
  1418. archive = Archive(repository, key, manifest, archive_name, cache=cache,
  1419. consider_part_files=args.consider_part_files)
  1420. archive.delete(stats, progress=args.progress, forced=args.forced)
  1421. delete_count += 1
  1422. if delete_count > 0:
  1423. # only write/commit if we actually changed something, see #6060.
  1424. manifest.write()
  1425. repository.commit(compact=False, save_space=args.save_space)
  1426. cache.commit()
  1427. if args.stats:
  1428. log_multi(DASHES,
  1429. STATS_HEADER,
  1430. stats.summary.format(label='Deleted data:', stats=stats),
  1431. str(cache),
  1432. DASHES, logger=logging.getLogger('borg.output.stats'))
  1433. return self.exit_code
  1434. def _delete_repository(self, args, repository):
  1435. """Delete a repository"""
  1436. dry_run = args.dry_run
  1437. keep_security_info = args.keep_security_info
  1438. if not args.cache_only:
  1439. if args.forced == 0: # without --force, we let the user see the archives list and confirm.
  1440. id = bin_to_hex(repository.id)
  1441. location = repository._location.canonical_path()
  1442. msg = []
  1443. try:
  1444. manifest, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK)
  1445. n_archives = len(manifest.archives)
  1446. msg.append(f"You requested to completely DELETE the following repository "
  1447. f"*including* {n_archives} archives it contains:")
  1448. except NoManifestError:
  1449. n_archives = None
  1450. msg.append("You requested to completely DELETE the following repository "
  1451. "*including* all archives it may contain:")
  1452. msg.append(DASHES)
  1453. msg.append(f"Repository ID: {id}")
  1454. msg.append(f"Location: {location}")
  1455. if self.output_list:
  1456. msg.append("")
  1457. msg.append("Archives:")
  1458. if n_archives is not None:
  1459. if n_archives > 0:
  1460. for archive_info in manifest.archives.list(sort_by=['ts']):
  1461. msg.append(format_archive(archive_info))
  1462. else:
  1463. msg.append("This repository seems to not have any archives.")
  1464. else:
  1465. msg.append("This repository seems to have no manifest, so we can't "
  1466. "tell anything about its contents.")
  1467. msg.append(DASHES)
  1468. msg.append("Type 'YES' if you understand this and want to continue: ")
  1469. msg = '\n'.join(msg)
  1470. if not yes(msg, false_msg="Aborting.", invalid_msg='Invalid answer, aborting.', truish=('YES',),
  1471. retry=False, env_var_override='BORG_DELETE_I_KNOW_WHAT_I_AM_DOING'):
  1472. self.exit_code = EXIT_ERROR
  1473. return self.exit_code
  1474. if not dry_run:
  1475. repository.destroy()
  1476. logger.info("Repository deleted.")
  1477. if not keep_security_info:
  1478. SecurityManager.destroy(repository)
  1479. else:
  1480. logger.info("Would delete repository.")
  1481. logger.info("Would %s security info." % ("keep" if keep_security_info else "delete"))
  1482. if not dry_run:
  1483. Cache.destroy(repository)
  1484. logger.info("Cache deleted.")
  1485. else:
  1486. logger.info("Would delete cache.")
  1487. return self.exit_code
  1488. def do_mount(self, args):
  1489. """Mount archive or an entire repository as a FUSE filesystem"""
  1490. # Perform these checks before opening the repository and asking for a passphrase.
  1491. from .fuse_impl import llfuse, BORG_FUSE_IMPL
  1492. if llfuse is None:
  1493. self.print_error('borg mount not available: no FUSE support, BORG_FUSE_IMPL=%s.' % BORG_FUSE_IMPL)
  1494. return self.exit_code
  1495. if not os.path.isdir(args.mountpoint) or not os.access(args.mountpoint, os.R_OK | os.W_OK | os.X_OK):
  1496. self.print_error('%s: Mountpoint must be a writable directory' % args.mountpoint)
  1497. return self.exit_code
  1498. return self._do_mount(args)
  1499. @with_repository(compatibility=(Manifest.Operation.READ,))
  1500. def _do_mount(self, args, repository, manifest, key):
  1501. from .fuse import FuseOperations
  1502. with cache_if_remote(repository, decrypted_cache=key) as cached_repo:
  1503. operations = FuseOperations(key, repository, manifest, args, cached_repo)
  1504. logger.info("Mounting filesystem")
  1505. try:
  1506. operations.mount(args.mountpoint, args.options, args.foreground)
  1507. except RuntimeError:
  1508. # Relevant error message already printed to stderr by FUSE
  1509. self.exit_code = EXIT_ERROR
  1510. return self.exit_code
  1511. def do_umount(self, args):
  1512. """un-mount the FUSE filesystem"""
  1513. return umount(args.mountpoint)
  1514. @with_repository(compatibility=(Manifest.Operation.READ,))
  1515. def do_list(self, args, repository, manifest, key):
  1516. """List archive or repository contents"""
  1517. if args.location.archive:
  1518. if args.json:
  1519. self.print_error('The --json option is only valid for listing archives, not archive contents.')
  1520. return self.exit_code
  1521. return self._list_archive(args, repository, manifest, key)
  1522. else:
  1523. if args.json_lines:
  1524. self.print_error('The --json-lines option is only valid for listing archive contents, not archives.')
  1525. return self.exit_code
  1526. return self._list_repository(args, repository, manifest, key)
  1527. def _list_archive(self, args, repository, manifest, key):
  1528. matcher = self.build_matcher(args.patterns, args.paths)
  1529. if args.format is not None:
  1530. format = args.format
  1531. elif args.short:
  1532. format = "{path}{NL}"
  1533. else:
  1534. format = "{mode} {user:6} {group:6} {size:8} {mtime} {path}{extra}{NL}"
  1535. def _list_inner(cache):
  1536. archive = Archive(repository, key, manifest, args.location.archive, cache=cache,
  1537. consider_part_files=args.consider_part_files)
  1538. formatter = ItemFormatter(archive, format, json_lines=args.json_lines)
  1539. for item in archive.iter_items(lambda item: matcher.match(item.path)):
  1540. sys.stdout.write(formatter.format_item(item))
  1541. # Only load the cache if it will be used
  1542. if ItemFormatter.format_needs_cache(format):
  1543. with Cache(repository, key, manifest, lock_wait=self.lock_wait) as cache:
  1544. _list_inner(cache)
  1545. else:
  1546. _list_inner(cache=None)
  1547. return self.exit_code
  1548. def _list_repository(self, args, repository, manifest, key):
  1549. if args.format is not None:
  1550. format = args.format
  1551. elif args.short:
  1552. format = "{archive}{NL}"
  1553. else:
  1554. format = "{archive:<36} {time} [{id}]{NL}"
  1555. formatter = ArchiveFormatter(format, repository, manifest, key, json=args.json, iec=args.iec)
  1556. output_data = []
  1557. for archive_info in manifest.archives.list_considering(args):
  1558. if args.json:
  1559. output_data.append(formatter.get_item_data(archive_info))
  1560. else:
  1561. sys.stdout.write(formatter.format_item(archive_info))
  1562. if args.json:
  1563. json_print(basic_json_data(manifest, extra={
  1564. 'archives': output_data
  1565. }))
  1566. return self.exit_code
  1567. @with_repository(cache=True, compatibility=(Manifest.Operation.READ,))
  1568. def do_info(self, args, repository, manifest, key, cache):
  1569. """Show archive details such as disk space used"""
  1570. if any((args.location.archive, args.first, args.last, args.prefix is not None, args.glob_archives)):
  1571. return self._info_archives(args, repository, manifest, key, cache)
  1572. else:
  1573. return self._info_repository(args, repository, manifest, key, cache)
  1574. def _info_archives(self, args, repository, manifest, key, cache):
  1575. def format_cmdline(cmdline):
  1576. return remove_surrogates(' '.join(shlex.quote(x) for x in cmdline))
  1577. if args.location.archive:
  1578. archive_names = (args.location.archive,)
  1579. else:
  1580. args.consider_checkpoints = True
  1581. archive_names = tuple(x.name for x in manifest.archives.list_considering(args))
  1582. output_data = []
  1583. for i, archive_name in enumerate(archive_names, 1):
  1584. archive = Archive(repository, key, manifest, archive_name, cache=cache,
  1585. consider_part_files=args.consider_part_files, iec=args.iec)
  1586. info = archive.info()
  1587. if args.json:
  1588. output_data.append(info)
  1589. else:
  1590. info['duration'] = format_timedelta(timedelta(seconds=info['duration']))
  1591. info['command_line'] = format_cmdline(info['command_line'])
  1592. print(textwrap.dedent("""
  1593. Archive name: {name}
  1594. Archive fingerprint: {id}
  1595. Comment: {comment}
  1596. Hostname: {hostname}
  1597. Username: {username}
  1598. Time (start): {start}
  1599. Time (end): {end}
  1600. Duration: {duration}
  1601. Number of files: {stats[nfiles]}
  1602. Command line: {command_line}
  1603. Utilization of maximum supported archive size: {limits[max_archive_size]:.0%}
  1604. ------------------------------------------------------------------------------
  1605. Original size Compressed size Deduplicated size
  1606. This archive: {stats[original_size]:>20s} {stats[compressed_size]:>20s} {stats[deduplicated_size]:>20s}
  1607. {cache}
  1608. """).strip().format(cache=cache, **info))
  1609. if self.exit_code:
  1610. break
  1611. if not args.json and len(archive_names) - i:
  1612. print()
  1613. if args.json:
  1614. json_print(basic_json_data(manifest, cache=cache, extra={
  1615. 'archives': output_data,
  1616. }))
  1617. return self.exit_code
  1618. def _info_repository(self, args, repository, manifest, key, cache):
  1619. info = basic_json_data(manifest, cache=cache, extra={
  1620. 'security_dir': cache.security_manager.dir,
  1621. })
  1622. if args.json:
  1623. json_print(info)
  1624. else:
  1625. encryption = 'Encrypted: '
  1626. if key.NAME in ('plaintext', 'authenticated'):
  1627. encryption += 'No'
  1628. else:
  1629. encryption += 'Yes (%s)' % key.NAME
  1630. if key.NAME.startswith('key file'):
  1631. encryption += '\nKey file: %s' % key.find_key()
  1632. info['encryption'] = encryption
  1633. print(textwrap.dedent("""
  1634. Repository ID: {id}
  1635. Location: {location}
  1636. {encryption}
  1637. Cache: {cache.path}
  1638. Security dir: {security_dir}
  1639. """).strip().format(
  1640. id=bin_to_hex(repository.id),
  1641. location=repository._location.canonical_path(),
  1642. **info))
  1643. print(DASHES)
  1644. print(STATS_HEADER)
  1645. print(str(cache))
  1646. return self.exit_code
  1647. @with_repository(exclusive=True, compatibility=(Manifest.Operation.DELETE,))
  1648. def do_prune(self, args, repository, manifest, key):
  1649. """Prune repository archives according to specified rules"""
  1650. if not any((args.secondly, args.minutely, args.hourly, args.daily,
  1651. args.weekly, args.monthly, args.yearly, args.within)):
  1652. self.print_error('At least one of the "keep-within", "keep-last", '
  1653. '"keep-secondly", "keep-minutely", "keep-hourly", "keep-daily", '
  1654. '"keep-weekly", "keep-monthly" or "keep-yearly" settings must be specified.')
  1655. return self.exit_code
  1656. if args.prefix is not None:
  1657. args.glob_archives = args.prefix + '*'
  1658. checkpoint_re = r'\.checkpoint(\.\d+)?'
  1659. archives_checkpoints = manifest.archives.list(glob=args.glob_archives,
  1660. consider_checkpoints=True,
  1661. match_end=r'(%s)?\Z' % checkpoint_re,
  1662. sort_by=['ts'], reverse=True)
  1663. is_checkpoint = re.compile(r'(%s)\Z' % checkpoint_re).search
  1664. checkpoints = [arch for arch in archives_checkpoints if is_checkpoint(arch.name)]
  1665. # keep the latest checkpoint, if there is no later non-checkpoint archive
  1666. if archives_checkpoints and checkpoints and archives_checkpoints[0] is checkpoints[0]:
  1667. keep_checkpoints = checkpoints[:1]
  1668. else:
  1669. keep_checkpoints = []
  1670. checkpoints = set(checkpoints)
  1671. # ignore all checkpoint archives to avoid keeping one (which is an incomplete backup)
  1672. # that is newer than a successfully completed backup - and killing the successful backup.
  1673. archives = [arch for arch in archives_checkpoints if arch not in checkpoints]
  1674. keep = []
  1675. # collect the rule responsible for the keeping of each archive in this dict
  1676. # keys are archive ids, values are a tuple
  1677. # (<rulename>, <how many archives were kept by this rule so far >)
  1678. kept_because = {}
  1679. # find archives which need to be kept because of the keep-within rule
  1680. if args.within:
  1681. keep += prune_within(archives, args.within, kept_because)
  1682. # find archives which need to be kept because of the various time period rules
  1683. for rule in PRUNING_PATTERNS.keys():
  1684. num = getattr(args, rule, None)
  1685. if num is not None:
  1686. keep += prune_split(archives, rule, num, kept_because)
  1687. to_delete = (set(archives) | checkpoints) - (set(keep) | set(keep_checkpoints))
  1688. stats = Statistics(iec=args.iec)
  1689. with Cache(repository, key, manifest, lock_wait=self.lock_wait, iec=args.iec) as cache:
  1690. list_logger = logging.getLogger('borg.output.list')
  1691. # set up counters for the progress display
  1692. to_delete_len = len(to_delete)
  1693. archives_deleted = 0
  1694. pi = ProgressIndicatorPercent(total=len(to_delete), msg='Pruning archives %3.0f%%', msgid='prune')
  1695. for archive in archives_checkpoints:
  1696. if archive in to_delete:
  1697. pi.show()
  1698. if args.dry_run:
  1699. log_message = 'Would prune:'
  1700. else:
  1701. archives_deleted += 1
  1702. log_message = 'Pruning archive (%d/%d):' % (archives_deleted, to_delete_len)
  1703. archive = Archive(repository, key, manifest, archive.name, cache,
  1704. consider_part_files=args.consider_part_files)
  1705. archive.delete(stats, forced=args.forced)
  1706. else:
  1707. if is_checkpoint(archive.name):
  1708. log_message = 'Keeping checkpoint archive:'
  1709. else:
  1710. log_message = 'Keeping archive (rule: {rule} #{num}):'.format(
  1711. rule=kept_because[archive.id][0], num=kept_because[archive.id][1]
  1712. )
  1713. if args.output_list:
  1714. list_logger.info("{message:<40} {archive}".format(
  1715. message=log_message, archive=format_archive(archive)
  1716. ))
  1717. pi.finish()
  1718. if to_delete and not args.dry_run:
  1719. manifest.write()
  1720. repository.commit(compact=False, save_space=args.save_space)
  1721. cache.commit()
  1722. if args.stats:
  1723. log_multi(DASHES,
  1724. STATS_HEADER,
  1725. stats.summary.format(label='Deleted data:', stats=stats),
  1726. str(cache),
  1727. DASHES, logger=logging.getLogger('borg.output.stats'))
  1728. return self.exit_code
  1729. @with_repository(fake=('tam', 'disable_tam'), invert_fake=True, manifest=False, exclusive=True)
  1730. def do_upgrade(self, args, repository, manifest=None, key=None):
  1731. """upgrade a repository from a previous version"""
  1732. if args.tam:
  1733. manifest, key = Manifest.load(repository, (Manifest.Operation.CHECK,), force_tam_not_required=args.force)
  1734. if not hasattr(key, 'change_passphrase'):
  1735. print('This repository is not encrypted, cannot enable TAM.')
  1736. return EXIT_ERROR
  1737. if not manifest.tam_verified or not manifest.config.get(b'tam_required', False):
  1738. # The standard archive listing doesn't include the archive ID like in borg 1.1.x
  1739. print('Manifest contents:')
  1740. for archive_info in manifest.archives.list(sort_by=['ts']):
  1741. print(format_archive(archive_info), '[%s]' % bin_to_hex(archive_info.id))
  1742. manifest.config[b'tam_required'] = True
  1743. manifest.write()
  1744. repository.commit(compact=False)
  1745. if not key.tam_required:
  1746. key.tam_required = True
  1747. key.change_passphrase(key._passphrase)
  1748. print('Key updated')
  1749. if hasattr(key, 'find_key'):
  1750. print('Key location:', key.find_key())
  1751. if not tam_required(repository):
  1752. tam_file = tam_required_file(repository)
  1753. open(tam_file, 'w').close()
  1754. print('Updated security database')
  1755. elif args.disable_tam:
  1756. manifest, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK, force_tam_not_required=True)
  1757. if tam_required(repository):
  1758. os.unlink(tam_required_file(repository))
  1759. if key.tam_required:
  1760. key.tam_required = False
  1761. key.change_passphrase(key._passphrase)
  1762. print('Key updated')
  1763. if hasattr(key, 'find_key'):
  1764. print('Key location:', key.find_key())
  1765. manifest.config[b'tam_required'] = False
  1766. manifest.write()
  1767. repository.commit(compact=False)
  1768. else:
  1769. # mainly for upgrades from borg 0.xx -> 1.0.
  1770. repo = BorgRepositoryUpgrader(args.location.path, create=False)
  1771. try:
  1772. repo.upgrade(args.dry_run, inplace=args.inplace, progress=args.progress)
  1773. except NotImplementedError as e:
  1774. print("warning: %s" % e)
  1775. return self.exit_code
  1776. @with_repository(cache=True, exclusive=True, compatibility=(Manifest.Operation.CHECK,))
  1777. def do_recreate(self, args, repository, manifest, key, cache):
  1778. """Re-create archives"""
  1779. matcher = self.build_matcher(args.patterns, args.paths)
  1780. self.output_list = args.output_list
  1781. self.output_filter = args.output_filter
  1782. recompress = args.recompress != 'never'
  1783. always_recompress = args.recompress == 'always'
  1784. recreater = ArchiveRecreater(repository, manifest, key, cache, matcher,
  1785. exclude_caches=args.exclude_caches, exclude_if_present=args.exclude_if_present,
  1786. keep_exclude_tags=args.keep_exclude_tags, chunker_params=args.chunker_params,
  1787. compression=args.compression, recompress=recompress, always_recompress=always_recompress,
  1788. progress=args.progress, stats=args.stats,
  1789. file_status_printer=self.print_file_status,
  1790. checkpoint_interval=args.checkpoint_interval,
  1791. dry_run=args.dry_run, timestamp=args.timestamp)
  1792. if args.location.archive:
  1793. name = args.location.archive
  1794. if recreater.is_temporary_archive(name):
  1795. self.print_error('Refusing to work on temporary archive of prior recreate: %s', name)
  1796. return self.exit_code
  1797. if not recreater.recreate(name, args.comment, args.target):
  1798. self.print_error('Nothing to do. Archive was not processed.\n'
  1799. 'Specify at least one pattern, PATH, --comment, re-compression or re-chunking option.')
  1800. else:
  1801. if args.target is not None:
  1802. self.print_error('--target: Need to specify single archive')
  1803. return self.exit_code
  1804. for archive in manifest.archives.list(sort_by=['ts']):
  1805. name = archive.name
  1806. if recreater.is_temporary_archive(name):
  1807. continue
  1808. print('Processing', name)
  1809. if not recreater.recreate(name, args.comment):
  1810. logger.info('Skipped archive %s: Nothing to do. Archive was not processed.', name)
  1811. if not args.dry_run:
  1812. manifest.write()
  1813. repository.commit(compact=False)
  1814. cache.commit()
  1815. return self.exit_code
  1816. @with_repository(cache=True, exclusive=True, compatibility=(Manifest.Operation.WRITE,))
  1817. def do_import_tar(self, args, repository, manifest, key, cache):
  1818. """Create a backup archive from a tarball"""
  1819. self.output_filter = args.output_filter
  1820. self.output_list = args.output_list
  1821. filter = get_tar_filter(args.tarfile, decompress=True) if args.tar_filter == 'auto' else args.tar_filter
  1822. tarstream = dash_open(args.tarfile, 'rb')
  1823. tarstream_close = args.tarfile != '-'
  1824. with create_filter_process(filter, stream=tarstream, stream_close=tarstream_close, inbound=True) as _stream:
  1825. self._import_tar(args, repository, manifest, key, cache, _stream)
  1826. return self.exit_code
  1827. def _import_tar(self, args, repository, manifest, key, cache, tarstream):
  1828. t0 = datetime.utcnow()
  1829. t0_monotonic = time.monotonic()
  1830. archive = Archive(repository, key, manifest, args.location.archive, cache=cache,
  1831. create=True, checkpoint_interval=args.checkpoint_interval,
  1832. progress=args.progress,
  1833. chunker_params=args.chunker_params, start=t0, start_monotonic=t0_monotonic,
  1834. log_json=args.log_json)
  1835. cp = ChunksProcessor(cache=cache, key=key,
  1836. add_item=archive.add_item, write_checkpoint=archive.write_checkpoint,
  1837. checkpoint_interval=args.checkpoint_interval, rechunkify=False)
  1838. tfo = TarfileObjectProcessors(cache=cache, key=key,
  1839. process_file_chunks=cp.process_file_chunks, add_item=archive.add_item,
  1840. chunker_params=args.chunker_params, show_progress=args.progress,
  1841. log_json=args.log_json, iec=args.iec,
  1842. file_status_printer=self.print_file_status)
  1843. tar = tarfile.open(fileobj=tarstream, mode='r|')
  1844. while True:
  1845. tarinfo = tar.next()
  1846. if not tarinfo:
  1847. break
  1848. if tarinfo.isreg():
  1849. status = tfo.process_file(tarinfo=tarinfo, status='A', type=stat.S_IFREG, tar=tar)
  1850. archive.stats.nfiles += 1
  1851. elif tarinfo.isdir():
  1852. status = tfo.process_dir(tarinfo=tarinfo, status='d', type=stat.S_IFDIR)
  1853. elif tarinfo.issym():
  1854. status = tfo.process_symlink(tarinfo=tarinfo, status='s', type=stat.S_IFLNK)
  1855. elif tarinfo.islnk():
  1856. # tar uses a hardlink model like: the first instance of a hardlink is stored as a regular file,
  1857. # later instances are special entries referencing back to the first instance.
  1858. status = tfo.process_hardlink(tarinfo=tarinfo, status='h', type=stat.S_IFREG)
  1859. elif tarinfo.isblk():
  1860. status = tfo.process_dev(tarinfo=tarinfo, status='b', type=stat.S_IFBLK)
  1861. elif tarinfo.ischr():
  1862. status = tfo.process_dev(tarinfo=tarinfo, status='c', type=stat.S_IFCHR)
  1863. elif tarinfo.isfifo():
  1864. status = tfo.process_fifo(tarinfo=tarinfo, status='f', type=stat.S_IFIFO)
  1865. else:
  1866. status = 'E'
  1867. self.print_warning('%s: Unsupported tarinfo type %s', tarinfo.name, tarinfo.type)
  1868. self.print_file_status(status, tarinfo.name)
  1869. # This does not close the fileobj (tarstream) we passed to it -- a side effect of the | mode.
  1870. tar.close()
  1871. if args.progress:
  1872. archive.stats.show_progress(final=True)
  1873. archive.stats += tfo.stats
  1874. archive.save(comment=args.comment, timestamp=args.timestamp)
  1875. args.stats |= args.json
  1876. if args.stats:
  1877. if args.json:
  1878. json_print(basic_json_data(archive.manifest, cache=archive.cache, extra={
  1879. 'archive': archive,
  1880. }))
  1881. else:
  1882. log_multi(DASHES,
  1883. str(archive),
  1884. DASHES,
  1885. STATS_HEADER,
  1886. str(archive.stats),
  1887. str(archive.cache),
  1888. DASHES, logger=logging.getLogger('borg.output.stats'))
  1889. @with_repository(manifest=False, exclusive=True)
  1890. def do_with_lock(self, args, repository):
  1891. """run a user specified command with the repository lock held"""
  1892. # for a new server, this will immediately take an exclusive lock.
  1893. # to support old servers, that do not have "exclusive" arg in open()
  1894. # RPC API, we also do it the old way:
  1895. # re-write manifest to start a repository transaction - this causes a
  1896. # lock upgrade to exclusive for remote (and also for local) repositories.
  1897. # by using manifest=False in the decorator, we avoid having to require
  1898. # the encryption key (and can operate just with encrypted data).
  1899. data = repository.get(Manifest.MANIFEST_ID)
  1900. repository.put(Manifest.MANIFEST_ID, data)
  1901. # usually, a 0 byte (open for writing) segment file would be visible in the filesystem here.
  1902. # we write and close this file, to rather have a valid segment file on disk, before invoking the subprocess.
  1903. # we can only do this for local repositories (with .io), though:
  1904. if hasattr(repository, 'io'):
  1905. repository.io.close_segment()
  1906. env = prepare_subprocess_env(system=True)
  1907. try:
  1908. # we exit with the return code we get from the subprocess
  1909. return subprocess.call([args.command] + args.args, env=env)
  1910. finally:
  1911. # we need to commit the "no change" operation we did to the manifest
  1912. # because it created a new segment file in the repository. if we would
  1913. # roll back, the same file would be later used otherwise (for other content).
  1914. # that would be bad if somebody uses rsync with ignore-existing (or
  1915. # any other mechanism relying on existing segment data not changing).
  1916. # see issue #1867.
  1917. repository.commit(compact=False)
  1918. @with_repository(manifest=False, exclusive=True)
  1919. def do_compact(self, args, repository):
  1920. """compact segment files in the repository"""
  1921. # see the comment in do_with_lock about why we do it like this:
  1922. data = repository.get(Manifest.MANIFEST_ID)
  1923. repository.put(Manifest.MANIFEST_ID, data)
  1924. threshold = args.threshold / 100
  1925. repository.commit(compact=True, threshold=threshold, cleanup_commits=args.cleanup_commits)
  1926. return EXIT_SUCCESS
  1927. @with_repository(exclusive=True, manifest=False)
  1928. def do_config(self, args, repository):
  1929. """get, set, and delete values in a repository or cache config file"""
  1930. def repo_validate(section, name, value=None, check_value=True):
  1931. if section not in ['repository', ]:
  1932. raise ValueError('Invalid section')
  1933. if name in ['segments_per_dir', 'last_segment_checked', ]:
  1934. if check_value:
  1935. try:
  1936. int(value)
  1937. except ValueError:
  1938. raise ValueError('Invalid value') from None
  1939. elif name in ['max_segment_size', 'additional_free_space', 'storage_quota', ]:
  1940. if check_value:
  1941. try:
  1942. parse_file_size(value)
  1943. except ValueError:
  1944. raise ValueError('Invalid value') from None
  1945. if name == 'storage_quota':
  1946. if parse_file_size(value) < parse_file_size('10M'):
  1947. raise ValueError('Invalid value: storage_quota < 10M')
  1948. elif name == 'max_segment_size':
  1949. if parse_file_size(value) >= MAX_SEGMENT_SIZE_LIMIT:
  1950. raise ValueError('Invalid value: max_segment_size >= %d' % MAX_SEGMENT_SIZE_LIMIT)
  1951. elif name in ['append_only', ]:
  1952. if check_value and value not in ['0', '1']:
  1953. raise ValueError('Invalid value')
  1954. elif name in ['id', ]:
  1955. if check_value:
  1956. try:
  1957. bin_id = unhexlify(value)
  1958. except:
  1959. raise ValueError('Invalid value, must be 64 hex digits') from None
  1960. if len(bin_id) != 32:
  1961. raise ValueError('Invalid value, must be 64 hex digits')
  1962. else:
  1963. raise ValueError('Invalid name')
  1964. def cache_validate(section, name, value=None, check_value=True):
  1965. if section not in ['cache', ]:
  1966. raise ValueError('Invalid section')
  1967. if name in ['previous_location', ]:
  1968. if check_value:
  1969. Location(value)
  1970. else:
  1971. raise ValueError('Invalid name')
  1972. def list_config(config):
  1973. default_values = {
  1974. 'version': '1',
  1975. 'segments_per_dir': str(DEFAULT_SEGMENTS_PER_DIR),
  1976. 'max_segment_size': str(MAX_SEGMENT_SIZE_LIMIT),
  1977. 'additional_free_space': '0',
  1978. 'storage_quota': repository.storage_quota,
  1979. 'append_only': repository.append_only
  1980. }
  1981. print('[repository]')
  1982. for key in ['version', 'segments_per_dir', 'max_segment_size',
  1983. 'storage_quota', 'additional_free_space', 'append_only',
  1984. 'id']:
  1985. value = config.get('repository', key, fallback=False)
  1986. if value is None:
  1987. value = default_values.get(key)
  1988. if value is None:
  1989. raise Error('The repository config is missing the %s key which has no default value' % key)
  1990. print(f'{key} = {value}')
  1991. for key in ['last_segment_checked', ]:
  1992. value = config.get('repository', key, fallback=None)
  1993. if value is None:
  1994. continue
  1995. print(f'{key} = {value}')
  1996. if not args.list:
  1997. if args.name is None:
  1998. self.print_error('No config key name was provided.')
  1999. return self.exit_code
  2000. try:
  2001. section, name = args.name.split('.')
  2002. except ValueError:
  2003. section = args.cache and "cache" or "repository"
  2004. name = args.name
  2005. if args.cache:
  2006. manifest, key = Manifest.load(repository, (Manifest.Operation.WRITE,))
  2007. assert_secure(repository, manifest, self.lock_wait)
  2008. cache = Cache(repository, key, manifest, lock_wait=self.lock_wait)
  2009. try:
  2010. if args.cache:
  2011. cache.cache_config.load()
  2012. config = cache.cache_config._config
  2013. save = cache.cache_config.save
  2014. validate = cache_validate
  2015. else:
  2016. config = repository.config
  2017. save = lambda: repository.save_config(repository.path, repository.config) # noqa
  2018. validate = repo_validate
  2019. if args.delete:
  2020. validate(section, name, check_value=False)
  2021. config.remove_option(section, name)
  2022. if len(config.options(section)) == 0:
  2023. config.remove_section(section)
  2024. save()
  2025. elif args.list:
  2026. list_config(config)
  2027. elif args.value:
  2028. validate(section, name, args.value)
  2029. if section not in config.sections():
  2030. config.add_section(section)
  2031. config.set(section, name, args.value)
  2032. save()
  2033. else:
  2034. try:
  2035. print(config.get(section, name))
  2036. except (configparser.NoOptionError, configparser.NoSectionError) as e:
  2037. print(e, file=sys.stderr)
  2038. return EXIT_WARNING
  2039. return EXIT_SUCCESS
  2040. finally:
  2041. if args.cache:
  2042. cache.close()
  2043. def do_debug_info(self, args):
  2044. """display system information for debugging / bug reports"""
  2045. print(sysinfo())
  2046. # Additional debug information
  2047. print('CRC implementation:', crc32.__name__)
  2048. print('Process ID:', get_process_id())
  2049. return EXIT_SUCCESS
  2050. @with_repository(compatibility=Manifest.NO_OPERATION_CHECK)
  2051. def do_debug_dump_archive_items(self, args, repository, manifest, key):
  2052. """dump (decrypted, decompressed) archive items metadata (not: data)"""
  2053. archive = Archive(repository, key, manifest, args.location.archive,
  2054. consider_part_files=args.consider_part_files)
  2055. for i, item_id in enumerate(archive.metadata.items):
  2056. data = key.decrypt(item_id, repository.get(item_id))
  2057. filename = '%06d_%s.items' % (i, bin_to_hex(item_id))
  2058. print('Dumping', filename)
  2059. with open(filename, 'wb') as fd:
  2060. fd.write(data)
  2061. print('Done.')
  2062. return EXIT_SUCCESS
  2063. @with_repository(compatibility=Manifest.NO_OPERATION_CHECK)
  2064. def do_debug_dump_archive(self, args, repository, manifest, key):
  2065. """dump decoded archive metadata (not: data)"""
  2066. try:
  2067. archive_meta_orig = manifest.archives.get_raw_dict()[safe_encode(args.location.archive)]
  2068. except KeyError:
  2069. raise Archive.DoesNotExist(args.location.archive)
  2070. indent = 4
  2071. def do_indent(d):
  2072. return textwrap.indent(json.dumps(d, indent=indent), prefix=' ' * indent)
  2073. def output(fd):
  2074. # this outputs megabytes of data for a modest sized archive, so some manual streaming json output
  2075. fd.write('{\n')
  2076. fd.write(' "_name": ' + json.dumps(args.location.archive) + ",\n")
  2077. fd.write(' "_manifest_entry":\n')
  2078. fd.write(do_indent(prepare_dump_dict(archive_meta_orig)))
  2079. fd.write(',\n')
  2080. data = key.decrypt(archive_meta_orig[b'id'], repository.get(archive_meta_orig[b'id']))
  2081. archive_org_dict = msgpack.unpackb(data, object_hook=StableDict)
  2082. fd.write(' "_meta":\n')
  2083. fd.write(do_indent(prepare_dump_dict(archive_org_dict)))
  2084. fd.write(',\n')
  2085. fd.write(' "_items": [\n')
  2086. unpacker = msgpack.Unpacker(use_list=False, object_hook=StableDict)
  2087. first = True
  2088. for item_id in archive_org_dict[b'items']:
  2089. data = key.decrypt(item_id, repository.get(item_id))
  2090. unpacker.feed(data)
  2091. for item in unpacker:
  2092. item = prepare_dump_dict(item)
  2093. if first:
  2094. first = False
  2095. else:
  2096. fd.write(',\n')
  2097. fd.write(do_indent(item))
  2098. fd.write('\n')
  2099. fd.write(' ]\n}\n')
  2100. with dash_open(args.path, 'w') as fd:
  2101. output(fd)
  2102. return EXIT_SUCCESS
  2103. @with_repository(compatibility=Manifest.NO_OPERATION_CHECK)
  2104. def do_debug_dump_manifest(self, args, repository, manifest, key):
  2105. """dump decoded repository manifest"""
  2106. data = key.decrypt(manifest.MANIFEST_ID, repository.get(manifest.MANIFEST_ID))
  2107. meta = prepare_dump_dict(msgpack.unpackb(data, object_hook=StableDict))
  2108. with dash_open(args.path, 'w') as fd:
  2109. json.dump(meta, fd, indent=4)
  2110. return EXIT_SUCCESS
  2111. @with_repository(manifest=False)
  2112. def do_debug_dump_repo_objs(self, args, repository):
  2113. """dump (decrypted, decompressed) repo objects, repo index MUST be current/correct"""
  2114. from .crypto.key import key_factory
  2115. def decrypt_dump(i, id, cdata, tag=None, segment=None, offset=None):
  2116. if cdata is not None:
  2117. data = key.decrypt(id, cdata)
  2118. else:
  2119. data = b''
  2120. tag_str = '' if tag is None else '_' + tag
  2121. segment_str = '_' + str(segment) if segment is not None else ''
  2122. offset_str = '_' + str(offset) if offset is not None else ''
  2123. id_str = '_' + bin_to_hex(id) if id is not None else ''
  2124. filename = '%08d%s%s%s%s.obj' % (i, segment_str, offset_str, tag_str, id_str)
  2125. print('Dumping', filename)
  2126. with open(filename, 'wb') as fd:
  2127. fd.write(data)
  2128. if args.ghost:
  2129. # dump ghosty stuff from segment files: not yet committed objects, deleted / superseded objects, commit tags
  2130. # set up the key without depending on a manifest obj
  2131. for id, cdata, tag, segment, offset in repository.scan_low_level():
  2132. if tag == TAG_PUT:
  2133. key = key_factory(repository, cdata)
  2134. break
  2135. i = 0
  2136. for id, cdata, tag, segment, offset in repository.scan_low_level():
  2137. if tag == TAG_PUT:
  2138. decrypt_dump(i, id, cdata, tag='put', segment=segment, offset=offset)
  2139. elif tag == TAG_DELETE:
  2140. decrypt_dump(i, id, None, tag='del', segment=segment, offset=offset)
  2141. elif tag == TAG_COMMIT:
  2142. decrypt_dump(i, None, None, tag='commit', segment=segment, offset=offset)
  2143. i += 1
  2144. else:
  2145. # set up the key without depending on a manifest obj
  2146. ids = repository.list(limit=1, marker=None)
  2147. cdata = repository.get(ids[0])
  2148. key = key_factory(repository, cdata)
  2149. marker = None
  2150. i = 0
  2151. while True:
  2152. result = repository.scan(limit=LIST_SCAN_LIMIT, marker=marker) # must use on-disk order scanning here
  2153. if not result:
  2154. break
  2155. marker = result[-1]
  2156. for id in result:
  2157. cdata = repository.get(id)
  2158. decrypt_dump(i, id, cdata)
  2159. i += 1
  2160. print('Done.')
  2161. return EXIT_SUCCESS
  2162. @with_repository(manifest=False)
  2163. def do_debug_search_repo_objs(self, args, repository):
  2164. """search for byte sequences in repo objects, repo index MUST be current/correct"""
  2165. context = 32
  2166. def print_finding(info, wanted, data, offset):
  2167. before = data[offset - context:offset]
  2168. after = data[offset + len(wanted):offset + len(wanted) + context]
  2169. print('{}: {} {} {} == {!r} {!r} {!r}'.format(info, before.hex(), wanted.hex(), after.hex(),
  2170. before, wanted, after))
  2171. wanted = args.wanted
  2172. try:
  2173. if wanted.startswith('hex:'):
  2174. wanted = unhexlify(wanted[4:])
  2175. elif wanted.startswith('str:'):
  2176. wanted = wanted[4:].encode()
  2177. else:
  2178. raise ValueError('unsupported search term')
  2179. except (ValueError, UnicodeEncodeError):
  2180. wanted = None
  2181. if not wanted:
  2182. self.print_error('search term needs to be hex:123abc or str:foobar style')
  2183. return EXIT_ERROR
  2184. from .crypto.key import key_factory
  2185. # set up the key without depending on a manifest obj
  2186. ids = repository.list(limit=1, marker=None)
  2187. cdata = repository.get(ids[0])
  2188. key = key_factory(repository, cdata)
  2189. marker = None
  2190. last_data = b''
  2191. last_id = None
  2192. i = 0
  2193. while True:
  2194. result = repository.scan(limit=LIST_SCAN_LIMIT, marker=marker) # must use on-disk order scanning here
  2195. if not result:
  2196. break
  2197. marker = result[-1]
  2198. for id in result:
  2199. cdata = repository.get(id)
  2200. data = key.decrypt(id, cdata)
  2201. # try to locate wanted sequence crossing the border of last_data and data
  2202. boundary_data = last_data[-(len(wanted) - 1):] + data[:len(wanted) - 1]
  2203. if wanted in boundary_data:
  2204. boundary_data = last_data[-(len(wanted) - 1 + context):] + data[:len(wanted) - 1 + context]
  2205. offset = boundary_data.find(wanted)
  2206. info = '%d %s | %s' % (i, last_id.hex(), id.hex())
  2207. print_finding(info, wanted, boundary_data, offset)
  2208. # try to locate wanted sequence in data
  2209. count = data.count(wanted)
  2210. if count:
  2211. offset = data.find(wanted) # only determine first occurrence's offset
  2212. info = "%d %s #%d" % (i, id.hex(), count)
  2213. print_finding(info, wanted, data, offset)
  2214. last_id, last_data = id, data
  2215. i += 1
  2216. if i % 10000 == 0:
  2217. print('%d objects processed.' % i)
  2218. print('Done.')
  2219. return EXIT_SUCCESS
  2220. @with_repository(manifest=False)
  2221. def do_debug_get_obj(self, args, repository):
  2222. """get object contents from the repository and write it into file"""
  2223. hex_id = args.id
  2224. try:
  2225. id = unhexlify(hex_id)
  2226. except ValueError:
  2227. print("object id %s is invalid." % hex_id)
  2228. else:
  2229. try:
  2230. data = repository.get(id)
  2231. except Repository.ObjectNotFound:
  2232. print("object %s not found." % hex_id)
  2233. else:
  2234. with open(args.path, "wb") as f:
  2235. f.write(data)
  2236. print("object %s fetched." % hex_id)
  2237. return EXIT_SUCCESS
  2238. @with_repository(manifest=False, exclusive=True)
  2239. def do_debug_put_obj(self, args, repository):
  2240. """put file(s) contents into the repository"""
  2241. for path in args.paths:
  2242. with open(path, "rb") as f:
  2243. data = f.read()
  2244. h = hashlib.sha256(data) # XXX hardcoded
  2245. repository.put(h.digest(), data)
  2246. print("object %s put." % h.hexdigest())
  2247. repository.commit(compact=False)
  2248. return EXIT_SUCCESS
  2249. @with_repository(manifest=False, exclusive=True)
  2250. def do_debug_delete_obj(self, args, repository):
  2251. """delete the objects with the given IDs from the repo"""
  2252. modified = False
  2253. for hex_id in args.ids:
  2254. try:
  2255. id = unhexlify(hex_id)
  2256. except ValueError:
  2257. print("object id %s is invalid." % hex_id)
  2258. else:
  2259. try:
  2260. repository.delete(id)
  2261. modified = True
  2262. print("object %s deleted." % hex_id)
  2263. except Repository.ObjectNotFound:
  2264. print("object %s not found." % hex_id)
  2265. if modified:
  2266. repository.commit(compact=False)
  2267. print('Done.')
  2268. return EXIT_SUCCESS
  2269. @with_repository(manifest=False, exclusive=True, cache=True, compatibility=Manifest.NO_OPERATION_CHECK)
  2270. def do_debug_refcount_obj(self, args, repository, manifest, key, cache):
  2271. """display refcounts for the objects with the given IDs"""
  2272. for hex_id in args.ids:
  2273. try:
  2274. id = unhexlify(hex_id)
  2275. except ValueError:
  2276. print("object id %s is invalid." % hex_id)
  2277. else:
  2278. try:
  2279. refcount = cache.chunks[id][0]
  2280. print("object %s has %d referrers [info from chunks cache]." % (hex_id, refcount))
  2281. except KeyError:
  2282. print("object %s not found [info from chunks cache]." % hex_id)
  2283. return EXIT_SUCCESS
  2284. @with_repository(manifest=False, exclusive=True)
  2285. def do_debug_dump_hints(self, args, repository):
  2286. """dump repository hints"""
  2287. if not repository._active_txn:
  2288. repository.prepare_txn(repository.get_transaction_id())
  2289. try:
  2290. hints = dict(
  2291. segments=repository.segments,
  2292. compact=repository.compact,
  2293. storage_quota_use=repository.storage_quota_use,
  2294. shadow_index={hexlify(k).decode(): v for k, v in repository.shadow_index.items()}
  2295. )
  2296. with dash_open(args.path, 'w') as fd:
  2297. json.dump(hints, fd, indent=4)
  2298. finally:
  2299. repository.rollback()
  2300. return EXIT_SUCCESS
  2301. def do_debug_convert_profile(self, args):
  2302. """convert Borg profile to Python profile"""
  2303. import marshal
  2304. with args.output, args.input:
  2305. marshal.dump(msgpack.unpack(args.input, use_list=False, raw=False), args.output)
  2306. return EXIT_SUCCESS
  2307. @with_repository(lock=False, manifest=False)
  2308. def do_break_lock(self, args, repository):
  2309. """Break the repository lock (e.g. in case it was left by a dead borg."""
  2310. repository.break_lock()
  2311. Cache.break_lock(repository)
  2312. return self.exit_code
  2313. helptext = collections.OrderedDict()
  2314. helptext['patterns'] = textwrap.dedent('''
  2315. The path/filenames used as input for the pattern matching start from the
  2316. currently active recursion root. You usually give the recursion root(s)
  2317. when invoking borg and these can be either relative or absolute paths.
  2318. So, when you give `relative/` as root, the paths going into the matcher
  2319. will look like `relative/.../file.ext`. When you give `/absolute/` as
  2320. root, they will look like `/absolute/.../file.ext`.
  2321. File paths in Borg archives are always stored normalized and relative.
  2322. This means that e.g. ``borg create /path/to/repo ../some/path`` will
  2323. store all files as `some/path/.../file.ext` and ``borg create
  2324. /path/to/repo /home/user`` will store all files as
  2325. `home/user/.../file.ext`.
  2326. A directory exclusion pattern can end either with or without a slash ('/').
  2327. If it ends with a slash, such as `some/path/`, the directory will be
  2328. included but not its content. If it does not end with a slash, such as
  2329. `some/path`, both the directory and content will be excluded.
  2330. File patterns support these styles: fnmatch, shell, regular expressions,
  2331. path prefixes and path full-matches. By default, fnmatch is used for
  2332. ``--exclude`` patterns and shell-style is used for the ``--pattern``
  2333. option. For commands that support patterns in their ``PATH`` argument
  2334. like (``borg list``), the default pattern is path prefix.
  2335. Starting with Borg 1.2, for all but regular expression pattern matching
  2336. styles, all paths are treated as relative, meaning that a leading path
  2337. separator is removed after normalizing and before matching. This allows
  2338. you to use absolute or relative patterns arbitrarily.
  2339. If followed by a colon (':') the first two characters of a pattern are
  2340. used as a style selector. Explicit style selection is necessary when a
  2341. non-default style is desired or when the desired pattern starts with
  2342. two alphanumeric characters followed by a colon (i.e. `aa:something/*`).
  2343. `Fnmatch <https://docs.python.org/3/library/fnmatch.html>`_, selector `fm:`
  2344. This is the default style for ``--exclude`` and ``--exclude-from``.
  2345. These patterns use a variant of shell pattern syntax, with '\\*' matching
  2346. any number of characters, '?' matching any single character, '[...]'
  2347. matching any single character specified, including ranges, and '[!...]'
  2348. matching any character not specified. For the purpose of these patterns,
  2349. the path separator (backslash for Windows and '/' on other systems) is not
  2350. treated specially. Wrap meta-characters in brackets for a literal
  2351. match (i.e. `[?]` to match the literal character `?`). For a path
  2352. to match a pattern, the full path must match, or it must match
  2353. from the start of the full path to just before a path separator. Except
  2354. for the root path, paths will never end in the path separator when
  2355. matching is attempted. Thus, if a given pattern ends in a path
  2356. separator, a '\\*' is appended before matching is attempted. A leading
  2357. path separator is always removed.
  2358. Shell-style patterns, selector `sh:`
  2359. This is the default style for ``--pattern`` and ``--patterns-from``.
  2360. Like fnmatch patterns these are similar to shell patterns. The difference
  2361. is that the pattern may include `**/` for matching zero or more directory
  2362. levels, `*` for matching zero or more arbitrary characters with the
  2363. exception of any path separator. A leading path separator is always removed.
  2364. Regular expressions, selector `re:`
  2365. Regular expressions similar to those found in Perl are supported. Unlike
  2366. shell patterns regular expressions are not required to match the full
  2367. path and any substring match is sufficient. It is strongly recommended to
  2368. anchor patterns to the start ('^'), to the end ('$') or both. Path
  2369. separators (backslash for Windows and '/' on other systems) in paths are
  2370. always normalized to a forward slash ('/') before applying a pattern. The
  2371. regular expression syntax is described in the `Python documentation for
  2372. the re module <https://docs.python.org/3/library/re.html>`_.
  2373. Path prefix, selector `pp:`
  2374. This pattern style is useful to match whole sub-directories. The pattern
  2375. `pp:root/somedir` matches `root/somedir` and everything therein. A leading
  2376. path separator is always removed.
  2377. Path full-match, selector `pf:`
  2378. This pattern style is (only) useful to match full paths.
  2379. This is kind of a pseudo pattern as it can not have any variable or
  2380. unspecified parts - the full path must be given. `pf:root/file.ext` matches
  2381. `root/file.ext` only. A leading path separator is always removed.
  2382. Implementation note: this is implemented via very time-efficient O(1)
  2383. hashtable lookups (this means you can have huge amounts of such patterns
  2384. without impacting performance much).
  2385. Due to that, this kind of pattern does not respect any context or order.
  2386. If you use such a pattern to include a file, it will always be included
  2387. (if the directory recursion encounters it).
  2388. Other include/exclude patterns that would normally match will be ignored.
  2389. Same logic applies for exclude.
  2390. .. note::
  2391. `re:`, `sh:` and `fm:` patterns are all implemented on top of the Python SRE
  2392. engine. It is very easy to formulate patterns for each of these types which
  2393. requires an inordinate amount of time to match paths. If untrusted users
  2394. are able to supply patterns, ensure they cannot supply `re:` patterns.
  2395. Further, ensure that `sh:` and `fm:` patterns only contain a handful of
  2396. wildcards at most.
  2397. Exclusions can be passed via the command line option ``--exclude``. When used
  2398. from within a shell, the patterns should be quoted to protect them from
  2399. expansion.
  2400. The ``--exclude-from`` option permits loading exclusion patterns from a text
  2401. file with one pattern per line. Lines empty or starting with the number sign
  2402. ('#') after removing whitespace on both ends are ignored. The optional style
  2403. selector prefix is also supported for patterns loaded from a file. Due to
  2404. whitespace removal, paths with whitespace at the beginning or end can only be
  2405. excluded using regular expressions.
  2406. To test your exclusion patterns without performing an actual backup you can
  2407. run ``borg create --list --dry-run ...``.
  2408. Examples::
  2409. # Exclude '/home/user/file.o' but not '/home/user/file.odt':
  2410. $ borg create -e '*.o' backup /
  2411. # Exclude '/home/user/junk' and '/home/user/subdir/junk' but
  2412. # not '/home/user/importantjunk' or '/etc/junk':
  2413. $ borg create -e '/home/*/junk' backup /
  2414. # Exclude the contents of '/home/user/cache' but not the directory itself:
  2415. $ borg create -e home/user/cache/ backup /
  2416. # The file '/home/user/cache/important' is *not* backed up:
  2417. $ borg create -e /home/user/cache/ backup / /home/user/cache/important
  2418. # The contents of directories in '/home' are not backed up when their name
  2419. # ends in '.tmp'
  2420. $ borg create --exclude 're:^/home/[^/]+\\.tmp/' backup /
  2421. # Load exclusions from file
  2422. $ cat >exclude.txt <<EOF
  2423. # Comment line
  2424. /home/*/junk
  2425. *.tmp
  2426. fm:aa:something/*
  2427. re:^/home/[^/]+\\.tmp/
  2428. sh:/home/*/.thumbnails
  2429. # Example with spaces, no need to escape as it is processed by borg
  2430. some file with spaces.txt
  2431. EOF
  2432. $ borg create --exclude-from exclude.txt backup /
  2433. A more general and easier to use way to define filename matching patterns exists
  2434. with the ``--pattern`` and ``--patterns-from`` options. Using these, you may
  2435. specify the backup roots (starting points) and patterns for inclusion/exclusion.
  2436. A root path starts with the prefix `R`, followed by a path (a plain path, not a
  2437. file pattern). An include rule starts with the prefix +, an exclude rule starts
  2438. with the prefix -, an exclude-norecurse rule starts with !, all followed by a pattern.
  2439. .. note::
  2440. Via ``--pattern`` or ``--patterns-from`` you can define BOTH inclusion and exclusion
  2441. of files using pattern prefixes ``+`` and ``-``. With ``--exclude`` and
  2442. ``--exclude-from`` ONLY excludes are defined.
  2443. Inclusion patterns are useful to include paths that are contained in an excluded
  2444. path. The first matching pattern is used so if an include pattern matches before
  2445. an exclude pattern, the file is backed up. If an exclude-norecurse pattern matches
  2446. a directory, it won't recurse into it and won't discover any potential matches for
  2447. include rules below that directory.
  2448. .. note::
  2449. It's possible that a sub-directory/file is matched while parent directories are not.
  2450. In that case, parent directories are not backed up thus their user, group, permission,
  2451. etc. can not be restored.
  2452. Note that the default pattern style for ``--pattern`` and ``--patterns-from`` is
  2453. shell style (`sh:`), so those patterns behave similar to rsync include/exclude
  2454. patterns. The pattern style can be set via the `P` prefix.
  2455. Patterns (``--pattern``) and excludes (``--exclude``) from the command line are
  2456. considered first (in the order of appearance). Then patterns from ``--patterns-from``
  2457. are added. Exclusion patterns from ``--exclude-from`` files are appended last.
  2458. Examples::
  2459. # backup pics, but not the ones from 2018, except the good ones:
  2460. # note: using = is essential to avoid cmdline argument parsing issues.
  2461. borg create --pattern=+pics/2018/good --pattern=-pics/2018 repo::arch pics
  2462. # use a file with patterns:
  2463. borg create --patterns-from patterns.lst repo::arch
  2464. The patterns.lst file could look like that::
  2465. # "sh:" pattern style is the default, so the following line is not needed:
  2466. P sh
  2467. R /
  2468. # can be rebuild
  2469. - /home/*/.cache
  2470. # they're downloads for a reason
  2471. - /home/*/Downloads
  2472. # susan is a nice person
  2473. # include susans home
  2474. + /home/susan
  2475. # also back up this exact file
  2476. + pf:/home/bobby/specialfile.txt
  2477. # don't backup the other home directories
  2478. - /home/*
  2479. # don't even look in /proc
  2480. ! /proc
  2481. You can specify recursion roots either on the command line or in a patternfile::
  2482. # these two commands do the same thing
  2483. borg create --exclude /home/bobby/junk repo::arch /home/bobby /home/susan
  2484. borg create --patterns-from patternfile.lst repo::arch
  2485. The patternfile::
  2486. # note that excludes use fm: by default and patternfiles use sh: by default.
  2487. # therefore, we need to specify fm: to have the same exact behavior.
  2488. P fm
  2489. R /home/bobby
  2490. R /home/susan
  2491. - /home/bobby/junk
  2492. This allows you to share the same patterns between multiple repositories
  2493. without needing to specify them on the command line.\n\n''')
  2494. helptext['placeholders'] = textwrap.dedent('''
  2495. Repository (or Archive) URLs, ``--prefix``, ``--glob-archives``, ``--comment``
  2496. and ``--remote-path`` values support these placeholders:
  2497. {hostname}
  2498. The (short) hostname of the machine.
  2499. {fqdn}
  2500. The full name of the machine.
  2501. {reverse-fqdn}
  2502. The full name of the machine in reverse domain name notation.
  2503. {now}
  2504. The current local date and time, by default in ISO-8601 format.
  2505. You can also supply your own `format string <https://docs.python.org/3.9/library/datetime.html#strftime-and-strptime-behavior>`_, e.g. {now:%Y-%m-%d_%H:%M:%S}
  2506. {utcnow}
  2507. The current UTC date and time, by default in ISO-8601 format.
  2508. You can also supply your own `format string <https://docs.python.org/3.9/library/datetime.html#strftime-and-strptime-behavior>`_, e.g. {utcnow:%Y-%m-%d_%H:%M:%S}
  2509. {user}
  2510. The user name (or UID, if no name is available) of the user running borg.
  2511. {pid}
  2512. The current process ID.
  2513. {borgversion}
  2514. The version of borg, e.g.: 1.0.8rc1
  2515. {borgmajor}
  2516. The version of borg, only the major version, e.g.: 1
  2517. {borgminor}
  2518. The version of borg, only major and minor version, e.g.: 1.0
  2519. {borgpatch}
  2520. The version of borg, only major, minor and patch version, e.g.: 1.0.8
  2521. If literal curly braces need to be used, double them for escaping::
  2522. borg create /path/to/repo::{{literal_text}}
  2523. Examples::
  2524. borg create /path/to/repo::{hostname}-{user}-{utcnow} ...
  2525. borg create /path/to/repo::{hostname}-{now:%Y-%m-%d_%H:%M:%S} ...
  2526. borg prune --prefix '{hostname}-' ...
  2527. .. note::
  2528. systemd uses a difficult, non-standard syntax for command lines in unit files (refer to
  2529. the `systemd.unit(5)` manual page).
  2530. When invoking borg from unit files, pay particular attention to escaping,
  2531. especially when using the now/utcnow placeholders, since systemd performs its own
  2532. %-based variable replacement even in quoted text. To avoid interference from systemd,
  2533. double all percent signs (``{hostname}-{now:%Y-%m-%d_%H:%M:%S}``
  2534. becomes ``{hostname}-{now:%%Y-%%m-%%d_%%H:%%M:%%S}``).\n\n''')
  2535. helptext['compression'] = textwrap.dedent('''
  2536. It is no problem to mix different compression methods in one repo,
  2537. deduplication is done on the source data chunks (not on the compressed
  2538. or encrypted data).
  2539. If some specific chunk was once compressed and stored into the repo, creating
  2540. another backup that also uses this chunk will not change the stored chunk.
  2541. So if you use different compression specs for the backups, whichever stores a
  2542. chunk first determines its compression. See also borg recreate.
  2543. Compression is lz4 by default. If you want something else, you have to specify what you want.
  2544. Valid compression specifiers are:
  2545. none
  2546. Do not compress.
  2547. lz4
  2548. Use lz4 compression. Very high speed, very low compression. (default)
  2549. zstd[,L]
  2550. Use zstd ("zstandard") compression, a modern wide-range algorithm.
  2551. If you do not explicitly give the compression level L (ranging from 1
  2552. to 22), it will use level 3.
  2553. Archives compressed with zstd are not compatible with borg < 1.1.4.
  2554. zlib[,L]
  2555. Use zlib ("gz") compression. Medium speed, medium compression.
  2556. If you do not explicitly give the compression level L (ranging from 0
  2557. to 9), it will use level 6.
  2558. Giving level 0 (means "no compression", but still has zlib protocol
  2559. overhead) is usually pointless, you better use "none" compression.
  2560. lzma[,L]
  2561. Use lzma ("xz") compression. Low speed, high compression.
  2562. If you do not explicitly give the compression level L (ranging from 0
  2563. to 9), it will use level 6.
  2564. Giving levels above 6 is pointless and counterproductive because it does
  2565. not compress better due to the buffer size used by borg - but it wastes
  2566. lots of CPU cycles and RAM.
  2567. auto,C[,L]
  2568. Use a built-in heuristic to decide per chunk whether to compress or not.
  2569. The heuristic tries with lz4 whether the data is compressible.
  2570. For incompressible data, it will not use compression (uses "none").
  2571. For compressible data, it uses the given C[,L] compression - with C[,L]
  2572. being any valid compression specifier.
  2573. obfuscate,SPEC,C[,L]
  2574. Use compressed-size obfuscation to make fingerprinting attacks based on
  2575. the observable stored chunk size more difficult.
  2576. Note:
  2577. - you must combine this with encryption or it won't make any sense.
  2578. - your repo size will be bigger, of course.
  2579. The SPEC value will determine how the size obfuscation will work:
  2580. Relative random reciprocal size variation:
  2581. Size will increase by a factor, relative to the compressed data size.
  2582. Smaller factors are often used, larger factors rarely.
  2583. 1: factor 0.01 .. 100.0
  2584. 2: factor 0.1 .. 1000.0
  2585. 3: factor 1.0 .. 10000.0
  2586. 4: factor 10.0 .. 100000.0
  2587. 5: factor 100.0 .. 1000000.0
  2588. 6: factor 1000.0 .. 10000000.0
  2589. Add a randomly sized padding up to the given size:
  2590. 110: 1kiB
  2591. ...
  2592. 120: 1MiB
  2593. ...
  2594. 123: 8MiB (max.)
  2595. Examples::
  2596. borg create --compression lz4 REPO::ARCHIVE data
  2597. borg create --compression zstd REPO::ARCHIVE data
  2598. borg create --compression zstd,10 REPO::ARCHIVE data
  2599. borg create --compression zlib REPO::ARCHIVE data
  2600. borg create --compression zlib,1 REPO::ARCHIVE data
  2601. borg create --compression auto,lzma,6 REPO::ARCHIVE data
  2602. borg create --compression auto,lzma ...
  2603. borg create --compression obfuscate,3,none ...
  2604. borg create --compression obfuscate,3,auto,zstd,10 ...
  2605. borg create --compression obfuscate,2,zstd,6 ...\n\n''')
  2606. def do_help(self, parser, commands, args):
  2607. if not args.topic:
  2608. parser.print_help()
  2609. elif args.topic in self.helptext:
  2610. print(rst_to_terminal(self.helptext[args.topic]))
  2611. elif args.topic in commands:
  2612. if args.epilog_only:
  2613. print(commands[args.topic].epilog)
  2614. elif args.usage_only:
  2615. commands[args.topic].epilog = None
  2616. commands[args.topic].print_help()
  2617. else:
  2618. commands[args.topic].print_help()
  2619. else:
  2620. msg_lines = []
  2621. msg_lines += ['No help available on %s.' % args.topic]
  2622. msg_lines += ['Try one of the following:']
  2623. msg_lines += [' Commands: %s' % ', '.join(sorted(commands.keys()))]
  2624. msg_lines += [' Topics: %s' % ', '.join(sorted(self.helptext.keys()))]
  2625. parser.error('\n'.join(msg_lines))
  2626. return self.exit_code
  2627. def do_subcommand_help(self, parser, args):
  2628. """display infos about subcommand"""
  2629. parser.print_help()
  2630. return EXIT_SUCCESS
  2631. do_maincommand_help = do_subcommand_help
  2632. def preprocess_args(self, args):
  2633. deprecations = [
  2634. # ('--old', '--new' or None, 'Warning: "--old" has been deprecated. Use "--new" instead.'),
  2635. ('--noatime', None, 'Warning: "--noatime" has been deprecated because it is the default now.'),
  2636. ('--nobsdflags', None, 'Warning: "--nobsdflags" has been deprecated. Use --noflags instead.'),
  2637. ('--numeric-owner', None, 'Warning: "--numeric-owner" has been deprecated. Use --numeric-ids instead.'),
  2638. ('--remote-ratelimit', None, 'Warning: "--remote-ratelimit" has been deprecated. Use --upload-ratelimit instead.'),
  2639. ('--remote-buffer', None, 'Warning: "--remote-buffer" has been deprecated. Use --upload-buffer instead.'),
  2640. ]
  2641. for i, arg in enumerate(args[:]):
  2642. for old_name, new_name, warning in deprecations:
  2643. if arg.startswith(old_name):
  2644. if new_name is not None:
  2645. args[i] = arg.replace(old_name, new_name)
  2646. print(warning, file=sys.stderr)
  2647. return args
  2648. class CommonOptions:
  2649. """
  2650. Support class to allow specifying common options directly after the top-level command.
  2651. Normally options can only be specified on the parser defining them, which means
  2652. that generally speaking *all* options go after all sub-commands. This is annoying
  2653. for common options in scripts, e.g. --remote-path or logging options.
  2654. This class allows adding the same set of options to both the top-level parser
  2655. and the final sub-command parsers (but not intermediary sub-commands, at least for now).
  2656. It does so by giving every option's target name ("dest") a suffix indicating its level
  2657. -- no two options in the parser hierarchy can have the same target --
  2658. then, after parsing the command line, multiple definitions are resolved.
  2659. Defaults are handled by only setting them on the top-level parser and setting
  2660. a sentinel object in all sub-parsers, which then allows one to discern which parser
  2661. supplied the option.
  2662. """
  2663. def __init__(self, define_common_options, suffix_precedence):
  2664. """
  2665. *define_common_options* should be a callable taking one argument, which
  2666. will be a argparse.Parser.add_argument-like function.
  2667. *define_common_options* will be called multiple times, and should call
  2668. the passed function to define common options exactly the same way each time.
  2669. *suffix_precedence* should be a tuple of the suffixes that will be used.
  2670. It is ordered from lowest precedence to highest precedence:
  2671. An option specified on the parser belonging to index 0 is overridden if the
  2672. same option is specified on any parser with a higher index.
  2673. """
  2674. self.define_common_options = define_common_options
  2675. self.suffix_precedence = suffix_precedence
  2676. # Maps suffixes to sets of target names.
  2677. # E.g. common_options["_subcommand"] = {..., "log_level", ...}
  2678. self.common_options = dict()
  2679. # Set of options with the 'append' action.
  2680. self.append_options = set()
  2681. # This is the sentinel object that replaces all default values in parsers
  2682. # below the top-level parser.
  2683. self.default_sentinel = object()
  2684. def add_common_group(self, parser, suffix, provide_defaults=False):
  2685. """
  2686. Add common options to *parser*.
  2687. *provide_defaults* must only be True exactly once in a parser hierarchy,
  2688. at the top level, and False on all lower levels. The default is chosen
  2689. accordingly.
  2690. *suffix* indicates the suffix to use internally. It also indicates
  2691. which precedence the *parser* has for common options. See *suffix_precedence*
  2692. of __init__.
  2693. """
  2694. assert suffix in self.suffix_precedence
  2695. def add_argument(*args, **kwargs):
  2696. if 'dest' in kwargs:
  2697. kwargs.setdefault('action', 'store')
  2698. assert kwargs['action'] in ('help', 'store_const', 'store_true', 'store_false', 'store', 'append')
  2699. is_append = kwargs['action'] == 'append'
  2700. if is_append:
  2701. self.append_options.add(kwargs['dest'])
  2702. assert kwargs['default'] == [], 'The default is explicitly constructed as an empty list in resolve()'
  2703. else:
  2704. self.common_options.setdefault(suffix, set()).add(kwargs['dest'])
  2705. kwargs['dest'] += suffix
  2706. if not provide_defaults:
  2707. # Interpolate help now, in case the %(default)d (or so) is mentioned,
  2708. # to avoid producing incorrect help output.
  2709. # Assumption: Interpolated output can safely be interpolated again,
  2710. # which should always be the case.
  2711. # Note: We control all inputs.
  2712. kwargs['help'] = kwargs['help'] % kwargs
  2713. if not is_append:
  2714. kwargs['default'] = self.default_sentinel
  2715. common_group.add_argument(*args, **kwargs)
  2716. common_group = parser.add_argument_group('Common options')
  2717. self.define_common_options(add_argument)
  2718. def resolve(self, args: argparse.Namespace): # Namespace has "in" but otherwise is not like a dict.
  2719. """
  2720. Resolve the multiple definitions of each common option to the final value.
  2721. """
  2722. for suffix in self.suffix_precedence:
  2723. # From highest level to lowest level, so the "most-specific" option wins, e.g.
  2724. # "borg --debug create --info" shall result in --info being effective.
  2725. for dest in self.common_options.get(suffix, []):
  2726. # map_from is this suffix' option name, e.g. log_level_subcommand
  2727. # map_to is the target name, e.g. log_level
  2728. map_from = dest + suffix
  2729. map_to = dest
  2730. # Retrieve value; depending on the action it may not exist, but usually does
  2731. # (store_const/store_true/store_false), either because the action implied a default
  2732. # or a default is explicitly supplied.
  2733. # Note that defaults on lower levels are replaced with default_sentinel.
  2734. # Only the top level has defaults.
  2735. value = getattr(args, map_from, self.default_sentinel)
  2736. if value is not self.default_sentinel:
  2737. # value was indeed specified on this level. Transfer value to target,
  2738. # and un-clobber the args (for tidiness - you *cannot* use the suffixed
  2739. # names for other purposes, obviously).
  2740. setattr(args, map_to, value)
  2741. try:
  2742. delattr(args, map_from)
  2743. except AttributeError:
  2744. pass
  2745. # Options with an "append" action need some special treatment. Instead of
  2746. # overriding values, all specified values are merged together.
  2747. for dest in self.append_options:
  2748. option_value = []
  2749. for suffix in self.suffix_precedence:
  2750. # Find values of this suffix, if any, and add them to the final list
  2751. extend_from = dest + suffix
  2752. if extend_from in args:
  2753. values = getattr(args, extend_from)
  2754. delattr(args, extend_from)
  2755. option_value.extend(values)
  2756. setattr(args, dest, option_value)
  2757. def build_parser(self):
  2758. # You can use :ref:`xyz` in the following usage pages. However, for plain-text view,
  2759. # e.g. through "borg ... --help", define a substitution for the reference here.
  2760. # It will replace the entire :ref:`foo` verbatim.
  2761. rst_plain_text_references = {
  2762. 'a_status_oddity': '"I am seeing ‘A’ (added) status for a unchanged file!?"',
  2763. 'separate_compaction': '"Separate compaction"',
  2764. 'list_item_flags': '"Item flags"',
  2765. 'borg_patterns': '"borg help patterns"',
  2766. 'borg_placeholders': '"borg help placeholders"',
  2767. 'key_files': 'Internals -> Data structures and file formats -> Key files',
  2768. 'borg_key_export': 'borg key export --help',
  2769. }
  2770. def process_epilog(epilog):
  2771. epilog = textwrap.dedent(epilog).splitlines()
  2772. try:
  2773. mode = borg.doc_mode
  2774. except AttributeError:
  2775. mode = 'command-line'
  2776. if mode in ('command-line', 'build_usage'):
  2777. epilog = [line for line in epilog if not line.startswith('.. man')]
  2778. epilog = '\n'.join(epilog)
  2779. if mode == 'command-line':
  2780. epilog = rst_to_terminal(epilog, rst_plain_text_references)
  2781. return epilog
  2782. def define_common_options(add_common_option):
  2783. add_common_option('-h', '--help', action='help', help='show this help message and exit')
  2784. add_common_option('--critical', dest='log_level',
  2785. action='store_const', const='critical', default='warning',
  2786. help='work on log level CRITICAL')
  2787. add_common_option('--error', dest='log_level',
  2788. action='store_const', const='error', default='warning',
  2789. help='work on log level ERROR')
  2790. add_common_option('--warning', dest='log_level',
  2791. action='store_const', const='warning', default='warning',
  2792. help='work on log level WARNING (default)')
  2793. add_common_option('--info', '-v', '--verbose', dest='log_level',
  2794. action='store_const', const='info', default='warning',
  2795. help='work on log level INFO')
  2796. add_common_option('--debug', dest='log_level',
  2797. action='store_const', const='debug', default='warning',
  2798. help='enable debug output, work on log level DEBUG')
  2799. add_common_option('--debug-topic', metavar='TOPIC', dest='debug_topics', action='append', default=[],
  2800. help='enable TOPIC debugging (can be specified multiple times). '
  2801. 'The logger path is borg.debug.<TOPIC> if TOPIC is not fully qualified.')
  2802. add_common_option('-p', '--progress', dest='progress', action='store_true',
  2803. help='show progress information')
  2804. add_common_option('--iec', dest='iec', action='store_true',
  2805. help='format using IEC units (1KiB = 1024B)')
  2806. add_common_option('--log-json', dest='log_json', action='store_true',
  2807. help='Output one JSON object per log line instead of formatted text.')
  2808. add_common_option('--lock-wait', metavar='SECONDS', dest='lock_wait', type=int, default=1,
  2809. help='wait at most SECONDS for acquiring a repository/cache lock (default: %(default)d).')
  2810. add_common_option('--bypass-lock', dest='lock', action='store_false',
  2811. default=argparse.SUPPRESS, # only create args attribute if option is specified
  2812. help='Bypass locking mechanism')
  2813. add_common_option('--show-version', dest='show_version', action='store_true',
  2814. help='show/log the borg version')
  2815. add_common_option('--show-rc', dest='show_rc', action='store_true',
  2816. help='show/log the return code (rc)')
  2817. add_common_option('--umask', metavar='M', dest='umask', type=lambda s: int(s, 8), default=UMASK_DEFAULT,
  2818. help='set umask to M (local only, default: %(default)04o)')
  2819. add_common_option('--remote-path', metavar='PATH', dest='remote_path',
  2820. help='use PATH as borg executable on the remote (default: "borg")')
  2821. add_common_option('--remote-ratelimit', metavar='RATE', dest='upload_ratelimit', type=int,
  2822. help='deprecated, use ``--upload-ratelimit`` instead')
  2823. add_common_option('--upload-ratelimit', metavar='RATE', dest='upload_ratelimit', type=int,
  2824. help='set network upload rate limit in kiByte/s (default: 0=unlimited)')
  2825. add_common_option('--remote-buffer', metavar='UPLOAD_BUFFER', dest='upload_buffer', type=int,
  2826. help='deprecated, use ``--upload-buffer`` instead')
  2827. add_common_option('--upload-buffer', metavar='UPLOAD_BUFFER', dest='upload_buffer', type=int,
  2828. help='set network upload buffer size in MiB. (default: 0=no buffer)')
  2829. add_common_option('--consider-part-files', dest='consider_part_files', action='store_true',
  2830. help='treat part files like normal files (e.g. to list/extract them)')
  2831. add_common_option('--debug-profile', metavar='FILE', dest='debug_profile', default=None,
  2832. help='Write execution profile in Borg format into FILE. For local use a Python-'
  2833. 'compatible file can be generated by suffixing FILE with ".pyprof".')
  2834. add_common_option('--rsh', metavar='RSH', dest='rsh',
  2835. help="Use this command to connect to the 'borg serve' process (default: 'ssh')")
  2836. def define_exclude_and_patterns(add_option, *, tag_files=False, strip_components=False):
  2837. add_option('-e', '--exclude', metavar='PATTERN', dest='patterns',
  2838. type=parse_exclude_pattern, action='append',
  2839. help='exclude paths matching PATTERN')
  2840. add_option('--exclude-from', metavar='EXCLUDEFILE', action=ArgparseExcludeFileAction,
  2841. help='read exclude patterns from EXCLUDEFILE, one per line')
  2842. add_option('--pattern', metavar='PATTERN', action=ArgparsePatternAction,
  2843. help='include/exclude paths matching PATTERN')
  2844. add_option('--patterns-from', metavar='PATTERNFILE', action=ArgparsePatternFileAction,
  2845. help='read include/exclude patterns from PATTERNFILE, one per line')
  2846. if tag_files:
  2847. add_option('--exclude-caches', dest='exclude_caches', action='store_true',
  2848. help='exclude directories that contain a CACHEDIR.TAG file '
  2849. '(http://www.bford.info/cachedir/spec.html)')
  2850. add_option('--exclude-if-present', metavar='NAME', dest='exclude_if_present',
  2851. action='append', type=str,
  2852. help='exclude directories that are tagged by containing a filesystem object with '
  2853. 'the given NAME')
  2854. add_option('--keep-exclude-tags', dest='keep_exclude_tags',
  2855. action='store_true',
  2856. help='if tag objects are specified with ``--exclude-if-present``, '
  2857. 'don\'t omit the tag objects themselves from the backup archive')
  2858. if strip_components:
  2859. add_option('--strip-components', metavar='NUMBER', dest='strip_components', type=int, default=0,
  2860. help='Remove the specified number of leading path elements. '
  2861. 'Paths with fewer elements will be silently skipped.')
  2862. def define_exclusion_group(subparser, **kwargs):
  2863. exclude_group = subparser.add_argument_group('Exclusion options')
  2864. define_exclude_and_patterns(exclude_group.add_argument, **kwargs)
  2865. return exclude_group
  2866. def define_archive_filters_group(subparser, *, sort_by=True, first_last=True):
  2867. filters_group = subparser.add_argument_group('Archive filters',
  2868. 'Archive filters can be applied to repository targets.')
  2869. group = filters_group.add_mutually_exclusive_group()
  2870. group.add_argument('-P', '--prefix', metavar='PREFIX', dest='prefix', type=PrefixSpec, action=Highlander,
  2871. help='only consider archive names starting with this prefix.')
  2872. group.add_argument('-a', '--glob-archives', metavar='GLOB', dest='glob_archives',
  2873. type=GlobSpec, action=Highlander,
  2874. help='only consider archive names matching the glob. '
  2875. 'sh: rules apply, see "borg help patterns". '
  2876. '``--prefix`` and ``--glob-archives`` are mutually exclusive.')
  2877. if sort_by:
  2878. sort_by_default = 'timestamp'
  2879. filters_group.add_argument('--sort-by', metavar='KEYS', dest='sort_by',
  2880. type=SortBySpec, default=sort_by_default,
  2881. help='Comma-separated list of sorting keys; valid keys are: {}; default is: {}'
  2882. .format(', '.join(AI_HUMAN_SORT_KEYS), sort_by_default))
  2883. if first_last:
  2884. group = filters_group.add_mutually_exclusive_group()
  2885. group.add_argument('--first', metavar='N', dest='first', default=0, type=positive_int_validator,
  2886. help='consider first N archives after other filters were applied')
  2887. group.add_argument('--last', metavar='N', dest='last', default=0, type=positive_int_validator,
  2888. help='consider last N archives after other filters were applied')
  2889. def define_borg_mount(parser):
  2890. parser.set_defaults(func=self.do_mount)
  2891. parser.add_argument('location', metavar='REPOSITORY_OR_ARCHIVE', type=location_validator(),
  2892. help='repository or archive to mount')
  2893. parser.add_argument('--consider-checkpoints', action='store_true', dest='consider_checkpoints',
  2894. help='Show checkpoint archives in the repository contents list (default: hidden).')
  2895. parser.add_argument('mountpoint', metavar='MOUNTPOINT', type=str,
  2896. help='where to mount filesystem')
  2897. parser.add_argument('-f', '--foreground', dest='foreground',
  2898. action='store_true',
  2899. help='stay in foreground, do not daemonize')
  2900. parser.add_argument('-o', dest='options', type=str, action=Highlander,
  2901. help='Extra mount options')
  2902. parser.add_argument('--numeric-owner', dest='numeric_ids', action='store_true',
  2903. help='deprecated, use ``--numeric-ids`` instead')
  2904. parser.add_argument('--numeric-ids', dest='numeric_ids', action='store_true',
  2905. help='use numeric user and group identifiers from archive(s)')
  2906. define_archive_filters_group(parser)
  2907. parser.add_argument('paths', metavar='PATH', nargs='*', type=str,
  2908. help='paths to extract; patterns are supported')
  2909. define_exclusion_group(parser, strip_components=True)
  2910. parser = argparse.ArgumentParser(prog=self.prog, description='Borg - Deduplicated Backups',
  2911. add_help=False)
  2912. # paths and patterns must have an empty list as default everywhere
  2913. parser.set_defaults(fallback2_func=functools.partial(self.do_maincommand_help, parser),
  2914. paths=[], patterns=[])
  2915. parser.common_options = self.CommonOptions(define_common_options,
  2916. suffix_precedence=('_maincommand', '_midcommand', '_subcommand'))
  2917. parser.add_argument('-V', '--version', action='version', version='%(prog)s ' + __version__,
  2918. help='show version number and exit')
  2919. parser.common_options.add_common_group(parser, '_maincommand', provide_defaults=True)
  2920. common_parser = argparse.ArgumentParser(add_help=False, prog=self.prog)
  2921. common_parser.set_defaults(paths=[], patterns=[])
  2922. parser.common_options.add_common_group(common_parser, '_subcommand')
  2923. mid_common_parser = argparse.ArgumentParser(add_help=False, prog=self.prog)
  2924. mid_common_parser.set_defaults(paths=[], patterns=[])
  2925. parser.common_options.add_common_group(mid_common_parser, '_midcommand')
  2926. # borg mount
  2927. mount_epilog = process_epilog("""
  2928. This command mounts an archive as a FUSE filesystem. This can be useful for
  2929. browsing an archive or restoring individual files. Unless the ``--foreground``
  2930. option is given the command will run in the background until the filesystem
  2931. is ``umounted``.
  2932. The command ``borgfs`` provides a wrapper for ``borg mount``. This can also be
  2933. used in fstab entries:
  2934. ``/path/to/repo /mnt/point fuse.borgfs defaults,noauto 0 0``
  2935. To allow a regular user to use fstab entries, add the ``user`` option:
  2936. ``/path/to/repo /mnt/point fuse.borgfs defaults,noauto,user 0 0``
  2937. For FUSE configuration and mount options, see the mount.fuse(8) manual page.
  2938. Borg's default behavior is to use the archived user and group names of each
  2939. file and map them to the system's respective user and group ids.
  2940. Alternatively, using ``numeric-ids`` will instead use the archived user and
  2941. group ids without any mapping.
  2942. The ``uid`` and ``gid`` mount options (implemented by Borg) can be used to
  2943. override the user and group ids of all files (i.e., ``borg mount -o
  2944. uid=1000,gid=1000``).
  2945. The man page references ``user_id`` and ``group_id`` mount options
  2946. (implemented by fuse) which specify the user and group id of the mount owner
  2947. (aka, the user who does the mounting). It is set automatically by libfuse (or
  2948. the filesystem if libfuse is not used). However, you should not specify these
  2949. manually. Unlike the ``uid`` and ``gid`` mount options which affect all files,
  2950. ``user_id`` and ``group_id`` affect the user and group id of the mounted
  2951. (base) directory.
  2952. Additional mount options supported by borg:
  2953. - versions: when used with a repository mount, this gives a merged, versioned
  2954. view of the files in the archives. EXPERIMENTAL, layout may change in future.
  2955. - allow_damaged_files: by default damaged files (where missing chunks were
  2956. replaced with runs of zeros by borg check ``--repair``) are not readable and
  2957. return EIO (I/O error). Set this option to read such files.
  2958. - ignore_permissions: for security reasons the "default_permissions" mount
  2959. option is internally enforced by borg. "ignore_permissions" can be given to
  2960. not enforce "default_permissions".
  2961. The BORG_MOUNT_DATA_CACHE_ENTRIES environment variable is meant for advanced users
  2962. to tweak the performance. It sets the number of cached data chunks; additional
  2963. memory usage can be up to ~8 MiB times this number. The default is the number
  2964. of CPU cores.
  2965. When the daemonized process receives a signal or crashes, it does not unmount.
  2966. Unmounting in these cases could cause an active rsync or similar process
  2967. to unintentionally delete data.
  2968. When running in the foreground ^C/SIGINT unmounts cleanly, but other
  2969. signals or crashes do not.
  2970. """)
  2971. if parser.prog == 'borgfs':
  2972. parser.description = self.do_mount.__doc__
  2973. parser.epilog = mount_epilog
  2974. parser.formatter_class = argparse.RawDescriptionHelpFormatter
  2975. parser.help = 'mount repository'
  2976. define_borg_mount(parser)
  2977. return parser
  2978. subparsers = parser.add_subparsers(title='required arguments', metavar='<command>')
  2979. # borg benchmark
  2980. benchmark_epilog = process_epilog("These commands do various benchmarks.")
  2981. subparser = subparsers.add_parser('benchmark', parents=[mid_common_parser], add_help=False,
  2982. description='benchmark command',
  2983. epilog=benchmark_epilog,
  2984. formatter_class=argparse.RawDescriptionHelpFormatter,
  2985. help='benchmark command')
  2986. benchmark_parsers = subparser.add_subparsers(title='required arguments', metavar='<command>')
  2987. subparser.set_defaults(fallback_func=functools.partial(self.do_subcommand_help, subparser))
  2988. bench_crud_epilog = process_epilog("""
  2989. This command benchmarks borg CRUD (create, read, update, delete) operations.
  2990. It creates input data below the given PATH and backups this data into the given REPO.
  2991. The REPO must already exist (it could be a fresh empty repo or an existing repo, the
  2992. command will create / read / update / delete some archives named borg-benchmark-crud\\* there.
  2993. Make sure you have free space there, you'll need about 1GB each (+ overhead).
  2994. If your repository is encrypted and borg needs a passphrase to unlock the key, use::
  2995. BORG_PASSPHRASE=mysecret borg benchmark crud REPO PATH
  2996. Measurements are done with different input file sizes and counts.
  2997. The file contents are very artificial (either all zero or all random),
  2998. thus the measurement results do not necessarily reflect performance with real data.
  2999. Also, due to the kind of content used, no compression is used in these benchmarks.
  3000. C- == borg create (1st archive creation, no compression, do not use files cache)
  3001. C-Z- == all-zero files. full dedup, this is primarily measuring reader/chunker/hasher.
  3002. C-R- == random files. no dedup, measuring throughput through all processing stages.
  3003. R- == borg extract (extract archive, dry-run, do everything, but do not write files to disk)
  3004. R-Z- == all zero files. Measuring heavily duplicated files.
  3005. R-R- == random files. No duplication here, measuring throughput through all processing
  3006. stages, except writing to disk.
  3007. U- == borg create (2nd archive creation of unchanged input files, measure files cache speed)
  3008. The throughput value is kind of virtual here, it does not actually read the file.
  3009. U-Z- == needs to check the 2 all-zero chunks' existence in the repo.
  3010. U-R- == needs to check existence of a lot of different chunks in the repo.
  3011. D- == borg delete archive (delete last remaining archive, measure deletion + compaction)
  3012. D-Z- == few chunks to delete / few segments to compact/remove.
  3013. D-R- == many chunks to delete / many segments to compact/remove.
  3014. Please note that there might be quite some variance in these measurements.
  3015. Try multiple measurements and having a otherwise idle machine (and network, if you use it).
  3016. """)
  3017. subparser = benchmark_parsers.add_parser('crud', parents=[common_parser], add_help=False,
  3018. description=self.do_benchmark_crud.__doc__,
  3019. epilog=bench_crud_epilog,
  3020. formatter_class=argparse.RawDescriptionHelpFormatter,
  3021. help='benchmarks borg CRUD (create, extract, update, delete).')
  3022. subparser.set_defaults(func=self.do_benchmark_crud)
  3023. subparser.add_argument('location', metavar='REPOSITORY',
  3024. type=location_validator(archive=False),
  3025. help='repository to use for benchmark (must exist)')
  3026. subparser.add_argument('path', metavar='PATH', help='path were to create benchmark input data')
  3027. bench_cpu_epilog = process_epilog("""
  3028. This command benchmarks misc. CPU bound borg operations.
  3029. It creates input data in memory, runs the operation and then displays throughput.
  3030. To reduce outside influence on the timings, please make sure to run this with:
  3031. - an otherwise as idle as possible machine
  3032. - enough free memory so there will be no slow down due to paging activity
  3033. """)
  3034. subparser = benchmark_parsers.add_parser('cpu', parents=[common_parser], add_help=False,
  3035. description=self.do_benchmark_cpu.__doc__,
  3036. epilog=bench_cpu_epilog,
  3037. formatter_class=argparse.RawDescriptionHelpFormatter,
  3038. help='benchmarks borg CPU bound operations.')
  3039. subparser.set_defaults(func=self.do_benchmark_cpu)
  3040. # borg break-lock
  3041. break_lock_epilog = process_epilog("""
  3042. This command breaks the repository and cache locks.
  3043. Please use carefully and only while no borg process (on any machine) is
  3044. trying to access the Cache or the Repository.
  3045. """)
  3046. subparser = subparsers.add_parser('break-lock', parents=[common_parser], add_help=False,
  3047. description=self.do_break_lock.__doc__,
  3048. epilog=break_lock_epilog,
  3049. formatter_class=argparse.RawDescriptionHelpFormatter,
  3050. help='break repository and cache locks')
  3051. subparser.set_defaults(func=self.do_break_lock)
  3052. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  3053. type=location_validator(archive=False),
  3054. help='repository for which to break the locks')
  3055. # borg check
  3056. check_epilog = process_epilog("""
  3057. The check command verifies the consistency of a repository and the corresponding archives.
  3058. check --repair is a potentially dangerous function and might lead to data loss
  3059. (for kinds of corruption it is not capable of dealing with). BE VERY CAREFUL!
  3060. Pursuant to the previous warning it is also highly recommended to test the
  3061. reliability of the hardware running this software with stress testing software
  3062. such as memory testers. Unreliable hardware can also lead to data loss especially
  3063. when this command is run in repair mode.
  3064. First, the underlying repository data files are checked:
  3065. - For all segments, the segment magic header is checked.
  3066. - For all objects stored in the segments, all metadata (e.g. CRC and size) and
  3067. all data is read. The read data is checked by size and CRC. Bit rot and other
  3068. types of accidental damage can be detected this way.
  3069. - In repair mode, if an integrity error is detected in a segment, try to recover
  3070. as many objects from the segment as possible.
  3071. - In repair mode, make sure that the index is consistent with the data stored in
  3072. the segments.
  3073. - If checking a remote repo via ``ssh:``, the repo check is executed on the server
  3074. without causing significant network traffic.
  3075. - The repository check can be skipped using the ``--archives-only`` option.
  3076. - A repository check can be time consuming. Partial checks are possible with the
  3077. ``--max-duration`` option.
  3078. Second, the consistency and correctness of the archive metadata is verified:
  3079. - Is the repo manifest present? If not, it is rebuilt from archive metadata
  3080. chunks (this requires reading and decrypting of all metadata and data).
  3081. - Check if archive metadata chunk is present; if not, remove archive from manifest.
  3082. - For all files (items) in the archive, for all chunks referenced by these
  3083. files, check if chunk is present. In repair mode, if a chunk is not present,
  3084. replace it with a same-size replacement chunk of zeroes. If a previously lost
  3085. chunk reappears (e.g. via a later backup), in repair mode the all-zero replacement
  3086. chunk will be replaced by the correct chunk. This requires reading of archive and
  3087. file metadata, but not data.
  3088. - In repair mode, when all the archives were checked, orphaned chunks are deleted
  3089. from the repo. One cause of orphaned chunks are input file related errors (like
  3090. read errors) in the archive creation process.
  3091. - In verify-data mode, a complete cryptographic verification of the archive data
  3092. integrity is performed. This conflicts with ``--repository-only`` as this mode
  3093. only makes sense if the archive checks are enabled. The full details of this mode
  3094. are documented below.
  3095. - If checking a remote repo via ``ssh:``, the archive check is executed on the
  3096. client machine because it requires decryption, and this is always done client-side
  3097. as key access is needed.
  3098. - The archive checks can be time consuming; they can be skipped using the
  3099. ``--repository-only`` option.
  3100. The ``--max-duration`` option can be used to split a long-running repository check
  3101. into multiple partial checks. After the given number of seconds the check is
  3102. interrupted. The next partial check will continue where the previous one stopped,
  3103. until the complete repository has been checked. Example: Assuming a complete check took 7
  3104. hours, then running a daily check with --max-duration=3600 (1 hour) resulted in one
  3105. completed check per week.
  3106. Attention: A partial --repository-only check can only do way less checking than a full
  3107. --repository-only check: only the non-cryptographic checksum checks on segment file
  3108. entries are done, while a full --repository-only check would also do a repo index check.
  3109. A partial check cannot be combined with the ``--repair`` option. Partial checks
  3110. may therefore be useful only with very large repositories where a full check would take
  3111. too long.
  3112. Doing a full repository check aborts a partial check; the next partial check will restart
  3113. from the beginning.
  3114. The ``--verify-data`` option will perform a full integrity verification (as opposed to
  3115. checking the CRC32 of the segment) of data, which means reading the data from the
  3116. repository, decrypting and decompressing it. This is a cryptographic verification,
  3117. which will detect (accidental) corruption. For encrypted repositories it is
  3118. tamper-resistant as well, unless the attacker has access to the keys. It is also very
  3119. slow.
  3120. """)
  3121. subparser = subparsers.add_parser('check', parents=[common_parser], add_help=False,
  3122. description=self.do_check.__doc__,
  3123. epilog=check_epilog,
  3124. formatter_class=argparse.RawDescriptionHelpFormatter,
  3125. help='verify repository')
  3126. subparser.set_defaults(func=self.do_check)
  3127. subparser.add_argument('location', metavar='REPOSITORY_OR_ARCHIVE', nargs='?', default='',
  3128. type=location_validator(),
  3129. help='repository or archive to check consistency of')
  3130. subparser.add_argument('--repository-only', dest='repo_only', action='store_true',
  3131. help='only perform repository checks')
  3132. subparser.add_argument('--archives-only', dest='archives_only', action='store_true',
  3133. help='only perform archives checks')
  3134. subparser.add_argument('--verify-data', dest='verify_data', action='store_true',
  3135. help='perform cryptographic archive data integrity verification '
  3136. '(conflicts with ``--repository-only``)')
  3137. subparser.add_argument('--repair', dest='repair', action='store_true',
  3138. help='attempt to repair any inconsistencies found')
  3139. subparser.add_argument('--save-space', dest='save_space', action='store_true',
  3140. help='work slower, but using less space')
  3141. subparser.add_argument('--max-duration', metavar='SECONDS', dest='max_duration',
  3142. type=int, default=0,
  3143. help='do only a partial repo check for max. SECONDS seconds (Default: unlimited)')
  3144. define_archive_filters_group(subparser)
  3145. # borg compact
  3146. compact_epilog = process_epilog("""
  3147. This command frees repository space by compacting segments.
  3148. Use this regularly to avoid running out of space - you do not need to use this
  3149. after each borg command though. It is especially useful after deleting archives,
  3150. because only compaction will really free repository space.
  3151. borg compact does not need a key, so it is possible to invoke it from the
  3152. client or also from the server.
  3153. Depending on the amount of segments that need compaction, it may take a while,
  3154. so consider using the ``--progress`` option.
  3155. A segment is compacted if the amount of saved space is above the percentage value
  3156. given by the ``--threshold`` option. If omitted, a threshold of 10% is used.
  3157. When using ``--verbose``, borg will output an estimate of the freed space.
  3158. After upgrading borg (server) to 1.2+, you can use ``borg compact --cleanup-commits``
  3159. to clean up the numerous 17byte commit-only segments that borg 1.1 did not clean up
  3160. due to a bug. It is enough to do that once per repository. After cleaning up the
  3161. commits, borg will also do a normal compaction.
  3162. See :ref:`separate_compaction` in Additional Notes for more details.
  3163. """)
  3164. subparser = subparsers.add_parser('compact', parents=[common_parser], add_help=False,
  3165. description=self.do_compact.__doc__,
  3166. epilog=compact_epilog,
  3167. formatter_class=argparse.RawDescriptionHelpFormatter,
  3168. help='compact segment files / free space in repo')
  3169. subparser.set_defaults(func=self.do_compact)
  3170. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  3171. type=location_validator(archive=False),
  3172. help='repository to compact')
  3173. subparser.add_argument('--cleanup-commits', dest='cleanup_commits', action='store_true',
  3174. help='cleanup commit-only 17-byte segment files')
  3175. subparser.add_argument('--threshold', metavar='PERCENT', dest='threshold',
  3176. type=int, default=10,
  3177. help='set minimum threshold for saved space in PERCENT (Default: 10)')
  3178. # borg config
  3179. config_epilog = process_epilog("""
  3180. This command gets and sets options in a local repository or cache config file.
  3181. For security reasons, this command only works on local repositories.
  3182. To delete a config value entirely, use ``--delete``. To list the values
  3183. of the configuration file or the default values, use ``--list``. To get and existing
  3184. key, pass only the key name. To set a key, pass both the key name and
  3185. the new value. Keys can be specified in the format "section.name" or
  3186. simply "name"; the section will default to "repository" and "cache" for
  3187. the repo and cache configs, respectively.
  3188. By default, borg config manipulates the repository config file. Using ``--cache``
  3189. edits the repository cache's config file instead.
  3190. """)
  3191. subparser = subparsers.add_parser('config', parents=[common_parser], add_help=False,
  3192. description=self.do_config.__doc__,
  3193. epilog=config_epilog,
  3194. formatter_class=argparse.RawDescriptionHelpFormatter,
  3195. help='get and set configuration values')
  3196. subparser.set_defaults(func=self.do_config)
  3197. subparser.add_argument('-c', '--cache', dest='cache', action='store_true',
  3198. help='get and set values from the repo cache')
  3199. group = subparser.add_mutually_exclusive_group()
  3200. group.add_argument('-d', '--delete', dest='delete', action='store_true',
  3201. help='delete the key from the config file')
  3202. group.add_argument('-l', '--list', dest='list', action='store_true',
  3203. help='list the configuration of the repo')
  3204. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  3205. type=location_validator(archive=False, proto='file'),
  3206. help='repository to configure')
  3207. subparser.add_argument('name', metavar='NAME', nargs='?',
  3208. help='name of config key')
  3209. subparser.add_argument('value', metavar='VALUE', nargs='?',
  3210. help='new value for key')
  3211. # borg create
  3212. create_epilog = process_epilog("""
  3213. This command creates a backup archive containing all files found while recursively
  3214. traversing all paths specified. Paths are added to the archive as they are given,
  3215. that means if relative paths are desired, the command has to be run from the correct
  3216. directory.
  3217. When giving '-' as path, borg will read data from standard input and create a
  3218. file 'stdin' in the created archive from that data. In some cases it's more
  3219. appropriate to use --content-from-command, however. See section *Reading from
  3220. stdin* below for details.
  3221. The archive will consume almost no disk space for files or parts of files that
  3222. have already been stored in other archives.
  3223. The archive name needs to be unique. It must not end in '.checkpoint' or
  3224. '.checkpoint.N' (with N being a number), because these names are used for
  3225. checkpoints and treated in special ways.
  3226. In the archive name, you may use the following placeholders:
  3227. {now}, {utcnow}, {fqdn}, {hostname}, {user} and some others.
  3228. Backup speed is increased by not reprocessing files that are already part of
  3229. existing archives and weren't modified. The detection of unmodified files is
  3230. done by comparing multiple file metadata values with previous values kept in
  3231. the files cache.
  3232. This comparison can operate in different modes as given by ``--files-cache``:
  3233. - ctime,size,inode (default)
  3234. - mtime,size,inode (default behaviour of borg versions older than 1.1.0rc4)
  3235. - ctime,size (ignore the inode number)
  3236. - mtime,size (ignore the inode number)
  3237. - rechunk,ctime (all files are considered modified - rechunk, cache ctime)
  3238. - rechunk,mtime (all files are considered modified - rechunk, cache mtime)
  3239. - disabled (disable the files cache, all files considered modified - rechunk)
  3240. inode number: better safety, but often unstable on network filesystems
  3241. Normally, detecting file modifications will take inode information into
  3242. consideration to improve the reliability of file change detection.
  3243. This is problematic for files located on sshfs and similar network file
  3244. systems which do not provide stable inode numbers, such files will always
  3245. be considered modified. You can use modes without `inode` in this case to
  3246. improve performance, but reliability of change detection might be reduced.
  3247. ctime vs. mtime: safety vs. speed
  3248. - ctime is a rather safe way to detect changes to a file (metadata and contents)
  3249. as it can not be set from userspace. But, a metadata-only change will already
  3250. update the ctime, so there might be some unnecessary chunking/hashing even
  3251. without content changes. Some filesystems do not support ctime (change time).
  3252. E.g. doing a chown or chmod to a file will change its ctime.
  3253. - mtime usually works and only updates if file contents were changed. But mtime
  3254. can be arbitrarily set from userspace, e.g. to set mtime back to the same value
  3255. it had before a content change happened. This can be used maliciously as well as
  3256. well-meant, but in both cases mtime based cache modes can be problematic.
  3257. The mount points of filesystems or filesystem snapshots should be the same for every
  3258. creation of a new archive to ensure fast operation. This is because the file cache that
  3259. is used to determine changed files quickly uses absolute filenames.
  3260. If this is not possible, consider creating a bind mount to a stable location.
  3261. The ``--progress`` option shows (from left to right) Original, Compressed and Deduplicated
  3262. (O, C and D, respectively), then the Number of files (N) processed so far, followed by
  3263. the currently processed path.
  3264. When using ``--stats``, you will get some statistics about how much data was
  3265. added - the "This Archive" deduplicated size there is most interesting as that is
  3266. how much your repository will grow. Please note that the "All archives" stats refer to
  3267. the state after creation. Also, the ``--stats`` and ``--dry-run`` options are mutually
  3268. exclusive because the data is not actually compressed and deduplicated during a dry run.
  3269. For more help on include/exclude patterns, see the :ref:`borg_patterns` command output.
  3270. For more help on placeholders, see the :ref:`borg_placeholders` command output.
  3271. .. man NOTES
  3272. The ``--exclude`` patterns are not like tar. In tar ``--exclude`` .bundler/gems will
  3273. exclude foo/.bundler/gems. In borg it will not, you need to use ``--exclude``
  3274. '\\*/.bundler/gems' to get the same effect.
  3275. In addition to using ``--exclude`` patterns, it is possible to use
  3276. ``--exclude-if-present`` to specify the name of a filesystem object (e.g. a file
  3277. or folder name) which, when contained within another folder, will prevent the
  3278. containing folder from being backed up. By default, the containing folder and
  3279. all of its contents will be omitted from the backup. If, however, you wish to
  3280. only include the objects specified by ``--exclude-if-present`` in your backup,
  3281. and not include any other contents of the containing folder, this can be enabled
  3282. through using the ``--keep-exclude-tags`` option.
  3283. The ``-x`` or ``--one-file-system`` option excludes directories, that are mountpoints (and everything in them).
  3284. It detects mountpoints by comparing the device number from the output of ``stat()`` of the directory and its
  3285. parent directory. Specifically, it excludes directories for which ``stat()`` reports a device number different
  3286. from the device number of their parent. Be aware that in Linux (and possibly elsewhere) there are directories
  3287. with device number different from their parent, which the kernel does not consider a mountpoint and also the
  3288. other way around. Examples are bind mounts (possibly same device number, but always a mountpoint) and ALL
  3289. subvolumes of a btrfs (different device number from parent but not necessarily a mountpoint). Therefore when
  3290. using ``--one-file-system``, one should make doubly sure that the backup works as intended especially when using
  3291. btrfs. This is even more important, if the btrfs layout was created by someone else, e.g. a distribution
  3292. installer.
  3293. .. _list_item_flags:
  3294. Item flags
  3295. ++++++++++
  3296. ``--list`` outputs a list of all files, directories and other
  3297. file system items it considered (no matter whether they had content changes
  3298. or not). For each item, it prefixes a single-letter flag that indicates type
  3299. and/or status of the item.
  3300. If you are interested only in a subset of that output, you can give e.g.
  3301. ``--filter=AME`` and it will only show regular files with A, M or E status (see
  3302. below).
  3303. A uppercase character represents the status of a regular file relative to the
  3304. "files" cache (not relative to the repo -- this is an issue if the files cache
  3305. is not used). Metadata is stored in any case and for 'A' and 'M' also new data
  3306. chunks are stored. For 'U' all data chunks refer to already existing chunks.
  3307. - 'A' = regular file, added (see also :ref:`a_status_oddity` in the FAQ)
  3308. - 'M' = regular file, modified
  3309. - 'U' = regular file, unchanged
  3310. - 'C' = regular file, it changed while we backed it up
  3311. - 'E' = regular file, an error happened while accessing/reading *this* file
  3312. A lowercase character means a file type other than a regular file,
  3313. borg usually just stores their metadata:
  3314. - 'd' = directory
  3315. - 'b' = block device
  3316. - 'c' = char device
  3317. - 'h' = regular file, hardlink (to already seen inodes)
  3318. - 's' = symlink
  3319. - 'f' = fifo
  3320. Other flags used include:
  3321. - 'i' = backup data was read from standard input (stdin)
  3322. - '-' = dry run, item was *not* backed up
  3323. - 'x' = excluded, item was *not* backed up
  3324. - '?' = missing status code (if you see this, please file a bug report!)
  3325. Reading from stdin
  3326. ++++++++++++++++++
  3327. There are two methods to read from stdin. Either specify ``-`` as path and
  3328. pipe directly to borg::
  3329. backup-vm --id myvm --stdout | borg create REPO::ARCHIVE -
  3330. Or use ``--content-from-command`` to have Borg manage the execution of the
  3331. command and piping. If you do so, the first PATH argument is interpreted
  3332. as command to execute and any further arguments are treated as arguments
  3333. to the command::
  3334. borg create --content-from-command REPO::ARCHIVE -- backup-vm --id myvm --stdout
  3335. ``--`` is used to ensure ``--id`` and ``--stdout`` are **not** considered
  3336. arguments to ``borg`` but rather ``backup-vm``.
  3337. The difference between the two approaches is that piping to borg creates an
  3338. archive even if the command piping to borg exits with a failure. In this case,
  3339. **one can end up with truncated output being backed up**. Using
  3340. ``--content-from-command``, in contrast, borg is guaranteed to fail without
  3341. creating an archive should the command fail. The command is considered failed
  3342. when it returned a non-zero exit code.
  3343. Reading from stdin yields just a stream of data without file metadata
  3344. associated with it, and the files cache is not needed at all. So it is
  3345. safe to disable it via ``--files-cache disabled`` and speed up backup
  3346. creation a bit.
  3347. By default, the content read from stdin is stored in a file called 'stdin'.
  3348. Use ``--stdin-name`` to change the name.
  3349. """)
  3350. subparser = subparsers.add_parser('create', parents=[common_parser], add_help=False,
  3351. description=self.do_create.__doc__,
  3352. epilog=create_epilog,
  3353. formatter_class=argparse.RawDescriptionHelpFormatter,
  3354. help='create backup')
  3355. subparser.set_defaults(func=self.do_create)
  3356. # note: --dry-run and --stats are mutually exclusive, but we do not want to abort when
  3357. # parsing, but rather proceed with the dry-run, but without stats (see run() method).
  3358. subparser.add_argument('-n', '--dry-run', dest='dry_run', action='store_true',
  3359. help='do not create a backup archive')
  3360. subparser.add_argument('-s', '--stats', dest='stats', action='store_true',
  3361. help='print statistics for the created archive')
  3362. subparser.add_argument('--list', dest='output_list', action='store_true',
  3363. help='output verbose list of items (files, dirs, ...)')
  3364. subparser.add_argument('--filter', metavar='STATUSCHARS', dest='output_filter', action=Highlander,
  3365. help='only display items with the given status characters (see description)')
  3366. subparser.add_argument('--json', action='store_true',
  3367. help='output stats as JSON. Implies ``--stats``.')
  3368. subparser.add_argument('--no-cache-sync', dest='no_cache_sync', action='store_true',
  3369. help='experimental: do not synchronize the cache. Implies not using the files cache.')
  3370. subparser.add_argument('--stdin-name', metavar='NAME', dest='stdin_name', default='stdin',
  3371. help='use NAME in archive for stdin data (default: %(default)r)')
  3372. subparser.add_argument('--stdin-user', metavar='USER', dest='stdin_user', default=uid2user(0),
  3373. help='set user USER in archive for stdin data (default: %(default)r)')
  3374. subparser.add_argument('--stdin-group', metavar='GROUP', dest='stdin_group', default=gid2group(0),
  3375. help='set group GROUP in archive for stdin data (default: %(default)r)')
  3376. subparser.add_argument('--stdin-mode', metavar='M', dest='stdin_mode', type=lambda s: int(s, 8), default=STDIN_MODE_DEFAULT,
  3377. help='set mode to M in archive for stdin data (default: %(default)04o)')
  3378. subparser.add_argument('--content-from-command', action='store_true',
  3379. help='interpret PATH as command and store its stdout. See also section Reading from'
  3380. ' stdin below.')
  3381. subparser.add_argument('--paths-from-stdin', action='store_true',
  3382. help='read DELIM-separated list of paths to backup from stdin. Will not '
  3383. 'recurse into directories.')
  3384. subparser.add_argument('--paths-from-command', action='store_true',
  3385. help='interpret PATH as command and treat its output as ``--paths-from-stdin``')
  3386. subparser.add_argument('--paths-delimiter', metavar='DELIM',
  3387. help='set path delimiter for ``--paths-from-stdin`` and ``--paths-from-command`` (default: \\n) ')
  3388. exclude_group = define_exclusion_group(subparser, tag_files=True)
  3389. exclude_group.add_argument('--exclude-nodump', dest='exclude_nodump', action='store_true',
  3390. help='exclude files flagged NODUMP')
  3391. fs_group = subparser.add_argument_group('Filesystem options')
  3392. fs_group.add_argument('-x', '--one-file-system', dest='one_file_system', action='store_true',
  3393. help='stay in the same file system and do not store mount points of other file systems. This might behave different from your expectations, see the docs.')
  3394. fs_group.add_argument('--numeric-owner', dest='numeric_ids', action='store_true',
  3395. help='deprecated, use ``--numeric-ids`` instead')
  3396. fs_group.add_argument('--numeric-ids', dest='numeric_ids', action='store_true',
  3397. help='only store numeric user and group identifiers')
  3398. # --noatime is the default now and the flag is deprecated. args.noatime is not used any more.
  3399. # use --atime if you want to store the atime (default behaviour before borg 1.2.0a7)..
  3400. fs_group.add_argument('--noatime', dest='noatime', action='store_true',
  3401. help='do not store atime into archive')
  3402. fs_group.add_argument('--atime', dest='atime', action='store_true',
  3403. help='do store atime into archive')
  3404. fs_group.add_argument('--noctime', dest='noctime', action='store_true',
  3405. help='do not store ctime into archive')
  3406. fs_group.add_argument('--nobirthtime', dest='nobirthtime', action='store_true',
  3407. help='do not store birthtime (creation date) into archive')
  3408. fs_group.add_argument('--nobsdflags', dest='nobsdflags', action='store_true',
  3409. help='deprecated, use ``--noflags`` instead')
  3410. fs_group.add_argument('--noflags', dest='noflags', action='store_true',
  3411. help='do not read and store flags (e.g. NODUMP, IMMUTABLE) into archive')
  3412. fs_group.add_argument('--noacls', dest='noacls', action='store_true',
  3413. help='do not read and store ACLs into archive')
  3414. fs_group.add_argument('--noxattrs', dest='noxattrs', action='store_true',
  3415. help='do not read and store xattrs into archive')
  3416. fs_group.add_argument('--sparse', dest='sparse', action='store_true',
  3417. help='detect sparse holes in input (supported only by fixed chunker)')
  3418. fs_group.add_argument('--files-cache', metavar='MODE', dest='files_cache_mode', action=Highlander,
  3419. type=FilesCacheMode, default=DEFAULT_FILES_CACHE_MODE_UI,
  3420. help='operate files cache in MODE. default: %s' % DEFAULT_FILES_CACHE_MODE_UI)
  3421. fs_group.add_argument('--read-special', dest='read_special', action='store_true',
  3422. help='open and read block and char device files as well as FIFOs as if they were '
  3423. 'regular files. Also follows symlinks pointing to these kinds of files.')
  3424. archive_group = subparser.add_argument_group('Archive options')
  3425. archive_group.add_argument('--comment', dest='comment', metavar='COMMENT', type=CommentSpec, default='',
  3426. help='add a comment text to the archive')
  3427. archive_group.add_argument('--timestamp', metavar='TIMESTAMP', dest='timestamp',
  3428. type=timestamp, default=None,
  3429. help='manually specify the archive creation date/time (UTC, yyyy-mm-ddThh:mm:ss format). '
  3430. 'Alternatively, give a reference file/directory.')
  3431. archive_group.add_argument('-c', '--checkpoint-interval', metavar='SECONDS', dest='checkpoint_interval',
  3432. type=int, default=1800,
  3433. help='write checkpoint every SECONDS seconds (Default: 1800)')
  3434. archive_group.add_argument('--chunker-params', metavar='PARAMS', dest='chunker_params',
  3435. type=ChunkerParams, default=CHUNKER_PARAMS, action=Highlander,
  3436. help='specify the chunker parameters (ALGO, CHUNK_MIN_EXP, CHUNK_MAX_EXP, '
  3437. 'HASH_MASK_BITS, HASH_WINDOW_SIZE). default: %s,%d,%d,%d,%d' % CHUNKER_PARAMS)
  3438. archive_group.add_argument('-C', '--compression', metavar='COMPRESSION', dest='compression',
  3439. type=CompressionSpec, default=CompressionSpec('lz4'),
  3440. help='select compression algorithm, see the output of the '
  3441. '"borg help compression" command for details.')
  3442. subparser.add_argument('location', metavar='ARCHIVE',
  3443. type=location_validator(archive=True),
  3444. help='name of archive to create (must be also a valid directory name)')
  3445. subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
  3446. help='paths to archive')
  3447. # borg debug
  3448. debug_epilog = process_epilog("""
  3449. These commands are not intended for normal use and potentially very
  3450. dangerous if used incorrectly.
  3451. They exist to improve debugging capabilities without direct system access, e.g.
  3452. in case you ever run into some severe malfunction. Use them only if you know
  3453. what you are doing or if a trusted developer tells you what to do.""")
  3454. subparser = subparsers.add_parser('debug', parents=[mid_common_parser], add_help=False,
  3455. description='debugging command (not intended for normal use)',
  3456. epilog=debug_epilog,
  3457. formatter_class=argparse.RawDescriptionHelpFormatter,
  3458. help='debugging command (not intended for normal use)')
  3459. debug_parsers = subparser.add_subparsers(title='required arguments', metavar='<command>')
  3460. subparser.set_defaults(fallback_func=functools.partial(self.do_subcommand_help, subparser))
  3461. debug_info_epilog = process_epilog("""
  3462. This command displays some system information that might be useful for bug
  3463. reports and debugging problems. If a traceback happens, this information is
  3464. already appended at the end of the traceback.
  3465. """)
  3466. subparser = debug_parsers.add_parser('info', parents=[common_parser], add_help=False,
  3467. description=self.do_debug_info.__doc__,
  3468. epilog=debug_info_epilog,
  3469. formatter_class=argparse.RawDescriptionHelpFormatter,
  3470. help='show system infos for debugging / bug reports (debug)')
  3471. subparser.set_defaults(func=self.do_debug_info)
  3472. debug_dump_archive_items_epilog = process_epilog("""
  3473. This command dumps raw (but decrypted and decompressed) archive items (only metadata) to files.
  3474. """)
  3475. subparser = debug_parsers.add_parser('dump-archive-items', parents=[common_parser], add_help=False,
  3476. description=self.do_debug_dump_archive_items.__doc__,
  3477. epilog=debug_dump_archive_items_epilog,
  3478. formatter_class=argparse.RawDescriptionHelpFormatter,
  3479. help='dump archive items (metadata) (debug)')
  3480. subparser.set_defaults(func=self.do_debug_dump_archive_items)
  3481. subparser.add_argument('location', metavar='ARCHIVE',
  3482. type=location_validator(archive=True),
  3483. help='archive to dump')
  3484. debug_dump_archive_epilog = process_epilog("""
  3485. This command dumps all metadata of an archive in a decoded form to a file.
  3486. """)
  3487. subparser = debug_parsers.add_parser('dump-archive', parents=[common_parser], add_help=False,
  3488. description=self.do_debug_dump_archive.__doc__,
  3489. epilog=debug_dump_archive_epilog,
  3490. formatter_class=argparse.RawDescriptionHelpFormatter,
  3491. help='dump decoded archive metadata (debug)')
  3492. subparser.set_defaults(func=self.do_debug_dump_archive)
  3493. subparser.add_argument('location', metavar='ARCHIVE',
  3494. type=location_validator(archive=True),
  3495. help='archive to dump')
  3496. subparser.add_argument('path', metavar='PATH', type=str,
  3497. help='file to dump data into')
  3498. debug_dump_manifest_epilog = process_epilog("""
  3499. This command dumps manifest metadata of a repository in a decoded form to a file.
  3500. """)
  3501. subparser = debug_parsers.add_parser('dump-manifest', parents=[common_parser], add_help=False,
  3502. description=self.do_debug_dump_manifest.__doc__,
  3503. epilog=debug_dump_manifest_epilog,
  3504. formatter_class=argparse.RawDescriptionHelpFormatter,
  3505. help='dump decoded repository metadata (debug)')
  3506. subparser.set_defaults(func=self.do_debug_dump_manifest)
  3507. subparser.add_argument('location', metavar='REPOSITORY',
  3508. type=location_validator(archive=False),
  3509. help='repository to dump')
  3510. subparser.add_argument('path', metavar='PATH', type=str,
  3511. help='file to dump data into')
  3512. debug_dump_repo_objs_epilog = process_epilog("""
  3513. This command dumps raw (but decrypted and decompressed) repo objects to files.
  3514. """)
  3515. subparser = debug_parsers.add_parser('dump-repo-objs', parents=[common_parser], add_help=False,
  3516. description=self.do_debug_dump_repo_objs.__doc__,
  3517. epilog=debug_dump_repo_objs_epilog,
  3518. formatter_class=argparse.RawDescriptionHelpFormatter,
  3519. help='dump repo objects (debug)')
  3520. subparser.set_defaults(func=self.do_debug_dump_repo_objs)
  3521. subparser.add_argument('location', metavar='REPOSITORY',
  3522. type=location_validator(archive=False),
  3523. help='repository to dump')
  3524. subparser.add_argument('--ghost', dest='ghost', action='store_true',
  3525. help='dump all segment file contents, including deleted/uncommitted objects and commits.')
  3526. debug_search_repo_objs_epilog = process_epilog("""
  3527. This command searches raw (but decrypted and decompressed) repo objects for a specific bytes sequence.
  3528. """)
  3529. subparser = debug_parsers.add_parser('search-repo-objs', parents=[common_parser], add_help=False,
  3530. description=self.do_debug_search_repo_objs.__doc__,
  3531. epilog=debug_search_repo_objs_epilog,
  3532. formatter_class=argparse.RawDescriptionHelpFormatter,
  3533. help='search repo objects (debug)')
  3534. subparser.set_defaults(func=self.do_debug_search_repo_objs)
  3535. subparser.add_argument('location', metavar='REPOSITORY',
  3536. type=location_validator(archive=False),
  3537. help='repository to search')
  3538. subparser.add_argument('wanted', metavar='WANTED', type=str,
  3539. help='term to search the repo for, either 0x1234abcd hex term or a string')
  3540. debug_get_obj_epilog = process_epilog("""
  3541. This command gets an object from the repository.
  3542. """)
  3543. subparser = debug_parsers.add_parser('get-obj', parents=[common_parser], add_help=False,
  3544. description=self.do_debug_get_obj.__doc__,
  3545. epilog=debug_get_obj_epilog,
  3546. formatter_class=argparse.RawDescriptionHelpFormatter,
  3547. help='get object from repository (debug)')
  3548. subparser.set_defaults(func=self.do_debug_get_obj)
  3549. subparser.add_argument('location', metavar='REPOSITORY',
  3550. type=location_validator(archive=False),
  3551. help='repository to use')
  3552. subparser.add_argument('id', metavar='ID', type=str,
  3553. help='hex object ID to get from the repo')
  3554. subparser.add_argument('path', metavar='PATH', type=str,
  3555. help='file to write object data into')
  3556. debug_put_obj_epilog = process_epilog("""
  3557. This command puts objects into the repository.
  3558. """)
  3559. subparser = debug_parsers.add_parser('put-obj', parents=[common_parser], add_help=False,
  3560. description=self.do_debug_put_obj.__doc__,
  3561. epilog=debug_put_obj_epilog,
  3562. formatter_class=argparse.RawDescriptionHelpFormatter,
  3563. help='put object to repository (debug)')
  3564. subparser.set_defaults(func=self.do_debug_put_obj)
  3565. subparser.add_argument('location', metavar='REPOSITORY',
  3566. type=location_validator(archive=False),
  3567. help='repository to use')
  3568. subparser.add_argument('paths', metavar='PATH', nargs='+', type=str,
  3569. help='file(s) to read and create object(s) from')
  3570. debug_delete_obj_epilog = process_epilog("""
  3571. This command deletes objects from the repository.
  3572. """)
  3573. subparser = debug_parsers.add_parser('delete-obj', parents=[common_parser], add_help=False,
  3574. description=self.do_debug_delete_obj.__doc__,
  3575. epilog=debug_delete_obj_epilog,
  3576. formatter_class=argparse.RawDescriptionHelpFormatter,
  3577. help='delete object from repository (debug)')
  3578. subparser.set_defaults(func=self.do_debug_delete_obj)
  3579. subparser.add_argument('location', metavar='REPOSITORY',
  3580. type=location_validator(archive=False),
  3581. help='repository to use')
  3582. subparser.add_argument('ids', metavar='IDs', nargs='+', type=str,
  3583. help='hex object ID(s) to delete from the repo')
  3584. debug_refcount_obj_epilog = process_epilog("""
  3585. This command displays the reference count for objects from the repository.
  3586. """)
  3587. subparser = debug_parsers.add_parser('refcount-obj', parents=[common_parser], add_help=False,
  3588. description=self.do_debug_refcount_obj.__doc__,
  3589. epilog=debug_refcount_obj_epilog,
  3590. formatter_class=argparse.RawDescriptionHelpFormatter,
  3591. help='show refcount for object from repository (debug)')
  3592. subparser.set_defaults(func=self.do_debug_refcount_obj)
  3593. subparser.add_argument('location', metavar='REPOSITORY',
  3594. type=location_validator(archive=False),
  3595. help='repository to use')
  3596. subparser.add_argument('ids', metavar='IDs', nargs='+', type=str,
  3597. help='hex object ID(s) to show refcounts for')
  3598. debug_dump_hints_epilog = process_epilog("""
  3599. This command dumps the repository hints data.
  3600. """)
  3601. subparser = debug_parsers.add_parser('dump-hints', parents=[common_parser], add_help=False,
  3602. description=self.do_debug_dump_hints.__doc__,
  3603. epilog=debug_dump_hints_epilog,
  3604. formatter_class=argparse.RawDescriptionHelpFormatter,
  3605. help='dump repo hints (debug)')
  3606. subparser.set_defaults(func=self.do_debug_dump_hints)
  3607. subparser.add_argument('location', metavar='REPOSITORY',
  3608. type=location_validator(archive=False),
  3609. help='repository to dump')
  3610. subparser.add_argument('path', metavar='PATH', type=str,
  3611. help='file to dump data into')
  3612. debug_convert_profile_epilog = process_epilog("""
  3613. Convert a Borg profile to a Python cProfile compatible profile.
  3614. """)
  3615. subparser = debug_parsers.add_parser('convert-profile', parents=[common_parser], add_help=False,
  3616. description=self.do_debug_convert_profile.__doc__,
  3617. epilog=debug_convert_profile_epilog,
  3618. formatter_class=argparse.RawDescriptionHelpFormatter,
  3619. help='convert Borg profile to Python profile (debug)')
  3620. subparser.set_defaults(func=self.do_debug_convert_profile)
  3621. subparser.add_argument('input', metavar='INPUT', type=argparse.FileType('rb'),
  3622. help='Borg profile')
  3623. subparser.add_argument('output', metavar='OUTPUT', type=argparse.FileType('wb'),
  3624. help='Output file')
  3625. # borg delete
  3626. delete_epilog = process_epilog("""
  3627. This command deletes an archive from the repository or the complete repository.
  3628. Important: When deleting archives, repository disk space is **not** freed until
  3629. you run ``borg compact``.
  3630. When you delete a complete repository, the security info and local cache for it
  3631. (if any) are also deleted. Alternatively, you can delete just the local cache
  3632. with the ``--cache-only`` option, or keep the security info with the
  3633. ``--keep-security-info`` option.
  3634. When in doubt, use ``--dry-run --list`` to see what would be deleted.
  3635. When using ``--stats``, you will get some statistics about how much data was
  3636. deleted - the "Deleted data" deduplicated size there is most interesting as
  3637. that is how much your repository will shrink.
  3638. Please note that the "All archives" stats refer to the state after deletion.
  3639. You can delete multiple archives by specifying their common prefix, if they
  3640. have one, using the ``--prefix PREFIX`` option. You can also specify a shell
  3641. pattern to match multiple archives using the ``--glob-archives GLOB`` option
  3642. (for more info on these patterns, see :ref:`borg_patterns`). Note that these
  3643. two options are mutually exclusive.
  3644. To avoid accidentally deleting archives, especially when using glob patterns,
  3645. it might be helpful to use the ``--dry-run`` to test out the command without
  3646. actually making any changes to the repository.
  3647. """)
  3648. subparser = subparsers.add_parser('delete', parents=[common_parser], add_help=False,
  3649. description=self.do_delete.__doc__,
  3650. epilog=delete_epilog,
  3651. formatter_class=argparse.RawDescriptionHelpFormatter,
  3652. help='delete archive')
  3653. subparser.set_defaults(func=self.do_delete)
  3654. subparser.add_argument('-n', '--dry-run', dest='dry_run', action='store_true',
  3655. help='do not change repository')
  3656. subparser.add_argument('--list', dest='output_list', action='store_true',
  3657. help='output verbose list of archives')
  3658. subparser.add_argument('-s', '--stats', dest='stats', action='store_true',
  3659. help='print statistics for the deleted archive')
  3660. subparser.add_argument('--cache-only', dest='cache_only', action='store_true',
  3661. help='delete only the local cache for the given repository')
  3662. subparser.add_argument('--force', dest='forced', action='count', default=0,
  3663. help='force deletion of corrupted archives, '
  3664. 'use ``--force --force`` in case ``--force`` does not work.')
  3665. subparser.add_argument('--keep-security-info', dest='keep_security_info', action='store_true',
  3666. help='keep the local security info when deleting a repository')
  3667. subparser.add_argument('--save-space', dest='save_space', action='store_true',
  3668. help='work slower, but using less space')
  3669. subparser.add_argument('location', metavar='REPOSITORY_OR_ARCHIVE', nargs='?', default='',
  3670. type=location_validator(),
  3671. help='repository or archive to delete')
  3672. subparser.add_argument('archives', metavar='ARCHIVE', nargs='*',
  3673. help='archives to delete')
  3674. define_archive_filters_group(subparser)
  3675. # borg transfer
  3676. transfer_epilog = process_epilog("""
  3677. This command transfers archives from one repository to another repository.
  3678. Suggested use:
  3679. # initialize DST_REPO reusing key material from SRC_REPO, so that
  3680. # chunking and chunk id generation will work in the same way as before.
  3681. borg init --other-location=SRC_REPO --encryption=DST_ENC DST_REPO
  3682. # transfer archives from SRC_REPO to DST_REPO
  3683. borg transfer --dry-run SRC_REPO DST_REPO # check what it would do
  3684. borg transfer SRC_REPO DST_REPO # do it!
  3685. borg transfer --dry-run SRC_REPO DST_REPO # check! anything left?
  3686. The default is to transfer all archives, including checkpoint archives.
  3687. You could use the misc. archive filter options to limit which archives it will
  3688. transfer, e.g. using the --prefix option. This is recommended for big
  3689. repositories with multiple data sets to keep the runtime per invocation lower.
  3690. """)
  3691. subparser = subparsers.add_parser('transfer', parents=[common_parser], add_help=False,
  3692. description=self.do_transfer.__doc__,
  3693. epilog=transfer_epilog,
  3694. formatter_class=argparse.RawDescriptionHelpFormatter,
  3695. help='transfer of archives from another repository')
  3696. subparser.set_defaults(func=self.do_transfer)
  3697. subparser.add_argument('-n', '--dry-run', dest='dry_run', action='store_true',
  3698. help='do not change repository, just check')
  3699. subparser.add_argument('other_location', metavar='SRC_REPOSITORY',
  3700. type=location_validator(archive=False, other=True),
  3701. help='source repository')
  3702. subparser.add_argument('location', metavar='DST_REPOSITORY',
  3703. type=location_validator(archive=False, other=False),
  3704. help='destination repository')
  3705. define_archive_filters_group(subparser)
  3706. # borg diff
  3707. diff_epilog = process_epilog("""
  3708. This command finds differences (file contents, user/group/mode) between archives.
  3709. A repository location and an archive name must be specified for REPO::ARCHIVE1.
  3710. ARCHIVE2 is just another archive name in same repository (no repository location
  3711. allowed).
  3712. For archives created with Borg 1.1 or newer diff automatically detects whether
  3713. the archives are created with the same chunker params. If so, only chunk IDs
  3714. are compared, which is very fast.
  3715. For archives prior to Borg 1.1 chunk contents are compared by default.
  3716. If you did not create the archives with different chunker params,
  3717. pass ``--same-chunker-params``.
  3718. Note that the chunker params changed from Borg 0.xx to 1.0.
  3719. For more help on include/exclude patterns, see the :ref:`borg_patterns` command output.
  3720. """)
  3721. subparser = subparsers.add_parser('diff', parents=[common_parser], add_help=False,
  3722. description=self.do_diff.__doc__,
  3723. epilog=diff_epilog,
  3724. formatter_class=argparse.RawDescriptionHelpFormatter,
  3725. help='find differences in archive contents')
  3726. subparser.set_defaults(func=self.do_diff)
  3727. subparser.add_argument('--numeric-owner', dest='numeric_ids', action='store_true',
  3728. help='deprecated, use ``--numeric-ids`` instead')
  3729. subparser.add_argument('--numeric-ids', dest='numeric_ids', action='store_true',
  3730. help='only consider numeric user and group identifiers')
  3731. subparser.add_argument('--same-chunker-params', dest='same_chunker_params', action='store_true',
  3732. help='Override check of chunker parameters.')
  3733. subparser.add_argument('--sort', dest='sort', action='store_true',
  3734. help='Sort the output lines by file path.')
  3735. subparser.add_argument('--json-lines', action='store_true',
  3736. help='Format output as JSON Lines. ')
  3737. subparser.add_argument('location', metavar='REPO::ARCHIVE1',
  3738. type=location_validator(archive=True),
  3739. help='repository location and ARCHIVE1 name')
  3740. subparser.add_argument('archive2', metavar='ARCHIVE2',
  3741. type=archivename_validator(),
  3742. help='ARCHIVE2 name (no repository location allowed)')
  3743. subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
  3744. help='paths of items inside the archives to compare; patterns are supported')
  3745. define_exclusion_group(subparser)
  3746. # borg export-tar
  3747. export_tar_epilog = process_epilog("""
  3748. This command creates a tarball from an archive.
  3749. When giving '-' as the output FILE, Borg will write a tar stream to standard output.
  3750. By default (``--tar-filter=auto``) Borg will detect whether the FILE should be compressed
  3751. based on its file extension and pipe the tarball through an appropriate filter
  3752. before writing it to FILE:
  3753. - .tar.gz or .tgz: gzip
  3754. - .tar.bz2 or .tbz: bzip2
  3755. - .tar.xz or .txz: xz
  3756. - .tar.zstd: zstd
  3757. - .tar.lz4: lz4
  3758. Alternatively, a ``--tar-filter`` program may be explicitly specified. It should
  3759. read the uncompressed tar stream from stdin and write a compressed/filtered
  3760. tar stream to stdout.
  3761. Depending on the ``-tar-format`` option, these formats are created:
  3762. +--------------+---------------------------+----------------------------+
  3763. | --tar-format | Specification | Metadata |
  3764. +--------------+---------------------------+----------------------------+
  3765. | BORG | BORG specific, like PAX | all as supported by borg |
  3766. +--------------+---------------------------+----------------------------+
  3767. | PAX | POSIX.1-2001 (pax) format | GNU + atime/ctime/mtime ns |
  3768. +--------------+---------------------------+----------------------------+
  3769. | GNU | GNU tar format | mtime s, no atime/ctime, |
  3770. | | | no ACLs/xattrs/bsdflags |
  3771. +--------------+---------------------------+----------------------------+
  3772. A ``--sparse`` option (as found in borg extract) is not supported.
  3773. By default the entire archive is extracted but a subset of files and directories
  3774. can be selected by passing a list of ``PATHs`` as arguments.
  3775. The file selection can further be restricted by using the ``--exclude`` option.
  3776. For more help on include/exclude patterns, see the :ref:`borg_patterns` command output.
  3777. ``--progress`` can be slower than no progress display, since it makes one additional
  3778. pass over the archive metadata.
  3779. """)
  3780. subparser = subparsers.add_parser('export-tar', parents=[common_parser], add_help=False,
  3781. description=self.do_export_tar.__doc__,
  3782. epilog=export_tar_epilog,
  3783. formatter_class=argparse.RawDescriptionHelpFormatter,
  3784. help='create tarball from archive')
  3785. subparser.set_defaults(func=self.do_export_tar)
  3786. subparser.add_argument('--tar-filter', dest='tar_filter', default='auto',
  3787. help='filter program to pipe data through')
  3788. subparser.add_argument('--list', dest='output_list', action='store_true',
  3789. help='output verbose list of items (files, dirs, ...)')
  3790. subparser.add_argument('--tar-format', metavar='FMT', dest='tar_format', default='GNU',
  3791. choices=('BORG', 'PAX', 'GNU'),
  3792. help='select tar format: BORG, PAX or GNU')
  3793. subparser.add_argument('location', metavar='ARCHIVE',
  3794. type=location_validator(archive=True),
  3795. help='archive to export')
  3796. subparser.add_argument('tarfile', metavar='FILE',
  3797. help='output tar file. "-" to write to stdout instead.')
  3798. subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
  3799. help='paths to extract; patterns are supported')
  3800. define_exclusion_group(subparser, strip_components=True)
  3801. # borg extract
  3802. extract_epilog = process_epilog("""
  3803. This command extracts the contents of an archive. By default the entire
  3804. archive is extracted but a subset of files and directories can be selected
  3805. by passing a list of ``PATHs`` as arguments. The file selection can further
  3806. be restricted by using the ``--exclude`` option.
  3807. For more help on include/exclude patterns, see the :ref:`borg_patterns` command output.
  3808. By using ``--dry-run``, you can do all extraction steps except actually writing the
  3809. output data: reading metadata and data chunks from the repo, checking the hash/hmac,
  3810. decrypting, decompressing.
  3811. ``--progress`` can be slower than no progress display, since it makes one additional
  3812. pass over the archive metadata.
  3813. .. note::
  3814. Currently, extract always writes into the current working directory ("."),
  3815. so make sure you ``cd`` to the right place before calling ``borg extract``.
  3816. When parent directories are not extracted (because of using file/directory selection
  3817. or any other reason), borg can not restore parent directories' metadata, e.g. owner,
  3818. group, permission, etc.
  3819. """)
  3820. subparser = subparsers.add_parser('extract', parents=[common_parser], add_help=False,
  3821. description=self.do_extract.__doc__,
  3822. epilog=extract_epilog,
  3823. formatter_class=argparse.RawDescriptionHelpFormatter,
  3824. help='extract archive contents')
  3825. subparser.set_defaults(func=self.do_extract)
  3826. subparser.add_argument('--list', dest='output_list', action='store_true',
  3827. help='output verbose list of items (files, dirs, ...)')
  3828. subparser.add_argument('-n', '--dry-run', dest='dry_run', action='store_true',
  3829. help='do not actually change any files')
  3830. subparser.add_argument('--numeric-owner', dest='numeric_ids', action='store_true',
  3831. help='deprecated, use ``--numeric-ids`` instead')
  3832. subparser.add_argument('--numeric-ids', dest='numeric_ids', action='store_true',
  3833. help='only obey numeric user and group identifiers')
  3834. subparser.add_argument('--nobsdflags', dest='nobsdflags', action='store_true',
  3835. help='deprecated, use ``--noflags`` instead')
  3836. subparser.add_argument('--noflags', dest='noflags', action='store_true',
  3837. help='do not extract/set flags (e.g. NODUMP, IMMUTABLE)')
  3838. subparser.add_argument('--noacls', dest='noacls', action='store_true',
  3839. help='do not extract/set ACLs')
  3840. subparser.add_argument('--noxattrs', dest='noxattrs', action='store_true',
  3841. help='do not extract/set xattrs')
  3842. subparser.add_argument('--stdout', dest='stdout', action='store_true',
  3843. help='write all extracted data to stdout')
  3844. subparser.add_argument('--sparse', dest='sparse', action='store_true',
  3845. help='create holes in output sparse file from all-zero chunks')
  3846. subparser.add_argument('location', metavar='ARCHIVE',
  3847. type=location_validator(archive=True),
  3848. help='archive to extract')
  3849. subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
  3850. help='paths to extract; patterns are supported')
  3851. define_exclusion_group(subparser, strip_components=True)
  3852. # borg help
  3853. subparser = subparsers.add_parser('help', parents=[common_parser], add_help=False,
  3854. description='Extra help')
  3855. subparser.add_argument('--epilog-only', dest='epilog_only', action='store_true')
  3856. subparser.add_argument('--usage-only', dest='usage_only', action='store_true')
  3857. subparser.set_defaults(func=functools.partial(self.do_help, parser, subparsers.choices))
  3858. subparser.add_argument('topic', metavar='TOPIC', type=str, nargs='?',
  3859. help='additional help on TOPIC')
  3860. # borg info
  3861. info_epilog = process_epilog("""
  3862. This command displays detailed information about the specified archive or repository.
  3863. Please note that the deduplicated sizes of the individual archives do not add
  3864. up to the deduplicated size of the repository ("all archives"), because the two
  3865. are meaning different things:
  3866. This archive / deduplicated size = amount of data stored ONLY for this archive
  3867. = unique chunks of this archive.
  3868. All archives / deduplicated size = amount of data stored in the repo
  3869. = all chunks in the repository.
  3870. Borg archives can only contain a limited amount of file metadata.
  3871. The size of an archive relative to this limit depends on a number of factors,
  3872. mainly the number of files, the lengths of paths and other metadata stored for files.
  3873. This is shown as *utilization of maximum supported archive size*.
  3874. """)
  3875. subparser = subparsers.add_parser('info', parents=[common_parser], add_help=False,
  3876. description=self.do_info.__doc__,
  3877. epilog=info_epilog,
  3878. formatter_class=argparse.RawDescriptionHelpFormatter,
  3879. help='show repository or archive information')
  3880. subparser.set_defaults(func=self.do_info)
  3881. subparser.add_argument('location', metavar='REPOSITORY_OR_ARCHIVE', nargs='?', default='',
  3882. type=location_validator(),
  3883. help='repository or archive to display information about')
  3884. subparser.add_argument('--json', action='store_true',
  3885. help='format output as JSON')
  3886. define_archive_filters_group(subparser)
  3887. # borg init
  3888. init_epilog = process_epilog("""
  3889. This command initializes an empty repository. A repository is a filesystem
  3890. directory containing the deduplicated data from zero or more archives.
  3891. Encryption mode TLDR
  3892. ++++++++++++++++++++
  3893. The encryption mode can only be configured when creating a new repository - you can
  3894. neither configure it on a per-archive basis nor change the mode of an existing repository.
  3895. This example will likely NOT give optimum performance on your machine (performance
  3896. tips will come below):
  3897. ::
  3898. borg init --encryption repokey /path/to/repo
  3899. Borg will:
  3900. 1. Ask you to come up with a passphrase.
  3901. 2. Create a borg key (which contains some random secrets. See :ref:`key_files`).
  3902. 3. Derive a "key encryption key" from your passphrase
  3903. 4. Encrypt and sign the key with the key encryption key
  3904. 5. Store the encrypted borg key inside the repository directory (in the repo config).
  3905. This is why it is essential to use a secure passphrase.
  3906. 6. Encrypt and sign your backups to prevent anyone from reading or forging them unless they
  3907. have the key and know the passphrase. Make sure to keep a backup of
  3908. your key **outside** the repository - do not lock yourself out by
  3909. "leaving your keys inside your car" (see :ref:`borg_key_export`).
  3910. For remote backups the encryption is done locally - the remote machine
  3911. never sees your passphrase, your unencrypted key or your unencrypted files.
  3912. Chunking and id generation are also based on your key to improve
  3913. your privacy.
  3914. 7. Use the key when extracting files to decrypt them and to verify that the contents of
  3915. the backups have not been accidentally or maliciously altered.
  3916. Picking a passphrase
  3917. ++++++++++++++++++++
  3918. Make sure you use a good passphrase. Not too short, not too simple. The real
  3919. encryption / decryption key is encrypted with / locked by your passphrase.
  3920. If an attacker gets your key, he can't unlock and use it without knowing the
  3921. passphrase.
  3922. Be careful with special or non-ascii characters in your passphrase:
  3923. - Borg processes the passphrase as unicode (and encodes it as utf-8),
  3924. so it does not have problems dealing with even the strangest characters.
  3925. - BUT: that does not necessarily apply to your OS / VM / keyboard configuration.
  3926. So better use a long passphrase made from simple ascii chars than one that
  3927. includes non-ascii stuff or characters that are hard/impossible to enter on
  3928. a different keyboard layout.
  3929. You can change your passphrase for existing repos at any time, it won't affect
  3930. the encryption/decryption key or other secrets.
  3931. Choosing an encryption mode
  3932. +++++++++++++++++++++++++++
  3933. Depending on your hardware, hashing and crypto performance may vary widely.
  3934. The easiest way to find out about what's fastest is to run ``borg benchmark cpu``.
  3935. `repokey` modes: if you want ease-of-use and "passphrase" security is good enough -
  3936. the key will be stored in the repository (in ``repo_dir/config``).
  3937. `keyfile` modes: if you rather want "passphrase and having-the-key" security -
  3938. the key will be stored in your home directory (in ``~/.config/borg/keys``).
  3939. The following table is roughly sorted in order of preference, the better ones are
  3940. in the upper part of the table, in the lower part is the old and/or unsafe(r) stuff:
  3941. .. nanorst: inline-fill
  3942. +-----------------------------------+--------------+----------------+--------------------+---------+
  3943. | Mode (K = keyfile or repokey) | ID-Hash | Encryption | Authentication | V >= |
  3944. +-----------------------------------+--------------+----------------+--------------------+---------+
  3945. | K-blake2-chacha20-poly1305 | BLAKE2b | CHACHA20 | POLY1305 | 1.3 |
  3946. +-----------------------------------+--------------+----------------+--------------------+---------+
  3947. | K-chacha20-poly1305 | HMAC-SHA-256 | CHACHA20 | POLY1305 | 1.3 |
  3948. +-----------------------------------+--------------+----------------+--------------------+---------+
  3949. | K-blake2-aes-ocb | BLAKE2b | AES256-OCB | AES256-OCB | 1.3 |
  3950. +-----------------------------------+--------------+----------------+--------------------+---------+
  3951. | K-aes-ocb | HMAC-SHA-256 | AES256-OCB | AES256-OCB | 1.3 |
  3952. +-----------------------------------+--------------+----------------+--------------------+---------+
  3953. | K-blake2 | BLAKE2b | AES256-CTR | BLAKE2b | 1.1 |
  3954. +-----------------------------------+--------------+----------------+--------------------+---------+
  3955. | K | HMAC-SHA-256 | AES256-CTR | HMAC-SHA256 | any |
  3956. +-----------------------------------+--------------+----------------+--------------------+---------+
  3957. | authenticated-blake2 | BLAKE2b | none | BLAKE2b | 1.1 |
  3958. +-----------------------------------+--------------+----------------+--------------------+---------+
  3959. | authenticated | HMAC-SHA-256 | none | HMAC-SHA256 | 1.1 |
  3960. +-----------------------------------+--------------+----------------+--------------------+---------+
  3961. | none | SHA-256 | none | none | any |
  3962. +-----------------------------------+--------------+----------------+--------------------+---------+
  3963. .. nanorst: inline-replace
  3964. `none` mode uses no encryption and no authentication. You're advised to NOT use this mode
  3965. as it would expose you to all sorts of issues (DoS, confidentiality, tampering, ...) in
  3966. case of malicious activity in the repository.
  3967. If you do **not** want to encrypt the contents of your backups, but still want to detect
  3968. malicious tampering use an `authenticated` mode. It's like `repokey` minus encryption.
  3969. Key derivation functions
  3970. ++++++++++++++++++++++++
  3971. - ``--key-algorithm argon2`` is the default and is recommended.
  3972. The key encryption key is derived from your passphrase via argon2-id.
  3973. Argon2 is considered more modern and secure than pbkdf2.
  3974. - You can use ``--key-algorithm pbkdf2`` if you want to access your repo via old versions of borg.
  3975. Our implementation of argon2-based key algorithm follows the cryptographic best practices:
  3976. - It derives two separate keys from your passphrase: one to encrypt your key and another one
  3977. to sign it. ``--key-algorithm pbkdf2`` uses the same key for both.
  3978. - It uses encrypt-then-mac instead of encrypt-and-mac used by ``--key-algorithm pbkdf2``
  3979. Neither is inherently linked to the key derivation function, but since we were going
  3980. to break backwards compatibility anyway we took the opportunity to fix all 3 issues at once.
  3981. """)
  3982. subparser = subparsers.add_parser('init', parents=[common_parser], add_help=False,
  3983. description=self.do_init.__doc__, epilog=init_epilog,
  3984. formatter_class=argparse.RawDescriptionHelpFormatter,
  3985. help='initialize empty repository')
  3986. subparser.set_defaults(func=self.do_init)
  3987. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  3988. type=location_validator(archive=False),
  3989. help='repository to create')
  3990. subparser.add_argument('--other-location', metavar='OTHER_REPOSITORY', dest='other_location',
  3991. type=location_validator(archive=False, other=True),
  3992. help='reuse the key material from the other repository')
  3993. subparser.add_argument('-e', '--encryption', metavar='MODE', dest='encryption', required=True,
  3994. choices=key_argument_names(),
  3995. help='select encryption key mode **(required)**')
  3996. subparser.add_argument('--append-only', dest='append_only', action='store_true',
  3997. help='create an append-only mode repository. Note that this only affects '
  3998. 'the low level structure of the repository, and running `delete` '
  3999. 'or `prune` will still be allowed. See :ref:`append_only_mode` in '
  4000. 'Additional Notes for more details.')
  4001. subparser.add_argument('--storage-quota', metavar='QUOTA', dest='storage_quota', default=None,
  4002. type=parse_storage_quota,
  4003. help='Set storage quota of the new repository (e.g. 5G, 1.5T). Default: no quota.')
  4004. subparser.add_argument('--make-parent-dirs', dest='make_parent_dirs', action='store_true',
  4005. help='create the parent directories of the repository directory, if they are missing.')
  4006. subparser.add_argument('--key-algorithm', dest='key_algorithm', default='argon2', choices=list(KEY_ALGORITHMS),
  4007. help='the algorithm we use to derive a key encryption key from your passphrase. Default: argon2')
  4008. # borg key
  4009. subparser = subparsers.add_parser('key', parents=[mid_common_parser], add_help=False,
  4010. description="Manage a keyfile or repokey of a repository",
  4011. epilog="",
  4012. formatter_class=argparse.RawDescriptionHelpFormatter,
  4013. help='manage repository key')
  4014. key_parsers = subparser.add_subparsers(title='required arguments', metavar='<command>')
  4015. subparser.set_defaults(fallback_func=functools.partial(self.do_subcommand_help, subparser))
  4016. key_export_epilog = process_epilog("""
  4017. If repository encryption is used, the repository is inaccessible
  4018. without the key. This command allows one to backup this essential key.
  4019. Note that the backup produced does not include the passphrase itself
  4020. (i.e. the exported key stays encrypted). In order to regain access to a
  4021. repository, one needs both the exported key and the original passphrase.
  4022. There are three backup formats. The normal backup format is suitable for
  4023. digital storage as a file. The ``--paper`` backup format is optimized
  4024. for printing and typing in while importing, with per line checks to
  4025. reduce problems with manual input. The ``--qr-html`` creates a printable
  4026. HTML template with a QR code and a copy of the ``--paper``-formatted key.
  4027. For repositories using keyfile encryption the key is saved locally
  4028. on the system that is capable of doing backups. To guard against loss
  4029. of this key, the key needs to be backed up independently of the main
  4030. data backup.
  4031. For repositories using the repokey encryption the key is saved in the
  4032. repository in the config file. A backup is thus not strictly needed,
  4033. but guards against the repository becoming inaccessible if the file
  4034. is damaged for some reason.
  4035. Examples::
  4036. borg key export /path/to/repo > encrypted-key-backup
  4037. borg key export --paper /path/to/repo > encrypted-key-backup.txt
  4038. borg key export --qr-html /path/to/repo > encrypted-key-backup.html
  4039. # Or pass the output file as an argument instead of redirecting stdout:
  4040. borg key export /path/to/repo encrypted-key-backup
  4041. borg key export --paper /path/to/repo encrypted-key-backup.txt
  4042. borg key export --qr-html /path/to/repo encrypted-key-backup.html
  4043. """)
  4044. subparser = key_parsers.add_parser('export', parents=[common_parser], add_help=False,
  4045. description=self.do_key_export.__doc__,
  4046. epilog=key_export_epilog,
  4047. formatter_class=argparse.RawDescriptionHelpFormatter,
  4048. help='export repository key for backup')
  4049. subparser.set_defaults(func=self.do_key_export)
  4050. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  4051. type=location_validator(archive=False))
  4052. subparser.add_argument('path', metavar='PATH', nargs='?', type=str,
  4053. help='where to store the backup')
  4054. subparser.add_argument('--paper', dest='paper', action='store_true',
  4055. help='Create an export suitable for printing and later type-in')
  4056. subparser.add_argument('--qr-html', dest='qr', action='store_true',
  4057. help='Create an html file suitable for printing and later type-in or qr scan')
  4058. key_import_epilog = process_epilog("""
  4059. This command restores a key previously backed up with the export command.
  4060. If the ``--paper`` option is given, the import will be an interactive
  4061. process in which each line is checked for plausibility before
  4062. proceeding to the next line. For this format PATH must not be given.
  4063. For repositories using keyfile encryption, the key file which ``borg key
  4064. import`` writes to depends on several factors. If the ``BORG_KEY_FILE``
  4065. environment variable is set and non-empty, ``borg key import`` creates
  4066. or overwrites that file named by ``$BORG_KEY_FILE``. Otherwise, ``borg
  4067. key import`` searches in the ``$BORG_KEYS_DIR`` directory for a key file
  4068. associated with the repository. If a key file is found in
  4069. ``$BORG_KEYS_DIR``, ``borg key import`` overwrites it; otherwise, ``borg
  4070. key import`` creates a new key file in ``$BORG_KEYS_DIR``.
  4071. """)
  4072. subparser = key_parsers.add_parser('import', parents=[common_parser], add_help=False,
  4073. description=self.do_key_import.__doc__,
  4074. epilog=key_import_epilog,
  4075. formatter_class=argparse.RawDescriptionHelpFormatter,
  4076. help='import repository key from backup')
  4077. subparser.set_defaults(func=self.do_key_import)
  4078. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  4079. type=location_validator(archive=False))
  4080. subparser.add_argument('path', metavar='PATH', nargs='?', type=str,
  4081. help='path to the backup (\'-\' to read from stdin)')
  4082. subparser.add_argument('--paper', dest='paper', action='store_true',
  4083. help='interactively import from a backup done with ``--paper``')
  4084. change_passphrase_epilog = process_epilog("""
  4085. The key files used for repository encryption are optionally passphrase
  4086. protected. This command can be used to change this passphrase.
  4087. Please note that this command only changes the passphrase, but not any
  4088. secret protected by it (like e.g. encryption/MAC keys or chunker seed).
  4089. Thus, changing the passphrase after passphrase and borg key got compromised
  4090. does not protect future (nor past) backups to the same repository.
  4091. """)
  4092. subparser = key_parsers.add_parser('change-passphrase', parents=[common_parser], add_help=False,
  4093. description=self.do_change_passphrase.__doc__,
  4094. epilog=change_passphrase_epilog,
  4095. formatter_class=argparse.RawDescriptionHelpFormatter,
  4096. help='change repository passphrase')
  4097. subparser.set_defaults(func=self.do_change_passphrase)
  4098. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  4099. type=location_validator(archive=False))
  4100. change_location_epilog = process_epilog("""
  4101. Change the location of a borg key. The key can be stored at different locations:
  4102. keyfile: locally, usually in the home directory
  4103. repokey: inside the repo (in the repo config)
  4104. Note: this command does NOT change the crypto algorithms, just the key location,
  4105. thus you must ONLY give the key location (keyfile or repokey).
  4106. """)
  4107. subparser = key_parsers.add_parser('change-location', parents=[common_parser], add_help=False,
  4108. description=self.do_change_location.__doc__,
  4109. epilog=change_location_epilog,
  4110. formatter_class=argparse.RawDescriptionHelpFormatter,
  4111. help='change key location')
  4112. subparser.set_defaults(func=self.do_change_location)
  4113. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  4114. type=location_validator(archive=False))
  4115. subparser.add_argument('key_mode', metavar='KEY_LOCATION', choices=('repokey', 'keyfile'),
  4116. help='select key location')
  4117. subparser.add_argument('--keep', dest='keep', action='store_true',
  4118. help='keep the key also at the current location (default: remove it)')
  4119. change_algorithm_epilog = process_epilog("""
  4120. Change the algorithm we use to encrypt and authenticate the borg key.
  4121. Important: In a `repokey` mode (e.g. repokey-blake2) all users share the same key.
  4122. In this mode upgrading to `argon2` will make it impossible to access the repo for users who use an old version of borg.
  4123. We recommend upgrading to the latest stable version.
  4124. Important: In a `keyfile` mode (e.g. keyfile-blake2) each user has their own key (in ``~/.config/borg/keys``).
  4125. In this mode this command will only change the key used by the current user.
  4126. If you want to upgrade to `argon2` to strengthen security, you will have to upgrade each user's key individually.
  4127. Your repository is encrypted and authenticated with a key that is randomly generated by ``borg init``.
  4128. The key is encrypted and authenticated with your passphrase.
  4129. We currently support two choices:
  4130. 1. argon2 - recommended. This algorithm is used by default when initialising a new repository.
  4131. The key encryption key is derived from your passphrase via argon2-id.
  4132. Argon2 is considered more modern and secure than pbkdf2.
  4133. 2. pbkdf2 - the legacy algorithm. Use this if you want to access your repo via old versions of borg.
  4134. The key encryption key is derived from your passphrase via PBKDF2-HMAC-SHA256.
  4135. Examples::
  4136. # Upgrade an existing key to argon2
  4137. borg key change-algorithm /path/to/repo argon2
  4138. # Downgrade to pbkdf2 - use this if upgrading borg is not an option
  4139. borg key change-algorithm /path/to/repo pbkdf2
  4140. """)
  4141. subparser = key_parsers.add_parser('change-algorithm', parents=[common_parser], add_help=False,
  4142. description=self.do_change_algorithm.__doc__,
  4143. epilog=change_algorithm_epilog,
  4144. formatter_class=argparse.RawDescriptionHelpFormatter,
  4145. help='change key algorithm')
  4146. subparser.set_defaults(func=self.do_change_algorithm)
  4147. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  4148. type=location_validator(archive=False))
  4149. subparser.add_argument('algorithm', metavar='ALGORITHM', choices=list(KEY_ALGORITHMS),
  4150. help='select key algorithm')
  4151. # borg list
  4152. list_epilog = process_epilog("""
  4153. This command lists the contents of a repository or an archive.
  4154. For more help on include/exclude patterns, see the :ref:`borg_patterns` command output.
  4155. .. man NOTES
  4156. The FORMAT specifier syntax
  4157. +++++++++++++++++++++++++++
  4158. The ``--format`` option uses python's `format string syntax
  4159. <https://docs.python.org/3.9/library/string.html#formatstrings>`_.
  4160. Examples:
  4161. ::
  4162. $ borg list --format '{archive}{NL}' /path/to/repo
  4163. ArchiveFoo
  4164. ArchiveBar
  4165. ...
  4166. # {VAR:NUMBER} - pad to NUMBER columns.
  4167. # Strings are left-aligned, numbers are right-aligned.
  4168. # Note: time columns except ``isomtime``, ``isoctime`` and ``isoatime`` cannot be padded.
  4169. $ borg list --format '{archive:36} {time} [{id}]{NL}' /path/to/repo
  4170. ArchiveFoo Thu, 2021-12-09 10:22:28 [0b8e9a312bef3f2f6e2d0fc110c196827786c15eba0188738e81697a7fa3b274]
  4171. $ borg list --format '{mode} {user:6} {group:6} {size:8} {mtime} {path}{extra}{NL}' /path/to/repo::ArchiveFoo
  4172. -rw-rw-r-- user user 1024 Thu, 2021-12-09 10:22:17 file-foo
  4173. ...
  4174. # {VAR:<NUMBER} - pad to NUMBER columns left-aligned.
  4175. # {VAR:>NUMBER} - pad to NUMBER columns right-aligned.
  4176. $ borg list --format '{mode} {user:>6} {group:>6} {size:<8} {mtime} {path}{extra}{NL}' /path/to/repo::ArchiveFoo
  4177. -rw-rw-r-- user user 1024 Thu, 2021-12-09 10:22:17 file-foo
  4178. ...
  4179. The following keys are always available:
  4180. """) + BaseFormatter.keys_help() + textwrap.dedent("""
  4181. Keys available only when listing archives in a repository:
  4182. """) + ArchiveFormatter.keys_help() + textwrap.dedent("""
  4183. Keys available only when listing files in an archive:
  4184. """) + ItemFormatter.keys_help()
  4185. subparser = subparsers.add_parser('list', parents=[common_parser], add_help=False,
  4186. description=self.do_list.__doc__,
  4187. epilog=list_epilog,
  4188. formatter_class=argparse.RawDescriptionHelpFormatter,
  4189. help='list archive or repository contents')
  4190. subparser.set_defaults(func=self.do_list)
  4191. subparser.add_argument('--consider-checkpoints', action='store_true', dest='consider_checkpoints',
  4192. help='Show checkpoint archives in the repository contents list (default: hidden).')
  4193. subparser.add_argument('--short', dest='short', action='store_true',
  4194. help='only print file/directory names, nothing else')
  4195. subparser.add_argument('--format', metavar='FORMAT', dest='format',
  4196. help='specify format for file or archive listing '
  4197. '(default for files: "{mode} {user:6} {group:6} {size:8} {mtime} {path}{extra}{NL}"; '
  4198. 'for archives: "{archive:<36} {time} [{id}]{NL}")')
  4199. subparser.add_argument('--json', action='store_true',
  4200. help='Only valid for listing repository contents. Format output as JSON. '
  4201. 'The form of ``--format`` is ignored, '
  4202. 'but keys used in it are added to the JSON output. '
  4203. 'Some keys are always present. Note: JSON can only represent text. '
  4204. 'A "barchive" key is therefore not available.')
  4205. subparser.add_argument('--json-lines', action='store_true',
  4206. help='Only valid for listing archive contents. Format output as JSON Lines. '
  4207. 'The form of ``--format`` is ignored, '
  4208. 'but keys used in it are added to the JSON output. '
  4209. 'Some keys are always present. Note: JSON can only represent text. '
  4210. 'A "bpath" key is therefore not available.')
  4211. subparser.add_argument('location', metavar='REPOSITORY_OR_ARCHIVE', nargs='?', default='',
  4212. type=location_validator(),
  4213. help='repository or archive to list contents of')
  4214. subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
  4215. help='paths to list; patterns are supported')
  4216. define_archive_filters_group(subparser)
  4217. define_exclusion_group(subparser)
  4218. subparser = subparsers.add_parser('mount', parents=[common_parser], add_help=False,
  4219. description=self.do_mount.__doc__,
  4220. epilog=mount_epilog,
  4221. formatter_class=argparse.RawDescriptionHelpFormatter,
  4222. help='mount repository')
  4223. define_borg_mount(subparser)
  4224. # borg prune
  4225. prune_epilog = process_epilog("""
  4226. The prune command prunes a repository by deleting all archives not matching
  4227. any of the specified retention options.
  4228. Important: Repository disk space is **not** freed until you run ``borg compact``.
  4229. This command is normally used by automated backup scripts wanting to keep a
  4230. certain number of historic backups. This retention policy is commonly referred to as
  4231. `GFS <https://en.wikipedia.org/wiki/Backup_rotation_scheme#Grandfather-father-son>`_
  4232. (Grandfather-father-son) backup rotation scheme.
  4233. Also, prune automatically removes checkpoint archives (incomplete archives left
  4234. behind by interrupted backup runs) except if the checkpoint is the latest
  4235. archive (and thus still needed). Checkpoint archives are not considered when
  4236. comparing archive counts against the retention limits (``--keep-X``).
  4237. If a prefix is set with -P, then only archives that start with the prefix are
  4238. considered for deletion and only those archives count towards the totals
  4239. specified by the rules.
  4240. Otherwise, *all* archives in the repository are candidates for deletion!
  4241. There is no automatic distinction between archives representing different
  4242. contents. These need to be distinguished by specifying matching prefixes.
  4243. If you have multiple sequences of archives with different data sets (e.g.
  4244. from different machines) in one shared repository, use one prune call per
  4245. data set that matches only the respective archives using the -P option.
  4246. The ``--keep-within`` option takes an argument of the form "<int><char>",
  4247. where char is "H", "d", "w", "m", "y". For example, ``--keep-within 2d`` means
  4248. to keep all archives that were created within the past 48 hours.
  4249. "1m" is taken to mean "31d". The archives kept with this option do not
  4250. count towards the totals specified by any other options.
  4251. A good procedure is to thin out more and more the older your backups get.
  4252. As an example, ``--keep-daily 7`` means to keep the latest backup on each day,
  4253. up to 7 most recent days with backups (days without backups do not count).
  4254. The rules are applied from secondly to yearly, and backups selected by previous
  4255. rules do not count towards those of later rules. The time that each backup
  4256. starts is used for pruning purposes. Dates and times are interpreted in
  4257. the local timezone, and weeks go from Monday to Sunday. Specifying a
  4258. negative number of archives to keep means that there is no limit. As of borg
  4259. 1.2.0, borg will retain the oldest archive if any of the secondly, minutely,
  4260. hourly, daily, weekly, monthly, or yearly rules was not otherwise able to meet
  4261. its retention target. This enables the first chronological archive to continue
  4262. aging until it is replaced by a newer archive that meets the retention criteria.
  4263. The ``--keep-last N`` option is doing the same as ``--keep-secondly N`` (and it will
  4264. keep the last N archives under the assumption that you do not create more than one
  4265. backup archive in the same second).
  4266. When using ``--stats``, you will get some statistics about how much data was
  4267. deleted - the "Deleted data" deduplicated size there is most interesting as
  4268. that is how much your repository will shrink.
  4269. Please note that the "All archives" stats refer to the state after pruning.
  4270. """)
  4271. subparser = subparsers.add_parser('prune', parents=[common_parser], add_help=False,
  4272. description=self.do_prune.__doc__,
  4273. epilog=prune_epilog,
  4274. formatter_class=argparse.RawDescriptionHelpFormatter,
  4275. help='prune archives')
  4276. subparser.set_defaults(func=self.do_prune)
  4277. subparser.add_argument('-n', '--dry-run', dest='dry_run', action='store_true',
  4278. help='do not change repository')
  4279. subparser.add_argument('--force', dest='forced', action='store_true',
  4280. help='force pruning of corrupted archives, '
  4281. 'use ``--force --force`` in case ``--force`` does not work.')
  4282. subparser.add_argument('-s', '--stats', dest='stats', action='store_true',
  4283. help='print statistics for the deleted archive')
  4284. subparser.add_argument('--list', dest='output_list', action='store_true',
  4285. help='output verbose list of archives it keeps/prunes')
  4286. subparser.add_argument('--keep-within', metavar='INTERVAL', dest='within', type=interval,
  4287. help='keep all archives within this time interval')
  4288. subparser.add_argument('--keep-last', '--keep-secondly', dest='secondly', type=int, default=0,
  4289. help='number of secondly archives to keep')
  4290. subparser.add_argument('--keep-minutely', dest='minutely', type=int, default=0,
  4291. help='number of minutely archives to keep')
  4292. subparser.add_argument('-H', '--keep-hourly', dest='hourly', type=int, default=0,
  4293. help='number of hourly archives to keep')
  4294. subparser.add_argument('-d', '--keep-daily', dest='daily', type=int, default=0,
  4295. help='number of daily archives to keep')
  4296. subparser.add_argument('-w', '--keep-weekly', dest='weekly', type=int, default=0,
  4297. help='number of weekly archives to keep')
  4298. subparser.add_argument('-m', '--keep-monthly', dest='monthly', type=int, default=0,
  4299. help='number of monthly archives to keep')
  4300. subparser.add_argument('-y', '--keep-yearly', dest='yearly', type=int, default=0,
  4301. help='number of yearly archives to keep')
  4302. define_archive_filters_group(subparser, sort_by=False, first_last=False)
  4303. subparser.add_argument('--save-space', dest='save_space', action='store_true',
  4304. help='work slower, but using less space')
  4305. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  4306. type=location_validator(archive=False),
  4307. help='repository to prune')
  4308. # borg recreate
  4309. recreate_epilog = process_epilog("""
  4310. Recreate the contents of existing archives.
  4311. recreate is a potentially dangerous function and might lead to data loss
  4312. (if used wrongly). BE VERY CAREFUL!
  4313. Important: Repository disk space is **not** freed until you run ``borg compact``.
  4314. ``--exclude``, ``--exclude-from``, ``--exclude-if-present``, ``--keep-exclude-tags``
  4315. and PATH have the exact same semantics as in "borg create", but they only check
  4316. for files in the archives and not in the local file system. If PATHs are specified,
  4317. the resulting archives will only contain files from these PATHs.
  4318. Note that all paths in an archive are relative, therefore absolute patterns/paths
  4319. will *not* match (``--exclude``, ``--exclude-from``, PATHs).
  4320. ``--recompress`` allows one to change the compression of existing data in archives.
  4321. Due to how Borg stores compressed size information this might display
  4322. incorrect information for archives that were not recreated at the same time.
  4323. There is no risk of data loss by this.
  4324. ``--chunker-params`` will re-chunk all files in the archive, this can be
  4325. used to have upgraded Borg 0.xx or Attic archives deduplicate with
  4326. Borg 1.x archives.
  4327. **USE WITH CAUTION.**
  4328. Depending on the PATHs and patterns given, recreate can be used to permanently
  4329. delete files from archives.
  4330. When in doubt, use ``--dry-run --verbose --list`` to see how patterns/PATHS are
  4331. interpreted. See :ref:`list_item_flags` in ``borg create`` for details.
  4332. The archive being recreated is only removed after the operation completes. The
  4333. archive that is built during the operation exists at the same time at
  4334. "<ARCHIVE>.recreate". The new archive will have a different archive ID.
  4335. With ``--target`` the original archive is not replaced, instead a new archive is created.
  4336. When rechunking (or recompressing), space usage can be substantial - expect
  4337. at least the entire deduplicated size of the archives using the previous
  4338. chunker (or compression) params.
  4339. If you recently ran borg check --repair and it had to fix lost chunks with all-zero
  4340. replacement chunks, please first run another backup for the same data and re-run
  4341. borg check --repair afterwards to heal any archives that had lost chunks which are
  4342. still generated from the input data.
  4343. Important: running borg recreate to re-chunk will remove the chunks_healthy
  4344. metadata of all items with replacement chunks, so healing will not be possible
  4345. any more after re-chunking (it is also unlikely it would ever work: due to the
  4346. change of chunking parameters, the missing chunk likely will never be seen again
  4347. even if you still have the data that produced it).
  4348. """)
  4349. subparser = subparsers.add_parser('recreate', parents=[common_parser], add_help=False,
  4350. description=self.do_recreate.__doc__,
  4351. epilog=recreate_epilog,
  4352. formatter_class=argparse.RawDescriptionHelpFormatter,
  4353. help=self.do_recreate.__doc__)
  4354. subparser.set_defaults(func=self.do_recreate)
  4355. subparser.add_argument('--list', dest='output_list', action='store_true',
  4356. help='output verbose list of items (files, dirs, ...)')
  4357. subparser.add_argument('--filter', metavar='STATUSCHARS', dest='output_filter', action=Highlander,
  4358. help='only display items with the given status characters (listed in borg create --help)')
  4359. subparser.add_argument('-n', '--dry-run', dest='dry_run', action='store_true',
  4360. help='do not change anything')
  4361. subparser.add_argument('-s', '--stats', dest='stats', action='store_true',
  4362. help='print statistics at end')
  4363. define_exclusion_group(subparser, tag_files=True)
  4364. archive_group = subparser.add_argument_group('Archive options')
  4365. archive_group.add_argument('--target', dest='target', metavar='TARGET', default=None,
  4366. type=archivename_validator(),
  4367. help='create a new archive with the name ARCHIVE, do not replace existing archive '
  4368. '(only applies for a single archive)')
  4369. archive_group.add_argument('-c', '--checkpoint-interval', dest='checkpoint_interval',
  4370. type=int, default=1800, metavar='SECONDS',
  4371. help='write checkpoint every SECONDS seconds (Default: 1800)')
  4372. archive_group.add_argument('--comment', dest='comment', metavar='COMMENT', type=CommentSpec, default=None,
  4373. help='add a comment text to the archive')
  4374. archive_group.add_argument('--timestamp', metavar='TIMESTAMP', dest='timestamp',
  4375. type=timestamp, default=None,
  4376. help='manually specify the archive creation date/time (UTC, yyyy-mm-ddThh:mm:ss format). '
  4377. 'alternatively, give a reference file/directory.')
  4378. archive_group.add_argument('-C', '--compression', metavar='COMPRESSION', dest='compression',
  4379. type=CompressionSpec, default=CompressionSpec('lz4'),
  4380. help='select compression algorithm, see the output of the '
  4381. '"borg help compression" command for details.')
  4382. archive_group.add_argument('--recompress', metavar='MODE', dest='recompress', nargs='?',
  4383. default='never', const='if-different', choices=('never', 'if-different', 'always'),
  4384. help='recompress data chunks according to `MODE` and ``--compression``. '
  4385. 'Possible modes are '
  4386. '`if-different`: recompress if current compression is with a different '
  4387. 'compression algorithm (the level is not considered); '
  4388. '`always`: recompress even if current compression is with the same '
  4389. 'compression algorithm (use this to change the compression level); and '
  4390. '`never`: do not recompress (use this option to explicitly prevent '
  4391. 'recompression). '
  4392. 'If no MODE is given, `if-different` will be used. '
  4393. 'Not passing --recompress is equivalent to "--recompress never".')
  4394. archive_group.add_argument('--chunker-params', metavar='PARAMS', dest='chunker_params', action=Highlander,
  4395. type=ChunkerParams, default=CHUNKER_PARAMS,
  4396. help='specify the chunker parameters (ALGO, CHUNK_MIN_EXP, CHUNK_MAX_EXP, '
  4397. 'HASH_MASK_BITS, HASH_WINDOW_SIZE) or `default` to use the current defaults. '
  4398. 'default: %s,%d,%d,%d,%d' % CHUNKER_PARAMS)
  4399. subparser.add_argument('location', metavar='REPOSITORY_OR_ARCHIVE', nargs='?', default='',
  4400. type=location_validator(),
  4401. help='repository or archive to recreate')
  4402. subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
  4403. help='paths to recreate; patterns are supported')
  4404. # borg rename
  4405. rename_epilog = process_epilog("""
  4406. This command renames an archive in the repository.
  4407. This results in a different archive ID.
  4408. """)
  4409. subparser = subparsers.add_parser('rename', parents=[common_parser], add_help=False,
  4410. description=self.do_rename.__doc__,
  4411. epilog=rename_epilog,
  4412. formatter_class=argparse.RawDescriptionHelpFormatter,
  4413. help='rename archive')
  4414. subparser.set_defaults(func=self.do_rename)
  4415. subparser.add_argument('location', metavar='ARCHIVE',
  4416. type=location_validator(archive=True),
  4417. help='archive to rename')
  4418. subparser.add_argument('name', metavar='NEWNAME',
  4419. type=archivename_validator(),
  4420. help='the new archive name to use')
  4421. # borg serve
  4422. serve_epilog = process_epilog("""
  4423. This command starts a repository server process. This command is usually not used manually.
  4424. """)
  4425. subparser = subparsers.add_parser('serve', parents=[common_parser], add_help=False,
  4426. description=self.do_serve.__doc__, epilog=serve_epilog,
  4427. formatter_class=argparse.RawDescriptionHelpFormatter,
  4428. help='start repository server process')
  4429. subparser.set_defaults(func=self.do_serve)
  4430. subparser.add_argument('--restrict-to-path', metavar='PATH', dest='restrict_to_paths', action='append',
  4431. help='restrict repository access to PATH. '
  4432. 'Can be specified multiple times to allow the client access to several directories. '
  4433. 'Access to all sub-directories is granted implicitly; PATH doesn\'t need to directly point to a repository.')
  4434. subparser.add_argument('--restrict-to-repository', metavar='PATH', dest='restrict_to_repositories', action='append',
  4435. help='restrict repository access. Only the repository located at PATH '
  4436. '(no sub-directories are considered) is accessible. '
  4437. 'Can be specified multiple times to allow the client access to several repositories. '
  4438. 'Unlike ``--restrict-to-path`` sub-directories are not accessible; '
  4439. 'PATH needs to directly point at a repository location. '
  4440. 'PATH may be an empty directory or the last element of PATH may not exist, in which case '
  4441. 'the client may initialize a repository there.')
  4442. subparser.add_argument('--append-only', dest='append_only', action='store_true',
  4443. help='only allow appending to repository segment files. Note that this only '
  4444. 'affects the low level structure of the repository, and running `delete` '
  4445. 'or `prune` will still be allowed. See :ref:`append_only_mode` in Additional '
  4446. 'Notes for more details.')
  4447. subparser.add_argument('--storage-quota', metavar='QUOTA', dest='storage_quota',
  4448. type=parse_storage_quota, default=None,
  4449. help='Override storage quota of the repository (e.g. 5G, 1.5T). '
  4450. 'When a new repository is initialized, sets the storage quota on the new '
  4451. 'repository as well. Default: no quota.')
  4452. # borg umount
  4453. umount_epilog = process_epilog("""
  4454. This command un-mounts a FUSE filesystem that was mounted with ``borg mount``.
  4455. This is a convenience wrapper that just calls the platform-specific shell
  4456. command - usually this is either umount or fusermount -u.
  4457. """)
  4458. subparser = subparsers.add_parser('umount', parents=[common_parser], add_help=False,
  4459. description=self.do_umount.__doc__,
  4460. epilog=umount_epilog,
  4461. formatter_class=argparse.RawDescriptionHelpFormatter,
  4462. help='umount repository')
  4463. subparser.set_defaults(func=self.do_umount)
  4464. subparser.add_argument('mountpoint', metavar='MOUNTPOINT', type=str,
  4465. help='mountpoint of the filesystem to umount')
  4466. # borg upgrade
  4467. upgrade_epilog = process_epilog("""
  4468. Upgrade an existing, local Borg repository.
  4469. When you do not need borg upgrade
  4470. +++++++++++++++++++++++++++++++++
  4471. Not every change requires that you run ``borg upgrade``.
  4472. You do **not** need to run it when:
  4473. - moving your repository to a different place
  4474. - upgrading to another point release (like 1.0.x to 1.0.y),
  4475. except when noted otherwise in the changelog
  4476. - upgrading from 1.0.x to 1.1.x,
  4477. except when noted otherwise in the changelog
  4478. Borg 1.x.y upgrades
  4479. +++++++++++++++++++
  4480. Use ``borg upgrade --tam REPO`` to require manifest authentication
  4481. introduced with Borg 1.0.9 to address security issues. This means
  4482. that modifying the repository after doing this with a version prior
  4483. to 1.0.9 will raise a validation error, so only perform this upgrade
  4484. after updating all clients using the repository to 1.0.9 or newer.
  4485. This upgrade should be done on each client for safety reasons.
  4486. If a repository is accidentally modified with a pre-1.0.9 client after
  4487. this upgrade, use ``borg upgrade --tam --force REPO`` to remedy it.
  4488. If you routinely do this you might not want to enable this upgrade
  4489. (which will leave you exposed to the security issue). You can
  4490. reverse the upgrade by issuing ``borg upgrade --disable-tam REPO``.
  4491. See
  4492. https://borgbackup.readthedocs.io/en/stable/changes.html#pre-1-0-9-manifest-spoofing-vulnerability
  4493. for details.
  4494. Borg 0.xx to Borg 1.x
  4495. +++++++++++++++++++++
  4496. This currently supports converting Borg 0.xx to 1.0.
  4497. Currently, only LOCAL repositories can be upgraded (issue #465).
  4498. Please note that ``borg create`` (since 1.0.0) uses bigger chunks by
  4499. default than old borg did, so the new chunks won't deduplicate
  4500. with the old chunks in the upgraded repository.
  4501. See ``--chunker-params`` option of ``borg create`` and ``borg recreate``.""")
  4502. subparser = subparsers.add_parser('upgrade', parents=[common_parser], add_help=False,
  4503. description=self.do_upgrade.__doc__,
  4504. epilog=upgrade_epilog,
  4505. formatter_class=argparse.RawDescriptionHelpFormatter,
  4506. help='upgrade repository format')
  4507. subparser.set_defaults(func=self.do_upgrade)
  4508. subparser.add_argument('-n', '--dry-run', dest='dry_run', action='store_true',
  4509. help='do not change repository')
  4510. subparser.add_argument('--inplace', dest='inplace', action='store_true',
  4511. help='rewrite repository in place, with no chance of going back '
  4512. 'to older versions of the repository.')
  4513. subparser.add_argument('--force', dest='force', action='store_true',
  4514. help='Force upgrade')
  4515. subparser.add_argument('--tam', dest='tam', action='store_true',
  4516. help='Enable manifest authentication (in key and cache) (Borg 1.0.9 and later).')
  4517. subparser.add_argument('--disable-tam', dest='disable_tam', action='store_true',
  4518. help='Disable manifest authentication (in key and cache).')
  4519. subparser.add_argument('location', metavar='REPOSITORY', nargs='?', default='',
  4520. type=location_validator(archive=False),
  4521. help='path to the repository to be upgraded')
  4522. # borg with-lock
  4523. with_lock_epilog = process_epilog("""
  4524. This command runs a user-specified command while the repository lock is held.
  4525. It will first try to acquire the lock (make sure that no other operation is
  4526. running in the repo), then execute the given command as a subprocess and wait
  4527. for its termination, release the lock and return the user command's return
  4528. code as borg's return code.
  4529. .. note::
  4530. If you copy a repository with the lock held, the lock will be present in
  4531. the copy. Thus, before using borg on the copy from a different host,
  4532. you need to use "borg break-lock" on the copied repository, because
  4533. Borg is cautious and does not automatically remove stale locks made by a different host.
  4534. """)
  4535. subparser = subparsers.add_parser('with-lock', parents=[common_parser], add_help=False,
  4536. description=self.do_with_lock.__doc__,
  4537. epilog=with_lock_epilog,
  4538. formatter_class=argparse.RawDescriptionHelpFormatter,
  4539. help='run user command with lock held')
  4540. subparser.set_defaults(func=self.do_with_lock)
  4541. subparser.add_argument('location', metavar='REPOSITORY',
  4542. type=location_validator(archive=False),
  4543. help='repository to lock')
  4544. subparser.add_argument('command', metavar='COMMAND',
  4545. help='command to run')
  4546. subparser.add_argument('args', metavar='ARGS', nargs=argparse.REMAINDER,
  4547. help='command arguments')
  4548. # borg import-tar
  4549. import_tar_epilog = process_epilog("""
  4550. This command creates a backup archive from a tarball.
  4551. When giving '-' as path, Borg will read a tar stream from standard input.
  4552. By default (--tar-filter=auto) Borg will detect whether the file is compressed
  4553. based on its file extension and pipe the file through an appropriate filter:
  4554. - .tar.gz or .tgz: gzip -d
  4555. - .tar.bz2 or .tbz: bzip2 -d
  4556. - .tar.xz or .txz: xz -d
  4557. - .tar.zstd: zstd -d
  4558. - .tar.lz4: lz4 -d
  4559. Alternatively, a --tar-filter program may be explicitly specified. It should
  4560. read compressed data from stdin and output an uncompressed tar stream on
  4561. stdout.
  4562. Most documentation of borg create applies. Note that this command does not
  4563. support excluding files.
  4564. A ``--sparse`` option (as found in borg create) is not supported.
  4565. About tar formats and metadata conservation or loss, please see ``borg export-tar``.
  4566. import-tar reads these tar formats:
  4567. - BORG: borg specific (PAX-based)
  4568. - PAX: POSIX.1-2001
  4569. - GNU: GNU tar
  4570. - POSIX.1-1988 (ustar)
  4571. - UNIX V7 tar
  4572. - SunOS tar with extended attributes
  4573. """)
  4574. subparser = subparsers.add_parser('import-tar', parents=[common_parser], add_help=False,
  4575. description=self.do_import_tar.__doc__,
  4576. epilog=import_tar_epilog,
  4577. formatter_class=argparse.RawDescriptionHelpFormatter,
  4578. help=self.do_import_tar.__doc__)
  4579. subparser.set_defaults(func=self.do_import_tar)
  4580. subparser.add_argument('--tar-filter', dest='tar_filter', default='auto', action=Highlander,
  4581. help='filter program to pipe data through')
  4582. subparser.add_argument('-s', '--stats', dest='stats',
  4583. action='store_true', default=False,
  4584. help='print statistics for the created archive')
  4585. subparser.add_argument('--list', dest='output_list',
  4586. action='store_true', default=False,
  4587. help='output verbose list of items (files, dirs, ...)')
  4588. subparser.add_argument('--filter', dest='output_filter', metavar='STATUSCHARS', action=Highlander,
  4589. help='only display items with the given status characters')
  4590. subparser.add_argument('--json', action='store_true',
  4591. help='output stats as JSON (implies --stats)')
  4592. archive_group = subparser.add_argument_group('Archive options')
  4593. archive_group.add_argument('--comment', dest='comment', metavar='COMMENT', default='',
  4594. help='add a comment text to the archive')
  4595. archive_group.add_argument('--timestamp', dest='timestamp',
  4596. type=timestamp, default=None,
  4597. metavar='TIMESTAMP',
  4598. help='manually specify the archive creation date/time (UTC, yyyy-mm-ddThh:mm:ss format). '
  4599. 'alternatively, give a reference file/directory.')
  4600. archive_group.add_argument('-c', '--checkpoint-interval', dest='checkpoint_interval',
  4601. type=int, default=1800, metavar='SECONDS',
  4602. help='write checkpoint every SECONDS seconds (Default: 1800)')
  4603. archive_group.add_argument('--chunker-params', dest='chunker_params', action=Highlander,
  4604. type=ChunkerParams, default=CHUNKER_PARAMS,
  4605. metavar='PARAMS',
  4606. help='specify the chunker parameters (ALGO, CHUNK_MIN_EXP, CHUNK_MAX_EXP, '
  4607. 'HASH_MASK_BITS, HASH_WINDOW_SIZE). default: %s,%d,%d,%d,%d' % CHUNKER_PARAMS)
  4608. archive_group.add_argument('-C', '--compression', metavar='COMPRESSION', dest='compression',
  4609. type=CompressionSpec, default=CompressionSpec('lz4'),
  4610. help='select compression algorithm, see the output of the '
  4611. '"borg help compression" command for details.')
  4612. subparser.add_argument('location', metavar='ARCHIVE',
  4613. type=location_validator(archive=True),
  4614. help='name of archive to create (must be also a valid directory name)')
  4615. subparser.add_argument('tarfile', metavar='TARFILE',
  4616. help='input tar file. "-" to read from stdin instead.')
  4617. return parser
  4618. def get_args(self, argv, cmd):
  4619. """usually, just returns argv, except if we deal with a ssh forced command for borg serve."""
  4620. result = self.parse_args(argv[1:])
  4621. if cmd is not None and result.func == self.do_serve:
  4622. # borg serve case:
  4623. # - "result" is how borg got invoked (e.g. via forced command from authorized_keys),
  4624. # - "client_result" (from "cmd") refers to the command the client wanted to execute,
  4625. # which might be different in the case of a forced command or same otherwise.
  4626. client_argv = shlex.split(cmd)
  4627. # Drop environment variables (do *not* interpret them) before trying to parse
  4628. # the borg command line.
  4629. client_argv = list(itertools.dropwhile(lambda arg: '=' in arg, client_argv))
  4630. client_result = self.parse_args(client_argv[1:])
  4631. if client_result.func == result.func:
  4632. # make sure we only process like normal if the client is executing
  4633. # the same command as specified in the forced command, otherwise
  4634. # just skip this block and return the forced command (== result).
  4635. # client is allowed to specify the allowlisted options,
  4636. # everything else comes from the forced "borg serve" command (or the defaults).
  4637. # stuff from denylist must never be used from the client.
  4638. denylist = {
  4639. 'restrict_to_paths',
  4640. 'restrict_to_repositories',
  4641. 'append_only',
  4642. 'storage_quota',
  4643. 'umask',
  4644. }
  4645. allowlist = {
  4646. 'debug_topics',
  4647. 'lock_wait',
  4648. 'log_level',
  4649. }
  4650. not_present = object()
  4651. for attr_name in allowlist:
  4652. assert attr_name not in denylist, 'allowlist has denylisted attribute name %s' % attr_name
  4653. value = getattr(client_result, attr_name, not_present)
  4654. if value is not not_present:
  4655. # note: it is not possible to specify a allowlisted option via a forced command,
  4656. # it always gets overridden by the value specified (or defaulted to) by the client command.
  4657. setattr(result, attr_name, value)
  4658. return result
  4659. def parse_args(self, args=None):
  4660. # We can't use argparse for "serve" since we don't want it to show up in "Available commands"
  4661. if args:
  4662. args = self.preprocess_args(args)
  4663. parser = self.build_parser()
  4664. args = parser.parse_args(args or ['-h'])
  4665. parser.common_options.resolve(args)
  4666. func = get_func(args)
  4667. if func == self.do_create and args.paths and args.paths_from_stdin:
  4668. parser.error('Must not pass PATH with ``--paths-from-stdin``.')
  4669. if func == self.do_create and not args.paths:
  4670. if args.content_from_command or args.paths_from_command:
  4671. parser.error('No command given.')
  4672. elif not args.paths_from_stdin:
  4673. # need at least 1 path but args.paths may also be populated from patterns
  4674. parser.error('Need at least one PATH argument.')
  4675. if not getattr(args, 'lock', True): # Option --bypass-lock sets args.lock = False
  4676. bypass_allowed = {self.do_check, self.do_config, self.do_diff,
  4677. self.do_export_tar, self.do_extract, self.do_info,
  4678. self.do_list, self.do_mount, self.do_umount}
  4679. if func not in bypass_allowed:
  4680. raise Error('Not allowed to bypass locking mechanism for chosen command')
  4681. if getattr(args, 'timestamp', None):
  4682. args.location = args.location.with_timestamp(args.timestamp)
  4683. return args
  4684. def prerun_checks(self, logger, is_serve):
  4685. if not is_serve:
  4686. # this is the borg *client*, we need to check the python:
  4687. check_python()
  4688. check_extension_modules()
  4689. selftest(logger)
  4690. def _setup_implied_logging(self, args):
  4691. """ turn on INFO level logging for args that imply that they will produce output """
  4692. # map of option name to name of logger for that option
  4693. option_logger = {
  4694. 'output_list': 'borg.output.list',
  4695. 'show_version': 'borg.output.show-version',
  4696. 'show_rc': 'borg.output.show-rc',
  4697. 'stats': 'borg.output.stats',
  4698. 'progress': 'borg.output.progress',
  4699. }
  4700. for option, logger_name in option_logger.items():
  4701. option_set = args.get(option, False)
  4702. logging.getLogger(logger_name).setLevel('INFO' if option_set else 'WARN')
  4703. def _setup_topic_debugging(self, args):
  4704. """Turn on DEBUG level logging for specified --debug-topics."""
  4705. for topic in args.debug_topics:
  4706. if '.' not in topic:
  4707. topic = 'borg.debug.' + topic
  4708. logger.debug('Enabling debug topic %s', topic)
  4709. logging.getLogger(topic).setLevel('DEBUG')
  4710. def run(self, args):
  4711. os.umask(args.umask) # early, before opening files
  4712. self.lock_wait = args.lock_wait
  4713. func = get_func(args)
  4714. # do not use loggers before this!
  4715. is_serve = func == self.do_serve
  4716. setup_logging(level=args.log_level, is_serve=is_serve, json=args.log_json)
  4717. self.log_json = args.log_json
  4718. args.progress |= is_serve
  4719. self._setup_implied_logging(vars(args))
  4720. self._setup_topic_debugging(args)
  4721. if getattr(args, 'stats', False) and getattr(args, 'dry_run', False):
  4722. # the data needed for --stats is not computed when using --dry-run, so we can't do it.
  4723. # for ease of scripting, we just ignore --stats when given with --dry-run.
  4724. logger.warning("Ignoring --stats. It is not supported when using --dry-run.")
  4725. args.stats = False
  4726. if args.show_version:
  4727. logging.getLogger('borg.output.show-version').info('borgbackup version %s' % __version__)
  4728. self.prerun_checks(logger, is_serve)
  4729. if not is_supported_msgpack():
  4730. logger.error("You do not have a supported version of the msgpack python package installed. Terminating.")
  4731. logger.error("This should never happen as specific, supported versions are required by our setup.py.")
  4732. logger.error("Do not contact borgbackup support about this.")
  4733. return set_ec(EXIT_ERROR)
  4734. if is_slow_msgpack():
  4735. logger.warning(PURE_PYTHON_MSGPACK_WARNING)
  4736. if args.debug_profile:
  4737. # Import only when needed - avoids a further increase in startup time
  4738. import cProfile
  4739. import marshal
  4740. logger.debug('Writing execution profile to %s', args.debug_profile)
  4741. # Open the file early, before running the main program, to avoid
  4742. # a very late crash in case the specified path is invalid.
  4743. with open(args.debug_profile, 'wb') as fd:
  4744. profiler = cProfile.Profile()
  4745. variables = dict(locals())
  4746. profiler.enable()
  4747. try:
  4748. return set_ec(func(args))
  4749. finally:
  4750. profiler.disable()
  4751. profiler.snapshot_stats()
  4752. if args.debug_profile.endswith('.pyprof'):
  4753. marshal.dump(profiler.stats, fd)
  4754. else:
  4755. # We use msgpack here instead of the marshal module used by cProfile itself,
  4756. # because the latter is insecure. Since these files may be shared over the
  4757. # internet we don't want a format that is impossible to interpret outside
  4758. # an insecure implementation.
  4759. # See scripts/msgpack2marshal.py for a small script that turns a msgpack file
  4760. # into a marshal file that can be read by e.g. pyprof2calltree.
  4761. # For local use it's unnecessary hassle, though, that's why .pyprof makes
  4762. # it compatible (see above).
  4763. msgpack.pack(profiler.stats, fd, use_bin_type=True)
  4764. else:
  4765. return set_ec(func(args))
  4766. def sig_info_handler(sig_no, stack): # pragma: no cover
  4767. """search the stack for infos about the currently processed file and print them"""
  4768. with signal_handler(sig_no, signal.SIG_IGN):
  4769. for frame in inspect.getouterframes(stack):
  4770. func, loc = frame[3], frame[0].f_locals
  4771. if func in ('process_file', '_rec_walk', ): # create op
  4772. path = loc['path']
  4773. try:
  4774. pos = loc['fd'].tell()
  4775. total = loc['st'].st_size
  4776. except Exception:
  4777. pos, total = 0, 0
  4778. logger.info(f"{path} {format_file_size(pos)}/{format_file_size(total)}")
  4779. break
  4780. if func in ('extract_item', ): # extract op
  4781. path = loc['item'].path
  4782. try:
  4783. pos = loc['fd'].tell()
  4784. except Exception:
  4785. pos = 0
  4786. logger.info(f"{path} {format_file_size(pos)}/???")
  4787. break
  4788. def sig_trace_handler(sig_no, stack): # pragma: no cover
  4789. print('\nReceived SIGUSR2 at %s, dumping trace...' % datetime.now().replace(microsecond=0), file=sys.stderr)
  4790. faulthandler.dump_traceback()
  4791. def main(): # pragma: no cover
  4792. # Make sure stdout and stderr have errors='replace' to avoid unicode
  4793. # issues when print()-ing unicode file names
  4794. sys.stdout = ErrorIgnoringTextIOWrapper(sys.stdout.buffer, sys.stdout.encoding, 'replace', line_buffering=True)
  4795. sys.stderr = ErrorIgnoringTextIOWrapper(sys.stderr.buffer, sys.stderr.encoding, 'replace', line_buffering=True)
  4796. # If we receive SIGINT (ctrl-c), SIGTERM (kill) or SIGHUP (kill -HUP),
  4797. # catch them and raise a proper exception that can be handled for an
  4798. # orderly exit.
  4799. # SIGHUP is important especially for systemd systems, where logind
  4800. # sends it when a session exits, in addition to any traditional use.
  4801. # Output some info if we receive SIGUSR1 or SIGINFO (ctrl-t).
  4802. # Register fault handler for SIGSEGV, SIGFPE, SIGABRT, SIGBUS and SIGILL.
  4803. faulthandler.enable()
  4804. with signal_handler('SIGINT', raising_signal_handler(KeyboardInterrupt)), \
  4805. signal_handler('SIGHUP', raising_signal_handler(SigHup)), \
  4806. signal_handler('SIGTERM', raising_signal_handler(SigTerm)), \
  4807. signal_handler('SIGUSR1', sig_info_handler), \
  4808. signal_handler('SIGUSR2', sig_trace_handler), \
  4809. signal_handler('SIGINFO', sig_info_handler):
  4810. archiver = Archiver()
  4811. msg = msgid = tb = None
  4812. tb_log_level = logging.ERROR
  4813. try:
  4814. args = archiver.get_args(sys.argv, os.environ.get('SSH_ORIGINAL_COMMAND'))
  4815. except Error as e:
  4816. msg = e.get_message()
  4817. tb_log_level = logging.ERROR if e.traceback else logging.DEBUG
  4818. tb = f'{traceback.format_exc()}\n{sysinfo()}'
  4819. # we might not have logging setup yet, so get out quickly
  4820. print(msg, file=sys.stderr)
  4821. if tb_log_level == logging.ERROR:
  4822. print(tb, file=sys.stderr)
  4823. sys.exit(e.exit_code)
  4824. try:
  4825. with sig_int:
  4826. exit_code = archiver.run(args)
  4827. except Error as e:
  4828. msg = e.get_message()
  4829. msgid = type(e).__qualname__
  4830. tb_log_level = logging.ERROR if e.traceback else logging.DEBUG
  4831. tb = f"{traceback.format_exc()}\n{sysinfo()}"
  4832. exit_code = e.exit_code
  4833. except RemoteRepository.RPCError as e:
  4834. important = e.exception_class not in ('LockTimeout', ) and e.traceback
  4835. msgid = e.exception_class
  4836. tb_log_level = logging.ERROR if important else logging.DEBUG
  4837. if important:
  4838. msg = e.exception_full
  4839. else:
  4840. msg = e.get_message()
  4841. tb = '\n'.join('Borg server: ' + l for l in e.sysinfo.splitlines())
  4842. tb += "\n" + sysinfo()
  4843. exit_code = EXIT_ERROR
  4844. except Exception:
  4845. msg = 'Local Exception'
  4846. msgid = 'Exception'
  4847. tb_log_level = logging.ERROR
  4848. tb = f'{traceback.format_exc()}\n{sysinfo()}'
  4849. exit_code = EXIT_ERROR
  4850. except KeyboardInterrupt:
  4851. msg = 'Keyboard interrupt'
  4852. tb_log_level = logging.DEBUG
  4853. tb = f'{traceback.format_exc()}\n{sysinfo()}'
  4854. exit_code = EXIT_SIGNAL_BASE + 2
  4855. except SigTerm:
  4856. msg = 'Received SIGTERM'
  4857. msgid = 'Signal.SIGTERM'
  4858. tb_log_level = logging.DEBUG
  4859. tb = f'{traceback.format_exc()}\n{sysinfo()}'
  4860. exit_code = EXIT_SIGNAL_BASE + 15
  4861. except SigHup:
  4862. msg = 'Received SIGHUP.'
  4863. msgid = 'Signal.SIGHUP'
  4864. exit_code = EXIT_SIGNAL_BASE + 1
  4865. if msg:
  4866. logger.error(msg, msgid=msgid)
  4867. if tb:
  4868. logger.log(tb_log_level, tb)
  4869. if args.show_rc:
  4870. rc_logger = logging.getLogger('borg.output.show-rc')
  4871. exit_msg = 'terminating with %s status, rc %d'
  4872. if exit_code == EXIT_SUCCESS:
  4873. rc_logger.info(exit_msg % ('success', exit_code))
  4874. elif exit_code == EXIT_WARNING:
  4875. rc_logger.warning(exit_msg % ('warning', exit_code))
  4876. elif exit_code == EXIT_ERROR:
  4877. rc_logger.error(exit_msg % ('error', exit_code))
  4878. elif exit_code >= EXIT_SIGNAL_BASE:
  4879. rc_logger.error(exit_msg % ('signal', exit_code))
  4880. else:
  4881. rc_logger.error(exit_msg % ('abnormal', exit_code or 666))
  4882. sys.exit(exit_code)
  4883. if __name__ == '__main__':
  4884. main()