15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""Utility functions for sdk_update.py and sdk_update_main.py."""
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import errno
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import logging
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import shutil
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import subprocess
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import sys
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import time
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Error(Exception):
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Generic error/exception for sdk_update module"""
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  pass
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def MakeDirs(directory):
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if not os.path.exists(directory):
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    logging.info('Making directory %s' % (directory,))
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    os.makedirs(directory)
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def RemoveDir(outdir):
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Removes the given directory
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  On Unix systems, this just runs shutil.rmtree, but on Windows, this doesn't
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  work when the directory contains junctions (as does our SDK installer).
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Therefore, on Windows, it runs rmdir /S /Q as a shell command.  This always
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  does the right thing on Windows. If the directory already didn't exist,
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  RemoveDir will return successfully without taking any action.
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Args:
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    outdir: The directory to delete
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Raises:
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Error - If this operation fails for any reason.
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  max_tries = 5
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  last_exception = None
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for num_tries in xrange(max_tries):
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    try:
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      shutil.rmtree(outdir)
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    except OSError as e:
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if not os.path.exists(outdir):
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # The directory can't be removed because it doesn't exist.
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      last_exception = e
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # On Windows this could be an issue with junctions, so try again with
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # rmdir.
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if sys.platform == 'win32':
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      try:
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        cmd = ['rmdir', '/S', '/Q', outdir]
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        process = subprocess.Popen(cmd, stderr=subprocess.PIPE, shell=True)
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        _, stderr = process.communicate()
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if process.returncode != 0:
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          raise Error('\"%s\" failed with code %d. Output:\n  %s' % (
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            ' '.join(cmd), process.returncode, stderr))
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Ignore failures, we'll just try again.
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      except subprocess.CalledProcessError as e:
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # CalledProcessError has no error message, generate one.
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        last_exception = Error('\"%s\" failed with code %d.' % (
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          ' '.join(e.cmd), e.returncode))
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      except Error as e:
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        last_exception = e
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Didn't work, sleep and try again.
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    time.sleep(num_tries + 1)
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Failed.
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  raise Error('Unable to remove directory "%s"\n  %s' % (outdir,
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                                         last_exception))
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def RenameDir(srcdir, destdir):
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Renames srcdir to destdir. Removes destdir before doing the
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     rename if it already exists."""
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  max_tries = 5
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  num_tries = 0
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for num_tries in xrange(max_tries):
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    try:
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      RemoveDir(destdir)
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      shutil.move(srcdir, destdir)
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    except OSError as err:
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if err.errno != errno.EACCES:
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        raise err
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # If we are here, we didn't exit due to raised exception, so we are
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # handling a Windows flaky access error.  Sleep one second and try
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # again.
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      time.sleep(num_tries + 1)
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # end of while loop -- could not RenameDir
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  raise Error('Could not RenameDir %s => %s after %d tries.\n'
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)              'Please check that no shells or applications '
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)              'are accessing files in %s.'
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)              % (srcdir, destdir, num_tries + 1, destdir))
106