permissions: fix file permissions on intermediate install directories (#12399)
- mkdirp now takes arguments to allow it to properly set permissions on created directories. - Two arguments (group and mode) set permissions for the leaf directory. - Intermediate directories can inherit permissions from either the topmost existing directory (the parent) or the leaf.
This commit is contained in:
		
				
					committed by
					
						
						Todd Gamblin
					
				
			
			
				
	
			
			
			
						parent
						
							368bf2d69c
						
					
				
				
					commit
					3b115fffb1
				
			@@ -226,7 +226,10 @@ def group_ids(uid=None):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
def chgrp(path, group):
 | 
					def chgrp(path, group):
 | 
				
			||||||
    """Implement the bash chgrp function on a single path"""
 | 
					    """Implement the bash chgrp function on a single path"""
 | 
				
			||||||
 | 
					    if isinstance(group, six.string_types):
 | 
				
			||||||
        gid = grp.getgrnam(group).gr_gid
 | 
					        gid = grp.getgrnam(group).gr_gid
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        gid = group
 | 
				
			||||||
    os.chown(path, -1, gid)
 | 
					    os.chown(path, -1, gid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -424,6 +427,13 @@ def get_filetype(path_name):
 | 
				
			|||||||
    return output.strip()
 | 
					    return output.strip()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def chgrp_if_not_world_writable(path, group):
 | 
				
			||||||
 | 
					    """chgrp path to group if path is not world writable"""
 | 
				
			||||||
 | 
					    mode = os.stat(path).st_mode
 | 
				
			||||||
 | 
					    if not mode & stat.S_IWOTH:
 | 
				
			||||||
 | 
					        chgrp(path, group)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def mkdirp(*paths, **kwargs):
 | 
					def mkdirp(*paths, **kwargs):
 | 
				
			||||||
    """Creates a directory, as well as parent directories if needed.
 | 
					    """Creates a directory, as well as parent directories if needed.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -431,23 +441,34 @@ def mkdirp(*paths, **kwargs):
 | 
				
			|||||||
        paths (str): paths to create with mkdirp
 | 
					        paths (str): paths to create with mkdirp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Keyword Aguments:
 | 
					    Keyword Aguments:
 | 
				
			||||||
        mode (permission bits or None, optional): optional permissions to
 | 
					        mode (permission bits or None, optional): optional permissions to set
 | 
				
			||||||
            set on the created directory -- use OS default if not provided
 | 
					            on the created directory -- use OS default if not provided
 | 
				
			||||||
        mode_intermediate (permission bits or None, optional):
 | 
					        group (group name or None, optional): optional group for permissions of
 | 
				
			||||||
            same as mode, but for newly-created intermediate directories
 | 
					            final created directory -- use OS default if not provided. Only
 | 
				
			||||||
 | 
					            used if world write permissions are not set
 | 
				
			||||||
 | 
					        default_perms ('parents' or 'args', optional): The default permissions
 | 
				
			||||||
 | 
					            that are set for directories that are not themselves an argument
 | 
				
			||||||
 | 
					            for mkdirp. 'parents' means intermediate directories get the
 | 
				
			||||||
 | 
					            permissions of their direct parent directory, 'args' means
 | 
				
			||||||
 | 
					            intermediate get the same permissions specified in the arguments to
 | 
				
			||||||
 | 
					            mkdirp -- default value is 'args'
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    mode = kwargs.get('mode', None)
 | 
					    mode = kwargs.get('mode', None)
 | 
				
			||||||
    mode_intermediate = kwargs.get('mode_intermediate', None)
 | 
					    group = kwargs.get('group', None)
 | 
				
			||||||
 | 
					    default_perms = kwargs.get('default_perms', 'args')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for path in paths:
 | 
					    for path in paths:
 | 
				
			||||||
        if not os.path.exists(path):
 | 
					        if not os.path.exists(path):
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                intermediate_folders = []
 | 
					 | 
				
			||||||
                if mode_intermediate is not None:
 | 
					 | 
				
			||||||
                # detect missing intermediate folders
 | 
					                # detect missing intermediate folders
 | 
				
			||||||
 | 
					                intermediate_folders = []
 | 
				
			||||||
 | 
					                last_parent = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                intermediate_path = os.path.dirname(path)
 | 
					                intermediate_path = os.path.dirname(path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                while intermediate_path:
 | 
					                while intermediate_path:
 | 
				
			||||||
                    if os.path.exists(intermediate_path):
 | 
					                    if os.path.exists(intermediate_path):
 | 
				
			||||||
 | 
					                        last_parent = intermediate_path
 | 
				
			||||||
                        break
 | 
					                        break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    intermediate_folders.append(intermediate_path)
 | 
					                    intermediate_folders.append(intermediate_path)
 | 
				
			||||||
@@ -459,13 +480,36 @@ def mkdirp(*paths, **kwargs):
 | 
				
			|||||||
                # leaf folder permissions
 | 
					                # leaf folder permissions
 | 
				
			||||||
                if mode is not None:
 | 
					                if mode is not None:
 | 
				
			||||||
                    os.chmod(path, mode)
 | 
					                    os.chmod(path, mode)
 | 
				
			||||||
 | 
					                if group:
 | 
				
			||||||
 | 
					                    chgrp_if_not_world_writable(path, group)
 | 
				
			||||||
 | 
					                    if mode is not None:
 | 
				
			||||||
 | 
					                        os.chmod(path, mode)  # reset sticky grp bit post chgrp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # for intermediate folders, change mode just for newly created
 | 
					                # for intermediate folders, change mode just for newly created
 | 
				
			||||||
                # ones and if mode_intermediate has been specified, otherwise
 | 
					                # ones and if mode_intermediate has been specified, otherwise
 | 
				
			||||||
                # intermediate folders list is not populated at all and default
 | 
					                # intermediate folders list is not populated at all and default
 | 
				
			||||||
                # OS mode will be used
 | 
					                # OS mode will be used
 | 
				
			||||||
 | 
					                if default_perms == 'args':
 | 
				
			||||||
 | 
					                    intermediate_mode = mode
 | 
				
			||||||
 | 
					                    intermediate_group = group
 | 
				
			||||||
 | 
					                elif default_perms == 'parents':
 | 
				
			||||||
 | 
					                    stat_info = os.stat(last_parent)
 | 
				
			||||||
 | 
					                    intermediate_mode = stat_info.st_mode
 | 
				
			||||||
 | 
					                    intermediate_group = stat_info.st_gid
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    msg = "Invalid value: '%s'. " % default_perms
 | 
				
			||||||
 | 
					                    msg += "Choose from 'args' or 'parents'."
 | 
				
			||||||
 | 
					                    raise ValueError(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                for intermediate_path in reversed(intermediate_folders):
 | 
					                for intermediate_path in reversed(intermediate_folders):
 | 
				
			||||||
                    os.chmod(intermediate_path, mode_intermediate)
 | 
					                    if intermediate_mode is not None:
 | 
				
			||||||
 | 
					                        os.chmod(intermediate_path, intermediate_mode)
 | 
				
			||||||
 | 
					                    if intermediate_group is not None:
 | 
				
			||||||
 | 
					                        chgrp_if_not_world_writable(intermediate_path,
 | 
				
			||||||
 | 
					                                                    intermediate_group)
 | 
				
			||||||
 | 
					                        os.chmod(intermediate_path,
 | 
				
			||||||
 | 
					                                 intermediate_mode)  # reset sticky bit after
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            except OSError as e:
 | 
					            except OSError as e:
 | 
				
			||||||
                if e.errno != errno.EEXIST or not os.path.isdir(path):
 | 
					                if e.errno != errno.EEXIST or not os.path.isdir(path):
 | 
				
			||||||
                    raise e
 | 
					                    raise e
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import ruamel.yaml as yaml
 | 
					import ruamel.yaml as yaml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from llnl.util.filesystem import mkdirp, chgrp
 | 
					from llnl.util.filesystem import mkdirp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import spack.config
 | 
					import spack.config
 | 
				
			||||||
import spack.spec
 | 
					import spack.spec
 | 
				
			||||||
@@ -256,23 +256,13 @@ def create_install_directory(self, spec):
 | 
				
			|||||||
        from spack.package_prefs import get_package_group
 | 
					        from spack.package_prefs import get_package_group
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Each package folder can have its own specific permissions, while
 | 
					        # Each package folder can have its own specific permissions, while
 | 
				
			||||||
        # intermediate folders (arch/compiler) are set with full access to
 | 
					        # intermediate folders (arch/compiler) are set with access permissions
 | 
				
			||||||
        # everyone (0o777) and install_tree root folder is the chokepoint
 | 
					        # equivalent to the root permissions of the layout.
 | 
				
			||||||
        # for restricting global access.
 | 
					 | 
				
			||||||
        # So, whoever has access to the install_tree is allowed to install
 | 
					 | 
				
			||||||
        # packages for same arch/compiler and since no data is stored in
 | 
					 | 
				
			||||||
        # intermediate folders, it does not represent a security threat.
 | 
					 | 
				
			||||||
        group = get_package_group(spec)
 | 
					        group = get_package_group(spec)
 | 
				
			||||||
        perms = get_package_dir_permissions(spec)
 | 
					        perms = get_package_dir_permissions(spec)
 | 
				
			||||||
        perms_intermediate = 0o777
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        mkdirp(spec.prefix, mode=perms, mode_intermediate=perms_intermediate)
 | 
					        mkdirp(spec.prefix, mode=perms, group=group, default_perms='parents')
 | 
				
			||||||
        if group:
 | 
					        mkdirp(self.metadata_path(spec), mode=perms, group=group)  # in prefix
 | 
				
			||||||
            chgrp(spec.prefix, group)
 | 
					 | 
				
			||||||
            # Need to reset the sticky group bit after chgrp
 | 
					 | 
				
			||||||
            os.chmod(spec.prefix, perms)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        mkdirp(self.metadata_path(spec), mode=perms)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.write_spec(spec, self.spec_file_path(spec))
 | 
					        self.write_spec(spec, self.spec_file_path(spec))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user