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, excludes=None):
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        @param excludes: A list of file pattern that matches files not to be
193                         sent. `send_file` will fail if exclude is set, since
194                         local copy does not support --exclude.
195        """
196        if excludes:
197            raise error.AutotestHostRunError(
198                    '--exclude is not supported in LocalHost.send_file method. '
199                    'excludes: %s' % ','.join(excludes), None)
200        self._copy_file(source, dest, delete_dest=delete_dest,
201                        preserve_symlinks=preserve_symlinks)
202
203
204    def get_tmp_dir(self, parent='/tmp'):
205        """
206        Return the pathname of a directory on the host suitable
207        for temporary file storage.
208
209        The directory and its content will be deleted automatically
210        on the destruction of the Host object that was used to obtain
211        it.
212
213        @param parent: The leading path to make the tmp dir.
214        """
215        self.run('mkdir -p "%s"' % parent)
216        tmp_dir = self.run('mktemp -d -p "%s"' % parent).stdout.rstrip()
217        self.tmp_dirs.append(tmp_dir)
218        return tmp_dir
219