def needsdata(self, orig, pkgplan): if self.replace_required: return True # import goes here to prevent circular import from pkg.client.imageconfig import CONTENT_UPDATE_POLICY use_content_hash = orig and pkgplan.image.cfg.get_policy_str( CONTENT_UPDATE_POLICY) == "when-required" # If content update policy allows it, check for a common # preferred content hash. if use_content_hash: content_hash_attr, content_hash_val, \ orig_content_hash_val, content_hash_func = \ digest.get_common_preferred_hash( self, orig, hash_type=digest.HASH_GELF) hash_attr, hash_val, orig_hash_val, hash_func = \ digest.get_common_preferred_hash(self, orig) if not orig: changed_hash = True elif orig and (orig_hash_val is None or hash_val is None): # we have no common hash so we have to treat this as a # changed action changed_hash = True else: changed_hash = hash_val != orig_hash_val if (changed_hash and (not use_content_hash or content_hash_val != orig_content_hash_val)): if ("preserve" not in self.attrs or not pkgplan.origin_fmri): return True elif orig: # It's possible that the file content hasn't changed # for an upgrade case, but the file is missing. This # ensures that for cases where the mode or some other # attribute of the file has changed that the file will # be installed. path = self.get_installed_path(pkgplan.image.get_root()) if not os.path.isfile(path): return True pres_type = self._check_preserve(orig, pkgplan) if pres_type not in (None, True, "abandon"): # Preserved files only need data if they're being # changed (e.g. "renameold", etc.). return True return False
def _check_preserve(self, orig, pkgplan, orig_path=None): """Return the type of preservation needed for this action. Returns None if preservation is not defined by the action. Returns False if it is, but no preservation is necessary. Returns True for the normal preservation form. Returns one of the strings 'renameold', 'renameold.update', 'renamenew', 'legacy', or 'abandon' for each of the respective forms of preservation. """ # If the logic in this function ever changes, all callers will # need to be updated to reflect how they interpret return # values. try: pres_type = self.attrs["preserve"] except KeyError: return # Should ultimately be conditioned on file type if "elfhash" in self.attrs: # Don't allow preserve logic to be applied to elf files; # if we ever stop tagging elf binaries with this # attribute, this will need to be updated. return if pres_type == "abandon": return pres_type final_path = self.get_installed_path(pkgplan.image.get_root()) # 'legacy' preservation is very different than other forms of # preservation as it doesn't account for the on-disk state of # the action's payload. if pres_type == "legacy": if not orig: # This is an initial install or a repair, so # there's nothing to deliver. return True return pres_type # If action has been marked with a preserve attribute, the # hash of the preserved file has changed between versions, # and the package being installed is older than the package # that was installed, and the version on disk is different # than the installed package's original version, then preserve # the installed file by renaming it. # # If pkgplan.origin_fmri isn't set, but there is an orig action, # then this file is moving between packages and it can't be # a downgrade since that isn't allowed across rename or obsolete # boundaries. is_file = os.path.isfile(final_path) # 'install-only' preservation has very specific semantics as # well; if there's an 'orig' or this is an initial install and # the file exists, we should not modify the file content. if pres_type == "install-only": if orig or is_file: return True return False changed_hash = False if orig: # We must use the same hash algorithm when comparing old # and new actions. Look for the most-preferred common # hash between old and new. Since the two actions may # not share a common hash (in which case, we get a tuple # of 'None' objects) we also need to know the preferred # hash to use when examining the old action on its own. common_hash_attr, common_hash_val, \ common_orig_hash_val, common_hash_func = \ digest.get_common_preferred_hash(self, orig) hattr, orig_hash_val, orig_hash_func = \ digest.get_preferred_hash(orig) if common_orig_hash_val and common_hash_val: changed_hash = common_hash_val != common_orig_hash_val else: # we don't have a common hash, so we must treat # this as a changed action changed_hash = True if pkgplan.destination_fmri and \ changed_hash and \ pkgplan.origin_fmri and \ pkgplan.destination_fmri.version < pkgplan.origin_fmri.version: # Installed, preserved file is for a package # newer than what will be installed. So check if # the version on disk is different than what # was originally delivered, and if so, preserve # it. if not is_file: return False preserve_version = self.__check_preserve_version(orig) if not preserve_version: return False ihash, cdata = misc.get_data_digest(final_path, hash_func=orig_hash_func) if ihash != orig_hash_val: return preserve_version return True if (orig and orig_path): # Comparison will be based on a file being moved. is_file = os.path.isfile(orig_path) # If the action has been marked with a preserve attribute, and # the file exists and has a content hash different from what the # system expected it to be, then we preserve the original file # in some way, depending on the value of preserve. if is_file: # if we had an action installed, then we know what hash # function was used to compute it's hash attribute. if orig: if not orig_path: orig_path = final_path chash, cdata = misc.get_data_digest(orig_path, hash_func=orig_hash_func) if not orig or chash != orig_hash_val: if pres_type in ("renameold", "renamenew"): return pres_type return True elif not changed_hash and chash == orig_hash_val: # If packaged content has not changed since last # version and on-disk content matches the last # version, preserve on-disk file. return True return False
def needsdata(self, orig, pkgplan): if self.replace_required: return True # import goes here to prevent circular import from pkg.client.imageconfig import CONTENT_UPDATE_POLICY use_content_hash = pkgplan.image.cfg.get_policy_str( CONTENT_UPDATE_POLICY) == "when-required" # If content update policy allows it, check for the presence of # a simple elfhash attribute, and if that's present, look for # the common preferred elfhash. For now, this is sufficient, # but when additional content types are supported (and we stop # publishing SHA-1 hashes) more work will be needed to compute # 'bothelf'. bothelf = use_content_hash and orig and \ "elfhash" in orig.attrs and "elfhash" in self.attrs if bothelf: common_elf_attr, common_elfhash, common_orig_elfhash, \ common_elf_func = \ digest.get_common_preferred_hash(self, orig, hash_type=digest.CONTENT_HASH) common_hash_attr, common_hash_val, \ common_orig_hash_val, common_hash_func = \ digest.get_common_preferred_hash(self, orig) if not orig: changed_hash = True elif orig and (common_orig_hash_val is None or common_hash_val is None): # we have no common hash so we have to treat this as a # changed action changed_hash = True else: changed_hash = common_hash_val != common_orig_hash_val if (changed_hash and (not bothelf or common_orig_elfhash != common_elfhash)): if ("preserve" not in self.attrs or not pkgplan.origin_fmri or (pkgplan.destination_fmri.version < pkgplan.origin_fmri.version)): return True elif orig: # It's possible that the file content hasn't changed # for an upgrade case, but the file is missing. This # ensures that for cases where the mode or some other # attribute of the file has changed that the file will # be installed. path = self.get_installed_path(pkgplan.image.get_root()) if not os.path.isfile(path): return True pres_type = self._check_preserve(orig, pkgplan) if pres_type not in (None, True, "abandon"): # Preserved files only need data if they're being # changed (e.g. "renameold", etc.). return True return False