Package duplicity :: Module commandline
[hide private]
[frames] | no frames]

Source Code for Module duplicity.commandline

   1  # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- 
   2  # 
   3  # Copyright 2002 Ben Escoto <ben@emerose.org> 
   4  # Copyright 2007 Kenneth Loafman <kenneth@loafman.com> 
   5  # 
   6  # This file is part of duplicity. 
   7  # 
   8  # Duplicity is free software; you can redistribute it and/or modify it 
   9  # under the terms of the GNU General Public License as published by the 
  10  # Free Software Foundation; either version 2 of the License, or (at your 
  11  # option) any later version. 
  12  # 
  13  # Duplicity is distributed in the hope that it will be useful, but 
  14  # WITHOUT ANY WARRANTY; without even the implied warranty of 
  15  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
  16  # General Public License for more details. 
  17  # 
  18  # You should have received a copy of the GNU General Public License 
  19  # along with duplicity; if not, write to the Free Software Foundation, 
  20  # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
  21   
  22  """Parse command line, check for consistency, and set globals""" 
  23   
  24  from copy import copy 
  25  import optparse 
  26  import os 
  27  import re 
  28  import sys 
  29  import socket 
  30   
  31  try: 
  32      from hashlib import md5 
  33  except ImportError: 
  34      from md5 import new as md5 
  35   
  36  from duplicity import backend 
  37  from duplicity import dup_time 
  38  from duplicity import globals 
  39  from duplicity import gpg 
  40  from duplicity import log 
  41  from duplicity import path 
  42  from duplicity import selection 
  43   
  44   
  45  select_opts = []            # Will hold all the selection options 
  46  select_files = []           # Will hold file objects when filelist given 
  47   
  48  full_backup = None          # Will be set to true if full command given 
  49  list_current = None         # Will be set to true if list-current command given 
  50  collection_status = None    # Will be set to true if collection-status command given 
  51  cleanup = None              # Set to true if cleanup command given 
  52  verify = None               # Set to true if verify command given 
  53   
  54  commands = ["cleanup", 
  55              "collection-status", 
  56              "full", 
  57              "incremental", 
  58              "list-current-files", 
  59              "remove-older-than", 
  60              "remove-all-but-n-full", 
  61              "remove-all-inc-of-but-n-full", 
  62              "restore", 
  63              "verify", 
  64              ] 
  65   
