]>
Commit | Line | Data |
---|---|---|
1e80d5d7 | 1 | #!/usr/bin/python |
b792d887 MT |
2 | ############################################################################### |
3 | # # | |
4 | # Pakfire - The IPFire package management system # | |
5 | # Copyright (C) 2011 Pakfire development team # | |
6 | # # | |
7 | # This program is free software: you can redistribute it and/or modify # | |
8 | # it under the terms of the GNU General Public License as published by # | |
9 | # the Free Software Foundation, either version 3 of the License, or # | |
10 | # (at your option) any later version. # | |
11 | # # | |
12 | # This program is distributed in the hope that it will be useful, # | |
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # | |
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # | |
15 | # GNU General Public License for more details. # | |
16 | # # | |
17 | # You should have received a copy of the GNU General Public License # | |
18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. # | |
19 | # # | |
20 | ############################################################################### | |
1e80d5d7 | 21 | |
94438733 | 22 | import os |
1e80d5d7 | 23 | |
94438733 | 24 | import chroot |
1e80d5d7 | 25 | import packages |
94438733 | 26 | import util |
1e80d5d7 | 27 | |
8b6bc023 MT |
28 | import logging |
29 | log = logging.getLogger("pakfire") | |
30 | ||
1e80d5d7 MT |
31 | from constants import * |
32 | from i18n import _ | |
33 | ||
34 | class Action(object): | |
35 | def __init__(self, pakfire, pkg): | |
36 | self.pakfire = pakfire | |
6ee3d6b9 | 37 | self.pkg_solv = self.pkg = pkg |
1e80d5d7 MT |
38 | |
39 | # Try to get the binary version of the package from the cache if | |
40 | # any. | |
41 | binary_package = self.pkg.get_from_cache() | |
42 | if binary_package: | |
43 | self.pkg = binary_package | |
44 | ||
94438733 MT |
45 | self.init() |
46 | ||
aa9f2645 MT |
47 | def __cmp__(self, other): |
48 | return cmp(self.pkg, other.pkg) | |
49 | ||
1e80d5d7 MT |
50 | def __repr__(self): |
51 | return "<%s %s>" % (self.__class__.__name__, self.pkg.friendly_name) | |
52 | ||
94438733 MT |
53 | def init(self): |
54 | # A function to run additional initialization. | |
55 | pass | |
56 | ||
862bea4d MT |
57 | def check(self, filelist): |
58 | # This is just a dummy test that does nothing at all. | |
59 | return filelist | |
60 | ||
1e80d5d7 MT |
61 | @property |
62 | def needs_download(self): | |
6ca2a97f | 63 | return self.type in ("install", "reinstall", "upgrade", "downgrade", "change",) \ |
1e80d5d7 MT |
64 | and not isinstance(self.pkg, packages.BinaryPackage) |
65 | ||
66 | def download(self, text): | |
67 | if not self.needs_download: | |
68 | return | |
69 | ||
70 | self.pkg = self.pkg.download(text) | |
71 | ||
72 | def run(self): | |
73 | raise NotImplementedError | |
74 | ||
75 | @property | |
76 | def local(self): | |
77 | """ | |
78 | Reference to local repository. | |
79 | """ | |
80 | return self.pakfire.repos.local | |
81 | ||
d335c40e MT |
82 | def do(self, cmd, **kwargs): |
83 | # If we are running in /, we do not need to chroot there. | |
84 | chroot_path = None | |
85 | if not self.pakfire.path == "/": | |
86 | chroot_path = self.pakfire.path | |
87 | ||
88 | # Find suitable cwd. | |
89 | cwd = "/" | |
90 | for i in ("tmp", "root"): | |
254f5e56 MT |
91 | if chroot_path: |
92 | _cwd = os.path.join(chroot_path, i) | |
93 | else: | |
94 | _cwd = i | |
95 | ||
d335c40e MT |
96 | if os.path.exists(_cwd): |
97 | cwd = _cwd | |
98 | break | |
99 | ||
100 | args = { | |
101 | "cwd" : cwd, | |
8b6bc023 | 102 | "logger" : log, |
d335c40e MT |
103 | "personality" : self.pakfire.distro.personality, |
104 | "shell" : False, | |
105 | "timeout" : SCRIPTLET_TIMEOUT, | |
106 | } | |
107 | ||
108 | # Overwrite by args that were passed. | |
109 | args.update(kwargs) | |
110 | ||
111 | # You can never overwrite chrootPath. | |
112 | args.update({ | |
113 | "chrootPath" : chroot_path, | |
114 | }) | |
115 | ||
116 | return chroot.do(cmd, **args) | |
117 | ||
1e80d5d7 MT |
118 | |
119 | class ActionScript(Action): | |
d767668b | 120 | type = "script" |
c07a3ca7 | 121 | script_action = None |
d767668b | 122 | |
94438733 MT |
123 | def init(self): |
124 | # Load the scriplet. | |
c07a3ca7 | 125 | self.scriptlet = self.pkg.get_scriptlet(self.script_action) |
94438733 MT |
126 | |
127 | @property | |
128 | def interpreter(self): | |
129 | """ | |
130 | Get the interpreter of this scriptlet. | |
131 | """ | |
c07a3ca7 | 132 | return util.scriptlet_interpreter(self.scriptlet) |
94438733 MT |
133 | |
134 | @property | |
135 | def args(self): | |
c07a3ca7 | 136 | return [] |
94438733 | 137 | |
1e80d5d7 | 138 | def run(self): |
94438733 MT |
139 | # Exit immediately, if the scriptlet is empty. |
140 | if not self.scriptlet: | |
141 | return | |
142 | ||
143 | # Actually run the scriplet. | |
8b6bc023 | 144 | log.debug("Running scriptlet %s" % self) |
94438733 MT |
145 | |
146 | # Check if the interpreter does exist and is executable. | |
c07a3ca7 MT |
147 | if self.interpreter: |
148 | interpreter = "%s/%s" % (self.pakfire.path, self.interpreter) | |
149 | if not os.path.exists(interpreter): | |
150 | raise ActionError, _("Cannot run scriptlet because no interpreter is available: %s" \ | |
151 | % self.interpreter) | |
94438733 | 152 | |
c07a3ca7 MT |
153 | if not os.access(interpreter, os.X_OK): |
154 | raise ActionError, _("Cannot run scriptlet because the interpreter is not executable: %s" \ | |
155 | % self.interpreter) | |
94438733 MT |
156 | |
157 | # Create a name for the temporary script file. | |
158 | script_file_chroot = os.path.join("/", LOCAL_TMP_PATH, | |
159 | "scriptlet_%s" % util.random_string(10)) | |
160 | script_file = os.path.join(self.pakfire.path, script_file_chroot[1:]) | |
254f5e56 | 161 | assert script_file.startswith(self.pakfire.path) |
94438733 MT |
162 | |
163 | # Create script directory, if it does not exist. | |
164 | script_dir = os.path.dirname(script_file) | |
165 | if not os.path.exists(script_dir): | |
166 | os.makedirs(script_dir) | |
167 | ||
168 | # Write the scriptlet to a file that we can execute it. | |
169 | try: | |
170 | f = open(script_file, "wb") | |
171 | f.write(self.scriptlet) | |
172 | f.close() | |
173 | ||
174 | # The file is only accessable by root. | |
175 | os.chmod(script_file, 700) | |
176 | except: | |
177 | # Remove the file if an error occurs. | |
178 | try: | |
179 | os.unlink(script_file) | |
180 | except OSError: | |
181 | pass | |
182 | ||
183 | # XXX catch errors and return a beautiful message to the user | |
184 | raise | |
185 | ||
c07a3ca7 MT |
186 | # Generate the script command. |
187 | command = [script_file_chroot] + self.args | |
94438733 | 188 | |
94438733 | 189 | try: |
d335c40e | 190 | self.do(command) |
94438733 MT |
191 | |
192 | except Error, e: | |
193 | raise ActionError, _("The scriptlet returned an error:\n%s" % e) | |
194 | ||
195 | except commandTimeoutExpired: | |
196 | raise ActionError, _("The scriptlet ran more than %s seconds and was killed." \ | |
197 | % SCRIPTLET_TIMEOUT) | |
198 | ||
199 | finally: | |
200 | # Remove the script file. | |
201 | try: | |
202 | os.unlink(script_file) | |
203 | except OSError: | |
8b6bc023 | 204 | log.debug("Could not remove scriptlet file: %s" % script_file) |
1e80d5d7 MT |
205 | |
206 | ||
207 | class ActionScriptPreIn(ActionScript): | |
c07a3ca7 | 208 | script_action = "prein" |
1e80d5d7 MT |
209 | |
210 | ||
211 | class ActionScriptPostIn(ActionScript): | |
c07a3ca7 | 212 | script_action = "postin" |
1e80d5d7 MT |
213 | |
214 | ||
215 | class ActionScriptPreUn(ActionScript): | |
c07a3ca7 | 216 | script_action = "preun" |
1e80d5d7 MT |
217 | |
218 | ||
219 | class ActionScriptPostUn(ActionScript): | |
c07a3ca7 | 220 | script_action = "postun" |
1e80d5d7 MT |
221 | |
222 | ||
d767668b | 223 | class ActionScriptPreUp(ActionScript): |
c07a3ca7 | 224 | script_action = "preup" |
d767668b MT |
225 | |
226 | ||
227 | class ActionScriptPostUp(ActionScript): | |
c07a3ca7 | 228 | script_action = "postup" |
d767668b MT |
229 | |
230 | ||
231 | class ActionScriptPostTrans(ActionScript): | |
232 | pass | |
233 | ||
234 | ||
235 | class ActionScriptPostTransIn(ActionScriptPostTrans): | |
c07a3ca7 | 236 | script_action = "posttransin" |
d767668b MT |
237 | |
238 | ||
239 | class ActionScriptPostTransUn(ActionScriptPostTrans): | |
c07a3ca7 | 240 | script_action = "posttransun" |
d767668b MT |
241 | |
242 | ||
243 | class ActionScriptPostTransUp(ActionScriptPostTrans): | |
c07a3ca7 | 244 | script_action = "posttransup" |
d767668b MT |
245 | |
246 | ||
1e80d5d7 MT |
247 | class ActionInstall(Action): |
248 | type = "install" | |
249 | ||
862bea4d | 250 | def check(self, check): |
8b6bc023 | 251 | log.debug(_("Running transaction test for %s") % self.pkg.friendly_name) |
862bea4d MT |
252 | |
253 | # Check if this package can be installed. | |
254 | check.install(self.pkg) | |
255 | ||
1e80d5d7 | 256 | def run(self): |
e871a081 MT |
257 | # Add package to the database. |
258 | self.local.add_package(self.pkg) | |
259 | ||
260 | self.pkg.extract(_("Installing"), prefix=self.pakfire.path) | |
1e80d5d7 | 261 | |
d335c40e MT |
262 | # Check if shared objects were extracted. If this is the case, we need |
263 | # to run ldconfig. | |
264 | ldconfig_needed = False | |
265 | for file in self.pkg.filelist: | |
266 | if ".so." in file.name: | |
267 | ldconfig_needed = True | |
268 | break | |
269 | ||
270 | if "etc/ld.so.conf" in file.name: | |
271 | ldconfig_needed = True | |
272 | break | |
273 | ||
274 | if ldconfig_needed: | |
275 | # Check if ldconfig is present. | |
276 | ldconfig = os.path.join(self.pakfire.path, LDCONFIG[1:]) | |
277 | ||
278 | if os.path.exists(ldconfig) and os.access(ldconfig, os.X_OK): | |
279 | self.do(LDCONFIG) | |
280 | ||
281 | else: | |
8b6bc023 | 282 | log.debug("ldconfig is not present or not executable.") |
d335c40e | 283 | |
1e80d5d7 MT |
284 | |
285 | class ActionUpdate(Action): | |
286 | type = "upgrade" | |
287 | ||
862bea4d | 288 | def check(self, check): |
8b6bc023 | 289 | log.debug(_("Running transaction test for %s") % self.pkg.friendly_name) |
862bea4d MT |
290 | |
291 | # Check if this package can be updated. | |
292 | check.update(self.pkg) | |
293 | ||
1e80d5d7 | 294 | def run(self): |
e871a081 MT |
295 | # Add new package to the database. |
296 | self.local.add_package(self.pkg) | |
297 | ||
298 | self.pkg.extract(_("Updating"), prefix=self.pakfire.path) | |
1e80d5d7 MT |
299 | |
300 | ||
6ee3d6b9 | 301 | class ActionRemove(Action): |
1e80d5d7 MT |
302 | type = "erase" |
303 | ||
6ee3d6b9 MT |
304 | def __init__(self, *args, **kwargs): |
305 | Action.__init__(self, *args, **kwargs) | |
1e80d5d7 | 306 | |
6ee3d6b9 MT |
307 | # XXX This is ugly, but works for the moment. |
308 | self.pkg = self.local.index.db.get_package_from_solv(self.pkg_solv) | |
309 | assert self.pkg | |
310 | ||
862bea4d | 311 | def check(self, check): |
8b6bc023 | 312 | log.debug(_("Running transaction test for %s") % self.pkg.friendly_name) |
862bea4d MT |
313 | |
314 | # Check if this package can be removed. | |
315 | check.remove(self.pkg) | |
316 | ||
6ee3d6b9 | 317 | def run(self): |
890c7e93 | 318 | self.pkg.cleanup(_("Removing"), prefix=self.pakfire.path) |
1e80d5d7 | 319 | |
e871a081 MT |
320 | # Remove package from the database. |
321 | self.local.rem_package(self.pkg) | |
322 | ||
323 | ||
324 | class ActionCleanup(Action): | |
325 | type = "ignore" | |
326 | ||
327 | def __init__(self, *args, **kwargs): | |
328 | Action.__init__(self, *args, **kwargs) | |
329 | ||
330 | # XXX This is ugly, but works for the moment. | |
331 | self.pkg = self.local.index.db.get_package_from_solv(self.pkg_solv) | |
332 | assert self.pkg | |
333 | ||
862bea4d | 334 | def check(self, check): |
8b6bc023 | 335 | log.debug(_("Running transaction test for %s") % self.pkg.friendly_name) |
862bea4d MT |
336 | |
337 | # Check if this package can be removed. | |
338 | check.cleanup(self.pkg) | |
339 | ||
e871a081 MT |
340 | def run(self): |
341 | # Cleaning up leftover files and stuff. | |
342 | self.pkg.cleanup(_("Cleanup"), prefix=self.pakfire.path) | |
343 | ||
344 | # Remove package from the database. | |
345 | self.local.rem_package(self.pkg) | |
1e80d5d7 MT |
346 | |
347 | ||
348 | class ActionReinstall(Action): | |
349 | type = "reinstall" | |
350 | ||
862bea4d | 351 | def check(self, check): |
8b6bc023 | 352 | log.debug(_("Running transaction test for %s") % self.pkg.friendly_name) |
862bea4d MT |
353 | |
354 | # Check if this package can be reinstalled. | |
355 | check.remove(self.pkg) | |
356 | check.install(self.pkg) | |
357 | ||
1e80d5d7 | 358 | def run(self): |
e871a081 MT |
359 | # Remove package from the database and add it afterwards. |
360 | # Sounds weird, but fixes broken entries in the database. | |
361 | self.local.rem_package(self.pkg) | |
362 | self.local.add_package(self.pkg) | |
363 | ||
364 | self.pkg.extract(_("Installing"), prefix=self.pakfire.path) | |
1e80d5d7 MT |
365 | |
366 | ||
367 | class ActionDowngrade(Action): | |
368 | type = "downgrade" | |
369 | ||
862bea4d | 370 | def check(self, check): |
8b6bc023 | 371 | log.debug(_("Running transaction test for %s") % self.pkg.friendly_name) |
862bea4d MT |
372 | |
373 | # Check if this package can be downgraded. | |
374 | check.install(self.pkg) | |
375 | ||
1e80d5d7 | 376 | def run(self): |
e871a081 MT |
377 | # Add new package to database. |
378 | self.local.add_package(self.pkg) | |
379 | ||
380 | self.pkg.extract(_("Downgrading"), prefix=self.pakfire.path) |