blob: f48cbc8fbdb711dfc34f7ffd0f626011b5054015 [file] [log] [blame]
# Copyright 2016 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A system module providing access to permanet storage on DUT"""
import posixpath
from six.moves import xrange
import factory_common # pylint: disable=unused-import
from cros.factory.device import types
class Path(types.DeviceComponent):
"""Provies operations on pathnames, similar to os.path.
If the operation doesn't need to access DUT, e.g. join and split,
we just use the posixpath module. These functions are listed in
Path.DELEGATED_ATTRIBUTES.
Not all functions in posixpath are ported, unsupported methods:
getmtime, getatime, getctime, ismount, walk, expanduser,
expandvars, abspath, samefile, sameopenfile, samestat, relpath
"""
DELEGATED_ATTRIBUTES = [
'normcase', 'isabs', 'join', 'splitdrive', 'split', 'splitext',
'basename', 'dirname', 'commonprefix', 'normpath', 'curdir', 'pardir',
'extsep', 'sep', 'pathsep', 'defpath', 'altsep', 'devnull',
'supports_unicode_filenames'
]
def __getattr__(self, attr):
if attr in Path.DELEGATED_ATTRIBUTES:
return getattr(posixpath, attr)
type_name = type(self).__name__
if attr in posixpath.__all__:
raise NotImplementedError('%r is not implemented in %r' % (attr,
type_name))
raise AttributeError('%r has no attribute %r' % (type_name, attr))
def exists(self, path):
"""Tests whether a path exists. Returns False for broken symbolic links."""
return self._device.Call(['test', '-e', path]) == 0
def getsize(self, path):
if not self.exists(path):
raise OSError('No such file or directory: %r' % path)
cmd = ['stat', '--printf', '%F\n%s', path]
output = self._device.CheckOutput(cmd)
(file_type, size) = output.splitlines()
if file_type in ('block special file', 'block device'):
return int(self._device.CallOutput(['blockdev', '--getsize64', path]))
else:
# For other files, just returns what we got from stat
return int(size)
def isdir(self, path):
"""Returns True if path refers to an existing directory."""
return self._device.Call(['test', '-d', path]) == 0
def isfile(self, path):
"""Returns True if path refers to a regular file."""
return self._device.Call(['test', '-f', path]) == 0
def islink(self, path):
"""Returns True if path refers to a symbolic link."""
return self._device.Call(['test', '-h', path]) == 0
def lexists(self, path):
"""Tests whether a path exists. Returns True for broken symbolic links."""
return self.islink(path) or self.exists(path)
def realpath(self, path):
"""Returns a canonical path of given pathname.
Symbolic links are resolved if possible.
Example:
Consider the content of tmp directory looks like:
tmp
`-- a
`-- b
`-- c -> ../
>>> print self.realpath('/path/to/tmp/a/b/c/d/e')
/path/to/tmp/a/d/e
"""
# this should never failed, a path should always be returned
output = self._device.CallOutput(['realpath', '-m', path])
return output.splitlines()[0]
class AndroidPath(Path):
"""Provides operations on pathnames for Android systems."""
def realpath(self, path):
"""Returns a canonical path of given pathname."""
# The realpath command on Android device does not have '-m' options,
# which means all but the last component must exist.
# We implement this function by calling 'realpath' to resolve each
# component.
# If we can't resolve any of component, we will stop and append rest of
# components to current path, and normalize the path.
#
# For example:
# If we are going to resolve /path/to/some/file,
# we will do the following things:
# $ realpath /
# /
# $ realpath /path
# /path
# $ realpath /path/to # /path/to is a symbolic link to /other/path
# /other/path
# $ realpath /other/path/some
# /other/path/some
# $ realpath /other/path/some/file
# /other/path/some/file
# And the final result is '/other/path/some/file'
# Since in many cases, the 'path' actually exists, we can reduce average
# cost by checking the entire path first.
output = self._device.CallOutput(['realpath', path])
if output:
return output.strip()
if self.isabs(path):
bits = ['/'] + path.split('/')[1:]
else:
bits = ['./'] + path.split('/')
# This should never fail (we are asking realpath for '/' or './').
output = self._device.CheckOutput(['realpath', bits[0]])
current = output.strip()
# Try to append each subdirectory to current path.
for i in xrange(1, len(bits)):
if bits[i] == '.' or not bits[i]: # /./ or //
continue
if bits[i] == '..':
current = self.dirname(current)
continue
output = self._device.CallOutput(
['realpath', self.join(current, bits[i])])
if not output:
# We can't find realpath of 'current/bits[i]',
# it might be a symbolic loop or non-existing file,
# so we just append everything left and normalize the path.
return self.normpath(self.join(current, *bits[i:]))
else:
current = output.strip()
return current