Gentoo Archives: gentoo-portage-dev

From: Jason Stubbs <jstubbs@g.o>
To: gentoo-portage-dev@l.g.o
Subject: Re: [gentoo-portage-dev] Plugin backport PATCH (1/2)/(2/2)
Date: Fri, 18 Nov 2005 15:42:34
Message-Id: 200511190043.43749.jstubbs@gentoo.org
In Reply to: [gentoo-portage-dev] Plugin backport PATCH (1/2)/(2/2) by Alec Warner
1 On Wednesday 16 November 2005 16:16, Alec Warner wrote:
2 > Brian asked me to split this up, and the first patch had some
3 > cruft...and I broke things, both from old messing around. So I started
4 > with a clean installed of rc7, hopefully these are a bit better.
5 >
6 > One patch is for the backend stuff, portage_modules, portage_plugins,
7 > portage_file additions, portage_lock additions, portage_const additions
8 > and portage_data additions.
9 >
10 > The second patch is the addition of register_plugin.py.
11
12 Err.. That's not very split up. ;)
13
14
15 +++ ./pym/portage_file.py       2005-11-16 01:56:19.157073160 -0500
16
17 +                       # if the dir perms would lack +wx, we have to force it
18 +                       force_temp_perms = ((mode & 0300) != 0300)
19 +                       resets = []
20 +                       apath = normpath(os.path.abspath(path))
21 +                       sticky_parent = False
22 +                       creating = False
23 +                       
24 +                       for dir in apath.split(os.path.sep):
25 +                               base = os.path.join(base,dir)
26 +                               try:
27 +                                       st = os.stat(base)
28 +                                       # something exists.  why are we doing isdir?
29 +                                       if not stat.S_ISDIR(st.st_mode):
30 +                                               return False
31
32 What's with the comment here?
33
34 +                                       # if it's a subdir, we need +wx at least
35 +                                       if apath != base:
36 +                                               if ((st.st_mode & 0300) != 0300):
37 +                                                       try:                    os.chmod(base, (st.st_mode | 0300))
38 +                                                       except OSError: return False
39 +                                                       resets.append((base, st.st_mode))
40 +                                               sticky_parent = (st.st_gid & stat.S_ISGID)
41
42 <snip>
43
44 +                       try:
45 +                               for base, m in reversed(resets):
46 +                                       os.chmod(base, m)
47 +                               if uid != -1 or gid != -1:
48 +                                       os.chown(base, uid, gid)
49 +                       except OSError:
50 +                               return False
51
52 If for some reason the parent dir originally has perms 0000, this code will
53 incorrectly change its uid and gid.
54
55 +++ ./pym/portage_locks.py      2005-11-16 01:56:25.152161768 -0500
56 @@ -358,3 +359,91 @@
57  
58         return results
59  
60 +class LockException(Exception):
61 +       """Base lock exception class"""
62 +       def __init__(self, path, reason):
63 +               self.path, self.reason = path, reason
64 +               
65 +class NonExistant(LockException):
66 +       """Missing file/dir exception"""
67 +       def __init__(self, path, reason=None):
68 +               LockException.__init__(self, path, reason)
69 +       def __str__(self):
70 +               return "Lock action for '%s' failed due to not being a valid dir/file %s" % (self.path, self.reason)
71 +
72 +class GenericFailed(LockException):
73 +       """the fallback lock exception class- covers perms, IOError's, and general whackyness"""
74 +       def __str__(self):
75 +               return "Lock action for '%s' failed due to '%s'" % (self.path, self.reason)
76
77 This should really be moved to portage_exception.
78
79 +# should the fd be left open indefinitely?
80 +# IMO, it shouldn't, but opening/closing everytime around is expensive
81
82 It should be closed when locks are released, I think. Otherwise the file may
83 be deleted and recreated while one FsLock instance has it open which could
84 create situations where two FsLocks are trying to lock the same path but
85 using different fds.
86
87 +class FsLock(object):
88 +       __slots__ = ["path", "fd", "create"]
89 +       def __init__(self, path, create=False):
90 +               """path specifies the fs path for the lock
91 +               create controls whether the file will be create if the file doesn't exist
92 +               if create is true, the base dir must exist, and it will create a file
93 +               
94 +               If you want a directory yourself, create it.
95 +               """
96 +               self.path = path
97 +               self.fd = None
98 +               self.create = create
99 +               if not create:
100 +                       if not os.path.exists(path):
101 +                               raise NonExistant(path)
102 +
103 +       def _acquire_fd(self):
104 +               if self.create:
105 +                       try:    self.fd = os.open(self.path, os.R_OK|os.O_CREAT)
106 +                       except OSError, oe:
107 +                               raise GenericFailed(self.path, oe)
108 +               else:
109 +                       try:    self.fd = os.open(self.path, os.R_OK)
110 +                       except OSError, oe:     raise NonExistant(self.path, oe)
111 +       
112 +       def _enact_change(self, flags, blocking):
113 +               if self.fd == None:
114 +                       self._acquire_fd()
115 +               # we do it this way, due to the fact try/except is a bit of a hit
116
117 Instead of?
118
119 +               if not blocking:
120 +                       try:    fcntl.flock(self.fd, flags|fcntl.LOCK_NB)
121 +                       except IOError, ie:
122 +                               if ie.errno == 11:
123
124 11 ?
125
126 +                                       return False
127 +                               raise GenericFailed(self.path, ie)
128 +               else:
129 +                       fcntl.flock(self.fd, flags)
130 +               return True
131 +
132 +       def acquire_write_lock(self, blocking=True):
133 +               """Acquire an exclusive lock
134 +               Returns True if lock is acquired, False if not.
135 +               Note if you have a read lock, it implicitly upgrades atomically"""
136 +               return self._enact_change(fcntl.LOCK_EX, blocking)
137 +
138 +       def acquire_read_lock(self, blocking=True):
139 +               """Acquire a shared lock
140 +               Returns True if lock is acquired, False if not.
141 +               Note, if you have a write_lock already, it'll implicitly downgrade atomically"""
142 +               return self._enact_change(fcntl.LOCK_SH, blocking)
143 +       
144 +       def release_write_lock(self):
145 +               """Release an exclusive lock if held"""
146 +               self._enact_change(fcntl.LOCK_UN, False)
147 +               
148 +       def release_read_lock(self):
149 +               self._enact_change(fcntl.LOCK_UN, False)
150 +
151 +       def __del__(self):
152 +               # alright, it's 5:45am, yes this is weird code.
153 +               try:
154 +                       if self.fd != None:
155 +                               self.release_read_lock()
156 +               finally:
157 +                       if self.fd != None:
158 +                               os.close(self.fd)
159
160 Allowing an exception to be raised during __del__ is not a good thing...
161 Any held lock should be cleaned up too otherwise misbehaving callers could
162 deadlock.
163
164 Locking should probably be separated out from this patch though. If this
165 needs to be added, the existing locking should really be removed.
166
167 +++ ./pym/portage_modules.py    2005-11-16 01:56:28.269687832 -0500
168
169 +class FailedImport(ImportError):
170 +       def __init__(self, trg, e):     self.trg, self.e = trg, e
171 +       def __str__(self):      return "Failed importing target '%s': '%s'" % (self.trg, self.e)
172
173 As above. Should really stick with the python convention of suffixing with
174 Error or Exception as well. "from portage_modules import FailedImport" is
175 a bit unclear...
176
177 +__import_lock = threading.Lock()
178 +
179 +def load_module(name):
180 +       """load a module, throwing a FailedImport if __import__ fails"""
181 +       __import_lock.acquire()
182 +       try:
183 +               if name in sys.modules:
184 +                       return sys.modules[name]
185 +               try:
186 +                       m = __import__(name)
187 +                       nl = name.split('.')
188 +                       # __import__ returns nl[0]... so.
189 +                       nl.pop(0)
190 +                       while len(nl):
191 +                               m = getattr(m, nl[0])
192 +                               nl.pop(0)
193 +                       return m
194 +               # * ferringb|afk hit it at some point, sure of that
195 +               # but no idea how, so commenting out to see if things break...
196 +               # except AttributeError, e:
197 +               #       raise FailedImport(name, e)
198
199 I can't see how either, but comments like these don't really help. Please
200 leave them out unless they describe how the code works.
201
202 +++ ./pym/portage_plugins.py    2005-11-16 01:56:51.419168576 -0500
203 @@ -0,0 +1,174 @@
204 +# Copyright: 2005 Gentoo Foundation
205 +# License: GPL2
206 +# $Id:$
207 +
208 +# I don't like this.
209 +# doesn't seem clean/right.
210
211 ???
212
213 +PLUGINS_EXTENSION = ".plugins"
214
215 Constants should go in portage_const.
216
217 +class RegistrationException(Exception):
218 +       def __init__(self, reason):             self.reason = reason
219 +       def __str__(self):      return "failed action due to %s" % self.reason
220 +       
221 +class FailedDir(RegistrationException):
222 +       pass
223 +       
224 +class PluginExistsAlready(RegistrationException):
225 +       def __init__(self):
226 +               RegistrationException.__init__(self, "plugin exists aleady, magic found")
227 +
228 +class FailedUpdating(RegistrationException):
229 +       def __str__(self):
230 +               return "failed updating plugin_type due error: %s" % (self.reason)
231 +
232 +class PluginNotFound(RegistrationException):
233 +       def __init__(self, plugin, reason="unknown"):
234 +               self.plugin, self.reason = plugin, reason
235 +       def __str__(self):
236 +               return "Plugin '%s' wasn't found; reason: %s" % (self.plugin, self.reason)
237
238 Suffixes and portage_exception.
239
240 +               # this could be fine grained down to per plugin_type
241 +               plug_lock = FsLock(plugins_dir)
242
243 Then it probably should be. ;)
244
245 +       def query_plugins(self, plugin_type=None, locking=True, raw=False):
246
247 Not sure about allowing the external callers to disable locking... That
248 should only be used by register and deregister, right? Perhaps breaking
249 the unlocked version into a "protected" method?
250
251 +               # designed this way to minimize lock holding time.
252 +               if plugin_type != None:
253 +                       ptypes = [plugin_type + PLUGINS_EXTENSION]
254 +               d = {}
255 +               if locking:
256 +                       try:
257 +                               plug_lock = FsLock(plugins_dir)
258 +                       except NonExistant:
259 +                               return {}
260 +                       plug_lock.acquire_read_lock()
261 +               try:
262 +                       if plugin_type == None:
263 +                               ptypes = [x for x in os.listdir(plugins_dir) if x.endswith(PLUGINS_EXTENSION) ]
264
265 The plugin_type if/else could be combined as ptypes is not used before this.
266 Is [x for x in list] 2.2 safe by the way?
267
268 +                       len_exten = len(PLUGINS_EXTENSION)
269 +                       for x in ptypes:
270 +                               c = RawConfigParser()
271 +                               c.read(os.path.join(plugins_dir, x.lstrip(os.path.sep)))
272 +                               if raw:
273 +                                       d[x[:-len_exten]] = c
274 +                               else:
275 +                                       d[x[:-len_exten]] = dict([(y, dict(c.items(y))) for y in c.sections()])
276
277 Same here on the 2.2 bit... Although that's pretty hard to read either way.
278
279 +               finally:
280 +                       if locking:
281 +                               plug_lock.release_read_lock()
282 +               if plugin_type != None:
283 +                       return d[plugin_type]
284 +               return d
285
286 Not sure about having different return types...
287
288 +registry = None
289 +from portage.util.currying import pre_curry, pretty_docs
290 +
291 +def proxy_it(method, *a, **kw):
292 +       global registry
293 +       if registry == None:
294 +               registry = GlobalPluginRegistry()
295 +       return getattr(registry, method)(*a, **kw)
296 +
297 +for x in ["register", "deregister", "query_plugins", "get_plugin"]:
298 +       v = pre_curry(proxy_it, x)
299 +       doc = getattr(GlobalPluginRegistry, x).__doc__
300 +       if doc == None:
301 +               doc = ''
302 +       else:
303 +               # do this so indentation on pydoc __doc__ is sane
304 +               doc = "\n".join(map(lambda x:x.lstrip(), doc.split("\n"))) +"\n"
305 +       doc += "proxied call to module level registry instances %s method" % x
306 +       globals()[x] = pretty_docs(v, doc)
307 +
308 +del x, v, proxy_it, doc
309
310 What's this currying and pretty_docs stuff?
311
312 --
313 Jason Stubbs
314
315 --
316 gentoo-portage-dev@g.o mailing list

Replies

Subject Author
Re: [gentoo-portage-dev] Plugin backport PATCH (1/2)/(2/2) Brian Harring <ferringb@g.o>