xref: /aosp_15_r20/external/autotest/client/bin/local_host.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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