blob: ba0e53639979d3bdf97c4f2266d957e6b96af42b [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
17from autotest_lib.client.common_lib.utils import *
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
24def sh_escape(command):
mblighdc735a22007-08-02 16:54:37 +000025 """
26 Escape special characters from a command so that it can be passed
mblighc8949b82007-07-23 16:33:58 +000027 as a double quoted (" ") string in a (ba)sh command.
mblighdc735a22007-08-02 16:54:37 +000028
mblighdcd57a82007-07-11 23:06:47 +000029 Args:
30 command: the command string to escape.
mblighdc735a22007-08-02 16:54:37 +000031
mblighdcd57a82007-07-11 23:06:47 +000032 Returns:
33 The escaped command string. The required englobing double
34 quotes are NOT added and so should be added at some point by
35 the caller.
mblighdc735a22007-08-02 16:54:37 +000036
mblighdcd57a82007-07-11 23:06:47 +000037 See also: http://www.tldp.org/LDP/abs/html/escapingsection.html
38 """
39 command= command.replace("\\", "\\\\")
40 command= command.replace("$", r'\$')
41 command= command.replace('"', r'\"')
42 command= command.replace('`', r'\`')
43 return command
44
45
46def scp_remote_escape(filename):
mblighdc735a22007-08-02 16:54:37 +000047 """
48 Escape special characters from a filename so that it can be passed
mblighdcd57a82007-07-11 23:06:47 +000049 to scp (within double quotes) as a remote file.
mblighdc735a22007-08-02 16:54:37 +000050
mblighdcd57a82007-07-11 23:06:47 +000051 Bis-quoting has to be used with scp for remote files, "bis-quoting"
52 as in quoting x 2
53 scp does not support a newline in the filename
mblighdc735a22007-08-02 16:54:37 +000054
mblighdcd57a82007-07-11 23:06:47 +000055 Args:
56 filename: the filename string to escape.
mblighdc735a22007-08-02 16:54:37 +000057
mblighdcd57a82007-07-11 23:06:47 +000058 Returns:
59 The escaped filename string. The required englobing double
60 quotes are NOT added and so should be added at some point by
61 the caller.
62 """
63 escape_chars= r' !"$&' "'" r'()*,:;<=>?[\]^`{|}'
mblighdc735a22007-08-02 16:54:37 +000064
mblighdcd57a82007-07-11 23:06:47 +000065 new_name= []
66 for char in filename:
67 if char in escape_chars:
68 new_name.append("\\%s" % (char,))
69 else:
70 new_name.append(char)
mblighdc735a22007-08-02 16:54:37 +000071
mblighdcd57a82007-07-11 23:06:47 +000072 return sh_escape("".join(new_name))
73
74
mbligh6e18dab2007-10-24 21:27:18 +000075def get(location, local_copy = False):
mblighdcd57a82007-07-11 23:06:47 +000076 """Get a file or directory to a local temporary directory.
mblighdc735a22007-08-02 16:54:37 +000077
mblighdcd57a82007-07-11 23:06:47 +000078 Args:
79 location: the source of the material to get. This source may
80 be one of:
81 * a local file or directory
82 * a URL (http or ftp)
83 * a python file-like object
mblighdc735a22007-08-02 16:54:37 +000084
mblighdcd57a82007-07-11 23:06:47 +000085 Returns:
86 The location of the file or directory where the requested
87 content was saved. This will be contained in a temporary
mblighc8949b82007-07-23 16:33:58 +000088 directory on the local host. If the material to get was a
89 directory, the location will contain a trailing '/'
mblighdcd57a82007-07-11 23:06:47 +000090 """
91 tmpdir = get_tmp_dir()
mblighdc735a22007-08-02 16:54:37 +000092
mblighdcd57a82007-07-11 23:06:47 +000093 # location is a file-like object
94 if hasattr(location, "read"):
95 tmpfile = os.path.join(tmpdir, "file")
96 tmpfileobj = file(tmpfile, 'w')
97 shutil.copyfileobj(location, tmpfileobj)
98 tmpfileobj.close()
99 return tmpfile
mblighdc735a22007-08-02 16:54:37 +0000100
mblighdcd57a82007-07-11 23:06:47 +0000101 if isinstance(location, types.StringTypes):
102 # location is a URL
103 if location.startswith('http') or location.startswith('ftp'):
104 tmpfile = os.path.join(tmpdir, os.path.basename(location))
105 urllib.urlretrieve(location, tmpfile)
106 return tmpfile
107 # location is a local path
108 elif os.path.exists(os.path.abspath(location)):
mbligh6e18dab2007-10-24 21:27:18 +0000109 if not local_copy:
mbligh59f70aa2007-10-25 14:44:38 +0000110 if os.path.isdir(location):
111 return location.rstrip('/') + '/'
112 else:
113 return location
mblighdcd57a82007-07-11 23:06:47 +0000114 tmpfile = os.path.join(tmpdir, os.path.basename(location))
115 if os.path.isdir(location):
116 tmpfile += '/'
117 shutil.copytree(location, tmpfile, symlinks=True)
118 return tmpfile
119 shutil.copyfile(location, tmpfile)
120 return tmpfile
121 # location is just a string, dump it to a file
122 else:
123 tmpfd, tmpfile = tempfile.mkstemp(dir=tmpdir)
124 tmpfileobj = os.fdopen(tmpfd, 'w')
125 tmpfileobj.write(location)
126 tmpfileobj.close()
127 return tmpfile
128
mbligh5f876ad2007-10-12 23:59:53 +0000129
mblighdcd57a82007-07-11 23:06:47 +0000130def get_tmp_dir():
131 """Return the pathname of a directory on the host suitable
132 for temporary file storage.
mblighdc735a22007-08-02 16:54:37 +0000133
mblighdcd57a82007-07-11 23:06:47 +0000134 The directory and its content will be deleted automatically
135 at the end of the program execution if they are still present.
136 """
137 global __tmp_dirs
mblighdc735a22007-08-02 16:54:37 +0000138
mblighdcd57a82007-07-11 23:06:47 +0000139 dir_name= tempfile.mkdtemp(prefix="autoserv-")
mblighbea56822007-08-31 08:53:40 +0000140 pid = os.getpid()
141 if not pid in __tmp_dirs:
142 __tmp_dirs[pid] = []
143 __tmp_dirs[pid].append(dir_name)
mblighdcd57a82007-07-11 23:06:47 +0000144 return dir_name
145
146
147@atexit.register
148def __clean_tmp_dirs():
149 """Erase temporary directories that were created by the get_tmp_dir()
150 function and that are still present.
151 """
152 global __tmp_dirs
mblighdc735a22007-08-02 16:54:37 +0000153
mblighbea56822007-08-31 08:53:40 +0000154 pid = os.getpid()
155 if pid not in __tmp_dirs:
156 return
157 for dir in __tmp_dirs[pid]:
158 try:
159 shutil.rmtree(dir)
160 except OSError, e:
161 if e.errno == 2:
162 pass
163 __tmp_dirs[pid] = []
mblighc8949b82007-07-23 16:33:58 +0000164
165
166def unarchive(host, source_material):
167 """Uncompress and untar an archive on a host.
mblighdc735a22007-08-02 16:54:37 +0000168
mblighc8949b82007-07-23 16:33:58 +0000169 If the "source_material" is compresses (according to the file
170 extension) it will be uncompressed. Supported compression formats
171 are gzip and bzip2. Afterwards, if the source_material is a tar
172 archive, it will be untarred.
mblighdc735a22007-08-02 16:54:37 +0000173
mblighc8949b82007-07-23 16:33:58 +0000174 Args:
175 host: the host object on which the archive is located
176 source_material: the path of the archive on the host
mblighdc735a22007-08-02 16:54:37 +0000177
mblighc8949b82007-07-23 16:33:58 +0000178 Returns:
179 The file or directory name of the unarchived source material.
180 If the material is a tar archive, it will be extracted in the
181 directory where it is and the path returned will be the first
182 entry in the archive, assuming it is the topmost directory.
183 If the material is not an archive, nothing will be done so this
184 function is "harmless" when it is "useless".
185 """
186 # uncompress
187 if (source_material.endswith(".gz") or
188 source_material.endswith(".gzip")):
189 host.run('gunzip "%s"' % (sh_escape(source_material)))
190 source_material= ".".join(source_material.split(".")[:-1])
191 elif source_material.endswith("bz2"):
192 host.run('bunzip2 "%s"' % (sh_escape(source_material)))
193 source_material= ".".join(source_material.split(".")[:-1])
mblighdc735a22007-08-02 16:54:37 +0000194
mblighc8949b82007-07-23 16:33:58 +0000195 # untar
196 if source_material.endswith(".tar"):
197 retval= host.run('tar -C "%s" -xvf "%s"' % (
198 sh_escape(os.path.dirname(source_material)),
199 sh_escape(source_material),))
200 source_material= os.path.join(os.path.dirname(source_material),
201 retval.stdout.split()[0])
mblighdc735a22007-08-02 16:54:37 +0000202
mblighc8949b82007-07-23 16:33:58 +0000203 return source_material
mblighf1c52842007-10-16 15:21:38 +0000204
205
mbligh9708f732007-10-18 03:18:54 +0000206def get_server_dir():
207 path = os.path.dirname(sys.modules['utils'].__file__)
208 return os.path.abspath(path)
mbligh40f122a2007-11-03 23:08:46 +0000209
210
mbligh34a3fd72007-12-10 17:16:22 +0000211def find_pid(command):
212 for line in system_output('ps -eo pid,cmd').rstrip().split('\n'):
213 (pid, cmd) = line.split(None, 1)
214 if re.search(command, cmd):
215 return int(pid)
216 return None
217
218
219def nohup(command, stdout='/dev/null', stderr='/dev/null', background=True,
220 env = {}):
221 cmd = ' '.join(key+'='+val for key, val in env.iteritems())
222 cmd += ' nohup ' + command
223 cmd += ' > %s' % stdout
224 if stdout == stderr:
225 cmd += ' 2>&1'
226 else:
227 cmd += ' 2> %s' % stderr
228 if background:
229 cmd += ' &'
230 system(cmd)
231
232
mbligh40f122a2007-11-03 23:08:46 +0000233class AutoservOptionParser:
234 """Custom command-line options parser for autoserv.
235
236 We can't use the general getopt methods here, as there will be unknown
237 extra arguments that we pass down into the control file instead.
238 Thus we process the arguments by hand, for which we are duly repentant.
239 Making a single function here just makes it harder to read. Suck it up.
240 """
241
242 def __init__(self, args):
243 self.args = args
244
245
246 def parse_opts(self, flag):
247 if self.args.count(flag):
248 idx = self.args.index(flag)
249 self.args[idx : idx+1] = []
250 return True
251 else:
252 return False
253
254
255 def parse_opts_param(self, flag, default = None, split = False):
256 if self.args.count(flag):
257 idx = self.args.index(flag)
258 ret = self.args[idx+1]
259 self.args[idx : idx+2] = []
260 if split:
261 return ret.split(split)
262 else:
263 return ret
264 else:
265 return default