1 |
On Fri, Dec 29, 2017 at 7:19 PM, Zac Medico <zmedico@g.o> wrote: |
2 |
|
3 |
> Bug: https://bugs.gentoo.org/642632 |
4 |
> --- |
5 |
> [PATCH v2] fix to copy atime, and split out _set_timestamps function |
6 |
> |
7 |
> bin/doins.py | 28 +++++++++++++++++++++++++--- |
8 |
> pym/portage/tests/bin/test_doins.py | 6 ++++-- |
9 |
> 2 files changed, 29 insertions(+), 5 deletions(-) |
10 |
> |
11 |
> diff --git a/bin/doins.py b/bin/doins.py |
12 |
> index 92e450979..0d03d8fb2 100644 |
13 |
> --- a/bin/doins.py |
14 |
> +++ b/bin/doins.py |
15 |
> @@ -107,6 +107,7 @@ def _parse_install_options( |
16 |
> parser.add_argument('-g', '--group', default=-1, type=_parse_group) |
17 |
> parser.add_argument('-o', '--owner', default=-1, type=_parse_user) |
18 |
> parser.add_argument('-m', '--mode', default=0o755, |
19 |
> type=_parse_mode) |
20 |
> + parser.add_argument('-p', '--preserve-timestamps', |
21 |
> action='store_true') |
22 |
> split_options = shlex.split(options) |
23 |
> namespace, remaining = parser.parse_known_args(split_options) |
24 |
> # Because parsing '--mode' option is partially supported. If |
25 |
> unknown |
26 |
> @@ -139,6 +140,24 @@ def _set_attributes(options, path): |
27 |
> os.chmod(path, options.mode) |
28 |
> |
29 |
> |
30 |
> +def _set_timestamps(source_stat, dest): |
31 |
> + """Apply timestamps from source_stat to dest. |
32 |
> + |
33 |
> + Args: |
34 |
> + source_stat: stat result for the source file. |
35 |
> + dest: path to the dest file. |
36 |
> + """ |
37 |
> + os.utime(dest, (source_stat.st_atime, source_stat.st_mtime)) |
38 |
> + |
39 |
> + |
40 |
> +if sys.version_info >= (3, 3): |
41 |
> + def _set_timestamps_ns(source_stat, dest): |
42 |
> + os.utime(dest, ns=(source_stat.st_atime_ns, |
43 |
> source_stat.st_mtime_ns)) |
44 |
> + |
45 |
> + _set_timestamps_ns.__doc__ = _set_timestamps.__doc__ |
46 |
> + _set_timestamps = _set_timestamps_ns |
47 |
> + |
48 |
> + |
49 |
> class _InsInProcessInstallRunner(object): |
50 |
> """Implements `install` command behavior running in a process.""" |
51 |
> |
52 |
> @@ -168,7 +187,8 @@ class _InsInProcessInstallRunner(object): |
53 |
> True on success, otherwise False. |
54 |
> """ |
55 |
> dest = os.path.join(dest_dir, os.path.basename(source)) |
56 |
> - if not self._is_install_allowed(source, dest): |
57 |
> |
58 |
|
59 |
I'm going to be api picky here (so forgive me). |
60 |
|
61 |
Previously is_install_allowed seemed to take great care with which stat was |
62 |
used. But now callers have to just know to use os.stat (as opposed to |
63 |
lstat, or other stats)? |
64 |
|
65 |
Should we update the is_install_allowed comment (perhaps to say that |
66 |
source_stat prefers os.stat?) |
67 |
|
68 |
Part of me just wishes the stat was cached at a different layer (so that |
69 |
code like this wasn't required just to save the syscall ;( |
70 |
|
71 |
-A |
72 |
|
73 |
+ sstat = os.stat(source) |
74 |
> + if not self._is_install_allowed(source, sstat, dest): |
75 |
> return False |
76 |
> |
77 |
> # To emulate the `install` command, remove the dest file in |
78 |
> @@ -187,6 +207,8 @@ class _InsInProcessInstallRunner(object): |
79 |
> movefile._copyxattr( |
80 |
> source, dest, |
81 |
> exclude=self._xattr_exclude) |
82 |
> + if self._parsed_options.preserve_timestamps: |
83 |
> + _set_timestamps(sstat, dest) |
84 |
> except Exception: |
85 |
> logging.exception( |
86 |
> 'Failed to copy file: ' |
87 |
> @@ -195,13 +217,14 @@ class _InsInProcessInstallRunner(object): |
88 |
> return False |
89 |
> return True |
90 |
> |
91 |
> - def _is_install_allowed(self, source, dest): |
92 |
> + def _is_install_allowed(self, source, source_stat, dest): |
93 |
> """Returns if installing source into dest should work. |
94 |
> |
95 |
> This is to keep compatibility with the `install` command. |
96 |
> |
97 |
> Args: |
98 |
> source: path to the source file. |
99 |
> + source_stat: stat result for the source file. |
100 |
> dest: path to the dest file. |
101 |
> |
102 |
> Returns: |
103 |
> @@ -210,7 +233,6 @@ class _InsInProcessInstallRunner(object): |
104 |
> # To match `install` command, use stat() for source, while |
105 |
> # lstat() for dest. Raise an exception if stat(source) |
106 |
> fails, |
107 |
> # intentionally. |
108 |
> - source_stat = os.stat(source) |
109 |
> try: |
110 |
> dest_lstat = os.lstat(dest) |
111 |
> except OSError as e: |
112 |
> diff --git a/pym/portage/tests/bin/test_doins.py |
113 |
> b/pym/portage/tests/bin/test_doins.py |
114 |
> index 14d7adfa6..9b6c55d85 100644 |
115 |
> --- a/pym/portage/tests/bin/test_doins.py |
116 |
> +++ b/pym/portage/tests/bin/test_doins.py |
117 |
> @@ -38,13 +38,15 @@ class DoIns(setup_env.BinTestCase): |
118 |
> self.init() |
119 |
> try: |
120 |
> env = setup_env.env |
121 |
> - env['INSOPTIONS'] = '-m0644' |
122 |
> + env['INSOPTIONS'] = '-pm0644' |
123 |
> with open(os.path.join(env['S'], 'test'), 'w'): |
124 |
> pass |
125 |
> doins('test') |
126 |
> st = os.lstat(env['D'] + '/test') |
127 |
> if stat.S_IMODE(st.st_mode) != 0o644: |
128 |
> raise tests.TestCase.failureException |
129 |
> + if os.stat(os.path.join(env['S'], |
130 |
> 'test')).st_mtime != st.st_mtime: |
131 |
> + raise tests.TestCase.failureException |
132 |
> finally: |
133 |
> self.cleanup() |
134 |
> |
135 |
> @@ -145,7 +147,7 @@ class DoIns(setup_env.BinTestCase): |
136 |
> env = setup_env.env |
137 |
> # Use an option which doins.py does not know. |
138 |
> # Then, fallback to `install` command is expected. |
139 |
> - env['INSOPTIONS'] = '-p' |
140 |
> + env['INSOPTIONS'] = '-b' |
141 |
> with open(os.path.join(env['S'], 'test'), 'w'): |
142 |
> pass |
143 |
> doins('test') |
144 |
> -- |
145 |
> 2.13.6 |
146 |
> |
147 |
> |
148 |
> |