Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / chromite / lib / osutils.py
index 7722360..80a551a 100644 (file)
@@ -4,17 +4,21 @@
 
 """Common file and os related utilities, including tempdir manipulation."""
 
+from __future__ import print_function
+
 import collections
+import contextlib
 import cStringIO
 import ctypes
 import ctypes.util
+import datetime
 import errno
 import logging
+import operator
 import os
 import pwd
 import re
 import shutil
-import signal
 import tempfile
 
 from chromite.lib import cros_build_lib
@@ -245,14 +249,6 @@ def Which(binary, path=None, mode=os.X_OK):
   return None
 
 
-def FindDepotTools():
-  """Returns the location of depot_tools if it is in $PATH."""
-  gclient_dir = os.path.dirname(Which('gclient.py', mode=os.F_OK) or '')
-  gitcl_dir = os.path.dirname(Which('git_cl.py', mode=os.F_OK) or '')
-  if gclient_dir and gclient_dir == gitcl_dir:
-    return gclient_dir
-
-
 def FindMissingBinaries(needed_tools):
   """Verifies that the required tools are present on the system.
 
@@ -625,7 +621,7 @@ def UmountDir(path, lazy=True, sudo=True, cleanup=True):
         # errno is bubbled up to us and we can detect it specifically without
         # potentially ignoring all other possible failures.
         return e.errno == errno.EBUSY
-    retry_util.GenericRetry(_retry, 5, RmDir, path, sudo=sudo, sleep=1)
+    retry_util.GenericRetry(_retry, 30, RmDir, path, sudo=sudo, sleep=60)
 
 
 def SetEnvironment(env):
@@ -672,29 +668,6 @@ def SourceEnvironment(script, whitelist, ifs=',', env=None, multiline=False):
                                          multiline=multiline)
 
 
-def StrSignal(sig_num):
-  """Convert a signal number to the symbolic name
-
-  Note: Some signal number have multiple names, so you might get
-  back a confusing result like "SIGIOT|SIGABRT".  Since they have
-  the same signal number, it's impossible to say which one is right.
-
-  Args:
-    sig_num: The numeric signal you wish to convert
-
-  Returns:
-    A string of the signal name(s)
-  """
-  sig_names = []
-  for name, num in signal.__dict__.iteritems():
-    if name.startswith('SIG') and num == sig_num:
-      sig_names.append(name)
-  if sig_names:
-    return '|'.join(sig_names)
-  else:
-    return 'SIG_%i' % sig_num
-
-
 def ListBlockDevices(device_path=None, in_bytes=False):
   """Lists all block devices.
 
@@ -717,7 +690,8 @@ def ListBlockDevices(device_path=None, in_bytes=False):
     cmd.append(device_path)
 
   cmd += ['--output', ','.join(keys)]
-  output = cros_build_lib.RunCommand(cmd, capture_output=True).output.strip()
+  output = cros_build_lib.RunCommand(
+      cmd, debug_level=logging.DEBUG, capture_output=True).output.strip()
   devices = []
   for line in output.splitlines():
     d = {}
@@ -772,3 +746,310 @@ def GetDeviceSize(device_path, in_bytes=False):
       return int(d.SIZE) if in_bytes else d.SIZE
 
   raise ValueError('No size info of %s is found.' % device_path)
