Print this page
305 http_proxy value needs more checking for valid url syntax
2341 Client should be more conservative about closing sockets
4235 Misleading "node name" unknown messages when http_proxy set incorrectly
4495 Want ability to cancel individual file downloads
8902 Need a transport that downloads files by GET
9508 Captive portal test only run during catalog refresh
9613 Implicit refresh should raise InvalidDepotResponseException
9615 EOL clientside search v0
9629 EOL clientside support for filelist
9630 Hostile depot should live with other depot code
9631 HTTPS transport should be more rigorous in verification
9670 More specific error exceptions requested from search
9686 network operations should use accept-encoding when appropriate
9715 The info() operation should use the activity_lock
| Split |
Close |
| Expand all |
| Collapse all |
--- old/src/modules/actions/generic.py
+++ new/src/modules/actions/generic.py
1 1 #!/usr/bin/python2.4
2 2 #
3 3 # CDDL HEADER START
4 4 #
5 5 # The contents of this file are subject to the terms of the
6 6 # Common Development and Distribution License (the "License").
7 7 # You may not use this file except in compliance with the License.
8 8 #
9 9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 10 # or http://www.opensolaris.org/os/licensing.
11 11 # See the License for the specific language governing permissions
12 12 # and limitations under the License.
13 13 #
14 14 # When distributing Covered Code, include this CDDL HEADER in each
15 15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 16 # If applicable, add the following below this CDDL HEADER, with the
17 17 # fields enclosed by brackets "[]" replaced with your own identifying
18 18 # information: Portions Copyright [yyyy] [name of copyright owner]
19 19 #
20 20 # CDDL HEADER END
21 21 #
22 22
23 23 #
24 24 # Copyright 2009 Sun Microsystems, Inc. All rights reserved.
25 25 # Use is subject to license terms.
26 26 #
27 27
28 28 """module describing a generic packaging object
29 29
30 30 This module contains the Action class, which represents a generic packaging
31 31 object."""
|
↓ open down ↓ |
31 lines elided |
↑ open up ↑ |
32 32
33 33 from cStringIO import StringIO
34 34 import errno
35 35 import os
36 36 try:
37 37 # Some versions of python don't have these constants.
38 38 os.SEEK_SET
39 39 except AttributeError:
40 40 os.SEEK_SET, os.SEEK_CUR, os.SEEK_END = range(3)
41 41 import pkg.actions
42 -import pkg.client.retrieve as retrieve
43 42 import pkg.portable as portable
44 43
45 44 class Action(object):
46 45 """Class representing a generic packaging object.
47 46
48 47 An Action is a very simple wrapper around two dictionaries: a named set
49 48 of data streams and a set of attributes. Data streams generally
50 49 represent files on disk, and attributes represent metadata about those
51 50 files.
52 51 """
53 52
54 53 # 'name' is the name of the action, as specified in a manifest.
55 54 name = "generic"
56 55 # 'attributes' is a list of the known usable attributes. Or something.
57 56 # There probably isn't a good use for it.
58 57 attributes = ()
59 58 # 'key_attr' is the name of the attribute whose value must be unique in
60 59 # the namespace of objects represented by a particular action. For
61 60 # instance, a file's key_attr would be its pathname. Or a driver's
62 61 # key_attr would be the driver name. When 'key_attr' is None, it means
63 62 # that all attributes of the action are distinguishing.
64 63 key_attr = None
65 64
66 65 # the following establishes the sort order between action types.
67 66 # Directories must precede all
68 67 # filesystem-modifying actions; hardlinks must follow all
69 68 # filesystem-modifying actions. Note that usr/group actions
70 69 # preceed file actions; this implies that /etc/group and /etc/passwd
71 70 # file ownership needs to be part of initial contents of those files
72 71
73 72 orderdict = {}
74 73 unknown = 0
75 74
76 75 def loadorderdict(self):
77 76 ol = [
78 77 "set",
79 78 "depend",
80 79 "group",
81 80 "user",
82 81 "dir",
83 82 "file",
84 83 "hardlink",
85 84 "link",
86 85 "driver",
87 86 "unknown",
88 87 "legacy"
89 88 ]
90 89 self.orderdict.update(dict((
91 90 (pkg.actions.types[t], i) for i, t in enumerate(ol)
92 91 )))
93 92 self.unknown = self.orderdict[pkg.actions.types["unknown"]]
94 93
95 94 def __init__(self, data=None, **attrs):
96 95 """Action constructor.
97 96
98 97 The optional 'data' argument may be either a string, a file-like
99 98 object, or a callable. If it is a string, then it will be
100 99 substituted with a callable that will return an open handle to
101 100 the file represented by the string. Otherwise, if it is not
102 101 already a callable, it is assumed to be a file-like object, and
103 102 will be substituted with a callable that will return the object.
104 103 If it is a callable, it will not be replaced at all.
105 104
106 105 Any remaining named arguments will be treated as attributes.
107 106 """
108 107
109 108 if not self.orderdict:
110 109 self.loadorderdict()
111 110 self.ord = self.orderdict.get(type(self), self.unknown)
112 111
113 112 self.attrs = attrs
114 113
115 114 if data == None:
116 115 self.data = None
117 116 return
118 117
119 118 if isinstance(data, basestring):
120 119 if not os.path.exists(data):
121 120 raise pkg.actions.ActionDataError(
122 121 _("No such file: '%s'.") % data)
123 122 elif os.path.isdir(data):
124 123 raise pkg.actions.ActionDataError(
125 124 _("'%s' is not a file.") % data)
126 125
127 126 def file_opener():
128 127 return open(data, "rb")
129 128 self.data = file_opener
130 129 if "pkg.size" not in self.attrs:
131 130 try:
132 131 fs = os.stat(data)
133 132 self.attrs["pkg.size"] = str(fs.st_size)
134 133 except EnvironmentError, e:
135 134 raise \
136 135 pkg.actions.ActionDataError(
137 136 e)
138 137 return
139 138
140 139 if callable(data):
141 140 # Data is not None, and is callable.
142 141 self.data = data
143 142 return
144 143
145 144 if "pkg.size" in self.attrs:
146 145 self.data = lambda: data
147 146 return
148 147
149 148 try:
150 149 sz = data.size
151 150 except AttributeError:
152 151 try:
153 152 try:
154 153 sz = os.fstat(data.fileno()).st_size
155 154 except (AttributeError, TypeError):
156 155 try:
157 156 try:
158 157 data.seek(0,
159 158 os.SEEK_END)
160 159 sz = data.tell()
161 160 data.seek(0)
162 161 except (AttributeError,
163 162 TypeError):
164 163 d = data.read()
165 164 sz = len(d)
166 165 data = StringIO(d)
167 166 except (AttributeError, TypeError):
168 167 # Raw data was provided; fake a
169 168 # file object.
170 169 sz = len(data)
171 170 data = StringIO(data)
172 171 except EnvironmentError, e:
173 172 raise pkg.actions.ActionDataError(e)
174 173
175 174 self.attrs["pkg.size"] = str(sz)
176 175 self.data = lambda: data
177 176
178 177 def __str__(self):
179 178 """Serialize the action into manifest form.
180 179
181 180 The form is the name, followed by the hash, if it exists,
182 181 followed by attributes in the form 'key=value'. All fields are
183 182 space-separated; fields with spaces in the values are quoted.
184 183
185 184 Note that an object with a datastream may have been created in
186 185 such a way that the hash field is not populated, or not
187 186 populated with real data. The action classes do not guarantee
188 187 that at the time that __str__() is called, the hash is properly
189 188 computed. This may need to be done externally.
190 189 """
191 190
192 191 out = self.name
193 192 if hasattr(self, "hash"):
194 193 out += " " + self.hash
195 194
196 195 def q(s):
197 196 if " " in s:
198 197 return '"%s"' % s
199 198 else:
200 199 return s
201 200
202 201 # Sort so that we get consistent action attribute ordering.
203 202 # We pay a performance penalty to do so, but it seems worth it.
204 203 for k in sorted(self.attrs.keys()):
205 204 v = self.attrs[k]
206 205 if isinstance(v, list):
207 206 out += " " + " ".join([
208 207 "%s=%s" % (k, q(lmt)) for lmt in v
209 208 ])
210 209 elif " " in v:
211 210 out += " " + k + "=\"" + v + "\""
212 211 else:
213 212 out += " " + k + "=" + v
214 213
215 214 return out
216 215
217 216 def __cmp__(self, other):
218 217 """Compare actions for ordering. The ordinality of a
219 218 given action is computed and stored at action
220 219 initialization."""
221 220
222 221 res = cmp(self.ord, other.ord)
223 222 if res == 0:
224 223 return self.compare(other) # often subclassed
225 224
226 225 return res
227 226
228 227 def compare(self, other):
229 228 return cmp(id(self), id(other))
230 229
231 230 def different(self, other):
232 231 """Returns True if other represents a non-ignorable change from
233 232 self.
234 233
235 234 By default, this means two actions are different if any of their
236 235 attributes are different. Subclasses should override this
237 236 behavior when appropriate.
238 237 """
239 238
240 239 # We could ignore key_attr, or possibly assert that it's the
241 240 # same.
242 241 sset = set(self.attrs.keys())
243 242 oset = set(other.attrs.keys())
244 243 if sset.symmetric_difference(oset):
245 244 return True
246 245
247 246 for a in self.attrs:
248 247 if self.attrs[a] != other.attrs[a]:
249 248 return True
250 249
251 250 if hasattr(self, "hash"):
252 251 assert(hasattr(other, "hash"))
253 252 if self.hash != other.hash:
254 253 return True
255 254
256 255 return False
257 256
258 257 def differences(self, other):
259 258 """Returns a list of attributes that have different values
260 259 between other and self"""
261 260 sset = set(self.attrs.keys())
262 261 oset = set(other.attrs.keys())
263 262 l = list(sset.symmetric_difference(oset))
264 263 l.extend([ k
265 264 for k in sset.intersection(oset)
266 265 if self.attrs[k] != other.attrs[k]
267 266 ])
268 267 return (l)
269 268
270 269 def generate_indices(self):
271 270 """Generate the information needed to index this action.
272 271
273 272 This method, and the overriding methods in subclasses, produce
274 273 a list of four-tuples. The tuples are of the form
275 274 (action_name, key, token, full value). action_name is the
276 275 string representation of the kind of action generating the
277 276 tuple. 'file' and 'depend' are two examples. It is required to
278 277 not be None. Key is the string representation of the name of
279 278 the attribute being indexed. Examples include 'basename' and
280 279 'path'. Token is the token to be searched against. Full value
281 280 is the value to display to the user in the event this token
282 281 matches their query. This is useful for things like categories
283 282 where what matched the query may be a substring of what the
284 283 desired user output is.
285 284 """
286 285
287 286 if hasattr(self, "hash"):
288 287 return [
289 288 (self.name, "content", self.hash, self.hash),
290 289 ]
291 290 return []
292 291
293 292 def distinguished_name(self):
294 293 """ Return the distinguishing name for this action,
295 294 preceded by the type of the distinguishing name. For
296 295 example, for a file action, 'path' might be the
297 296 key_attr. So, the distinguished name might be
298 297 "path: usr/lib/libc.so.1".
299 298 """
300 299
301 300 if self.key_attr == None:
302 301 return str(self)
303 302 return "%s: %s" % \
304 303 (self.name, self.attrs.get(self.key_attr, "???"))
305 304
306 305 @staticmethod
307 306 def makedirs(path, **kw):
308 307 """Make directory specified by 'path' with given permissions, as
309 308 well as all missing parent directories. Permissions are
310 309 specified by the keyword arguments 'mode', 'uid', and 'gid'.
311 310
312 311 The difference between this and os.makedirs() is that the
313 312 permissions specify only those of the leaf directory. Missing
314 313 parent directories inherit the permissions of the deepest
315 314 existing directory. The leaf directory will also inherit any
316 315 permissions not explicitly set."""
317 316
318 317 # generate the components of the path. The first
319 318 # element will be empty since all absolute paths
320 319 # always start with a root specifier.
321 320 pathlist = portable.split_path(path)
322 321
323 322 # Fill in the first path with the root of the filesystem
324 323 # (this ends up being something like C:\ on windows systems,
325 324 # and "/" on unix.
326 325 pathlist[0] = portable.get_root(path)
327 326
328 327 g = enumerate(pathlist)
329 328 for i, e in g:
330 329 if not os.path.isdir(os.path.join(*pathlist[:i + 1])):
331 330 break
332 331 else:
333 332 # XXX Because the filelist codepath may create
334 333 # directories with incorrect permissions (see
335 334 # pkgtarfile.py), we need to correct those permissions
336 335 # here. Note that this solution relies on all
337 336 # intermediate directories being explicitly created by
338 337 # the packaging system; otherwise intermediate
339 338 # directories will not get their permissions corrected.
340 339 stat = os.stat(path)
341 340 mode = kw.get("mode", stat.st_mode)
342 341 uid = kw.get("uid", stat.st_uid)
343 342 gid = kw.get("gid", stat.st_gid)
344 343 try:
345 344 if mode != stat.st_mode:
346 345 os.chmod(path, mode)
347 346 if uid != stat.st_uid or gid != stat.st_gid:
348 347 portable.chown(path, uid, gid)
349 348 except OSError, e:
350 349 if e.errno != errno.EPERM and \
351 350 e.errno != errno.ENOSYS:
352 351 raise
353 352 return
354 353
355 354 stat = os.stat(os.path.join(*pathlist[:i]))
356 355 for i, e in g:
357 356 p = os.path.join(*pathlist[:i])
358 357 os.mkdir(p, stat.st_mode)
359 358 os.chmod(p, stat.st_mode)
360 359 try:
361 360 portable.chown(p, stat.st_uid, stat.st_gid)
362 361 except OSError, e:
363 362 if e.errno != errno.EPERM:
364 363 raise
365 364
366 365 # Create the leaf with any requested permissions, substituting
367 366 # missing perms with the parent's perms.
368 367 mode = kw.get("mode", stat.st_mode)
369 368 uid = kw.get("uid", stat.st_uid)
370 369 gid = kw.get("gid", stat.st_gid)
371 370 os.mkdir(path, mode)
372 371 os.chmod(path, mode)
373 372 try:
374 373 portable.chown(path, uid, gid)
375 374 except OSError, e:
376 375 if e.errno != errno.EPERM:
377 376 raise
378 377
379 378 def get_varcet_keys(self):
380 379 """Return the names of any facet or variant tags in this
381 380 action."""
382 381
383 382 variants = []
384 383 facets = []
385 384
386 385 for k in self.attrs.iterkeys():
387 386 if k.startswith("variant."):
388 387 variants.append(k)
389 388 if k.startswith("facet."):
390 389 facets.append(k)
391 390 return variants, facets
|
↓ open down ↓ |
339 lines elided |
↑ open up ↑ |
392 391
393 392 def get_remote_opener(self, img, fmri):
394 393 """Return an opener for the action's datastream which pulls from
395 394 the server. The caller may have to decompress the
396 395 datastream."""
397 396
398 397 if not hasattr(self, "hash"):
399 398 return None
400 399
401 400 def opener():
402 - return retrieve.get_datastream(img, fmri, self.hash)
401 + return img.transport.get_datastream(fmri, self.hash)
403 402
404 403 return opener
405 404
406 405 def verify(self, img, **args):
407 406 """Returns an empty list if correctly installed in the given
408 407 image."""
409 408 return []
410 409
411 410 def needsdata(self, orig):
412 411 """Returns True if the action transition requires a
413 412 datastream."""
414 413 return False
415 414
416 415 def attrlist(self, name):
417 416 """return list containing value of named attribute."""
418 417 value = self.attrs.get(name, [])
419 418 if isinstance(value, list):
420 419 return value
421 420 else:
422 421 return [ value ]
423 422
424 423 def directory_references(self):
425 424 """Returns references to paths in action."""
426 425 if "path" in self.attrs:
427 426 return [os.path.dirname(os.path.normpath(
428 427 self.attrs["path"]))]
429 428 return []
430 429
431 430 def preinstall(self, pkgplan, orig):
432 431 """Client-side method that performs pre-install actions."""
433 432 pass
434 433
435 434 def install(self, pkgplan, orig):
436 435 """Client-side method that installs the object."""
437 436 pass
438 437
439 438 def postinstall(self, pkgplan, orig):
440 439 """Client-side method that performs post-install actions."""
441 440 pass
442 441
443 442 def preremove(self, pkgplan):
444 443 """Client-side method that performs pre-remove actions."""
445 444 pass
446 445
447 446 def remove(self, pkgplan):
448 447 """Client-side method that removes the object."""
449 448 pass
450 449
451 450 def postremove(self, pkgplan):
452 451 """Client-side method that performs post-remove actions."""
453 452 pass
454 453
455 454 def include_this(self, excludes):
456 455 """Callables in excludes list returns True
457 456 if action is to be included, False if
458 457 not"""
459 458 for c in excludes:
460 459 if not c(self):
461 460 return False
462 461 return True
|
↓ open down ↓ |
50 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX