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 |