Bug: https://bugs.gentoo.org/642632 --- [PATCH v2] fix to copy atime, and split out _set_timestamps function
bin/doins.py | 28 +++++++++++++++++++++++++--- pym/portage/tests/bin/test_doins.py | 6 ++++-- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/bin/doins.py b/bin/doins.py index 92e450979..0d03d8fb2 100644 --- a/bin/doins.py +++ b/bin/doins.py @@ -107,6 +107,7 @@ def _parse_install_options( parser.add_argument('-g', '--group', default=-1, type=_parse_group) parser.add_argument('-o', '--owner', default=-1, type=_parse_user) parser.add_argument('-m', '--mode', default=0o755, type=_parse_mode) + parser.add_argument('-p', '--preserve-timestamps', action='store_true') split_options = shlex.split(options) namespace, remaining = parser.parse_known_args(split_options) # Because parsing '--mode' option is partially supported. If unknown @@ -139,6 +140,24 @@ def _set_attributes(options, path): os.chmod(path, options.mode) +def _set_timestamps(source_stat, dest): + """Apply timestamps from source_stat to dest. + + Args: + source_stat: stat result for the source file. + dest: path to the dest file. + """ + os.utime(dest, (source_stat.st_atime, source_stat.st_mtime)) + + +if sys.version_info >= (3, 3): + def _set_timestamps_ns(source_stat, dest): + os.utime(dest, ns=(source_stat.st_atime_ns, source_stat.st_mtime_ns)) + + _set_timestamps_ns.__doc__ = _set_timestamps.__doc__ + _set_timestamps = _set_timestamps_ns + + class _InsInProcessInstallRunner(object): """Implements `install` command behavior running in a process.""" @@ -168,7 +187,8 @@ class _InsInProcessInstallRunner(object): True on success, otherwise False. """ dest = os.path.join(dest_dir, os.path.basename(source)) - if not self._is_install_allowed(source, dest): + sstat = os.stat(source) + if not self._is_install_allowed(source, sstat, dest): return False # To emulate the `install` command, remove the dest file in @@ -187,6 +207,8 @@ class _InsInProcessInstallRunner(object): movefile._copyxattr( source, dest, exclude=self._xattr_exclude) + if self._parsed_options.preserve_timestamps: + _set_timestamps(sstat, dest) except Exception: logging.exception( 'Failed to copy file: ' @@ -195,13 +217,14 @@ class _InsInProcessInstallRunner(object): return False return True - def _is_install_allowed(self, source, dest): + def _is_install_allowed(self, source, source_stat, dest): """Returns if installing source into dest should work. This is to keep compatibility with the `install` command. Args: source: path to the source file. + source_stat: stat result for the source file. dest: path to the dest file. Returns: @@ -210,7 +233,6 @@ class _InsInProcessInstallRunner(object): # To match `install` command, use stat() for source, while # lstat() for dest. Raise an exception if stat(source) fails, # intentionally. - source_stat = os.stat(source) try: dest_lstat = os.lstat(dest) except OSError as e: diff --git a/pym/portage/tests/bin/test_doins.py b/pym/portage/tests/bin/test_doins.py index 14d7adfa6..9b6c55d85 100644 --- a/pym/portage/tests/bin/test_doins.py +++ b/pym/portage/tests/bin/test_doins.py @@ -38,13 +38,15 @@ class DoIns(setup_env.BinTestCase): self.init() try: env = setup_env.env - env['INSOPTIONS'] = '-m0644' + env['INSOPTIONS'] = '-pm0644' with open(os.path.join(env['S'], 'test'), 'w'): pass doins('test') st = os.lstat(env['D'] + '/test') if stat.S_IMODE(st.st_mode) != 0o644: raise tests.TestCase.failureException + if os.stat(os.path.join(env['S'], 'test')).st_mtime != st.st_mtime: + raise tests.TestCase.failureException finally: self.cleanup() @@ -145,7 +147,7 @@ class DoIns(setup_env.BinTestCase): env = setup_env.env # Use an option which doins.py does not know. # Then, fallback to `install` command is expected. - env['INSOPTIONS'] = '-p' + env['INSOPTIONS'] = '-b' with open(os.path.join(env['S'], 'test'), 'w'): pass doins('test') -- 2.13.6