1# drpm.py 2# Delta RPM support 3# 4# Copyright (C) 2012-2016 Red Hat, Inc. 5# 6# This copyrighted material is made available to anyone wishing to use, 7# modify, copy, or redistribute it subject to the terms and conditions of 8# the GNU General Public License v.2, or (at your option) any later version. 9# This program is distributed in the hope that it will be useful, but WITHOUT 10# ANY WARRANTY expressed or implied, including the implied warranties of 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 12# Public License for more details. You should have received a copy of the 13# GNU General Public License along with this program; if not, write to the 14# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 15# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the 16# source code or documentation are not subject to the GNU General Public 17# License and may only be used or replicated with the express permission of 18# Red Hat, Inc. 19# 20 21from __future__ import absolute_import 22from __future__ import unicode_literals 23from binascii import hexlify 24from dnf.yum.misc import unlink_f 25from dnf.i18n import _ 26 27import dnf.callback 28import dnf.logging 29import dnf.repo 30import hawkey 31import logging 32import libdnf.repo 33import os 34 35APPLYDELTA = '/usr/bin/applydeltarpm' 36 37logger = logging.getLogger("dnf") 38 39 40class DeltaPayload(dnf.repo.PackagePayload): 41 def __init__(self, delta_info, delta, pkg, progress): 42 super(DeltaPayload, self).__init__(pkg, progress) 43 self.delta_info = delta_info 44 self.delta = delta 45 46 def __str__(self): 47 return os.path.basename(self.delta.location) 48 49 def _end_cb(self, cbdata, lr_status, msg): 50 super(DeltaPayload, self)._end_cb(cbdata, lr_status, msg) 51 if lr_status != libdnf.repo.PackageTargetCB.TransferStatus_ERROR: 52 self.delta_info.enqueue(self) 53 54 def _target_params(self): 55 delta = self.delta 56 ctype, csum = delta.chksum 57 ctype = hawkey.chksum_name(ctype) 58 chksum = hexlify(csum).decode() 59 60 ctype_code = libdnf.repo.PackageTarget.checksumType(ctype) 61 if ctype_code == libdnf.repo.PackageTarget.ChecksumType_UNKNOWN: 62 logger.warning(_("unsupported checksum type: %s"), ctype) 63 64 return { 65 'relative_url' : delta.location, 66 'checksum_type' : ctype_code, 67 'checksum' : chksum, 68 'expectedsize' : delta.downloadsize, 69 'base_url' : delta.baseurl, 70 } 71 72 @property 73 def download_size(self): 74 return self.delta.downloadsize 75 76 @property 77 def _full_size(self): 78 return self.pkg.downloadsize 79 80 def localPkg(self): 81 location = self.delta.location 82 return os.path.join(self.pkg.repo.pkgdir, os.path.basename(location)) 83 84 85class DeltaInfo(object): 86 def __init__(self, query, progress, deltarpm_percentage=None): 87 '''A delta lookup and rebuild context 88 query -- installed packages to use when looking up deltas 89 progress -- progress obj to display finished delta rebuilds 90 ''' 91 self.deltarpm_installed = False 92 if os.access(APPLYDELTA, os.X_OK): 93 self.deltarpm_installed = True 94 try: 95 self.deltarpm_jobs = os.sysconf('SC_NPROCESSORS_ONLN') 96 except (TypeError, ValueError): 97 self.deltarpm_jobs = 4 98 if deltarpm_percentage is None: 99 self.deltarpm_percentage = dnf.conf.Conf().deltarpm_percentage 100 else: 101 self.deltarpm_percentage = deltarpm_percentage 102 self.query = query 103 self.progress = progress 104 105 self.queue = [] 106 self.jobs = {} 107 self.err = {} 108 109 def delta_factory(self, po, progress): 110 '''Turn a po to Delta RPM po, if possible''' 111 if not self.deltarpm_installed: 112 # deltarpm is not installed 113 return None 114 if not po.repo.deltarpm or not self.deltarpm_percentage: 115 # drpm disabled 116 return None 117 if po._is_local_pkg(): 118 # drpm disabled for local 119 return None 120 if os.path.exists(po.localPkg()): 121 # already there 122 return None 123 124 best = po._size * self.deltarpm_percentage / 100 125 best_delta = None 126 for ipo in self.query.filter(name=po.name, arch=po.arch): 127 delta = po.get_delta_from_evr(ipo.evr) 128 if delta and delta.downloadsize < best: 129 best = delta.downloadsize 130 best_delta = delta 131 if best_delta: 132 return DeltaPayload(self, best_delta, po, progress) 133 return None 134 135 def job_done(self, pid, code): 136 # handle a finished delta rebuild 137 logger.log(dnf.logging.SUBDEBUG, 'drpm: %d: return code: %d, %d', pid, 138 code >> 8, code & 0xff) 139 140 pload = self.jobs.pop(pid) 141 pkg = pload.pkg 142 if code != 0: 143 unlink_f(pload.pkg.localPkg()) 144 self.err[pkg] = [_('Delta RPM rebuild failed')] 145 elif not pload.pkg.verifyLocalPkg(): 146 self.err[pkg] = [_('Checksum of the delta-rebuilt RPM failed')] 147 else: 148 os.unlink(pload.localPkg()) 149 self.progress.end(pload, dnf.callback.STATUS_DRPM, _('done')) 150 151 def start_job(self, pload): 152 # spawn a delta rebuild job 153 spawn_args = [APPLYDELTA, APPLYDELTA, 154 '-a', pload.pkg.arch, 155 pload.localPkg(), pload.pkg.localPkg()] 156 pid = os.spawnl(os.P_NOWAIT, *spawn_args) 157 logger.log(dnf.logging.SUBDEBUG, 'drpm: spawned %d: %s', pid, 158 ' '.join(spawn_args[1:])) 159 self.jobs[pid] = pload 160 161 def enqueue(self, pload): 162 # process finished jobs, start new ones 163 while self.jobs: 164 pid, code = os.waitpid(-1, os.WNOHANG) 165 if not pid: 166 break 167 self.job_done(pid, code) 168 self.queue.append(pload) 169 while len(self.jobs) < self.deltarpm_jobs: 170 self.start_job(self.queue.pop(0)) 171 if not self.queue: 172 break 173 174 def wait(self): 175 '''Wait until all jobs have finished''' 176 while self.jobs: 177 pid, code = os.wait() 178 self.job_done(pid, code) 179 if self.queue: 180 self.start_job(self.queue.pop(0)) 181