blob: b7ac4f2fc8be39a2877be0e9446a863152618eca [file] [log] [blame]
mbligh63073c92008-03-31 16:49:32 +00001#!/usr/bin/python
2#
3# Copyright 2008 Google Inc. Released under the GPL v2
4
mbligh849a0f62008-08-28 20:12:19 +00005import os, pickle, random, re, resource, select, shutil, signal, StringIO
6import socket, struct, subprocess, sys, time, textwrap, urllib, urlparse
mbligh25284cd2009-06-08 16:17:24 +00007import warnings, smtplib, logging, urllib2
showard108d73e2009-06-22 18:14:41 +00008from autotest_lib.client.common_lib import error, barrier, logging_manager
mbligh81edd792008-08-26 16:54:02 +00009
mbligh849a0f62008-08-28 20:12:19 +000010def deprecated(func):
11 """This is a decorator which can be used to mark functions as deprecated.
12 It will result in a warning being emmitted when the function is used."""
13 def new_func(*args, **dargs):
14 warnings.warn("Call to deprecated function %s." % func.__name__,
15 category=DeprecationWarning)
16 return func(*args, **dargs)
17 new_func.__name__ = func.__name__
18 new_func.__doc__ = func.__doc__
19 new_func.__dict__.update(func.__dict__)
20 return new_func
21
22
showard108d73e2009-06-22 18:14:41 +000023class _NullStream(object):
24 def write(self, data):
25 pass
26
27
28 def flush(self):
29 pass
30
31
32TEE_TO_LOGS = object()
33_the_null_stream = _NullStream()
34
35def get_stream_tee_file(stream, level):
36 if stream is None:
37 return _the_null_stream
38 if stream is TEE_TO_LOGS:
39 return logging_manager.LoggingFile(level=level)
40 return stream
41
42
mbligh849a0f62008-08-28 20:12:19 +000043class BgJob(object):
showard170873e2009-01-07 00:22:26 +000044 def __init__(self, command, stdout_tee=None, stderr_tee=None, verbose=True,
45 stdin=None):
mbligh849a0f62008-08-28 20:12:19 +000046 self.command = command
showard108d73e2009-06-22 18:14:41 +000047 self.stdout_tee = get_stream_tee_file(stdout_tee, logging.DEBUG)
48 self.stderr_tee = get_stream_tee_file(stderr_tee, logging.ERROR)
mbligh849a0f62008-08-28 20:12:19 +000049 self.result = CmdResult(command)
mblighbd96b452008-09-03 23:14:27 +000050 if verbose:
showardb18134f2009-03-20 20:52:18 +000051 logging.debug("Running '%s'" % command)
mbligh849a0f62008-08-28 20:12:19 +000052 self.sp = subprocess.Popen(command, stdout=subprocess.PIPE,
53 stderr=subprocess.PIPE,
54 preexec_fn=self._reset_sigpipe, shell=True,
showard170873e2009-01-07 00:22:26 +000055 executable="/bin/bash",
56 stdin=stdin)
mbligh849a0f62008-08-28 20:12:19 +000057
58
59 def output_prepare(self, stdout_file=None, stderr_file=None):
60 self.stdout_file = stdout_file
61 self.stderr_file = stderr_file
62
mbligh45ffc432008-12-09 23:35:17 +000063
mbligh849a0f62008-08-28 20:12:19 +000064 def process_output(self, stdout=True, final_read=False):
65 """output_prepare must be called prior to calling this"""
66 if stdout:
67 pipe, buf, tee = self.sp.stdout, self.stdout_file, self.stdout_tee
68 else:
69 pipe, buf, tee = self.sp.stderr, self.stderr_file, self.stderr_tee
70
71 if final_read:
72 # read in all the data we can from pipe and then stop
73 data = []
74 while select.select([pipe], [], [], 0)[0]:
75 data.append(os.read(pipe.fileno(), 1024))
76 if len(data[-1]) == 0:
77 break
78 data = "".join(data)
79 else:
80 # perform a single read
81 data = os.read(pipe.fileno(), 1024)
82 buf.write(data)
showard108d73e2009-06-22 18:14:41 +000083 tee.write(data)
mbligh849a0f62008-08-28 20:12:19 +000084
85
86 def cleanup(self):
showard108d73e2009-06-22 18:14:41 +000087 self.stdout_tee.flush()
88 self.stderr_tee.flush()
mbligh849a0f62008-08-28 20:12:19 +000089 self.sp.stdout.close()
90 self.sp.stderr.close()
91 self.result.stdout = self.stdout_file.getvalue()
92 self.result.stderr = self.stderr_file.getvalue()
93
94
95 def _reset_sigpipe(self):
96 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
97
mbligh81edd792008-08-26 16:54:02 +000098
99def ip_to_long(ip):
100 # !L is a long in network byte order
101 return struct.unpack('!L', socket.inet_aton(ip))[0]
102
103
104def long_to_ip(number):
105 # See above comment.
106 return socket.inet_ntoa(struct.pack('!L', number))
107
108
109def create_subnet_mask(bits):
mbligh81edd792008-08-26 16:54:02 +0000110 return (1 << 32) - (1 << 32-bits)
111
112
113def format_ip_with_mask(ip, mask_bits):
114 masked_ip = ip_to_long(ip) & create_subnet_mask(mask_bits)
115 return "%s/%s" % (long_to_ip(masked_ip), mask_bits)
mbligh6231cd62008-02-02 19:18:33 +0000116
mblighde0d47e2008-03-28 14:37:18 +0000117
jadmanskie80d4712008-10-03 16:15:59 +0000118def normalize_hostname(alias):
119 ip = socket.gethostbyname(alias)
120 return socket.gethostbyaddr(ip)[0]
121
122
mblighd6d043c2008-09-27 21:00:45 +0000123def get_ip_local_port_range():
124 match = re.match(r'\s*(\d+)\s*(\d+)\s*$',
125 read_one_line('/proc/sys/net/ipv4/ip_local_port_range'))
126 return (int(match.group(1)), int(match.group(2)))
127
128
129def set_ip_local_port_range(lower, upper):
130 write_one_line('/proc/sys/net/ipv4/ip_local_port_range',
131 '%d %d\n' % (lower, upper))
132
mbligh315b9412008-10-01 03:34:11 +0000133
mbligh45ffc432008-12-09 23:35:17 +0000134
135def send_email(mail_from, mail_to, subject, body):
136 """
137 Sends an email via smtp
138
139 mail_from: string with email address of sender
140 mail_to: string or list with email address(es) of recipients
141 subject: string with subject of email
142 body: (multi-line) string with body of email
143 """
144 if isinstance(mail_to, str):
145 mail_to = [mail_to]
146 msg = "From: %s\nTo: %s\nSubject: %s\n\n%s" % (mail_from, ','.join(mail_to),
147 subject, body)
148 try:
149 mailer = smtplib.SMTP('localhost')
150 try:
151 mailer.sendmail(mail_from, mail_to, msg)
152 finally:
153 mailer.quit()
154 except Exception, e:
155 # Emails are non-critical, not errors, but don't raise them
156 print "Sending email failed. Reason: %s" % repr(e)
157
158
jadmanski5182e162008-05-13 21:48:16 +0000159def read_one_line(filename):
mbligh6e8840c2008-07-11 18:05:49 +0000160 return open(filename, 'r').readline().rstrip('\n')
jadmanski5182e162008-05-13 21:48:16 +0000161
162
mblighb9d05512008-10-18 13:53:27 +0000163def write_one_line(filename, line):
164 open_write_close(filename, line.rstrip('\n') + '\n')
165
166
167def open_write_close(filename, data):
mbligh618ac9e2008-10-06 17:14:32 +0000168 f = open(filename, 'w')
mblighb9d05512008-10-18 13:53:27 +0000169 try:
170 f.write(data)
171 finally:
172 f.close()
jadmanski5182e162008-05-13 21:48:16 +0000173
174
mblighde0d47e2008-03-28 14:37:18 +0000175def read_keyval(path):
jadmanski0afbb632008-06-06 21:10:57 +0000176 """
177 Read a key-value pair format file into a dictionary, and return it.
178 Takes either a filename or directory name as input. If it's a
179 directory name, we assume you want the file to be called keyval.
180 """
181 if os.path.isdir(path):
182 path = os.path.join(path, 'keyval')
183 keyval = {}
jadmanski58962982009-04-21 19:54:34 +0000184 if os.path.exists(path):
185 for line in open(path):
186 line = re.sub('#.*', '', line).rstrip()
187 if not re.search(r'^[-\.\w]+=', line):
188 raise ValueError('Invalid format line: %s' % line)
189 key, value = line.split('=', 1)
190 if re.search('^\d+$', value):
191 value = int(value)
192 elif re.search('^(\d+\.)?\d+$', value):
193 value = float(value)
194 keyval[key] = value
jadmanski0afbb632008-06-06 21:10:57 +0000195 return keyval
mblighde0d47e2008-03-28 14:37:18 +0000196
197
jadmanskicc549172008-05-21 18:11:51 +0000198def write_keyval(path, dictionary, type_tag=None):
jadmanski0afbb632008-06-06 21:10:57 +0000199 """
200 Write a key-value pair format file out to a file. This uses append
201 mode to open the file, so existing text will not be overwritten or
202 reparsed.
jadmanskicc549172008-05-21 18:11:51 +0000203
jadmanski0afbb632008-06-06 21:10:57 +0000204 If type_tag is None, then the key must be composed of alphanumeric
205 characters (or dashes+underscores). However, if type-tag is not
206 null then the keys must also have "{type_tag}" as a suffix. At
207 the moment the only valid values of type_tag are "attr" and "perf".
208 """
209 if os.path.isdir(path):
210 path = os.path.join(path, 'keyval')
211 keyval = open(path, 'a')
jadmanskicc549172008-05-21 18:11:51 +0000212
jadmanski0afbb632008-06-06 21:10:57 +0000213 if type_tag is None:
mbligh97227ea2009-03-11 17:09:50 +0000214 key_regex = re.compile(r'^[-\.\w]+$')
jadmanski0afbb632008-06-06 21:10:57 +0000215 else:
216 if type_tag not in ('attr', 'perf'):
217 raise ValueError('Invalid type tag: %s' % type_tag)
218 escaped_tag = re.escape(type_tag)
mbligh97227ea2009-03-11 17:09:50 +0000219 key_regex = re.compile(r'^[-\.\w]+\{%s\}$' % escaped_tag)
jadmanski0afbb632008-06-06 21:10:57 +0000220 try:
221 for key, value in dictionary.iteritems():
222 if not key_regex.search(key):
223 raise ValueError('Invalid key: %s' % key)
224 keyval.write('%s=%s\n' % (key, value))
225 finally:
226 keyval.close()
mbligh6231cd62008-02-02 19:18:33 +0000227
228
229def is_url(path):
jadmanski0afbb632008-06-06 21:10:57 +0000230 """Return true if path looks like a URL"""
231 # for now, just handle http and ftp
232 url_parts = urlparse.urlparse(path)
233 return (url_parts[0] in ('http', 'ftp'))
mbligh6231cd62008-02-02 19:18:33 +0000234
235
jadmanskied91ba92008-09-30 17:19:27 +0000236def urlopen(url, data=None, proxies=None, timeout=5):
jadmanski0afbb632008-06-06 21:10:57 +0000237 """Wrapper to urllib.urlopen with timeout addition."""
mbligh02ff2d52008-06-03 15:00:21 +0000238
jadmanski0afbb632008-06-06 21:10:57 +0000239 # Save old timeout
240 old_timeout = socket.getdefaulttimeout()
241 socket.setdefaulttimeout(timeout)
242 try:
243 return urllib.urlopen(url, data=data, proxies=proxies)
244 finally:
245 socket.setdefaulttimeout(old_timeout)
mbligh02ff2d52008-06-03 15:00:21 +0000246
247
248def urlretrieve(url, filename=None, reporthook=None, data=None, timeout=300):
jadmanski0afbb632008-06-06 21:10:57 +0000249 """Wrapper to urllib.urlretrieve with timeout addition."""
250 old_timeout = socket.getdefaulttimeout()
251 socket.setdefaulttimeout(timeout)
252 try:
253 return urllib.urlretrieve(url, filename=filename,
254 reporthook=reporthook, data=data)
255 finally:
256 socket.setdefaulttimeout(old_timeout)
257
mbligh02ff2d52008-06-03 15:00:21 +0000258
mbligh6231cd62008-02-02 19:18:33 +0000259def get_file(src, dest, permissions=None):
jadmanski0afbb632008-06-06 21:10:57 +0000260 """Get a file from src, which can be local or a remote URL"""
mbligh25284cd2009-06-08 16:17:24 +0000261 if src == dest:
jadmanski0afbb632008-06-06 21:10:57 +0000262 return
mbligh25284cd2009-06-08 16:17:24 +0000263
264 if is_url(src):
265 logging.debug('PWD: %s', os.getcwd())
266 logging.info('Fetching %s -> %s', src, dest)
267
268 src_file = urllib2.urlopen(src)
jadmanski0afbb632008-06-06 21:10:57 +0000269 try:
mbligh25284cd2009-06-08 16:17:24 +0000270 dest_file = open(dest, 'wb')
271 try:
272 shutil.copyfileobj(src_file, dest_file)
273 finally:
274 dest_file.close()
275 finally:
276 src_file.close()
jadmanski0afbb632008-06-06 21:10:57 +0000277 else:
278 shutil.copyfile(src, dest)
mbligh25284cd2009-06-08 16:17:24 +0000279
jadmanski0afbb632008-06-06 21:10:57 +0000280 if permissions:
281 os.chmod(dest, permissions)
282 return dest
mbligh6231cd62008-02-02 19:18:33 +0000283
284
285def unmap_url(srcdir, src, destdir='.'):
jadmanski0afbb632008-06-06 21:10:57 +0000286 """
287 Receives either a path to a local file or a URL.
288 returns either the path to the local file, or the fetched URL
mbligh6231cd62008-02-02 19:18:33 +0000289
jadmanski0afbb632008-06-06 21:10:57 +0000290 unmap_url('/usr/src', 'foo.tar', '/tmp')
291 = '/usr/src/foo.tar'
292 unmap_url('/usr/src', 'http://site/file', '/tmp')
293 = '/tmp/file'
294 (after retrieving it)
295 """
296 if is_url(src):
297 url_parts = urlparse.urlparse(src)
298 filename = os.path.basename(url_parts[2])
299 dest = os.path.join(destdir, filename)
300 return get_file(src, dest)
301 else:
302 return os.path.join(srcdir, src)
mbligh6231cd62008-02-02 19:18:33 +0000303
304
305def update_version(srcdir, preserve_srcdir, new_version, install,
jadmanski0afbb632008-06-06 21:10:57 +0000306 *args, **dargs):
307 """
308 Make sure srcdir is version new_version
mbligh6231cd62008-02-02 19:18:33 +0000309
jadmanski0afbb632008-06-06 21:10:57 +0000310 If not, delete it and install() the new version.
mbligh6231cd62008-02-02 19:18:33 +0000311
jadmanski0afbb632008-06-06 21:10:57 +0000312 In the preserve_srcdir case, we just check it's up to date,
313 and if not, we rerun install, without removing srcdir
314 """
315 versionfile = os.path.join(srcdir, '.version')
316 install_needed = True
mbligh6231cd62008-02-02 19:18:33 +0000317
jadmanski0afbb632008-06-06 21:10:57 +0000318 if os.path.exists(versionfile):
319 old_version = pickle.load(open(versionfile))
320 if old_version == new_version:
321 install_needed = False
mbligh6231cd62008-02-02 19:18:33 +0000322
jadmanski0afbb632008-06-06 21:10:57 +0000323 if install_needed:
324 if not preserve_srcdir and os.path.exists(srcdir):
325 shutil.rmtree(srcdir)
326 install(*args, **dargs)
327 if os.path.exists(srcdir):
328 pickle.dump(new_version, open(versionfile, 'w'))
mbligh462c0152008-03-13 15:37:10 +0000329
330
mbligh63073c92008-03-31 16:49:32 +0000331def run(command, timeout=None, ignore_status=False,
showard170873e2009-01-07 00:22:26 +0000332 stdout_tee=None, stderr_tee=None, verbose=True, stdin=None):
jadmanski0afbb632008-06-06 21:10:57 +0000333 """
334 Run a command on the host.
mbligh63073c92008-03-31 16:49:32 +0000335
jadmanski0afbb632008-06-06 21:10:57 +0000336 Args:
337 command: the command line string
338 timeout: time limit in seconds before attempting to
339 kill the running process. The run() function
340 will take a few seconds longer than 'timeout'
341 to complete if it has to kill the process.
342 ignore_status: do not raise an exception, no matter what
343 the exit code of the command is.
344 stdout_tee: optional file-like object to which stdout data
345 will be written as it is generated (data will still
346 be stored in result.stdout)
347 stderr_tee: likewise for stderr
showard108d73e2009-06-22 18:14:41 +0000348 verbose: if True, log the command being run
showard170873e2009-01-07 00:22:26 +0000349 stdin: stdin to pass to the executed process
mbligh63073c92008-03-31 16:49:32 +0000350
jadmanski0afbb632008-06-06 21:10:57 +0000351 Returns:
352 a CmdResult object
mbligh63073c92008-03-31 16:49:32 +0000353
jadmanski0afbb632008-06-06 21:10:57 +0000354 Raises:
355 CmdError: the exit code of the command
356 execution was not 0
357 """
showard170873e2009-01-07 00:22:26 +0000358 bg_job = join_bg_jobs(
359 (BgJob(command, stdout_tee, stderr_tee, verbose, stdin=stdin),),
360 timeout)[0]
mbligh849a0f62008-08-28 20:12:19 +0000361 if not ignore_status and bg_job.result.exit_status:
jadmanski9c1098b2008-09-02 14:18:48 +0000362 raise error.CmdError(command, bg_job.result,
mbligh849a0f62008-08-28 20:12:19 +0000363 "Command returned non-zero exit status")
mbligh63073c92008-03-31 16:49:32 +0000364
mbligh849a0f62008-08-28 20:12:19 +0000365 return bg_job.result
mbligh63073c92008-03-31 16:49:32 +0000366
mbligh45ffc432008-12-09 23:35:17 +0000367
mbligha5630a52008-09-03 22:09:50 +0000368def run_parallel(commands, timeout=None, ignore_status=False,
369 stdout_tee=None, stderr_tee=None):
370 """Beahves the same as run with the following exceptions:
371
372 - commands is a list of commands to run in parallel.
373 - ignore_status toggles whether or not an exception should be raised
374 on any error.
375
376 returns a list of CmdResult objects
377 """
378 bg_jobs = []
379 for command in commands:
380 bg_jobs.append(BgJob(command, stdout_tee, stderr_tee))
381
382 # Updates objects in bg_jobs list with their process information
383 join_bg_jobs(bg_jobs, timeout)
384
385 for bg_job in bg_jobs:
386 if not ignore_status and bg_job.result.exit_status:
387 raise error.CmdError(command, bg_job.result,
388 "Command returned non-zero exit status")
389
390 return [bg_job.result for bg_job in bg_jobs]
391
392
mbligh849a0f62008-08-28 20:12:19 +0000393@deprecated
mbligh63073c92008-03-31 16:49:32 +0000394def run_bg(command):
mbligh849a0f62008-08-28 20:12:19 +0000395 """Function deprecated. Please use BgJob class instead."""
396 bg_job = BgJob(command)
397 return bg_job.sp, bg_job.result
mbligh63073c92008-03-31 16:49:32 +0000398
399
mbligh849a0f62008-08-28 20:12:19 +0000400def join_bg_jobs(bg_jobs, timeout=None):
mbligha5630a52008-09-03 22:09:50 +0000401 """Joins the bg_jobs with the current thread.
402
403 Returns the same list of bg_jobs objects that was passed in.
404 """
mblighae69f262009-04-17 20:14:56 +0000405 ret, timeout_error = 0, False
mbligh849a0f62008-08-28 20:12:19 +0000406 for bg_job in bg_jobs:
407 bg_job.output_prepare(StringIO.StringIO(), StringIO.StringIO())
mbligh63073c92008-03-31 16:49:32 +0000408
jadmanski0afbb632008-06-06 21:10:57 +0000409 try:
410 # We are holding ends to stdin, stdout pipes
411 # hence we need to be sure to close those fds no mater what
412 start_time = time.time()
mbligh849a0f62008-08-28 20:12:19 +0000413 timeout_error = _wait_for_commands(bg_jobs, start_time, timeout)
414
415 for bg_job in bg_jobs:
416 # Process stdout and stderr
417 bg_job.process_output(stdout=True,final_read=True)
418 bg_job.process_output(stdout=False,final_read=True)
jadmanski0afbb632008-06-06 21:10:57 +0000419 finally:
420 # close our ends of the pipes to the sp no matter what
mbligh849a0f62008-08-28 20:12:19 +0000421 for bg_job in bg_jobs:
422 bg_job.cleanup()
mbligh63073c92008-03-31 16:49:32 +0000423
mbligh849a0f62008-08-28 20:12:19 +0000424 if timeout_error:
425 # TODO: This needs to be fixed to better represent what happens when
426 # running in parallel. However this is backwards compatable, so it will
427 # do for the time being.
428 raise error.CmdError(bg_jobs[0].command, bg_jobs[0].result,
429 "Command(s) did not complete within %d seconds"
430 % timeout)
mbligh63073c92008-03-31 16:49:32 +0000431
mbligh63073c92008-03-31 16:49:32 +0000432
mbligh849a0f62008-08-28 20:12:19 +0000433 return bg_jobs
mbligh63073c92008-03-31 16:49:32 +0000434
mbligh849a0f62008-08-28 20:12:19 +0000435
436def _wait_for_commands(bg_jobs, start_time, timeout):
437 # This returns True if it must return due to a timeout, otherwise False.
438
mblighf0b4a0a2008-09-03 20:46:16 +0000439 # To check for processes which terminate without producing any output
440 # a 1 second timeout is used in select.
441 SELECT_TIMEOUT = 1
442
mbligh849a0f62008-08-28 20:12:19 +0000443 select_list = []
444 reverse_dict = {}
445 for bg_job in bg_jobs:
446 select_list.append(bg_job.sp.stdout)
447 select_list.append(bg_job.sp.stderr)
448 reverse_dict[bg_job.sp.stdout] = (bg_job,True)
449 reverse_dict[bg_job.sp.stderr] = (bg_job,False)
450
jadmanski0afbb632008-06-06 21:10:57 +0000451 if timeout:
452 stop_time = start_time + timeout
453 time_left = stop_time - time.time()
454 else:
455 time_left = None # so that select never times out
456 while not timeout or time_left > 0:
457 # select will return when stdout is ready (including when it is
458 # EOF, that is the process has terminated).
mblighf0b4a0a2008-09-03 20:46:16 +0000459 ready, _, _ = select.select(select_list, [], [], SELECT_TIMEOUT)
mbligh849a0f62008-08-28 20:12:19 +0000460
jadmanski0afbb632008-06-06 21:10:57 +0000461 # os.read() has to be used instead of
462 # subproc.stdout.read() which will otherwise block
mbligh849a0f62008-08-28 20:12:19 +0000463 for fileno in ready:
464 bg_job,stdout = reverse_dict[fileno]
465 bg_job.process_output(stdout)
mbligh63073c92008-03-31 16:49:32 +0000466
mbligh849a0f62008-08-28 20:12:19 +0000467 remaining_jobs = [x for x in bg_jobs if x.result.exit_status is None]
468 if len(remaining_jobs) == 0:
469 return False
470 for bg_job in remaining_jobs:
471 bg_job.result.exit_status = bg_job.sp.poll()
mbligh8ea61e22008-05-09 18:09:37 +0000472
jadmanski0afbb632008-06-06 21:10:57 +0000473 if timeout:
474 time_left = stop_time - time.time()
mbligh63073c92008-03-31 16:49:32 +0000475
mbligh849a0f62008-08-28 20:12:19 +0000476 # Kill all processes which did not complete prior to timeout
477 for bg_job in [x for x in bg_jobs if x.result.exit_status is None]:
mbligh7afc3a62008-11-27 00:35:44 +0000478 print '* Warning: run process timeout (%s) fired' % timeout
mbligh849a0f62008-08-28 20:12:19 +0000479 nuke_subprocess(bg_job.sp)
mbligh095dc642008-10-01 03:41:35 +0000480 bg_job.result.exit_status = bg_job.sp.poll()
mbligh8ea61e22008-05-09 18:09:37 +0000481
mbligh849a0f62008-08-28 20:12:19 +0000482 return True
mbligh63073c92008-03-31 16:49:32 +0000483
484
mbligh63073c92008-03-31 16:49:32 +0000485def nuke_subprocess(subproc):
jadmanski09f92032008-09-17 14:05:27 +0000486 # check if the subprocess is still alive, first
487 if subproc.poll() is not None:
488 return subproc.poll()
489
jadmanski0afbb632008-06-06 21:10:57 +0000490 # the process has not terminated within timeout,
491 # kill it via an escalating series of signals.
492 signal_queue = [signal.SIGTERM, signal.SIGKILL]
493 for sig in signal_queue:
494 try:
495 os.kill(subproc.pid, sig)
496 # The process may have died before we could kill it.
497 except OSError:
498 pass
mbligh63073c92008-03-31 16:49:32 +0000499
jadmanski0afbb632008-06-06 21:10:57 +0000500 for i in range(5):
501 rc = subproc.poll()
mblighd876f452008-12-03 15:09:17 +0000502 if rc is not None:
jadmanski0afbb632008-06-06 21:10:57 +0000503 return rc
504 time.sleep(1)
mbligh63073c92008-03-31 16:49:32 +0000505
506
507def nuke_pid(pid):
jadmanski0afbb632008-06-06 21:10:57 +0000508 # the process has not terminated within timeout,
509 # kill it via an escalating series of signals.
510 signal_queue = [signal.SIGTERM, signal.SIGKILL]
511 for sig in signal_queue:
512 try:
513 os.kill(pid, sig)
mbligh63073c92008-03-31 16:49:32 +0000514
jadmanski0afbb632008-06-06 21:10:57 +0000515 # The process may have died before we could kill it.
516 except OSError:
517 pass
mbligh63073c92008-03-31 16:49:32 +0000518
jadmanski0afbb632008-06-06 21:10:57 +0000519 try:
520 for i in range(5):
521 status = os.waitpid(pid, os.WNOHANG)[0]
522 if status == pid:
523 return
524 time.sleep(1)
mbligh63073c92008-03-31 16:49:32 +0000525
jadmanski0afbb632008-06-06 21:10:57 +0000526 if status != pid:
527 raise error.AutoservRunError('Could not kill %d'
528 % pid, None)
mbligh63073c92008-03-31 16:49:32 +0000529
jadmanski0afbb632008-06-06 21:10:57 +0000530 # the process died before we join it.
531 except OSError:
532 pass
mbligh63073c92008-03-31 16:49:32 +0000533
534
mbligh298ed592009-06-15 21:52:57 +0000535def pid_is_alive(pid):
536 """
537 True if process pid exists and is not yet stuck in Zombie state.
538 Zombies are impossible to move between cgroups, etc.
539 pid can be integer, or text of integer.
540 """
541 try:
mbligh277a0e42009-07-11 00:11:45 +0000542 stat = read_one_line('/proc/%s/stat' % pid) # pid exists
mbligh298ed592009-06-15 21:52:57 +0000543 return stat.split()[2] != 'Z' # and is not in Zombie state
544 except Exception:
545 return False # process no longer exists at all
546
547
mbligh63073c92008-03-31 16:49:32 +0000548def system(command, timeout=None, ignore_status=False):
mbligha5630a52008-09-03 22:09:50 +0000549 """This function returns the exit status of command."""
mblighf8dffb12008-10-29 16:45:26 +0000550 return run(command, timeout=timeout, ignore_status=ignore_status,
showard108d73e2009-06-22 18:14:41 +0000551 stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS).exit_status
mbligh63073c92008-03-31 16:49:32 +0000552
553
mbligha5630a52008-09-03 22:09:50 +0000554def system_parallel(commands, timeout=None, ignore_status=False):
555 """This function returns a list of exit statuses for the respective
556 list of commands."""
557 return [bg_jobs.exit_status for bg_jobs in
mblighf8dffb12008-10-29 16:45:26 +0000558 run_parallel(commands, timeout=timeout, ignore_status=ignore_status,
showard108d73e2009-06-22 18:14:41 +0000559 stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS)]
mbligh849a0f62008-08-28 20:12:19 +0000560
561
mbligh8ea61e22008-05-09 18:09:37 +0000562def system_output(command, timeout=None, ignore_status=False,
jadmanski0afbb632008-06-06 21:10:57 +0000563 retain_output=False):
564 if retain_output:
mblighf8dffb12008-10-29 16:45:26 +0000565 out = run(command, timeout=timeout, ignore_status=ignore_status,
showard108d73e2009-06-22 18:14:41 +0000566 stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS).stdout
jadmanski0afbb632008-06-06 21:10:57 +0000567 else:
mblighf8dffb12008-10-29 16:45:26 +0000568 out = run(command, timeout=timeout, ignore_status=ignore_status).stdout
jadmanski0afbb632008-06-06 21:10:57 +0000569 if out[-1:] == '\n': out = out[:-1]
570 return out
mbligh63073c92008-03-31 16:49:32 +0000571
mbligh849a0f62008-08-28 20:12:19 +0000572
mbligha5630a52008-09-03 22:09:50 +0000573def system_output_parallel(commands, timeout=None, ignore_status=False,
574 retain_output=False):
575 if retain_output:
showard108d73e2009-06-22 18:14:41 +0000576 out = [bg_job.stdout for bg_job
577 in run_parallel(commands, timeout=timeout,
578 ignore_status=ignore_status,
579 stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS)]
mbligha5630a52008-09-03 22:09:50 +0000580 else:
mblighf8dffb12008-10-29 16:45:26 +0000581 out = [bg_job.stdout for bg_job in run_parallel(commands,
582 timeout=timeout, ignore_status=ignore_status)]
mbligha5630a52008-09-03 22:09:50 +0000583 for x in out:
584 if out[-1:] == '\n': out = out[:-1]
585 return out
586
587
mbligh98467952008-11-19 00:25:45 +0000588def strip_unicode(input):
589 if type(input) == list:
590 return [strip_unicode(i) for i in input]
591 elif type(input) == dict:
592 output = {}
593 for key in input.keys():
594 output[str(key)] = strip_unicode(input[key])
595 return output
596 elif type(input) == unicode:
597 return str(input)
598 else:
599 return input
600
601
mbligha5630a52008-09-03 22:09:50 +0000602def get_cpu_percentage(function, *args, **dargs):
603 """Returns a tuple containing the CPU% and return value from function call.
604
605 This function calculates the usage time by taking the difference of
606 the user and system times both before and after the function call.
607 """
608 child_pre = resource.getrusage(resource.RUSAGE_CHILDREN)
609 self_pre = resource.getrusage(resource.RUSAGE_SELF)
610 start = time.time()
611 to_return = function(*args, **dargs)
612 elapsed = time.time() - start
613 self_post = resource.getrusage(resource.RUSAGE_SELF)
614 child_post = resource.getrusage(resource.RUSAGE_CHILDREN)
615
616 # Calculate CPU Percentage
617 s_user, s_system = [a - b for a, b in zip(self_post, self_pre)[:2]]
618 c_user, c_system = [a - b for a, b in zip(child_post, child_pre)[:2]]
619 cpu_percent = (s_user + c_user + s_system + c_system) / elapsed
620
621 return cpu_percent, to_return
622
623
mblighc1cbc992008-05-27 20:01:45 +0000624"""
625This function is used when there is a need to run more than one
626job simultaneously starting exactly at the same time. It basically returns
627a modified control file (containing the synchronization code prepended)
628whenever it is ready to run the control file. The synchronization
629is done using barriers to make sure that the jobs start at the same time.
630
631Here is how the synchronization is done to make sure that the tests
632start at exactly the same time on the client.
633sc_bar is a server barrier and s_bar, c_bar are the normal barriers
634
635 Job1 Job2 ...... JobN
636 Server: | sc_bar
637 Server: | s_bar ...... s_bar
638 Server: | at.run() at.run() ...... at.run()
639 ----------|------------------------------------------------------
640 Client | sc_bar
641 Client | c_bar c_bar ...... c_bar
642 Client | <run test> <run test> ...... <run test>
643
644
645PARAMS:
646 control_file : The control file which to which the above synchronization
647 code would be prepended to
648 host_name : The host name on which the job is going to run
649 host_num (non negative) : A number to identify the machine so that we have
650 different sets of s_bar_ports for each of the machines.
651 instance : The number of the job
652 num_jobs : Total number of jobs that are going to run in parallel with
653 this job starting at the same time
654 port_base : Port number that is used to derive the actual barrier ports.
655
656RETURN VALUE:
657 The modified control file.
658
659"""
660def get_sync_control_file(control, host_name, host_num,
jadmanski0afbb632008-06-06 21:10:57 +0000661 instance, num_jobs, port_base=63100):
662 sc_bar_port = port_base
663 c_bar_port = port_base
664 if host_num < 0:
665 print "Please provide a non negative number for the host"
666 return None
667 s_bar_port = port_base + 1 + host_num # The set of s_bar_ports are
668 # the same for a given machine
mblighc1cbc992008-05-27 20:01:45 +0000669
jadmanski0afbb632008-06-06 21:10:57 +0000670 sc_bar_timeout = 180
671 s_bar_timeout = c_bar_timeout = 120
mblighc1cbc992008-05-27 20:01:45 +0000672
jadmanski0afbb632008-06-06 21:10:57 +0000673 # The barrier code snippet is prepended into the conrol file
674 # dynamically before at.run() is called finally.
675 control_new = []
mblighc1cbc992008-05-27 20:01:45 +0000676
jadmanski0afbb632008-06-06 21:10:57 +0000677 # jobid is the unique name used to identify the processes
678 # trying to reach the barriers
679 jobid = "%s#%d" % (host_name, instance)
mblighc1cbc992008-05-27 20:01:45 +0000680
jadmanski0afbb632008-06-06 21:10:57 +0000681 rendv = []
682 # rendvstr is a temp holder for the rendezvous list of the processes
683 for n in range(num_jobs):
684 rendv.append("'%s#%d'" % (host_name, n))
685 rendvstr = ",".join(rendv)
mblighc1cbc992008-05-27 20:01:45 +0000686
jadmanski0afbb632008-06-06 21:10:57 +0000687 if instance == 0:
688 # Do the setup and wait at the server barrier
689 # Clean up the tmp and the control dirs for the first instance
690 control_new.append('if os.path.exists(job.tmpdir):')
691 control_new.append("\t system('umount -f %s > /dev/null"
692 "2> /dev/null' % job.tmpdir,"
693 "ignore_status=True)")
694 control_new.append("\t system('rm -rf ' + job.tmpdir)")
695 control_new.append(
696 'b0 = job.barrier("%s", "sc_bar", %d, port=%d)'
697 % (jobid, sc_bar_timeout, sc_bar_port))
698 control_new.append(
mbligh9c12f772009-06-22 19:03:55 +0000699 'b0.rendezvous_servers("PARALLEL_MASTER", "%s")'
jadmanski0afbb632008-06-06 21:10:57 +0000700 % jobid)
mblighc1cbc992008-05-27 20:01:45 +0000701
jadmanski0afbb632008-06-06 21:10:57 +0000702 elif instance == 1:
703 # Wait at the server barrier to wait for instance=0
704 # process to complete setup
705 b0 = barrier.barrier("PARALLEL_MASTER", "sc_bar", sc_bar_timeout,
706 port=sc_bar_port)
mbligh9c12f772009-06-22 19:03:55 +0000707 b0.rendezvous_servers("PARALLEL_MASTER", jobid)
mblighc1cbc992008-05-27 20:01:45 +0000708
jadmanski0afbb632008-06-06 21:10:57 +0000709 if(num_jobs > 2):
710 b1 = barrier.barrier(jobid, "s_bar", s_bar_timeout,
711 port=s_bar_port)
mbligh9c12f772009-06-22 19:03:55 +0000712 b1.rendezvous(rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000713
jadmanski0afbb632008-06-06 21:10:57 +0000714 else:
715 # For the rest of the clients
716 b2 = barrier.barrier(jobid, "s_bar", s_bar_timeout, port=s_bar_port)
mbligh9c12f772009-06-22 19:03:55 +0000717 b2.rendezvous(rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000718
jadmanski0afbb632008-06-06 21:10:57 +0000719 # Client side barrier for all the tests to start at the same time
720 control_new.append('b1 = job.barrier("%s", "c_bar", %d, port=%d)'
721 % (jobid, c_bar_timeout, c_bar_port))
mbligh9c12f772009-06-22 19:03:55 +0000722 control_new.append("b1.rendezvous(%s)" % rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000723
jadmanski0afbb632008-06-06 21:10:57 +0000724 # Stick in the rest of the control file
725 control_new.append(control)
mblighc1cbc992008-05-27 20:01:45 +0000726
jadmanski0afbb632008-06-06 21:10:57 +0000727 return "\n".join(control_new)
mblighc1cbc992008-05-27 20:01:45 +0000728
mbligh63073c92008-03-31 16:49:32 +0000729
mblighc5ddfd12008-08-04 17:15:00 +0000730def get_arch(run_function=run):
731 """
732 Get the hardware architecture of the machine.
733 run_function is used to execute the commands. It defaults to
734 utils.run() but a custom method (if provided) should be of the
735 same schema as utils.run. It should return a CmdResult object and
736 throw a CmdError exception.
737 """
738 arch = run_function('/bin/uname -m').stdout.rstrip()
739 if re.match(r'i\d86$', arch):
740 arch = 'i386'
741 return arch
742
743
showard4745ecd2009-05-26 19:34:56 +0000744def get_num_logical_cpus_per_socket(run_function=run):
mbligh9fd9afe2009-04-28 18:27:25 +0000745 """
746 Get the number of cores (including hyperthreading) per cpu.
747 run_function is used to execute the commands. It defaults to
748 utils.run() but a custom method (if provided) should be of the
749 same schema as utils.run. It should return a CmdResult object and
750 throw a CmdError exception.
751 """
showard4745ecd2009-05-26 19:34:56 +0000752 siblings = run_function('grep "^siblings" /proc/cpuinfo').stdout.rstrip()
753 num_siblings = map(int,
754 re.findall(r'^siblings\s*:\s*(\d+)\s*$',
755 siblings, re.M))
756 if len(num_siblings) == 0:
757 raise error.TestError('Unable to find siblings info in /proc/cpuinfo')
758 if min(num_siblings) != max(num_siblings):
759 raise error.TestError('Number of siblings differ %r' %
760 num_siblings)
761 return num_siblings[0]
mbligh9fd9afe2009-04-28 18:27:25 +0000762
763
jadmanski4f909252008-12-01 20:47:10 +0000764def merge_trees(src, dest):
765 """
766 Merges a source directory tree at 'src' into a destination tree at
767 'dest'. If a path is a file in both trees than the file in the source
768 tree is APPENDED to the one in the destination tree. If a path is
769 a directory in both trees then the directories are recursively merged
770 with this function. In any other case, the function will skip the
771 paths that cannot be merged (instead of failing).
772 """
773 if not os.path.exists(src):
774 return # exists only in dest
775 elif not os.path.exists(dest):
776 if os.path.isfile(src):
777 shutil.copy2(src, dest) # file only in src
778 else:
779 shutil.copytree(src, dest, symlinks=True) # dir only in src
780 return
781 elif os.path.isfile(src) and os.path.isfile(dest):
782 # src & dest are files in both trees, append src to dest
783 destfile = open(dest, "a")
784 try:
785 srcfile = open(src)
786 try:
787 destfile.write(srcfile.read())
788 finally:
789 srcfile.close()
790 finally:
791 destfile.close()
792 elif os.path.isdir(src) and os.path.isdir(dest):
793 # src & dest are directories in both trees, so recursively merge
794 for name in os.listdir(src):
795 merge_trees(os.path.join(src, name), os.path.join(dest, name))
796 else:
797 # src & dest both exist, but are incompatible
798 return
799
800
mbligh63073c92008-03-31 16:49:32 +0000801class CmdResult(object):
jadmanski0afbb632008-06-06 21:10:57 +0000802 """
803 Command execution result.
mbligh63073c92008-03-31 16:49:32 +0000804
jadmanski0afbb632008-06-06 21:10:57 +0000805 command: String containing the command line itself
806 exit_status: Integer exit code of the process
807 stdout: String containing stdout of the process
808 stderr: String containing stderr of the process
809 duration: Elapsed wall clock time running the process
810 """
mbligh63073c92008-03-31 16:49:32 +0000811
812
mblighcd63a212009-05-01 23:04:38 +0000813 def __init__(self, command="", stdout="", stderr="",
jadmanski0afbb632008-06-06 21:10:57 +0000814 exit_status=None, duration=0):
815 self.command = command
816 self.exit_status = exit_status
817 self.stdout = stdout
818 self.stderr = stderr
819 self.duration = duration
mbligh63073c92008-03-31 16:49:32 +0000820
821
jadmanski0afbb632008-06-06 21:10:57 +0000822 def __repr__(self):
823 wrapper = textwrap.TextWrapper(width = 78,
824 initial_indent="\n ",
825 subsequent_indent=" ")
826
827 stdout = self.stdout.rstrip()
828 if stdout:
829 stdout = "\nstdout:\n%s" % stdout
830
831 stderr = self.stderr.rstrip()
832 if stderr:
833 stderr = "\nstderr:\n%s" % stderr
834
835 return ("* Command: %s\n"
836 "Exit status: %s\n"
837 "Duration: %s\n"
838 "%s"
839 "%s"
840 % (wrapper.fill(self.command), self.exit_status,
841 self.duration, stdout, stderr))
mbligh63073c92008-03-31 16:49:32 +0000842
843
mbligh462c0152008-03-13 15:37:10 +0000844class run_randomly:
jadmanski0afbb632008-06-06 21:10:57 +0000845 def __init__(self, run_sequentially=False):
846 # Run sequentially is for debugging control files
847 self.test_list = []
848 self.run_sequentially = run_sequentially
mbligh462c0152008-03-13 15:37:10 +0000849
850
jadmanski0afbb632008-06-06 21:10:57 +0000851 def add(self, *args, **dargs):
852 test = (args, dargs)
853 self.test_list.append(test)
mbligh462c0152008-03-13 15:37:10 +0000854
855
jadmanski0afbb632008-06-06 21:10:57 +0000856 def run(self, fn):
857 while self.test_list:
858 test_index = random.randint(0, len(self.test_list)-1)
859 if self.run_sequentially:
860 test_index = 0
861 (args, dargs) = self.test_list.pop(test_index)
862 fn(*args, **dargs)
mbligha7007722009-01-13 00:37:11 +0000863
864
mblighdd669372009-02-03 21:57:18 +0000865def import_site_symbol(path, module, name, dummy=None, modulefile=None):
866 """
867 Try to import site specific symbol from site specific file if it exists
868
869 @param path full filename of the source file calling this (ie __file__)
870 @param module full module name
871 @param name symbol name to be imported from the site file
872 @param dummy dummy value to return in case there is no symbol to import
873 @param modulefile module filename
874
875 @return site specific symbol or dummy
876
877 @exception ImportError if the site file exists but imports fails
878 """
mbligha7007722009-01-13 00:37:11 +0000879 short_module = module[module.rfind(".") + 1:]
880
881 if not modulefile:
882 modulefile = short_module + ".py"
883
884 try:
885 site_exists = os.path.getsize(os.path.join(os.path.dirname(path),
886 modulefile))
887 except os.error:
888 site_exists = False
889
mbligh61f4e442009-06-08 16:48:20 +0000890 msg = None
mbligha7007722009-01-13 00:37:11 +0000891 if site_exists:
mbligh61f4e442009-06-08 16:48:20 +0000892 # special unique value to tell us if the symbol can't be imported
893 cant_import = object()
894
mbligh062ed152009-01-13 00:57:14 +0000895 # return the object from the imported module
mbligh61f4e442009-06-08 16:48:20 +0000896 obj = getattr(__import__(module, {}, {}, [short_module]), name,
897 cant_import)
898 if obj is cant_import:
899 msg = ("unable to import site symbol '%s', using non-site "
900 "implementation") % name
mbligha7007722009-01-13 00:37:11 +0000901 else:
jadmanski0dc8ff82009-02-11 15:11:15 +0000902 msg = "unable to import site module '%s', using non-site implementation"
903 msg %= modulefile
mbligh61f4e442009-06-08 16:48:20 +0000904
905 if msg:
showardb18134f2009-03-20 20:52:18 +0000906 logging.info(msg)
mbligh062ed152009-01-13 00:57:14 +0000907 obj = dummy
908
909 return obj
910
911
912def import_site_class(path, module, classname, baseclass, modulefile=None):
913 """
914 Try to import site specific class from site specific file if it exists
915
916 Args:
917 path: full filename of the source file calling this (ie __file__)
918 module: full module name
919 classname: class name to be loaded from site file
mbligh0a8c3322009-04-28 18:32:19 +0000920 baseclass: base class object to return when no site file present or
921 to mixin when site class exists but is not inherited from baseclass
mbligh062ed152009-01-13 00:57:14 +0000922 modulefile: module filename
923
mbligh0a8c3322009-04-28 18:32:19 +0000924 Returns: baseclass if site specific class does not exist, the site specific
925 class if it exists and is inherited from baseclass or a mixin of the
926 site specific class and baseclass when the site specific class exists
927 and is not inherited from baseclass
mbligh062ed152009-01-13 00:57:14 +0000928
929 Raises: ImportError if the site file exists but imports fails
930 """
931
mblighdd669372009-02-03 21:57:18 +0000932 res = import_site_symbol(path, module, classname, None, modulefile)
mbligh0a8c3322009-04-28 18:32:19 +0000933 if res:
934 if not issubclass(res, baseclass):
935 # if not a subclass of baseclass then mix in baseclass with the
936 # site specific class object and return the result
937 res = type(classname, (res, baseclass), {})
938 else:
939 res = baseclass
mbligha7007722009-01-13 00:37:11 +0000940
mbligh062ed152009-01-13 00:57:14 +0000941 return res
942
943
944def import_site_function(path, module, funcname, dummy, modulefile=None):
945 """
946 Try to import site specific function from site specific file if it exists
947
948 Args:
949 path: full filename of the source file calling this (ie __file__)
950 module: full module name
951 funcname: function name to be imported from site file
952 dummy: dummy function to return in case there is no function to import
953 modulefile: module filename
954
955 Returns: site specific function object or dummy
956
957 Raises: ImportError if the site file exists but imports fails
958 """
959
mblighdd669372009-02-03 21:57:18 +0000960 return import_site_symbol(path, module, funcname, dummy, modulefile)
mblighfb676032009-04-01 18:25:38 +0000961
962
963def write_pid(program_name):
964 """
965 Try to drop <program_name>.pid in the main autotest directory.
966
967 Args:
968 program_name: prefix for file name
969 """
970
971 my_path = os.path.dirname(__file__)
972 pid_path = os.path.abspath(os.path.join(my_path, "../.."))
973 pidf = open(os.path.join(pid_path, "%s.pid" % program_name), "w")
974 if pidf:
975 pidf.write("%s\n" % os.getpid())
976 pidf.close()
mbligh45561782009-05-11 21:14:34 +0000977
978
979def get_relative_path(path, reference):
980 """Given 2 absolute paths "path" and "reference", compute the path of
981 "path" as relative to the directory "reference".
982
983 @param path the absolute path to convert to a relative path
984 @param reference an absolute directory path to which the relative
985 path will be computed
986 """
987 # normalize the paths (remove double slashes, etc)
988 assert(os.path.isabs(path))
989 assert(os.path.isabs(reference))
990
991 path = os.path.normpath(path)
992 reference = os.path.normpath(reference)
993
994 # we could use os.path.split() but it splits from the end
995 path_list = path.split(os.path.sep)[1:]
996 ref_list = reference.split(os.path.sep)[1:]
997
998 # find the longest leading common path
999 for i in xrange(min(len(path_list), len(ref_list))):
1000 if path_list[i] != ref_list[i]:
1001 # decrement i so when exiting this loop either by no match or by
1002 # end of range we are one step behind
1003 i -= 1
1004 break
1005 i += 1
1006 # drop the common part of the paths, not interested in that anymore
1007 del path_list[:i]
1008
1009 # for each uncommon component in the reference prepend a ".."
1010 path_list[:0] = ['..'] * (len(ref_list) - i)
1011
1012 return os.path.join(*path_list)
mbligh277a0e42009-07-11 00:11:45 +00001013
1014
1015def sh_escape(command):
1016 """
1017 Escape special characters from a command so that it can be passed
1018 as a double quoted (" ") string in a (ba)sh command.
1019
1020 Args:
1021 command: the command string to escape.
1022
1023 Returns:
1024 The escaped command string. The required englobing double
1025 quotes are NOT added and so should be added at some point by
1026 the caller.
1027
1028 See also: http://www.tldp.org/LDP/abs/html/escapingsection.html
1029 """
1030 command = command.replace("\\", "\\\\")
1031 command = command.replace("$", r'\$')
1032 command = command.replace('"', r'\"')
1033 command = command.replace('`', r'\`')
1034 return command