def test_valid_elf(self): """Test that elf routines work on a small set of objects.""" arch = pkg.portable.get_isainfo()[0] for p in self.elf_paths: p = re.sub("__ARCH__", arch, p) self.debug("testing elf file {0}".format(p)) self.assertTrue(os.path.exists(p), "{0} does not exist".format(p)) self.assertEqual(elf.is_elf_object(p), True) elf.get_dynamic(p) elf.get_info(p)
def test_valid_elf(self): """Test that elf routines work on a small set of objects.""" arch = pkg.portable.get_isainfo()[0] for p in self.elf_paths: p = re.sub("__ARCH__", arch, p) self.debug("testing elf file %s" % p) self.assert_(os.path.exists(p)) self.assertEqual(elf.is_elf_object(p), True) elf.get_dynamic(p) elf.get_info(p)
def __elf_wrong_location_check(self, path): result = None ei = elf.get_info(path) bits = ei.get("bits") type = ei.get("type"); elems = os.path.dirname(path).split("/") if ("amd64" in elems) or ("sparcv9" in elems) or ("64" in elems): path64 = True else: path64 = False if ("i86" in elems) or ("sparcv7" in elems) or ("32" in elems): path32 = True else: path32 = False # ignore 64-bit executables in normal (non-32-bit-specific) # locations, that's ok now. if (type == "exe" and bits == 64 and path32 == False and path64 == False): return result if bits == 32 and path64: result = _("32-bit object '%s' in 64-bit path") elif bits == 64 and not path64: result = _("64-bit object '%s' in 32-bit path") return result
def process_elf_dependencies(action, pkg_vars, dyn_tok_conv, kernel_paths, **kwargs): """Produce the elf dependencies for the file delivered in the action provided. 'action' is the file action to analyze. 'pkg_vars' is the list of variants against which the package delivering the action was published. 'dyn_tok_conv' is the dictionary which maps the dynamic tokens, like $PLATFORM, to the values they should be expanded to. 'kernel_paths' contains the run paths which kernel modules should use. """ if not action.name == "file": return [], [], {} installed_path = action.attrs[action.key_attr] proto_file = action.attrs[PD_LOCAL_PATH] if not os.path.exists(proto_file): raise base.MissingFile(proto_file) if not elf.is_elf_object(proto_file): return [], [], {} try: ei = elf.get_info(proto_file) ed = elf.get_dynamic(proto_file) except elf.ElfError, e: raise BadElfFile(proto_file, e)
def __elf_aslr_check(self, path, engine): result = None ei = elf.get_info(path) type = ei.get("type"); if type != "exe": return result # get the ASLR tag string for this binary aslr_tag_process = subprocess.Popen( "/usr/bin/elfedit -r -e 'dyn:sunw_aslr' " + path, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # aslr_tag_string will get stdout; err will get stderr aslr_tag_string, err = aslr_tag_process.communicate() # No ASLR tag was found; everthing must be tagged if aslr_tag_process.returncode != 0: engine.error( _("'%s' is not tagged for aslr") % (path), msgid="%s%s.5" % (self.name, "001")) return result # look for "ENABLE" anywhere in the string; # warn about binaries which are not ASLR enabled if re.search("ENABLE", aslr_tag_string) is not None: return result engine.warning( _("'%s' does not have aslr enabled") % (path), msgid="%s%s.6" % (self.name, "001")) return result
def __elf_aslr_check(self, path, engine): result = None ei = elf.get_info(path) type = ei.get("type") if type != "exe": return result # get the ASLR tag string for this binary aslr_tag_process = subprocess.Popen( "/usr/bin/elfedit -r -e 'dyn:sunw_aslr' " + path, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # aslr_tag_string will get stdout; err will get stderr aslr_tag_string, err = aslr_tag_process.communicate() # No ASLR tag was found; everthing must be tagged if aslr_tag_process.returncode != 0: engine.error(_("'%s' is not tagged for aslr") % (path), msgid="%s%s.5" % (self.name, "001")) return result # look for "ENABLE" anywhere in the string; # warn about binaries which are not ASLR enabled if re.search("ENABLE", aslr_tag_string) is not None: return result engine.warning(_("'%s' does not have aslr enabled") % (path), msgid="%s%s.6" % (self.name, "001")) return result
def __elf_wrong_location_check(self, path, inspath): result = None ei = elf.get_info(path) bits = ei.get("bits") type = ei.get("type") elems = os.path.dirname(inspath).split("/") path32 = False path64 = False # Walk through the path elements backward and at the first # 32/64 bit specific element, flag it and break. for p in elems[::-1]: if (p in self.pathlist32): path32 = True break if (p in self.pathlist64): path64 = True break # ignore 64-bit executables in normal (non-32-bit-specific) # locations, that's ok now. if (type == "exe" and bits == 64 and path32 == False and path64 == False): return result if bits == 32 and path64: result = _("32-bit object '%%s' in 64-bit path(%s)" % elems) elif bits == 64 and not path64: result = _("64-bit object '%s' in 32-bit path") return result
def __elf_wrong_location_check(self, path): result = None ei = elf.get_info(path) bits = ei.get("bits") type = ei.get("type") elems = os.path.dirname(path).split("/") path64 = False for p in self.pathlist64: if (p in elems): path64 = True path32 = False for p in self.pathlist32: if (p in elems): path32 = True # ignore 64-bit executables in normal (non-32-bit-specific) # locations, that's ok now. if (type == "exe" and bits == 64 and path32 == False and path64 == False): return result if bits == 32 and path64: result = _("32-bit object '%s' in 64-bit path") elif bits == 64 and not path64: result = _("64-bit object '%s' in 32-bit path") return result
def __elf_runpath_check(self, path, engine): result = None list = [] ed = elf.get_dynamic(path) ei = elf.get_info(path) bits = ei.get("bits") for dir in ed.get("runpath", "").split(":"): if dir == None or dir == '': continue match = False for expr in self.runpath_re: if expr.match(dir): match = True break # The RUNPATH shouldn't contain any runtime linker # default paths (or the /64 equivalent link) if dir in [ '/lib', '/lib/64', '/lib/amd64', '/lib/sparcv9', '/usr/lib', '/usr/lib/64', '/usr/lib/amd64', '/usr/lib/sparcv9' ]: list.append(dir) if match == False: list.append(dir) if bits == 32: for expr in self.runpath_64_re: if expr.search(dir): engine.warning(_( "64-bit runpath in 32-bit binary, '%s' includes '%s'" ) % (path, dir), msgid="%s%s.3" % (self.name, "001")) else: match = False for expr in self.runpath_64_re: if expr.search(dir): match = True break if match == False: engine.warning( _("32-bit runpath in 64-bit binary, '%s' includes '%s'" ) % (path, dir), msgid="%s%s.3" % (self.name, "001")) if len(list) > 0: result = _("bad RUNPATH, '%%s' includes '%s'" % ":".join(list)) return result
def __elf_wrong_location_check(self, path): result = None ei = elf.get_info(path) bits = ei.get("bits") elems = os.path.dirname(path).split("/") if ("amd64" in elems) or ("sparcv9" in elems) or ("64" in elems): path64 = True else: path64 = False if bits == 32 and path64: result = _("32-bit object '%s' in 64-bit path") elif bits == 64 and not path64: result = _("64-bit object '%s' in 32-bit path") return result
def __elf_runpath_check(self, path, engine): result = None list = [] ed = elf.get_dynamic(path) ei = elf.get_info(path) bits = ei.get("bits") for dir in ed.get("runpath", "").split(":"): if dir == None or dir == '': continue match = False for expr in self.runpath_re: if expr.match(dir): match = True break if match == False: list.append(dir) if bits == 32: for expr in self.runpath_64_re: if expr.search(dir): engine.warning(_( "64-bit runpath in 32-bit binary, '%s' includes '%s'" ) % (path, dir), msgid="%s%s.3" % (self.name, "001")) else: match = False for expr in self.runpath_64_re: if expr.search(dir): match = True break if match == False: engine.warning( _("32-bit runpath in 64-bit binary, '%s' includes '%s'" ) % (path, dir), msgid="%s%s.3" % (self.name, "001")) if len(list) > 0: result = _("bad RUNPATH, '%%s' includes '%s'" % ":".join(list)) return result
def __elf_runpath_check(self, path, engine): result = None list = [] ed = elf.get_dynamic(path) ei = elf.get_info(path) bits = ei.get("bits") for dir in ed.get("runpath", "").split(":"): if dir == None or dir == '': continue match = False for expr in self.runpath_re: if expr.match(dir): match = True break if match == False: list.append(dir) if bits == 32: for expr in self.runpath_64_re: if expr.search(dir): engine.warning( _("64-bit runpath in 32-bit binary, '%s' includes '%s'") % (path, dir), msgid="%s%s.3" % (self.name, "001")) else: match = False for expr in self.runpath_64_re: if expr.search(dir): match = True break if match == False: engine.warning( _("32-bit runpath in 64-bit binary, '%s' includes '%s'") % (path, dir), msgid="%s%s.3" % (self.name, "001")) if len(list) > 0: result = _("bad RUNPATH, '%%s' includes '%s'" % ":".join(list)) return result
def __elf_aslr_check(self, path, engine, _pkglint_id): """Verify that given executable binary is ASLR tagged and enabled.""" elfinfo = elf.get_info(path) if elfinfo["type"] != "exe": return # get the ASLR tag string for this binary res = subprocess.run( ["/usr/bin/elfedit", "-r", "-e", "dyn:sunw_aslr", path], capture_output=True) # No ASLR tag was found; everything must be tagged if res.returncode != 0: engine.error(f"'{path}' is not tagged for aslr", msgid=f"{self.name}{_pkglint_id}.5") # look for "ENABLE" anywhere in the string; # warn about binaries which are not ASLR enabled elif b"ENABLE" not in res.stdout: engine.warning(f"'{path}' does not have aslr enabled", msgid=f"{self.name}{_pkglint_id}.6")
def process_elf_dependencies(action, proto_dir, pkg_vars, **kwargs): """Given a file action and proto directory, produce the elf dependencies for that file.""" if not action.name == "file": return [] installed_path = action.attrs[action.key_attr] proto_file = os.path.join(proto_dir, installed_path) if not os.path.exists(proto_file): raise base.MissingFile(proto_file) if not elf.is_elf_object(proto_file): return [] try: ei = elf.get_info(proto_file) ed = elf.get_dynamic(proto_file) except elf.ElfError, e: raise BadElfFile(proto_file, e)
def __elf_wrong_location_check(self, path, inspath): result = None ei = elf.get_info(path) bits = ei.get("bits") type = ei.get("type") elems = os.path.dirname(inspath).split("/") path32 = False path64 = False # Walk through the path elements backward and at the first # 32/64 bit specific element, flag it and break. for p in elems[::-1]: if (p in self.pathlist32): path32 = True break if (p in self.pathlist64): path64 = True break # The Xorg module directory is a hybrid case - everything # but the dri subdirectory is 64-bit if (os.path.dirname(inspath).startswith("usr/lib/xorg/modules") and not os.path.dirname(inspath) == "usr/lib/xorg/modules/dri"): path64 = True # ignore 64-bit executables in normal (non-32-bit-specific) # locations, that's ok now. if (type == "exe" and bits == 64 and path32 == False and path64 == False): return result if bits == 32 and path64: result = _("32-bit object '%%s' in 64-bit path(%s)" % elems) elif bits == 64 and not path64: result = _("64-bit object '%s' in 32-bit path") return result
def __elf_location_check(self, path, inspath, engine, _pkglint_id): """Make sure that file is placed within correct 32/64 directory.""" elfinfo = elf.get_info(path) bits = elfinfo["bits"] elftype = elfinfo["type"] elems = os.path.dirname(inspath).split("/") path32 = False path64 = False # Walk through the path elements backward and at the first # 32/64 bit specific element, flag it and break. for part in elems[::-1]: if part in self.pathlist32: path32 = True break if part in self.pathlist64: path64 = True break # The Xorg module directory is a hybrid case - everything # but the dri subdirectory is 64-bit dirname = os.path.dirname(inspath) if dirname.startswith("usr/lib/xorg/modules" ) and dirname != "usr/lib/xorg/modules/dri": path64 = True # ignore 64-bit executables in normal (non-32-bit-specific) # locations, that's ok now. if elftype == "exe" and bits == 64 and not path32 and not path64: return if bits == 32 and path64: engine.error(f"32-bit object '{inspath}' in 64-bit path({elems})", msgid=f"{self.name}{_pkglint_id}.2") elif bits == 64 and not path64: engine.error(f"64-bit object '{inspath}' in 32-bit path", msgid=f"{self.name}{_pkglint_id}.2")
fname, data = misc.get_data_digest(action.data(), length=size, return_content=True) action.hash = fname # Extract ELF information # XXX This needs to be modularized. if haveelf and data[:4] == "\x7fELF": elf_name = os.path.join(self.dir, ".temp-%s" % fname) elf_file = open(elf_name, "wb") elf_file.write(data) elf_file.close() try: elf_info = elf.get_info(elf_name) except elf.ElfError, e: raise TransactionContentError(e) try: elf_hash = elf.get_dynamic( elf_name)["hash"] action.attrs["elfhash"] = elf_hash except elf.ElfError: pass action.attrs["elfbits"] = str(elf_info["bits"]) action.attrs["elfarch"] = elf_info["arch"] os.unlink(elf_name) try: dst_path = self.rstore.file(fname)
fname, data = misc.get_data_digest(action.data(), length=size, return_content=True) action.hash = fname # Extract ELF information # XXX This needs to be modularized. if haveelf and data[:4] == "\x7fELF": elf_name = "%s/.temp" % self.dir elf_file = open(elf_name, "wb") elf_file.write(data) elf_file.close() try: elf_info = elf.get_info(elf_name) except elf.ElfError, e: raise TransactionContentError(e) try: elf_hash = elf.get_dynamic(elf_name)["hash"] action.attrs["elfhash"] = elf_hash except elf.ElfError: pass action.attrs["elfbits"] = str(elf_info["bits"]) action.attrs["elfarch"] = elf_info["arch"] os.unlink(elf_name) # # This check prevents entering into the depot store # a file which is already there in the store.
def __elf_runpath_check(self, path, engine, _pkglint_id): """Verify that RUNPATH of given binary is correct.""" runpath_list = [] dyninfo = elf.get_dynamic(path) elfinfo = elf.get_info(path) for runpath in dyninfo.get("runpath", "").split(":"): if not runpath: continue match = False for expr in self.runpath_re: if expr.match(runpath): match = True break if not match: runpath_list.append(runpath) # Make sure RUNPATH matches against a packaged path. # Don't check runpaths starting with $ORIGIN, which # is specially handled by the linker. elif not runpath.startswith("$ORIGIN/"): # Strip out leading and trailing '/' in the # runpath, since the reference paths don't start # with '/' and trailing '/' could cause mismatches. # Check first if there is an exact match, then check # if any reference path starts with this runpath # plus a trailing slash, since it may still be a link # to a directory that has no action because it uses # the default attributes. relative_dir = runpath.strip("/") if relative_dir not in self.ref_paths and not any( key.startswith(relative_dir + "/") for key in self.ref_paths): # If still no match, if the runpath contains # an embedded symlink, emit a warning; it may or may # not resolve to a legitimate path. # E.g., for usr/openwin/lib, usr/openwin->X11 and # usr/X11/lib are packaged, but usr/openwin/lib is not. # Otherwise, runpath is bad; add it to list. pdir = os.path.dirname(relative_dir) while pdir != "": if pdir in self.ref_paths and self.ref_paths[pdir][0][ 1].name == "link": engine.warning( f"runpath '{runpath}' in '{path}' not found in reference " f"paths but contains symlink at '{pdir}'", msgid=f"{self.name}{_pkglint_id}.3") break pdir = os.path.dirname(pdir) else: runpath_list.append(runpath) if elfinfo["bits"] == 32: for expr in self.runpath_64_re: if expr.search(runpath): engine.warning( f"64-bit runpath in 32-bit binary, '{path}' includes '{runpath}'", msgid=f"{self.name}{_pkglint_id}.3") else: for expr in self.runpath_64_re: if expr.search(runpath): break else: engine.warning( f"32-bit runpath in 64-bit binary, '{path}' includes '{runpath}'", msgid=f"{self.name}{_pkglint_id}.3") # handle all incorrect RUNPATHs in a single error if runpath_list: engine.error( f"bad RUNPATH, '{path}' includes '{':'.join(runpath_list)}'", msgid=f"{self.name}{_pkglint_id}.3")
def __get_elf_attrs(self, action, fname, size): """Helper function to get the ELF information.""" # This currently uses the presence of "elfhash" to indicate the # need for *any* content hashes to be added. This will work as # expected until elfhash is no longer generated by default, and # then this logic will need to be updated accordingly. need_elf_info = False need_elfhash = False bufsz = misc.PKG_FILE_BUFSIZ if bufsz > size: bufsz = size f = action.data() magic = f.read(4) if haveelf and magic == b"\x7fELF": need_elf_info = ("elfarch" not in action.attrs or "elfbits" not in action.attrs) need_elfhash = "elfhash" not in action.attrs if not need_elf_info or not need_elfhash: f.close() return misc.EmptyDict elf_name = os.path.join(self._tmpdir, ".temp-{0}".format(fname)) with open(elf_name, "wb") as elf_file: elf_file.write(magic) while True: data = f.read(bufsz) if not data: break elf_file.write(data) f.close() attrs = {} if need_elf_info: try: elf_info = elf.get_info(elf_name) except elf.ElfError as e: raise TransactionError(e) attrs["elfbits"] = str(elf_info["bits"]) attrs["elfarch"] = elf_info["arch"] # Check which content checksums to compute and add to the action get_elfhash = (need_elfhash and "elfhash" in digest.DEFAULT_GELF_HASH_ATTRS) get_sha256 = (need_elfhash and not digest.sha512_supported and "pkg.content-hash" in digest.DEFAULT_GELF_HASH_ATTRS) get_sha512t_256 = (need_elfhash and digest.sha512_supported and "pkg.content-hash" in digest.DEFAULT_GELF_HASH_ATTRS) if get_elfhash or get_sha256 or get_sha512t_256: try: attrs.update( elf.get_hashes(elf_name, elfhash=get_elfhash, sha256=get_sha256, sha512t_256=get_sha512t_256)) except elf.ElfError: pass os.unlink(elf_name) return attrs
def __elf_runpath_check(self, path, engine): result = None list = [] ed = elf.get_dynamic(path) ei = elf.get_info(path) bits = ei.get("bits") for dir in ed.get("runpath", "").split(":"): if dir == None or dir == '': continue match = False for expr in self.runpath_re: if expr.match(dir): match = True break if match == False: list.append(dir) # Make sure RUNPATH matches against a packaged path. # Don't check runpaths starting with $ORIGIN, which # is specially handled by the linker. elif not dir.startswith('$ORIGIN/'): # Strip out leading and trailing '/' in the # runpath, since the reference paths don't start # with '/' and trailing '/' could cause mismatches. # Check first if there is an exact match, then check # if any reference path starts with this runpath # plus a trailing slash, since it may still be a link # to a directory that has no action because it uses # the default attributes. relative_dir = dir.strip('/') if not relative_dir in self.ref_paths and \ not any(key.startswith(relative_dir + '/') for key in self.ref_paths): # If still no match, if the runpath contains # an embedded symlink, emit a warning; it may or may # not resolve to a legitimate path. # E.g., for usr/openwin/lib, usr/openwin->X11 and # usr/X11/lib are packaged, but usr/openwin/lib is not. # Otherwise, runpath is bad; add it to list. embedded_link = False pdir = os.path.dirname(relative_dir) while pdir != '': if (pdir in self.ref_paths and self.ref_paths[pdir][0][1].name == "link"): embedded_link = True engine.warning(_( "runpath '%s' in '%s' not found in reference paths but contains symlink at '%s'" ) % (dir, path, pdir), msgid="%s%s.3" % (self.name, "001")) break pdir = os.path.dirname(pdir) if not embedded_link: list.append(dir) if bits == 32: for expr in self.runpath_64_re: if expr.search(dir): engine.warning(_( "64-bit runpath in 32-bit binary, '%s' includes '%s'" ) % (path, dir), msgid="%s%s.3" % (self.name, "001")) else: match = False for expr in self.runpath_64_re: if expr.search(dir): match = True break if match == False: engine.warning( _("32-bit runpath in 64-bit binary, '%s' includes '%s'" ) % (path, dir), msgid="%s%s.3" % (self.name, "001")) if len(list) > 0: result = _("bad RUNPATH, '%%s' includes '%s'" % ":".join(list)) return result
def process_elf_dependencies(action, pkg_vars, dyn_tok_conv, run_paths, **kwargs): """Produce the elf dependencies for the file delivered in the action provided. 'action' is the file action to analyze. 'pkg_vars' is the list of variants against which the package delivering the action was published. 'dyn_tok_conv' is the dictionary which maps the dynamic tokens, like $PLATFORM, to the values they should be expanded to. 'run_paths' contains the run paths which elf binaries should use. """ if not action.name == "file": return [], [], {} installed_path = action.attrs[action.key_attr] proto_file = action.attrs[PD_LOCAL_PATH] if not os.path.exists(proto_file): raise base.MissingFile(proto_file) if not elf.is_elf_object(proto_file): return [], [], {} try: ei = elf.get_info(proto_file) ed = elf.get_dynamic(proto_file) except elf.ElfError as e: raise BadElfFile(proto_file, e) deps = [ d[0] for d in ed.get("deps", []) ] rp = ed.get("runpath", "").split(":") if len(rp) == 1 and rp[0] == "": rp = [] dyn_tok_conv["$ORIGIN"] = [os.path.join("/", os.path.dirname(installed_path))] kernel64 = None # For kernel modules, default path resolution is /platform/<platform>, # /kernel, /usr/kernel. But how do we know what <platform> would be for # a given module? Does it do fallbacks to, say, sun4u? if installed_path.startswith("kernel") or \ installed_path.startswith("usr/kernel") or \ (installed_path.startswith("platform") and \ installed_path.split("/")[2] == "kernel"): if rp and (len(rp) > 1 or not re.match(r'^/usr/gcc/\d/lib$', rp[0])): raise RuntimeError("RUNPATH set for kernel module " "({0}): {1}".format(installed_path, rp)) # Add this platform to the search path. if installed_path.startswith("platform"): rp.append("/platform/{0}/kernel".format( installed_path.split("/")[1])) else: for p in dyn_tok_conv.get("$PLATFORM", []): rp.append("/platform/{0}/kernel".format(p)) # Default kernel search path rp.extend(["/kernel", "/usr/kernel"]) # What subdirectory should we look in for 64-bit kernel modules? if ei["bits"] == 64: if ei["arch"] == "i386": kernel64 = "amd64" elif ei["arch"] == "sparc": kernel64 = "sparcv9" else: raise RuntimeError("Unknown arch:{0}".format( ei["arch"])) else: for p in default_run_paths: if ei["bits"] == 64: p += "/64" if p not in rp: rp.append(p) elist = [] if run_paths: # add our detected runpaths into the user-supplied one (if any) rp = base.insert_default_runpath(rp, run_paths) rp, errs = expand_variables(rp, dyn_tok_conv) elist.extend([ UnsupportedDynamicToken(proto_file, installed_path, p, tok) for p, tok in errs ]) res = [] for d in deps: pn, fn = os.path.split(d) pathlist = [] for p in rp: if kernel64: # Find 64-bit modules the way krtld does. # XXX We don't resolve dependencies found in # /platform, since we don't know where under # /platform to look. deppath = \ os.path.join(p, pn, kernel64, fn).lstrip( os.path.sep) else: deppath = os.path.join(p, d).lstrip(os.path.sep) # deppath includes filename; remove that. head, tail = os.path.split(deppath) if head: pathlist.append(head) res.append(ElfDependency(action, fn, pathlist, pkg_vars, action.attrs[PD_PROTO_DIR])) del dyn_tok_conv["$ORIGIN"] return res, elist, {}
def add_content(self, action): """Adds the content of the provided action (if applicable) to the Transaction.""" # Perform additional publication-time validation of actions # before further processing is done. try: action.validate() except actions.ActionError as e: raise TransactionOperationError(e) if self.append_trans and action.name != "signature": raise TransactionOperationError(non_sig=True) size = int(action.attrs.get("pkg.size", 0)) if action.has_payload and size <= 0: # XXX hack for empty files action.data = lambda: open(os.devnull, "rb") if action.data is not None: # get all hashes for this action hashes, data = misc.get_data_digest(action.data(), length=size, return_content=True, hash_attrs=digest.LEGACY_HASH_ATTRS, hash_algs=digest.HASH_ALGS) # set the hash member for backwards compatibility and # remove it from the dictionary action.hash = hashes.pop("hash", None) action.attrs.update(hashes) # now set the hash value that will be used for storing # the file in the repository. hash_attr, hash_val, hash_func = \ digest.get_least_preferred_hash(action) fname = hash_val # Extract ELF information if not already provided. # XXX This needs to be modularized. if haveelf and data[:4] == b"\x7fELF" and ( "elfarch" not in action.attrs or "elfbits" not in action.attrs or "elfhash" not in action.attrs): elf_name = os.path.join(self.dir, ".temp-{0}".format(fname)) elf_file = open(elf_name, "wb") elf_file.write(data) elf_file.close() try: elf_info = elf.get_info(elf_name) except elf.ElfError as e: raise TransactionContentError(e) try: # Check which content checksums to # compute and add to the action elf1 = "elfhash" if elf1 in \ digest.LEGACY_CONTENT_HASH_ATTRS: get_sha1 = True else: get_sha1 = False hashes = elf.get_hashes(elf_name, elfhash=get_sha1) if get_sha1: action.attrs[elf1] = hashes[elf1] except elf.ElfError: pass action.attrs["elfbits"] = str(elf_info["bits"]) action.attrs["elfarch"] = elf_info["arch"] os.unlink(elf_name) try: dst_path = self.rstore.file(fname) except Exception as e: # The specific exception can't be named here due # to the cyclic dependency between this class # and the repository class. if getattr(e, "data", "") != fname: raise dst_path = None csize, chashes = misc.compute_compressed_attrs( fname, dst_path, data, size, self.dir) for attr in chashes: action.attrs[attr] = chashes[attr] action.attrs["pkg.csize"] = csize self.remaining_payload_cnt = \ len(action.attrs.get("chain.sizes", "").split()) # Do some sanity checking on packages marked or being marked # obsolete or renamed. if action.name == "set" and \ action.attrs["name"] == "pkg.obsolete" and \ action.attrs["value"] == "true": self.obsolete = True if self.types_found.difference( set(("set", "signature"))): raise TransactionOperationError(_("An obsolete " "package cannot contain actions other than " "'set' and 'signature'.")) elif action.name == "set" and \ action.attrs["name"] == "pkg.renamed" and \ action.attrs["value"] == "true": self.renamed = True if self.types_found.difference( set(("depend", "set", "signature"))): raise TransactionOperationError(_("A renamed " "package cannot contain actions other than " "'set', 'depend', and 'signature'.")) if not self.has_reqdeps and action.name == "depend" and \ action.attrs["type"] == "require": self.has_reqdeps = True if self.obsolete and self.renamed: # Reset either obsolete or renamed, depending on which # action this was. if action.attrs["name"] == "pkg.obsolete": self.obsolete = False else: self.renamed = False raise TransactionOperationError(_("A package may not " " be marked for both obsoletion and renaming.")) elif self.obsolete and action.name not in ("set", "signature"): raise TransactionOperationError(_("A '{type}' action " "cannot be present in an obsolete package: " "{action}").format( type=action.name, action=action)) elif self.renamed and action.name not in \ ("depend", "set", "signature"): raise TransactionOperationError(_("A '{type}' action " "cannot be present in a renamed package: " "{action}").format( type=action.name, action=action)) # Now that the action is known to be sane, we can add it to the # manifest. tfpath = os.path.join(self.dir, "manifest") tfile = open(tfpath, "a+") print(action, file=tfile) tfile.close() self.types_found.add(action.name)
def add_content(self, action): """Adds the content of the provided action (if applicable) to the Transaction.""" size = int(action.attrs.get("pkg.size", 0)) if action.name in ("file", "license") and size <= 0: # XXX hack for empty files action.data = lambda: open(os.devnull, "rb") if action.data is not None: bufsz = 64 * 1024 fname, data = misc.get_data_digest(action.data(), length=size, return_content=True) action.hash = fname # Extract ELF information # XXX This needs to be modularized. if haveelf and data[:4] == "\x7fELF": elf_name = "%s/.temp" % self.dir elf_file = open(elf_name, "wb") elf_file.write(data) elf_file.close() try: elf_info = elf.get_info(elf_name) except elf.ElfError, e: raise TransactionContentError(e) try: elf_hash = elf.get_dynamic( elf_name)["hash"] action.attrs["elfhash"] = elf_hash except elf.ElfError: pass action.attrs["elfbits"] = str(elf_info["bits"]) action.attrs["elfarch"] = elf_info["arch"] os.unlink(elf_name) # # This check prevents entering into the depot store # a file which is already there in the store. # This takes CPU load off the depot on large imports # of mostly-the-same stuff. And in general it saves # disk bandwidth, and on ZFS in particular it saves # us space in differential snapshots. We also need # to check that the destination is in the same # compression format as the source, as we must have # properly formed files for chash/csize properties # to work right. # fpath = misc.hash_file_name(fname) dst_path = "%s/%s" % (self.cfg.file_root, fpath) fileneeded = True if os.path.exists(dst_path): if PkgGzipFile.test_is_pkggzipfile(dst_path): fileneeded = False opath = dst_path if fileneeded: opath = os.path.join(self.dir, fname) ofile = PkgGzipFile(opath, "wb") nbuf = size / bufsz for n in range(0, nbuf): l = n * bufsz h = (n + 1) * bufsz ofile.write(data[l:h]) m = nbuf * bufsz ofile.write(data[m:]) ofile.close() data = None # Now that the file has been compressed, determine its # size and store that as an attribute in the manifest # for the file. fs = os.stat(opath) action.attrs["pkg.csize"] = str(fs.st_size) # Compute the SHA hash of the compressed file. # Store this as the chash attribute of the file's # action. In order for this to work correctly, we # have to use the PkgGzipFile class. It omits # filename and timestamp information from the gzip # header, allowing us to generate deterministic # hashes for different files with identical content. cfile = open(opath, "rb") chash = sha.new() while True: cdata = cfile.read(bufsz) if cdata == "": break chash.update(cdata) cfile.close() action.attrs["chash"] = chash.hexdigest() cdata = None