Merge lp:~parthm/bzr/376388-dot-bazaar-ownership into lp:bzr
- 376388-dot-bazaar-ownership
- Merge into bzr.dev
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | not available | ||||
Proposed branch: | lp:~parthm/bzr/376388-dot-bazaar-ownership | ||||
Merge into: | lp:bzr | ||||
Diff against target: |
221 lines (+112/-3) 7 files modified
NEWS (+5/-0) bzrlib/config.py (+3/-2) bzrlib/osutils.py (+47/-0) bzrlib/tests/features.py (+10/-0) bzrlib/tests/test_osutils.py (+41/-0) bzrlib/trace.py (+4/-1) doc/developers/testing.txt (+2/-0) |
||||
To merge this branch: | bzr merge lp:~parthm/bzr/376388-dot-bazaar-ownership | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Martin Pool | Approve | ||
Vincent Ladeuil | Needs Fixing | ||
Martin Packman (community) | Needs Fixing | ||
Review via email: mp+19691@code.launchpad.net |
Commit message
Description of the change
Parth Malwankar (parthm) wrote : | # |
Parth Malwankar (parthm) wrote : | # |
Similar issue filed as bug #524306
Martin Packman (gz) wrote : | # |
As mentioned in a different (and now deleted?) merge request, this clashes with that change as they both try and create a shadow of a builtin function in osutils, which I think is bad style.
In this case, I don't see why:
+ ownership_src = osutils.
+ bzr_log_file = osutils.
Is preferable to the more obvious:
+ bzr_log_file = open(_bzr_
+ osutils.
+ osutils.
Would also be nice to avoid this junk entirely on windows, but that doesn't matter much.
Parth Malwankar (parthm) wrote : | # |
> As mentioned in a different (and now deleted?) merge request, this clashes
> with that change as they both try and create a shadow of a builtin function in
> osutils, which I think is bad style.
>
> In this case, I don't see why:
>
> + ownership_src = osutils.
> + bzr_log_file = osutils.
> ownership_src)
>
Good point. Maybe we should have open_with_ownership (or something similar)
for which ownership_src is mandatory. This will keep the usage clean and
also not be confused with the builtin.
Vincent Ladeuil (vila) wrote : | # |
You have some PEP8 issues (see doc/developpers
namely:
- no spaces around '='
27 + f = osutils.
- lines should be no more than 79 characters (watch for your tests docstrings
in particular)
58 +def parent_dir(path):
This looks weird, why not just embed that code in copy_ownership like your docstring
seem to imply ?
87 +def open_with_
88 + """This function wraps the python builtin open. filename, mode and bufsize
From "Code Layout" cited above:
One often-missed requirement is that the first line of docstrings
should be a self-contained one-sentence summary.
135 + # we can't overrideAttr os.chown on OS that doesn't support chown
136 + if os.name == 'posix' and hasattr(os, 'chown'):
137 + self.overrideAt
You don't need to protect the call since your tests already depends on tests.ChownFeature.
145 + def test_mkdir_
146 + """ensure that osutils.
147 + self.requireFea
You can avoid avoid duplicating that check for each test by doing:
class TestCreationOps
_test_
142 + def _dummy_chown(self, path, uid, gid):
143 + self.path, self.uid, self.gid = path, uid, gid
Hmm, using a dummy here is questionable since you don't really test calling chown(),
I'd prefer a recording variant like:
def _instrumented_
self.
return os.chown(path, uid, gid)
so you can both check that it was called *and* verify what was done.
180 + ownsrc = '/'
Ouch ! You're playing with fire here, I realize you may want to test with a different
uid/gid than the current user but... you'd better create such a reference file explicitly.
On the overall, I'm still a bit doubtful on the whole approach, it seems fine for root
but what happens if we use 'sudo -Ujoe' ?
Parth Malwankar (parthm) wrote : | # |
Thanks for your comments Vincent.
I have updated the code based on your review comments.
The testing approach ( http://
Based on the discussion vila and I had on IRC it seems that user foo cannot change ownership to user bar and chown would work only under sudo. Can such a situation arise? Not having a thorough test case or approach that works under all situations seems somewhat risky.
As discussed I am setting the status to Needs Review so that we can thrash out the solution some more as needed. I would be happy to try out any other ideas. Based on the outcome we could either update the patch or reject it.
Vincent Ladeuil (vila) wrote : | # |
See https:/
only root can use chown.
Parth Malwankar (parthm) wrote : | # |
> See https:/
> only root can use chown.
One approach could be to catch OSError during chown. That way
the ownership would be correct under sudo and in the normal
case the user won't be bothered.
Martin Pool (mbp) wrote : | # |
56
57 +def copy_ownership(dst, src=None):
58 + """copy usr/grp ownership from src file/dir to dst file/dir.
59 + If src is None, the containing directory is used as source."""
60 + if os.name != 'posix':
61 + return False
62 +
63 + if src == None:
64 + src = os.path.
65 + if src == '':
66 + src = '.'
67 +
68 + try:
69 + s = os.stat(src)
70 + os.chown(dst, s.st_uid, s.st_gid)
71 + except OSError, e:
72 + trace.warning(
73 + return True
74 +
Your approach of catching OSError is fne.
Normally we would write the message as:
Unable to copy ownership from "%s" to "%s": %s
with the message at the end.
I think rather than checking os.name it would be better to check if os.chown exists, ie
chown = getattr(os, 'chown')
if chown is None: return
Why does this return a boolean? If it needs to, you should document and maybe test the value. Otherwise, don't return anything.
For consistency our docstrings have:
* Sentence capitalization and punctuation
* If possible a single sentence on the first line
* A blank line between paragraphs
* The closing quotes on a separate line
See http://
114 +
115 +class _ChownFeature(
116 + """os.chown is supported"""
117 +
118 + def _probe(self):
119 + return os.name == 'posix' and hasattr(os, 'chown')
120 +
121 +ChownFeature = _ChownFeature()
122 +
Normally the instance of the feature object should be named like an instance, ie lowercase chown_feature. Sorry this is confusing, we changed it a while ago.
Thanks!
Martin
Parth Malwankar (parthm) wrote : | # |
Thanks for the review Martin.
I have made the relevant changes.
Martin Pool (mbp) : | # |
Martin Pool (mbp) wrote : | # |
I'll resolve the conflicts, tweak the feature name to be lowercase, and submit this.
Parth Malwankar (parthm) wrote : | # |
Thanks Martin.
Preview Diff
1 | === modified file 'NEWS' |
2 | --- NEWS 2010-03-11 05:38:54 +0000 |
3 | +++ NEWS 2010-03-11 06:39:25 +0000 |
4 | @@ -120,6 +120,11 @@ |
5 | * Tolerate patches with leading noise in ``bzr-handle-patch``. |
6 | (Toshio Kuratomi, Martin Pool, #502076) |
7 | |
8 | +* ``.bazaar``, ``.bazaar/bazaar.conf`` and ``.bzr.log`` inherit user and |
9 | + group ownership from the containing directory. This allow bzr to work |
10 | + better with sudo. |
11 | + (Parth Malwankar, #376388) |
12 | + |
13 | API Changes |
14 | *********** |
15 | |
16 | |
17 | === modified file 'bzrlib/config.py' |
18 | --- bzrlib/config.py 2010-02-23 07:43:11 +0000 |
19 | +++ bzrlib/config.py 2010-03-11 06:39:25 +0000 |
20 | @@ -510,7 +510,8 @@ |
21 | self._write_config_file() |
22 | |
23 | def _write_config_file(self): |
24 | - f = open(self._get_filename(), 'wb') |
25 | + path = self._get_filename() |
26 | + f = osutils.open_with_ownership(path, 'wb') |
27 | self._get_parser().write(f) |
28 | f.close() |
29 | |
30 | @@ -809,7 +810,7 @@ |
31 | trace.mutter('creating config parent directory: %r', parent_dir) |
32 | os.mkdir(parent_dir) |
33 | trace.mutter('creating config directory: %r', path) |
34 | - os.mkdir(path) |
35 | + osutils.mkdir_with_ownership(path) |
36 | |
37 | |
38 | def config_dir(): |
39 | |
40 | === modified file 'bzrlib/osutils.py' |
41 | --- bzrlib/osutils.py 2010-03-11 04:33:41 +0000 |
42 | +++ bzrlib/osutils.py 2010-03-11 06:39:25 +0000 |
43 | @@ -51,7 +51,9 @@ |
44 | cache_utf8, |
45 | errors, |
46 | win32utils, |
47 | + trace, |
48 | ) |
49 | + |
50 | """) |
51 | |
52 | # sha and md5 modules are deprecated in python2.6 but hashlib is available as |
53 | @@ -1804,6 +1806,51 @@ |
54 | real_handlers[kind](abspath, relpath) |
55 | |
56 | |
57 | +def copy_ownership(dst, src=None): |
58 | + """Copy usr/grp ownership from src file/dir to dst file/dir. |
59 | + |
60 | + If src is None, the containing directory is used as source. If chown |
61 | + fails, the error is ignored and a warning is printed. |
62 | + """ |
63 | + has_chown = getattr(os, 'chown') |
64 | + if has_chown is None: return |
65 | + |
66 | + if src == None: |
67 | + src = os.path.dirname(dst) |
68 | + if src == '': |
69 | + src = '.' |
70 | + |
71 | + try: |
72 | + s = os.stat(src) |
73 | + os.chown(dst, s.st_uid, s.st_gid) |
74 | + except OSError, e: |
75 | + trace.warning("Unable to copy ownership from '%s' to '%s': IOError: %s." % (src, dst, e)) |
76 | + |
77 | + |
78 | +def mkdir_with_ownership(path, ownership_src=None): |
79 | + """Create the directory 'path' with specified ownership. |
80 | + |
81 | + If ownership_src is given, copies (chown) usr/grp ownership |
82 | + from 'ownership_src' to 'path'. If ownership_src is None, use the |
83 | + containing dir ownership. |
84 | + """ |
85 | + os.mkdir(path) |
86 | + copy_ownership(path, ownership_src) |
87 | + |
88 | + |
89 | +def open_with_ownership(filename, mode='r', bufsize=-1, ownership_src=None): |
90 | + """Open the file 'filename' with the specified ownership. |
91 | + |
92 | + If ownership_src is specified, copy usr/grp ownership from ownership_src |
93 | + to filename. If ownership_src is None, copy ownership from containing |
94 | + directory. |
95 | + Returns the opened file object. |
96 | + """ |
97 | + f = open(filename, mode, bufsize) |
98 | + copy_ownership(filename, ownership_src) |
99 | + return f |
100 | + |
101 | + |
102 | def path_prefix_key(path): |
103 | """Generate a prefix-order path key for path. |
104 | |
105 | |
106 | === modified file 'bzrlib/tests/features.py' |
107 | --- bzrlib/tests/features.py 2010-02-23 07:43:11 +0000 |
108 | +++ bzrlib/tests/features.py 2010-03-11 06:39:25 +0000 |
109 | @@ -14,6 +14,7 @@ |
110 | # along with this program; if not, write to the Free Software |
111 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
112 | |
113 | +import os |
114 | |
115 | from bzrlib import tests |
116 | from bzrlib.symbol_versioning import deprecated_in |
117 | @@ -23,3 +24,12 @@ |
118 | paramiko = tests.ModuleAvailableFeature('paramiko') |
119 | pycurl = tests.ModuleAvailableFeature('pycurl') |
120 | subunit = tests.ModuleAvailableFeature('subunit') |
121 | + |
122 | +class _ChownFeature(tests.Feature): |
123 | + """os.chown is supported""" |
124 | + |
125 | + def _probe(self): |
126 | + return os.name == 'posix' and hasattr(os, 'chown') |
127 | + |
128 | +chown_feature = _ChownFeature() |
129 | + |
130 | |
131 | === modified file 'bzrlib/tests/test_osutils.py' |
132 | --- bzrlib/tests/test_osutils.py 2010-02-23 07:43:11 +0000 |
133 | +++ bzrlib/tests/test_osutils.py 2010-03-11 06:39:25 +0000 |
134 | @@ -33,6 +33,7 @@ |
135 | win32utils, |
136 | ) |
137 | from bzrlib.tests import ( |
138 | + features, |
139 | file_utils, |
140 | test__walkdirs_win32, |
141 | ) |
142 | @@ -1975,3 +1976,43 @@ |
143 | del os.environ['COLUMNS'] |
144 | # Whatever the result is, if we don't raise an exception, it's ok. |
145 | osutils.terminal_width() |
146 | + |
147 | +class TestCreationOps(tests.TestCaseInTempDir): |
148 | + _test_needs_features = [features.chown_feature] |
149 | + |
150 | + def setUp(self): |
151 | + tests.TestCaseInTempDir.setUp(self) |
152 | + self.overrideAttr(os, 'chown', self._dummy_chown) |
153 | + |
154 | + # params set by call to _dummy_chown |
155 | + self.path = self.uid = self.gid = None |
156 | + |
157 | + def _dummy_chown(self, path, uid, gid): |
158 | + self.path, self.uid, self.gid = path, uid, gid |
159 | + |
160 | + def test_mkdir_with_ownership_chown(self): |
161 | + """Ensure that osutils.mkdir_with_ownership chowns correctly with ownership_src. |
162 | + """ |
163 | + ownsrc = '/' |
164 | + osutils.mkdir_with_ownership('foo', ownsrc) |
165 | + |
166 | + s = os.stat(ownsrc) |
167 | + self.assertEquals(self.path, 'foo') |
168 | + self.assertEquals(self.uid, s.st_uid) |
169 | + self.assertEquals(self.gid, s.st_gid) |
170 | + |
171 | + def test_open_with_ownership_chown(self): |
172 | + """Ensure that osutils.open_with_ownership chowns correctly with ownership_src. |
173 | + """ |
174 | + ownsrc = '/' |
175 | + f = osutils.open_with_ownership('foo', 'w', ownership_src=ownsrc) |
176 | + |
177 | + # do a test write and close |
178 | + f.write('hello') |
179 | + f.close() |
180 | + |
181 | + s = os.stat(ownsrc) |
182 | + self.assertEquals(self.path, 'foo') |
183 | + self.assertEquals(self.uid, s.st_uid) |
184 | + self.assertEquals(self.gid, s.st_gid) |
185 | + |
186 | |
187 | === modified file 'bzrlib/trace.py' |
188 | --- bzrlib/trace.py 2010-02-24 01:20:51 +0000 |
189 | +++ bzrlib/trace.py 2010-03-11 06:39:25 +0000 |
190 | @@ -241,14 +241,17 @@ |
191 | _bzr_log_filename = _get_bzr_log_filename() |
192 | _rollover_trace_maybe(_bzr_log_filename) |
193 | try: |
194 | - bzr_log_file = open(_bzr_log_filename, 'at', buffering=0) # unbuffered |
195 | + buffering = 0 # unbuffered |
196 | + bzr_log_file = osutils.open_with_ownership(_bzr_log_filename, 'at', buffering) |
197 | # bzr_log_file.tell() on windows always return 0 until some writing done |
198 | bzr_log_file.write('\n') |
199 | if bzr_log_file.tell() <= 2: |
200 | bzr_log_file.write("this is a debug log for diagnosing/reporting problems in bzr\n") |
201 | bzr_log_file.write("you can delete or truncate this file, or include sections in\n") |
202 | bzr_log_file.write("bug reports to https://bugs.launchpad.net/bzr/+filebug\n\n") |
203 | + |
204 | return bzr_log_file |
205 | + |
206 | except IOError, e: |
207 | # If we are failing to open the log, then most likely logging has not |
208 | # been set up yet. So we just write to stderr rather than using |
209 | |
210 | === modified file 'doc/developers/testing.txt' |
211 | --- doc/developers/testing.txt 2010-02-28 10:08:29 +0000 |
212 | +++ doc/developers/testing.txt 2010-03-11 06:39:25 +0000 |
213 | @@ -576,6 +576,8 @@ |
214 | - UnicodeFilenameFeature |
215 | - FTPServerFeature |
216 | - CaseInsensitiveFilesystemFeature. |
217 | + - ChownFeature: The test can rely on OS being POSIX and python |
218 | + supporting os.chown. |
219 | |
220 | |
221 | Defining a new feature that tests can require |
This patch fixes bug #376388 by ensuring the .bazaar,
.bazaar/bazaar.conf and .bzr.log inherit usr and grp ownership
from the containing folder.
Following functions are added to osutils:
* copy_ownership: copies usr/grp ownership from src file/dir to dst file/dir
* mkdir: wraps os.mkdir and adds an optional arg ownership_src. When provided,
the newly created dir is given ownership based on ownership_src.
* open: wraps __builtin__.open and adds an optional arg ownership_src. When provided,
the newly created file is given ownership based on ownership_src.
* parent_dir: wraps os.path.dirname handling the special case of dirname returning ''
by returning '.' instead.
config.py and trace.py are updated to use osutils. {mkdir, open} rather than the
base functions during creation of above mentioned files.
Whitebox tests are added to test_osutils.py to verify mkdir, open and parent_dir.
Manual testing for done using "sudo bzr whoami a@b".
ChmodFeature was added to tests/__init__.py to ensure os.chmod support for test
cases.
This proposal was originally based of 2.0: /code.launchpad .net/~parthm/ bzr/2.0_ 376388_ dot_bazaar_ ownership/ +merge/ 19593/
https:/
but is resubmitted now as it is meant to go into trunk and testing requires
overrideAttr support that is not available in 2.0 branch.