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 errno
27 import itertools
28 import os
29
30 import pkg.manifest as manifest
31 import pkg.client.filelist as filelist
32 import pkg.actions.directory as directory
33 from pkg.misc import msg
34 from pkg.misc import get_pkg_otw_size
35
36 class PkgPlan(object):
37 """A package plan takes two package FMRIs and an Image, and produces the
38 set of actions required to take the Image from the origin FMRI to the
39 destination FMRI.
40
41 If the destination FMRI is None, the package is removed.
42 """
43
44 def __init__(self, image, progtrack):
45 self.origin_fmri = None
46 self.destination_fmri = None
47 self.actions = []
48
49 self.__origin_mfst = manifest.null
50 self.__destination_mfst = manifest.null
51 self.__legacy_info = {}
52
53 self.image = image
54 self.__progtrack = progtrack
55
56 self.__xfersize = -1
57 self.__xferfiles = -1
58
59 self.__destination_filters = []
60
61 def __str__(self):
62 s = "%s -> %s\n" % (self.origin_fmri, self.destination_fmri)
63
64 for src, dest in itertools.chain(*self.actions):
65 s += " %s -> %s\n" % (src, dest)
66
67 return s
68
69 def propose_destination(self, fmri, mfst):
70 self.destination_fmri = fmri
71 self.__destination_mfst = mfst
72 self.__legacy_info["version"] = self.destination_fmri.version
73
74 if self.image.install_file_present(fmri):
75 raise RuntimeError, "already installed"
76
77 def propose_removal(self, fmri, mfst):
78 self.origin_fmri = fmri
79 self.__origin_mfst = mfst
80
81 if not self.image.install_file_present(fmri):
82 raise RuntimeError, "not installed"
83
84 def get_actions(self):
85 raise NotImplementedError()
86
87 def get_nactions(self):
88 return len(self.actions[0]) + len(self.actions[1]) + \
89 len(self.actions[2])
90
91 def update_pkg_set(self, fmri_set):
92 """ updates a set of installed fmris to reflect
93 proposed new state"""
94
95 if self.origin_fmri:
96 fmri_set.discard(self.origin_fmri)
97
98 if self.destination_fmri:
99 fmri_set.add(self.destination_fmri)
100
101 def evaluate(self, filters = []):
102 """Determine the actions required to transition the package."""
103 # if origin unset, determine if we're dealing with an previously
104 # installed version or if we're dealing with the null package
105 #
106 # XXX Perhaps make the pkgplan creator make this explicit, so we
107 # don't have to check?
108 f = None
109 if not self.origin_fmri:
110 f = self.image.older_version_installed(
111 self.destination_fmri)
112 if f:
113 self.origin_fmri = f
114 self.__origin_mfst = \
115 self.image.get_manifest(f)
116
117 self.__destination_filters = filters
118
119 # Try to load the filter used for the last install of the
120 # package.
121 origin_filters = []
122 if self.origin_fmri:
123 try:
124 f = file("%s/pkg/%s/filters" % \
125 (self.image.imgdir,
126 self.origin_fmri.get_dir_path()), "r")
127 except EnvironmentError, e:
128 if e.errno != errno.ENOENT:
129 raise
130 else:
131 origin_filters = [
132 (l.strip(), compile(
133 l.strip(), "<filter string>", "eval"))
134 for l in f.readlines()
135 ]
136
137 self.__destination_mfst.filter(self.__destination_filters)
138 self.__origin_mfst.filter(origin_filters)
139
140 # Assume that origin actions are unique, but make sure that
141 # destination ones are.
142 ddups = self.__destination_mfst.duplicates()
143 if ddups:
144 raise RuntimeError, ["Duplicate actions", ddups]
145
146 self.actions = self.__destination_mfst.difference(
147 self.__origin_mfst)
148
149 # figure out how many implicit directories disappear in this
150 # transition and add directory remove actions. These won't
151 # do anything unless no pkgs reference that directory in
152 # new state....
153
154 tmpset = set()
155
156 for a in self.__origin_mfst.actions:
157 tmpset.update(a.directory_references())
158
159 absent_dirs = self.image.expanddirs(tmpset)
160
161 tmpset = set()
162
163 for a in self.__destination_mfst.actions:
164 tmpset.update(a.directory_references())
165
166 absent_dirs.difference_update(self.image.expanddirs(tmpset))
167
168 for a in absent_dirs:
169 self.actions[2].append(
170 [directory.DirectoryAction(path=a), None])
171
172 # Stash information needed by legacy actions.
173 self.__legacy_info["description"] = \
174 self.__destination_mfst.get("description", "none provided")
175
176 #
177 # We cross a point of no return here, and throw away the origin
178 # and destination manifests. This is really important for
179 # memory footprint.
180 #
181 self.__origin_mfst = None
182 self.__destination_mfst = None
183
184 def get_legacy_info(self):
185 """ Returns information needed by the legacy action to
186 populate the SVR4 packaging info. """
187 return self.__legacy_info
188
189 def get_xferstats(self):
190 if self.__xfersize != -1:
191 return (self.__xferfiles, self.__xfersize)
192
193 self.__xfersize = 0
194 self.__xferfiles = 0
195 for src, dest in itertools.chain(*self.actions):
196 if dest and dest.needsdata(src):
197 self.__xfersize += get_pkg_otw_size(dest)
198 self.__xferfiles += 1
199
200 return (self.__xferfiles, self.__xfersize)
201
202 def will_xfer(self):
203 nf, nb = self.get_xferstats()
204 if nf > 0:
205 return True
206 else:
207 return False
208
209 def get_xfername(self):
210 if self.destination_fmri:
211 return self.destination_fmri.get_name()
212 if self.origin_fmri:
213 return self.origin_fmri.get_name()
214 return None
215
216 def preexecute(self):
217 """Perform actions required prior to installation or removal of
218 a package.
219
220 This method executes each action's preremove() or preinstall()
221 methods, as well as any package-wide steps that need to be taken
222 at such a time.
223 """
224
225 for src, dest in itertools.chain(*self.actions):
226 if dest:
227 dest.preinstall(self, src)
228 else:
229 src.preremove(self)
230
231 def download(self):
232 """Download data for any actions that need it."""
233 flist = filelist.FileList(self.image, self.destination_fmri,
234 self.__progtrack)
235 self.__progtrack.download_start_pkg(self.get_xfername())
236 for src, dest in itertools.chain(*self.actions):
237 if dest:
238 if dest.needsdata(src):
239 flist.add_action(dest)
240
241 flist.flush()
242 self.__progtrack.download_end_pkg()
243
244 def gen_install_actions(self):
245 for src, dest in self.actions[0]:
246 yield src, dest
247
248 def gen_removal_actions(self):
249 for src, dest in self.actions[2]:
250 yield src, dest
251
252 def gen_update_actions(self):
253 for src, dest in self.actions[1]:
254 yield src, dest
255
256 def execute_install(self, src, dest):
257 """ perform action for installation of package"""
258 try:
259 dest.install(self, src)
260 except Exception, e:
261 msg("Action install failed for '%s' (%s):\n %s: %s" % \
262 (dest.attrs.get(dest.key_attr, id(dest)),
263 self.destination_fmri.get_pkg_stem(),
264 e.__class__.__name__, e))
265 raise
266
267 def execute_update(self, src, dest):
268 """ handle action updates"""
269 try:
270 dest.install(self, src)
271 except Exception, e:
272 msg("Action upgrade failed for '%s' (%s):\n %s: %s" % \
273 (dest.attrs.get(dest.key_attr, id(dest)),
274 self.destination_fmri.get_pkg_stem(),
275 e.__class__.__name__, e))
276 raise
277
278 def execute_removal(self, src, dest):
279 """ handle action removals"""
280 try:
281 src.remove(self)
282 except Exception, e:
283 msg("Action removal failed for '%s' (%s):\n %s: %s" % \
284 (src.attrs.get(src.key_attr, id(src)),
285 self.origin_fmri.get_pkg_stem(),
286 e.__class__.__name__, e))
287 raise
288
289 def postexecute(self):
290 """Perform actions required after install or remove of a pkg.
291
292 This method executes each action's postremove() or postinstall()
293 methods, as well as any package-wide steps that need to be taken
294 at such a time.
295 """
296 # record that package states are consistent
297 for src, dest in itertools.chain(*self.actions):
298 if dest:
299 dest.postinstall(self, src)
300 else:
301 src.postremove(self)
302
303 # For an uninstall or an upgrade, remove the installation
304 # turds from the origin's directory.
305 # XXX should this just go in preexecute?
306 if self.destination_fmri == None or self.origin_fmri != None:
307 self.image.remove_install_file(self.origin_fmri)
308
309 try:
310 os.unlink("%s/pkg/%s/filters" % (
311 self.image.imgdir,
312 self.origin_fmri.get_dir_path()))
313 except EnvironmentError, e:
314 if e.errno != errno.ENOENT:
315 raise
316
317 if self.destination_fmri != None:
318 self.image.add_install_file(self.destination_fmri)
319
320 # Save the filters we used to install the package, so
321 # they can be referenced later.
322 if self.__destination_filters:
323 f = file("%s/pkg/%s/filters" % \
324 (self.image.imgdir,
325 self.destination_fmri.get_dir_path()), "w")
326
327 f.writelines([
328 myfilter + "\n"
329 for myfilter, code in \
330 self.__destination_filters
331 ])
332 f.close()
333