aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJussi Pakkanen <jpakkane@gmail.com>2018-04-26 23:14:00 +0300
committerGitHub <noreply@github.com>2018-04-26 23:14:00 +0300
commit9b0453d3e937195ca234963182dc1b5a61a13f23 (patch)
tree56bde337fe06e8726af04676c0a386cd15be6e62
parent9c073620aa351c4075aa838fa5003ee0fc8ec17a (diff)
parent170776d626373762b220aad8ac7e5b57f18156ed (diff)
downloadmeson-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.md17
-rw-r--r--mesonbuild/backend/backends.py4
-rw-r--r--mesonbuild/backend/ninjabackend.py4
-rw-r--r--mesonbuild/coredata.py25
-rw-r--r--mesonbuild/mconf.py2
-rw-r--r--mesonbuild/scripts/meson_install.py41
-rwxr-xr-xrun_unittests.py68
-rw-r--r--test cases/common/12 data/meson.build3
-rw-r--r--test cases/common/66 install subdir/meson.build3
-rw-r--r--test cases/unit/24 install umask/datafile.cat1
-rw-r--r--test cases/unit/24 install umask/meson.build7
-rw-r--r--test cases/unit/24 install umask/myinstall.py17
-rw-r--r--test cases/unit/24 install umask/prog.11
-rw-r--r--test cases/unit/24 install umask/prog.c3
-rw-r--r--test cases/unit/24 install umask/sample.h6
-rw-r--r--test cases/unit/24 install umask/subdir/datafile.dog1
-rwxr-xr-xtest cases/unit/24 install umask/subdir/sayhello2
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!'