Package backend :: Package common :: Module rhn_rpm
[hide private]
[frames] | no frames]

Source Code for Module backend.common.rhn_rpm

  1  # 
  2  # Copyright (c) 2008--2017 Red Hat, Inc. 
  3  # 
  4  # This software is licensed to you under the GNU General Public License, 
  5  # version 2 (GPLv2). There is NO WARRANTY for this software, express or 
  6  # implied, including the implied warranties of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 
  8  # along with this software; if not, see 
  9  # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. 
 10  # 
 11  # Red Hat trademarks are not licensed under GPLv2. No permission is 
 12  # granted to use or replicate Red Hat trademarks that are incorporated 
 13  # in this software or its documentation. 
 14  # 
 15   
 16  import os 
 17  import sys 
 18  import struct 
 19  import tempfile 
 20  import functools 
 21   
 22  import rpm 
 23   
 24  from spacewalk.common.usix import raise_with_tb 
 25  from spacewalk.common.usix import next as usix_next 
 26  from spacewalk.common import checksum 
 27  from spacewalk.common.rhn_pkg import A_Package, InvalidPackageError 
 28  from rhn.i18n import sstr 
 29   
 30  # bare-except and broad-except 
 31  # pylint: disable=W0702,W0703 
 32   
 33  if not hasattr(tempfile, 'SpooledTemporaryFile'): 
 34      # RHEL5 
 35      tempfile.SpooledTemporaryFile = tempfile.NamedTemporaryFile 
 36   
 37  # pylint: disable=E1101 
 38  # Expose a bunch of useful constants from rpm 
 39  error = rpm.error 
 40   
 41  sym, val = None, None 
 42  for sym, val in rpm.__dict__.items(): 
 43      if sym[:3] == 'RPM': 
 44          # A constant, probably - import it into our namespace 
 45          globals()[sym] = val 
 46  del sym, val 
 47   
 48  # need this for rpm-pyhon < 4.6 (e.g. on RHEL5) 
 49  rpm.RPMTAG_FILEDIGESTALGO = 5011 
 50   
 51  # these values are taken from /usr/include/rpm/rpmpgp.h 
 52  # PGPHASHALGO_MD5             =  1,   /*!< MD5 */ 
 53  # PGPHASHALGO_SHA1            =  2,   /*!< SHA1 */ 
 54  # PGPHASHALGO_RIPEMD160       =  3,   /*!< RIPEMD160 */ 
 55  # PGPHASHALGO_MD2             =  5,   /*!< MD2 */ 
 56  # PGPHASHALGO_TIGER192        =  6,   /*!< TIGER192 */ 
 57  # PGPHASHALGO_HAVAL_5_160     =  7,   /*!< HAVAL-5-160 */ 
 58  # PGPHASHALGO_SHA256          =  8,   /*!< SHA256 */ 
 59  # PGPHASHALGO_SHA384          =  9,   /*!< SHA384 */ 
 60  # PGPHASHALGO_SHA512          = 10,   /*!< SHA512 */ 
 61  PGPHASHALGO = { 
 62      1: 'md5', 
 63      2: 'sha1', 
 64      3: 'ripemd160', 
 65      5: 'md2', 
 66      6: 'tiger192', 
 67      7: 'haval-5-160', 
 68      8: 'sha256', 
 69      9: 'sha384', 
 70      10: 'sha512', 
 71  } 
