Add initial version of autoserv
git-svn-id: http://test.kernel.org/svn/autotest/trunk@557 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/server/utils.py b/server/utils.py
new file mode 100644
index 0000000..9ed6218
--- /dev/null
+++ b/server/utils.py
@@ -0,0 +1,248 @@
+#!/usr/bin/python
+#
+# Copyright 2007 Google Inc. Released under the GPL v2
+
+"""Miscellaneous small functions.
+"""
+
+__author__ = """mbligh@google.com (Martin J. Bligh),
+poirier@google.com (Benjamin Poirier),
+stutsman@google.com (Ryan Stutsman)"""
+
+
+import atexit
+import os
+import os.path
+import select
+import shutil
+import signal
+import StringIO
+import subprocess
+import tempfile
+import time
+import types
+import urllib
+
+import hosts
+import errors
+
+
+__tmp_dirs= []
+
+
+def sh_escape(command):
+ """Escape special characters from a command so that it can be passed
+ as a double quoted (" ") string.
+
+ Args:
+ command: the command string to escape.
+
+ Returns:
+ The escaped command string. The required englobing double
+ quotes are NOT added and so should be added at some point by
+ the caller.
+
+ See also: http://www.tldp.org/LDP/abs/html/escapingsection.html
+ """
+ command= command.replace("\\", "\\\\")
+ command= command.replace("$", r'\$')
+ command= command.replace('"', r'\"')
+ command= command.replace('`', r'\`')
+ return command
+
+
+def scp_remote_escape(filename):
+ """Escape special characters from a filename so that it can be passed
+ to scp (within double quotes) as a remote file.
+
+ Bis-quoting has to be used with scp for remote files, "bis-quoting"
+ as in quoting x 2
+ scp does not support a newline in the filename
+
+ Args:
+ filename: the filename string to escape.
+
+ Returns:
+ The escaped filename string. The required englobing double
+ quotes are NOT added and so should be added at some point by
+ the caller.
+ """
+ escape_chars= r' !"$&' "'" r'()*,:;<=>?[\]^`{|}'
+
+ new_name= []
+ for char in filename:
+ if char in escape_chars:
+ new_name.append("\\%s" % (char,))
+ else:
+ new_name.append(char)
+
+ return sh_escape("".join(new_name))
+
+
+def get(location):
+ """Get a file or directory to a local temporary directory.
+
+ Args:
+ location: the source of the material to get. This source may
+ be one of:
+ * a local file or directory
+ * a URL (http or ftp)
+ * a python file-like object
+
+ Returns:
+ The location of the file or directory where the requested
+ content was saved. This will be contained in a temporary
+ directory on the local host.
+ """
+ tmpdir = get_tmp_dir()
+
+ # location is a file-like object
+ if hasattr(location, "read"):
+ tmpfile = os.path.join(tmpdir, "file")
+ tmpfileobj = file(tmpfile, 'w')
+ shutil.copyfileobj(location, tmpfileobj)
+ tmpfileobj.close()
+ return tmpfile
+
+ if isinstance(location, types.StringTypes):
+ # location is a URL
+ if location.startswith('http') or location.startswith('ftp'):
+ tmpfile = os.path.join(tmpdir, os.path.basename(location))
+ urllib.urlretrieve(location, tmpfile)
+ return tmpfile
+ # location is a local path
+ elif os.path.exists(os.path.abspath(location)):
+ tmpfile = os.path.join(tmpdir, os.path.basename(location))
+ if os.path.isdir(location):
+ tmpfile += '/'
+ shutil.copytree(location, tmpfile, symlinks=True)
+ return tmpfile
+ shutil.copyfile(location, tmpfile)
+ return tmpfile
+ # location is just a string, dump it to a file
+ else:
+ tmpfd, tmpfile = tempfile.mkstemp(dir=tmpdir)
+ tmpfileobj = os.fdopen(tmpfd, 'w')
+ tmpfileobj.write(location)
+ tmpfileobj.close()
+ return tmpfile
+
+
+def run(command, timeout=None):
+ """Run a command on the host.
+
+ Args:
+ command: the command line string
+ timeout: time limit in seconds before attempting to
+ kill the running process. The run() function
+ will take a few seconds longer than 'timeout'
+ to complete if it has to kill the process.
+
+ Returns:
+ a hosts.CmdResult object
+
+ Raises:
+ AutoservRunError: the exit code of the command
+ execution was not 0
+
+ TODO(poirier): Add a "tee" option to send the command's
+ stdout and stderr to python's stdout and stderr? At
+ the moment, there is no way to see the command's
+ output as it is running.
+ TODO(poirier): Should a timeout raise an exception? Should
+ exceptions be raised at all?
+ """
+ result= hosts.CmdResult()
+ result.command= command
+ sp= subprocess.Popen(command, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, close_fds=True, shell=True,
+ executable="/bin/bash")
+
+ start_time= time.time()
+ if timeout:
+ stop_time= start_time + timeout
+ time_left= stop_time - time.time()
+ while time_left > 0:
+ # select will return when stdout is ready
+ # (including when it is EOF, that is the
+ # process has terminated).
+ (retval, tmp, tmp) = select.select(
+ [sp.stdout], [], [], time_left)
+ if len(retval):
+ # os.read() has to be used instead of
+ # sp.stdout.read() which will
+ # otherwise block
+ result.stdout += os.read(
+ sp.stdout.fileno(), 1024)
+
+ (pid, exit_status_indication) = os.waitpid(
+ sp.pid, os.WNOHANG)
+ if pid:
+ stop_time= time.time()
+ time_left= stop_time - time.time()
+
+ # the process has not terminated within timeout,
+ # kill it via an escalating series of signals.
+ if not pid:
+ signal_queue = [signal.SIGTERM, signal.SIGKILL]
+ for sig in signal_queue:
+ try:
+ os.kill(sp.pid, sig)
+ # handle race condition in which
+ # process died before we could kill it.
+ except OSError:
+ pass
+
+ for i in range(5):
+ (pid, exit_status_indication
+ ) = os.waitpid(sp.pid,
+ os.WNOHANG)
+ if pid:
+ break
+ else:
+ time.sleep(1)
+ if pid:
+ break
+ else:
+ exit_status_indication = os.waitpid(sp.pid, 0)[1]
+
+ result.duration = time.time() - start_time
+ result.aborted = exit_status_indication & 127
+ if result.aborted:
+ result.exit_status= None
+ else:
+ result.exit_status= exit_status_indication / 256
+ result.stdout += sp.stdout.read()
+ result.stderr = sp.stderr.read()
+
+ if result.exit_status > 0:
+ raise errors.AutoservRunError("command execution error",
+ result)
+
+ return result
+
+
+def get_tmp_dir():
+ """Return the pathname of a directory on the host suitable
+ for temporary file storage.
+
+ The directory and its content will be deleted automatically
+ at the end of the program execution if they are still present.
+ """
+ global __tmp_dirs
+
+ dir_name= tempfile.mkdtemp(prefix="autoserv-")
+ __tmp_dirs.append(dir_name)
+ return dir_name
+
+
+@atexit.register
+def __clean_tmp_dirs():
+ """Erase temporary directories that were created by the get_tmp_dir()
+ function and that are still present.
+ """
+ global __tmp_dirs
+
+ for dir in __tmp_dirs:
+ shutil.rmtree(dir)
+ __tmp_dirs= []