+
+
+def GetExitStatus(status):
+  """Get the exit status of a child from an os.waitpid call.
+
+  Args:
+    status: The return value of os.waitpid(pid, 0)[1]
+
+  Returns:
+    The exit status of the process. If the process exited with a signal,
+    the return value will be 128 plus the signal number.
+  """
+  if os.WIFSIGNALED(status):
+    return 128 + os.WTERMSIG(status)
+  else:
+    assert os.WIFEXITED(status), 'Unexpected exit status %r' % status
+    return os.WEXITSTATUS(status)
+
+
+FileInfo = collections.namedtuple(
+    'FileInfo', ['path', 'owner', 'size', 'atime', 'mtime'])
+
+
+def StatFilesInDirectory(path, recursive=False, to_string=False):
+  """Stat files in the directory |path|.
+
+  Args:
+    path: Path to the target directory.
+    recursive: Whether to recurisvely list all files in |path|.
+    to_string: Whether to return a string containing the metadata of the
+      files.
+
+  Returns:
+    If |to_string| is False, returns a list of FileInfo objects. Otherwise,
+    returns a string of metadata of the files.
+  """
+  path = ExpandPath(path)
+  def ToFileInfo(path, stat):
+    return FileInfo(path,
+                    pwd.getpwuid(stat.st_uid)[0],
+                    stat.st_size,
+                    datetime.datetime.fromtimestamp(stat.st_atime),
+                    datetime.datetime.fromtimestamp(stat.st_mtime))
+
+  file_infos = []
+  for root, dirs, files in os.walk(path, topdown=True):
+    for filename in dirs + files:
+      filepath = os.path.join(root, filename)
+      file_infos.append(ToFileInfo(filepath, os.lstat(filepath)))
+
+    if not recursive:
+      # Process only the top-most directory.
+      break
+
+  if not to_string:
+    return file_infos
+
+  msg = 'Listing the content of %s' % path
+  msg_format = ('Path: {x.path}, Owner: {x.owner}, Size: {x.size} bytes, '
+                'Accessed: {x.atime}, Modified: {x.mtime}')
+  msg = '%s\n%s' % (msg,
+                    '\n'.join([msg_format.format(x=x) for x in file_infos]))
+  return msg
+
+
+def MountImagePartition(image_file, part_number, destination, gpt_table=None,
+                        sudo=True, makedirs=True, mount_opts=('ro', ),
+                        skip_mtab=False):
+  """Mount a |partition| from |image_file| to |destination|.
+
+  If there is a GPT table (GetImageDiskPartitionInfo), it will be used for
+  start offset and size of the selected partition. Otherwise, the GPT will
+  be read again from |image_file|. The GPT table MUST have unit of "B".
+
+  The mount option will be:
+
+    -o offset=XXX,sizelimit=YYY,(*mount_opts)
+
+  Args:
+    image_file: A path to the image file (chromiumos_base_image.bin).
+    part_number: A partition number.
+    destination: A path to the mount point.
+    gpt_table: A dictionary of PartitionInfo objects. See
+      cros_build_lib.GetImageDiskPartitionInfo.
+    sudo: Same as MountDir.
+    makedirs: Same as MountDir.
+    mount_opts: Same as MountDir.
+    skip_mtab: Same as MountDir.
+  """
+
+  if gpt_table is None:
+    gpt_table = cros_build_lib.GetImageDiskPartitionInfo(image_file, 'B',
+                                                         key_selector='number')
+
+  for _, part in gpt_table.items():
+    if part.number == part_number:
+      break
+  else:
+    part = None
+    raise ValueError('Partition number %d not found in the GPT %r.' %
+                     (part_number, gpt_table))
+
+  opts = ['loop', 'offset=%d' % part.start, 'sizelimit=%d' % part.size]
+  opts += mount_opts
+  MountDir(image_file, destination, sudo=sudo, makedirs=makedirs,
+           mount_opts=opts, skip_mtab=skip_mtab)
+
+
+@contextlib.contextmanager
+def ChdirContext(target_dir):
+  """A context manager to chdir() into |target_dir| and back out on exit.
+
+  Args:
+    target_dir: A target directory to chdir into.
+  """
+
+  cwd = os.getcwd()
+  os.chdir(target_dir)
+  try:
+    yield
+  finally:
+    os.chdir(cwd)
+
+
+class MountImageContext(object):
+  """A context manager to mount an image."""
+
+  def __init__(self, image_file, destination, part_selects=(1, 3)):
+    """Construct a context manager object to actually do the job.
+
+    Specified partitions will be mounted under |destination| according to the
+    pattern:
+
+      partition ---mount--> dir-<partition number>
+
+    Symlinks with labels "dir-<label>" will also be created in |destination| to
+    point to the mounted partitions. If there is a conflict in symlinks, the
+    first one wins.
+
+    The image is unmounted when this context manager exits.
+
+      with MountImageContext('build/images/wolf/latest', 'root_mount_point'):
+        # "dir-1", and "dir-3" will be mounted in root_mount_point
+        ...
+
+    Args:
+      image_file: A path to the image file.
+      destination: A directory in which all mount points and symlinks will be
+        created. This parameter is relative to the CWD at the time __init__ is
+        called.
+      part_selects: A list of partition numbers or labels to be mounted. If an
+        element is an integer, it is matched as partition number, otherwise
+        a partition label.
+    """
+    self._image_file = image_file
+    self._gpt_table = cros_build_lib.GetImageDiskPartitionInfo(
+        self._image_file, 'B', key_selector='number'
+    )
+    # Target dir is absolute path so that we do not have to worry about
+    # CWD being changed later.
+    self._target_dir = ExpandPath(destination)
+    self._part_selects = part_selects
+    self._mounted = set()
+    self._linked_labels = set()
+
+  def _GetMountPointAndSymlink(self, part):
+    """Given a PartitionInfo, return a tuple of mount point and symlink.
+
+    Args:
+      part: A PartitionInfo object.
+
+    Returns:
+      A tuple (mount_point, symlink).
+    """
+    dest_number = os.path.join(self._target_dir, 'dir-%d' % part.number)
+    dest_label = os.path.join(self._target_dir, 'dir-%s' % part.name)
+    return dest_number, dest_label
+
+  def _Mount(self, part):
+    """Mount the partition and create a symlink to the mount point.
+
+    The partition is mounted as "dir-partNumber", and the symlink "dir-label".
+    If "dir-label" already exists, no symlink is created.
+
+    Args:
+      part: A PartitionInfo object.
+
+    Raises:
+      ValueError if mount point already exists.
+    """
+    if part in self._mounted:
+      return
+
+    dest_number, dest_label = self._GetMountPointAndSymlink(part)
+    if os.path.exists(dest_number):
+      raise ValueError('Mount point %s already exists.' % dest_number)
+
+    MountImagePartition(self._image_file, part.number,
+                        dest_number, self._gpt_table)
+    self._mounted.add(part)
+
+    if not os.path.exists(dest_label):
+      os.symlink(os.path.basename(dest_number), dest_label)
+      self._linked_labels.add(dest_label)
+
+  def _Unmount(self, part):
+    """Unmount a partition that was mounted by _Mount."""
+    dest_number, dest_label = self._GetMountPointAndSymlink(part)
+    # Due to crosbug/358933, the RmDir call might fail. So we skip the cleanup.
+    UmountDir(dest_number, cleanup=False)
+    self._mounted.remove(part)
+
+    if dest_label in self._linked_labels:
+      SafeUnlink(dest_label)
+      self._linked_labels.remove(dest_label)
+
+  def _CleanUp(self):
+    """Unmount all mounted partitions."""
+    to_be_rmdir = []
+    for part in list(self._mounted):
+      self._Unmount(part)
+      dest_number, _ = self._GetMountPointAndSymlink(part)
+      to_be_rmdir.append(dest_number)
+    # Because _Unmount did not RmDir the mount points, we do that here.
+    for path in to_be_rmdir:
+      retry_util.RetryException(cros_build_lib.RunCommandError, 30,
+                                RmDir, path, sudo=True, sleep=60)
+
+  def __enter__(self):
+    for selector in self._part_selects:
+      matcher = operator.attrgetter('number')
+      if not isinstance(selector, int):
+        matcher = operator.attrgetter('name')
+      for _, part in self._gpt_table.items():
+        if matcher(part) == selector:
+          try:
+            self._Mount(part)
+          except:
+            self._CleanUp()
+            raise
+          break
+      else:
+        self._CleanUp()
+        raise ValueError('Partition %r not found in the GPT %r.' %
+                         (selector, self._gpt_table))
+
+    return self
+
+  def __exit__(self, exc_type, exc_value, traceback):
+    self._CleanUp()
+
+
+MountInfo = collections.namedtuple('MountInfo',
+    'source destination filesystem options')
+
+
+def IterateMountPoints(proc_file='/proc/mounts'):
+  """Iterate over all mounts as reported by "/proc/mounts".
+
+  Args:
+    proc_file: A path to a file whose content is similar to /proc/mounts.
+      Default to "/proc/mounts" itself.
+
+  Returns:
+    A generator that yields MountInfo objects.
+  """
+  with open(proc_file, 'rt') as f:
+    for line in f:
+      # Escape any \xxx to a char.
+      source, destination, filesystem, options, _, _ = [
+          re.sub(r'\\([0-7]{3})', lambda m: chr(int(m.group(1), 8)), x)
+          for x in line.split()
+      ]
+      mtab = MountInfo(source, destination, filesystem, options)
+      yield mtab
+
+
+def ResolveSymlink(file_name, root='/'):
+  """Resolve a symlink |file_name| relative to |root|.
+
+  For example:
+
+    ROOT-A/absolute_symlink --> /an/abs/path
+    ROOT-A/relative_symlink --> a/relative/path
+
+    absolute_symlink will be resolved to ROOT-A/an/abs/path
+    relative_symlink will be resolved to ROOT-A/a/relative/path
+
+  Args:
+    file_name: A path to the file.
+    root: A path to the root directory.
+
+  Returns:
+    |file_name| if |file_name| is not a symlink. Otherwise, the ultimate path
+    that |file_name| points to, with links resolved relative to |root|.
+  """
+  count = 0
+  while os.path.islink(file_name):
+    count += 1
+    if count > 128:
+      raise ValueError('Too many link levels for %s.' % file_name)
+    link = os.readlink(file_name)
+    if link.startswith('/'):
+      file_name = os.path.join(root, link[1:])
+    else:
+      file_name = os.path.join(os.path.dirname(file_name), link)
+  return file_name