浏览代码

- Added 'umask' option to process config. If you set this option,
supervisor will set the umask of the child program. (Thanks to
Ian Bicking for the suggestion).

Chris McDonough 18 年之前
父节点
当前提交
1a58b5397d

+ 4 - 0
CHANGES.txt

@@ -147,6 +147,10 @@ Next Release
     option, supervisor will chdir to this directory before executing
     the child program (and thus it will be the child's cwd).
 
+  - Added 'umask' option to process config.  If you set this option,
+    supervisor will set the umask of the child program.  (Thanks to
+    Ian Bicking for the suggestion).
+
 3.0a2
 
   - Fixed the README.txt example for defining the supervisor RPC

+ 4 - 2
README.txt

@@ -552,8 +552,10 @@ Configuration File '[program:x]' Section Settings
   overridden here.  See "Subprocess Environment" below.
 
   'directory' -- a file path representing a directory to which
-  supervisord should chdir before exec'ing the child.  Default: no
-  cwd.
+  supervisord should chdir before exec'ing the child. Default: no cwd.
+
+  'umask' -- an octal number (e.g. 002, 022) representing the umask of
+  the process.  Default: no special umask (inherit supervisor's).
 
   Note that a '[program:x]' section actually represents a "homogeneous
   process group" to supervisor (new in 3.0).  The members of the group

+ 4 - 1
src/supervisor/datatypes.py

@@ -164,7 +164,10 @@ def dot_separated_user_group(arg):
         raise ValueError, 'Invalid user.group definition %s' % arg
 
 def octal_type(arg):
-    return int(arg, 8)
+    try:
+        return int(arg, 8)
+    except TypeError:
+        raise ValueError('%s is not convertable to an octal type' % arg)
 
 def name_to_uid(name):
     if name is None:

+ 15 - 6
src/supervisor/options.py

@@ -655,6 +655,10 @@ class ServerOptions(Options):
         stderr_cmaxbytes = byte_size(get(section,'stderr_capture_maxbytes','0'))
         directory = get(section, 'directory', None)
 
+        umask = get(section, 'umask', None)
+        if umask is not None:
+            umask = octal_type(umask)
+
         command = get(section, 'command', None)
         if command is None:
             raise ValueError, (
@@ -709,6 +713,8 @@ class ServerOptions(Options):
                 self,
                 name=expand(process_name, expansions, 'process_name'),
                 command=expand(command, expansions, 'command'),
+                directory=directory,
+                umask=umask,
                 priority=priority,
                 autostart=autostart,
                 autorestart=autorestart,
@@ -727,8 +733,7 @@ class ServerOptions(Options):
                 stopwaitsecs=stopwaitsecs,
                 exitcodes=exitcodes,
                 redirect_stderr=redirect_stderr,
-                environment=environment,
-                directory=directory)
+                environment=environment)
 
             programs.append(pconfig)
 
@@ -1119,6 +1124,9 @@ class ServerOptions(Options):
     def _exit(self, code):
         os._exit(code)
 
+    def setumask(self, mask):
+        os.umask(mask)
+
     def get_path(self):
         """Return a list corresponding to $PATH, or a default."""
         path = ["/bin", "/usr/bin", "/usr/local/bin"]
@@ -1317,17 +1325,19 @@ class Config:
                                                  self.name)
     
 class ProcessConfig(Config):
-    def __init__(self, options, name, command, priority, autostart,
-                 autorestart, startsecs, startretries, uid,
+    def __init__(self, options, name, command, directory, umask,
+                 priority, autostart, autorestart, startsecs, startretries, uid,
                  stdout_logfile, stdout_capture_maxbytes,
                  stdout_logfile_backups, stdout_logfile_maxbytes,
                  stderr_logfile, stderr_capture_maxbytes,
                  stderr_logfile_backups, stderr_logfile_maxbytes,
                  stopsignal, stopwaitsecs, exitcodes, redirect_stderr,
-                 environment=None, directory=None):
+                 environment=None):
         self.options = options
         self.name = name
         self.command = command
+        self.directory = directory
+        self.umask = umask
         self.priority = priority
         self.autostart = autostart
         self.autorestart = autorestart
@@ -1347,7 +1357,6 @@ class ProcessConfig(Config):
         self.exitcodes = exitcodes
         self.redirect_stderr = redirect_stderr
         self.environment = environment
