Home | History | Annotate | Download | only in gslib
      1 #!/usr/bin/env python
      2 # -*- coding: utf-8 -*-
      3 # Copyright 2013 Google Inc. All Rights Reserved.
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #     http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 """Main module for Google Cloud Storage command line tool."""
     17 
     18 from __future__ import absolute_import
     19 
     20 import ConfigParser
     21 import datetime
     22 import errno
     23 import getopt
     24 import logging
     25 import os
     26 import re
     27 import signal
     28 import socket
     29 import sys
     30 import textwrap
     31 import traceback
     32 
     33 # Load the gsutil version number and append it to boto.UserAgent so the value is
     34 # set before anything instantiates boto. This has to run after THIRD_PARTY_DIR
     35 # is modified (done in gsutil.py) but before any calls are made that would cause
     36 # boto.s3.Connection to be loaded - otherwise the Connection class would end up
     37 # with a static reference to the pre-modified version of the UserAgent field,
     38 # so boto requests would not include gsutil/version# in the UserAgent string.
     39 import boto
     40 import gslib
     41 # TODO: gsutil-beta: Cloud SDK scans for this string and performs
     42 # substitution; ensure this works with both apitools and boto.
     43 boto.UserAgent += ' gsutil/%s (%s)' % (gslib.VERSION, sys.platform)
     44 if os.environ.get('CLOUDSDK_WRAPPER') == '1':
     45   boto.UserAgent += ' Cloud SDK Command Line Tool'
     46   if os.environ.get('CLOUDSDK_VERSION'):
     47     boto.UserAgent += ' %s' % os.environ.get('CLOUDSDK_VERSION')
     48 
     49 # pylint: disable=g-bad-import-order
     50 # pylint: disable=g-import-not-at-top
     51 import httplib2
     52 import oauth2client
     53 from gslib import wildcard_iterator
     54 from gslib.cloud_api import AccessDeniedException
     55 from gslib.cloud_api import ArgumentException
     56 from gslib.cloud_api import BadRequestException
     57 from gslib.cloud_api import ProjectIdException
     58 from gslib.cloud_api import ServiceException
     59 from gslib.command_runner import CommandRunner
     60 import gslib.exception
     61 from gslib.exception import CommandException
     62 import apitools.base.py.exceptions as apitools_exceptions
     63 from gslib.util import CreateLock
     64 from gslib.util import GetBotoConfigFileList
     65 from gslib.util import GetCertsFile
     66 from gslib.util import GetCleanupFiles
     67 from gslib.util import GsutilStreamHandler
     68 from gslib.util import ProxyInfoFromEnvironmentVar
     69 from gslib.sig_handling import GetCaughtSignals
     70 from gslib.sig_handling import InitializeSignalHandling
     71 from gslib.sig_handling import RegisterSignalHandler
     72 
     73 GSUTIL_CLIENT_ID = '909320924072.apps.googleusercontent.com'
     74 # Google OAuth2 clients always have a secret, even if the client is an installed
     75 # application/utility such as gsutil.  Of course, in such cases the "secret" is
     76 # actually publicly known; security depends entirely on the secrecy of refresh
     77 # tokens, which effectively become bearer tokens.
     78 GSUTIL_CLIENT_NOTSOSECRET = 'p3RlpR10xMFh9ZXBS/ZNLYUu'
     79 if os.environ.get('CLOUDSDK_WRAPPER') == '1':
     80   # Cloud SDK installs have a separate client ID / secret.
     81   GSUTIL_CLIENT_ID = '32555940559.apps.googleusercontent.com'
     82   GSUTIL_CLIENT_NOTSOSECRET = 'ZmssLNjJy2998hD4CTg2ejr2'
     83 
     84 CONFIG_KEYS_TO_REDACT = ['proxy', 'proxy_port', 'proxy_user', 'proxy_pass']
     85 
     86 
     87 # We don't use the oauth2 authentication plugin directly; importing it here
     88 # ensures that it's loaded and available by default when an operation requiring
     89 # authentication is performed.
     90 try:
     91   # pylint: disable=unused-import,g-import-not-at-top
     92   import gcs_oauth2_boto_plugin
     93 except ImportError:
     94   pass
     95 
     96 DEBUG_WARNING = """
     97 ***************************** WARNING *****************************
     98 *** You are running gsutil with debug output enabled.
     99 *** Be aware that debug output includes authentication credentials.
    100 *** Make sure to remove the value of the Authorization header for
    101 *** each HTTP request printed to the console prior to posting to
    102 *** a public medium such as a forum post or Stack Overflow.
    103 ***************************** WARNING *****************************
    104 """.lstrip()
    105 
    106 TRACE_WARNING = """
    107 ***************************** WARNING *****************************
    108 *** You are running gsutil with trace output enabled.
    109 *** Be aware that trace output includes authentication credentials
    110 *** and may include the contents of any files accessed during the trace.
    111 ***************************** WARNING *****************************
    112 """.lstrip()
    113 
    114 HTTP_WARNING = """
    115 ***************************** WARNING *****************************
    116 *** You are running gsutil with the "https_validate_certificates" config
    117 *** variable set to False. This option should always be set to True in
    118 *** production environments to protect against man-in-the-middle attacks,
    119 *** and leaking of user data.
    120 ***************************** WARNING *****************************
    121 """.lstrip()
    122 
    123 debug = 0
    124 test_exception_traces = False
    125 
    126 
    127 # pylint: disable=unused-argument
    128 def _CleanupSignalHandler(signal_num, cur_stack_frame):
    129   """Cleans up if process is killed with SIGINT, SIGQUIT or SIGTERM."""
    130   _Cleanup()
    131 
    132 
    133 def _Cleanup():
    134   for fname in GetCleanupFiles():
    135     try:
    136       os.unlink(fname)
    137     except:  # pylint: disable=bare-except
    138       pass
    139 
    140 
    141 def _OutputAndExit(message):
    142   """Outputs message and exists with code 1."""
    143   from gslib.util import UTF8  # pylint: disable=g-import-not-at-top
    144   if debug >= 2 or test_exception_traces:
    145     stack_trace = traceback.format_exc()
    146     err = ('DEBUG: Exception stack trace:\n    %s\n' %
    147            re.sub('\\n', '\n    ', stack_trace))
    148   else:
    149     err = '%s\n' % message
    150   try:
    151     sys.stderr.write(err.encode(UTF8))
    152   except UnicodeDecodeError:
    153     # Can happen when outputting invalid Unicode filenames.
    154     sys.stderr.write(err)
    155   sys.exit(1)
    156 
    157 
    158 def _OutputUsageAndExit(command_runner):
    159   command_runner.RunNamedCommand('help')
    160   sys.exit(1)
    161 
    162 
    163 class GsutilFormatter(logging.Formatter):
    164   """A logging.Formatter that supports logging microseconds (%f)."""
    165 
    166   def formatTime(self, record, datefmt=None):
    167     if datefmt:
    168       return datetime.datetime.fromtimestamp(record.created).strftime(datefmt)
    169 
    170     # Use default implementation if datefmt is not specified.
    171     return super(GsutilFormatter, self).formatTime(record, datefmt=datefmt)
    172 
    173 
    174 def _ConfigureLogging(level=logging.INFO):
    175   """Similar to logging.basicConfig() except it always adds a handler."""
    176   log_format = '%(levelname)s %(asctime)s %(filename)s] %(message)s'
    177   date_format = '%m%d %H:%M:%S.%f'
    178   formatter = GsutilFormatter(fmt=log_format, datefmt=date_format)
    179   handler = GsutilStreamHandler()
    180   handler.setFormatter(formatter)
    181   root_logger = logging.getLogger()
    182   root_logger.addHandler(handler)
    183   root_logger.setLevel(level)
    184 
    185 
    186 def main():
    187   InitializeSignalHandling()
    188   # Any modules used in initializing multiprocessing variables must be
    189   # imported after importing gslib.__main__.
    190   # pylint: disable=redefined-outer-name,g-import-not-at-top
    191   import gslib.boto_translation
    192   import gslib.command
    193   import gslib.util
    194   from gslib.util import BOTO_IS_SECURE
    195   from gslib.util import CERTIFICATE_VALIDATION_ENABLED
    196   # pylint: disable=unused-variable
    197   from gcs_oauth2_boto_plugin import oauth2_client
    198   from apitools.base.py import credentials_lib
    199   # pylint: enable=unused-variable
    200   from gslib.util import CheckMultiprocessingAvailableAndInit
    201   if CheckMultiprocessingAvailableAndInit().is_available:
    202     # These setup methods must be called, and, on Windows, they can only be
    203     # called from within an "if __name__ == '__main__':" block.
    204     gslib.command.InitializeMultiprocessingVariables()
    205     gslib.boto_translation.InitializeMultiprocessingVariables()
    206   else:
    207     gslib.command.InitializeThreadingVariables()
    208 
    209   # This needs to be done after gslib.util.InitializeMultiprocessingVariables(),
    210   # since otherwise we can't call gslib.util.CreateLock.
    211   try:
    212     # pylint: disable=unused-import,g-import-not-at-top
    213     import gcs_oauth2_boto_plugin
    214     gcs_oauth2_boto_plugin.oauth2_helper.SetFallbackClientIdAndSecret(
    215         GSUTIL_CLIENT_ID, GSUTIL_CLIENT_NOTSOSECRET)
    216     gcs_oauth2_boto_plugin.oauth2_helper.SetLock(CreateLock())
    217     credentials_lib.SetCredentialsCacheFileLock(CreateLock())
    218   except ImportError:
    219     pass
    220 
    221   global debug
    222   global test_exception_traces
    223 
    224   if not (2, 6) <= sys.version_info[:3] < (3,):
    225     raise gslib.exception.CommandException(
    226         'gsutil requires python 2.6 or 2.7.')
    227 
    228   # In gsutil 4.0 and beyond, we don't use the boto library for the JSON
    229   # API. However, we still store gsutil configuration data in the .boto
    230   # config file for compatibility with previous versions and user convenience.
    231   # Many users have a .boto configuration file from previous versions, and it
    232   # is useful to have all of the configuration for gsutil stored in one place.
    233   command_runner = CommandRunner()
    234   if not BOTO_IS_SECURE:
    235     raise CommandException('\n'.join(textwrap.wrap(
    236         'Your boto configuration has is_secure = False. Gsutil cannot be '
    237         'run this way, for security reasons.')))
    238 
    239   headers = {}
    240   parallel_operations = False
    241   quiet = False
    242   version = False
    243   debug = 0
    244   trace_token = None
    245   test_exception_traces = False
    246 
    247   # If user enters no commands just print the usage info.
    248   if len(sys.argv) == 1:
    249     sys.argv.append('help')
    250 
    251   # Change the default of the 'https_validate_certificates' boto option to
    252   # True (it is currently False in boto).
    253   if not boto.config.has_option('Boto', 'https_validate_certificates'):
    254     if not boto.config.has_section('Boto'):
    255       boto.config.add_section('Boto')
    256     boto.config.setbool('Boto', 'https_validate_certificates', True)
    257 
    258   gslib.util.certs_file_lock = CreateLock()
    259   for signal_num in GetCaughtSignals():
    260     RegisterSignalHandler(signal_num, _CleanupSignalHandler)
    261   GetCertsFile()
    262 
    263   try:
    264     try:
    265       opts, args = getopt.getopt(sys.argv[1:], 'dDvo:h:mq',
    266                                  ['debug', 'detailedDebug', 'version', 'option',
    267                                   'help', 'header', 'multithreaded', 'quiet',
    268                                   'testexceptiontraces', 'trace-token='])
    269     except getopt.GetoptError as e:
    270       _HandleCommandException(gslib.exception.CommandException(e.msg))
    271     for o, a in opts:
    272       if o in ('-d', '--debug'):
    273         # Passing debug=2 causes boto to include httplib header output.
    274         debug = 3
    275       elif o in ('-D', '--detailedDebug'):
    276         # We use debug level 3 to ask gsutil code to output more detailed
    277         # debug output. This is a bit of a hack since it overloads the same
    278         # flag that was originally implemented for boto use. And we use -DD
    279         # to ask for really detailed debugging (i.e., including HTTP payload).
    280         if debug == 3:
    281           debug = 4
    282         else:
    283           debug = 3
    284       elif o in ('-?', '--help'):
    285         _OutputUsageAndExit(command_runner)
    286       elif o in ('-h', '--header'):
    287         (hdr_name, _, hdr_val) = a.partition(':')
    288         if not hdr_name:
    289           _OutputUsageAndExit(command_runner)
    290         headers[hdr_name.lower()] = hdr_val
    291       elif o in ('-m', '--multithreaded'):
    292         parallel_operations = True
    293       elif o in ('-q', '--quiet'):
    294         quiet = True
    295       elif o in ('-v', '--version'):
    296         version = True
    297       elif o == '--trace-token':
    298         trace_token = a
    299       elif o == '--testexceptiontraces':  # Hidden flag for integration tests.
    300         test_exception_traces = True
    301       elif o in ('-o', '--option'):
    302         (opt_section_name, _, opt_value) = a.partition('=')
    303         if not opt_section_name:
    304           _OutputUsageAndExit(command_runner)
    305         (opt_section, _, opt_name) = opt_section_name.partition(':')
    306         if not opt_section or not opt_name:
    307           _OutputUsageAndExit(command_runner)
    308         if not boto.config.has_section(opt_section):
    309           boto.config.add_section(opt_section)
    310         boto.config.set(opt_section, opt_name, opt_value)
    311     httplib2.debuglevel = debug
    312     if trace_token:
    313       sys.stderr.write(TRACE_WARNING)
    314     if debug > 1:
    315       sys.stderr.write(DEBUG_WARNING)
    316     if debug >= 2:
    317       _ConfigureLogging(level=logging.DEBUG)
    318       command_runner.RunNamedCommand('ver', ['-l'])
    319       config_items = []
    320       try:
    321         config_items.extend(boto.config.items('Boto'))
    322         config_items.extend(boto.config.items('GSUtil'))
    323       except ConfigParser.NoSectionError:
    324         pass
    325       for i in xrange(len(config_items)):
    326         config_item_key = config_items[i][0]
    327         if config_item_key in CONFIG_KEYS_TO_REDACT:
    328           config_items[i] = (config_item_key, 'REDACTED')
    329       sys.stderr.write('Command being run: %s\n' % ' '.join(sys.argv))
    330       sys.stderr.write('config_file_list: %s\n' % GetBotoConfigFileList())
    331       sys.stderr.write('config: %s\n' % str(config_items))
    332     elif quiet:
    333       _ConfigureLogging(level=logging.WARNING)
    334     else:
    335       _ConfigureLogging(level=logging.INFO)
    336       # oauth2client uses info logging in places that would better
    337       # correspond to gsutil's debug logging (e.g., when refreshing
    338       # access tokens).
    339       oauth2client.client.logger.setLevel(logging.WARNING)
    340 
    341     if not CERTIFICATE_VALIDATION_ENABLED:
    342       sys.stderr.write(HTTP_WARNING)
    343 
    344     if version:
    345       command_name = 'version'
    346     elif not args:
    347       command_name = 'help'
    348     else:
    349       command_name = args[0]
    350 
    351     _CheckAndWarnForProxyDifferences()
    352 
    353     if os.environ.get('_ARGCOMPLETE', '0') == '1':
    354       return _PerformTabCompletion(command_runner)
    355 
    356     return _RunNamedCommandAndHandleExceptions(
    357         command_runner, command_name, args=args[1:], headers=headers,
    358         debug_level=debug, trace_token=trace_token,
    359         parallel_operations=parallel_operations)
    360   finally:
    361     _Cleanup()
    362 
    363 
    364 def _CheckAndWarnForProxyDifferences():
    365   # If there are both boto config and environment variable config present for
    366   # proxies, unset the environment variable and warn if it differs.
    367   boto_port = boto.config.getint('Boto', 'proxy_port', 0)
    368   if boto.config.get('Boto', 'proxy', None) or boto_port:
    369     for proxy_env_var in ['http_proxy', 'https_proxy', 'HTTPS_PROXY']:
    370       if proxy_env_var in os.environ and os.environ[proxy_env_var]:
    371         differing_values = []
    372         proxy_info = ProxyInfoFromEnvironmentVar(proxy_env_var)
    373         if proxy_info.proxy_host != boto.config.get('Boto', 'proxy', None):
    374           differing_values.append(
    375               'Boto proxy host: "%s" differs from %s proxy host: "%s"' %
    376               (boto.config.get('Boto', 'proxy', None), proxy_env_var,
    377                proxy_info.proxy_host))
    378         if (proxy_info.proxy_user !=
    379             boto.config.get('Boto', 'proxy_user', None)):
    380           differing_values.append(
    381               'Boto proxy user: "%s" differs from %s proxy user: "%s"' %
    382               (boto.config.get('Boto', 'proxy_user', None), proxy_env_var,
    383                proxy_info.proxy_user))
    384         if (proxy_info.proxy_pass !=
    385             boto.config.get('Boto', 'proxy_pass', None)):
    386           differing_values.append(
    387               'Boto proxy password differs from %s proxy password' %
    388               proxy_env_var)
    389         # Only compare ports if at least one is present, since the
    390         # boto logic for selecting default ports has not yet executed.
    391         if ((proxy_info.proxy_port or boto_port) and
    392             proxy_info.proxy_port != boto_port):
    393           differing_values.append(
    394               'Boto proxy port: "%s" differs from %s proxy port: "%s"' %
    395               (boto_port, proxy_env_var, proxy_info.proxy_port))
    396         if differing_values:
    397           sys.stderr.write('\n'.join(textwrap.wrap(
    398               'WARNING: Proxy configuration is present in both the %s '
    399               'environment variable and boto configuration, but '
    400               'configuration differs. boto configuration proxy values will '
    401               'be used. Differences detected:' % proxy_env_var)))
    402           sys.stderr.write('\n%s\n' % '\n'.join(differing_values))
    403         # Regardless of whether the proxy configuration values matched,
    404         # delete the environment variable so as not to confuse boto.
    405         del os.environ[proxy_env_var]
    406 
    407 
    408 def _HandleUnknownFailure(e):
    409   # Called if we fall through all known/handled exceptions.
    410   _OutputAndExit('Failure: %s.' % e)
    411 
    412 
    413 def _HandleCommandException(e):
    414   if e.informational:
    415     _OutputAndExit(e.reason)
    416   else:
    417     _OutputAndExit('CommandException: %s' % e.reason)
    418 
    419 
    420 # pylint: disable=unused-argument
    421 def _HandleControlC(signal_num, cur_stack_frame):
    422   """Called when user hits ^C.
    423 
    424   This function prints a brief message instead of the normal Python stack trace
    425   (unless -D option is used).
    426 
    427   Args:
    428     signal_num: Signal that was caught.
    429     cur_stack_frame: Unused.
    430   """
    431   if debug >= 2:
    432     stack_trace = ''.join(traceback.format_list(traceback.extract_stack()))
    433     _OutputAndExit(
    434         'DEBUG: Caught signal %d - Exception stack trace:\n'
    435         '    %s' % (signal_num, re.sub('\\n', '\n    ', stack_trace)))
    436   else:
    437     _OutputAndExit('Caught signal %d - exiting' % signal_num)
    438 
    439 
    440 def _HandleSigQuit(signal_num, cur_stack_frame):
    441   """Called when user hits ^\\, so we can force breakpoint a running gsutil."""
    442   import pdb  # pylint: disable=g-import-not-at-top
    443   pdb.set_trace()
    444 
    445 
    446 def _ConstructAccountProblemHelp(reason):
    447   """Constructs a help string for an access control error.
    448 
    449   Args:
    450     reason: e.reason string from caught exception.
    451 
    452   Returns:
    453     Contructed help text.
    454   """
    455   default_project_id = boto.config.get_value('GSUtil', 'default_project_id')
    456   # pylint: disable=line-too-long, g-inconsistent-quotes
    457   acct_help = (
    458       "Your request resulted in an AccountProblem (403) error. Usually this "
    459       "happens if you attempt to create a bucket without first having "
    460       "enabled billing for the project you are using. Please ensure billing is "
    461       "enabled for your project by following the instructions at "
    462       "`Google Developers Console<https://developers.google.com/console/help/billing>`. ")
    463   if default_project_id:
    464     acct_help += (
    465         "In the project overview, ensure that the Project Number listed for "
    466         "your project matches the project ID (%s) from your boto config file. "
    467         % default_project_id)
    468   acct_help += (
    469       "If the above doesn't resolve your AccountProblem, please send mail to "
    470       "gs-team (at] google.com requesting assistance, noting the exact command you "
    471       "ran, the fact that you received a 403 AccountProblem error, and your "
    472       "project ID. Please do not post your project ID on StackOverflow. "
    473       "Note: It's possible to use Google Cloud Storage without enabling "
    474       "billing if you're only listing or reading objects for which you're "
    475       "authorized, or if you're uploading objects to a bucket billed to a "
    476       "project that has billing enabled. But if you're attempting to create "
    477       "buckets or upload objects to a bucket owned by your own project, you "
    478       "must first enable billing for that project.")
    479   return acct_help
    480 
    481 
    482 def _CheckAndHandleCredentialException(e, args):
    483   # Provide detail to users who have no boto config file (who might previously
    484   # have been using gsutil only for accessing publicly readable buckets and
    485   # objects).
    486   # pylint: disable=g-import-not-at-top
    487   from gslib.util import HasConfiguredCredentials
    488   if (not HasConfiguredCredentials() and
    489       not boto.config.get_value('Tests', 'bypass_anonymous_access_warning',
    490                                 False)):
    491     # The check above allows tests to assert that we get a particular,
    492     # expected failure, rather than always encountering this error message
    493     # when there are no configured credentials. This allows tests to
    494     # simulate a second user without permissions, without actually requiring
    495     # two separate configured users.
    496     if os.environ.get('CLOUDSDK_WRAPPER') == '1':
    497       _OutputAndExit('\n'.join(textwrap.wrap(
    498           'You are attempting to access protected data with no configured '
    499           'credentials. Please visit '
    500           'https://cloud.google.com/console#/project and sign up for an '
    501           'account, and then run the "gcloud auth login" command to '
    502           'configure gsutil to use these credentials.')))
    503     else:
    504       _OutputAndExit('\n'.join(textwrap.wrap(
    505           'You are attempting to access protected data with no configured '
    506           'credentials. Please visit '
    507           'https://cloud.google.com/console#/project and sign up for an '
    508           'account, and then run the "gsutil config" command to configure '
    509           'gsutil to use these credentials.')))
    510   elif (e.reason and
    511         (e.reason == 'AccountProblem' or e.reason == 'Account disabled.' or
    512          'account for the specified project has been disabled' in e.reason)
    513         and ','.join(args).find('gs://') != -1):
    514     _OutputAndExit('\n'.join(textwrap.wrap(
    515         _ConstructAccountProblemHelp(e.reason))))
    516 
    517 
    518 def _RunNamedCommandAndHandleExceptions(command_runner, command_name, args=None,
    519                                         headers=None, debug_level=0,
    520                                         trace_token=None,
    521                                         parallel_operations=False):
    522   """Runs the command with the given command runner and arguments."""
    523   # pylint: disable=g-import-not-at-top
    524   from gslib.util import GetConfigFilePath
    525   from gslib.util import IS_WINDOWS
    526   from gslib.util import IsRunningInteractively
    527   try:
    528     # Catch ^C so we can print a brief message instead of the normal Python
    529     # stack trace. Register as a final signal handler because this handler kills
    530     # the main gsutil process (so it must run last).
    531     RegisterSignalHandler(signal.SIGINT, _HandleControlC, is_final_handler=True)
    532     # Catch ^\ so we can force a breakpoint in a running gsutil.
    533     if not IS_WINDOWS:
    534       RegisterSignalHandler(signal.SIGQUIT, _HandleSigQuit)
    535     return command_runner.RunNamedCommand(command_name, args, headers,
    536                                           debug_level, trace_token,
    537                                           parallel_operations)
    538   except AttributeError as e:
    539     if str(e).find('secret_access_key') != -1:
    540       _OutputAndExit('Missing credentials for the given URI(s). Does your '
    541                      'boto config file contain all needed credentials?')
    542     else:
    543       _OutputAndExit(str(e))
    544   except gslib.exception.CommandException as e:
    545     _HandleCommandException(e)
    546   except getopt.GetoptError as e:
    547     _HandleCommandException(gslib.exception.CommandException(e.msg))
    548   except boto.exception.InvalidUriError as e:
    549     _OutputAndExit('InvalidUriError: %s.' % e.message)
    550   except gslib.exception.InvalidUrlError as e:
    551     _OutputAndExit('InvalidUrlError: %s.' % e.message)
    552   except boto.auth_handler.NotReadyToAuthenticate:
    553     _OutputAndExit('NotReadyToAuthenticate')
    554   except OSError as e:
    555     _OutputAndExit('OSError: %s.' % e.strerror)
    556   except IOError as e:
    557     if (e.errno == errno.EPIPE or (IS_WINDOWS and e.errno == errno.EINVAL)
    558         and not IsRunningInteractively()):
    559       # If we get a pipe error, this just means that the pipe to stdout or
    560       # stderr is broken. This can happen if the user pipes gsutil to a command
    561       # that doesn't use the entire output stream. Instead of raising an error,
    562       # just swallow it up and exit cleanly.
    563       sys.exit(0)
    564     else:
    565       raise
    566   except wildcard_iterator.WildcardException as e:
    567     _OutputAndExit(e.reason)
    568   except ProjectIdException as e:
    569     _OutputAndExit(
    570         'You are attempting to perform an operation that requires a '
    571         'project id, with none configured. Please re-run '
    572         'gsutil config and make sure to follow the instructions for '
    573         'finding and entering your default project id.')
    574   except BadRequestException as e:
    575     if e.reason == 'MissingSecurityHeader':
    576       _CheckAndHandleCredentialException(e, args)
    577     _OutputAndExit(e)
    578   except AccessDeniedException as e:
    579     _CheckAndHandleCredentialException(e, args)
    580     _OutputAndExit(e)
    581   except ArgumentException as e:
    582     _OutputAndExit(e)
    583   except ServiceException as e:
    584     _OutputAndExit(e)
    585   except apitools_exceptions.HttpError as e:
    586     # These should usually be retried by the underlying implementation or
    587     # wrapped by CloudApi ServiceExceptions, but if we do get them,
    588     # print something useful.
    589     _OutputAndExit('HttpError: %s, %s' % (getattr(e.response, 'status', ''),
    590                                           e.content or ''))
    591   except socket.error as e:
    592     if e.args[0] == errno.EPIPE:
    593       # Retrying with a smaller file (per suggestion below) works because
    594       # the library code send loop (in boto/s3/key.py) can get through the
    595       # entire file and then request the HTTP response before the socket
    596       # gets closed and the response lost.
    597       _OutputAndExit(
    598           'Got a "Broken pipe" error. This can happen to clients using Python '
    599           '2.x, when the server sends an error response and then closes the '
    600           'socket (see http://bugs.python.org/issue5542). If you are trying to '
    601           'upload a large object you might retry with a small (say 200k) '
    602           'object, and see if you get a more specific error code.'
    603       )
    604     else:
    605       _HandleUnknownFailure(e)
    606   except Exception as e:
    607     # Check for two types of errors related to service accounts. These errors
    608     # appear to be the same except for their messages, but they are caused by
    609     # different problems and both have unhelpful error messages. Moreover,
    610     # the error type belongs to PyOpenSSL, which is not necessarily installed.
    611     if 'mac verify failure' in str(e):
    612       _OutputAndExit(
    613           'Encountered an error while refreshing access token. '
    614           'If you are using a service account,\nplease verify that the '
    615           'gs_service_key_file_password field in your config file,'
    616           '\n%s, is correct.' % GetConfigFilePath())
    617     elif 'asn1 encoding routines' in str(e):
    618       _OutputAndExit(
    619           'Encountered an error while refreshing access token. '
    620           'If you are using a service account,\nplease verify that the '
    621           'gs_service_key_file field in your config file,\n%s, is correct.'
    622           % GetConfigFilePath())
    623     _HandleUnknownFailure(e)
    624 
    625 
    626 def _PerformTabCompletion(command_runner):
    627   """Performs gsutil-specific tab completion for the shell."""
    628   # argparse and argcomplete are bundled with the Google Cloud SDK.
    629   # When gsutil is invoked from the Google Cloud SDK, both should be available.
    630   try:
    631     import argcomplete
    632     import argparse
    633   except ImportError as e:
    634     _OutputAndExit('A library required for performing tab completion was'
    635                    ' not found.\nCause: %s' % e)
    636   parser = argparse.ArgumentParser(add_help=False)
    637   subparsers = parser.add_subparsers()
    638   command_runner.ConfigureCommandArgumentParsers(subparsers)
    639   argcomplete.autocomplete(parser, exit_method=sys.exit)
    640 
    641   return 0
    642 
    643 if __name__ == '__main__':
    644   sys.exit(main())
    645