66 -def old_fn_deprecation(opt):
67 print >>sys.stderr, _("Warning: Option %s is pending deprecation " 68 "and will be removed in a future release.\n" 69 "Use of default filenames is strongly suggested.") % opt
70 71
72 -def expand_fn(filename):
73 return os.path.expanduser(os.path.expandvars(filename))
74 75
76 -def expand_archive_dir(archdir, backname):
77 """ 78 Return expanded version of archdir joined with backname. 79 """ 80 assert globals.backup_name is not None, \ 81 "expand_archive_dir() called prior to globals.backup_name being set" 82 83 return expand_fn(os.path.join(archdir, backname))
84 85
86 -def generate_default_backup_name(backend_url):
87 """ 88 @param backend_url: URL to backend. 89 @returns A default backup name (string). 90 """ 91 # For default, we hash args to obtain a reasonably safe default. 92 # We could be smarter and resolve things like relative paths, but 93 # this should actually be a pretty good compromise. Normally only 94 # the destination will matter since you typically only restart 95 # backups of the same thing to a given destination. The inclusion 96 # of the source however, does protect against most changes of 97 # source directory (for whatever reason, such as 98 # /path/to/different/snapshot). If the user happens to have a case 99 # where relative paths are used yet the relative path is the same 100 # (but duplicity is run from a different directory or similar), 101 # then it is simply up to the user to set --archive-dir properly. 102 burlhash = md5() 103 burlhash.update(backend_url) 104 return burlhash.hexdigest()
105
106 -def check_file(option, opt, value):
107 return expand_fn(value)
108
109 -def check_time(option, opt, value):
110 try: 111 return dup_time.genstrtotime(value) 112 except dup_time.TimeException, e: 113 raise optparse.OptionValueError(str(e))
114
115 -def check_verbosity(option, opt, value):
116 fail = False 117 118 value = value.lower() 119 if value in ['e', 'error']: 120 verb = log.ERROR 121 elif value in ['w', 'warning']: 122 verb = log.WARNING 123 elif value in ['n', 'notice']: 124 verb = log.NOTICE 125 elif value in ['i', 'info']: 126 verb = log.INFO 127 elif value in ['d', 'debug']: 128 verb = log.DEBUG 129 else: 130 try: 131 verb = int(value) 132 if verb < 0 or verb > 9: 133 fail = True 134 except ValueError: 135 fail = True 136 137 if fail: 138 # TRANSL: In this portion of the usage instructions, "[ewnid]" indicates which 139 # characters are permitted (e, w, n, i, or d); the brackets imply their own 140 # meaning in regex; i.e., only one of the characters is allowed in an instance. 141 raise optparse.OptionValueError("Verbosity must be one of: digit [0-9], character [ewnid], " 142 "or word ['error', 'warning', 'notice', 'info', 'debug']. " 143 "The default is 4 (Notice). It is strongly recommended " 144 "that verbosity level is set at 2 (Warning) or higher.") 145 146 return verb
147 148
149 -class DupOption(optparse.Option):
150 TYPES = optparse.Option.TYPES + ("file", "time", "verbosity",) 151 TYPE_CHECKER = copy(optparse.Option.TYPE_CHECKER) 152 TYPE_CHECKER["file"] = check_file 153 TYPE_CHECKER["time"] = check_time 154 TYPE_CHECKER["verbosity"] = check_verbosity 155 156 ACTIONS = optparse.Option.ACTIONS + ("extend",) 157 STORE_ACTIONS = optparse.Option.STORE_ACTIONS + ("extend",) 158 TYPED_ACTIONS = optparse.Option.TYPED_ACTIONS + ("extend",) 159 ALWAYS_TYPED_ACTIONS = optparse.Option.ALWAYS_TYPED_ACTIONS + ("extend",) 160
161 - def take_action(self, action, dest, opt, value, values, parser):
162 if action == "extend": 163 if not value: 164 return 165 if hasattr(values, dest) and getattr(values, dest): 166 setattr(values, dest, getattr(values, dest) + ' ' + value) 167 else: 168 setattr(values, dest, value) 169 else: 170 optparse.Option.take_action( 171 self, action, dest, opt, value, values, parser)
172 173 """ 174 Fix: 175 File "/usr/lib/pythonX.X/optparse.py", line XXXX, in print_help 176 file.write(self.format_help().encode(encoding, "replace")) 177 UnicodeDecodeError: 'ascii' codec can't decode byte 0xXX in position XXXX: 178 See: 179 http://bugs.python.org/issue2931 180 http://mail.python.org/pipermail/python-dev/2006-May/065458.html 181 """
182 -class OPHelpFix(optparse.OptionParser):
183 - def _get_encoding(self, file):
184 """ 185 try to get the encoding or switch to UTF-8 186 which is default encoding in python3 and most recent unixes 187 """ 188 encoding = getattr(file, "encoding", "UTF-8") 189 return encoding
190
191 - def print_help(self, file=None):
192 """ 193 overwrite method with proper utf-8 decoding 194 """ 195 if file is None: 196 file = sys.stdout 197 encoding = self._get_encoding(file) 198 file.write(self.format_help().decode('utf-8').encode(encoding, "replace"))
199 200
201 -def parse_cmdline_options(arglist):
202 """Parse argument list""" 203 global select_opts, select_files, full_backup 204 global list_current, collection_status, cleanup, remove_time, verify 205 206 def use_gio(*args): 207 try: 208 import duplicity.backends.giobackend 209 backend.force_backend(duplicity.backends.giobackend.GIOBackend) 210 except ImportError: 211 log.FatalError(_("Unable to load gio module"), log.ErrorCode.gio_not_available)
212 213 def set_log_fd(fd): 214 if fd < 1: 215 raise optparse.OptionValueError("log-fd must be greater than zero.") 216 log.add_fd(fd) 217 218 def set_time_sep(sep, opt): 219 if sep == '-': 220 raise optparse.OptionValueError("Dash ('-') not valid for time-separator.") 221 globals.time_separator = sep 222 old_fn_deprecation(opt) 223 224 def add_selection(o, s, v, p): 225 select_opts.append((s, v)) 226 227 def add_filelist(o, s, v, p): 228 filename = v 229 select_opts.append((s, filename)) 230 try: 231 select_files.append(open(filename, "r")) 232 except IOError: 233 log.FatalError(_("Error opening file %s") % filename, 234 log.ErrorCode.cant_open_filelist) 235 236 def print_ver(o, s, v, p): 237 print "duplicity %s" % (globals.version) 238 sys.exit(0) 239 240 def add_rename(o, s, v, p): 241 globals.rename[os.path.normcase(os.path.normpath(v[0]))] = v[1] 242 243 parser = OPHelpFix( option_class=DupOption, usage=usage() ) 244 245 # If this is true, only warn and don't raise fatal error when backup 246 # source directory doesn't match previous backup source directory. 247 parser.add_option("--allow-source-mismatch", action="store_true") 248 249 # Set to the path of the archive directory (the directory which 250 # contains the signatures and manifests of the relevent backup 251 # collection), and for checkpoint state between volumes. 252 # TRANSL: Used in usage help to represent a Unix-style path name. Example: 253 # --archive-dir <path> 254 parser.add_option("--archive-dir", type="file", metavar=_("path")) 255 256 # Asynchronous put/get concurrency limit 257 # (default of 0 disables asynchronicity). 258 parser.add_option("--asynchronous-upload", action="store_const", const=1, 259 dest="async_concurrency") 260 261 # config dir for future use 262 parser.add_option("--config-dir", type="file", metavar=_("path"), 263 help=optparse.SUPPRESS_HELP) 264 265 # for testing -- set current time 266 parser.add_option("--current-time", type="int", 267 dest="current_time", help=optparse.SUPPRESS_HELP) 268 269 # Don't actually do anything, but still report what would be done 270 parser.add_option("--dry-run", action="store_true") 271 272 # TRANSL: Used in usage help to represent an ID for a GnuPG key. Example: 273 # --encrypt-key <gpg_key_id> 274 parser.add_option("--encrypt-key", type="string", metavar=_("gpg-key-id"), 275 dest="", action="callback", 276 callback=lambda o, s, v, p: globals.gpg_profile.recipients.append(v)) #@UndefinedVariable 277 278 # secret keyring in which the private encrypt key can be found 279 parser.add_option("--encrypt-secret-keyring", type="string", metavar=_("path")) 280 281 parser.add_option("--encrypt-sign-key", type="string", metavar=_("gpg-key-id"), 282 dest="", action="callback", 283 callback=lambda o, s, v, p: ( globals.gpg_profile.recipients.append(v), set_sign_key(v)) ) 284 285 # TRANSL: Used in usage help to represent a "glob" style pattern for 286 # matching one or more files, as described in the documentation. 287 # Example: 288 # --exclude <shell_pattern> 289 parser.add_option("--exclude", action="callback", metavar=_("shell_pattern"), 290 dest="", type="string", callback=add_selection) 291 292 parser.add_option("--exclude-device-files", action="callback", 293 dest="", callback=add_selection) 294 295 parser.add_option("--exclude-filelist", type="file", metavar=_("filename"), 296 dest="", action="callback", callback=add_filelist) 297 298 parser.add_option("--exclude-filelist-stdin", action="callback", dest="", 299 callback=lambda o, s, v, p: (select_opts.append(("--exclude-filelist", "standard input")), 300 select_files.append(sys.stdin))) 301 302 parser.add_option("--exclude-globbing-filelist", type="file", metavar=_("filename"), 303 dest="", action="callback", callback=add_filelist) 304 305 # TRANSL: Used in usage help to represent the name of a file. Example: 306 # --log-file <filename> 307 parser.add_option("--exclude-if-present", metavar=_("filename"), dest="", 308 type="file", action="callback", callback=add_selection) 309 310 parser.add_option("--exclude-other-filesystems", action="callback", 311 dest="", callback=add_selection) 312 313 # TRANSL: Used in usage help to represent a regular expression (regexp). 314 parser.add_option("--exclude-regexp", metavar=_("regular_expression"), 315 dest="", type="string", action="callback", callback=add_selection) 316 317 # Whether we should be particularly aggressive when cleaning up 318 parser.add_option("--extra-clean", action="store_true") 319 320 # used in testing only - raises exception after volume 321 parser.add_option("--fail-on-volume", type="int", 322 help=optparse.SUPPRESS_HELP) 323 324 # used in testing only - skips upload for a given volume 325 parser.add_option("--skip-volume", type="int", 326 help=optparse.SUPPRESS_HELP) 327 328 # If set, restore only the subdirectory or file specified, not the 329 # whole root. 330 # TRANSL: Used in usage help to represent a Unix-style path name. Example: 331 # --archive-dir <path> 332 parser.add_option("--file-to-restore", "-r", action="callback", type="file", 333 metavar=_("path"), dest="restore_dir", 334 callback=lambda o, s, v, p: setattr(p.values, "restore_dir", v.rstrip('/'))) 335 336 # Used to confirm certain destructive operations like deleting old files. 337 parser.add_option("--force", action="store_true") 338 339 # FTP data connection type 340 parser.add_option("--ftp-passive", action="store_const", const="passive", dest="ftp_connection") 341 parser.add_option("--ftp-regular", action="store_const", const="regular", dest="ftp_connection") 342 343 # If set, forces a full backup if the last full backup is older than 344 # the time specified 345 parser.add_option("--full-if-older-than", type="time", dest="full_force_time", metavar=_("time")) 346 347 parser.add_option("--gio", action="callback", callback=use_gio) 348 349 parser.add_option("--gpg-options", action="extend", metavar=_("options")) 350 351 # ignore (some) errors during operations; supposed to make it more 352 # likely that you are able to restore data under problematic 353 # circumstances. the default should absolutely always be False unless 354 # you know what you are doing. 355 parser.add_option("--ignore-errors", action="callback", 356 dest="ignore_errors", 357 callback=lambda o, s, v, p: (log.Warn( 358 _("Running in 'ignore errors' mode due to %s; please " 359 "re-consider if this was not intended") % s), 360 setattr(p.values, "ignore errors", True))) 361 362 # Whether to use the full email address as the user name when 363 # logging into an imap server. If false just the user name 364 # part of the email address is used. 365 parser.add_option("--imap-full-address", action="store_true", 366 help=optparse.SUPPRESS_HELP) 367 368 # Name of the imap folder where we want to store backups. 369 # Can be changed with a command line argument. 370 # TRANSL: Used in usage help to represent an imap mailbox 371 parser.add_option("--imap-mailbox", metavar=_("imap_mailbox")) 372 373 parser.add_option("--include", action="callback", metavar=_("shell_pattern"), 374 dest="", type="string", callback=add_selection) 375 parser.add_option("--include-filelist", type="file", metavar=_("filename"), 376 dest="", action="callback", callback=add_filelist) 377 parser.add_option("--include-filelist-stdin", action="callback", dest="", 378 callback=lambda o, s, v, p: (select_opts.append(("--include-filelist", "standard input")), 379 select_files.append(sys.stdin))) 380 parser.add_option("--include-globbing-filelist", type="file", metavar=_("filename"), 381 dest="", action="callback", callback=add_filelist) 382 parser.add_option("--include-regexp", metavar=_("regular_expression"), dest="", 383 type="string", action="callback", callback=add_selection) 384 385 parser.add_option("--log-fd", type="int", metavar=_("file_descriptor"), 386 dest="", action="callback", 387 callback=lambda o, s, v, p: set_log_fd(v)) 388 389 # TRANSL: Used in usage help to represent the name of a file. Example: 390 # --log-file <filename> 391 parser.add_option("--log-file", type="file", metavar=_("filename"), 392 dest="", action="callback", 393 callback=lambda o, s, v, p: log.add_file(v)) 394 395 # TRANSL: Used in usage help (noun) 396 parser.add_option("--name", dest="backup_name", metavar=_("backup name")) 397 398 # If set to false, then do not encrypt files on remote system 399 parser.add_option("--no-encryption", action="store_false", dest="encryption") 400 401 # If set, print the statistics after every backup session 402 parser.add_option("--no-print-statistics", action="store_false", dest="print_statistics") 403 404 # If true, filelists and directory statistics will be split on 405 # nulls instead of newlines. 406 parser.add_option("--null-separator", action="store_true") 407 408 # number of retries on network operations 409 # TRANSL: Used in usage help to represent a desired number of 410 # something. Example: 411 # --num-retries <number> 412 parser.add_option("--num-retries", type="int", metavar=_("number")) 413 414 # File owner uid keeps number from tar file. Like same option in GNU tar. 415 parser.add_option("--numeric-owner", action="store_true") 416 417 # Whether the old filename format is in effect. 418 parser.add_option("--old-filenames", action="callback", 419 dest="old_filenames", 420 callback=lambda o, s, v, p: (setattr(p.values, o.dest, True), 421 old_fn_deprecation(s))) 422 423 # option to trigger Pydev debugger 424 parser.add_option("--pydevd", action="store_true") 425 426 # option to rename files during restore 427 parser.add_option("--rename", type="file", action="callback", nargs=2, 428 callback=add_rename) 429 430 # Restores will try to bring back the state as of the following time. 431 # If it is None, default to current time. 432 # TRANSL: Used in usage help to represent a time spec for a previous 433 # point in time, as described in the documentation. Example: 434 # duplicity remove-older-than time [options] target_url 435 parser.add_option("--restore-time", "--time", "-t", type="time", metavar=_("time")) 436 437 # user added rsync options 438 parser.add_option("--rsync-options", action="extend", metavar=_("options")) 439 440 # Whether to create European buckets (sorry, hard-coded to only 441 # support european for now). 442 parser.add_option("--s3-european-buckets", action="store_true") 443 444 # Whether to use S3 Reduced Redudancy Storage 445 parser.add_option("--s3-use-rrs", action="store_true") 446 447 # Whether to use "new-style" subdomain addressing for S3 buckets. Such 448 # use is not backwards-compatible with upper-case buckets, or buckets 449 # that are otherwise not expressable in a valid hostname. 450 parser.add_option("--s3-use-new-style", action="store_true") 451 452 # Whether to use plain HTTP (without SSL) to send data to S3 453 # See <https://bugs.launchpad.net/duplicity/+bug/433970>. 454 parser.add_option("--s3-unencrypted-connection", action="store_true") 455 456 # Chunk size used for S3 multipart uploads.The number of parallel uploads to 457 # S3 be given by chunk size / volume size. Use this to maximize the use of 458 # your bandwidth. Defaults to 25MB 459 parser.add_option("--s3-multipart-chunk-size", type="int", action="callback", metavar=_("number"), 460 callback=lambda o, s, v, p: setattr(p.values, "s3_multipart_chunk_size", v*1024*1024)) 461 462 # Option to allow the s3/boto backend use the multiprocessing version. 463 # By default it is off since it does not work for Python 2.4 or 2.5. 464 if sys.version_info[:2] >= (2,6): 465 parser.add_option("--s3-use-multiprocessing", action="store_true") 466 467 # scp command to use 468 # TRANSL: noun 469 parser.add_option("--scp-command", metavar=_("command")) 470 471 # sftp command to use 472 # TRANSL: noun 473 parser.add_option("--sftp-command", metavar=_("command")) 474 475 # If set, use short (< 30 char) filenames for all the remote files. 476 parser.add_option("--short-filenames", action="callback", 477 dest="short_filenames", 478 callback=lambda o, s, v, p: (setattr(p.values, o.dest, True), 479 old_fn_deprecation(s))) 480 481 # TRANSL: Used in usage help to represent an ID for a GnuPG key. Example: 482 # --encrypt-key <gpg_key_id> 483 parser.add_option("--sign-key", type="string", metavar=_("gpg-key-id"), 484 dest="", action="callback", 485 callback=lambda o, s, v, p: set_sign_key(v)) 486 487 # default to batch mode using public-key encryption 488 parser.add_option("--ssh-askpass", action="store_true") 489 490 # user added ssh options 491 parser.add_option("--ssh-options", action="extend", metavar=_("options")) 492 493 # Working directory for the tempfile module. Defaults to /tmp on most systems. 494 parser.add_option("--tempdir", dest="temproot", type="file", metavar=_("path")) 495 496 # network timeout value 497 # TRANSL: Used in usage help. Example: 498 # --timeout <seconds> 499 parser.add_option("--timeout", type="int", metavar=_("seconds")) 500 501 # Character used like the ":" in time strings like 502 # 2002-08-06T04:22:00-07:00. The colon isn't good for filenames on 503 # windows machines. 504 # TRANSL: abbreviation for "character" (noun) 505 parser.add_option("--time-separator", type="string", metavar=_("char"), 506 action="callback", 507 callback=lambda o, s, v, p: set_time_sep(v, s)) 508 509 # Whether to specify --use-agent in GnuPG options 510 parser.add_option("--use-agent", action="store_true") 511 512 parser.add_option("--use-scp", action="store_true") 513 514 parser.add_option("--verbosity", "-v", type="verbosity", metavar="[0-9]", 515 dest="", action="callback", 516 callback=lambda o, s, v, p: log.setverbosity(v)) 517 518 parser.add_option("-V", "--version", action="callback", callback=print_ver) 519 520 # volume size 521 # TRANSL: Used in usage help to represent a desired number of 522 # something. Example: 523 # --num-retries <number> 524 parser.add_option("--volsize", type="int", action="callback", metavar=_("number"), 525 callback=lambda o, s, v, p: setattr(p.values, "volsize", v*1024*1024)) 526 527 # parse the options 528 (options, args) = parser.parse_args() 529 530 # Copy all arguments and their values to the globals module. Don't copy 531 # attributes that are 'hidden' (start with an underscore) or whose name is 532 # the empty string (used for arguments that don't directly store a value 533 # by using dest="") 534 for f in filter(lambda x: x and not x.startswith("_"), dir(options)): 535 v = getattr(options, f) 536 # Only set if v is not None because None is the default for all the 537 # variables. If user didn't set it, we'll use defaults in globals.py 538 if v is not None: 539 setattr(globals, f, v) 540 541 socket.setdefaulttimeout(globals.timeout) 542 543 # expect no cmd and two positional args 544 cmd = "" 545 num_expect = 2 546 547 # process first arg as command 548 if args: 549 cmd = args.pop(0) 550 possible = [c for c in commands if c.startswith(cmd)] 551 # no unique match, that's an error 552 if len(possible) > 1: 553 command_line_error("command '%s' not unique, could be %s" % (cmd, possible)) 554 # only one match, that's a keeper 555 elif len(possible) == 1: 556 cmd = possible[0] 557 # no matches, assume no cmd 558 elif not possible: 559 args.insert(0, cmd) 560 561 if cmd == "cleanup": 562 cleanup = True 563 num_expect = 1 564 elif cmd == "collection-status": 565 collection_status = True 566 num_expect = 1 567 elif cmd == "full": 568 full_backup = True 569 num_expect = 2 570 elif cmd == "incremental": 571 globals.incremental = True 572 num_expect = 2 573 elif cmd == "list-current-files": 574 list_current = True 575 num_expect = 1 576 elif cmd == "remove-older-than": 577 try: 578 arg = args.pop(0) 579 except Exception: 580 command_line_error("Missing time string for remove-older-than") 581 globals.remove_time = dup_time.genstrtotime(arg) 582 num_expect = 1 583 elif cmd == "remove-all-but-n-full" or cmd == "remove-all-inc-of-but-n-full": 584 if cmd == "remove-all-but-n-full" : 585 globals.remove_all_but_n_full_mode = True 586 if cmd == "remove-all-inc-of-but-n-full" : 587 globals.remove_all_inc_of_but_n_full_mode = True 588 try: 589 arg = args.pop(0) 590 except Exception: 591 command_line_error("Missing count for " + cmd) 592 globals.keep_chains = int(arg) 593 if not globals.keep_chains > 0: 594 command_line_error(cmd + " count must be > 0") 595 num_expect = 1 596 elif cmd == "verify": 597 verify = True 598 599 if len(args) != num_expect: 600 command_line_error("Expected %d args, got %d" % (num_expect, len(args))) 601 602 # expand pathname args, but not URL 603 for loc in range(len(args)): 604 if not '://' in args[loc]: 605 args[loc] = expand_fn(args[loc]) 606 607 # Note that ProcessCommandLine depends on us verifying the arg 608 # count here; do not remove without fixing it. We must make the 609 # checks here in order to make enough sense of args to identify 610 # the backend URL/lpath for args_to_path_backend(). 611 if len(args) < 1: 612 command_line_error("Too few arguments") 613 elif len(args) == 1: 614 backend_url = args[0] 615 elif len(args) == 2: 616 lpath, backend_url = args_to_path_backend(args[0], args[1]) #@UnusedVariable 617 else: 618 command_line_error("Too many arguments") 619 620 if globals.backup_name is None: 621 globals.backup_name = generate_default_backup_name(backend_url) 622 623 # set and expand archive dir 624 set_archive_dir(expand_archive_dir(globals.archive_dir, 625 globals.backup_name)) 626 627 log.Info(_("Using archive dir: %s") % (globals.archive_dir.name,)) 628 log.Info(_("Using backup name: %s") % (globals.backup_name,)) 629 630 return args 631 632
633 -def command_line_error(message):
634 """Indicate a command line error and exit""" 635 log.FatalError(_("Command line error: %s") % (message,) + "\n" + 636 _("Enter 'duplicity --help' for help screen."), 637 log.ErrorCode.command_line)
638 639
640 -def usage():
641 """Returns terse usage info. The code is broken down into pieces for ease of 642 translation maintenance. Any comments that look extraneous or redundant should 643 be assumed to be for the benefit of translators, since they can get each string 644 (paired with its preceding comment, if any) independently of the others.""" 645 646 dict = { 647 # TRANSL: Used in usage help to represent a Unix-style path name. Example: 648 # rsync://user[:password]@other_host[:port]//absolute_path 649 'absolute_path' : _("absolute_path"), 650 651 # TRANSL: Used in usage help. Example: 652 # tahoe://alias/some_dir 653 'alias' : _("alias"), 654 655 # TRANSL: Used in help to represent a "bucket name" for Amazon Web 656 # Services' Simple Storage Service (S3). Example: 657 # s3://other.host/bucket_name[/prefix] 658 'bucket_name' : _("bucket_name"), 659 660 # TRANSL: abbreviation for "character" (noun) 661 'char' : _("char"), 662 663 # TRANSL: noun 664 'command' : _("command"), 665 666 # TRANSL: Used in usage help to represent the name of a container in 667 # Amazon Web Services' Cloudfront. Example: 668 # cf+http://container_name 669 'container_name' : _("container_name"), 670 671 # TRANSL: noun 672 'count' : _("count"), 673 674 # TRANSL: Used in usage help to represent the name of a file directory 675 'directory' : _("directory"), 676 677 # TRANSL: Used in usage help to represent the name of a file. Example: 678 # --log-file <filename> 679 'filename' : _("filename"), 680 681 # TRANSL: Used in usage help to represent an ID for a GnuPG key. Example: 682 # --encrypt-key <gpg_key_id> 683 'gpg_key_id' : _("gpg-key-id"), 684 685 # TRANSL: Used in usage help, e.g. to represent the name of a code 686 # module. Example: 687 # rsync://user[:password]@other.host[:port]::/module/some_dir 688 'module' : _("module"), 689 690 # TRANSL: Used in usage help to represent a desired number of 691 # something. Example: 692 # --num-retries <number> 693 'number' : _("number"), 694 695 # TRANSL: Used in usage help. (Should be consistent with the "Options:" 696 # header.) Example: 697 # duplicity [full|incremental] [options] source_dir target_url 698 'options' : _("options"), 699 700 # TRANSL: Used in usage help to represent an internet hostname. Example: 701 # ftp://user[:password]@other.host[:port]/some_dir 702 'other_host' : _("other.host"), 703 704 # TRANSL: Used in usage help. Example: 705 # ftp://user[:password]@other.host[:port]/some_dir 706 'password' : _("password"), 707 708 # TRANSL: Used in usage help to represent a Unix-style path name. Example: 709 # --archive-dir <path> 710 'path' : _("path"), 711 712 # TRANSL: Used in usage help to represent a TCP port number. Example: 713 # ftp://user[:password]@other.host[:port]/some_dir 714 'port' : _("port"), 715 716 # TRANSL: Used in usage help. This represents a string to be used as a 717 # prefix to names for backup files created by Duplicity. Example: 718 # s3://other.host/bucket_name[/prefix] 719 'prefix' : _("prefix"), 720 721 # TRANSL: Used in usage help to represent a Unix-style path name. Example: 722 # rsync://user[:password]@other.host[:port]/relative_path 723 'relative_path' : _("relative_path"), 724 725 # TRANSL: Used in usage help. Example: 726 # --timeout <seconds> 727 'seconds' : _("seconds"), 728 729 # TRANSL: Used in usage help to represent a "glob" style pattern for 730 # matching one or more files, as described in the documentation. 731 # Example: 732 # --exclude <shell_pattern> 733 'shell_pattern' : _("shell_pattern"), 734 735 # TRANSL: Used in usage help to represent the name of a single file 736 # directory or a Unix-style path to a directory. Example: 737 # file:///some_dir 738 'some_dir' : _("some_dir"), 739 740 # TRANSL: Used in usage help to represent the name of a single file 741 # directory or a Unix-style path to a directory where files will be 742 # coming FROM. Example: 743 # duplicity [full|incremental] [options] source_dir target_url 744 'source_dir' : _("source_dir"), 745 746 # TRANSL: Used in usage help to represent a URL files will be coming 747 # FROM. Example: 748 # duplicity [restore] [options] source_url target_dir 749 'source_url' : _("source_url"), 750 751 # TRANSL: Used in usage help to represent the name of a single file 752 # directory or a Unix-style path to a directory. where files will be 753 # going TO. Example: 754 # duplicity [restore] [options] source_url target_dir 755 'target_dir' : _("target_dir"), 756 757 # TRANSL: Used in usage help to represent a URL files will be going TO. 758 # Example: 759 # duplicity [full|incremental] [options] source_dir target_url 760 'target_url' : _("target_url"), 761 762 # TRANSL: Used in usage help to represent a time spec for a previous 763 # point in time, as described in the documentation. Example: 764 # duplicity remove-older-than time [options] target_url 765 'time' : _("time"), 766 767 # TRANSL: Used in usage help to represent a user name (i.e. login). 768 # Example: 769 # ftp://user[:password]@other.host[:port]/some_dir 770 'user' : _("user") } 771 772 # TRANSL: Header in usage help 773 msg = """ 774 duplicity [full|incremental] [%(options)s] %(source_dir)s %(target_url)s 775 duplicity [restore] [%(options)s] %(source_url)s %(target_dir)s 776 duplicity verify [%(options)s] %(source_url)s %(target_dir)s 777 duplicity collection-status [%(options)s] %(target_url)s 778 duplicity list-current-files [%(options)s] %(target_url)s 779 duplicity cleanup [%(options)s] %(target_url)s 780 duplicity remove-older-than %(time)s [%(options)s] %(target_url)s 781 duplicity remove-all-but-n-full %(count)s [%(options)s] %(target_url)s 782 duplicity remove-all-inc-of-but-n-full %(count)s [%(options)s] %(target_url)s 783 784 """ % dict 785 786 # TRANSL: Header in usage help 787 msg = msg + _("Backends and their URL formats:") + """ 788 cf+http://%(container_name)s 789 file:///%(some_dir)s 790 ftp://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]/%(some_dir)s 791 ftps://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]/%(some_dir)s 792 hsi://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]/%(some_dir)s 793 imap://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]/%(some_dir)s 794 rsync://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]::/%(module)s/%(some_dir)s 795 rsync://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]/%(relative_path)s 796 rsync://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]//%(absolute_path)s 797 s3://%(other_host)s/%(bucket_name)s[/%(prefix)s] 798 s3+http://%(bucket_name)s[/%(prefix)s] 799 scp://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]/%(some_dir)s 800 ssh://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]/%(some_dir)s 801 tahoe://%(alias)s/%(directory)s 802 webdav://%(user)s[:%(password)s]@%(other_host)s/%(some_dir)s 803 webdavs://%(user)s[:%(password)s]@%(other_host)s/%(some_dir)s 804 gdocs://%(user)s[:%(password)s]@%(other_host)s/%(some_dir)s 805 806 """ % dict 807 808 # TRANSL: Header in usage help 809 msg = msg + _("Commands:") + """ 810 cleanup <%(target_url)s> 811 collection-status <%(target_url)s> 812 full <%(source_dir)s> <%(target_url)s> 813 incr <%(source_dir)s> <%(target_url)s> 814 list-current-files <%(target_url)s> 815 restore <%(target_url)s> <%(source_dir)s> 816 remove-older-than <%(time)s> <%(target_url)s> 817 remove-all-but-n-full <%(count)s> <%(target_url)s> 818 remove-all-inc-of-but-n-full <%(count)s> <%(target_url)s> 819 verify <%(target_url)s> <%(source_dir)s>""" % dict 820 821 return msg
822 823
824 -def set_archive_dir(dirstring):
825 """Check archive dir and set global""" 826 if not os.path.exists(dirstring): 827 try: 828 os.makedirs(dirstring) 829 except Exception: 830 pass 831 archive_dir = path.Path(dirstring) 832 if not archive_dir.isdir(): 833 log.FatalError(_("Specified archive directory '%s' does not exist, " 834 "or is not a directory") % (archive_dir.name,), 835 log.ErrorCode.bad_archive_dir) 836 globals.archive_dir = archive_dir
837 838
839 -def set_sign_key(sign_key):
840 """Set globals.sign_key assuming proper key given""" 841 if not len(sign_key) == 8 or not re.search("^[0-9A-F]*$", sign_key): 842 log.FatalError(_("Sign key should be an 8 character hex string, like " 843 "'AA0E73D2'.\nReceived '%s' instead.") % (sign_key,), 844 log.ErrorCode.bad_sign_key) 845 globals.gpg_profile.sign_key = sign_key
846 847
848 -def set_selection():
849 """Return selection iter starting at filename with arguments applied""" 850 global select_opts, select_files 851 sel = selection.Select(globals.local_path) 852 sel.ParseArgs(select_opts, select_files) 853 globals.select = sel.set_iter()
854 855
856 -def args_to_path_backend(arg1, arg2):
857 """ 858 Given exactly two arguments, arg1 and arg2, figure out which one 859 is the backend URL and which one is a local path, and return 860 (local, backend). 861 """ 862 arg1_is_backend, arg2_is_backend = backend.is_backend_url(arg1), backend.is_backend_url(arg2) 863 864 if not arg1_is_backend and not arg2_is_backend: 865 command_line_error( 866 """One of the arguments must be an URL. Examples of URL strings are 867 "scp://user@host.net:1234/path" and "file:///usr/local". See the man 868 page for more information.""") 869 if arg1_is_backend and arg2_is_backend: 870 command_line_error("Two URLs specified. " 871 "One argument should be a path.") 872 if arg1_is_backend: 873 return (arg2, arg1) 874 elif arg2_is_backend: 875 return (arg1, arg2) 876 else: 877 raise AssertionError('should not be reached')
878 879
880 -def set_backend(arg1, arg2):
881 """Figure out which arg is url, set backend 882 883 Return value is pair (path_first, path) where is_first is true iff 884 path made from arg1. 885 886 """ 887 path, bend = args_to_path_backend(arg1, arg2) 888 889 globals.backend = backend.get_backend(bend) 890 891 if path == arg2: 892 return (None, arg2) # False? 893 else: 894 return (1, arg1) # True?
895 896
897 -def process_local_dir(action, local_pathname):
898 """Check local directory, set globals.local_path""" 899 local_path = path.Path(path.Path(local_pathname).get_canonical()) 900 if action == "restore": 901 if (local_path.exists() and not local_path.isemptydir()) and not globals.force: 902 log.FatalError(_("Restore destination directory %s already " 903 "exists.\nWill not overwrite.") % (local_pathname,), 904 log.ErrorCode.restore_dir_exists) 905 elif action == "verify": 906 if not local_path.exists(): 907 log.FatalError(_("Verify directory %s does not exist") % 908 (local_path.name,), 909 log.ErrorCode.verify_dir_doesnt_exist) 910 else: 911 assert action == "full" or action == "inc" 912 if not local_path.exists(): 913 log.FatalError(_("Backup source directory %s does not exist.") 914 % (local_path.name,), 915 log.ErrorCode.backup_dir_doesnt_exist) 916 917 globals.local_path = local_path
918 919
920 -def check_consistency(action):
921 """Final consistency check, see if something wrong with command line""" 922 global full_backup, select_opts, list_current 923 def assert_only_one(arglist): 924 """Raises error if two or more of the elements of arglist are true""" 925 n = 0 926 for m in arglist: 927 if m: 928 n+=1 929 assert n <= 1, "Invalid syntax, two conflicting modes specified"
930 if action in ["list-current", "collection-status", 931 "cleanup", "remove-old", "remove-all-but-n-full", "remove-all-inc-of-but-n-full"]: 932 assert_only_one([list_current, collection_status, cleanup, 933 globals.remove_time is not None]) 934 elif action == "restore" or action == "verify": 935 if full_backup: 936 command_line_error("--full option cannot be used when " 937 "restoring or verifying") 938 elif globals.incremental: 939 command_line_error("--incremental option cannot be used when " 940 "restoring or verifying") 941 if select_opts and action == "restore": 942 log.Warn( _("Command line warning: %s") % _("Selection options --exclude/--include\n" 943 "currently work only when backing up," 944 "not restoring.") ) 945 else: 946 assert action == "inc" or action == "full" 947 if verify: 948 command_line_error("--verify option cannot be used " 949 "when backing up") 950 if globals.restore_dir: 951 command_line_error("restore option incompatible with %s backup" 952 % (action,)) 953 954
955 -def ProcessCommandLine(cmdline_list):
956 """Process command line, set globals, return action 957 958 action will be "list-current", "collection-status", "cleanup", 959 "remove-old", "restore", "verify", "full", or "inc". 960 961 """ 962 globals.gpg_profile = gpg.GPGProfile() 963 964 args = parse_cmdline_options(cmdline_list) 965 966 # we can now try to import all the backends 967 backend.import_backends() 968 969 # parse_cmdline_options already verified that we got exactly 1 or 2 970 # non-options arguments 971 assert len(args) >= 1 and len(args) <= 2, "arg count should have been checked already" 972 973 if len(args) == 1: 974 if list_current: 975 action = "list-current" 976 elif collection_status: 977 action = "collection-status" 978 elif cleanup: 979 action = "cleanup" 980 elif globals.remove_time is not None: 981 action = "remove-old" 982 elif globals.remove_all_but_n_full_mode: 983 action = "remove-all-but-n-full" 984 elif globals.remove_all_inc_of_but_n_full_mode: 985 action = "remove-all-inc-of-but-n-full" 986 else: 987 command_line_error("Too few arguments") 988 globals.backend = backend.get_backend(args[0]) 989 if not globals.backend: 990 log.FatalError(_("""Bad URL '%s'. 991 Examples of URL strings are "scp://user@host.net:1234/path" and 992 "file:///usr/local". See the man page for more information.""") % (args[0],), 993 log.ErrorCode.bad_url) 994 elif len(args) == 2: 995 # Figure out whether backup or restore 996 backup, local_pathname = set_backend(args[0], args[1]) 997 if backup: 998 if full_backup: 999 action = "full" 1000 else: 1001 action = "inc" 1002 else: 1003 if verify: 1004 action = "verify" 1005 else: 1006 action = "restore" 1007 1008 process_local_dir(action, local_pathname) 1009 if action in ['full', 'inc', 'verify']: 1010 set_selection() 1011 elif len(args) > 2: 1012 raise AssertionError("this code should not be reachable") 1013 1014 check_consistency(action) 1015 log.Info(_("Main action: ") + action) 1016 return action
1017