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