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