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