]> git.ipfire.org Git - people/stevee/pakfire.git/blob - pakfire/packages/file.py
Add copyright information to all files.
[people/stevee/pakfire.git] / pakfire / packages / file.py
1 #!/usr/bin/python
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 ###############################################################################
21
22 import logging
23 import os
24 import re
25 import tarfile
26 import tempfile
27 import xattr
28
29 import pakfire.util as util
30 import pakfire.compress as compress
31 from pakfire.errors import FileError
32 from pakfire.constants import *
33
34 from base import Package
35
36 class InnerTarFile(tarfile.TarFile):
37 SUPPORTED_XATTRS = ("security.capability",)
38
39 def __init__(self, *args, **kwargs):
40 # Force the PAX format.
41 kwargs["format"] = tarfile.PAX_FORMAT
42
43 tarfile.TarFile.__init__(self, *args, **kwargs)
44
45 def add(self, name, arcname=None, recursive=None, exclude=None, filter=None):
46 """
47 Emulate the add function with xattrs support.
48 """
49 tarinfo = self.gettarinfo(name, arcname)
50
51 if tarinfo.isreg():
52 attrs = []
53
54 # Use new modules code...
55 if hasattr(xattr, "get_all"):
56 attrs = xattr.get_all(name)
57
58 # ...or use the deprecated API.
59 else:
60 for attr in xattr.listxattr(name):
61 val = xattr.getxattr(name, attr)
62 attrs.append((attr, val))
63
64 for attr, val in attrs:
65 # Skip all attrs that are not supported (e.g. selinux).
66 if not attr in self.SUPPORTED_XATTRS:
67 continue
68
69 logging.debug("Saving xattr %s=%s from %s" % (attr, val, name))
70
71 tarinfo.pax_headers[attr] = val
72
73 # Append the tar header and data to the archive.
74 f = tarfile.bltn_open(name, "rb")
75 self.addfile(tarinfo, f)
76 f.close()
77
78 elif tarinfo.isdir():
79 self.addfile(tarinfo)
80 if recursive:
81 for f in os.listdir(name):
82 self.add(os.path.join(name, f), os.path.join(arcname, f),
83 recursive, exclude, filter)
84
85 else:
86 self.addfile(tarinfo)
87
88 def extract(self, member, path=""):
89 # Extract file the normal way...
90 tarfile.TarFile.extract(self, member, path)
91
92 # ...and then apply the extended attributes.
93 if member.pax_headers:
94 target = os.path.join(path, member.name)
95
96 for attr, val in member.pax_headers.items():
97 # Skip all attrs that are not supported (e.g. selinux).
98 if not attr in self.SUPPORTED_XATTRS:
99 continue
100
101 logging.debug("Restoring xattr %s=%s to %s" % (attr, val, target))
102 if hasattr(xattr, "set"):
103 xattr.set(target, attr, val)
104
105 else:
106 xattr.setxattr(target, attr, val)
107
108
109 class FilePackage(Package):
110 """
111 This class is a wrapper that reads package data from the (outer)
112 tarball and should never be used solely.
113 """
114 def __init__(self, pakfire, repo, filename):
115 Package.__init__(self, pakfire, repo)
116 self.filename = os.path.abspath(filename)
117
118 # Place to cache the metadata
119 self._metadata = {}
120
121 self.check()
122
123 def check(self):
124 """
125 Initially check if the given file is of the correct type and
126 can be opened.
127 """
128 if not tarfile.is_tarfile(self.filename):
129 raise FileError, "Given file is not of correct format: %s" % self.filename
130
131 def __repr__(self):
132 return "<%s %s>" % (self.__class__.__name__, self.filename)
133
134 @property
135 def local(self):
136 # A file package is always local.
137 return True
138
139 def open_archive(self):
140 return tarfile.open(self.filename)
141
142 def extract(self, message=None, prefix=None):
143 logging.debug("Extracting package %s" % self.friendly_name)
144
145 if prefix is None:
146 prefix = ""
147
148 # A place to store temporary data.
149 tempf = None
150
151 # Open package data for read.
152 archive = self.open_archive()
153
154 # Get the package payload.
155 payload = archive.extractfile("data.img")
156
157 # Decompress the payload if needed.
158 logging.debug("Compression: %s" % self.payload_compression)
159
160 # Create a temporary file to store the decompressed output.
161 garbage, tempf = tempfile.mkstemp(prefix="pakfire")
162
163 i = payload
164 o = open(tempf, "w")
165
166 # Decompress the package payload.
167 if self.payload_compression:
168 compress.decompressobj(i, o, algo=self.payload_compression)
169
170 else:
171 buf = i.read(BUFFER_SIZE)
172 while buf:
173 o.write(buf)
174 buf = i.read(BUFFER_SIZE)
175
176 i.close()
177 o.close()
178
179 payload = open(tempf)
180
181 # Open the tarball in the package.
182 payload_archive = InnerTarFile.open(fileobj=payload)
183
184 members = payload_archive.getmembers()
185
186 # Load progressbar.
187 pb = None
188 if message:
189 message = "%-10s : %s" % (message, self.friendly_name)
190 pb = util.make_progress(message, len(members), eta=False)
191
192 i = 0
193 for member in members:
194 # Update progress.
195 if pb:
196 i += 1
197 pb.update(i)
198
199 target = os.path.join(prefix, member.name)
200
201 # If the member is a directory and if it already exists, we
202 # don't need to create it again.
203
204 if os.path.exists(target):
205 if member.isdir():
206 continue
207
208 else:
209 # Remove file if it has been existant
210 os.unlink(target)
211
212 #if self.pakfire.config.get("debug"):
213 # msg = "Creating file (%s:%03d:%03d) " % \
214 # (tarfile.filemode(member.mode), member.uid, member.gid)
215 # if member.issym():
216 # msg += "/%s -> %s" % (member.name, member.linkname)
217 # elif member.islnk():
218 # msg += "/%s link to /%s" % (member.name, member.linkname)
219 # else:
220 # msg += "/%s" % member.name
221 # logging.debug(msg)
222
223 payload_archive.extract(member, path=prefix)
224
225 # Close all open files.
226 payload_archive.close()
227 payload.close()
228 archive.close()
229
230 if tempf:
231 os.unlink(tempf)
232
233 if pb:
234 pb.finish()
235
236 @property
237 def file_version(self):
238 """
239 Returns the version of the package metadata.
240 """
241 return self.metadata.get("VERSION")
242
243 @property
244 def metadata(self):
245 """
246 Read-in the metadata from the "info" file and cache it in _metadata.
247 """
248 if not self._metadata:
249 a = self.open_archive()
250 f = a.extractfile("info")
251
252 for line in f.readlines():
253 m = re.match(r"^(\w+)=(.*)$", line)
254 if m is None:
255 continue
256
257 key, val = m.groups()
258 self._metadata[key] = val.strip("\"")
259
260 f.close()
261 a.close()
262
263 return self._metadata
264
265 @property
266 def size(self):
267 """
268 Return the size of the package file.
269 """
270 return os.path.getsize(self.filename)
271
272 def __filelist_from_metadata(self):
273 a = self.open_archive()
274 f = a.extractfile("filelist")
275
276 ret = []
277 for line in f.readlines():
278 line = line.strip()
279 if not line.startswith("/"):
280 line = "/%s" % line
281
282 ret.append(line)
283
284 f.close()
285 a.close()
286
287 return ret
288
289 def __filelist_from_payload(self):
290 # XXX expect uncompressed payload for now
291 # this is very simple and very slow
292
293 a = self.open_archive()
294 f = a.extractfile("data.img")
295 t = tarfile.open(fileobj=f)
296
297 ret = ["/%s" % n for n in t.getnames()]
298
299 t.close()
300 f.close()
301 a.close()
302
303 return ret
304
305 @property
306 def filelist(self):
307 """
308 Return a list of the files that are contained in the package
309 payload.
310
311 At first, we try to get them from the metadata (which is the
312 'filelist' file).
313 If the file is not existant, we will open the payload and
314 read it instead. The latter is a very slow procedure and
315 should not be used anyway.
316 """
317 if not hasattr(self, "__filelist"):
318 try:
319 self.__filelist = self.__filelist_from_metadata()
320 except KeyError:
321 self.__filelist = self.__filelist_from_payload()
322
323 return self.__filelist
324
325 @property
326 def configfiles(self):
327 return [] # XXX to be done
328
329 @property
330 def payload_compression(self):
331 """
332 Return the compression type of the payload.
333 """
334 comp = self.metadata.get("PKG_PAYLOAD_COMP", None)
335
336 if comp == "XXX":
337 return
338
339 return comp
340
341 @property
342 def signature(self):
343 """
344 Read the signature from the archive or return None if no
345 signature does exist.
346 """
347 ret = None
348 try:
349 a = self.open_archive()
350 f = a.extractfile("signature")
351
352 ret = f.read()
353
354 f.close()
355 a.close()
356
357 except KeyError:
358 # signature file could not be found
359 pass
360
361 return ret or None
362
363 @property
364 def hash1(self):
365 """
366 Calculate the hash1 of this package.
367 """
368 return util.calc_hash1(self.filename)
369
370 @property
371 def scriptlet(self):
372 """
373 Read the scriptlet from the archive or return an empty string if no
374 scriptlet does exist.
375 """
376 ret = None
377 try:
378 a = self.open_archive()
379 f = a.extractfile("control")
380
381 ret = f.read()
382
383 f.close()
384 a.close()
385
386 except KeyError:
387 # scriptlet file could not be found
388 pass
389
390 return ret or ""