Gentoo Archives: gentoo-portage-dev

From: Alec Warner <antarus@g.o>
To: gentoo-portage-dev@l.g.o
Cc: Zac Medico <zmedico@g.o>
Subject: Re: [gentoo-portage-dev] [PATCH v2] bin/doins.py: implement install -p option (bug 642632)
Date: Sun, 31 Dec 2017 19:55:53
Message-Id: CAAr7Pr8um=fso3ULxGLDGQ1aD2ERjabnr3VKPueUFV6QRYJ8FA@mail.gmail.com
In Reply to: [gentoo-portage-dev] [PATCH v2] bin/doins.py: implement install -p option (bug 642632) by Zac Medico
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 >

Replies