-        self.directory = directory
 
     def create_autochildlogs(self):
         # temporary logfiles which are erased at start time

+ 2 - 0
src/supervisor/process.py

@@ -294,6 +294,8 @@ class Subprocess:
                 options.write(2, msg)
             else:
                 try:
+                    if self.config.umask:
+                        options.setumask(self.config.umask)
                     options.execve(filename, argv, env)
                 except OSError, why:
                     code = errno.errorcode.get(why[0], why[0])

+ 8 - 2
src/supervisor/tests/base.py

@@ -67,6 +67,7 @@ class DummyOptions:
         self.serverurl = 'http://localhost:9001'
         self.changed_directory = False
         self.chdir_error = None
+        self.umaskset = None
 
     def getLogger(self, *args, **kw):
         logger = DummyLogger()
@@ -239,6 +240,9 @@ class DummyOptions:
             raise OSError(self.chdir_error)
         self.changed_directory = True
 
+    def setumask(self, mask):
+        self.umaskset = mask
+
 class DummyLogger:
     def __init__(self):
         self.reopened = False
@@ -409,7 +413,8 @@ class DummyProcess:
         self.transitioned = True
 
 class DummyPConfig:
-    def __init__(self, options, name, command, priority=999, autostart=True,
+    def __init__(self, options, name, command, directory=None, umask=None,
+                 priority=999, autostart=True,
                  autorestart=True, startsecs=10, startretries=999,
                  uid=None, stdout_logfile=None, stdout_capture_maxbytes=0,
                  stdout_logfile_backups=0, stdout_logfile_maxbytes=0,
@@ -417,7 +422,7 @@ class DummyPConfig:
                  stderr_logfile_backups=0, stderr_logfile_maxbytes=0,
                  redirect_stderr=False,
                  stopsignal=None, stopwaitsecs=10,
-                 exitcodes=(0,2), environment=None, directory=None):
+                 exitcodes=(0,2), environment=None):
         self.options = options
         self.name = name
         self.command = command
@@ -444,6 +449,7 @@ class DummyPConfig:
         self.exitcodes = exitcodes
         self.environment = environment
         self.directory = directory
+        self.umask = umask
         self.autochildlogs_created = False
 
     def create_autochildlogs(self):

+ 4 - 1
src/supervisor/tests/test_options.py

@@ -54,6 +54,7 @@ class ServerOptionsTests(unittest.TestCase):
         startsecs=5
         startretries=10
         directory=/tmp
+        umask=002
         
         [program:cat2]
         priority=2
@@ -129,6 +130,7 @@ class ServerOptionsTests(unittest.TestCase):
         self.assertEqual(proc1.stdout_logfile_backups, 10)
         self.assertEqual(proc1.exitcodes, [0,2])
         self.assertEqual(proc1.directory, '/tmp')
+        self.assertEqual(proc1.umask, 002)
 
         cat2 = options.process_group_configs[1]
         self.assertEqual(cat2.name, 'cat2')
@@ -686,7 +688,8 @@ class TestProcessConfig(unittest.TestCase):
 
     def _makeOne(self, *arg, **kw):
         defaults = {}
-        for name in ('name', 'command', 'priority', 'autostart', 'autorestart',
+        for name in ('name', 'command', 'directory', 'umask',
+                     'priority', 'autostart', 'autorestart',
                      'startsecs', 'startretries', 'uid',
                      'stdout_logfile', 'stdout_capture_maxbytes',
                      'stdout_logfile_backups', 'stdout_logfile_maxbytes',

+ 13 - 0
src/supervisor/tests/test_process.py

@@ -333,6 +333,19 @@ class SubprocessTests(unittest.TestCase):
         self.assertEqual(options._exitcode, 127)
         self.assertEqual(options.changed_directory, True)
 
+    def test_spawn_as_child_sets_umask(self):
+        options = DummyOptions()
+        options.forkpid = 0
+        config = DummyPConfig(options, 'good', '/good/filename', umask=002)
+        instance = self._makeOne(config)
+        result = instance.spawn()
+        self.assertEqual(result, None)
+        self.assertEqual(options.written, {})
+        self.assertEqual(options.execv_args,
+                         ('/good/filename', ['/good/filename']) )
+        self.assertEqual(options._exitcode, 127)
+        self.assertEqual(options.umaskset, 002)
+
     def test_spawn_as_child_cwd_fail(self):
         options = DummyOptions()
         options.forkpid = 0