blob: 480e8ec3f718b2166ad9fabc81c6f0c191c3a927 [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
mblighea397bb2008-02-02 19:17:51 +000016from common.utils import *
17
18
mblighbea56822007-08-31 08:53:40 +000019# A dictionary of pid and a list of tmpdirs for that pid
20__tmp_dirs = {}
mblighdcd57a82007-07-11 23:06:47 +000021
22
23def sh_escape(command):
mblighdc735a22007-08-02 16:54:37 +000024 """
25 Escape special characters from a command so that it can be passed
mblighc8949b82007-07-23 16:33:58 +000026 as a double quoted (" ") string in a (ba)sh command.
mblighdc735a22007-08-02 16:54:37 +000027
mblighdcd57a82007-07-11 23:06:47 +000028 Args:
29 command: the command string to escape.
mblighdc735a22007-08-02 16:54:37 +000030
mblighdcd57a82007-07-11 23:06:47 +000031 Returns:
32 The escaped command string. The required englobing double
33 quotes are NOT added and so should be added at some point by
34 the caller.
mblighdc735a22007-08-02 16:54:37 +000035
mblighdcd57a82007-07-11 23:06:47 +000036 See also: http://www.tldp.org/LDP/abs/html/escapingsection.html
37 """
38 command= command.replace("\\", "\\\\")
39 command= command.replace("$", r'\$')
40 command= command.replace('"', r'\"')
41 command= command.replace('`', r'\`')
42 return command
43
44
45def scp_remote_escape(filename):
mblighdc735a22007-08-02 16:54:37 +000046 """
47 Escape special characters from a filename so that it can be passed
mblighdcd57a82007-07-11 23:06:47 +000048 to scp (within double quotes) as a remote file.
mblighdc735a22007-08-02 16:54:37 +000049
mblighdcd57a82007-07-11 23:06:47 +000050 Bis-quoting has to be used with scp for remote files, "bis-quoting"
51 as in quoting x 2
52 scp does not support a newline in the filename
mblighdc735a22007-08-02 16:54:37 +000053
mblighdcd57a82007-07-11 23:06:47 +000054 Args:
55 filename: the filename string to escape.
mblighdc735a22007-08-02 16:54:37 +000056
mblighdcd57a82007-07-11 23:06:47 +000057 Returns:
58 The escaped filename string. The required englobing double
59 quotes are NOT added and so should be added at some point by
60 the caller.
61 """
62 escape_chars= r' !"$&' "'" r'()*,:;<=>?[\]^`{|}'
mblighdc735a22007-08-02 16:54:37 +000063
mblighdcd57a82007-07-11 23:06:47 +000064 new_name= []
65 for char in filename:
66 if char in escape_chars:
67 new_name.append("\\%s" % (char,))
68 else:
69 new_name.append(char)
mblighdc735a22007-08-02 16:54:37 +000070
mblighdcd57a82007-07-11 23:06:47 +000071 return sh_escape("".join(new_name))
72
73
mbligh6e18dab2007-10-24 21:27:18 +000074def get(location, local_copy = False):
mblighdcd57a82007-07-11 23:06:47 +000075 """Get a file or directory to a local temporary directory.
mblighdc735a22007-08-02 16:54:37 +000076
mblighdcd57a82007-07-11 23:06:47 +000077 Args:
78 location: the source of the material to get. This source may
79 be one of:
80 * a local file or directory
81 * a URL (http or ftp)
82 * a python file-like object
mblighdc735a22007-08-02 16:54:37 +000083
mblighdcd57a82007-07-11 23:06:47 +000084 Returns:
85 The location of the file or directory where the requested
86 content was saved. This will be contained in a temporary
mblighc8949b82007-07-23 16:33:58 +000087 directory on the local host. If the material to get was a
88 directory, the location will contain a trailing '/'
mblighdcd57a82007-07-11 23:06:47 +000089 """
90 tmpdir = get_tmp_dir()
mblighdc735a22007-08-02 16:54:37 +000091
mblighdcd57a82007-07-11 23:06:47 +000092 # location is a file-like object
93 if hasattr(location, "read"):
94 tmpfile = os.path.join(tmpdir, "file")
95 tmpfileobj = file(tmpfile, 'w')
96 shutil.copyfileobj(location, tmpfileobj)
97 tmpfileobj.close()
98 return tmpfile
mblighdc735a22007-08-02 16:54:37 +000099
mblighdcd57a82007-07-11 23:06:47 +0000100 if isinstance(location, types.StringTypes):
101 # location is a URL
102 if location.startswith('http') or location.startswith('ftp'):
103 tmpfile = os.path.join(tmpdir, os.path.basename(location))
104 urllib.urlretrieve(location, tmpfile)
105 return tmpfile
106 # location is a local path
107 elif os.path.exists(os.path.abspath(location)):
mbligh6e18dab2007-10-24 21:27:18 +0000108 if not local_copy:
mbligh59f70aa2007-10-25 14:44:38 +0000109 if os.path.isdir(location):
110 return location.rstrip('/') + '/'
111 else:
112 return location
mblighdcd57a82007-07-11 23:06:47 +0000113 tmpfile = os.path.join(tmpdir, os.path.basename(location))
114 if os.path.isdir(location):
115 tmpfile += '/'
116 shutil.copytree(location, tmpfile, symlinks=True)
117 return tmpfile
118 shutil.copyfile(location, tmpfile)
119 return tmpfile
120 # location is just a string, dump it to a file
121 else:
122 tmpfd, tmpfile = tempfile.mkstemp(dir=tmpdir)
123 tmpfileobj = os.fdopen(tmpfd, 'w')
124 tmpfileobj.write(location)
125 tmpfileobj.close()
126 return tmpfile
127
mbligh5f876ad2007-10-12 23:59:53 +0000128
mblighdcd57a82007-07-11 23:06:47 +0000129def get_tmp_dir():
130 """Return the pathname of a directory on the host suitable
131 for temporary file storage.
mblighdc735a22007-08-02 16:54:37 +0000132
mblighdcd57a82007-07-11 23:06:47 +0000133 The directory and its content will be deleted automatically
134 at the end of the program execution if they are still present.
135 """
136 global __tmp_dirs
mblighdc735a22007-08-02 16:54:37 +0000137
mblighdcd57a82007-07-11 23:06:47 +0000138 dir_name= tempfile.mkdtemp(prefix="autoserv-")
mblighbea56822007-08-31 08:53:40 +0000139 pid = os.getpid()
140 if not pid in __tmp_dirs:
141 __tmp_dirs[pid] = []
142 __tmp_dirs[pid].append(dir_name)
mblighdcd57a82007-07-11 23:06:47 +0000143 return dir_name
144
145
146@atexit.register
147def __clean_tmp_dirs():
148 """Erase temporary directories that were created by the get_tmp_dir()
149 function and that are still present.
150 """
151 global __tmp_dirs
mblighdc735a22007-08-02 16:54:37 +0000152
mblighbea56822007-08-31 08:53:40 +0000153 pid = os.getpid()
154 if pid not in __tmp_dirs:
155 return
156 for dir in __tmp_dirs[pid]:
157 try:
158 shutil.rmtree(dir)
159 except OSError, e:
160 if e.errno == 2:
161 pass
162 __tmp_dirs[pid] = []
mblighc8949b82007-07-23 16:33:58 +0000163
164
165def unarchive(host, source_material):
166 """Uncompress and untar an archive on a host.
mblighdc735a22007-08-02 16:54:37 +0000167
mblighc8949b82007-07-23 16:33:58 +0000168 If the "source_material" is compresses (according to the file
169 extension) it will be uncompressed. Supported compression formats
170 are gzip and bzip2. Afterwards, if the source_material is a tar
171 archive, it will be untarred.
mblighdc735a22007-08-02 16:54:37 +0000172
mblighc8949b82007-07-23 16:33:58 +0000173 Args:
174 host: the host object on which the archive is located
175 source_material: the path of the archive on the host
mblighdc735a22007-08-02 16:54:37 +0000176
mblighc8949b82007-07-23 16:33:58 +0000177 Returns:
178 The file or directory name of the unarchived source material.
179 If the material is a tar archive, it will be extracted in the
180 directory where it is and the path returned will be the first
181 entry in the archive, assuming it is the topmost directory.
182 If the material is not an archive, nothing will be done so this
183 function is "harmless" when it is "useless".
184 """
185 # uncompress
186 if (source_material.endswith(".gz") or
187 source_material.endswith(".gzip")):
188 host.run('gunzip "%s"' % (sh_escape(source_material)))
189 source_material= ".".join(source_material.split(".")[:-1])
190 elif source_material.endswith("bz2"):
191 host.run('bunzip2 "%s"' % (sh_escape(source_material)))
192 source_material= ".".join(source_material.split(".")[:-1])
mblighdc735a22007-08-02 16:54:37 +0000193
mblighc8949b82007-07-23 16:33:58 +0000194 # untar
195 if source_material.endswith(".tar"):
196 retval= host.run('tar -C "%s" -xvf "%s"' % (
197 sh_escape(os.path.dirname(source_material)),
198 sh_escape(source_material),))
199 source_material= os.path.join(os.path.dirname(source_material),
200 retval.stdout.split()[0])
mblighdc735a22007-08-02 16:54:37 +0000201
mblighc8949b82007-07-23 16:33:58 +0000202 return source_material
mblighf1c52842007-10-16 15:21:38 +0000203
204
mbligh9708f732007-10-18 03:18:54 +0000205def get_server_dir():
206 path = os.path.dirname(sys.modules['utils'].__file__)
207 return os.path.abspath(path)
mbligh40f122a2007-11-03 23:08:46 +0000208
209
mbligh34a3fd72007-12-10 17:16:22 +0000210def find_pid(command):
211 for line in system_output('ps -eo pid,cmd').rstrip().split('\n'):
212 (pid, cmd) = line.split(None, 1)
213 if re.search(command, cmd):
214 return int(pid)
215 return None
216
217
218def nohup(command, stdout='/dev/null', stderr='/dev/null', background=True,
219 env = {}):
220 cmd = ' '.join(key+'='+val for key, val in env.iteritems())
221 cmd += ' nohup ' + command
222 cmd += ' > %s' % stdout
223 if stdout == stderr:
224 cmd += ' 2>&1'
225 else:
226 cmd += ' 2> %s' % stderr
227 if background:
228 cmd += ' &'
229 system(cmd)
230
231
mbligh40f122a2007-11-03 23:08:46 +0000232class AutoservOptionParser:
233 """Custom command-line options parser for autoserv.
234
235 We can't use the general getopt methods here, as there will be unknown
236 extra arguments that we pass down into the control file instead.
237 Thus we process the arguments by hand, for which we are duly repentant.
238 Making a single function here just makes it harder to read. Suck it up.
239 """
240
241 def __init__(self, args):
242 self.args = args
243
244
245 def parse_opts(self, flag):
246 if self.args.count(flag):
247 idx = self.args.index(flag)
248 self.args[idx : idx+1] = []
249 return True
250 else:
251 return False
252
253
254 def parse_opts_param(self, flag, default = None, split = False):
255 if self.args.count(flag):
256 idx = self.args.index(flag)
257 ret = self.args[idx+1]
258 self.args[idx : idx+2] = []
259 if split:
260 return ret.split(split)
261 else:
262 return ret
263 else:
264 return default