# Copyright 2009 Google Inc. Released under the GPL v2
"""
This file contains the implementation of a host object for the local machine.
"""
import distutils.core
import glob
import os
import platform
import shutil
import sys
import common
from autotest_lib.client.common_lib import hosts, error
from autotest_lib.client.bin import utils
class LocalHost(hosts.Host):
"""This class represents a host running locally on the host."""
def _initialize(self, hostname=None, bootloader=None, *args, **dargs):
super(LocalHost, self)._initialize(*args, **dargs)
# hostname will be an actual hostname when this client was created
# by an autoserv process
if not hostname:
hostname = platform.node()
self.hostname = hostname
self.bootloader = bootloader
self.tmp_dirs = []
def close(self):
"""Cleanup after we're done."""
for tmp_dir in self.tmp_dirs:
self.run('rm -rf "%s"' % (utils.sh_escape(tmp_dir)),
ignore_status=True)
def wait_up(self, timeout=None):
# a local host is always up
return True
def run(self, command, timeout=3600, ignore_status=False,
ignore_timeout=False,
stdout_tee=utils.TEE_TO_LOGS, stderr_tee=utils.TEE_TO_LOGS,
stdin=None, args=(), **kwargs):
"""
@see common_lib.hosts.Host.run()
"""
try:
return utils.run(
command, timeout=timeout, ignore_status=ignore_status,
ignore_timeout=ignore_timeout, stdout_tee=stdout_tee,
stderr_tee=stderr_tee, stdin=stdin, args=args)
except error.CmdTimeoutError as e:
# CmdTimeoutError is a subclass of CmdError, so must be caught first
new_error = error.AutotestHostRunTimeoutError(
e.command, e.result_obj, additional_text=e.additional_text)
raise error.AutotestHostRunTimeoutError, new_error, \
sys.exc_info()[2]
except error.CmdError as e:
new_error = error.AutotestHostRunCmdError(
e.command, e.result_obj, additional_text=e.additional_text)
raise error.AutotestHostRunCmdError, new_error, sys.exc_info()[2]
def list_files_glob(self, path_glob):
"""
Get a list of files on a remote host given a glob pattern path.
"""
return glob.glob(path_glob)
def symlink_closure(self, paths):
"""
Given a sequence of path strings, return the set of all paths that
can be reached from the initial set by following symlinks.
@param paths: sequence of path strings.
@return: a sequence of path strings that are all the unique paths that
can be reached from the given ones after following symlinks.
"""
paths = set(paths)
closure = set()
while paths:
path = paths.pop()
if not os.path.exists(path):
continue
closure.add(path)
if os.path.islink(path):
link_to = os.path.join(os.path.dirname(path),
os.readlink(path))
if link_to not in closure:
paths.add(link_to)
return closure
def _copy_file(self, source, dest, delete_dest=False, preserve_perm=False,
preserve_symlinks=False):
"""Copy files from source to dest, will be the base for {get,send}_file.
If source is a directory and ends with a trailing slash, only the
contents of the source directory will be copied to dest, otherwise
source itself will be copied under dest.
@param source: The file/directory on localhost to copy.
@param dest: The destination path on localhost to copy to.
@param delete_dest: A flag set to choose whether or not to delete
dest if it exists.
@param preserve_perm: Tells get_file() to try to preserve the sources
permissions on files and dirs.
@param preserve_symlinks: Try to preserve symlinks instead of
transforming them into files/dirs on copy.
"""
# We copy dest under source if either:
# 1. Source is a directory and doesn't end with /.
# 2. Source is a file and dest is a directory.
source_is_dir = os.path.isdir(source)
if ((source_is_dir and not source.endswith(os.sep)) or
(not source_is_dir and os.path.isdir(dest))):
dest = os.path.join(dest, os.path.basename(source))
if delete_dest and os.path.exists(dest):
# Check if it's a file or a dir and use proper remove method.
if os.path.isdir(dest):
shutil.rmtree(dest)
os.mkdir(dest)
else:
os.remove(dest)
if preserve_symlinks and os.path.islink(source):
os.symlink(os.readlink(source), dest)
# If source is a dir, use distutils.dir_util.copytree since
# shutil.copy_tree has weird limitations.
elif os.path.isdir(source):
distutils.dir_util.copy_tree(source, dest,
preserve_symlinks=preserve_symlinks,
preserve_mode=preserve_perm,
update=1)
else:
shutil.copyfile(source, dest)
if preserve_perm:
shutil.copymode(source, dest)
def get_file(self, source, dest, delete_dest=False, preserve_perm=True,
preserve_symlinks=False):
"""Copy files from source to dest.
If source is a directory and ends with a trailing slash, only the
contents of the source directory will be copied to dest, otherwise
source itself will be copied under dest. This is to match the
behavior of AbstractSSHHost.get_file().
@param source: The file/directory on localhost to copy.
@param dest: The destination path on localhost to copy to.
@param delete_dest: A flag set to choose whether or not to delete
dest if it exists.
@param preserve_perm: Tells get_file() to try to preserve the sources
permissions on files and dirs.
@param preserve_symlinks: Try to preserve symlinks instead of
transforming them into files/dirs on copy.
"""
self._copy_file(source, dest, delete_dest=delete_dest,
preserve_perm=preserve_perm,
preserve_symlinks=preserve_symlinks)
def send_file(self, source, dest, delete_dest=False,
preserve_symlinks=False, excludes=None):
"""Copy files from source to dest.
If source is a directory and ends with a trailing slash, only the
contents of the source directory will be copied to dest, otherwise
source itself will be copied under dest. This is to match the
behavior of AbstractSSHHost.send_file().
@param source: The file/directory on the drone to send to the device.
@param dest: The destination path on the device to copy to.
@param delete_dest: A flag set to choose whether or not to delete
dest on the device if it exists.
@param preserve_symlinks: Controls if symlinks on the source will be
copied as such on the destination or
transformed into the referenced
file/directory.
@param excludes: A list of file pattern that matches files not to be
sent. `send_file` will fail if exclude is set, since
local copy does not support --exclude.
"""
if excludes:
raise error.AutotestHostRunError(
'--exclude is not supported in LocalHost.send_file method. '
'excludes: %s' % ','.join(excludes), None)
self._copy_file(source, dest, delete_dest=delete_dest,
preserve_symlinks=preserve_symlinks)
def get_tmp_dir(self, parent='/tmp'):
"""
Return the pathname of a directory on the host suitable
for temporary file storage.
The directory and its content will be deleted automatically
on the destruction of the Host object that was used to obtain
it.
@param parent: The leading path to make the tmp dir.
"""
self.run('mkdir -p "%s"' % parent)
tmp_dir = self.run('mktemp -d -p "%s"' % parent).stdout.rstrip()
self.tmp_dirs.append(tmp_dir)
return tmp_dir