1 #!/usr/bin/python2.4
2 #
3 # CDDL HEADER START
4 #
5 # The contents of this file are subject to the terms of the
6 # Common Development and Distribution License (the "License").
7 # You may not use this file except in compliance with the License.
8 #
9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 # or http://www.opensolaris.org/os/licensing.
11 # See the License for the specific language governing permissions
12 # and limitations under the License.
13 #
14 # When distributing Covered Code, include this CDDL HEADER in each
15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 # If applicable, add the following below this CDDL HEADER, with the
17 # fields enclosed by brackets "[]" replaced with your own identifying
18 # information: Portions Copyright [yyyy] [name of copyright owner]
19 #
20 # CDDL HEADER END
21 #
22
23 # Copyright 2008 Sun Microsystems, Inc. All rights reserved.
24 # Use is subject to license terms.
25
26 import cPickle
27 import errno
28 import os
29 import socket
30 import urllib
31 import urllib2
32 import httplib
33 import shutil
34 import tempfile
35 import time
36 import datetime
37 import calendar
38
39 import OpenSSL.crypto as osc
40
41 from pkg.misc import msg, emsg
42
43 # import uuid # XXX interesting 2.5 module
44
45 import pkg.catalog as catalog
46 import pkg.updatelog as updatelog
47 import pkg.fmri
48 import pkg.manifest as manifest
49 import pkg.misc as misc
50 import pkg.Uuid25
51 import pkg.version
52 import pkg.client.history as history
53 import pkg.client.imageconfig as imageconfig
54 import pkg.client.imageplan as imageplan
55 import pkg.client.pkgplan as pkgplan
56 import pkg.client.imagestate as imagestate
57 import pkg.client.retrieve as retrieve
58 import pkg.client.progress as progress
59 import pkg.portable as portable
60 import pkg.client.query_engine as query_e
61 import pkg.client.indexer as indexer
62 import pkg.search_errors as search_errors
63 import pkg.client.api_errors as api_errors
64
65 from pkg.misc import versioned_urlopen
66 from pkg.misc import TransportException
67 from pkg.misc import TransferTimedOutException
68 from pkg.misc import TransportFailures
69 from pkg.misc import CLIENT_DEFAULT_MEM_USE_KB
70 from pkg.client import global_settings
71 from pkg.client.imagetypes import *
72
73 img_user_prefix = ".org.opensolaris,pkg"
74 img_root_prefix = "var/pkg"
75
76 PKG_STATE_INSTALLED = "installed"
77 PKG_STATE_KNOWN = "known"
78
79 # Minimum number of days to issue warning before a certificate expires
80 MIN_WARN_DAYS = datetime.timedelta(days=30)
81
82 class Image(object):
83 """An Image object is a directory tree containing the laid-down contents
84 of a self-consistent graph of Packages.
85
86 An Image has a root path.
87
88 An Image of type IMG_ENTIRE does not have a parent Image. Other Image
89 types must have a parent Image. The external state of the parent Image
90 must be accessible from the Image's context, or duplicated within the
91 Image (IMG_PARTIAL for zones, for instance).
92
93 The parent of a user Image can be a partial Image. The parent of a
94 partial Image must be an entire Image.
95
96 An Image of type IMG_USER stores its external state at self.root +
97 ".org.opensolaris,pkg".
98
99 An Image of type IMG_ENTIRE or IMG_PARTIAL stores its external state at
100 self.root + "/var/pkg".
101
102 An Image needs to be able to have a different repository set than the
103 system's root Image.
104
105 Directory layout
106
107 $IROOT/catalog
108 Directory containing catalogs for URIs of interest. Filename is
109 the escaped URI of the catalog.
110
111 $IROOT/file
112 Directory containing file hashes of installed packages.
113
114 $IROOT/pkg
115 Directory containing manifests and states of installed packages.
116
117 $IROOT/index
118 Directory containing reverse-index databases.
119
120 $IROOT/cfg_cache
121 File containing image's cached configuration.
122
123 $IROOT/opaque
124 File containing image's opaque state.
125
126 $IROOT/state/installed
127 Directory containing files whose names identify the installed
128 packages.
129
130 All of these directories and files other than state are considered
131 essential for any image to be complete. To add a new essential file or
132 subdirectory, the following steps should be done.
133
134 If it's a directory, add it to the image_subdirs list below and it will
135 be created automatically. The programmer must fill the directory as
136 needed. If a file is added, the programmer is responsible for creating
137 that file during image creation at an appropriate place and time.
138
139 If a directory is required to be present in order for an image to be
140 identifiable as such, it should go into required_subdirs instead.
141 However, upgrade issues abound; this list should probably not change.
142
143 Once those steps have been carried out, the change should be added
144 to the test suite for image corruption (t_pkg_install_corrupt_image.py).
145 This will likely also involve a change to
146 SingleDepotTestCaseCorruptImage in testutils.py. Each of these files
147 outline what must be updated.
148
149 XXX Root path probably can't be absolute, so that we can combine or
150 reuse Image contents.
151
152 XXX Image file format? Image file manipulation API?"""
153
154 required_subdirs = [ "catalog", "file", "pkg" ]
155 image_subdirs = required_subdirs + [ "index", "state/installed" ]
156
157 def __init__(self):
158 self.cfg_cache = None
159 self.type = None
160 self.root = None
161 self.history = history.History()
162 self.imgdir = None
163 self.img_prefix = None
164 self.index_dir = None
165 self.repo_uris = []
166 self.filter_tags = {}
167 self.catalogs = {}
168 self._catalog = {}
169 self.pkg_states = None
170 self.dl_cache_dir = None
171 self.dl_cache_incoming = None
172 self.is_user_cache_dir = False
173 self.state = imagestate.ImageState()
174 self.attrs = {
175 "Policy-Require-Optional": False,
176 "Policy-Pursue-Latest": True
177 }
178
179 self.imageplan = None # valid after evaluation succeeds
180
181 # contains a dictionary w/ key = pkgname, value is miminum
182 # frmi.XXX Needs rewrite using graph follower
183 self.optional_dependencies = {}
184
185 # a place to keep info about saved_files; needed by file action
186 self.saved_files = {}
187
188 # A place to keep track of which manifests (based on fmri and
189 # operation) have already provided intent information.
190 self.__touched_manifests = {}
191
192 self.__manifest_cache = {}
193
194 def _check_subdirs(self, sub_d, prefix):
195 for n in self.required_subdirs:
196 if not os.path.isdir(os.path.join(sub_d, prefix, n)):
197 return False
198 return True
199
200 def image_type(self, d):
201 """Returns the type of image at directory: d; or None"""
202 rv = None
203 if os.path.isdir(os.path.join(d, img_user_prefix)) and \
204 os.path.isfile(os.path.join(d, img_user_prefix,
205 "cfg_cache")) and \
206 self._check_subdirs(d, img_user_prefix):
207 rv = IMG_USER
208 elif os.path.isdir(os.path.join(d, img_root_prefix)) \
209 and os.path.isfile(os.path.join(d,
210 img_root_prefix, "cfg_cache")) and \
211 self._check_subdirs(d, img_root_prefix):
212 rv = IMG_ENTIRE
213 return rv
214
215 def find_root(self, d, exact_match=False):
216 # Ascend from the given directory d to find first
217 # encountered image. If exact_match is true, if the
218 # image found doesn't match startd, raise an
219 # ImageNotFoundException.
220 startd = d
221 # eliminate problem if relative path such as "." is passed in
222 d = os.path.realpath(d)
223 while True:
224 imgtype = self.image_type(d)
225 if imgtype == IMG_USER:
226 # XXX Look at image file to determine filter
227 # tags and repo URIs.
228 if exact_match and \
229 os.path.realpath(startd) != \
230 os.path.realpath(d):
231 raise api_errors.ImageNotFoundException(
232 exact_match, startd, d)
233 self.__set_dirs(imgtype=imgtype, root=d)
234 self.attrs["Build-Release"] = "5.11"
235 return
236 elif imgtype == IMG_ENTIRE:
237 # XXX Look at image file to determine filter
238 # tags and repo URIs.
239 # XXX Look at image file to determine if this
240 # image is a partial image.
241 if exact_match and \
242 os.path.realpath(startd) != \
243 os.path.realpath(d):
244 raise api_errors.ImageNotFoundException(
245 exact_match, startd, d)
246 self.__set_dirs(imgtype=imgtype, root=d)
247 self.attrs["Build-Release"] = "5.11"
248 return
249
250 # XXX follow symlinks or not?
251 oldpath = d
252 d = os.path.normpath(os.path.join(d, os.path.pardir))
253
254 # Make sure we are making progress and aren't in an
255 # infinite loop.
256 #
257 # (XXX - Need to deal with symlinks here too)
258 if d == oldpath:
259 raise api_errors.ImageNotFoundException(
260 exact_match, startd, d)
261
262 def load_config(self):
263 """Load this image's cached configuration from the default
264 location."""
265
266 # XXX Incomplete with respect to doc/image.txt description of
267 # configuration.
268
269 if self.root == None:
270 raise RuntimeError, "self.root must be set"
271
272 ic = imageconfig.ImageConfig()
273
274 if os.path.isfile("%s/cfg_cache" % self.imgdir):
275 ic.read("%s/cfg_cache" % self.imgdir)
276
277 self.cfg_cache = ic
278
279 def save_config(self):
280 self.cfg_cache.write("%s/cfg_cache" % self.imgdir)
281
282 # XXX mkdirs and set_attrs() need to be combined into a create
283 # operation.
284 def mkdirs(self):
285 for sd in self.image_subdirs:
286 if not os.path.isdir(os.path.join(self.imgdir, sd)):
287 os.makedirs(os.path.join(self.imgdir, sd))
288
289 def __set_dirs(self, imgtype, root):
290 self.type = imgtype
291 self.root = root
292 if self.type == IMG_USER:
293 self.img_prefix = img_user_prefix
294 else:
295 self.img_prefix = img_root_prefix
296 self.imgdir = os.path.join(self.root, self.img_prefix)
297 self.history.root_dir = self.imgdir
298
299 if "PKG_CACHEDIR" in os.environ:
300 self.dl_cache_dir = os.path.normpath( \
301 os.environ["PKG_CACHEDIR"])
302 self.is_user_cache_dir = True
303 else:
304 self.dl_cache_dir = os.path.normpath( \
305 os.path.join(self.imgdir, "download"))
306 self.dl_cache_incoming = os.path.normpath(os.path.join(
307 self.dl_cache_dir, "incoming-%d" % os.getpid()))
308
309 def set_attrs(self, type, root, is_zone, auth_name, auth_url,
310 ssl_key = None, ssl_cert = None):
311 self.__set_dirs(imgtype=type, root=root)
312
313 if not os.path.exists(os.path.join(self.imgdir, "cfg_cache")):
314 self.history.operation_name = "image-create"
315 else:
316 self.history.operation_name = "image-set-attributes"
317
318 self.mkdirs()
319
320 self.cfg_cache = imageconfig.ImageConfig()
321
322 if is_zone:
323 self.cfg_cache.filters["opensolaris.zone"] = "nonglobal"
324
325 self.cfg_cache.authorities[auth_name] = {}
326 self.cfg_cache.authorities[auth_name]["prefix"] = auth_name
327 self.cfg_cache.authorities[auth_name]["origin"] = \
328 misc.url_affix_trailing_slash(auth_url)
329 self.cfg_cache.authorities[auth_name]["mirrors"] = []
330 self.cfg_cache.authorities[auth_name]["ssl_key"] = ssl_key
331 self.cfg_cache.authorities[auth_name]["ssl_cert"] = ssl_cert
332 self.cfg_cache.authorities[auth_name]["uuid"] = pkg.Uuid25.uuid1()
333
334 self.cfg_cache.preferred_authority = auth_name
335
336 self.save_config()
337 self.history.operation_result = history.RESULT_SUCCEEDED
338
339 def is_liveroot(self):
340 return self.root == "/"
341
342 def is_zone(self):
343 zone = self.cfg_cache.filters.get("opensolaris.zone", "")
344 return zone == "nonglobal"
345
346 def get_root(self):
347 return self.root
348
349 def gen_authorities(self):
350 if not self.cfg_cache:
351 raise RuntimeError, "empty ImageConfig"
352 if not self.cfg_cache.authorities:
353 raise RuntimeError, "no defined authorities"
354 for a in self.cfg_cache.authorities:
355 yield self.cfg_cache.authorities[a]
356
357 def get_url_by_authority(self, authority = None):
358 """Return the URL prefix associated with the given authority.
359 For the undefined case, represented by None, return the
360 preferred authority."""
361
362 # XXX This function is a possible location to insert one or more
363 # policies regarding use of mirror responses, etc.
364
365 if authority == None:
366 authority = self.cfg_cache.preferred_authority
367
368 try:
369 o = self.cfg_cache.authorities[authority]["origin"]
370 except KeyError:
371 # If the authority that we're trying to get no longer
372 # exists, fall back to preferred authority.
373 authority = self.cfg_cache.preferred_authority
374 o = self.cfg_cache.authorities[authority]["origin"]
375
376 return o.rstrip("/")
377
378 def select_mirror(self, auth = None, chosen_set = None):
379 """For the given authority, look through the status of
380 the mirrors. Pick the best one. This method returns
381 a DepotStatus object or None. The chosen_set argument
382 contains a set object that lists the mirrors that were
383 previously chosen. This allows us to choose both
384 by depot status statistics and ensures we don't
385 always pick the same depot."""
386
387 if auth == None:
388 auth = self.cfg_cache.preferred_authority
389 try:
390 slst = self.cfg_cache.mirror_status[auth]
391 except KeyError:
392 # If the authority that we're trying to get no longer
393 # exists, fall back to preferred authority.
394 auth = self.cfg_cache.preferred_authority
395 slst = self.cfg_cache.mirror_status[auth]
396
397 if len(slst) == 0:
398 if auth in self.cfg_cache.authority_status:
399 return self.cfg_cache.authority_status[auth]
400 else:
401 return None
402
403 # Choose mirror with fewest errors.
404 # If mirrors have same number of errors, choose mirror
405 # with smaller number of good transactions. Assume it's
406 # being underused, not high-latency.
407 #
408 # XXX Will need to revisit the above assumption.
409 def cmp_depotstatus(a, b):
410 res = cmp(a.errors, b.errors)
411 if res == 0:
412 return cmp(a.good_tx, b.good_tx)
413 return res
414
415 slst.sort(cmp = cmp_depotstatus)
416
417 if chosen_set and len(chosen_set) == len(slst):
418 chosen_set.clear()
419 chosen_set = None
420
421 if chosen_set and slst[0] in chosen_set:
422 for ds in slst:
423 if ds not in chosen_set:
424 return ds
425
426 return slst[0]
427
428 def get_ssl_credentials(self, authority = None, origin = None):
429 """Return a tuple containing (ssl_key, ssl_cert) for the
430 specified authority prefix. If the authority isn't specified,
431 attempt to determine the authority by the given origin. If
432 neither is specified, use the preferred authority.
433 """
434
435 if authority is None:
436 if origin is None:
437 authority = self.cfg_cache.preferred_authority
438 else:
439 auths = self.cfg_cache.authorities
440 for pfx, auth in auths.iteritems():
441 if auth["origin"] == origin:
442 authority = pfx
443 break
444 else:
445 return None
446
447 try:
448 authent = self.cfg_cache.authorities[authority]
449 except KeyError:
450 authority = self.cfg_cache.preferred_authority
451 authent = self.cfg_cache.authorities[authority]
452
453 return (authent["ssl_key"], authent["ssl_cert"])
454
455 @staticmethod
456 def build_cert(path):
457 """Take the file given in path, open it, and use it to create
458 an X509 certificate object."""
459
460 cf = file(path, "rb")
461 certdata = cf.read()
462 cf.close()
463 cert = osc.load_certificate(osc.FILETYPE_PEM, certdata)
464
465 return cert
466
467 def check_cert_validity(self):
468 """Look through the authorities defined for the image. Print
469 a message and exit with an error if one of the certificates
470 has expired. If certificates are getting close to expiration,
471 print a warning instead."""
472
473 for a in self.gen_authorities():
474 pfx, url, ssl_key, ssl_cert, dt, mir = \
475 self.split_authority(a)
476
477 if not ssl_cert:
478 continue
479
480 try:
481 cert = self.build_cert(ssl_cert)
482 except IOError, e:
483 if e.errno == errno.ENOENT:
484 emsg(_("Certificate for authority %s" \
485 " not found") % pfx)
486 emsg(_("File was supposed to exist at" \
487 " path %s") % ssl_cert)
488 return False
489 else:
490 raise
491 # OpenSSL.crypto.Error
492 except osc.Error, e:
493 emsg(_("Certificate for authority %s at" \
494 " %s has an invalid format.") % \
495 (pfx, ssl_cert))
496 return False
497
498 if cert.has_expired():
499 emsg(_("Certificate for authority %s" \
500 " has expired") % pfx)
501 emsg(_("Please install a valid certificate"))
502 return False
503
504 now = datetime.datetime.utcnow()
505 nb = cert.get_notBefore()
506 t = time.strptime(nb, "%Y%m%d%H%M%SZ")
507 nbdt = datetime.datetime.utcfromtimestamp(
508 calendar.timegm(t))
509
510 # PyOpenSSL's has_expired() doesn't validate the notBefore
511 # time on the certificate. Don't ask me why.
512
513 if nbdt > now:
514 emsg(_("Certificate for authority %s is" \
515 " invalid") % pfx)
516 emsg(_("Certificate effective date is in" \
517 " the future"))
518 return False
519
520 na = cert.get_notAfter()
521 t = time.strptime(na, "%Y%m%d%H%M%SZ")
522 nadt = datetime.datetime.utcfromtimestamp(
523 calendar.timegm(t))
524
525 diff = nadt - now
526
527 if diff <= MIN_WARN_DAYS:
528 emsg(_("Certificate for authority %s will" \
529 " expire in %d days" % (pfx, diff.days)))
530
531 return True
532
533 def get_uuid(self, authority):
534 """Return the UUID for the specified authority prefix. If the
535 policy for sending the UUID is set to false, return None.
536 """
537 if not self.cfg_cache.get_policy(imageconfig.SEND_UUID):
538 return None
539
540 try:
541 return self.cfg_cache.authorities[authority]["uuid"]
542 except KeyError:
543 return None
544
545 def get_default_authority(self):
546 return self.cfg_cache.preferred_authority
547
548 def has_authority(self, auth_name):
549 return auth_name in self.cfg_cache.authorities
550
551 def delete_authority(self, auth_name):
552 self.history.operation_name = "delete-authority"
553 if not self.has_authority(auth_name):
554 error = "no such authority '%s'" % auth_name
555 self.history.operation_errors.append(error)
556 self.history.operation_result = \
557 history.RESULT_FAILED_UNKNOWN
558 raise KeyError, error
559 self.cfg_cache.delete_authority(auth_name)
560 self.save_config()
561 self.destroy_catalog(auth_name)
562 self.cache_catalogs()
563 self.history.operation_result = history.RESULT_SUCCEEDED
564
565 def get_authority(self, auth_name):
566 if not self.has_authority(auth_name):
567 raise KeyError, "no such authority '%s'" % auth_name
568
569 return self.cfg_cache.authorities[auth_name]
570
571 def split_authority(self, auth):
572 prefix = auth["prefix"]
573 update_dt = None
574
575 try:
576 cat = self.catalogs[prefix]
577 except KeyError:
578 cat = None
579
580 if cat:
581 update_dt = cat.last_modified()
582 if update_dt:
583 update_dt = catalog.ts_to_datetime(update_dt)
584
585 return (prefix, auth["origin"], auth["ssl_key"],
586 auth["ssl_cert"], update_dt, auth["mirrors"])
587
588 def set_preferred_authority(self, auth_name):
589 self.history.operation_name = "set-preferred-authority"
590 if not self.has_authority(auth_name):
591 error = "no such authority '%s'" % auth_name
592 self.history.operation_errors.append(error)
593 self.history.operation_result = \
594 history.RESULT_FAILED_UNKNOWN
595 raise KeyError, error
596 self.cfg_cache.preferred_authority = auth_name
597 self.save_config()
598 self.history.operation_result = history.RESULT_SUCCEEDED
599
600 def set_authority(self, auth_name, origin_url = None, ssl_key = None,
601 ssl_cert = None, refresh_allowed = True, uuid = None):
602 self.history.operation_name = "set-authority"
603 auths = self.cfg_cache.authorities
604
605 refresh_needed = False
606
607 if auth_name in auths:
608 # If authority already exists, only update non-NULL
609 # values passed to set_authority
610 if origin_url:
611 auths[auth_name]["origin"] = \
612 misc.url_affix_trailing_slash(origin_url)
613 refresh_needed = True
614 if ssl_key:
615 auths[auth_name]["ssl_key"] = ssl_key
616 if ssl_cert:
617 auths[auth_name]["ssl_cert"] = ssl_cert
618 if uuid:
619 auths[auth_name]["uuid"] = uuid
620
621 else:
622 auths[auth_name] = {}
623 auths[auth_name]["prefix"] = auth_name
624 auths[auth_name]["origin"] = \
625 misc.url_affix_trailing_slash(origin_url)
626 auths[auth_name]["mirrors"] = []
627 auths[auth_name]["ssl_key"] = ssl_key
628 auths[auth_name]["ssl_cert"] = ssl_cert
629 if not uuid:
630 uuid = pkg.Uuid25.uuid1()
631 auths[auth_name]["uuid"] = uuid
632 refresh_needed = True
633
634 self.save_config()
635
636 if refresh_needed and refresh_allowed:
637 self.destroy_catalog(auth_name)
638 self.destroy_catalog_cache()
639 self.retrieve_catalogs(full_refresh=True,
640 auths=[auths[auth_name]])
641
642 self.history.operation_result = history.RESULT_SUCCEEDED
643
644 def set_property(self, prop_name, prop_value):
645 self.cfg_cache.properties[prop_name] = prop_value
646 self.save_config()
647
648 def get_property(self, prop_name):
649 return self.cfg_cache.properties[prop_name]
650
651 def has_property(self, prop_name):
652 return prop_name in self.cfg_cache.properties
653
654 def delete_property(self, prop_name):
655 del self.cfg_cache.properties[prop_name]
656 self.save_config()
657
658 def properties(self):
659 for p in self.cfg_cache.properties:
660 yield p
661
662 def add_mirror(self, auth_name, mirror):
663 """Add the mirror URL contained in mirror to
664 auth_name's list of mirrors."""
665 self.history.operation_name = "add-mirror"
666 auths = self.cfg_cache.authorities
667 auths[auth_name]["mirrors"].append(mirror)
668 self.save_config()
669 self.history.operation_result = history.RESULT_SUCCEEDED
670
671 def has_mirror(self, auth_name, url):
672 """Returns true if url is in auth_name's list of mirrors."""
673
674 return url in self.cfg_cache.authorities[auth_name]["mirrors"]
675
676 def del_mirror(self, auth_name, mirror):
677 """Remove the mirror URL contained in mirror from
678 auth_name's list of mirrors."""
679
680 self.history.operation_name = "delete-mirror"
681 auths = self.cfg_cache.authorities
682
683 if mirror in self.cfg_cache.authorities[auth_name]["mirrors"]:
684 auths[auth_name]["mirrors"].remove(mirror)
685 self.save_config()
686 self.history.operation_result = history.RESULT_SUCCEEDED
687
688 def verify(self, fmri, progresstracker, **args):
689 """generator that returns any errors in installed pkgs
690 as tuple of action, list of errors"""
691
692 for act in self.get_manifest(fmri, filtered = True).actions:
693 errors = act.verify(self, pkg_fmri=fmri, **args)
694 progresstracker.verify_add_progress(fmri)
695 actname = act.distinguished_name()
696 if errors:
697 progresstracker.verify_yield_error(actname,
698 errors)
699 yield (act, errors)
700
701 def repair(self, repairs, progtrack):
702 """Repair any actions in the fmri that failed a verify."""
703 pps = []
704 for fmri, actions in repairs:
705 msg("Repairing: %-50s" % fmri.get_pkg_stem())
706 m = self.get_manifest(fmri, filtered=True)
707 pp = pkgplan.PkgPlan(self, progtrack, False)
708
709 pp.propose_repair(fmri, m, actions)
710 pp.evaluate()
711 pps.append(pp)
712
713 ip = imageplan.ImagePlan(self, progtrack, False)
714 ip.pkg_plans = pps
715 try:
716 ip.evaluate()
717 ip.preexecute()
718 ip.execute()
719 except KeyboardInterrupt:
720 raise
721 except:
722 return False
723
724 return True
725
726 def get_fmri_manifest_pairs(self):
727 """For each installed fmri, finds the path to its manifest file
728 and adds the pair of the fmri and the path to a list. Once all
729 installed fmris have been processed, the list is returned."""
730 return [
731 (fmri, self.get_manifest_path(fmri))
732 for fmri in self.gen_installed_pkgs()
733 ]
734
735 def has_manifest(self, fmri):
736 mpath = fmri.get_dir_path()
737
738 local_mpath = "%s/pkg/%s/manifest" % (self.imgdir, mpath)
739
740 if (os.path.exists(local_mpath)):
741 return True
742
743 return False
744
745 def __fetch_manifest_with_retries(self, fmri):
746 """Wrapper function around __fetch_manifest to handle some
747 exceptions and keep track of additional state."""
748
749 m = None
750 retry_count = global_settings.PKG_TIMEOUT_MAX
751 failures = TransportFailures()
752
753 while not m:
754 try:
755 m = self.__fetch_manifest(fmri)
756 except TransportException, e:
757 retry_count -= 1
758 failures.append(e)
759
760 if retry_count <= 0:
761 raise failures
762
763 return m
764
765 def __get_touched_manifest(self, fmri):
766 """Returns whether intent information has been provided for the
767 given fmri."""
768
769 op = self.history.operation_name
770 if not op:
771 # The client may not have provided the name of the
772 # operation it is performing.
773 op = "unknown"
774
775 if op not in self.__touched_manifests:
776 # No intent information has been provided for fmris
777 # for the current operation.
778 return False
779
780 f = str(fmri)
781 if f not in self.__touched_manifests[op]:
782 # No intent information has been provided for this
783 # fmri for the current operation.
784 return False
785
786 return True
787
788 def __set_touched_manifest(self, fmri):
789 """Records that intent information has been provided for the
790 given fmri's manifest."""
791
792 op = self.history.operation_name
793 if not op:
794 # The client may not have provided the name of the
795 # operation it is performing.
796 op = "unknown"
797
798 if op not in self.__touched_manifests:
799 # No intent information has yet been provided for fmris
800 # for the current operation.
801 self.__touched_manifests[op] = {}
802
803 f = str(fmri)
804 if f not in self.__touched_manifests[op]:
805 # No intent information has yet been provided for this
806 # fmri for the current operation.
807 self.__touched_manifests[op][f] = None
808
809 def __touch_manifest(self, fmri):
810 """Perform steps necessary to 'touch' a manifest to provide
811 intent information. Ignores most exceptions as this operation
812 is only for informational purposes."""
813
814 if not self.__get_touched_manifest(fmri):
815 # If the manifest for this fmri hasn't been "seen"
816 # before, determine if intent information needs to be
817 # provided.
818
819 # What is the client currently processing?
820 target, intent = self.state.get_target()
821
822 if target and intent != imagestate.INTENT_EVALUATE:
823 # If the client is currently performing an
824 # image-modifying operation, not just an
825 # an evaluation, then perform further checks.
826
827 # Ignore the authority for comparison.
828 na_target = target.get_fmri(anarchy=True)
829 na_fmri = target.get_fmri(anarchy=True)
830
831 if na_target == na_fmri:
832 # If the client is currently processing
833 # the given fmri (for an install, etc.)
834 # then intent information is needed.
835 retrieve.touch_manifest(self, fmri)
836 self.__set_touched_manifest(fmri)
837
838 def __fetch_manifest(self, fmri):
839 """Perform steps necessary to get manifest from remote host
840 and write resulting contents to disk. Helper routine for
841 get_manifest. Does not filter the results, caller must do
842 that. """
843
844 m = manifest.Manifest()
845 m.set_fmri(self, fmri)
846
847 fmri_dir_path = os.path.join(self.imgdir, "pkg",
848 fmri.get_dir_path())
849 mpath = os.path.join(fmri_dir_path, "manifest")
850
851 # Get manifest as a string from the remote host, then build
852 # it up into an in-memory manifest, then write the finished
853 # representation to disk. Note that this may throw a
854 # TransportException of some sort; we let upper layers
855 # handle that.
856 mcontent = retrieve.get_manifest(self, fmri)
857 m.set_content(mcontent)
858
859 # Write the originating authority into the manifest.
860 # Manifests prior to this change won't contain this information.
861 # In that case, the client attempts to re-download the manifest
862 # from the depot.
863 if not fmri.has_authority():
864 m["authority"] = self.get_default_authority()
865 else:
866 m["authority"] = fmri.get_authority()
867
868 try:
869 m.store(mpath)
870 except EnvironmentError, e:
871 if e.errno not in (errno.EROFS, errno.EACCES):
872 raise
873
874 self.__set_touched_manifest(fmri)
875
876 return m
877
878 def _valid_manifest(self, fmri, manifest):
879 """Check authority attached to manifest. Make sure
880 it matches authority specified in FMRI."""
881
882 authority = fmri.get_authority()
883 if not authority:
884 authority = self.get_default_authority()
885
886 if not "authority" in manifest:
887 return False
888
889 if manifest["authority"] != authority:
890 return False
891
892 return True
893
894 def get_manifest_path(self, fmri):
895 """Find on-disk manifest and create in-memory Manifest
896 object, applying appropriate filters as needed."""
897 mpath = os.path.join(self.imgdir, "pkg",
898 fmri.get_dir_path(), "manifest")
899 return mpath
900
901 def __get_manifest(self, fmri):
902 """Find on-disk manifest and create in-memory Manifest
903 object."""
904
905 m = None
906 mpath = os.path.join(self.imgdir, "pkg", fmri.get_dir_path(),
907 "manifest")
908 if os.path.exists(mpath):
909 # If the manifest already exists, load it from storage.
910 m = manifest.Manifest()
911 mcontent = file(mpath).read()
912 m.set_fmri(self, fmri)
913 m.set_content(mcontent)
914
915 try:
916 # If the manifest didn't already exist, or isn't from
917 # the correct authority, or no authority is attached
918 # to the manifest, attempt to download a new one.
919 if not m or not self._valid_manifest(fmri, m):
920 m = self.__fetch_manifest_with_retries(fmri)
921 except (retrieve.ManifestRetrievalError,
922 retrieve.DatastreamRetrievalError):
923 # In this case, the client has failed to download a new
924 # manifest or re-download an existing one with the same
925 # name.
926 if not m:
927 # Since an older copy doesn't exist, give up.
928 raise
929
930 # Since the old manifest exists, keep it, and drive on.
931
932 return m
933
934 def get_manifest(self, fmri, filtered = False, use_cache = True):
935 """Find on-disk manifest and create in-memory Manifest
936 object, applying appropriate filters as needed."""
937
938 if use_cache and fmri in self.__manifest_cache:
939 m = self.__manifest_cache[fmri]
940 else:
941 m = self.__get_manifest(fmri)
942 if use_cache:
943 self.__manifest_cache[fmri] = m
944
945 self.__touch_manifest(fmri)
946
947 # XXX perhaps all of the below should live in Manifest.filter()?
948 if filtered:
949 fmri_dir_path = os.path.join(self.imgdir, "pkg",
950 fmri.get_dir_path())
951
952 filters = []
953 try:
954 f = file("%s/filters" % fmri_dir_path, "r")
955 except IOError, e:
956 if e.errno != errno.ENOENT:
957 raise
958 else:
959 filters = [
960 (l.strip(), compile(
961 l.strip(), "<filter string>", "eval"))
962 for l in f.readlines()
963 ]
964 m.filter(filters)
965
966 return m
967
968 def installed_file_authority(self, filepath):
969 """Find the pkg's installed file named by filepath.
970 Return the authority that installed this package."""
971
972 read_only = False
973
974 try:
975 f = file(filepath, "r+")
976 except IOError, e:
977 if e.errno == errno.EACCES or e.errno == errno.EROFS:
978 read_only = True
979 else:
980 raise
981 if read_only:
982 f = file(filepath, "r")
983
984 flines = f.readlines()
985 newauth = None
986
987 try:
988 version, auth = flines
989 except ValueError:
990 # If we get a ValueError, we've encoutered an
991 # installed file of a previous format. If we want
992 # upgrade to work in this situation, it's necessary
993 # to assume that the package was installed from
994 # the preferred authority. Here, we set up
995 # the authority to record that.
996 if flines:
997 auth = flines[0]
998 auth = auth.strip()
999 newauth = "%s_%s" % (pkg.fmri.PREF_AUTH_PFX,
1000 auth)
1001 else:
1002 newauth = "%s_%s" % (pkg.fmri.PREF_AUTH_PFX,
1003 self.get_default_authority())
1004
1005 # Exception handler is only part of this code that
1006 # sets newauth
1007 auth = newauth
1008
1009 if newauth and not read_only:
1010 # This is where we actually update the installed
1011 # file with the new authority.
1012 f.seek(0)
1013 f.writelines(["VERSION_1\n", newauth])
1014
1015 f.close()
1016
1017 assert auth
1018
1019 return auth
1020
1021 def _install_file(self, fmri):
1022 """Returns the path to the "installed" file for a given fmri."""
1023 return "%s/pkg/%s/installed" % (self.imgdir, fmri.get_dir_path())
1024
1025 def install_file_present(self, fmri):
1026 """Returns true if the package named by the fmri is installed
1027 on the system. Otherwise, returns false."""
1028
1029 return os.path.exists(self._install_file(fmri))
1030
1031 def add_install_file(self, fmri):
1032 """Take an image and fmri. Write a file to disk that
1033 indicates that the package named by the fmri has been
1034 installed."""
1035
1036 # XXX This can be removed at some point in the future once we
1037 # think this link is available on all systems
1038 if not os.path.isdir("%s/state/installed" % self.imgdir):
1039 self.update_installed_pkgs()
1040
1041 f = file(self._install_file(fmri), "w")
1042
1043 f.writelines(["VERSION_1\n", fmri.get_authority_str()])
1044 f.close()
1045
1046 fi = file("%s/state/installed/%s" % (self.imgdir,
1047 fmri.get_link_path()), "w")
1048 fi.close()
1049 self.pkg_states[urllib.unquote(fmri.get_link_path())] = \
1050 (PKG_STATE_INSTALLED, fmri)
1051
1052 def remove_install_file(self, fmri):
1053 """Take an image and a fmri. Remove the file from disk
1054 that indicates that the package named by the fmri has been
1055 installed."""
1056
1057 # XXX This can be removed at some point in the future once we
1058 # think this link is available on all systems
1059 if not os.path.isdir("%s/state/installed" % self.imgdir):
1060 self.update_installed_pkgs()
1061
1062 os.unlink(self._install_file(fmri))
1063 try:
1064 os.unlink("%s/state/installed/%s" % (self.imgdir,
1065 fmri.get_link_path()))
1066 except EnvironmentError, e:
1067 if e.errno != errno.ENOENT:
1068 raise
1069 self.pkg_states[urllib.unquote(fmri.get_link_path())] = \
1070 (PKG_STATE_KNOWN, fmri)
1071
1072 def update_installed_pkgs(self):
1073 """Take the image's record of installed packages from the
1074 prototype layout, with an installed file in each
1075 $META/pkg/stem/version directory, to the $META/state/installed
1076 summary directory form."""
1077
1078 tmpdir = "%s/state/installed.build" % self.imgdir
1079
1080 # Create the link forest in a temporary directory. We should
1081 # only execute this method once (if ever) in the lifetime of an
1082 # image, but if the path already exists and makedirs() blows up,
1083 # just be quiet if it's already a directory. If it's not a
1084 # directory or something else weird happens, re-raise.
1085 try:
1086 os.makedirs(tmpdir)
1087 except OSError, e:
1088 if e.errno != errno.EEXIST or \
1089 not os.path.isdir(tmpdir):
1090 raise
1091 return
1092
1093 proot = "%s/pkg" % self.imgdir
1094
1095 for pd, vd in (
1096 (p, v)
1097 for p in sorted(os.listdir(proot))
1098 for v in sorted(os.listdir("%s/%s" % (proot, p)))
1099 ):
1100 path = "%s/%s/%s/installed" % (proot, pd, vd)
1101 if not os.path.exists(path):
1102 continue
1103
1104 fmristr = urllib.unquote("%s@%s" % (pd, vd))
1105 auth = self.installed_file_authority(path)
1106 f = pkg.fmri.PkgFmri(fmristr, authority = auth)
1107 fi = file("%s/%s" % (tmpdir, f.get_link_path()), "w")
1108 fi.close()
1109
1110 # Someone may have already created this directory. Junk the
1111 # directory we just populated if that's the case.
1112 try:
1113 portable.rename(tmpdir, "%s/state/installed" % self.imgdir)
1114 except EnvironmentError, e:
1115 if e.errno != errno.EEXIST:
1116 raise
1117 shutil.rmtree(tmpdir)
1118
1119 def get_version_installed(self, pfmri):
1120 """Returns an fmri of the installed package matching the
1121 package stem of the given fmri or None if no match is found."""
1122 for f in self.gen_installed_pkgs():
1123 if self.fmri_is_same_pkg(f, pfmri):
1124 return f
1125 return None
1126
1127 def get_pkg_state_by_fmri(self, pfmri):
1128 """Given pfmri, determine the local state of the package."""
1129
1130 return self.pkg_states.get(pfmri.get_fmri(anarchy = True)[5:],
1131 (PKG_STATE_KNOWN, None))[0]
1132
1133 def get_pkg_auth_by_fmri(self, pfmri):
1134 """Return the authority from which 'pfmri' was installed."""
1135
1136 f = self.pkg_states.get(pfmri.get_fmri(anarchy = True)[5:],
1137 (PKG_STATE_KNOWN, None))[1]
1138 if f:
1139 # Return the non-preferred-prefixed name
1140 return f.get_authority()
1141 return None
1142
1143 def fmri_set_default_authority(self, fmri):
1144 """If the FMRI supplied as an argument does not have
1145 an authority, set it to the image's preferred authority."""
1146
1147 if fmri.has_authority():
1148 return
1149
1150 fmri.set_authority(self.get_default_authority(), True)
1151
1152 def get_catalog(self, fmri, exception = False):
1153 """Given a FMRI, look at the authority and return the
1154 correct catalog for this image."""
1155
1156 # If FMRI has no authority, or is default authority,
1157 # then return the catalog for the preferred authority
1158 if not fmri.has_authority() or fmri.preferred_authority():
1159 cat = self.catalogs[self.get_default_authority()]
1160 else:
1161 try:
1162 cat = self.catalogs[fmri.get_authority()]
1163 except KeyError:
1164 # If the authority that installed this package
1165 # has vanished, pick the default authority
1166 # instead.
1167 if exception:
1168 raise
1169 else:
1170 cat = self.catalogs[\
1171 self.get_default_authority()]
1172
1173 return cat
1174
1175 def has_version_installed(self, fmri):
1176 """Check that the version given in the FMRI or a successor is
1177 installed in the current image."""
1178
1179 v = self.get_version_installed(fmri)
1180
1181 if v and not fmri.has_authority():
1182 fmri.set_authority(v.get_authority_str())
1183 elif not fmri.has_authority():
1184 fmri.set_authority(self.get_default_authority(), True)
1185
1186 if v and self.fmri_is_successor(v, fmri):
1187 return True
1188 else:
1189 try:
1190 cat = self.get_catalog(fmri, exception = True)
1191 except KeyError:
1192 return False
1193
1194 # If fmri has been renamed, get the list of newer
1195 # packages that are equivalent to fmri.
1196 rpkgs = cat.rename_newer_pkgs(fmri)
1197 for f in rpkgs:
1198
1199 v = self.get_version_installed(f)
1200
1201 if v and self.fmri_is_successor(v, fmri):
1202 return True
1203
1204 return False
1205
1206 def older_version_installed(self, fmri):
1207 """This method is used by the package plan to determine if an
1208 older version of the package is installed. This takes
1209 the destination fmri and checks if an older package exists.
1210 This looks first under the existing name, and then sees
1211 if an older version is installed under another name. This
1212 allows upgrade correctly locate the src fmri, if one exists."""
1213
1214 v = self.get_version_installed(fmri)
1215
1216 assert fmri.has_authority()
1217
1218 if v:
1219 return v
1220 else:
1221 cat = self.get_catalog(fmri)
1222
1223 rpkgs = cat.rename_older_pkgs(fmri)
1224 for f in rpkgs:
1225 v = self.get_version_installed(f)
1226 if v and self.fmri_is_successor(fmri, v):
1227 return v
1228
1229 return None
1230
1231 def is_installed(self, fmri):
1232 """Check that the exact version given in the FMRI is installed
1233 in the current image."""
1234
1235 # All FMRIs passed to is_installed shall have an authority
1236 assert fmri.has_authority()
1237
1238 v = self.get_version_installed(fmri)
1239 if not v:
1240 return False
1241
1242 return v == fmri
1243
1244 def __build_dependents(self, progtrack):
1245 """Build a dictionary mapping packages to the list of packages
1246 that have required dependencies on them."""
1247 self.__req_dependents = {}
1248
1249 for fmri in self.gen_installed_pkgs():
1250 progtrack.evaluate_progress(fmri)
1251 mfst = self.get_manifest(fmri, filtered = True)
1252
1253 for dep in mfst.gen_actions_by_type("depend"):
1254 if dep.attrs["type"] != "require":
1255 continue
1256 dfmri = self.strtofmri(dep.attrs["fmri"])
1257 if dfmri not in self.__req_dependents:
1258 self.__req_dependents[dfmri] = []
1259 self.__req_dependents[dfmri].append(fmri)
1260
1261 def get_dependents(self, pfmri, progtrack):
1262 """Return a list of the packages directly dependent on the given
1263 FMRI."""
1264
1265 if not hasattr(self, "_Image__req_dependents"):
1266 self.__build_dependents(progtrack)
1267
1268 dependents = []
1269 # We run through all the keys, in case a package is depended
1270 # upon under multiple versions. That is, if pkgA depends on
1271 # libc@1 and pkgB depends on libc@2, we need to return both pkgA
1272 # and pkgB. If we used package names as keys, this would be
1273 # simpler, but it wouldn't handle package rename.
1274 for f in self.__req_dependents.iterkeys():
1275 if self.fmri_is_successor(pfmri, f):
1276 dependents.extend(self.__req_dependents[f])
1277 return dependents
1278
1279 def retrieve_catalogs(self, full_refresh = False,
1280 auths = None):
1281 failed = []
1282 total = 0
1283 succeeded = 0
1284 cat = None
1285 ts = 0
1286
1287 if not auths:
1288 auths = self.gen_authorities()
1289
1290 for auth in auths:
1291 total += 1
1292
1293 full_refresh_this_auth = False
1294
1295 if auth["prefix"] in self.catalogs:
1296 cat = self.catalogs[auth["prefix"]]
1297 ts = cat.last_modified()
1298
1299 # Although we may have a catalog with a
1300 # timestamp, the user may have changed the
1301 # origin URL for the authority. If this has
1302 # occurred, we need to perform a full refresh.
1303 if cat.origin() != auth["origin"]:
1304 full_refresh_this_auth = True
1305
1306 if ts and not full_refresh and \
1307 not full_refresh_this_auth:
1308 hdr = {'If-Modified-Since': ts}
1309 else:
1310 hdr = {}
1311
1312 ssl_tuple = self.get_ssl_credentials(auth["prefix"])
1313
1314 # XXX Mirror selection and retrieval policy?
1315 try:
1316 c, v = versioned_urlopen(auth["origin"],
1317 "catalog", [0], ssl_creds=ssl_tuple,
1318 headers=hdr, imgtype=self.type,
1319 uuid=self.get_uuid(auth["prefix"]))
1320 except urllib2.HTTPError, e:
1321 # Server returns NOT_MODIFIED if catalog is up
1322 # to date
1323 if e.code == httplib.NOT_MODIFIED:
1324 succeeded += 1
1325 else:
1326 failed.append((auth, e))
1327 continue
1328
1329 except urllib2.URLError, e:
1330 failed.append((auth, e))
1331 continue
1332 except ValueError, e:
1333 failed.append((auth, e))
1334 continue
1335
1336 # root for this catalog
1337 croot = "%s/catalog/%s" % (self.imgdir, auth["prefix"])
1338
1339 try:
1340 updatelog.recv(c, croot, ts, auth)
1341 except IOError, e:
1342 failed.append((auth, e))
1343 except socket.timeout, e:
1344 failed.append((auth, e))
1345 else:
1346 succeeded += 1
1347
1348 self.cache_catalogs()
1349
1350 if failed:
1351 raise api_errors.CatalogRefreshException(failed, total,
1352 succeeded)
1353
1354 CATALOG_CACHE_VERSION = 1
1355
1356 def cache_catalogs(self):
1357 """Read in all the catalogs and cache the data."""
1358 cache = {}
1359 for auth in self.gen_authorities():
1360 croot = "%s/catalog/%s" % (self.imgdir, auth["prefix"])
1361 # XXX Should I be removing pkg_names.pkl now that we're
1362 # not using it anymore?
1363 try:
1364 catalog.Catalog.read_catalog(cache,
1365 croot, auth = auth["prefix"])
1366 except EnvironmentError, e:
1367 # If a catalog file is just missing, ignore it.
1368 # If there's a worse error, make sure the user
1369 # knows about it.
1370 if e.errno == errno.ENOENT:
1371 pass
1372 else:
1373 raise
1374
1375 pickle_file = os.path.join(self.imgdir, "catalog/catalog.pkl")
1376
1377 try:
1378 pf = file(pickle_file, "wb")
1379 # Version the dump file
1380 cPickle.dump((self.CATALOG_CACHE_VERSION, cache), pf,
1381 protocol = cPickle.HIGHEST_PROTOCOL)
1382 pf.close()
1383 except (cPickle.PickleError, EnvironmentError):
1384 try:
1385 os.remove(pickle_file)
1386 except EnvironmentError:
1387 pass
1388
1389 self._catalog = cache
1390
1391 def load_catalog_cache(self):
1392 """Read in the cached catalog data."""
1393
1394 self.__load_pkg_states()
1395
1396 cache_file = os.path.join(self.imgdir, "catalog/catalog.pkl")
1397 try:
1398 version, self._catalog = \
1399 cPickle.load(file(cache_file, "rb"))
1400 except (cPickle.PickleError, EnvironmentError):
1401 raise RuntimeError
1402
1403 # If we don't recognize the version, complain.
1404 if version != self.CATALOG_CACHE_VERSION:
1405 raise RuntimeError
1406
1407 # Add the packages which are installed, but not in the catalog.
1408 # XXX Should we have a different state for these, so we can flag
1409 # them to the user?
1410 for state, f in self.pkg_states.values():
1411 if state != PKG_STATE_INSTALLED:
1412 continue
1413 auth, name, vers = f.tuple()
1414
1415 if name not in self._catalog or \
1416 vers not in self._catalog[name]["versions"]:
1417 catalog.Catalog.cache_fmri(self._catalog, f,
1418 f.get_authority())
1419
1420 def load_catalogs(self, progresstracker):
1421 for auth in self.gen_authorities():
1422 croot = "%s/catalog/%s" % (self.imgdir, auth["prefix"])
1423 progresstracker.catalog_start(auth["prefix"])
1424 if auth["prefix"] == self.cfg_cache.preferred_authority:
1425 authpfx = "%s_%s" % (pkg.fmri.PREF_AUTH_PFX,
1426 auth["prefix"])
1427 c = catalog.Catalog(croot,
1428 authority=authpfx)
1429 else:
1430 c = catalog.Catalog(croot,
1431 authority = auth["prefix"])
1432 self.catalogs[auth["prefix"]] = c
1433 progresstracker.catalog_done()
1434
1435 # Try to load the catalog cache file. If that fails, load the
1436 # data from the canonical text copies of the catalogs from each
1437 # authority. Try to save it, to spare the time in the future.
1438 # XXX Given that this is a read operation, should we be writing?
1439 try:
1440 self.load_catalog_cache()
1441 except RuntimeError:
1442 self.cache_catalogs()
1443
1444 def destroy_catalog_cache(self):
1445 pickle_file = os.path.join(self.imgdir, "catalog/catalog.pkl")
1446 try:
1447 portable.remove(pickle_file)
1448 except OSError, e:
1449 if e.errno != errno.ENOENT:
1450 raise
1451
1452 def destroy_catalog(self, auth_name):
1453 try:
1454 shutil.rmtree("%s/catalog/%s" %
1455 (self.imgdir, auth_name))
1456 except OSError, e:
1457 if e.errno != errno.ENOENT:
1458 raise
1459
1460 def fmri_is_same_pkg(self, cfmri, pfmri):
1461 """Determine whether fmri and pfmri share the same package
1462 name, even if they're not equivalent versions. This
1463 also checks if two packages with different names are actually
1464 the same because of a rename operation."""
1465
1466 # If the catalog has a rename record that names fmri as a
1467 # destination, it's possible that pfmri could be the same pkg by
1468 # rename.
1469 if cfmri.is_same_pkg(pfmri):
1470 return True
1471
1472 # Get the catalog for the correct authority
1473 cat = self.get_catalog(cfmri)
1474 return cat.rename_is_same_pkg(cfmri, pfmri)
1475
1476
1477 def fmri_is_successor(self, cfmri, pfmri):
1478 """Since the catalog keeps track of renames, it's no longer
1479 sufficient to rely on the FMRI class to determine whether a
1480 package is a successor. This routine takes two FMRIs, and
1481 if they have the same authority, checks if they've been
1482 renamed. If a rename has occurred, this runs the is_successor
1483 routine from the catalog. Otherwise, this runs the standard
1484 fmri.is_successor() code."""
1485
1486 # Get the catalog for the correct authority
1487 cat = self.get_catalog(cfmri)
1488
1489 # If the catalog has a rename record that names fmri as a
1490 # destination, it's possible that pfmri could be a successor by
1491 # rename.
1492 if cfmri.is_successor(pfmri):
1493 return True
1494 else:
1495 return cat.rename_is_successor(cfmri, pfmri)
1496
1497 def gen_installed_pkg_names(self):
1498 """Generate the string representation of all installed
1499 packages. This is faster than going through gen_installed_pkgs
1500 when all that will be done is to extract the strings from
1501 the result.
1502 """
1503 if self.pkg_states is not None:
1504 for i in self.pkg_states.values():
1505 yield i[1].get_fmri(anarchy=True)
1506 else:
1507 installed_state_dir = "%s/state/installed" % \
1508 self.imgdir
1509 if os.path.isdir(installed_state_dir):
1510 for pl in os.listdir(installed_state_dir):
1511 yield "pkg:/" + urllib.unquote(pl)
1512 else:
1513 proot = "%s/pkg" % self.imgdir
1514 for pd in sorted(os.listdir(proot)):
1515 for vd in \
1516 sorted(os.listdir("%s/%s" %
1517 (proot, pd))):
1518 path = "%s/%s/%s/installed" % \
1519 (proot, pd, vd)
1520 if not os.path.exists(path):
1521 continue
1522
1523 yield urllib.unquote(
1524 "pkg:/%s@%s" % (pd, vd))
1525
1526 # This could simply call self.inventory() (or be replaced by inventory),
1527 # but it turns out to be about 20% slower.
1528 def gen_installed_pkgs(self):
1529 """Return an iteration through the installed packages."""
1530 self.__load_pkg_states()
1531 return (i[1] for i in self.pkg_states.values())
1532
1533 def __load_pkg_states(self):
1534 """Build up the package state dictionary.
1535
1536 This dictionary maps the full fmri string to a tuple of the
1537 state, the prefix of the authority from which it's installed,
1538 and the fmri object.
1539
1540 Note that this dictionary only maps installed packages. Use
1541 get_pkg_state_by_fmri() to retrieve the state for arbitrary
1542 packages.
1543 """
1544
1545 if self.pkg_states is not None:
1546 return
1547
1548 installed_state_dir = "%s/state/installed" % self.imgdir
1549
1550 self.pkg_states = {}
1551
1552 # If the state directory structure has already been created,
1553 # loading information from it is fast. The directory is
1554 # populated with symlinks, named by their (url-encoded) FMRI,
1555 # which point to the "installed" file in the corresponding
1556 # directory under /var/pkg.
1557 if os.path.isdir(installed_state_dir):
1558 for pl in sorted(os.listdir(installed_state_dir)):
1559 fmristr = urllib.unquote(pl)
1560 tmpf = pkg.fmri.PkgFmri(fmristr)
1561 path = self._install_file(tmpf)
1562 auth = self.installed_file_authority(path)
1563 f = pkg.fmri.PkgFmri(fmristr, authority = auth)
1564
1565 self.pkg_states[fmristr] = \
1566 (PKG_STATE_INSTALLED, f)
1567
1568 return
1569
1570 # Otherwise, we must iterate through the earlier installed
1571 # state. One day, this can be removed.
1572 proot = "%s/pkg" % self.imgdir
1573 for pd in sorted(os.listdir(proot)):
1574 for vd in sorted(os.listdir("%s/%s" % (proot, pd))):
1575 path = "%s/%s/%s/installed" % (proot, pd, vd)
1576 if not os.path.exists(path):
1577 continue
1578
1579 fmristr = urllib.unquote("%s@%s" % (pd, vd))
1580 auth = self.installed_file_authority(path)
1581 f = pkg.fmri.PkgFmri(fmristr, authority = auth)
1582
1583 self.pkg_states[fmristr] = \
1584 (PKG_STATE_INSTALLED, f)
1585
1586 def clear_pkg_state(self):
1587 self.pkg_states = None
1588 self.__manifest_cache = {}
1589
1590 def strtofmri(self, myfmri):
1591 return pkg.fmri.PkgFmri(myfmri, self.attrs["Build-Release"])
1592
1593 def strtomatchingfmri(self, myfmri):
1594 return pkg.fmri.MatchingPkgFmri(myfmri, self.attrs["Build-Release"])
1595
1596 def update_optional_dependency(self, inputfmri):
1597 """Updates pkgname to min fmri mapping if fmri is newer"""
1598
1599 myfmri = inputfmri
1600
1601 try:
1602 name = myfmri.get_name()
1603 except AttributeError:
1604 name = pkg.fmri.extract_pkg_name(myfmri)
1605 myfmri = self.strtomatchingfmri(myfmri)
1606
1607 try:
1608 myfmri = self.inventory([ myfmri ], all_known = True,
1609 matcher = pkg.fmri.exact_name_match).next()[0]
1610 except api_errors.InventoryException:
1611 # If we didn't find the package in the authority it's
1612 # currently installed from, try again without specifying
1613 # the authority. This will get the first available
1614 # instance of the package preferring the preferred
1615 # authority. Make sure to unset the authority on a copy
1616 # of myfmri, just in case myfmri is the same object as
1617 # the input fmri.
1618 myfmri = myfmri.copy()
1619 myfmri.set_authority(None)
1620 myfmri = self.inventory([ myfmri ], all_known = True,
1621 matcher = pkg.fmri.exact_name_match).next()[0]
1622
1623 ofmri = self.optional_dependencies.get(name, None)
1624 if not ofmri or self.fmri_is_successor(myfmri, ofmri):
1625 self.optional_dependencies[name] = myfmri
1626
1627 def apply_optional_dependencies(self, myfmri):
1628 """Updates an fmri if optional dependencies require a newer version.
1629 Doesn't handle catalog renames... to ease programming for now,
1630 unversioned fmris are returned upgraded"""
1631
1632 try:
1633 name = myfmri.get_name()
1634 except AttributeError:
1635 name = pkg.fmri.extract_pkg_name(myfmri)
1636 myfmri = self.strtomatchingfmri(myfmri)
1637
1638 minfmri = self.optional_dependencies.get(name, None)
1639 if not minfmri:
1640 return myfmri
1641
1642 if self.fmri_is_successor(minfmri, myfmri):
1643 return minfmri
1644 return myfmri
1645
1646 def load_optional_dependencies(self):
1647 for fmri in self.gen_installed_pkgs():
1648 mfst = self.get_manifest(fmri, filtered = True)
1649 for dep in mfst.gen_actions_by_type("depend"):
1650 required, min_fmri, max_fmri = dep.parse(self)
1651 if required == False:
1652 self.update_optional_dependency(min_fmri)
1653
1654 def get_user_by_name(self, name):
1655 return portable.get_user_by_name(name, self.root,
1656 self.type != IMG_USER)
1657
1658 def get_name_by_uid(self, uid, returnuid = False):
1659 # XXX What to do about IMG_PARTIAL?
1660 try:
1661 return portable.get_name_by_uid(uid, self.root,
1662 self.type != IMG_USER)
1663 except KeyError:
1664 if returnuid:
1665 return uid
1666 else:
1667 raise
1668
1669 def get_group_by_name(self, name):
1670 return portable.get_group_by_name(name, self.root,
1671 self.type != IMG_USER)
1672
1673 def get_name_by_gid(self, gid, returngid = False):
1674 try:
1675 return portable.get_name_by_gid(gid, self.root,
1676 self.type != IMG_USER)
1677 except KeyError:
1678 if returngid:
1679 return gid
1680 else:
1681 raise
1682
1683 @staticmethod
1684 def __multimatch(name, patterns, matcher):
1685 """Applies a matcher to a name across a list of patterns.
1686 Returns all tuples of patterns which match the name. Each tuple
1687 contains the index into the original list, the pattern itself,
1688 the package version, the authority, and the raw authority
1689 string."""
1690 return [
1691 (i, pat, pat.tuple()[2],
1692 pat.get_authority(), pat.get_authority_str())
1693 for i, pat in enumerate(patterns)
1694 if matcher(name, pat.tuple()[1])
1695 ]
1696
1697 def __inventory(self, patterns = None, all_known = False, matcher = None,
1698 constraint = pkg.version.CONSTRAINT_AUTO):
1699 """Private method providing the back-end for inventory()."""
1700
1701 if not matcher:
1702 matcher = pkg.fmri.fmri_match
1703
1704 if not patterns:
1705 patterns = []
1706
1707 # Store the original patterns before we possibly turn them into
1708 # PkgFmri objects, so we can give them back to the user in error
1709 # messages.
1710 opatterns = patterns[:]
1711
1712 illegals = []
1713 for i, pat in enumerate(patterns):
1714 if not isinstance(pat, pkg.fmri.PkgFmri):
1715 try:
1716 if "*" in pat or "?" in pat:
1717 matcher = pkg.fmri.glob_match
1718 patterns[i] = \
1719 pkg.fmri.MatchingPkgFmri(pat,
1720 "5.11")
1721 else:
1722 patterns[i] = \
1723 pkg.fmri.PkgFmri(pat,
1724 "5.11")
1725 except pkg.fmri.IllegalFmri, e:
1726 illegals.append(e)
1727
1728 if illegals:
1729 raise api_errors.InventoryException(illegal=illegals)
1730
1731 pauth = self.cfg_cache.preferred_authority
1732
1733 # matchingpats is the set of all the patterns which matched a
1734 # package in the catalog. This allows us to return partial
1735 # failure if some patterns match and some don't.
1736 # XXX It would be nice to keep track of why some patterns failed
1737 # to match -- based on name, version, or authority.
1738 matchingpats = set()
1739
1740 # XXX Perhaps we shouldn't sort here, but in the caller, to save
1741 # memory?
1742 for name in sorted(self._catalog.keys()):
1743 # Eliminate all patterns not matching "name". If there
1744 # are no patterns left, go on to the next name, but only
1745 # if there were any to start with.
1746 matches = self.__multimatch(name, patterns, matcher)
1747 if patterns and not matches:
1748 continue
1749
1750 newest = self._catalog[name]["versions"][-1]
1751 for ver in reversed(self._catalog[name]["versions"]):
1752 # If a pattern specified a version and that
1753 # version isn't succeeded by "ver", then record
1754 # the pattern for removal from consideration.
1755 nomatch = []
1756 for i, match in enumerate(matches):
1757 if match[2] and \
1758 not ver.is_successor(match[2],
1759 constraint):
1760 nomatch.append(i)
1761
1762 # Eliminate the name matches that didn't match
1763 # on versions. We need to create a new list
1764 # because we need to reuse the original
1765 # "matches" for each new version.
1766 vmatches = [
1767 matches[i]
1768 for i, match in enumerate(matches)
1769 if i not in nomatch
1770 ]
1771
1772 # If we deleted all contenders (if we had any to
1773 # begin with), go on to the next version.
1774 if matches and not vmatches:
1775 continue
1776
1777 # Like the version skipping above, do the same
1778 # for authorities.
1779 authlist = set(self._catalog[name][str(ver)][1])
1780 nomatch = []
1781 for i, match in enumerate(vmatches):
1782 if match[3] and \
1783 match[3] not in authlist:
1784 nomatch.append(i)
1785
1786 amatches = [
1787 vmatches[i]
1788 for i, match in enumerate(vmatches)
1789 if i not in nomatch
1790 ]
1791
1792 if vmatches and not amatches:
1793 continue
1794
1795 # If no patterns were specified or any still-
1796 # matching pattern specified no authority, we
1797 # use the entire authlist for this version.
1798 # Otherwise, we use the intersection of authlist
1799 # and the auths in the patterns.
1800 aset = set(i[3] for i in amatches)
1801 if aset and None not in aset:
1802 authlist = set(
1803 m[3:5]
1804 for m in amatches
1805 if m[3] in authlist
1806 )
1807 else:
1808 authlist = zip(authlist, authlist)
1809
1810 pfmri = self._catalog[name][str(ver)][0]
1811
1812 inst_state = self.get_pkg_state_by_fmri(pfmri)
1813 inst_auth = self.get_pkg_auth_by_fmri(pfmri)
1814 state = {
1815 "upgradable": ver != newest,
1816 "frozen": False,
1817 "incorporated": False,
1818 "excludes": False
1819 }
1820
1821 # We yield copies of the fmri objects in the
1822 # catalog because we add the authorities in, and
1823 # don't want to mess up the canonical catalog.
1824 # If a pattern had specified an authority as
1825 # preferred, be sure to emit an fmri that way,
1826 # too.
1827 yielded = False
1828 if all_known:
1829 for auth, rauth in authlist:
1830 nfmri = pfmri.copy()
1831 nfmri.set_authority(rauth,
1832 auth == pauth)
1833 if auth == inst_auth:
1834 state["state"] = \
1835 PKG_STATE_INSTALLED
1836 else:
1837 state["state"] = \
1838 PKG_STATE_KNOWN
1839 yield nfmri, state
1840 yielded = True
1841 elif inst_state == PKG_STATE_INSTALLED:
1842 nfmri = pfmri.copy()
1843 nfmri.set_authority(inst_auth,
1844 inst_auth == pauth)
1845 state["state"] = inst_state
1846 yield nfmri, state
1847 yielded = True
1848
1849 if yielded:
1850 matchingpats |= set(i[:2] for i in amatches)
1851
1852 nonmatchingpats = [
1853 opatterns[i]
1854 for i, f in set(enumerate(patterns)) - matchingpats
1855 ]
1856 if nonmatchingpats:
1857 raise api_errors.InventoryException(
1858 notfound=nonmatchingpats)
1859
1860 def inventory(self, *args, **kwargs):
1861 """Enumerate the package FMRIs in the image's catalog.
1862
1863 If "patterns" is None (the default) or an empty sequence, all
1864 package names will match. Otherwise, it is a list of patterns
1865 to match against FMRIs in the catalog.
1866
1867 If "all_known" is False (the default), only installed packages
1868 will be enumerated. If True, all known packages will be
1869 enumerated.
1870
1871 The "matcher" parameter should specify a function taking two
1872 string arguments: a name and a pattern, returning True if the
1873 pattern matches the name, and False otherwise. By default, the
1874 matcher will be pkg.fmri.fmri_match().
1875
1876 The "constraint" parameter defines how a version specified in a
1877 pattern matches a version in the catalog. By default, a natural
1878 "subsetting" constraint is used."""
1879
1880 # "preferred" and "first_only" are private arguments that are
1881 # currently only used in evaluate_fmri(), but could be made more
1882 # generally useful. "preferred" ensures that all potential
1883 # matches from the preferred authority are generated before
1884 # those from non-preferred authorities. In the current
1885 # implementation, this consumes more memory. "first_only"
1886 # signals us to return only the first match, which allows us to
1887 # save all the memory that "preferred" currently eats up.
1888 preferred = kwargs.pop("preferred", False)
1889 first_only = kwargs.pop("first_only", False)
1890 pauth = self.cfg_cache.preferred_authority
1891
1892 if not preferred:
1893 for f in self.__inventory(*args, **kwargs):
1894 yield f
1895 else:
1896 nplist = []
1897 firstnp = None
1898 for f in self.__inventory(*args, **kwargs):
1899 if f[0].get_authority() == pauth:
1900 yield f
1901 if first_only:
1902 return
1903 else:
1904 if first_only:
1905 if not firstnp:
1906 firstnp = f
1907 else:
1908 nplist.append(f)
1909 if first_only:
1910 yield firstnp
1911 return
1912
1913 for f in nplist:
1914 yield f
1915
1916 def update_index_dir(self, postfix="index"):
1917 """Since the index directory will not reliably be updated when
1918 the image root is, this should be called prior to using the
1919 index directory.
1920 """
1921 self.index_dir = os.path.join(self.imgdir, postfix)
1922
1923 def degraded_local_search(self, args):
1924 msg("Search capabilities and performance are degraded.\n"
1925 "To improve, run 'pkg rebuild-index'.")
1926 res = []
1927
1928 for fmri, mfst in self.get_fmri_manifest_pairs():
1929 m = manifest.Manifest()
1930 try:
1931 mcontent = file(mfst).read()
1932 except EnvironmentError:
1933 # XXX log something?
1934 continue
1935 m.set_content(mcontent)
1936 new_dict = m.search_dict()
1937
1938 tok = args[0]
1939
1940 for tok_type in new_dict.keys():
1941 if new_dict[tok_type].has_key(tok):
1942 ak_list = new_dict[tok_type][tok]
1943 for action, keyval in ak_list:
1944 res.append((tok_type, fmri, \
1945 action, keyval))
1946 return res
1947
1948 def local_search(self, args, case_sensitive):
1949 """Search the image for the token in args[0]."""
1950 assert args[0]
1951 self.update_index_dir()
1952 qe = query_e.ClientQueryEngine(self.index_dir)
1953 query = query_e.Query(args[0], case_sensitive)
1954 try:
1955 res = qe.search(query, self.gen_installed_pkg_names())
1956 except search_errors.NoIndexException:
1957 res = self.degraded_local_search(args)
1958 return res
1959
1960 def remote_search(self, args, servers = None):
1961 """Search for the token in args[0] on the servers in 'servers'.
1962 If 'servers' is empty or None, search on all known servers."""
1963 failed = []
1964
1965 if not servers:
1966 servers = self.gen_authorities()
1967
1968 for auth in servers:
1969 ssl_tuple = self.get_ssl_credentials(
1970 authority = auth.get("prefix", None),
1971 origin = auth["origin"])
1972 try:
1973 uuid = self.get_uuid(auth["prefix"])
1974 except KeyError:
1975 uuid = None
1976
1977 try:
1978 res, v = versioned_urlopen(auth["origin"],
1979 "search", [0], urllib.quote(args[0], ""),
1980 ssl_creds=ssl_tuple, imgtype=self.type,
1981 uuid=uuid)
1982 except urllib2.HTTPError, e:
1983 if e.code != httplib.NOT_FOUND:
1984 failed.append((auth, e))
1985 continue
1986 except urllib2.URLError, e:
1987 failed.append((auth, e))
1988 continue
1989
1990 try:
1991 for line in res.read().splitlines():
1992 fields = line.split()
1993 if len(fields) < 4:
1994 yield fields[:2] + [ "", "" ]
1995 else:
1996 yield fields[:4]
1997 except socket.timeout, e:
1998 failed.append((auth, e))
1999 continue
2000
2001 if failed:
2002 raise RuntimeError, failed
2003
2004 def incoming_download_dir(self):
2005 """Return the directory path for incoming downloads
2006 that have yet to be completed. Once a file has been
2007 successfully downloaded, it is moved to the cached download
2008 directory."""
2009
2010 return self.dl_cache_incoming
2011
2012 def cached_download_dir(self):
2013 """Return the directory path for cached content.
2014 Files that have been successfully downloaded live here."""
2015
2016 return self.dl_cache_dir
2017
2018 def cleanup_downloads(self):
2019 """Clean up any downloads that were in progress but that
2020 did not successfully finish."""
2021
2022 shutil.rmtree(self.dl_cache_incoming, True)
2023
2024 def cleanup_cached_content(self):
2025 """Delete the directory that stores all of our cached
2026 downloaded content. This may take a while for a large
2027 directory hierarchy. Don't clean up caches if the
2028 user overrode the underlying setting using PKG_CACHEDIR. """
2029
2030 if not self.is_user_cache_dir and \
2031 self.cfg_cache.get_policy(imageconfig.FLUSH_CONTENT_CACHE):
2032 msg("Deleting content cache")
2033 shutil.rmtree(self.dl_cache_dir, True)
2034
2035 def salvagedir(self, path):
2036 """Called when directory contains something and it's not supposed
2037 to because it's being deleted. XXX Need to work out a better error
2038 passback mechanism. Path is rooted in /...."""
2039
2040 salvagedir = os.path.normpath(
2041 os.path.join(self.imgdir, "lost+found",
2042 path + "-" + time.strftime("%Y%m%dT%H%M%SZ")))
2043
2044 parent = os.path.dirname(salvagedir)
2045 if not os.path.exists(parent):
2046 os.makedirs(parent)
2047 shutil.move(os.path.normpath(os.path.join(self.root, path)), salvagedir)
2048 # XXX need a better way to do this.
2049 emsg("\nWarning - directory %s not empty - contents preserved "
2050 "in %s" % (path, salvagedir))
2051
2052 def temporary_file(self):
2053 """ create a temp file under image directory for various purposes"""
2054 tempdir = os.path.normpath(os.path.join(self.imgdir, "tmp"))
2055 if not os.path.exists(tempdir):
2056 os.makedirs(tempdir)
2057 fd, name = tempfile.mkstemp(dir=tempdir)
2058 os.close(fd)
2059 return name
2060
2061 def expanddirs(self, dirs):
2062 """given a set of directories, return expanded set that includes
2063 all components"""
2064 out = set()
2065 for d in dirs:
2066 p = d
2067 while p != "":
2068 out.add(p)
2069 p = os.path.dirname(p)
2070 return out
2071
2072
2073 def make_install_plan(self, pkg_list, prtrack, check_cancelation,
2074 noexecute, filters = None, verbose=False, use_cache=True):
2075 """Take a list of packages, specified in pkg_list, and attempt
2076 to assemble an appropriate image plan. This is a helper
2077 routine for some common operations in the client.
2078
2079 This method checks all authorities for a package match;
2080 however, it defaults to choosing the preferred authority
2081 when an ambiguous package name is specified. If the user
2082 wishes to install a package from a non-preferred authority,
2083 the full FMRI that contains an authority should be used
2084 to name the package."""
2085
2086 if filters is None:
2087 filters = []
2088
2089 error = 0
2090 ip = imageplan.ImagePlan(self, prtrack, check_cancelation,
2091 filters=filters, noexecute=noexecute, use_cache=use_cache)
2092
2093 self.load_optional_dependencies()
2094
2095 unfound_fmris = []
2096 multiple_matches = []
2097
2098 for p in pkg_list:
2099 try:
2100 conp = self.apply_optional_dependencies(p)
2101 matches = list(self.inventory([ conp ],
2102 all_known = True))
2103 except api_errors.InventoryException:
2104 error = 1
2105 unfound_fmris.append(p)
2106 continue
2107
2108 pnames = {}
2109 pmatch = []
2110 npnames = {}
2111 npmatch = []
2112 for m, state in matches:
2113 if m.preferred_authority():
2114 pnames[m.get_pkg_stem()] = 1
2115 pmatch.append(m)
2116 else:
2117 npnames[m.get_pkg_stem()] = 1
2118 npmatch.append(m)
2119
2120 if len(pnames.keys()) > 1:
2121 multiple_matches.append((p, pnames.keys()))
2122 error = 1
2123 continue
2124 elif len(pnames.keys()) < 1 and len(npnames.keys()) > 1:
2125 multiple_matches.append((p, pnames.keys()))
2126 error = 1
2127 continue
2128
2129 # matches is a list reverse sorted by version, so take
2130 # the first; i.e., the latest.
2131 if len(pmatch) > 0:
2132 ip.propose_fmri(pmatch[0])
2133 else:
2134 ip.propose_fmri(npmatch[0])
2135
2136 if error != 0:
2137 raise api_errors.PlanCreationException(unfound_fmris,
2138 multiple_matches, None)
2139
2140 if verbose:
2141 msg(_("Before evaluation:"))
2142 msg(ip)
2143
2144 # A plan can be requested without actually performing an
2145 # operation on the image.
2146 if self.history.operation_name:
2147 self.history.operation_start_state = ip.get_plan()
2148
2149 ip.evaluate()
2150 self.imageplan = ip
2151
2152 if self.history.operation_name:
2153 self.history.operation_end_state = \
2154 ip.get_plan(full=False)
2155
2156 if verbose:
2157 msg(_("After evaluation:"))
2158 msg(ip.display())
2159
2160 def make_uninstall_plan(self, fmri_list, recursive_removal,
2161 progresstracker, check_cancelation, noexecute, verbose=False,
2162 use_cache=True):
2163 ip = imageplan.ImagePlan(self, progresstracker,
2164 check_cancelation, recursive_removal,
2165 noexecute=noexecute, use_cache=use_cache)
2166
2167 err = 0
2168
2169 unfound_fmris = []
2170 multiple_matches = []
2171 missing_matches = []
2172
2173 for ppat in fmri_list:
2174 try:
2175 matches = list(self.inventory([ppat]))
2176 except api_errors.InventoryException:
2177 unfound_fmris.append(ppat)
2178 err = 1
2179 continue
2180
2181 if len(matches) > 1:
2182 multiple_matches.append((ppat, matches))
2183 err = 1
2184 continue
2185
2186 if len(matches) < 1:
2187 missing_matches.append(ppat)
2188 err = 1
2189 continue
2190
2191 # Propose the removal of the first (and only!) match.
2192 ip.propose_fmri_removal(matches[0][0])
2193
2194 if err == 1:
2195 raise api_errors.PlanCreationException(unfound_fmris,
2196 multiple_matches, missing_matches)
2197 if verbose:
2198 msg(_("Before evaluation:"))
2199 msg(ip)
2200
2201 self.history.operation_start_state = ip.get_plan()
2202 ip.evaluate()
2203 self.history.operation_end_state = ip.get_plan(full=False)
2204 self.imageplan = ip
2205
2206 if verbose:
2207 msg(_("After evaluation:"))
2208 ip.display()
2209
2210 def ipkg_is_up_to_date(self, actual_cmd, check_cancelation, noexecute,
2211 refresh_catalogs):
2212 """ Test whether SUNWipkg is updated to the latest version
2213 known to be available for this image """
2214 #
2215 # This routine makes the distinction between the "target image",
2216 # which will be altered, and the "running image", which is
2217 # to say whatever image appears to contain the version of the
2218 # pkg command we're running.
2219 #
2220
2221 #
2222 # There are two relevant cases here:
2223 # 1) Packaging code and image we're updating are the same
2224 # image. (i.e. 'pkg image-update')
2225 #
2226 # 2) Packaging code's image and the image we're updating are
2227 # different (i.e. 'pkg image-update -R')
2228 #
2229 # In general, we care about getting the user to run the
2230 # most recent packaging code available for their build. So,
2231 # if we're not in the liveroot case, we create a new image
2232 # which represents "/" on the system.
2233 #
2234
2235 # always quiet for this part of the code.
2236 progresstracker = progress.QuietProgressTracker()
2237
2238 img = self
2239
2240 if not img.is_liveroot():
2241 newimg = Image()
2242 cmdpath = os.path.join(os.getcwd(), actual_cmd)
2243 cmdpath = os.path.realpath(cmdpath)
2244 cmddir = os.path.dirname(os.path.realpath(cmdpath))
2245 #
2246 # Find the path to ourselves, and use that
2247 # as a way to locate the image we're in. It's
2248 # not perfect-- we could be in a developer's
2249 # workspace, for example.
2250 #
2251 newimg.find_root(cmddir)
2252 newimg.load_config()
2253
2254 if refresh_catalogs:
2255 # Refresh the catalog, so that we can discover
2256 # if a new SUNWipkg is available.
2257 try:
2258 newimg.retrieve_catalogs()
2259 except api_errors.CatalogRefreshException, cre:
2260 cre.message = \
2261 _("SUNWipkg update check failed.")
2262 raise
2263
2264 # Load catalog.
2265 newimg.load_catalogs(progresstracker)
2266 img = newimg
2267
2268 # XXX call to progress tracker that SUNWipkg is being checked
2269
2270 img.make_install_plan(["SUNWipkg"], progresstracker,
2271 check_cancelation, noexecute, filters = [])
2272
2273 return img.imageplan.nothingtodo()
2274
2275 def installed_fmris_from_args(self, args):
2276 """Helper function to translate client command line arguments
2277 into a list of installed fmris. Used by info, contents,
2278 verify.
2279 """
2280 found = []
2281 notfound = []
2282 illegals = []
2283 try:
2284 for m in self.inventory(args):
2285 found.append(m[0])
2286 except api_errors.InventoryException, e:
2287 illegals = e.illegal
2288 notfound = e.notfound
2289
2290 return found, notfound, illegals
2291
2292 def rebuild_search_index(self, progtracker):
2293 """Rebuilds the search indexes. Removes all
2294 existing indexes and replaces them from scratch rather than
2295 performing the incremental update which is usually used."""
2296 self.update_index_dir()
2297 if not os.path.isdir(self.index_dir):
2298 self.mkdirs()
2299 ind = indexer.Indexer(self.index_dir,
2300 CLIENT_DEFAULT_MEM_USE_KB, progtracker)
2301 ind.rebuild_index_from_scratch(self.get_fmri_manifest_pairs())