72 73 74 -class RPM_Header:
75 76 "Wrapper class for an rpm header - we need to store a flag is_source" 77
78 - def __init__(self, hdr, is_source=None):
79 self.hdr = hdr 80 self.is_source = is_source 81 self.packaging = 'rpm' 82 self.signatures = [] 83 self._extract_signatures()
84
85 - def __getitem__(self, name):
86 item = self.hdr[name] 87 if isinstance(item, bytes): 88 item = sstr(item) 89 return item
90
91 - def __setitem__(self, name, item):
92 self.hdr[name] = item
93
94 - def __delitem__(self, name):
95 del self.hdr[name]
96
97 - def __getattr__(self, name):
98 item = getattr(self.hdr, name) 99 if isinstance(item, bytes): 100 item = sstr(item) 101 return item
102
103 - def __len__(self):
104 return len(self.hdr)
105
106 - def __nonzero__(self):
107 return bool(self.hdr)
108 109 __bool__ = __nonzero__ 110
111 - def checksum_type(self):
112 if self.hdr[rpm.RPMTAG_FILEDIGESTALGO] \ 113 and self.hdr[rpm.RPMTAG_FILEDIGESTALGO] in PGPHASHALGO: 114 checksum_type = PGPHASHALGO[self.hdr[rpm.RPMTAG_FILEDIGESTALGO]] 115 else: 116 checksum_type = 'md5' 117 return checksum_type
118
119 - def is_signed(self):
120 if hasattr(rpm, "RPMTAG_DSAHEADER"): 121 dsaheader = self.hdr["dsaheader"] 122 else: 123 dsaheader = 0 124 if self.hdr["siggpg"] or self.hdr["sigpgp"] or dsaheader: 125 return 1 126 return 0
127
128 - def _extract_signatures(self):
129 header_tags = [ 130 [rpm.RPMTAG_DSAHEADER, "dsa"], 131 [rpm.RPMTAG_RSAHEADER, "rsa"], 132 [rpm.RPMTAG_SIGGPG, "gpg"], 133 [rpm.RPMTAG_SIGPGP, 'pgp'], 134 ] 135 for ht, sig_type in header_tags: 136 ret = self.hdr[ht] 137 if not ret: 138 continue 139 ret_len = len(ret) 140 if ret_len < 17: 141 continue 142 # Get the key id - hopefully we get it right 143 elif ret_len <= 65: # V3 DSA signature 144 key_id = ret[9:17] 145 elif ret_len <= 72: # V4 DSA signature 146 key_id = ret[18:26] 147 elif ret_len <= 280: # V3 RSA/8 signature 148 key_id = ret[10:18] 149 elif ret_len <= 287: # V4 RSA/SHA1 signature 150 key_id = ret[19:27] 151 elif ret_len <= 536: # V3 RSA/SHA256 signature 152 key_id = ret[10:18] 153 else: # ret_len > 543 # V4 RSA/SHA signature 154 key_id = ret[19:27] 155 156 key_id_len = len(key_id) 157 fmt = "%dB" % key_id_len 158 t = struct.unpack(fmt, key_id) 159 fmt = "%02x" * key_id_len 160 key_id = fmt % t 161 self.signatures.append({ 162 'signature_type': sig_type, 163 'key_id': key_id, 164 'signature': ret, 165 })
166
167 168 -class RPM_Package(A_Package):
169 # pylint: disable=R0902 170
171 - def __init__(self, input_stream=None):
172 A_Package.__init__(self, input_stream) 173 self.header = None 174 self.header_data = tempfile.SpooledTemporaryFile() 175 self.header_start = None 176 self.header_end = None 177 self.checksum_type = None 178 self.checksum = None 179 self.payload_stream = None 180 self.payload_size = None
181
182 - def read_header(self):
183 self._get_header_byte_range() 184 try: 185 self.header = get_package_header(file_obj=self.header_data) 186 except InvalidPackageError: 187 e = sys.exc_info()[1] 188 raise_with_tb(InvalidPackageError(*e.args), sys.exc_info()[2]) 189 except error: 190 e = sys.exc_info()[1] 191 raise_with_tb(InvalidPackageError(e), sys.exc_info()[2]) 192 except: 193 raise_with_tb(InvalidPackageError, sys.exc_info()[2]) 194 self.checksum_type = self.header.checksum_type()
195
196 - def _get_header_byte_range(self):
197 """ 198 Return the start and end bytes of the rpm header object. 199 Raw header data are then stored in self.header_data. 200 201 For details of the rpm file format, see: 202 http://www.rpm.org/max-rpm/s1-rpm-file-format-rpm-file-format.html 203 """ 204 205 lead_size = 96 206 struct_lead_size = 16 207 # Move past the rpm lead 208 buf = self._read_bytes(self.input_stream, lead_size) 209 self.header_data.write(buf) 210 211 buf = self._read_bytes(self.input_stream, struct_lead_size) 212 self.header_data.write(buf) 213 214 sig_size = self._get_header_struct_size(buf) 215 216 # Now we can find the start of the actual header. 217 self.header_start = lead_size + sig_size 218 219 buf = self._read_bytes(self.input_stream, sig_size - struct_lead_size) 220 self.header_data.write(buf) 221 222 buf = self._read_bytes(self.input_stream, struct_lead_size) 223 self.header_data.write(buf) 224 225 header_size = self._get_header_struct_size(buf) 226 self.header_end = self.header_start + header_size 227 228 buf = self._read_bytes(self.input_stream, header_size - struct_lead_size) 229 self.header_data.write(buf)
230 231 @staticmethod
232 - def _get_header_struct_size(struct_lead):
233 """ 234 Compute the size in bytes of the rpm header struct starting at the current 235 position in package_file. 236 """ 237 # Read the number of index entries 238 header_index = struct_lead[8:12] 239 (header_index_value, ) = struct.unpack('>I', header_index) 240 241 # Read the the size of the header data store 242 header_store = struct_lead[12:16] 243 (header_store_value, ) = struct.unpack('>I', header_store) 244 245 # The total size of the header. Each index entry is 16 bytes long. 246 header_size = 8 + 4 + 4 + header_index_value * 16 + header_store_value 247 248 # Headers end on an 8-byte boundary. Round out the extra data. 249 round_out = header_size % 8 250 if round_out != 0: 251 header_size = header_size + (8 - round_out) 252 253 return header_size
254
255 - def save_payload(self, output_stream):
256 c_hash = checksum.getHashlibInstance(self.checksum_type, False) 257 if output_stream: 258 output_start = output_stream.tell() 259 self.header_data.seek(0, 0) 260 self._stream_copy(self.header_data, output_stream, c_hash) 261 self._stream_copy(self.input_stream, output_stream, c_hash) 262 self.checksum = c_hash.hexdigest() 263 self.header_data.close() 264 if output_stream: 265 self.payload_stream = output_stream 266 self.payload_size = output_stream.tell() - output_start
267
268 269 -def get_header_byte_range(package_file):
270 """ 271 Return the start and end bytes of the rpm header object. 272 273 For details of the rpm file format, see: 274 http://www.rpm.org/max-rpm/s1-rpm-file-format-rpm-file-format.html 275 """ 276 277 lead_size = 96 278 279 # Move past the rpm lead 280 package_file.seek(lead_size) 281 282 sig_size = get_header_struct_size(package_file) 283 284 # Now we can find the start of the actual header. 285 header_start = lead_size + sig_size 286 287 package_file.seek(header_start) 288 289 header_size = get_header_struct_size(package_file) 290 291 header_end = header_start + header_size 292 293 return (header_start, header_end)
294
295 296 -def get_header_struct_size(package_file):
297 """ 298 Compute the size in bytes of the rpm header struct starting at the current 299 position in package_file. 300 """ 301 # Move past the header preamble 302 package_file.seek(8, 1) 303 304 # Read the number of index entries 305 header_index = package_file.read(4) 306 (header_index_value, ) = struct.unpack('>I', header_index) 307 308 # Read the the size of the header data store 309 header_store = package_file.read(4) 310 (header_store_value, ) = struct.unpack('>I', header_store) 311 312 # The total size of the header. Each index entry is 16 bytes long. 313 header_size = 8 + 4 + 4 + header_index_value * 16 + header_store_value 314 315 # Headers end on an 8-byte boundary. Round out the extra data. 316 round_out = header_size % 8 317 if round_out != 0: 318 header_size = header_size + (8 - round_out) 319 320 return header_size
321 322 SHARED_TS = None
323 324 325 -def get_package_header(filename=None, file_obj=None, fd=None):
326 """ Loads the package header from a file / stream / file descriptor 327 Raises rpm.error if an error is found, or InvalidPacageError if package is 328 busted 329 """ 330 global SHARED_TS 331 # XXX Deal with exceptions better 332 if (filename is None and file_obj is None and fd is None): 333 raise ValueError("No parameters passed") 334 335 if filename is not None: 336 f = open(filename, 'rb') 337 elif file_obj is not None: 338 f = file_obj 339 f.seek(0, 0) 340 else: # fd is not None 341 f = None 342 343 if f is None: 344 os.lseek(fd, 0, 0) 345 file_desc = fd 346 else: 347 file_desc = f.fileno() 348 349 # don't try to use rpm.readHeaderFromFD() here, it brokes signatures 350 # see commit message 351 if not SHARED_TS: 352 SHARED_TS = rpm.ts() 353 SHARED_TS.setVSFlags(-1) 354 355 rpm.addMacro('_dbpath', '/var/cache/rhn/rhnpush-rpmdb') 356 try: 357 hdr = SHARED_TS.hdrFromFdno(file_desc) 358 rpm.delMacro('_dbpath') 359 except: 360 rpm.delMacro('_dbpath') 361 raise 362 363 if hdr is None: 364 raise InvalidPackageError 365 is_source = hdr[rpm.RPMTAG_SOURCEPACKAGE] 366 367 return RPM_Header(hdr, is_source)
368
369 370 -class MatchIterator:
371
372 - def __init__(self, tag_name=None, value=None):
373 # Query by name, by default 374 if not tag_name: 375 tag_name = "name" 376 377 # rpm 4.1 or later 378 self.ts = rpm.TransactionSet() 379 self.ts.setVSFlags(8) 380 381 m_args = (tag_name,) 382 if value: 383 m_args += (value,) 384 # pylint: disable=E1101 385 self.mi = self.ts.dbMatch(*m_args)
386
387 - def pattern(self, tag_name, mode, pattern):
388 self.mi.pattern(tag_name, mode, pattern)
389
390 - def next(self):
391 try: 392 hdr = usix_next(self.mi) 393 except StopIteration: 394 hdr = None 395 396 if hdr is None: 397 return None 398 is_source = hdr[rpm.RPMTAG_SOURCEPACKAGE] 399 return RPM_Header(hdr, is_source)
400
401 402 -def headerLoad(data):
403 hdr = rpm.headerLoad(data) 404 is_source = hdr[rpm.RPMTAG_SOURCEPACKAGE] 405 return RPM_Header(hdr, is_source)
406
407 408 -def labelCompare(t1, t2):
409 return rpm.labelCompare(t1, t2)
410
411 412 -def nvre_compare(t1, t2):
413 def build_evr(p): 414 evr = [p[3], p[1], p[2]] 415 evr = list(map(str, evr)) 416 if evr[0] == "": 417 evr[0] = None 418 return evr
419 if t1[0] != t2[0]: 420 raise ValueError("You should only compare packages with the same name") 421 evr1, evr2 = (build_evr(t1), build_evr(t2)) 422 return rpm.labelCompare(evr1, evr2) 423
424 425 -def hdrLabelCompare(hdr1, hdr2):
426 """ take two RPMs or headers and compare them for order """ 427 428 if hdr1['name'] == hdr2['name']: 429 hdr1 = [hdr1['epoch'] or None, hdr1['version'], hdr1['release']] 430 hdr2 = [hdr2['epoch'] or None, hdr2['version'], hdr2['release']] 431 if hdr1[0]: 432 hdr1[0] = str(hdr1[0]) 433 if hdr2[0]: 434 hdr2[0] = str(hdr2[0]) 435 return rpm.labelCompare(hdr1, hdr2) 436 elif hdr1['name'] < hdr2['name']: 437 return -1 438 return 1
439
440 441 -def sortRPMs(rpms):
442 """ Sorts a list of RPM files. They *must* exist. """ 443 444 assert isinstance(rpms, type([])) 445 446 # Build a list of (header, rpm) 447 helper = [(get_package_header(x), x) for x in rpms] 448 449 # Sort the list using the headers as a comparison 450 sort_cmp=lambda x, y: hdrLabelCompare(x[0], y[0]) 451 try: 452 helper.sort(sort_cmp) 453 except TypeError: 454 helper.sort(key=functools.cmp_to_key(sort_cmp)) 455 456 # Extract the rpm names now 457 return [x[1] for x in helper]
458
459 460 -def getInstalledHeader(rpmName):
461 """ quieries the RPM DB for a header matching rpmName. """ 462 463 matchiter = MatchIterator("name") 464 matchiter.pattern("name", rpm.RPMMIRE_STRCMP, rpmName) 465 return matchiter.next()
466 467 468 if __name__ == '__main__': 469 pass 470