1# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4; encoding:utf8 -*-
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
22u"""Parse command line, check for consistency, and set config"""
23
24from __future__ import print_function
25from future import standard_library
26standard_library.install_aliases()
27from builtins import str
28from builtins import range
29
30from copy import copy
31import optparse
32import os
33import re
34import sys
35import socket
36import io
37
38try:
39    from hashlib import md5
40except ImportError:
41    from md5 import new as md5
42
43from duplicity import backend
44from duplicity import dup_time
45from duplicity import config
46from duplicity import gpg
47from duplicity import log
48from duplicity import path
49from duplicity import selection
50from duplicity import util
51
52
53select_opts = []  # Will hold all the selection options
54select_files = []  # Will hold file objects when filelist given
55
56full_backup = None  # Will be set to true if full command given
57list_current = None  # Will be set to true if list-current command given
58collection_status = None  # Will be set to true if collection-status command given
59cleanup = None  # Set to true if cleanup command given
60verify = None  # Set to true if verify command given
61replicate = None  # Set to true if replicate command given
62
63commands = [u"cleanup",
64            u"collection-status",
65            u"full",
66            u"incremental",
67            u"list-current-files",
68            u"remove-older-than",
69            u"remove-all-but-n-full",
70            u"remove-all-inc-of-but-n-full",
71            u"restore",
72            u"verify",
73            u"replicate"
74            ]
75
76
77def old_fn_deprecation(opt):
78    log.Log(_(u"Warning: Option %s is pending deprecation "
79              u"and will be removed in version 0.9.0.\n"
80              u"Use of default filenames is strongly suggested.") % opt,
81            log.ERROR, force_print=True)
82
83
84def old_globbing_filelist_deprecation(opt):
85    log.Log(_(u"Warning: Option %s is pending deprecation and will be removed in a future release.\n"
86              u"--include-filelist and --exclude-filelist now accept globbing characters and should "
87              u"be used instead.") % opt,
88            log.ERROR, force_print=True)
89
90
91def stdin_deprecation(opt):
92    # See https://bugs.launchpad.net/duplicity/+bug/1423367
93    # In almost all Linux distros stdin is a file represented by /dev/stdin,
94    # so --exclude-file=/dev/stdin will work as a substitute.
95    log.Log(_(u"Warning: Option %s is pending deprecation and will be removed in a future release.\n"
96              u"On many GNU/Linux systems, stdin is represented by /dev/stdin and\n"
97              u"--include-filelist=/dev/stdin or --exclude-filelist=/dev/stdin could\n"
98              u"be used as a substitute.") % opt,
99            log.ERROR, force_print=True)
100
101
102# log options handled in log.py.  Add noop to make optparse happy
103def noop():
104    pass
105
106
107def expand_fn(filename):
108    return os.path.expanduser(os.path.expandvars(filename))
109
110
111def expand_archive_dir(archdir, backname):
112    u"""
113    Return expanded version of archdir joined with backname.
114    """
115    assert config.backup_name is not None, \
116        u"expand_archive_dir() called prior to config.backup_name being set"
117
118    return expand_fn(os.path.join(archdir, backname))
119
120
121def generate_default_backup_name(backend_url):
122    u"""
123    @param backend_url: URL to backend.
124    @returns A default backup name (string).
125    """
126    # For default, we hash args to obtain a reasonably safe default.
127    # We could be smarter and resolve things like relative paths, but
128    # this should actually be a pretty good compromise. Normally only
129    # the destination will matter since you typically only restart
130    # backups of the same thing to a given destination. The inclusion
131    # of the source however, does protect against most changes of
132    # source directory (for whatever reason, such as
133    # /path/to/different/snapshot). If the user happens to have a case
134    # where relative paths are used yet the relative path is the same
135    # (but duplicity is run from a different directory or similar),
136    # then it is simply up to the user to set --archive-dir properly.
137    burlhash = md5()
138    burlhash.update(backend_url.encode())
139    return burlhash.hexdigest()
140
141
142def check_file(option, opt, value):  # pylint: disable=unused-argument
143    return expand_fn(value)
144
145
146def check_time(option, opt, value):  # pylint: disable=unused-argument
147    try:
148        return dup_time.genstrtotime(value)
149    except dup_time.TimeException as e:
150        raise optparse.OptionValueError(str(e))
151
152
153def check_verbosity(option, opt, value):  # pylint: disable=unused-argument
154    fail = False
155
156    value = value.lower()
157    if value in [u'e', u'error']:
158        verb = log.ERROR
159    elif value in [u'w', u'warning']:
160        verb = log.WARNING
161    elif value in [u'n', u'notice']:
162        verb = log.NOTICE
163    elif value in [u'i', u'info']:
164        verb = log.INFO
165    elif value in [u'd', u'debug']:
166        verb = log.DEBUG
167    else:
168        try:
169            verb = int(value)
170            if verb < 0 or verb > 9:
171                fail = True
172        except ValueError:
173            fail = True
174
175    if fail:
176        # TRANSL: In this portion of the usage instructions, "[ewnid]" indicates which
177        # characters are permitted (e, w, n, i, or d); the brackets imply their own
178        # meaning in regex; i.e., only one of the characters is allowed in an instance.
179        raise optparse.OptionValueError(u"Verbosity must be one of: digit [0-9], character [ewnid], "
180                                        u"or word ['error', 'warning', 'notice', 'info', 'debug']. "
181                                        u"The default is 4 (Notice).  It is strongly recommended "
182                                        u"that verbosity level is set at 2 (Warning) or higher.")
183
184    return verb
185
186
187class DupOption(optparse.Option):
188    TYPES = optparse.Option.TYPES + (u"file", u"time", u"verbosity",)
189    TYPE_CHECKER = copy(optparse.Option.TYPE_CHECKER)
190    TYPE_CHECKER[u"file"] = check_file
191    TYPE_CHECKER[u"time"] = check_time
192    TYPE_CHECKER[u"verbosity"] = check_verbosity
193
194    ACTIONS = optparse.Option.ACTIONS + (u"extend",)
195    STORE_ACTIONS = optparse.Option.STORE_ACTIONS + (u"extend",)
196    TYPED_ACTIONS = optparse.Option.TYPED_ACTIONS + (u"extend",)
197    ALWAYS_TYPED_ACTIONS = optparse.Option.ALWAYS_TYPED_ACTIONS + (u"extend",)
198
199    def take_action(self, action, dest, opt, value, values, parser):
200        if action == u"extend":
201            if not value:
202                return
203            if hasattr(values, dest) and getattr(values, dest):
204                setattr(values, dest, getattr(values, dest) + u' ' + value)
205            else:
206                setattr(values, dest, value)
207        else:
208            optparse.Option.take_action(
209                self, action, dest, opt, value, values, parser)
210
211
212def parse_cmdline_options(arglist):
213    u"""Parse argument list"""
214    global select_opts, select_files, full_backup
215    global list_current, collection_status, cleanup, remove_time, verify, replicate
216
217    def set_log_fd(fd):
218        if fd < 1:
219            raise optparse.OptionValueError(u"log-fd must be greater than zero.")
220        log.add_fd(fd)
221
222    def set_time_sep(sep, opt):
223        if sep == u'-':
224            raise optparse.OptionValueError(u"Dash ('-') not valid for time-separator.")
225        config.time_separator = sep
226        old_fn_deprecation(opt)
227
228    def add_selection(o, option, additional_arg, p):  # pylint: disable=unused-argument
229        if o.type in (u"string", u"file"):
230            addarg = util.fsdecode(additional_arg)
231        else:
232            addarg = additional_arg
233        select_opts.append((util.fsdecode(option), addarg))
234
235    def add_filelist(o, s, filename, p):  # pylint: disable=unused-argument
236        select_opts.append((util.fsdecode(s), util.fsdecode(filename)))
237        try:
238            select_files.append(io.open(filename, u"rt", encoding=u"UTF-8"))
239        except IOError:
240            log.FatalError(_(u"Error opening file %s") % filename,
241                           log.ErrorCode.cant_open_filelist)
242
243    def print_ver(o, s, v, p):  # pylint: disable=unused-argument
244        print(u"duplicity %s" % (config.version))
245        sys.exit(0)
246
247    def add_rename(o, s, v, p):  # pylint: disable=unused-argument
248        key = util.fsencode(os.path.normcase(os.path.normpath(v[0])))
249        config.rename[key] = util.fsencode(v[1])
250
251    parser = optparse.OptionParser(option_class=DupOption, usage=usage())
252
253    # If this is true, only warn and don't raise fatal error when backup
254    # source directory doesn't match previous backup source directory.
255    parser.add_option(u"--allow-source-mismatch", action=u"store_true")
256
257    # Set to the path of the archive directory (the directory which
258    # contains the signatures and manifests of the relevent backup
259    # collection), and for checkpoint state between volumes.
260    # TRANSL: Used in usage help to represent a Unix-style path name. Example:
261    # --archive-dir <path>
262    parser.add_option(u"--archive-dir", type=u"file", metavar=_(u"path"))
263
264    # Asynchronous put/get concurrency limit
265    # (default of 0 disables asynchronicity).
266    parser.add_option(u"--asynchronous-upload", action=u"store_const", const=1,
267                      dest=u"async_concurrency")
268
269    parser.add_option(u"--compare-data", action=u"store_true")
270
271    # config dir for future use
272    parser.add_option(u"--config-dir", type=u"file", metavar=_(u"path"),
273                      help=optparse.SUPPRESS_HELP)
274
275    # When symlinks are encountered, the item they point to is copied rather than
276    # the symlink.
277    parser.add_option(u"--copy-links", action=u"store_true")
278
279    # for testing -- set current time
280    parser.add_option(u"--current-time", type=u"int",
281                      dest=u"current_time", help=optparse.SUPPRESS_HELP)
282
283    # Don't actually do anything, but still report what would be done
284    parser.add_option(u"--dry-run", action=u"store_true")
285
286    # TRANSL: Used in usage help to represent an ID for a GnuPG key. Example:
287    # --encrypt-key <gpg_key_id>
288    parser.add_option(u"--encrypt-key", type=u"string", metavar=_(u"gpg-key-id"),
289                      dest=u"", action=u"callback",
290                      callback=lambda o, s, v, p: config.gpg_profile.recipients.append(v))
291
292    # secret keyring in which the private encrypt key can be found
293    parser.add_option(u"--encrypt-secret-keyring", type=u"string", metavar=_(u"path"))
294
295    parser.add_option(u"--encrypt-sign-key", type=u"string", metavar=_(u"gpg-key-id"),
296                      dest=u"", action=u"callback",
297                      callback=lambda o, s, v, p: (config.gpg_profile.recipients.append(v), set_sign_key(v)))
298
299    # TRANSL: Used in usage help to represent a "glob" style pattern for
300    # matching one or more files, as described in the documentation.
301    # Example:
302    # --exclude <shell_pattern>
303    parser.add_option(u"--exclude", action=u"callback", metavar=_(u"shell_pattern"),
304                      dest=u"", type=u"string", callback=add_selection)
305
306    parser.add_option(u"--exclude-device-files", action=u"callback",
307                      dest=u"", callback=add_selection)
308
309    parser.add_option(u"--exclude-filelist", type=u"file", metavar=_(u"filename"),
310                      dest=u"", action=u"callback", callback=add_filelist)
311
312    parser.add_option(u"--exclude-filelist-stdin", action=u"callback", dest=u"",
313                      callback=lambda o, s, v, p: (select_opts.append((u"--exclude-filelist", u"standard input")),
314                                                   select_files.append(sys.stdin),
315                                                   stdin_deprecation(o)),
316                      help=optparse.SUPPRESS_HELP)
317
318    parser.add_option(u"--exclude-globbing-filelist", type=u"file", metavar=_(u"filename"),
319                      dest=u"", action=u"callback", callback=lambda o, s, v, p: (add_filelist(o, s, v, p),
320                                                                                 old_globbing_filelist_deprecation(s)),
321                      help=optparse.SUPPRESS_HELP)
322
323    # TRANSL: Used in usage help to represent the name of a file. Example:
324    # --log-file <filename>
325    parser.add_option(u"--exclude-if-present", metavar=_(u"filename"), dest=u"",
326                      type=u"file", action=u"callback", callback=add_selection)
327
328    parser.add_option(u"--exclude-other-filesystems", action=u"callback",
329                      dest=u"", callback=add_selection)
330
331    # TRANSL: Used in usage help to represent a regular expression (regexp).
332    parser.add_option(u"--exclude-regexp", metavar=_(u"regular_expression"),
333                      dest=u"", type=u"string", action=u"callback", callback=add_selection)
334
335    # Exclude any files with modification dates older than this from the backup
336    parser.add_option(u"--exclude-older-than", type=u"time", metavar=_(u"time"),
337                      dest=u"", action=u"callback", callback=add_selection)
338
339    # used in testing only - raises exception after volume
340    parser.add_option(u"--fail-on-volume", type=u"int",
341                      help=optparse.SUPPRESS_HELP)
342
343    # used to provide a prefix on top of the defaul tar file name
344    parser.add_option(u"--file-prefix", type=u"string", dest=u"file_prefix", action=u"store")
345
346    # used to provide a suffix for manifest files only
347    parser.add_option(u"--file-prefix-manifest", type=u"string", dest=u"file_prefix_manifest", action=u"store")
348
349    # used to provide a suffix for archive files only
350    parser.add_option(u"--file-prefix-archive", type=u"string", dest=u"file_prefix_archive", action=u"store")
351
352    # used to provide a suffix for sigature files only
353    parser.add_option(u"--file-prefix-signature", type=u"string", dest=u"file_prefix_signature", action=u"store")
354
355    # used in testing only - skips upload for a given volume
356    parser.add_option(u"--skip-volume", type=u"int",
357                      help=optparse.SUPPRESS_HELP)
358
359    # If set, restore only the subdirectory or file specified, not the
360    # whole root.
361    # TRANSL: Used in usage help to represent a Unix-style path name. Example:
362    # --archive-dir <path>
363    parser.add_option(u"--file-to-restore", u"-r", action=u"callback", type=u"file",
364                      metavar=_(u"path"), dest=u"restore_dir",
365                      callback=lambda o, s, v, p: setattr(p.values, u"restore_dir", util.fsencode(v.strip(u'/'))))
366
367    # Used to confirm certain destructive operations like deleting old files.
368    parser.add_option(u"--force", action=u"store_true")
369
370    # FTP data connection type
371    parser.add_option(u"--ftp-passive", action=u"store_const", const=u"passive", dest=u"ftp_connection")
372    parser.add_option(u"--ftp-regular", action=u"store_const", const=u"regular", dest=u"ftp_connection")
373
374    # If set, forces a full backup if the last full backup is older than
375    # the time specified
376    parser.add_option(u"--full-if-older-than", type=u"time", dest=u"full_force_time", metavar=_(u"time"))
377
378    parser.add_option(u"--gio", action=u"callback", dest=u"use_gio",
379                      callback=lambda o, s, v, p: (setattr(p.values, o.dest, True),
380                                                   old_fn_deprecation(s)))
381
382    parser.add_option(u"--gpg-binary", type=u"file", metavar=_(u"path"))
383
384    parser.add_option(u"--gpg-options", action=u"extend", metavar=_(u"options"))
385
386    # TRANSL: Used in usage help to represent an ID for a hidden GnuPG key. Example:
387    # --hidden-encrypt-key <gpg_key_id>
388    parser.add_option(u"--hidden-encrypt-key", type=u"string", metavar=_(u"gpg-key-id"),
389                      dest=u"", action=u"callback",
390                      callback=lambda o, s, v, p: config.gpg_profile.hidden_recipients.append(v))
391
392    # Fake-root for iDrived backend
393    parser.add_option(u"--idr-fakeroot", dest=u"fakeroot", type=u"file", metavar=_(u"path"))
394
395    # ignore (some) errors during operations; supposed to make it more
396    # likely that you are able to restore data under problematic
397    # circumstances. the default should absolutely always be False unless
398    # you know what you are doing.
399    parser.add_option(u"--ignore-errors", action=u"callback",
400                      dest=u"ignore_errors",
401                      callback=lambda o, s, v, p: (log.Warn(
402                          _(u"Running in 'ignore errors' mode due to %s; please "
403                            u"re-consider if this was not intended") % s),
404                          setattr(p.values, u"ignore_errors", True)))
405
406    # Whether to use the full email address as the user name when
407    # logging into an imap server. If false just the user name
408    # part of the email address is used.
409    parser.add_option(u"--imap-full-address", action=u"store_true")
410
411    # Name of the imap folder where we want to store backups.
412    # Can be changed with a command line argument.
413    # TRANSL: Used in usage help to represent an imap mailbox
414    parser.add_option(u"--imap-mailbox", metavar=_(u"imap_mailbox"))
415
416    parser.add_option(u"--include", action=u"callback", metavar=_(u"shell_pattern"),
417                      dest=u"", type=u"string", callback=add_selection)
418    parser.add_option(u"--include-filelist", type=u"file", metavar=_(u"filename"),
419                      dest=u"", action=u"callback", callback=add_filelist)
420    parser.add_option(u"--include-filelist-stdin", action=u"callback", dest=u"",
421                      callback=lambda o, s, v, p: (select_opts.append((u"--include-filelist", u"standard input")),
422                                                   select_files.append(sys.stdin),
423                                                   stdin_deprecation(o)),
424                      help=optparse.SUPPRESS_HELP)
425    parser.add_option(u"--include-globbing-filelist", type=u"file", metavar=_(u"filename"),
426                      dest=u"", action=u"callback", callback=lambda o, s, v, p: (add_filelist(o, s, v, p),
427                                                                                 old_globbing_filelist_deprecation(s)),
428                      help=optparse.SUPPRESS_HELP)
429    parser.add_option(u"--include-regexp", metavar=_(u"regular_expression"), dest=u"",
430                      type=u"string", action=u"callback", callback=add_selection)
431
432    parser.add_option(u"--log-fd", type=u"int", metavar=_(u"file_descriptor"),
433                      dest=u"", action=u"callback",
434                      callback=lambda o, s, v, p: set_log_fd(v))
435
436    # TRANSL: Used in usage help to represent the name of a file. Example:
437    # --log-file <filename>
438    parser.add_option(u"--log-file", type=u"file", metavar=_(u"filename"),
439                      dest=u"", action=u"callback",
440                      callback=lambda o, s, v, p: log.add_file(v))
441
442    # log option to add timestamp and level to log entries
443    parser.add_option(u"--log-timestamp", action=u"callback",
444                      callback=lambda o, s, v, p: noop())
445
446    # Maximum block size for large files
447    parser.add_option(u"--max-blocksize", type=u"int", metavar=_(u"number"))
448
449    # TRANSL: Used in usage help (noun)
450    parser.add_option(u"--name", dest=u"backup_name", metavar=_(u"backup name"))
451
452    # If set to false, then do not encrypt files on remote system
453    parser.add_option(u"--no-encryption", action=u"store_false", dest=u"encryption")
454
455    # If set to false, then do not compress files on remote system
456    parser.add_option(u"--no-compression", action=u"store_false", dest=u"compression")
457
458    # If set, print the statistics after every backup session
459    parser.add_option(u"--no-print-statistics", action=u"store_false", dest=u"print_statistics")
460
461    # If true, filelists and directory statistics will be split on
462    # nulls instead of newlines.
463    parser.add_option(u"--null-separator", action=u"store_true")
464
465    # number of retries on network operations
466    # TRANSL: Used in usage help to represent a desired number of
467    # something. Example:
468    # --num-retries <number>
469    parser.add_option(u"--num-retries", type=u"int", metavar=_(u"number"))
470
471    # File owner uid keeps number from tar file. Like same option in GNU tar.
472    parser.add_option(u"--numeric-owner", action=u"store_true")
473
474    # Do no restore the uid/gid when finished, useful if you're restoring
475    # data without having root privileges or Unix users support
476    parser.add_option(u"--do-not-restore-ownership", action=u"store_true")
477
478    # Whether the old filename format is in effect.
479    parser.add_option(u"--old-filenames", action=u"callback",
480                      dest=u"old_filenames",
481                      callback=lambda o, s, v, p: (setattr(p.values, o.dest, True),
482                                                   old_fn_deprecation(s)))
483
484    # Sync only required metadata
485    parser.add_option(u"--metadata-sync-mode",
486                      default=u"partial",
487                      choices=(u"full", u"partial"))
488
489    # Level of Redundancy in % for Par2 files
490    parser.add_option(u"--par2-redundancy", type=u"int", metavar=_(u"number"))
491
492    # Verbatim par2 options
493    parser.add_option(u"--par2-options", action=u"extend", metavar=_(u"options"))
494
495    # Used to display the progress for the full and incremental backup operations
496    parser.add_option(u"--progress", action=u"store_true")
497
498    # Used to control the progress option update rate in seconds. Default: prompts each 3 seconds
499    parser.add_option(u"--progress-rate", type=u"int", metavar=_(u"number"))
500
501    # option to trigger Pydev debugger
502    parser.add_option(u"--pydevd", action=u"store_true")
503
504    # option to rename files during restore
505    parser.add_option(u"--rename", type=u"file", action=u"callback", nargs=2,
506                      callback=add_rename)
507
508    # Restores will try to bring back the state as of the following time.
509    # If it is None, default to current time.
510    # TRANSL: Used in usage help to represent a time spec for a previous
511    # point in time, as described in the documentation. Example:
512    # duplicity remove-older-than time [options] target_url
513    parser.add_option(u"--restore-time", u"--time", u"-t", type=u"time", metavar=_(u"time"))
514
515    # user added rsync options
516    parser.add_option(u"--rsync-options", action=u"extend", metavar=_(u"options"))
517
518    # Whether to create European buckets (sorry, hard-coded to only
519    # support european for now).
520    parser.add_option(u"--s3-european-buckets", action=u"store_true")
521
522    # Whether to use S3 Reduced Redundancy Storage
523    parser.add_option(u"--s3-use-rrs", action=u"store_true")
524
525    # Whether to use S3 Infrequent Access Storage
526    parser.add_option(u"--s3-use-ia", action=u"store_true")
527
528    # Whether to use S3 Glacier Storage
529    parser.add_option(u"--s3-use-glacier", action=u"store_true")
530
531    # Whether to use S3 Glacier Deep Archive Storage
532    parser.add_option(u"--s3-use-deep-archive", action=u"store_true")
533
534    # Whether to use S3 One Zone Infrequent Access Storage
535    parser.add_option(u"--s3-use-onezone-ia", action=u"store_true")
536
537    # Whether to use "new-style" subdomain addressing for S3 buckets. Such
538    # use is not backwards-compatible with upper-case buckets, or buckets
539    # that are otherwise not expressable in a valid hostname.
540    parser.add_option(u"--s3-use-new-style", action=u"store_true")
541
542    # Whether to use plain HTTP (without SSL) to send data to S3
543    # See <https://bugs.launchpad.net/duplicity/+bug/433970>.
544    parser.add_option(u"--s3-unencrypted-connection", action=u"store_true")
545
546    # Chunk size used for S3 multipart uploads.The number of parallel uploads to
547    # S3 be given by chunk size / volume size. Use this to maximize the use of
548    # your bandwidth. Defaults to 25MB
549    parser.add_option(u"--s3-multipart-chunk-size", type=u"int", action=u"callback", metavar=_(u"number"),
550                      callback=lambda o, s, v, p: setattr(p.values, u"s3_multipart_chunk_size", v * 1024 * 1024))
551
552    # Number of processes to set the Processor Pool to when uploading multipart
553    # uploads to S3. Use this to control the maximum simultaneous uploads to S3.
554    parser.add_option(u"--s3-multipart-max-procs", type=u"int", metavar=_(u"number"))
555
556    # Number of seconds to wait for each part of a multipart upload to S3. Use this
557    # to prevent hangups when doing a multipart upload to S3.
558    parser.add_option(u"--s3-multipart-max-timeout", type=u"int", metavar=_(u"number"))
559
560    # Option to allow the s3/boto backend use the multiprocessing version.
561    parser.add_option(u"--s3-use-multiprocessing", action=u"store_true")
562
563    # Option to allow use of server side encryption in s3
564    parser.add_option(u"--s3-use-server-side-encryption", action=u"store_true", dest=u"s3_use_sse")
565
566    # Options to allow use of server side KMS encryption
567    parser.add_option(u"--s3-use-server-side-kms-encryption", action=u"store_true", dest=u"s3_use_sse_kms")
568    parser.add_option(u"--s3-kms-key-id", action=u"store", dest=u"s3_kms_key_id")
569    parser.add_option(u"--s3-kms-grant", action=u"store", dest=u"s3_kms_grant")
570
571    # Options for specifying region and endpoint of s3
572    parser.add_option(u"--s3-region-name", type=u"string", dest=u"s3_region_name", action=u"store")
573    parser.add_option(u"--s3-endpoint-url", type=u"string", dest=u"s3_endpoint_url", action=u"store")
574
575    # Option to specify a Swift container storage policy.
576    parser.add_option(u"--swift-storage-policy", type=u"string", metavar=_(u"policy"))
577
578    # Number of the largest supported upload size where the Azure library makes only one put call.
579    # This is used to upload a single block if the content length is known and is less than this value.
580    # The default is 67108864 (64MiB)
581    parser.add_option(u"--azure-max-single-put-size", type=u"int", metavar=_(u"number"))
582
583    # Number for the block size used by the Azure library to upload a blob if the length is unknown
584    # or is larger than the value set by --azure-max-single-put-size".
585    # The maximum block size the service supports is 100MiB.
586    # The default is 4 * 1024 * 1024 (4MiB)
587    parser.add_option(u"--azure-max-block-size", type=u"int", metavar=_(u"number"))
588
589    # The number for the maximum parallel connections to use when the blob size exceeds 64MB.
590    # max_connections (int) - Maximum number of parallel connections to use when the blob size exceeds 64MB.
591    parser.add_option(u"--azure-max-connections", type=u"int", metavar=_(u"number"))
592
593    # Standard storage tier used for storring backup files (Hot|Cool|Archive).
594    parser.add_option(u"--azure-blob-tier", type=u"string", metavar=_(u"Hot|Cool|Archive"))
595
596    # scp command to use (ssh pexpect backend)
597    parser.add_option(u"--scp-command", metavar=_(u"command"))
598
599    # sftp command to use (ssh pexpect backend)
600    parser.add_option(u"--sftp-command", metavar=_(u"command"))
601
602    # allow the user to switch cloudfiles backend
603    parser.add_option(u"--cf-backend", metavar=_(u"pyrax|cloudfiles"))
604
605    # Option that causes the B2 backend to hide files instead of deleting them
606    parser.add_option(u"--b2-hide-files", action=u"store_true")
607
608    # If set, use short (< 30 char) filenames for all the remote files.
609    parser.add_option(u"--short-filenames", action=u"callback",
610                      dest=u"short_filenames",
611                      callback=lambda o, s, v, p: (setattr(p.values, o.dest, True),
612                                                   old_fn_deprecation(s)))
613
614    # TRANSL: Used in usage help to represent an ID for a GnuPG key. Example:
615    # --encrypt-key <gpg_key_id>
616    parser.add_option(u"--sign-key", type=u"string", metavar=_(u"gpg-key-id"),
617                      dest=u"", action=u"callback",
618                      callback=lambda o, s, v, p: set_sign_key(v))
619
620    # default to batch mode using public-key encryption
621    parser.add_option(u"--ssh-askpass", action=u"store_true")
622
623    # user added ssh options
624    parser.add_option(u"--ssh-options", action=u"extend", metavar=_(u"options"))
625
626    # user added ssl options (used by webdav, lftp backend)
627    parser.add_option(u"--ssl-cacert-file", metavar=_(u"pem formatted bundle of certificate authorities"))
628    parser.add_option(u"--ssl-cacert-path", metavar=_(u"path to a folder with certificate authority files"))
629    parser.add_option(u"--ssl-no-check-certificate", action=u"store_true")
630
631    # Working directory for the tempfile module. Defaults to /tmp on most systems.
632    parser.add_option(u"--tempdir", dest=u"temproot", type=u"file", metavar=_(u"path"))
633
634    # network timeout value
635    # TRANSL: Used in usage help. Example:
636    # --timeout <seconds>
637    parser.add_option(u"--timeout", type=u"int", metavar=_(u"seconds"))
638
639    # Character used like the ":" in time strings like
640    # 2002-08-06T04:22:00-07:00.  The colon isn't good for filenames on
641    # windows machines.
642    # TRANSL: abbreviation for "character" (noun)
643    parser.add_option(u"--time-separator", type=u"string", metavar=_(u"char"),
644                      action=u"callback",
645                      callback=lambda o, s, v, p: set_time_sep(v, s))
646
647    # Whether to specify --use-agent in GnuPG options
648    parser.add_option(u"--use-agent", action=u"store_true")
649
650    parser.add_option(u"--verbosity", u"-v", type=u"verbosity", metavar=u"[0-9]",
651                      dest=u"", action=u"callback",
652                      callback=lambda o, s, v, p: log.setverbosity(v))
653
654    parser.add_option(u"-V", u"--version", action=u"callback", callback=print_ver)
655
656    # option for mediafire to purge files on delete instead of sending to trash
657    parser.add_option(u"--mf-purge", action=u"store_true")
658
659    def set_mpsize(o, s, v, p):  # pylint: disable=unused-argument
660        setattr(p.values, u"mp_segment_size", v * 1024 * 1024)
661        setattr(p.values, u"mp_set", True)
662    parser.add_option(u"--mp-segment-size", type=u"int", action=u"callback", metavar=_(u"number"),
663                      callback=set_mpsize)
664    # volume size
665    # TRANSL: Used in usage help to represent a desired number of
666    # something. Example:
667    # --num-retries <number>
668
669    def set_volsize(o, s, v, p):  # pylint: disable=unused-argument
670        setattr(p.values, u"volsize", v * 1024 * 1024)
671        # if mp_size was not explicity given, default it to volsize
672        if not getattr(p.values, u'mp_set', False):
673            setattr(p.values, u"mp_segment_size", int(config.mp_factor * p.values.volsize))
674
675    parser.add_option(u"--volsize", type=u"int", action=u"callback", metavar=_(u"number"),
676                      callback=set_volsize)
677
678    # If set, collect only the file status, not the whole root.
679    parser.add_option(u"--file-changed", action=u"callback", type=u"file",
680                      metavar=_(u"path"), dest=u"file_changed",
681                      callback=lambda o, s, v, p: setattr(p.values, u"file_changed", v.rstrip(u'/')))
682
683    # delay time before next try after a failure of a backend operation
684    # TRANSL: Used in usage help. Example:
685    # --backend-retry-delay <seconds>
686    parser.add_option(u"--backend-retry-delay", type=u"int", metavar=_(u"seconds"))
687
688    # parse the options
689    (options, args) = parser.parse_args(arglist)
690
691    # Copy all arguments and their values to the config module.  Don't copy
692    # attributes that are 'hidden' (start with an underscore) or whose name is
693    # the empty string (used for arguments that don't directly store a value
694    # by using dest="")
695    for f in [x for x in dir(options) if x and not x.startswith(u"_")]:
696        v = getattr(options, f)
697        # Only set if v is not None because None is the default for all the
698        # variables.  If user didn't set it, we'll use defaults in config.py
699        if v is not None:
700            setattr(config, f, v)
701
702    # convert file_prefix* string
703    if sys.version_info.major >= 3:
704        if isinstance(config.file_prefix, str):
705            config.file_prefix = bytes(config.file_prefix, u'utf-8')
706        if isinstance(config.file_prefix_manifest, str):
707            config.file_prefix_manifest = bytes(config.file_prefix_manifest, u'utf-8')
708        if isinstance(config.file_prefix_archive, str):
709            config.file_prefix_archive = bytes(config.file_prefix_archive, u'utf-8')
710        if isinstance(config.file_prefix_signature, str):
711            config.file_prefix_signature = bytes(config.file_prefix_signature, u'utf-8')
712
713    # todo: this should really NOT be done here
714    socket.setdefaulttimeout(config.timeout)
715
716    # expect no cmd and two positional args
717    cmd = u""
718    num_expect = 2
719
720    # process first arg as command
721    if args:
722        cmd = args.pop(0)
723        possible = [c for c in commands if c.startswith(cmd)]
724        # no unique match, that's an error
725        if len(possible) > 1:
726            command_line_error(u"command '%s' not unique, could be %s" % (cmd, possible))
727        # only one match, that's a keeper
728        elif len(possible) == 1:
729            cmd = possible[0]
730        # no matches, assume no cmd
731        elif not possible:
732            args.insert(0, cmd)
733
734    if cmd == u"cleanup":
735        cleanup = True
736        num_expect = 1
737    elif cmd == u"collection-status":
738        collection_status = True
739        num_expect = 1
740    elif cmd == u"full":
741        full_backup = True
742        num_expect = 2
743    elif cmd == u"incremental":
744        config.incremental = True
745        num_expect = 2
746    elif cmd == u"list-current-files":
747        list_current = True
748        num_expect = 1
749    elif cmd == u"remove-older-than":
750        try:
751            arg = args.pop(0)
752        except Exception:
753            command_line_error(u"Missing time string for remove-older-than")
754        config.remove_time = dup_time.genstrtotime(arg)
755        num_expect = 1
756    elif cmd == u"remove-all-but-n-full" or cmd == u"remove-all-inc-of-but-n-full":
757        if cmd == u"remove-all-but-n-full":
758            config.remove_all_but_n_full_mode = True
759        if cmd == u"remove-all-inc-of-but-n-full":
760            config.remove_all_inc_of_but_n_full_mode = True
761        try:
762            arg = args.pop(0)
763        except Exception:
764            command_line_error(u"Missing count for " + cmd)
765        config.keep_chains = int(arg)
766        if not config.keep_chains > 0:
767            command_line_error(cmd + u" count must be > 0")
768        num_expect = 1
769    elif cmd == u"verify":
770        verify = True
771    elif cmd == u"replicate":
772        replicate = True
773        num_expect = 2
774
775    if len(args) != num_expect:
776        command_line_error(u"Expected %d args, got %d" % (num_expect, len(args)))
777
778    # expand pathname args, but not URL
779    for loc in range(len(args)):
780        if isinstance(args[loc], bytes):
781            args[loc] = args[loc].decode(u'utf8')
782        if u'://' not in args[loc]:
783            args[loc] = expand_fn(args[loc])
784
785    # Note that ProcessCommandLine depends on us verifying the arg
786    # count here; do not remove without fixing it. We must make the
787    # checks here in order to make enough sense of args to identify
788    # the backend URL/lpath for args_to_path_backend().
789    if len(args) < 1:
790        command_line_error(u"Too few arguments")
791    elif len(args) == 1:
792        backend_url = args[0]
793    elif len(args) == 2:
794        if replicate:
795            if not backend.is_backend_url(args[0]) or not backend.is_backend_url(args[1]):
796                command_line_error(u"Two URLs expected for replicate.")
797            src_backend_url, backend_url = args[0], args[1]
798        else:
799            lpath, backend_url = args_to_path_backend(args[0], args[1])
800    else:
801        command_line_error(u"Too many arguments")
802
803    if config.backup_name is None:
804        config.backup_name = generate_default_backup_name(backend_url)
805
806    # set and expand archive dir
807    set_archive_dir(expand_archive_dir(config.archive_dir,
808                                       config.backup_name))
809
810    log.Info(_(u"Using archive dir: %s") % (config.archive_dir_path.uc_name,))
811    log.Info(_(u"Using backup name: %s") % (config.backup_name,))
812
813    return args
814
815
816def command_line_error(message):
817    u"""Indicate a command line error and exit"""
818    log.FatalError(_(u"Command line error: %s") % (message,) + u"\n" +
819                   _(u"Enter 'duplicity --help' for help screen."),
820                   log.ErrorCode.command_line)
821
822
823def usage():
824    u"""Returns terse usage info. The code is broken down into pieces for ease of
825    translation maintenance. Any comments that look extraneous or redundant should
826    be assumed to be for the benefit of translators, since they can get each string
827    (paired with its preceding comment, if any) independently of the others."""
828
829    trans = {
830        # TRANSL: Used in usage help to represent a Unix-style path name. Example:
831        # rsync://user[:password]@other_host[:port]//absolute_path
832        u'absolute_path': _(u"absolute_path"),
833
834        # TRANSL: Used in usage help. Example:
835        # tahoe://alias/some_dir
836        u'alias': _(u"alias"),
837
838        # TRANSL: Used in help to represent a "bucket name" for Amazon Web
839        # Services' Simple Storage Service (S3). Example:
840        # s3://other.host/bucket_name[/prefix]
841        u'bucket_name': _(u"bucket_name"),
842
843        # TRANSL: abbreviation for "character" (noun)
844        u'char': _(u"char"),
845
846        # TRANSL: noun
847        u'command': _(u"command"),
848
849        # TRANSL: Used in usage help to represent the name of a container in
850        # Amazon Web Services' Cloudfront. Example:
851        # cf+http://container_name
852        u'container_name': _(u"container_name"),
853
854        # TRANSL: noun
855        u'count': _(u"count"),
856
857        # TRANSL: Used in usage help to represent the name of a file directory
858        u'directory': _(u"directory"),
859
860        # TRANSL: Used in usage help to represent the name of a file. Example:
861        # --log-file <filename>
862        u'filename': _(u"filename"),
863
864        # TRANSL: Used in usage help to represent an ID for a GnuPG key. Example:
865        # --encrypt-key <gpg_key_id>
866        u'gpg_key_id': _(u"gpg-key-id"),
867
868        # TRANSL: Used in usage help, e.g. to represent the name of a code
869        # module. Example:
870        # rsync://user[:password]@other.host[:port]::/module/some_dir
871        u'module': _(u"module"),
872
873        # TRANSL: Used in usage help to represent a desired number of
874        # something. Example:
875        # --num-retries <number>
876        u'number': _(u"number"),
877
878        # TRANSL: Used in usage help. (Should be consistent with the "Options:"
879        # header.) Example:
880        # duplicity [full|incremental] [options] source_dir target_url
881        u'options': _(u"options"),
882
883        # TRANSL: Used in usage help to represent an internet hostname. Example:
884        # ftp://user[:password]@other.host[:port]/some_dir
885        u'other_host': _(u"other.host"),
886
887        # TRANSL: Used in usage help. Example:
888        # ftp://user[:password]@other.host[:port]/some_dir
889        u'password': _(u"password"),
890
891        # TRANSL: Used in usage help to represent a Unix-style path name. Example:
892        # --archive-dir <path>
893        u'path': _(u"path"),
894
895        # TRANSL: Used in usage help to represent a TCP port number. Example:
896        # ftp://user[:password]@other.host[:port]/some_dir
897        u'port': _(u"port"),
898
899        # TRANSL: Used in usage help. This represents a string to be used as a
900        # prefix to names for backup files created by Duplicity. Example:
901        # s3://other.host/bucket_name[/prefix]
902        u'prefix': _(u"prefix"),
903
904        # TRANSL: Used in usage help to represent a Unix-style path name. Example:
905        # rsync://user[:password]@other.host[:port]/relative_path
906        u'relative_path': _(u"relative_path"),
907
908        # TRANSL: Used in usage help. Example:
909        # --timeout <seconds>
910        u'seconds': _(u"seconds"),
911
912        # TRANSL: Used in usage help to represent a "glob" style pattern for
913        # matching one or more files, as described in the documentation.
914        # Example:
915        # --exclude <shell_pattern>
916        u'shell_pattern': _(u"shell_pattern"),
917
918        # TRANSL: Used in usage help to represent the name of a single file
919        # directory or a Unix-style path to a directory. Example:
920        # file:///some_dir
921        u'some_dir': _(u"some_dir"),
922
923        # TRANSL: Used in usage help to represent the name of a single file
924        # directory or a Unix-style path to a directory where files will be
925        # coming FROM. Example:
926        # duplicity [full|incremental] [options] source_dir target_url
927        u'source_dir': _(u"source_dir"),
928
929        # TRANSL: Used in usage help to represent a URL files will be coming
930        # FROM. Example:
931        # duplicity [restore] [options] source_url target_dir
932        u'source_url': _(u"source_url"),
933
934        # TRANSL: Used in usage help to represent the name of a single file
935        # directory or a Unix-style path to a directory. where files will be
936        # going TO. Example:
937        # duplicity [restore] [options] source_url target_dir
938        u'target_dir': _(u"target_dir"),
939
940        # TRANSL: Used in usage help to represent a URL files will be going TO.
941        # Example:
942        # duplicity [full|incremental] [options] source_dir target_url
943        u'target_url': _(u"target_url"),
944
945        # TRANSL: Used in usage help to represent a time spec for a previous
946        # point in time, as described in the documentation. Example:
947        # duplicity remove-older-than time [options] target_url
948        u'time': _(u"time"),
949
950        # TRANSL: Used in usage help to represent a user name (i.e. login).
951        # Example:
952        # ftp://user[:password]@other.host[:port]/some_dir
953        u'user': _(u"user"),
954
955        # TRANSL: account id for b2. Example: b2://account_id@bucket/
956        u'account_id': _(u"account_id"),
957
958        # TRANSL: application_key for b2.
959        # Example: b2://account_id:application_key@bucket/
960        u'application_key': _(u"application_key"),
961
962        # TRANSL: remote name for rclone.
963        # Example: rclone://remote:/some_dir
964        u'remote': _(u"remote"),
965    }
966
967    # TRANSL: Header in usage help
968    msg = u"""
969  duplicity [full|incremental] [%(options)s] %(source_dir)s %(target_url)s
970  duplicity [restore] [%(options)s] %(source_url)s %(target_dir)s
971  duplicity verify [%(options)s] %(source_url)s %(target_dir)s
972  duplicity collection-status [%(options)s] %(target_url)s
973  duplicity list-current-files [%(options)s] %(target_url)s
974  duplicity cleanup [%(options)s] %(target_url)s
975  duplicity remove-older-than %(time)s [%(options)s] %(target_url)s
976  duplicity remove-all-but-n-full %(count)s [%(options)s] %(target_url)s
977  duplicity remove-all-inc-of-but-n-full %(count)s [%(options)s] %(target_url)s
978  duplicity replicate %(source_url)s %(target_url)s
979
980""" % trans
981
982    # TRANSL: Header in usage help
983    msg = msg + _(u"Backends and their URL formats:") + u"""
984  azure://%(container_name)s
985  b2://%(account_id)s[:%(application_key)s]@%(bucket_name)s/[%(some_dir)s/]
986  boto3+s3://%(bucket_name)s[/%(prefix)s]
987  cf+http://%(container_name)s
988  dpbx:///%(some_dir)s
989  file:///%(some_dir)s
990  ftp://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]/%(some_dir)s
991  ftps://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]/%(some_dir)s
992  gdocs://%(user)s[:%(password)s]@%(other_host)s/%(some_dir)s
993  for gdrive:// a <service-account-url> like the following is required
994        <serviceaccount-name>@<serviceaccount-name>.iam.gserviceaccount.com
995  gdrive://<service-account-url>/target-folder/?driveID=<SHARED DRIVE ID> (for GOOGLE Shared Drive)
996  gdrive://<service-account-url>/target-folder/?myDriveFolderID=<google-myDrive-folder-id> (for GOOGLE MyDrive)
997  hsi://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]/%(some_dir)s
998  imap://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]/%(some_dir)s
999  mega://%(user)s[:%(password)s]@%(other_host)s/%(some_dir)s
1000  megav2://%(user)s[:%(password)s]@%(other_host)s/%(some_dir)s
1001  mf://%(user)s[:%(password)s]@%(other_host)s/%(some_dir)s
1002  onedrive://%(some_dir)s
1003  pca://%(container_name)s
1004  pydrive://%(user)s@%(other_host)s/%(some_dir)s
1005  rclone://%(remote)s:/%(some_dir)s
1006  rsync://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]/%(relative_path)s
1007  rsync://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]//%(absolute_path)s
1008  rsync://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]::/%(module)s/%(some_dir)s
1009  s3+http://%(bucket_name)s[/%(prefix)s]
1010  s3://%(other_host)s[:%(port)s]/%(bucket_name)s[/%(prefix)s]
1011  scp://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]/%(some_dir)s
1012  ssh://%(user)s[:%(password)s]@%(other_host)s[:%(port)s]/%(some_dir)s
1013  swift://%(container_name)s
1014  tahoe://%(alias)s/%(directory)s
1015  webdav://%(user)s[:%(password)s]@%(other_host)s/%(some_dir)s
1016  webdavs://%(user)s[:%(password)s]@%(other_host)s/%(some_dir)s
1017
1018""" % trans
1019
1020    # TRANSL: Header in usage help
1021    msg = msg + _(u"Commands:") + u"""
1022  cleanup <%(target_url)s>
1023  collection-status <%(target_url)s>
1024  full <%(source_dir)s> <%(target_url)s>
1025  incr <%(source_dir)s> <%(target_url)s>
1026  list-current-files <%(target_url)s>
1027  remove-all-but-n-full <%(count)s> <%(target_url)s>
1028  remove-all-inc-of-but-n-full <%(count)s> <%(target_url)s>
1029  remove-older-than <%(time)s> <%(target_url)s>
1030  replicate <%(source_url)s> <%(target_url)s>
1031  restore <%(source_url)s> <%(target_dir)s>
1032  verify <%(target_url)s> <%(source_dir)s>
1033
1034""" % trans
1035
1036    return msg
1037
1038
1039def set_archive_dir(dirstring):
1040    u"""Check archive dir and set global"""
1041    if not os.path.exists(dirstring):
1042        try:
1043            os.makedirs(dirstring)
1044        except Exception:
1045            pass
1046    archive_dir_path = path.Path(dirstring)
1047    if not archive_dir_path.isdir():
1048        log.FatalError(_(u"Specified archive directory '%s' does not exist, "
1049                         u"or is not a directory") % (archive_dir_path.uc_name,),
1050                       log.ErrorCode.bad_archive_dir)
1051    config.archive_dir_path = archive_dir_path
1052
1053
1054def set_sign_key(sign_key):
1055    u"""Set config.sign_key assuming proper key given"""
1056    if not re.search(u"^(0x)?([0-9A-Fa-f]{8}|[0-9A-Fa-f]{16}|[0-9A-Fa-f]{40})$", sign_key):
1057        log.FatalError(_(u"Sign key should be an 8, 16 alt. 40 character hex string, like "
1058                         u"'AA0E73D2'.\nReceived '%s' instead.") % (sign_key,),
1059                       log.ErrorCode.bad_sign_key)
1060    config.gpg_profile.sign_key = sign_key
1061
1062
1063def set_selection():
1064    u"""Return selection iter starting at filename with arguments applied"""
1065    global select_opts, select_files
1066    sel = selection.Select(config.local_path)
1067    sel.ParseArgs(select_opts, select_files)
1068    config.select = sel.set_iter()
1069
1070
1071def args_to_path_backend(arg1, arg2):
1072    u"""
1073    Given exactly two arguments, arg1 and arg2, figure out which one
1074    is the backend URL and which one is a local path, and return
1075    (local, backend).
1076    """
1077    arg1_is_backend, arg2_is_backend = backend.is_backend_url(arg1), backend.is_backend_url(arg2)
1078
1079    if not arg1_is_backend and not arg2_is_backend:
1080        command_line_error(u"""\
1081One of the arguments must be an URL.  Examples of URL strings are
1082"scp://user@host.net:1234/path" and "file:///usr/local".  See the man
1083page for more information.""")
1084    if arg1_is_backend and arg2_is_backend:
1085        command_line_error(u"Two URLs specified.  "
1086                           u"One argument should be a path.")
1087    if arg1_is_backend:
1088        return (arg2, arg1)
1089    elif arg2_is_backend:
1090        return (arg1, arg2)
1091    else:
1092        raise AssertionError(u'should not be reached')
1093
1094
1095def set_backend(arg1, arg2):
1096    u"""Figure out which arg is url, set backend
1097
1098    Return value is pair (path_first, path) where is_first is true iff
1099    path made from arg1.
1100
1101    """
1102    path, bend = args_to_path_backend(arg1, arg2)
1103
1104    config.backend = backend.get_backend(bend)
1105
1106    if path == arg2:
1107        return (None, arg2)  # False?
1108    else:
1109        return (1, arg1)  # True?
1110
1111
1112def process_local_dir(action, local_pathname):
1113    u"""Check local directory, set config.local_path"""
1114    local_path = path.Path(path.Path(local_pathname).get_canonical())
1115    if action == u"restore":
1116        if (local_path.exists() and not local_path.isemptydir()) and not config.force:
1117            log.FatalError(_(u"Restore destination directory %s already "
1118                             u"exists.\nWill not overwrite.") % (local_path.uc_name,),
1119                           log.ErrorCode.restore_dir_exists)
1120    elif action == u"verify":
1121        if not local_path.exists():
1122            log.FatalError(_(u"Verify directory %s does not exist") %
1123                           (local_path.uc_name,),
1124                           log.ErrorCode.verify_dir_doesnt_exist)
1125    else:
1126        assert action == u"full" or action == u"inc"
1127        if not local_path.exists():
1128            log.FatalError(_(u"Backup source directory %s does not exist.")
1129                           % (local_path.uc_name,),
1130                           log.ErrorCode.backup_dir_doesnt_exist)
1131
1132    config.local_path = local_path
1133
1134
1135def check_consistency(action):
1136    u"""Final consistency check, see if something wrong with command line"""
1137    global full_backup, select_opts, list_current, collection_status, cleanup, replicate
1138
1139    def assert_only_one(arglist):
1140        u"""Raises error if two or more of the elements of arglist are true"""
1141        n = 0
1142        for m in arglist:
1143            if m:
1144                n += 1
1145        assert n <= 1, u"Invalid syntax, two conflicting modes specified"
1146
1147    if action in [u"list-current", u"collection-status",
1148                  u"cleanup", u"remove-old", u"remove-all-but-n-full", u"remove-all-inc-of-but-n-full", u"replicate"]:
1149        assert_only_one([list_current, collection_status, cleanup, replicate,
1150                         config.remove_time is not None])
1151    elif action == u"restore" or action == u"verify":
1152        if full_backup:
1153            command_line_error(u"--full option cannot be used when "
1154                               u"restoring or verifying")
1155        elif config.incremental:
1156            command_line_error(u"--incremental option cannot be used when "
1157                               u"restoring or verifying")
1158        if select_opts and action == u"restore":
1159            log.Warn(_(u"Command line warning: %s") % _(u"Selection options --exclude/--include\n"
1160                                                        u"currently work only when backing up,"
1161                                                        u"not restoring."))
1162    else:
1163        assert action == u"inc" or action == u"full"
1164        if verify:
1165            command_line_error(u"--verify option cannot be used "
1166                               u"when backing up")
1167        if config.restore_dir:
1168            command_line_error(u"restore option incompatible with %s backup"
1169                               % (action,))
1170        if sum([config.s3_use_rrs, config.s3_use_ia, config.s3_use_onezone_ia]) >= 2:
1171            command_line_error(u"only one of --s3-use-rrs, --s3-use-ia, and --s3-use-onezone-ia may be used")
1172
1173
1174def ProcessCommandLine(cmdline_list):
1175    u"""Process command line, set config, return action
1176
1177    action will be "list-current", "collection-status", "cleanup",
1178    "remove-old", "restore", "verify", "full", or "inc".
1179
1180    """
1181    # build initial gpg_profile
1182    config.gpg_profile = gpg.GPGProfile()
1183
1184    # parse command line
1185    args = parse_cmdline_options(cmdline_list)
1186
1187    # if we get a different gpg-binary from the commandline then redo gpg_profile
1188    if config.gpg_binary is not None:
1189        src = config.gpg_profile
1190        config.gpg_profile = gpg.GPGProfile(
1191            passphrase=src.passphrase,
1192            sign_key=src.sign_key,
1193            recipients=src.recipients,
1194            hidden_recipients=src.hidden_recipients)
1195    log.Debug(_(u"GPG binary is %s, version %s") %
1196              ((config.gpg_binary or u'gpg'), config.gpg_profile.gpg_version))
1197
1198    # we can now try to import all the backends
1199    backend.import_backends()
1200
1201    # parse_cmdline_options already verified that we got exactly 1 or 2
1202    # non-options arguments
1203    assert len(args) >= 1 and len(args) <= 2, u"arg count should have been checked already"
1204
1205    if len(args) == 1:
1206        if list_current:
1207            action = u"list-current"
1208        elif collection_status:
1209            action = u"collection-status"
1210        elif cleanup:
1211            action = u"cleanup"
1212        elif config.remove_time is not None:
1213            action = u"remove-old"
1214        elif config.remove_all_but_n_full_mode:
1215            action = u"remove-all-but-n-full"
1216        elif config.remove_all_inc_of_but_n_full_mode:
1217            action = u"remove-all-inc-of-but-n-full"
1218        else:
1219            command_line_error(u"Too few arguments")
1220        config.backend = backend.get_backend(args[0])
1221        if not config.backend:
1222            log.FatalError(_(u"""Bad URL '%s'.
1223Examples of URL strings are "scp://user@host.net:1234/path" and
1224"file:///usr/local".  See the man page for more information.""") % (args[0],),
1225                           log.ErrorCode.bad_url)
1226    elif len(args) == 2:
1227        if replicate:
1228            config.src_backend = backend.get_backend(args[0])
1229            config.backend = backend.get_backend(args[1])
1230            action = u"replicate"
1231        else:
1232            # Figure out whether backup or restore
1233            backup, local_pathname = set_backend(args[0], args[1])
1234            if backup:
1235                if full_backup:
1236                    action = u"full"
1237                else:
1238                    action = u"inc"
1239            else:
1240                if verify:
1241                    action = u"verify"
1242                else:
1243                    action = u"restore"
1244
1245            process_local_dir(action, local_pathname)
1246            if action in [u'full', u'inc', u'verify']:
1247                set_selection()
1248    elif len(args) > 2:
1249        raise AssertionError(u"this code should not be reachable")
1250
1251    check_consistency(action)
1252    log.Info(_(u"Main action: ") + action)
1253    return action
1254