def test_skip_object_files(self): open(os.path.join(self.fake_elf.root_path, "object_file.o"), "w").close() elf_files = elf.get_elf_files(self.fake_elf.root_path, {"object_file.o"}) self.assertThat(elf_files, Equals(set()))
def test_symlinks(self): symlinked_path = os.path.join(self.fake_elf.root_path, "symlinked") os.symlink("/bin/dash", symlinked_path) elf_files = elf.get_elf_files(self.fake_elf.root_path, {"symlinked"}) self.assertThat(elf_files, Equals(set()))
def test_non_elf_files(self): with open(os.path.join(self.fake_elf.root_path, "non-elf"), "wb") as f: # A bz2 header f.write(b"\x42\x5a\x68") elf_files = elf.get_elf_files(self.fake_elf.root_path, {"non-elf"}) self.assertThat(elf_files, Equals(set()))
def test_skip_object_files(self): open(os.path.join(self.fake_elf.root_path, 'object_file.o'), 'w').close() elf_files = elf.get_elf_files(self.fake_elf.root_path, {'object_file.o'}) self.assertThat(elf_files, Equals(set()))
def test_get_elf_files(self): elf_files = elf.get_elf_files(self.fake_elf.root_path, {'fake_elf-2.23'}) self.assertThat(len(elf_files), Equals(1)) elf_file = set(elf_files).pop() self.assertThat(elf_file.interp, Equals('/lib64/ld-linux-x86-64.so.2'))
def test_symlinks(self): symlinked_path = os.path.join(self.fake_elf.root_path, 'symlinked') os.symlink('/bin/dash', symlinked_path) elf_files = elf.get_elf_files(self.fake_elf.root_path, {'symlinked'}) self.assertThat(elf_files, Equals(set()))
def test_non_elf_files(self): with open(os.path.join(self.fake_elf.root_path, 'non-elf'), 'wb') as f: # A bz2 header f.write(b'\x42\x5a\x68') elf_files = elf.get_elf_files(self.fake_elf.root_path, {'non-elf'}) self.assertThat(elf_files, Equals(set()))
def test_get_elf_files(self): elf_files = elf.get_elf_files(self.fake_elf.root_path, {'fake_elf-2.23'}) self.assertThat(len(elf_files), Equals(1)) elf_file = set(elf_files).pop() self.assertThat(elf_file.is_executable(), Equals(True))
def test_get_elf_files_to_patch(self): elf_files = elf.get_elf_files( self.fake_elf.root_path, {'libc.so.6', 'libssl.so.1.0.0', 'fake_elf-shared-object', 'fake_elf-2.26'}) to_patch = elf.get_elf_files_to_patch(elf_files) self.assertThat({os.path.basename(e.path) for e in to_patch}, Equals({'fake_elf-shared-object', 'fake_elf-2.26'}))
def test_get_elf_is_library(self): elf_files = elf.get_elf_files(self.fake_elf.root_path, {'fake_elf-shared-object'}) self.assertThat(len(elf_files), Equals(1)) elf_file = set(elf_files).pop() self.assertThat(elf_file.is_executable(), Equals(False)) self.assertThat(elf_file.is_shared_object(), Equals(True))
def test_skip_object_files(self): open(os.path.join(self.workdir, 'object_file.o'), 'w').close() elf_files = elf.get_elf_files(self.workdir, {'object_file.o'}) self.assertFalse(self.ms_mock.file.called, 'Expected object file to be skipped') self.assertThat(elf_files, Equals(set()))
def test_symlinks(self): symlinked_path = os.path.join(self.workdir, 'symlinked') os.symlink('/bin/dash', symlinked_path) elf_files = elf.get_elf_files(self.workdir, {'symlinked'}) self.assertFalse(self.ms_mock.file.called, 'magic is not needed for symlinks') self.assertThat(elf_files, Equals(set()))
def prime(self, force=False) -> None: self.makedirs() self.notify_part_progress('Priming') snap_files, snap_dirs = self.migratable_fileset_for('prime') _migrate_files(snap_files, snap_dirs, self.stagedir, self.primedir) elf_files = elf.get_elf_files(self.primedir, snap_files) all_dependencies = set() # TODO: base snap support core_path = common.get_core_path() for elf_file in elf_files: all_dependencies.update( elf_file.load_dependencies(root_path=self.primedir, core_base_path=core_path)) # Split the necessary dependencies into their corresponding location. # We'll both migrate and track the system dependencies, but we'll only # track the part and staged dependencies, since they should have # already been primed by other means, and migrating them again could # potentially override the `stage` or `snap` filtering. (in_part, staged, primed, system) = _split_dependencies( all_dependencies, self.installdir, self.stagedir, self.primedir) part_dependency_paths = {os.path.dirname(d) for d in in_part} staged_dependency_paths = {os.path.dirname(d) for d in staged} dependency_paths = part_dependency_paths | staged_dependency_paths if not self._build_attributes.no_system_libraries(): system_dependency_paths = {os.path.dirname(d) for d in system} dependency_paths.update(system_dependency_paths) if system: # Lots of dependencies are linked with a symlink, so we need to # make sure we follow those symlinks when we migrate the # dependencies. _migrate_files(system, system_dependency_paths, '/', self.primedir, follow_symlinks=True) formatted_system = '\n'.join(sorted(system)) logger.warning( 'Files from the build host were migrated into the snap to ' 'satisfy dependencies that would otherwise not be met. ' 'This feature will be removed in a future release. If ' 'these libraries are needed in the final snap, ensure ' 'that the following are either satisfied by a ' 'stage-packages entry or through a part:\n{}'.format( formatted_system)) if self._confinement == 'classic': dynamic_linker = self._project_options.get_core_dynamic_linker() elf_patcher = elf.Patcher(dynamic_linker=dynamic_linker, root_path=self.primedir) for elf_file in elf_files: elf_patcher.patch(elf_file=elf_file) self.mark_prime_done(snap_files, snap_dirs, dependency_paths)
def test_non_elf_files(self): non_elf_path = os.path.join(self.workdir, 'non-elf') open(non_elf_path, 'w').close() non_elf_path_b = non_elf_path.encode(sys.getfilesystemencoding()) self.ms_mock.file.return_value = 'JPEG image data, Exif standard: ...' elf_files = elf.get_elf_files(self.workdir, {'non-elf'}) self.ms_mock.file.assert_called_once_with(non_elf_path_b) self.assertThat(elf_files, Equals(set()))
def test_get_elf_files(self): linked_elf_path = os.path.join(self.workdir, 'linked') open(linked_elf_path, 'w').close() linked_elf_path_b = linked_elf_path.encode(sys.getfilesystemencoding()) elf_files = elf.get_elf_files(self.workdir, {'linked'}) self.assertThat(len(elf_files), Equals(1)) self.ms_mock.file.assert_called_once_with(linked_elf_path_b) elf_file = set(elf_files).pop() self.assertThat(elf_file.path, Equals(linked_elf_path)) self.assertThat(elf_file.is_executable, Equals(True))
def test_no_find_dependencies_of_non_dynamically_linked(self): statically_linked_elf_path = os.path.join(self.workdir, 'statically-linked') open(statically_linked_elf_path, 'w').close() statically_linked_elf_path_b = statically_linked_elf_path.encode( sys.getfilesystemencoding()) self.ms_mock.file.return_value = ( 'ELF 64-bit LSB executable, x86-64, version 1 (SYSV), ' 'statically linked, for GNU/Linux 2.6.32, ' 'BuildID[sha1]=XYZ, stripped') elf_files = elf.get_elf_files(self.workdir, {'statically-linked'}) self.ms_mock.file.assert_called_once_with(statically_linked_elf_path_b) self.assertThat(elf_files, Equals(set()))
def prime(self, force=False) -> None: self.makedirs() self.notify_part_progress('Priming') snap_files, snap_dirs = self.migratable_fileset_for('prime') _migrate_files(snap_files, snap_dirs, self.stagedir, self.primedir) elf_files = elf.get_elf_files(self.primedir, snap_files) dependencies = set() for elf_file in elf_files: dependencies.update(elf.get_dependencies(elf_file.path)) # Split the necessary dependencies into their corresponding location. # We'll both migrate and track the system dependencies, but we'll only # track the part and staged dependencies, since they should have # already been primed by other means, and migrating them again could # potentially override the `stage` or `snap` filtering. (in_part, staged, primed, system) = _split_dependencies(dependencies, self.installdir, self.stagedir, self.primedir) part_dependency_paths = {os.path.dirname(d) for d in in_part} staged_dependency_paths = {os.path.dirname(d) for d in staged} dependency_paths = part_dependency_paths | staged_dependency_paths if not self._build_attributes.no_system_libraries(): system_dependency_paths = {os.path.dirname(d) for d in system} dependency_paths.update(system_dependency_paths) if system: # Lots of dependencies are linked with a symlink, so we need to # make sure we follow those symlinks when we migrate the # dependencies. _migrate_files(system, system_dependency_paths, '/', self.primedir, follow_symlinks=True) if self._confinement == 'classic': dynamic_linker = self._project_options.get_core_dynamic_linker() elf_patcher = elf.Patcher(dynamic_linker=dynamic_linker) for elf_file in elf_files: elf_patcher.patch(elf_file=elf_file) self.mark_prime_done(snap_files, snap_dirs, dependency_paths)
def test_get_elf_is_library(self): self.ms_mock.file.return_value = ( 'ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), ' 'dynamically linked, ' 'BuildID[sha1]=62b2bc59168b25ab9b025182c1f5f43194ba167b, stripped') linked_elf_path = os.path.join(self.workdir, 'linked') open(linked_elf_path, 'w').close() linked_elf_path_b = linked_elf_path.encode(sys.getfilesystemencoding()) elf_files = elf.get_elf_files(self.workdir, {'linked'}) self.assertThat(len(elf_files), Equals(1)) self.ms_mock.file.assert_called_once_with(linked_elf_path_b) elf_file = set(elf_files).pop() self.assertThat(elf_file.path, Equals(linked_elf_path)) self.assertThat(elf_file.is_executable, Equals(False))
def _handle_elf(self, snap_files: Sequence[str]) -> Set[str]: elf_files = elf.get_elf_files(self.primedir, snap_files) all_dependencies = set() # TODO: base snap support core_path = common.get_core_path(self._base) # Clear the cache of all libs that aren't already in the primedir self._soname_cache.reset_except_root(self.primedir) for elf_file in elf_files: all_dependencies.update( elf_file.load_dependencies( root_path=self.primedir, core_base_path=core_path, soname_cache=self._soname_cache, ) ) dependency_paths = self._handle_dependencies(all_dependencies) if not self._build_attributes.keep_execstack(): clear_execstack(elf_files=elf_files) if self._build_attributes.no_patchelf(): logger.warning( "The primed files for part {!r} will not be verified for " "correctness or patched: build-attributes: [no-patchelf] " "is set.".format(self.name) ) else: part_patcher = PartPatcher( elf_files=elf_files, plugin=self.plugin, project=self._project_options, confinement=self._confinement, core_base=self._base, snap_base_path=self._snap_base_path, stagedir=self.stagedir, primedir=self.primedir, stage_packages=self._part_properties.get("stage-packages", []), ) part_patcher.patch() return dependency_paths
def _handle_elf(self, snap_files: Sequence[str]) -> Set[str]: elf_files = elf.get_elf_files(self.primedir, snap_files) all_dependencies = set() core_path = common.get_core_path(self._base) # Clear the cache of all libs that aren't already in the primedir self._soname_cache.reset_except_root(self.primedir) for elf_file in elf_files: all_dependencies.update( elf_file.load_dependencies( root_path=self.primedir, core_base_path=core_path, soname_cache=self._soname_cache, ) ) dependency_paths = self._handle_dependencies(all_dependencies) if not self._build_attributes.keep_execstack(): clear_execstack(elf_files=elf_files) if self._build_attributes.no_patchelf(): logger.warning( "The primed files for part {!r} will not be verified for " "correctness or patched: build-attributes: [no-patchelf] " "is set.".format(self.name) ) else: part_patcher = PartPatcher( elf_files=elf_files, plugin=self.plugin, project=self._project_options, confinement=self._confinement, core_base=self._base, snap_base_path=self._snap_base_path, stagedir=self.stagedir, primedir=self.primedir, stage_packages=self._part_properties.get("stage-packages", []), ) part_patcher.patch() return dependency_paths
def test_device_files(self): elf_files = elf.get_elf_files('/dev', {'null'}) self.assertThat(elf_files, Equals(set()))
def test_elf_without_execstack(self): elf_files = elf.get_elf_files(self.fake_elf.root_path, {'fake_elf-2.23'}) elf_file = set(elf_files).pop() self.assertThat(elf_file.execstack_set, Equals(False))
def test_elf_with_execstack(self): elf_files = elf.get_elf_files(self.fake_elf.root_path, {'fake_elf-with-execstack'}) elf_file = set(elf_files).pop() self.assertThat(elf_file.execstack_set, Equals(True))
def test_no_find_dependencies_statically_linked(self): elf_files = elf.get_elf_files(self.fake_elf.root_path, {'fake_elf-static'}) self.assertThat(elf_files, Equals(set()))
def prime(self, force=False) -> None: self.makedirs() self.notify_part_progress('Priming') snap_files, snap_dirs = self.migratable_fileset_for('prime') _migrate_files(snap_files, snap_dirs, self.stagedir, self.primedir) elf_files = elf.get_elf_files(self.primedir, snap_files) all_dependencies = set() for elf_file in elf_files: all_dependencies.update(elf_file.load_dependencies()) # Split the necessary dependencies into their corresponding location. # We'll both migrate and track the system dependencies, but we'll only # track the part and staged dependencies, since they should have # already been primed by other means, and migrating them again could # potentially override the `stage` or `snap` filtering. (in_part, staged, primed, system) = _split_dependencies( all_dependencies, self.installdir, self.stagedir, self.primedir) part_dependency_paths = {os.path.dirname(d) for d in in_part} staged_dependency_paths = {os.path.dirname(d) for d in staged} dependency_paths = part_dependency_paths | staged_dependency_paths if not self._build_attributes.no_system_libraries(): system_dependency_paths = {os.path.dirname(d) for d in system} dependency_paths.update(system_dependency_paths) if system: # Lots of dependencies are linked with a symlink, so we need to # make sure we follow those symlinks when we migrate the # dependencies. _migrate_files(system, system_dependency_paths, '/', self.primedir, follow_symlinks=True) # TODO: base snap support core_path = common.get_core_path() def mangle_library_path(library_path: str, elf_file_path: str) -> str: # If the path is is in the core snap, use the absolute path, # if the path is primed, use $ORIGIN, and last if the dependency # is not anywhere return an empty string. # # Once we move away from the system library grabbing logic # we can move to a smarter library capturing mechanism. library_path = library_path.replace(self.installdir, self.primedir) if library_path.startswith(core_path): return os.path.dirname(library_path) elif (library_path.startswith(self.primedir) and os.path.exists(library_path)): rel_library_path = os.path.relpath(library_path, elf_file_path) rel_library_path_dir = os.path.dirname(rel_library_path) # return the dirname, with the first .. replace with $ORIGIN return rel_library_path_dir.replace('..', '$ORIGIN', 1) else: return '' if self._confinement == 'classic': core_rpaths = common.get_library_paths( core_path, self._project_options.arch_triplet, existing_only=False) dynamic_linker = self._project_options.get_core_dynamic_linker() elf_patcher = elf.Patcher(dynamic_linker=dynamic_linker, library_path_func=mangle_library_path, base_rpaths=core_rpaths) for elf_file in elf_files: elf_patcher.patch(elf_file=elf_file) self.mark_prime_done(snap_files, snap_dirs, dependency_paths)
def test_device_files(self): elf_files = elf.get_elf_files("/dev", {"null"}) self.assertThat(elf_files, Equals(set()))
def prime(self, force=False) -> None: self.makedirs() self.notify_part_progress('Priming') snap_files, snap_dirs = self.migratable_fileset_for('prime') _migrate_files(snap_files, snap_dirs, self.stagedir, self.primedir) elf_files = elf.get_elf_files(self.primedir, snap_files) all_dependencies = set() # TODO: base snap support core_path = common.get_core_path() for elf_file in elf_files: all_dependencies.update( elf_file.load_dependencies(root_path=self.primedir, core_base_path=core_path)) # Split the necessary dependencies into their corresponding location. # We'll both migrate and track the system dependencies, but we'll only # track the part and staged dependencies, since they should have # already been primed by other means, and migrating them again could # potentially override the `stage` or `snap` filtering. (in_part, staged, primed, system) = _split_dependencies(all_dependencies, self.installdir, self.stagedir, self.primedir) part_dependency_paths = {os.path.dirname(d) for d in in_part} staged_dependency_paths = {os.path.dirname(d) for d in staged} dependency_paths = part_dependency_paths | staged_dependency_paths if not self._build_attributes.no_system_libraries(): system_dependency_paths = {os.path.dirname(d) for d in system} dependency_paths.update(system_dependency_paths) if system: # Lots of dependencies are linked with a symlink, so we need to # make sure we follow those symlinks when we migrate the # dependencies. _migrate_files(system, system_dependency_paths, '/', self.primedir, follow_symlinks=True) formatted_system = '\n'.join(sorted(system)) logger.warning( 'Files from the build host were migrated into the snap to ' 'satisfy dependencies that would otherwise not be met. ' 'This feature will be removed in a future release. If ' 'these libraries are needed in the final snap, ensure ' 'that the following are either satisfied by a ' 'stage-packages entry or through a part:\n{}'.format( formatted_system)) # TODO revisit if we need to support variations and permutations # of this staged_patchelf_path = os.path.join(self.stagedir, 'bin', 'patchelf') if not os.path.exists(staged_patchelf_path): staged_patchelf_path = None # We need to verify now that the GLIBC version would be compatible # with that of the base. # TODO the linker version depends on the chosen base, but that # base may not be installed so we cannot depend on # get_core_dynamic_linker to resolve the final path for which # we resort to our only working base 16, ld-2.23.so. linker_compatible = (e.is_linker_compatible(linker='ld-2.23.so') for e in elf_files) if not all((x for x in linker_compatible)): handle_glibc_mismatch(elf_files=elf_files, root_path=self.primedir, snap_base_path=self._snap_base_path, core_base_path=core_path, preferred_patchelf_path=staged_patchelf_path) elif self._confinement == 'classic': dynamic_linker = self._project_options.get_core_dynamic_linker() elf_patcher = elf.Patcher( dynamic_linker=dynamic_linker, root_path=self.primedir, preferred_patchelf_path=staged_patchelf_path) for elf_file in elf_files: elf_patcher.patch(elf_file=elf_file) self.mark_prime_done(snap_files, snap_dirs, dependency_paths)
def test_fifo(self): fifo_path = os.path.join(self.fake_elf.root_path, 'fifo') os.mkfifo(fifo_path) elf_files = elf.get_elf_files(self.fake_elf.root_path, {'fifo'}) self.assertThat(elf_files, Equals(set()))
def prime(self, force=False) -> None: # noqa: C901 self.makedirs() self.notify_part_progress('Priming') snap_files, snap_dirs = self.migratable_fileset_for('prime') _migrate_files(snap_files, snap_dirs, self.stagedir, self.primedir) elf_files = elf.get_elf_files(self.primedir, snap_files) all_dependencies = set() # TODO: base snap support core_path = common.get_core_path() # Reset to take into account new data inside prime provided by other # parts. self._soname_cache.reset() for elf_file in elf_files: all_dependencies.update( elf_file.load_dependencies(root_path=self.primedir, core_base_path=core_path, soname_cache=self._soname_cache)) dependency_paths = self._handle_dependencies(all_dependencies) if not self._build_attributes.keep_execstack(): clear_execstack(elf_files=elf_files) # TODO revisit if we need to support variations and permutations # of this staged_patchelf_path = os.path.join(self.stagedir, 'bin', 'patchelf') if not os.path.exists(staged_patchelf_path): staged_patchelf_path = None # We need to verify now that the GLIBC version would be compatible # with that of the base. # TODO the linker version depends on the chosen base, but that # base may not be installed so we cannot depend on # get_core_dynamic_linker to resolve the final path for which # we resort to our only working base 16, ld-2.23.so. linker_incompat = dict() # type: Dict[str, str] for elf_file in elf_files: if not elf_file.is_linker_compatible(linker='ld-2.23.so'): linker_incompat[elf_file.path] = elf_file.get_required_glibc() # If libc6 is staged, to avoid symbol mixups we will resort to # glibc mangling. libc6_staged = 'libc6' in self._part_properties.get( 'stage-packages', []) is_classic = self._confinement == 'classic' # classic confined snaps built on anything but a host supporting the # the target base will require glibc mangling. classic_mangling_needed = ( is_classic and not self._project_options.is_host_compatible_with_base) if linker_incompat: formatted_items = [ '- {} (requires GLIBC {})'.format(k, v) for k, v in linker_incompat.items() ] logger.warning( 'The GLIBC version of the targeted core is 2.23. A newer ' 'libc will be required for the following files:\n{}'.format( '\n'.join(formatted_items))) if (linker_incompat or libc6_staged or classic_mangling_needed): if not libc6_staged: raise errors.StagePackageMissingError(package='libc6') handle_glibc_mismatch(elf_files=elf_files, root_path=self.primedir, snap_base_path=self._snap_base_path, core_base_path=core_path, preferred_patchelf_path=staged_patchelf_path, soname_cache=self._soname_cache) elif is_classic: dynamic_linker = self._project_options.get_core_dynamic_linker() elf_patcher = elf.Patcher( dynamic_linker=dynamic_linker, root_path=self.primedir, preferred_patchelf_path=staged_patchelf_path) for elf_file in elf_files: elf_patcher.patch(elf_file=elf_file) self.mark_prime_done(snap_files, snap_dirs, dependency_paths)
def test_invalid_elf_file(self): invalid_elf = os.path.join(self.path, "invalid-elf") open(invalid_elf, "wb").write(b"\x7fELF\x00") elf_files = elf.get_elf_files(self.path, ["invalid-elf"]) self.assertThat(elf_files, Equals(set()))