1# Lint as: python2, python3 2# Copyright 2009 Google Inc. Released under the GPL v2 3 4""" 5This file contains the implementation of a host object for the local machine. 6""" 7from __future__ import absolute_import 8from __future__ import division 9from __future__ import print_function 10 11import distutils.core 12import glob 13import os 14import platform 15import shutil 16import sys 17 18import common 19from autotest_lib.client.common_lib import hosts, error 20from autotest_lib.client.bin import utils 21import six 22 23 24class LocalHost(hosts.Host): 25 """This class represents a host running locally on the host.""" 26 27 28 def _initialize(self, hostname=None, bootloader=None, *args, **dargs): 29 super(LocalHost, self)._initialize(*args, **dargs) 30 31 # hostname will be an actual hostname when this client was created 32 # by an autoserv process 33 if not hostname: 34 hostname = platform.node() 35 self.hostname = hostname 36 self.bootloader = bootloader 37 self.tmp_dirs = [] 38 39 40 def close(self): 41 """Cleanup after we're done.""" 42 for tmp_dir in self.tmp_dirs: 43 self.run('rm -rf "%s"' % (utils.sh_escape(tmp_dir)), 44 ignore_status=True) 45 46 47 def wait_up(self, timeout=None): 48 # a local host is always up 49 return True 50 51 52 def run(self, command, timeout=3600, ignore_status=False, 53 ignore_timeout=False, 54 stdout_tee=utils.TEE_TO_LOGS, stderr_tee=utils.TEE_TO_LOGS, 55 stdin=None, args=(), **kwargs): 56 """ 57 @see common_lib.hosts.Host.run() 58 """ 59 try: 60 return utils.run( 61 command, timeout=timeout, ignore_status=ignore_status, 62 ignore_timeout=ignore_timeout, stdout_tee=stdout_tee, 63 stderr_tee=stderr_tee, stdin=stdin, args=args) 64 except error.CmdTimeoutError as e: 65 # CmdTimeoutError is a subclass of CmdError, so must be caught first 66 new_error = error.AutotestHostRunTimeoutError( 67 e.command, e.result_obj, additional_text=e.additional_text) 68 six.reraise(error.AutotestHostRunTimeoutError, new_error, sys.exc_info()[2]) 69 except error.CmdError as e: 70 new_error = error.AutotestHostRunCmdError( 71 e.command, e.result_obj, additional_text=e.additional_text) 72 six.reraise(error.AutotestHostRunCmdError, new_error, sys.exc_info()[2]) 73 74 75 def list_files_glob(self, path_glob): 76 """ 77 Get a list of files on a remote host given a glob pattern path. 78 """ 79 return glob.glob(path_glob) 80 81 82 def symlink_closure(self, paths): 83 """ 84 Given a sequence of path strings, return the set of all paths that 85 can be reached from the initial set by following symlinks. 86 87 @param paths: sequence of path strings. 88 @return: a sequence of path strings that are all the unique paths that 89 can be reached from the given ones after following symlinks. 90 """ 91 paths = set(paths) 92 closure = set() 93 94 while paths: 95 path = paths.pop() 96 if not os.path.exists(path): 97 continue 98 closure.add(path) 99 if os.path.islink(path): 100 link_to = os.path.join(os.path.dirname(path), 101 os.readlink(path)) 102 if link_to not in closure: 103 paths.add(link_to) 104 105 return closure 106 107 108 def _copy_file(self, source, dest, delete_dest=False, preserve_perm=False, 109 preserve_symlinks=False): 110 """Copy files from source to dest, will be the base for {get,send}_file. 111 112 If source is a directory and ends with a trailing slash, only the 113 contents of the source directory will be copied to dest, otherwise 114 source itself will be copied under dest. 115 116 @param source: The file/directory on localhost to copy. 117 @param dest: The destination path on localhost to copy to. 118 @param delete_dest: A flag set to choose whether or not to delete 119 dest if it exists. 120 @param preserve_perm: Tells get_file() to try to preserve the sources 121 permissions on files and dirs. 122 @param preserve_symlinks: Try to preserve symlinks instead of 123 transforming them into files/dirs on copy. 124 """ 125 # We copy dest under source if either: 126 # 1. Source is a directory and doesn't end with /. 127 # 2. Source is a file and dest is a directory. 128 source_is_dir = os.path.isdir(source) 129 if ((source_is_dir and not source.endswith(os.sep)) or 130 (not source_is_dir and os.path.isdir(dest))): 131 dest = os.path.join(dest, os.path.basename(source)) 132 133 if delete_dest and os.path.exists(dest): 134 # Check if it's a file or a dir and use proper remove method. 135 if os.path.isdir(dest): 136 shutil.rmtree(dest) 137 os.mkdir(dest) 138 else: 139 os.remove(dest) 140 141 if preserve_symlinks and os.path.islink(source): 142 os.symlink(os.readlink(source), dest) 143 # If source is a dir, use distutils.dir_util.copytree since 144 # shutil.copy_tree has weird limitations. 145 elif os.path.isdir(source): 146 distutils.dir_util.copy_tree(source, dest, 147 preserve_symlinks=preserve_symlinks, 148 preserve_mode=preserve_perm, 149 update=1) 150 else: 151 shutil.copyfile(source, dest) 152 153 if preserve_perm: 154 shutil.copymode(source, dest) 155 156 157 def get_file(self, source, dest, delete_dest=False, preserve_perm=True, 158 preserve_symlinks=False): 159 """Copy files from source to dest. 160 161 If source is a directory and ends with a trailing slash, only the 162 contents of the source directory will be copied to dest, otherwise 163 source itself will be copied under dest. This is to match the 164 behavior of AbstractSSHHost.get_file(). 165 166 @param source: The file/directory on localhost to copy. 167 @param dest: The destination path on localhost to copy to. 168 @param delete_dest: A flag set to choose whether or not to delete 169 dest if it exists. 170 @param preserve_perm: Tells get_file() to try to preserve the sources 171 permissions on files and dirs. 172 @param preserve_symlinks: Try to preserve symlinks instead of 173 transforming them into files/dirs on copy. 174 """ 175 self._copy_file(source, dest, delete_dest=delete_dest, 176 preserve_perm=preserve_perm, 177 preserve_symlinks=preserve_symlinks) 178 179 180 def send_file(self, source, dest, delete_dest=False, 181 preserve_symlinks=False, excludes=None): 182 """Copy files from source to dest. 183 184 If source is a directory and ends with a trailing slash, only the 185 contents of the source directory will be copied to dest, otherwise 186 source itself will be copied under dest. This is to match the 187 behavior of AbstractSSHHost.send_file(). 188 189 @param source: The file/directory on the drone to send to the device. 190 @param dest: The destination path on the device to copy to. 191 @param delete_dest: A flag set to choose whether or not to delete 192 dest on the device if it exists. 193 @param preserve_symlinks: Controls if symlinks on the source will be 194 copied as such on the destination or 195 transformed into the referenced 196 file/directory. 197 @param excludes: A list of file pattern that matches files not to be 198 sent. `send_file` will fail if exclude is set, since 199 local copy does not support --exclude. 200 """ 201 if excludes: 202 raise error.AutotestHostRunError( 203 '--exclude is not supported in LocalHost.send_file method. ' 204 'excludes: %s' % ','.join(excludes), None) 205 self._copy_file(source, dest, delete_dest=delete_dest, 206 preserve_symlinks=preserve_symlinks) 207 208 209 def get_tmp_dir(self, parent='/tmp'): 210 """ 211 Return the pathname of a directory on the host suitable 212 for temporary file storage. 213 214 The directory and its content will be deleted automatically 215 on the destruction of the Host object that was used to obtain 216 it. 217 218 @param parent: The leading path to make the tmp dir. 219 """ 220 tmp_dir = self.run( 221 'mkdir -p "%s" && mktemp -d -p "%s"' % (parent, parent) 222 ).stdout.rstrip() 223 self.tmp_dirs.append(tmp_dir) 224 return tmp_dir 225