1 |
On Sat, Nov 19, 2005 at 12:43:43AM +0900, Jason Stubbs wrote: |
2 |
> +++ ./pym/portage_file.py 2005-11-16 01:56:19.157073160 -0500 |
3 |
> |
4 |
> + # if the dir perms would lack +wx, we have to force it |
5 |
> + force_temp_perms = ((mode & 0300) != 0300) |
6 |
> + resets = [] |
7 |
> + apath = normpath(os.path.abspath(path)) |
8 |
> + sticky_parent = False |
9 |
> + creating = False |
10 |
> + |
11 |
> + for dir in apath.split(os.path.sep): |
12 |
> + base = os.path.join(base,dir) |
13 |
> + try: |
14 |
> + st = os.stat(base) |
15 |
> + # something exists. why are we doing isdir? |
16 |
> + if not stat.S_ISDIR(st.st_mode): |
17 |
> + return False |
18 |
> |
19 |
> What's with the comment here? |
20 |
I blame crack. |
21 |
Rhetorical question in the code, which isn't very clear in hindsight |
22 |
|
23 |
> |
24 |
> + # if it's a subdir, we need +wx at least |
25 |
> + if apath != base: |
26 |
> + if ((st.st_mode & 0300) != 0300): |
27 |
> + try: os.chmod(base, (st.st_mode | 0300)) |
28 |
> + except OSError: return False |
29 |
> + resets.append((base, st.st_mode)) |
30 |
> + sticky_parent = (st.st_gid & stat.S_ISGID) |
31 |
> |
32 |
> <snip> |
33 |
> |
34 |
> + try: |
35 |
> + for base, m in reversed(resets): |
36 |
> + os.chmod(base, m) |
37 |
> + if uid != -1 or gid != -1: |
38 |
> + os.chown(base, uid, gid) |
39 |
> + except OSError: |
40 |
> + return False |
41 |
> |
42 |
> If for some reason the parent dir originally has perms 0000, this code will |
43 |
> incorrectly change its uid and gid. |
44 |
That's buggy anyways, since it won't play well with /blah |
45 |
|
46 |
> +++ ./pym/portage_locks.py 2005-11-16 01:56:25.152161768 -0500 |
47 |
> @@ -358,3 +359,91 @@ |
48 |
> +# should the fd be left open indefinitely? |
49 |
> +# IMO, it shouldn't, but opening/closing everytime around is expensive |
50 |
> |
51 |
> It should be closed when locks are released, I think. Otherwise the file may |
52 |
> be deleted and recreated while one FsLock instance has it open which could |
53 |
> create situations where two FsLocks are trying to lock the same path but |
54 |
> using different fds. |
55 |
Leave it open, on locking do a inode verification of the existing |
56 |
handle- if it's behind, close the fd, open, resume locking (offhand). |
57 |
|
58 |
Race potential, but that can be addressed by getting the read lock, |
59 |
then doing a second (or first, if it's not re-upping a lock) inode |
60 |
verification |
61 |
|
62 |
> +class FsLock(object): |
63 |
> + __slots__ = ["path", "fd", "create"] |
64 |
> + def __init__(self, path, create=False): |
65 |
> + """path specifies the fs path for the lock |
66 |
> + create controls whether the file will be create if the file doesn't exist |
67 |
> + if create is true, the base dir must exist, and it will create a file |
68 |
> + |
69 |
> + If you want a directory yourself, create it. |
70 |
> + """ |
71 |
> + self.path = path |
72 |
> + self.fd = None |
73 |
> + self.create = create |
74 |
> + if not create: |
75 |
> + if not os.path.exists(path): |
76 |
> + raise NonExistant(path) |
77 |
> + |
78 |
> + def _acquire_fd(self): |
79 |
> + if self.create: |
80 |
> + try: self.fd = os.open(self.path, os.R_OK|os.O_CREAT) |
81 |
> + except OSError, oe: |
82 |
> + raise GenericFailed(self.path, oe) |
83 |
> + else: |
84 |
> + try: self.fd = os.open(self.path, os.R_OK) |
85 |
> + except OSError, oe: raise NonExistant(self.path, oe) |
86 |
> + |
87 |
> + def _enact_change(self, flags, blocking): |
88 |
> + if self.fd == None: |
89 |
> + self._acquire_fd() |
90 |
> + # we do it this way, due to the fact try/except is a bit of a hit |
91 |
> |
92 |
> Instead of? |
93 |
Two fcntl calls instead of one. |
94 |
It's minor offhand, just how it was originally written. |
95 |
|
96 |
> |
97 |
> + if not blocking: |
98 |
> + try: fcntl.flock(self.fd, flags|fcntl.LOCK_NB) |
99 |
> + except IOError, ie: |
100 |
> + if ie.errno == 11: |
101 |
> |
102 |
> 11 ? |
103 |
42 is better? ;) |
104 |
|
105 |
Should be using errno.EAGAIN instead of hardcoding though... |
106 |
|
107 |
> + return False |
108 |
> + raise GenericFailed(self.path, ie) |
109 |
> + else: |
110 |
> + fcntl.flock(self.fd, flags) |
111 |
> + return True |
112 |
> + |
113 |
> + def acquire_write_lock(self, blocking=True): |
114 |
> + """Acquire an exclusive lock |
115 |
> + Returns True if lock is acquired, False if not. |
116 |
> + Note if you have a read lock, it implicitly upgrades atomically""" |
117 |
> + return self._enact_change(fcntl.LOCK_EX, blocking) |
118 |
> + |
119 |
> + def acquire_read_lock(self, blocking=True): |
120 |
> + """Acquire a shared lock |
121 |
> + Returns True if lock is acquired, False if not. |
122 |
> + Note, if you have a write_lock already, it'll implicitly downgrade atomically""" |
123 |
> + return self._enact_change(fcntl.LOCK_SH, blocking) |
124 |
> + |
125 |
> + def release_write_lock(self): |
126 |
> + """Release an exclusive lock if held""" |
127 |
> + self._enact_change(fcntl.LOCK_UN, False) |
128 |
> + |
129 |
> + def release_read_lock(self): |
130 |
> + self._enact_change(fcntl.LOCK_UN, False) |
131 |
> + |
132 |
> + def __del__(self): |
133 |
> + # alright, it's 5:45am, yes this is weird code. |
134 |
> + try: |
135 |
> + if self.fd != None: |
136 |
> + self.release_read_lock() |
137 |
> + finally: |
138 |
> + if self.fd != None: |
139 |
> + os.close(self.fd) |
140 |
> |
141 |
> Allowing an exception to be raised during __del__ is not a good thing... |
142 |
|
143 |
The reason it's left in is that- |
144 |
A) try/finally ensures despite whatever crazyness might occur with |
145 |
locking, the fd gets waxed |
146 |
B) blow ups in the code are visible, rather then silently swallowed |
147 |
C) exceptions in __del__ python complains about, but swallows. |
148 |
|
149 |
So... rather then silently ignoring any crazyness, it allows some |
150 |
noise to be made if the locking code does something weird, and also |
151 |
ensures the fd is closed. |
152 |
|
153 |
Should check self.fd.closed on a sidenote. |
154 |
|
155 |
> Any held lock should be cleaned up too otherwise misbehaving callers could |
156 |
> deadlock. |
157 |
|
158 |
Closing the fd == release of any locks that are bound to that fd. |
159 |
Hence the anal measures to make damn sure the fd gets closed :) |
160 |
|
161 |
> +++ ./pym/portage_modules.py 2005-11-16 01:56:28.269687832 -0500 |
162 |
> +__import_lock = threading.Lock() |
163 |
> + |
164 |
> +def load_module(name): |
165 |
> + """load a module, throwing a FailedImport if __import__ fails""" |
166 |
> + __import_lock.acquire() |
167 |
> + try: |
168 |
> + if name in sys.modules: |
169 |
> + return sys.modules[name] |
170 |
> + try: |
171 |
> + m = __import__(name) |
172 |
> + nl = name.split('.') |
173 |
> + # __import__ returns nl[0]... so. |
174 |
> + nl.pop(0) |
175 |
> + while len(nl): |
176 |
> + m = getattr(m, nl[0]) |
177 |
> + nl.pop(0) |
178 |
> + return m |
179 |
> + # * ferringb|afk hit it at some point, sure of that |
180 |
> + # but no idea how, so commenting out to see if things break... |
181 |
> + # except AttributeError, e: |
182 |
> + # raise FailedImport(name, e) |
183 |
> |
184 |
> I can't see how either, but comments like these don't really help. Please |
185 |
> leave them out unless they describe how the code works. |
186 |
Leaving comments regarding "weirdness occured via here, but went away" |
187 |
isn't a bad thing- if it reappears, you know that at least it was hit |
188 |
once prior. |
189 |
|
190 |
I know how it was hit also (just dawned on me how it would happen)- |
191 |
echo $'import os;print os.dar' > test.py |
192 |
python -c'import test' |
193 |
|
194 |
AttributeError... |
195 |
|
196 |
Should nuke the module imo in that case still, since it's a failed |
197 |
import. |
198 |
|
199 |
> +++ ./pym/portage_plugins.py 2005-11-16 01:56:51.419168576 -0500 |
200 |
> @@ -0,0 +1,174 @@ |
201 |
> +# Copyright: 2005 Gentoo Foundation |
202 |
> +# License: GPL2 |
203 |
> +# $Id:$ |
204 |
> + |
205 |
> +# I don't like this. |
206 |
> +# doesn't seem clean/right. |
207 |
> |
208 |
> ??? |
209 |
Someone didn't clean up the files prior to posting this... |
210 |
|
211 |
> + # this could be fine grained down to per plugin_type |
212 |
> + plug_lock = FsLock(plugins_dir) |
213 |
> |
214 |
> Then it probably should be. ;) |
215 |
It's not needed though. Need a write lock on the plugins dir for when |
216 |
new plugin types are added; the only gain from it is when read locks |
217 |
are held long term, and parallel registration is occuring (for savior, |
218 |
it's possible, for stable, it isn't, not within the portage process at |
219 |
least). |
220 |
|
221 |
> |
222 |
> + def query_plugins(self, plugin_type=None, locking=True, raw=False): |
223 |
> |
224 |
> Not sure about allowing the external callers to disable locking... That |
225 |
> should only be used by register and deregister, right? Perhaps breaking |
226 |
> the unlocked version into a "protected" method? |
227 |
|
228 |
Debatable; any protection used people can stomp right past. Not much |
229 |
for the "sitting on the porch with the shotgun" approach. |
230 |
|
231 |
Not really seeing the gain in hiding the locking tbh; shoving it into |
232 |
the instance namespace would require locking the attribute to maintain |
233 |
thread safety; the only gain is just an extra hoop to jump through if |
234 |
I'm after doing something stupid. :) |
235 |
|
236 |
|
237 |
> Is [x for x in list] 2.2 safe by the way? |
238 |
Yes. The reversed in the code however is not. |
239 |
|
240 |
> + d[x[:-len_exten]] = dict([(y, dict(c.items(y))) for y in c.sections()]) |
241 |
> |
242 |
> Same here on the 2.2 bit... Although that's pretty hard to read either way. |
243 |
Prefer a map call? :) |
244 |
|
245 |
> |
246 |
> + finally: |
247 |
> + if locking: |
248 |
> + plug_lock.release_read_lock() |
249 |
> + if plugin_type != None: |
250 |
> + return d[plugin_type] |
251 |
> + return d |
252 |
> |
253 |
> Not sure about having different return types... |
254 |
Agreed. |
255 |
|
256 |
> +registry = None |
257 |
> +from portage.util.currying import pre_curry, pretty_docs |
258 |
> + |
259 |
> +def proxy_it(method, *a, **kw): |
260 |
> + global registry |
261 |
> + if registry == None: |
262 |
> + registry = GlobalPluginRegistry() |
263 |
> + return getattr(registry, method)(*a, **kw) |
264 |
> + |
265 |
> +for x in ["register", "deregister", "query_plugins", "get_plugin"]: |
266 |
> + v = pre_curry(proxy_it, x) |
267 |
> + doc = getattr(GlobalPluginRegistry, x).__doc__ |
268 |
> + if doc == None: |
269 |
> + doc = '' |
270 |
> + else: |
271 |
> + # do this so indentation on pydoc __doc__ is sane |
272 |
> + doc = "\n".join(map(lambda x:x.lstrip(), doc.split("\n"))) +"\n" |
273 |
> + doc += "proxied call to module level registry instances %s method" % x |
274 |
> + globals()[x] = pretty_docs(v, doc) |
275 |
> + |
276 |
> +del x, v, proxy_it, doc |
277 |
> |
278 |
> What's this currying and pretty_docs stuff? |
279 |
currying == arg binding to a func- in this case, proxy_it gets first |
280 |
arg of the attr requested; the proxy_it func instantiates a |
281 |
GlobalPluginRegistry instance if it's missing. |
282 |
|
283 |
From there, pulls the request attribute, and passes the actual called |
284 |
args into the getattr'd func. |
285 |
|
286 |
basically, a call to |
287 |
plugins.query_plugins(blah,a,sdf) |
288 |
becomes |
289 |
proxy_it("query_plugins", blah, a, sdf) |
290 |
|
291 |
proxy_it raids the method name, getattr's the requested method (after |
292 |
instantiating if needed), then passes the remaining args/opt keywords |
293 |
to the method it retrieved. |
294 |
|
295 |
Since curry'ing is basically wrapping/decorating the func, pretty_docs |
296 |
is used to rebind __doc__ string crap onto the func, so it actually |
297 |
has a sane docstring, rather then lacking any. |
298 |
~harring |