Remove the obsolete EvacuateNode OpCode/LU
[ganeti-local] / lib / utils.py
index ffb8b70..b19e5a4 100644 (file)
@@ -81,6 +81,8 @@ X509_SIGNATURE = re.compile(r"^%s:\s*(?P<salt>%s+)/(?P<sign>%s+)$" %
                              HEX_CHAR_RE, HEX_CHAR_RE),
                             re.S | re.I)
 
+_VALID_SERVICE_NAME_RE = re.compile("^[-_.a-zA-Z0-9]{1,128}$")
+
 # Structure definition for getsockopt(SOL_SOCKET, SO_PEERCRED, ...):
 # struct ucred { pid_t pid; uid_t uid; gid_t gid; };
 #
@@ -1127,8 +1129,9 @@ class HostInfo:
     """
     try:
       result = socket.gethostbyname_ex(hostname)
-    except socket.gaierror, err:
-      # hostname not found in DNS
+    except (socket.gaierror, socket.herror, socket.error), err:
+      # hostname not found in DNS, or other socket exception in the
+      # (code, description format)
       raise errors.ResolverError(hostname, err.args[0], err.args[1])
 
     return result
@@ -1155,6 +1158,30 @@ class HostInfo:
     return hostname
 
 
+def ValidateServiceName(name):
+  """Validate the given service name.
+
+  @type name: number or string
+  @param name: Service name or port specification
+
+  """
+  try:
+    numport = int(name)
+  except (ValueError, TypeError):
+    # Non-numeric service name
+    valid = _VALID_SERVICE_NAME_RE.match(name)
+  else:
+    # Numeric port (protocols other than TCP or UDP might need adjustments
+    # here)
+    valid = (numport >= 0 and numport < (1 << 16))
+
+  if not valid:
+    raise errors.OpPrereqError("Invalid service name '%s'" % name,
+                               errors.ECODE_INVAL)
+
+  return name
+
+
 def GetHostInfo(name=None):
   """Lookup host name and raise an OpPrereqError for failures"""
 
@@ -1729,7 +1756,6 @@ def ListVisibleFiles(path):
     raise errors.ProgrammerError("Path passed to ListVisibleFiles is not"
                                  " absolute/normalized: '%s'" % path)
   files = [i for i in os.listdir(path) if not i.startswith(".")]
-  files.sort()
   return files
 
 
@@ -2192,7 +2218,7 @@ def Mlockall():
   logging.debug("Memory lock set")
 
 
-def Daemonize(logfile):
+def Daemonize(logfile, run_uid, run_gid):
   """Daemonize the current process.
 
   This detaches the current process from the controlling terminal and
@@ -2200,6 +2226,10 @@ def Daemonize(logfile):
 
   @type logfile: str
   @param logfile: the logfile to which we should redirect stdout/stderr
+  @type run_uid: int
+  @param run_uid: Run the child under this uid
+  @type run_gid: int
+  @param run_gid: Run the child under this gid
   @rtype: int
   @return: the value zero
 
@@ -2213,6 +2243,11 @@ def Daemonize(logfile):
   pid = os.fork()
   if (pid == 0):  # The first child.
     os.setsid()
+    # FIXME: When removing again and moving to start-stop-daemon privilege drop
+    #        make sure to check for config permission and bail out when invoked
+    #        with wrong user.
+    os.setgid(run_gid)
+    os.setuid(run_uid)
     # this might fail
     pid = os.fork() # Fork a second child.
     if (pid == 0):  # The second child.
@@ -2330,8 +2365,7 @@ def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
   """
   def _helper(pid, signal_, wait):
     """Simple helper to encapsulate the kill/waitpid sequence"""
-    os.kill(pid, signal_)
-    if wait:
+    if IgnoreProcessNotFound(os.kill, pid, signal_) and wait:
       try:
         os.waitpid(pid, os.WNOHANG)
       except OSError:
@@ -3110,6 +3144,26 @@ def RunInSeparateProcess(fn, *args):
   return bool(exitcode)
 
 
+def IgnoreProcessNotFound(fn, *args, **kwargs):
+  """Ignores ESRCH when calling a process-related function.
+
+  ESRCH is raised when a process is not found.
+
+  @rtype: bool
+  @return: Whether process was found
+
+  """
+  try:
+    fn(*args, **kwargs)
+  except EnvironmentError, err:
+    # Ignore ESRCH
+    if err.errno == errno.ESRCH:
+      return False
+    raise
+
+  return True
+
+
 def IgnoreSignals(fn, *args, **kwargs):
   """Tries to call a function ignoring failures due to EINTR.