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