blob: 3104842ccc48b6055f09f228a4a98633271e282a [file] [log] [blame]
mblighdcd57a82007-07-11 23:06:47 +00001#!/usr/bin/python
2#
mbligh63073c92008-03-31 16:49:32 +00003# Copyright 2008 Google Inc. Released under the GPL v2
mblighdcd57a82007-07-11 23:06:47 +00004
mblighdc735a22007-08-02 16:54:37 +00005"""
6Miscellaneous small functions.
mblighdcd57a82007-07-11 23:06:47 +00007"""
8
mblighdc735a22007-08-02 16:54:37 +00009__author__ = """
10mbligh@google.com (Martin J. Bligh),
mblighdcd57a82007-07-11 23:06:47 +000011poirier@google.com (Benjamin Poirier),
mblighdc735a22007-08-02 16:54:37 +000012stutsman@google.com (Ryan Stutsman)
13"""
mblighdcd57a82007-07-11 23:06:47 +000014
mbligh63073c92008-03-31 16:49:32 +000015import atexit, os, re, shutil, textwrap, sys, tempfile, types, urllib
mblighccb9e182008-04-17 15:42:10 +000016
mblighc25b58f2008-05-29 21:05:25 +000017from autotest_lib.client.common_lib import utils
mblighea397bb2008-02-02 19:17:51 +000018
19
mblighbea56822007-08-31 08:53:40 +000020# A dictionary of pid and a list of tmpdirs for that pid
21__tmp_dirs = {}
mblighdcd57a82007-07-11 23:06:47 +000022
23
mblighc25b58f2008-05-29 21:05:25 +000024############# we need pass throughs for the methods in client/common_lib/utils
25def run(command, timeout=None, ignore_status=False,
26 stdout_tee=None, stderr_tee=None):
27 return utils.run(command, timeout, ignore_status,
28 stdout_tee, stderr_tee)
29
30
mblighc25b58f2008-05-29 21:05:25 +000031def system(command, timeout=None, ignore_status=False):
32 return utils.system(command, timeout, ignore_status)
33
34
35def system_output(command, timeout=None, ignore_status=False,
36 retain_output=False):
37 return utils.system_output(command, timeout, ignore_status,
38 retain_output)
39
40
41def read_keyval(path):
42 return utils.read_keyval(path)
43
44
45def write_keyval(path, dictionary):
46 return utils.write_keyval(path, dictionary)
47
48
49####################################################################
50
mblighdcd57a82007-07-11 23:06:47 +000051def sh_escape(command):
mblighdc735a22007-08-02 16:54:37 +000052 """
53 Escape special characters from a command so that it can be passed
mblighc8949b82007-07-23 16:33:58 +000054 as a double quoted (" ") string in a (ba)sh command.
mblighdc735a22007-08-02 16:54:37 +000055
mblighdcd57a82007-07-11 23:06:47 +000056 Args:
57 command: the command string to escape.
mblighdc735a22007-08-02 16:54:37 +000058
mblighdcd57a82007-07-11 23:06:47 +000059 Returns:
60 The escaped command string. The required englobing double
61 quotes are NOT added and so should be added at some point by
62 the caller.
mblighdc735a22007-08-02 16:54:37 +000063
mblighdcd57a82007-07-11 23:06:47 +000064 See also: http://www.tldp.org/LDP/abs/html/escapingsection.html
65 """
mblighc25b58f2008-05-29 21:05:25 +000066 command = command.replace("\\", "\\\\")
67 command = command.replace("$", r'\$')
68 command = command.replace('"', r'\"')
69 command = command.replace('`', r'\`')
mblighdcd57a82007-07-11 23:06:47 +000070 return command
71
72
73def scp_remote_escape(filename):
mblighdc735a22007-08-02 16:54:37 +000074 """
75 Escape special characters from a filename so that it can be passed
mblighdcd57a82007-07-11 23:06:47 +000076 to scp (within double quotes) as a remote file.
mblighdc735a22007-08-02 16:54:37 +000077
mblighdcd57a82007-07-11 23:06:47 +000078 Bis-quoting has to be used with scp for remote files, "bis-quoting"
79 as in quoting x 2
80 scp does not support a newline in the filename
mblighdc735a22007-08-02 16:54:37 +000081
mblighdcd57a82007-07-11 23:06:47 +000082 Args:
83 filename: the filename string to escape.
mblighdc735a22007-08-02 16:54:37 +000084
mblighdcd57a82007-07-11 23:06:47 +000085 Returns:
86 The escaped filename string. The required englobing double
87 quotes are NOT added and so should be added at some point by
88 the caller.
89 """
90 escape_chars= r' !"$&' "'" r'()*,:;<=>?[\]^`{|}'
mblighdc735a22007-08-02 16:54:37 +000091
mblighdcd57a82007-07-11 23:06:47 +000092 new_name= []
93 for char in filename:
94 if char in escape_chars:
95 new_name.append("\\%s" % (char,))
96 else:
97 new_name.append(char)
mblighdc735a22007-08-02 16:54:37 +000098
mblighdcd57a82007-07-11 23:06:47 +000099 return sh_escape("".join(new_name))
100
101
mbligh6e18dab2007-10-24 21:27:18 +0000102def get(location, local_copy = False):
mblighdcd57a82007-07-11 23:06:47 +0000103 """Get a file or directory to a local temporary directory.
mblighdc735a22007-08-02 16:54:37 +0000104
mblighdcd57a82007-07-11 23:06:47 +0000105 Args:
106 location: the source of the material to get. This source may
107 be one of:
108 * a local file or directory
109 * a URL (http or ftp)
110 * a python file-like object
mblighdc735a22007-08-02 16:54:37 +0000111
mblighdcd57a82007-07-11 23:06:47 +0000112 Returns:
113 The location of the file or directory where the requested
114 content was saved. This will be contained in a temporary
mblighc8949b82007-07-23 16:33:58 +0000115 directory on the local host. If the material to get was a
116 directory, the location will contain a trailing '/'
mblighdcd57a82007-07-11 23:06:47 +0000117 """
118 tmpdir = get_tmp_dir()
mblighdc735a22007-08-02 16:54:37 +0000119
mblighdcd57a82007-07-11 23:06:47 +0000120 # location is a file-like object
121 if hasattr(location, "read"):
122 tmpfile = os.path.join(tmpdir, "file")
123 tmpfileobj = file(tmpfile, 'w')
124 shutil.copyfileobj(location, tmpfileobj)
125 tmpfileobj.close()
126 return tmpfile
mblighdc735a22007-08-02 16:54:37 +0000127
mblighdcd57a82007-07-11 23:06:47 +0000128 if isinstance(location, types.StringTypes):
129 # location is a URL
130 if location.startswith('http') or location.startswith('ftp'):
131 tmpfile = os.path.join(tmpdir, os.path.basename(location))
132 urllib.urlretrieve(location, tmpfile)
133 return tmpfile
134 # location is a local path
135 elif os.path.exists(os.path.abspath(location)):
mbligh6e18dab2007-10-24 21:27:18 +0000136 if not local_copy:
mbligh59f70aa2007-10-25 14:44:38 +0000137 if os.path.isdir(location):
138 return location.rstrip('/') + '/'
139 else:
140 return location
mblighdcd57a82007-07-11 23:06:47 +0000141 tmpfile = os.path.join(tmpdir, os.path.basename(location))
142 if os.path.isdir(location):
143 tmpfile += '/'
144 shutil.copytree(location, tmpfile, symlinks=True)
145 return tmpfile
146 shutil.copyfile(location, tmpfile)
147 return tmpfile
148 # location is just a string, dump it to a file
149 else:
150 tmpfd, tmpfile = tempfile.mkstemp(dir=tmpdir)
151 tmpfileobj = os.fdopen(tmpfd, 'w')
152 tmpfileobj.write(location)
153 tmpfileobj.close()
154 return tmpfile
155
mbligh5f876ad2007-10-12 23:59:53 +0000156
mblighdcd57a82007-07-11 23:06:47 +0000157def get_tmp_dir():
158 """Return the pathname of a directory on the host suitable
159 for temporary file storage.
mblighdc735a22007-08-02 16:54:37 +0000160
mblighdcd57a82007-07-11 23:06:47 +0000161 The directory and its content will be deleted automatically
162 at the end of the program execution if they are still present.
163 """
164 global __tmp_dirs
mblighdc735a22007-08-02 16:54:37 +0000165
mblighdcd57a82007-07-11 23:06:47 +0000166 dir_name= tempfile.mkdtemp(prefix="autoserv-")
mblighbea56822007-08-31 08:53:40 +0000167 pid = os.getpid()
168 if not pid in __tmp_dirs:
169 __tmp_dirs[pid] = []
170 __tmp_dirs[pid].append(dir_name)
mblighdcd57a82007-07-11 23:06:47 +0000171 return dir_name
172
173
174@atexit.register
175def __clean_tmp_dirs():
176 """Erase temporary directories that were created by the get_tmp_dir()
177 function and that are still present.
178 """
179 global __tmp_dirs
mblighdc735a22007-08-02 16:54:37 +0000180
mblighbea56822007-08-31 08:53:40 +0000181 pid = os.getpid()
182 if pid not in __tmp_dirs:
183 return
184 for dir in __tmp_dirs[pid]:
185 try:
186 shutil.rmtree(dir)
187 except OSError, e:
188 if e.errno == 2:
189 pass
190 __tmp_dirs[pid] = []
mblighc8949b82007-07-23 16:33:58 +0000191
192
193def unarchive(host, source_material):
194 """Uncompress and untar an archive on a host.
mblighdc735a22007-08-02 16:54:37 +0000195
mblighc8949b82007-07-23 16:33:58 +0000196 If the "source_material" is compresses (according to the file
197 extension) it will be uncompressed. Supported compression formats
198 are gzip and bzip2. Afterwards, if the source_material is a tar
199 archive, it will be untarred.
mblighdc735a22007-08-02 16:54:37 +0000200
mblighc8949b82007-07-23 16:33:58 +0000201 Args:
202 host: the host object on which the archive is located
203 source_material: the path of the archive on the host
mblighdc735a22007-08-02 16:54:37 +0000204
mblighc8949b82007-07-23 16:33:58 +0000205 Returns:
206 The file or directory name of the unarchived source material.
207 If the material is a tar archive, it will be extracted in the
208 directory where it is and the path returned will be the first
209 entry in the archive, assuming it is the topmost directory.
210 If the material is not an archive, nothing will be done so this
211 function is "harmless" when it is "useless".
212 """
213 # uncompress
214 if (source_material.endswith(".gz") or
215 source_material.endswith(".gzip")):
216 host.run('gunzip "%s"' % (sh_escape(source_material)))
217 source_material= ".".join(source_material.split(".")[:-1])
218 elif source_material.endswith("bz2"):
219 host.run('bunzip2 "%s"' % (sh_escape(source_material)))
220 source_material= ".".join(source_material.split(".")[:-1])
mblighdc735a22007-08-02 16:54:37 +0000221
mblighc8949b82007-07-23 16:33:58 +0000222 # untar
223 if source_material.endswith(".tar"):
224 retval= host.run('tar -C "%s" -xvf "%s"' % (
225 sh_escape(os.path.dirname(source_material)),
226 sh_escape(source_material),))
227 source_material= os.path.join(os.path.dirname(source_material),
228 retval.stdout.split()[0])
mblighdc735a22007-08-02 16:54:37 +0000229
mblighc8949b82007-07-23 16:33:58 +0000230 return source_material
mblighf1c52842007-10-16 15:21:38 +0000231
232
mbligh9708f732007-10-18 03:18:54 +0000233def get_server_dir():
mbligh8fc3b912008-05-06 20:43:02 +0000234 path = os.path.dirname(sys.modules['autotest_lib.server.utils'].__file__)
mbligh9708f732007-10-18 03:18:54 +0000235 return os.path.abspath(path)
mbligh40f122a2007-11-03 23:08:46 +0000236
237
mbligh34a3fd72007-12-10 17:16:22 +0000238def find_pid(command):
mblighc25b58f2008-05-29 21:05:25 +0000239 for line in utils.system_output('ps -eo pid,cmd').rstrip().split('\n'):
mbligh34a3fd72007-12-10 17:16:22 +0000240 (pid, cmd) = line.split(None, 1)
241 if re.search(command, cmd):
242 return int(pid)
243 return None
244
245
246def nohup(command, stdout='/dev/null', stderr='/dev/null', background=True,
247 env = {}):
248 cmd = ' '.join(key+'='+val for key, val in env.iteritems())
249 cmd += ' nohup ' + command
250 cmd += ' > %s' % stdout
251 if stdout == stderr:
252 cmd += ' 2>&1'
253 else:
254 cmd += ' 2> %s' % stderr
255 if background:
256 cmd += ' &'
mblighc25b58f2008-05-29 21:05:25 +0000257 utils.system(cmd)
mbligh34a3fd72007-12-10 17:16:22 +0000258
259
mbligh0b4fe6e2008-05-06 20:41:37 +0000260def default_mappings(machines):
261 """
262 Returns a simple mapping in which all machines are assigned to the
263 same key. Provides the default behavior for
264 form_ntuples_from_machines. """
265 mappings = {}
266 failures = []
267
268 mach = machines[0]
269 mappings['ident'] = [mach]
270 if len(machines) > 1:
271 machines = machines[1:]
272 for machine in machines:
273 mappings['ident'].append(machine)
274
275 return (mappings, failures)
276
277
278def form_ntuples_from_machines(machines, n=2, mapping_func=default_mappings):
279 """Returns a set of ntuples from machines where the machines in an
280 ntuple are in the same mapping, and a set of failures which are
281 (machine name, reason) tuples."""
282 ntuples = []
283 (mappings, failures) = mapping_func(machines)
284
285 # now run through the mappings and create n-tuples.
286 # throw out the odd guys out
287 for key in mappings:
288 key_machines = mappings[key]
289 total_machines = len(key_machines)
290
291 # form n-tuples
292 while len(key_machines) >= n:
293 ntuples.append(key_machines[0:n])
294 key_machines = key_machines[n:]
295
296 for mach in key_machines:
297 failures.append((mach, "machine can not be tupled"))
298
299 return (ntuples, failures)