Gentoo Archives: gentoo-portage-dev

From: Zac Medico <zmedico@g.o>
To: gentoo-portage-dev@l.g.o
Cc: Zac Medico <zmedico@g.o>
Subject: [gentoo-portage-dev] [PATCH v3] bin/doins.py: implement install -p option (bug 642632)
Date: Tue, 02 Jan 2018 00:00:37
Message-Id: 20180101235444.230302-1-zmedico@gentoo.org
In Reply to: [gentoo-portage-dev] [PATCH] bin/doins.py: implement install -p option (bug 642632) by Zac Medico
1 Bug: https://bugs.gentoo.org/642632
2 ---
3 [PATCH v3] update _is_install_allowed docstring to specify that source_stat
4 should be obtained with stat() rather than lstat()
5
6 bin/doins.py | 34 +++++++++++++++++++++++++++++-----
7 pym/portage/tests/bin/test_doins.py | 6 ++++--
8 2 files changed, 33 insertions(+), 7 deletions(-)
9
10 diff --git a/bin/doins.py b/bin/doins.py
11 index 92e450979..9e6566097 100644
12 --- a/bin/doins.py
13 +++ b/bin/doins.py
14 @@ -107,6 +107,7 @@ def _parse_install_options(
15 parser.add_argument('-g', '--group', default=-1, type=_parse_group)
16 parser.add_argument('-o', '--owner', default=-1, type=_parse_user)
17 parser.add_argument('-m', '--mode', default=0o755, type=_parse_mode)
18 + parser.add_argument('-p', '--preserve-timestamps', action='store_true')
19 split_options = shlex.split(options)
20 namespace, remaining = parser.parse_known_args(split_options)
21 # Because parsing '--mode' option is partially supported. If unknown
22 @@ -139,6 +140,24 @@ def _set_attributes(options, path):
23 os.chmod(path, options.mode)
24
25
26 +def _set_timestamps(source_stat, dest):
27 + """Apply timestamps from source_stat to dest.
28 +
29 + Args:
30 + source_stat: stat result for the source file.
31 + dest: path to the dest file.
32 + """
33 + os.utime(dest, (source_stat.st_atime, source_stat.st_mtime))
34 +
35 +
36 +if sys.version_info >= (3, 3):
37 + def _set_timestamps_ns(source_stat, dest):
38 + os.utime(dest, ns=(source_stat.st_atime_ns, source_stat.st_mtime_ns))
39 +
40 + _set_timestamps_ns.__doc__ = _set_timestamps.__doc__
41 + _set_timestamps = _set_timestamps_ns
42 +
43 +
44 class _InsInProcessInstallRunner(object):
45 """Implements `install` command behavior running in a process."""
46
47 @@ -168,7 +187,9 @@ class _InsInProcessInstallRunner(object):
48 True on success, otherwise False.
49 """
50 dest = os.path.join(dest_dir, os.path.basename(source))
51 - if not self._is_install_allowed(source, dest):
52 + # Raise an exception if stat(source) fails, intentionally.
53 + sstat = os.stat(source)
54 + if not self._is_install_allowed(source, sstat, dest):
55 return False
56
57 # To emulate the `install` command, remove the dest file in
58 @@ -187,6 +208,8 @@ class _InsInProcessInstallRunner(object):
59 movefile._copyxattr(
60 source, dest,
61 exclude=self._xattr_exclude)
62 + if self._parsed_options.preserve_timestamps:
63 + _set_timestamps(sstat, dest)
64 except Exception:
65 logging.exception(
66 'Failed to copy file: '
67 @@ -195,22 +218,23 @@ class _InsInProcessInstallRunner(object):
68 return False
69 return True
70
71 - def _is_install_allowed(self, source, dest):
72 + def _is_install_allowed(self, source, source_stat, dest):
73 """Returns if installing source into dest should work.
74
75 This is to keep compatibility with the `install` command.
76
77 Args:
78 source: path to the source file.
79 + source_stat: stat result for the source file, using stat()
80 + rather than lstat(), in order to match the `install`
81 + command
82 dest: path to the dest file.
83
84 Returns:
85 True if it should succeed.
86 """
87 # To match `install` command, use stat() for source, while
88 - # lstat() for dest. Raise an exception if stat(source) fails,
89 - # intentionally.
90 - source_stat = os.stat(source)
91 + # lstat() for dest.
92 try:
93 dest_lstat = os.lstat(dest)
94 except OSError as e:
95 diff --git a/pym/portage/tests/bin/test_doins.py b/pym/portage/tests/bin/test_doins.py
96 index 14d7adfa6..9b6c55d85 100644
97 --- a/pym/portage/tests/bin/test_doins.py
98 +++ b/pym/portage/tests/bin/test_doins.py
99 @@ -38,13 +38,15 @@ class DoIns(setup_env.BinTestCase):
100 self.init()
101 try:
102 env = setup_env.env
103 - env['INSOPTIONS'] = '-m0644'
104 + env['INSOPTIONS'] = '-pm0644'
105 with open(os.path.join(env['S'], 'test'), 'w'):
106 pass
107 doins('test')
108 st = os.lstat(env['D'] + '/test')
109 if stat.S_IMODE(st.st_mode) != 0o644:
110 raise tests.TestCase.failureException
111 + if os.stat(os.path.join(env['S'], 'test')).st_mtime != st.st_mtime:
112 + raise tests.TestCase.failureException
113 finally:
114 self.cleanup()
115
116 @@ -145,7 +147,7 @@ class DoIns(setup_env.BinTestCase):
117 env = setup_env.env
118 # Use an option which doins.py does not know.
119 # Then, fallback to `install` command is expected.
120 - env['INSOPTIONS'] = '-p'
121 + env['INSOPTIONS'] = '-b'
122 with open(os.path.join(env['S'], 'test'), 'w'):
123 pass
124 doins('test')
125 --
126 2.13.6