diff options
author | Jussi Pakkanen <jpakkane@gmail.com> | 2018-04-26 23:14:00 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-04-26 23:14:00 +0300 |
commit | 9b0453d3e937195ca234963182dc1b5a61a13f23 (patch) | |
tree | 56bde337fe06e8726af04676c0a386cd15be6e62 | |
parent | 9c073620aa351c4075aa838fa5003ee0fc8ec17a (diff) | |
parent | 170776d626373762b220aad8ac7e5b57f18156ed (diff) | |
download | meson-9b0453d3e937195ca234963182dc1b5a61a13f23.zip meson-9b0453d3e937195ca234963182dc1b5a61a13f23.tar.gz meson-9b0453d3e937195ca234963182dc1b5a61a13f23.tar.bz2 |
Merge pull request #3225 from filbranden/fixperms3
Introduce install_umask to determine permissions of files in install tree. Default it to 022
-rw-r--r-- | docs/markdown/snippets/install_umask.md | 17 | ||||
-rw-r--r-- | mesonbuild/backend/backends.py | 4 | ||||
-rw-r--r-- | mesonbuild/backend/ninjabackend.py | 4 | ||||
-rw-r--r-- | mesonbuild/coredata.py | 25 | ||||
-rw-r--r-- | mesonbuild/mconf.py | 2 | ||||
-rw-r--r-- | mesonbuild/scripts/meson_install.py | 41 | ||||
-rwxr-xr-x | run_unittests.py | 68 | ||||
-rw-r--r-- | test cases/common/12 data/meson.build | 3 | ||||
-rw-r--r-- | test cases/common/66 install subdir/meson.build | 3 | ||||
-rw-r--r-- | test cases/unit/24 install umask/datafile.cat | 1 | ||||
-rw-r--r-- | test cases/unit/24 install umask/meson.build | 7 | ||||
-rw-r--r-- | test cases/unit/24 install umask/myinstall.py | 17 | ||||
-rw-r--r-- | test cases/unit/24 install umask/prog.1 | 1 | ||||
-rw-r--r-- | test cases/unit/24 install umask/prog.c | 3 | ||||
-rw-r--r-- | test cases/unit/24 install umask/sample.h | 6 | ||||
-rw-r--r-- | test cases/unit/24 install umask/subdir/datafile.dog | 1 | ||||
-rwxr-xr-x | test cases/unit/24 install umask/subdir/sayhello | 2 |
17 files changed, 189 insertions, 16 deletions
diff --git a/docs/markdown/snippets/install_umask.md b/docs/markdown/snippets/install_umask.md new file mode 100644 index 0000000..b3a2427 --- /dev/null +++ b/docs/markdown/snippets/install_umask.md @@ -0,0 +1,17 @@ +## New built-in option install_umask with a default value 022 + +This umask is used to define the default permissions of files and directories +created in the install tree. Files will preserve their executable mode, but the +exact permissions will obey the install_umask. + +The install_umask can be overridden in the meson command-line: + + $ meson --install-umask=027 builddir/ + +A project can also override the default in the project() call: + + project('myproject', 'c', + default_options : ['install_umask=027']) + +To disable the install_umask, set it to 'preserve', in which case permissions +are copied from the files in their origin. diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 5a401fe..2fd028d 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -37,11 +37,13 @@ class CleanTrees: self.trees = trees class InstallData: - def __init__(self, source_dir, build_dir, prefix, strip_bin, mesonintrospect): + def __init__(self, source_dir, build_dir, prefix, strip_bin, + install_umask, mesonintrospect): self.source_dir = source_dir self.build_dir = build_dir self.prefix = prefix self.strip_bin = strip_bin + self.install_umask = install_umask self.targets = [] self.headers = [] self.man = [] diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index eeac388..20b2e08 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -671,7 +671,9 @@ int dummy; d = InstallData(self.environment.get_source_dir(), self.environment.get_build_dir(), self.environment.get_prefix(), - strip_bin, self.environment.get_build_command() + ['introspect']) + strip_bin, + self.environment.coredata.get_builtin_option('install_umask'), + self.environment.get_build_command() + ['introspect']) elem = NinjaBuildElement(self.all_outputs, 'meson-install', 'CUSTOM_COMMAND', 'PHONY') elem.add_dep('all') elem.add_item('DESC', 'Installing files.') diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 3dfb429..61ea2ef 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -105,6 +105,22 @@ class UserIntegerOption(UserOption): except ValueError: raise MesonException('Value string "%s" is not convertable to an integer.' % valuestring) +class UserUmaskOption(UserIntegerOption): + def __init__(self, name, description, value, yielding=None): + super().__init__(name, description, 0, 0o777, value, yielding) + + def set_value(self, newvalue): + if newvalue is None or newvalue == 'preserve': + self.value = None + else: + super().set_value(newvalue) + + def toint(self, valuestring): + try: + return int(valuestring, 8) + except ValueError as e: + raise MesonException('Invalid mode: {}'.format(e)) + class UserComboOption(UserOption): def __init__(self, name, description, choices, value, yielding=None): super().__init__(name, description, choices, yielding) @@ -351,12 +367,12 @@ def is_builtin_option(optname): def get_builtin_option_choices(optname): if is_builtin_option(optname): - if builtin_options[optname][0] == UserStringOption: - return None + if builtin_options[optname][0] == UserComboOption: + return builtin_options[optname][2] elif builtin_options[optname][0] == UserBooleanOption: return [True, False] else: - return builtin_options[optname][2] + return None else: raise RuntimeError('Tried to get the supported values for an unknown builtin option \'%s\'.' % optname) @@ -385,6 +401,8 @@ def get_builtin_option_default(optname, prefix='', noneIfSuppress=False): o = builtin_options[optname] if o[0] == UserComboOption: return o[3] + if o[0] == UserIntegerOption: + return o[4] if optname in builtin_dir_noprefix_options: if noneIfSuppress: # Return None if argparse defaulting should be suppressed for @@ -444,6 +462,7 @@ builtin_options = { 'backend': [UserComboOption, 'Backend to use.', backendlist, 'ninja'], 'stdsplit': [UserBooleanOption, 'Split stdout and stderr in test logs.', True], 'errorlogs': [UserBooleanOption, "Whether to print the logs from failing tests.", True], + 'install_umask': [UserUmaskOption, 'Default umask to apply on permissions of installed files.', '022'], } # Special prefix-dependent defaults for installation directories that reside in diff --git a/mesonbuild/mconf.py b/mesonbuild/mconf.py index 9a11332..fd4c141 100644 --- a/mesonbuild/mconf.py +++ b/mesonbuild/mconf.py @@ -152,7 +152,7 @@ class Conf: print(' Build dir ', self.build.environment.build_dir) print('\nCore options:\n') carr = [] - for key in ['buildtype', 'warning_level', 'werror', 'strip', 'unity', 'default_library']: + for key in ['buildtype', 'warning_level', 'werror', 'strip', 'unity', 'default_library', 'install_umask']: carr.append({'name': key, 'descr': coredata.get_builtin_option_description(key), 'value': self.coredata.get_builtin_option(key), diff --git a/mesonbuild/scripts/meson_install.py b/mesonbuild/scripts/meson_install.py index 013f2a0..b454260 100644 --- a/mesonbuild/scripts/meson_install.py +++ b/mesonbuild/scripts/meson_install.py @@ -51,12 +51,25 @@ class DirMaker: for d in self.dirs: append_to_log(d) -def set_mode(path, mode): - if mode is None: - # Keep mode unchanged +def is_executable(path): + '''Checks whether any of the "x" bits are set in the source file mode.''' + return bool(os.stat(path).st_mode & 0o111) + +def sanitize_permissions(path, umask): + if umask is None: return - if (mode.perms_s or mode.owner or mode.group) is None: - # Nothing to set + new_perms = 0o777 if is_executable(path) else 0o666 + new_perms &= ~umask + try: + os.chmod(path, new_perms) + except PermissionError as e: + msg = '{!r}: Unable to set permissions {!r}: {}, ignoring...' + print(msg.format(path, new_perms, e.strerror)) + +def set_mode(path, mode, default_umask): + if mode is None or (mode.perms_s or mode.owner or mode.group) is None: + # Just sanitize permissions with the default umask + sanitize_permissions(path, default_umask) return # No chown() on Windows, and must set one of owner/group if not is_windows() and (mode.owner or mode.group) is not None: @@ -83,6 +96,8 @@ def set_mode(path, mode): except PermissionError as e: msg = '{!r}: Unable to set permissions {!r}: {}, ignoring...' print(msg.format(path, mode.perms_s, e.strerror)) + else: + sanitize_permissions(path, default_umask) def restore_selinux_contexts(): ''' @@ -180,6 +195,7 @@ def do_copydir(data, src_dir, dst_dir, exclude): sys.exit(1) data.dirmaker.makedirs(abs_dst) shutil.copystat(abs_src, abs_dst) + sanitize_permissions(abs_dst, data.install_umask) for f in files: abs_src = os.path.join(root, f) filepart = os.path.relpath(abs_src, start=src_dir) @@ -195,6 +211,7 @@ def do_copydir(data, src_dir, dst_dir, exclude): os.mkdir(parent_dir) shutil.copystat(os.path.dirname(abs_src), parent_dir) shutil.copy2(abs_src, abs_dst, follow_symlinks=False) + sanitize_permissions(abs_dst, data.install_umask) append_to_log(abs_dst) def get_destdir_path(d, path): @@ -210,6 +227,8 @@ def do_install(datafilename): d.destdir = os.environ.get('DESTDIR', '') d.fullprefix = destdir_join(d.destdir, d.prefix) + if d.install_umask is not None: + os.umask(d.install_umask) d.dirmaker = DirMaker() with d.dirmaker: install_subdirs(d) # Must be first, because it needs to delete the old subtree. @@ -226,7 +245,7 @@ def install_subdirs(d): print('Installing subdir %s to %s' % (src_dir, full_dst_dir)) d.dirmaker.makedirs(full_dst_dir, exist_ok=True) do_copydir(d, src_dir, full_dst_dir, exclude) - set_mode(full_dst_dir, mode) + set_mode(full_dst_dir, mode, d.install_umask) def install_data(d): for i in d.data: @@ -237,7 +256,7 @@ def install_data(d): d.dirmaker.makedirs(outdir, exist_ok=True) print('Installing %s to %s' % (fullfilename, outdir)) do_copyfile(fullfilename, outfilename) - set_mode(outfilename, mode) + set_mode(outfilename, mode, d.install_umask) def install_man(d): for m in d.man: @@ -256,6 +275,7 @@ def install_man(d): append_to_log(outfilename) else: do_copyfile(full_source_filename, outfilename) + sanitize_permissions(outfilename, d.install_umask) def install_headers(d): for t in d.headers: @@ -266,6 +286,7 @@ def install_headers(d): print('Installing %s to %s' % (fname, outdir)) d.dirmaker.makedirs(outdir, exist_ok=True) do_copyfile(fullfilename, outfilename) + sanitize_permissions(outfilename, d.install_umask) def run_install_script(d): env = {'MESON_SOURCE_ROOT': d.source_dir, @@ -330,6 +351,7 @@ def install_targets(d): raise RuntimeError('File {!r} could not be found'.format(fname)) elif os.path.isfile(fname): do_copyfile(fname, outname) + sanitize_permissions(outname, d.install_umask) if should_strip and d.strip_bin is not None: if fname.endswith('.jar'): print('Not stripping jar target:', os.path.basename(fname)) @@ -346,9 +368,12 @@ def install_targets(d): pdb_outname = os.path.splitext(outname)[0] + '.pdb' print('Installing pdb file %s to %s' % (pdb_filename, pdb_outname)) do_copyfile(pdb_filename, pdb_outname) + sanitize_permissions(pdb_outname, d.install_umask) elif os.path.isdir(fname): fname = os.path.join(d.build_dir, fname.rstrip('/')) - do_copydir(d, fname, os.path.join(outdir, os.path.basename(fname)), None) + outname = os.path.join(outdir, os.path.basename(fname)) + do_copydir(d, fname, outname, None) + sanitize_permissions(outname, d.install_umask) else: raise RuntimeError('Unknown file type for {!r}'.format(fname)) printed_symlink_error = False diff --git a/run_unittests.py b/run_unittests.py index ce18a1a..f06c9a0 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -2731,6 +2731,74 @@ class LinuxlikeTests(BasePlatformTests): # The chown failed nonfatally if we're not root self.assertEqual(0, statf.st_uid) + def test_install_umask(self): + ''' + Test that files are installed with correct permissions using default + install umask of 022, regardless of the umask at time the worktree + was checked out or the build was executed. + ''' + # Copy source tree to a temporary directory and change permissions + # there to simulate a checkout with umask 002. + orig_testdir = os.path.join(self.unit_test_dir, '24 install umask') + # Create a new testdir under tmpdir. + tmpdir = os.path.realpath(tempfile.mkdtemp()) + self.addCleanup(windows_proof_rmtree, tmpdir) + testdir = os.path.join(tmpdir, '24 install umask') + # Copy the tree using shutil.copyfile, which will use the current umask + # instead of preserving permissions of the old tree. + save_umask = os.umask(0o002) + self.addCleanup(os.umask, save_umask) + shutil.copytree(orig_testdir, testdir, copy_function=shutil.copyfile) + # Preserve the executable status of subdir/sayhello though. + os.chmod(os.path.join(testdir, 'subdir', 'sayhello'), 0o775) + self.init(testdir) + # Run the build under a 027 umask now. + os.umask(0o027) + self.build() + # And keep umask 027 for the install step too. + self.install() + + for executable in [ + 'bin/prog', + 'share/subdir/sayhello', + ]: + f = os.path.join(self.installdir, 'usr', *executable.split('/')) + found_mode = stat.filemode(os.stat(f).st_mode) + want_mode = '-rwxr-xr-x' + self.assertEqual(want_mode, found_mode, + msg=('Expected file %s to have mode %s but found %s instead.' % + (executable, want_mode, found_mode))) + + for directory in [ + 'usr', + 'usr/bin', + 'usr/include', + 'usr/share', + 'usr/share/man', + 'usr/share/man/man1', + 'usr/share/subdir', + ]: + f = os.path.join(self.installdir, *directory.split('/')) + found_mode = stat.filemode(os.stat(f).st_mode) + want_mode = 'drwxr-xr-x' + self.assertEqual(want_mode, found_mode, + msg=('Expected directory %s to have mode %s but found %s instead.' % + (directory, want_mode, found_mode))) + + for datafile in [ + 'include/sample.h', + 'share/datafile.cat', + 'share/file.dat', + 'share/man/man1/prog.1.gz', + 'share/subdir/datafile.dog', + ]: + f = os.path.join(self.installdir, 'usr', *datafile.split('/')) + found_mode = stat.filemode(os.stat(f).st_mode) + want_mode = '-rw-r--r--' + self.assertEqual(want_mode, found_mode, + msg=('Expected file %s to have mode %s but found %s instead.' % + (datafile, want_mode, found_mode))) + def test_cpp_std_override(self): testdir = os.path.join(self.unit_test_dir, '6 std override') self.init(testdir) diff --git a/test cases/common/12 data/meson.build b/test cases/common/12 data/meson.build index d855bba..b5b1e8a 100644 --- a/test cases/common/12 data/meson.build +++ b/test cases/common/12 data/meson.build @@ -1,4 +1,5 @@ -project('data install test', 'c') +project('data install test', 'c', + default_options : ['install_umask=preserve']) install_data(sources : 'datafile.dat', install_dir : 'share/progname') # Some file in /etc that is only read-write by root; add a sticky bit for testing install_data(sources : 'etcfile.dat', install_dir : '/etc', install_mode : 'rw------T') diff --git a/test cases/common/66 install subdir/meson.build b/test cases/common/66 install subdir/meson.build index 403b6f0..6f92efd 100644 --- a/test cases/common/66 install subdir/meson.build +++ b/test cases/common/66 install subdir/meson.build @@ -1,4 +1,5 @@ -project('install a whole subdir', 'c') +project('install a whole subdir', 'c', + default_options : ['install_umask=preserve']) # A subdir with an exclusion: install_subdir('sub2', diff --git a/test cases/unit/24 install umask/datafile.cat b/test cases/unit/24 install umask/datafile.cat new file mode 100644 index 0000000..53d81fc --- /dev/null +++ b/test cases/unit/24 install umask/datafile.cat @@ -0,0 +1 @@ +Installed cat is installed. diff --git a/test cases/unit/24 install umask/meson.build b/test cases/unit/24 install umask/meson.build new file mode 100644 index 0000000..225f71c --- /dev/null +++ b/test cases/unit/24 install umask/meson.build @@ -0,0 +1,7 @@ +project('install umask', 'c') +executable('prog', 'prog.c', install : true) +install_headers('sample.h') +install_man('prog.1') +install_data('datafile.cat', install_dir : get_option('prefix') + '/share') +install_subdir('subdir', install_dir : get_option('prefix') + '/share') +meson.add_install_script('myinstall.py', 'share', 'file.dat') diff --git a/test cases/unit/24 install umask/myinstall.py b/test cases/unit/24 install umask/myinstall.py new file mode 100644 index 0000000..db6a51c --- /dev/null +++ b/test cases/unit/24 install umask/myinstall.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +import os +import sys + +prefix = os.environ['MESON_INSTALL_DESTDIR_PREFIX'] + +dirname = os.path.join(prefix, sys.argv[1]) + +try: + os.makedirs(dirname) +except FileExistsError: + if not os.path.isdir(dirname): + raise + +with open(os.path.join(dirname, sys.argv[2]), 'w') as f: + f.write('') diff --git a/test cases/unit/24 install umask/prog.1 b/test cases/unit/24 install umask/prog.1 new file mode 100644 index 0000000..08ef7da --- /dev/null +++ b/test cases/unit/24 install umask/prog.1 @@ -0,0 +1 @@ +Man up, you. diff --git a/test cases/unit/24 install umask/prog.c b/test cases/unit/24 install umask/prog.c new file mode 100644 index 0000000..0f0061d --- /dev/null +++ b/test cases/unit/24 install umask/prog.c @@ -0,0 +1,3 @@ +int main(int argc, char **arv) { + return 0; +} diff --git a/test cases/unit/24 install umask/sample.h b/test cases/unit/24 install umask/sample.h new file mode 100644 index 0000000..dc030da --- /dev/null +++ b/test cases/unit/24 install umask/sample.h @@ -0,0 +1,6 @@ +#ifndef SAMPLE_H +#define SAMPLE_H + +int wackiness(); + +#endif diff --git a/test cases/unit/24 install umask/subdir/datafile.dog b/test cases/unit/24 install umask/subdir/datafile.dog new file mode 100644 index 0000000..7a5bcb7 --- /dev/null +++ b/test cases/unit/24 install umask/subdir/datafile.dog @@ -0,0 +1 @@ +Installed dog is installed. diff --git a/test cases/unit/24 install umask/subdir/sayhello b/test cases/unit/24 install umask/subdir/sayhello new file mode 100755 index 0000000..1e1c90a --- /dev/null +++ b/test cases/unit/24 install umask/subdir/sayhello @@ -0,0 +1,2 @@ +#!/bin/sh +echo 'Hello, World!' |