changeset 4:f64af329930f

Any user can now use ProcessManager to manage processes--root is no longer required. However, an error will be displayed if the user doesn't have privileges to manage a process because a user/group change is required.
author Atul Varma <varmaa@toolness.com>
date Wed, 19 Mar 2008 00:01:25 +0000
parents 6d1dc2d106f6
children 7e11415cf272
files ProcessManager.py
diffstat 1 files changed, 77 insertions(+), 43 deletions(-) [+]
line wrap: on
line diff
--- a/ProcessManager.py	Tue Mar 18 23:08:58 2008 +0000
+++ b/ProcessManager.py	Wed Mar 19 00:01:25 2008 +0000
@@ -51,10 +51,6 @@
 # TODO's
 #
 # * Document the public methods better.
-#
-# * Don't require ProcessManager to be run as root, but do raise
-#   exceptions if the user tries to control a process that requires
-#   changing the user ID and it can't be done.
 # ----------------------------------------------------------------------------
 
 # ----------------------------------------------------------------------------
@@ -162,8 +158,8 @@
                   program,
                   args,
                   workingDir,
-                  uid,
-                  gid,
+                  uid = None,
+                  gid = None,
                   stopSignal = None ):
         """
         Creates a process with the given name/identifier, description,
@@ -184,12 +180,46 @@
         self.args.extend( args )
         self.workingDir = workingDir
         self.stopSignal = stopSignal
-        
-        import grp
-        import pwd
+
+        if gid and uid:
+            import grp
+            import pwd
+
+            self.gid = grp.getgrnam( gid )[2]
+            self.uid = pwd.getpwnam( uid )[2]
+        elif gid or uid:
+            raise ValueError(
+                "For process '%s', either gid or uid must both be None, "
+                "or both must be set." % self.name
+                )
+        else:
+            self.gid = None
+            self.uid = None
 
-        self.gid = grp.getgrnam( gid )[2]
-        self.uid = pwd.getpwnam( uid )[2]
+    def canCurrentUserManage( self ):
+        """
+        Returns whether the current user has the ability to manage this
+        process.
+        """
+
+        if os.getuid() == 0:
+            # we're running as root, so all is good.
+            result = True
+        elif self.uid is None:
+            # We don't need to change the user to manage the
+            # process, so all is good.
+            result = True
+        elif self.uid == os.getuid() and self.gid == os.getgid():
+            # uid and gid are specified, but they're the
+            # current user, so all is good.
+            result = True
+        else:
+            # uid and gid are specified, they're different from
+            # the current user, and we're not root, so this
+            # isn't good.
+            result = False
+
+        return result
 
     def _pidfile( self ):
         """
@@ -249,7 +279,8 @@
                 print "Process '%s' is already running!" % self.name
                 return
             else:
-                print "Hmm. Process '%s' seems to have died prematurely." % self.name
+                print ( "Hmm. Process '%s' seems to have "
+                        "died prematurely." % self.name )
 
         # Start the process now.
         leftColumnText = "Launching %s..." % self.name
@@ -267,8 +298,10 @@
         forkResult = os.fork()
         if forkResult == 0:
             # We're the child process.
-            os.setgid( self.gid )
-            os.setuid( self.uid )
+            if self.uid is not None:
+                assert self.gid is not None
+                os.setgid( self.gid )
+                os.setuid( self.uid )
 
             os.chdir( self.workingDir )
 
@@ -327,7 +360,8 @@
                 else:
                     print "FAILED"
             elif warnCrashed:
-                print "Hmm. Process '%s' seems to have died prematurely." % self.name
+                print ( "Hmm. Process '%s' seems to have "
+                        "died prematurely." % self.name )
             os.remove( self._pidfile() )
         else:
             print "Process '%s' is not running." % self.name
@@ -389,6 +423,31 @@
 
     pass
 
+def _runCommandOnProcesses( command, processes ):
+    """
+    Runs the given command on the given Process objects and
+    returns True if successful, False if an error occurred.
+    """
+
+    success = True
+
+    if command != "status":
+        for process in processes:
+            if not process.canCurrentUserManage():
+                print ( "The process '%s' cannot be managed "
+                        "by the current user." % process.name )
+                success = False
+
+    if success:
+        for process in processes:
+            method = getattr( process, command )
+            try:
+                method()
+            except ProcessStartupError:
+                success = False
+
+    return success
+
 def _runCommandOnTarget( command, target ):
     """
     Runs the given command on the given target.
@@ -399,33 +458,12 @@
         print "Please use ProcessManager.init()."
         sys.exit( -1 )
 
-    errorOccurred = False
-    
     if target == "all":
-        for process in _processes.values():
-            method = getattr( process, command )
-            try:
-                method()
-            except ProcessStartupError:
-                errorOccurred = True
+        processes = [ process for process in _processes.values() ]
     else:
-        method = getattr( _processes[target], command )
-        try:
-            method()
-        except ProcessStartupError:
-            errorOccurred = True
-
-    if errorOccurred:
-        sys.exit( -1 )
+        processes = [_processes[target]]
 
-def _checkPrivileges():
-    """
-    Checks to ensure that the current user has the proper privileges
-    to run the ProcessManager; exits the program if not.
-    """
-
-    if os.getuid() != 0:
-        print "ERROR: This script must be run as root."
+    if not _runCommandOnProcesses( command, processes ):
         sys.exit( -1 )
 
 def _generateTargetHelpText():
@@ -458,8 +496,6 @@
     first command-line parameter determines the command.
     """
 
-    _checkPrivileges()
-
     target = os.path.split( sys.argv[0] )[1]
     if not _processes.has_key( target ):
         # If we're in a rc.d directory, we may have 3 characters
@@ -546,8 +582,6 @@
     command-line arguments and acts on them.
     """
     
-    _checkPrivileges()
-    
     usageTextDict = {
         "scriptName" : os.path.split( sys.argv[0] )[1],
         "targets" : _generateTargetHelpText(),