# 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