Radish alpha
H
rad:z3QDZAW2FAfuLvihrhiyDC9fAD8G9
HardenedBSD Package Manager
Radicle
Git
Introduce hints to ABI_FILE to select specific entries in an universal binary
Keve committed 1 year ago
commit d5eb29ff65ec4613290d02d24f1fc64bac66f58b
parent 1c503a0
5 files changed +410 -181
modified libpkg/pkg_abi.c
@@ -33,10 +33,9 @@
#include <unistd.h>

#include "pkg.h"
-

-
#include "private/pkg.h"
-
#include "private/event.h"
#include "private/binfmt.h"
+
#include "private/event.h"
+
#include "private/pkg.h"

#define _PATH_UNAME "/usr/bin/uname"

@@ -46,33 +45,22 @@ struct arch_trans {
	const char *archid;
};

-
static struct arch_trans machine_arch_translation[] = {
-
	{ "x86:32", "i386" },
-
	{ "x86:64", "amd64" },
-
	{ "powerpc:32:eb", "powerpc" },
-
	{ "powerpc:64:eb", "powerpc64" },
-
	{ "powerpc:64:el", "powerpc64le" },
-
	{ "sparc64:64", "sparc64" },
-
	{ "ia64:64", "ia64" },
+
static struct arch_trans machine_arch_translation[] = { { "x86:32", "i386" },
+
	{ "x86:64", "amd64" }, { "powerpc:32:eb", "powerpc" },
+
	{ "powerpc:64:eb", "powerpc64" }, { "powerpc:64:el", "powerpc64le" },
+
	{ "sparc64:64", "sparc64" }, { "ia64:64", "ia64" },
	/* All the ARM stuff */
	{ "armv6:32:el:eabi:hardfp", "armv6" },
-
	{ "armv7:32:el:eabi:hardfp", "armv7" },
-
	{ "aarch64:64", "aarch64" },
+
	{ "armv7:32:el:eabi:hardfp", "armv7" }, { "aarch64:64", "aarch64" },
	/* And now MIPS */
-
	{ "mips:32:el:o32", "mipsel" },
-
	{ "mips:32:el:n32", "mipsn32el" },
-
	{ "mips:32:eb:o32", "mips" },
-
	{ "mips:32:eb:n32", "mipsn32" },
-
	{ "mips:64:el:n64", "mips64el" },
-
	{ "mips:64:eb:n64", "mips64" },
+
	{ "mips:32:el:o32", "mipsel" }, { "mips:32:el:n32", "mipsn32el" },
+
	{ "mips:32:eb:o32", "mips" }, { "mips:32:eb:n32", "mipsn32" },
+
	{ "mips:64:el:n64", "mips64el" }, { "mips:64:eb:n64", "mips64" },
	/* And RISC-V */
-
	{ "riscv:32:hf", "riscv32" },
-
	{ "riscv:32:sf", "riscv32sf" },
-
	{ "riscv:64:hf", "riscv64" },
-
	{ "riscv:64:sf", "riscv64sf" },
+
	{ "riscv:32:hf", "riscv32" }, { "riscv:32:sf", "riscv32sf" },
+
	{ "riscv:64:hf", "riscv64" }, { "riscv:64:sf", "riscv64sf" },

-
	{ NULL, NULL }
-
};
+
	{ NULL, NULL } };

static int
pkg_get_myarch_fromfile(struct os_info *oi)
@@ -83,6 +71,9 @@ pkg_get_myarch_fromfile(struct os_info *oi)
		_PATH_UNAME,
		_PATH_BSHELL,
	};
+
	char work_abi_file[PATH_MAX];
+
	char work_arch_hint[PATH_MAX];
+

	int i, fd;

	/*
@@ -94,34 +85,70 @@ pkg_get_myarch_fromfile(struct os_info *oi)
	for (fd = -1, i = 0; i < NELEM(abi_files); i++) {
		if (abi_files[i] == NULL)
			continue;
+

+
		const char *sep = strrchr(abi_files[i], '#');
+
		if (sep) {
+
			strlcpy(work_abi_file, abi_files[i],
+
			    MIN(sep - abi_files[i] + 1, sizeof(work_abi_file)));
+
			strlcpy(work_arch_hint, sep + 1,
+
			    sizeof(work_arch_hint));
+
		} else {
+
			strlcpy(work_abi_file, abi_files[i],
+
			    sizeof(work_abi_file));
+
			work_arch_hint[0] = '\0';
+
		}
+

		/*
		 * Try prepending rootdir and using that if it exists.  If
		 * ABI_FILE is specified, assume that the consumer didn't want
		 * it mangled by rootdir.
		 */
-
		if (i > 0 && checkroot && snprintf(rooted_abi_file, PATH_MAX,
-
		    "%s/%s", ctx.pkg_rootdir, abi_files[i]) < PATH_MAX) {
-
			if ((fd = open(rooted_abi_file, O_RDONLY)) >= 0)
+
		if (i > 0 && checkroot &&
+
		    snprintf(rooted_abi_file, PATH_MAX, "%s/%s",
+
			ctx.pkg_rootdir, work_abi_file) < PATH_MAX) {
+
			if ((fd = open(rooted_abi_file, O_RDONLY)) >= 0) {
+
				strlcpy(work_abi_file, rooted_abi_file,
+
				    sizeof(work_abi_file));
				break;
+
			}
		}
-
		if ((fd = open(abi_files[i], O_RDONLY)) >= 0)
+
		if ((fd = open(work_abi_file, O_RDONLY)) >= 0) {
			break;
+
		}
		/* if the ABI_FILE was provided we only care about it */
		if (i == 0)
			break;
	}
	if (fd == -1) {
-
		pkg_emit_error("Unable to determine the ABI\n");
-
		return (EPKG_FATAL);
+
		pkg_emit_error(
+
		    "Unable to determine the ABI, none of the ABI_FILEs can be read.");
+
		return EPKG_FATAL;
+
	}
+

+
	if (work_arch_hint[0]) {
+
		snprintf(oi->abi, sizeof(oi->abi), "::%s",
+
		    work_arch_hint);
	}

	int ret = pkg_get_myarch_elfparse(fd, oi);
	if (EPKG_OK != ret) {
-
		lseek(fd, 0, SEEK_SET);
+
		if (-1 == lseek(fd, 0, SEEK_SET)) {
+
			pkg_emit_errno("Error seeking file", work_abi_file);
+
			ret = EPKG_FATAL;
+
		}
		ret = pkg_get_myarch_macho(fd, oi);
+
		if (EPKG_OK != ret) {
+
			pkg_emit_error(
+
			    "Unable to determine the ABI, %s cannot be parsed.",
+
			    work_abi_file);
+
			ret = EPKG_FATAL;
+
		}
	}

-
	close(fd);
+
	if (close(fd)) {
+
		pkg_emit_errno("Error closing file", work_abi_file);
+
		ret = EPKG_FATAL;
+
	}
	return ret;
}

@@ -132,6 +159,7 @@ pkg_get_myarch_with_legacy(struct os_info *oi)
		return (EPKG_FATAL);
	int err = pkg_get_myarch_fromfile(oi);
	if (err) {
+
		pkg_debug(1, "Error %d when trying to determine myarch.", err);
		free(oi->name);
		return (err);
	}
@@ -185,7 +213,7 @@ pkg_arch_to_legacy(const char *arch, char *dest, size_t sz)
			return (0);
		}
	}
-
	strlcpy(dest + i, arch + i, sz - (arch + i  - dest));
+
	strlcpy(dest + i, arch + i, sz - (arch + i - dest));

	return (0);
}
@@ -195,13 +223,14 @@ pkg_analyse_files(struct pkgdb *db __unused, struct pkg *pkg, const char *stage)
{
	struct pkg_file *file = NULL;
	int ret = EPKG_OK;
-
	char fpath[MAXPATHLEN +1];
+
	char fpath[MAXPATHLEN + 1];
	const char *lib;
	bool failures = false;

-
	int (*pkg_analyse_init)(const char* stage)  = pkg_analyse_init_elf;
-
	int (*pkg_analyse)(const bool developer_mode, struct pkg *pkg, const char *fpath) = pkg_analyse_elf;
-
	int (*pkg_analyse_close)()  = pkg_analyse_close_elf;
+
	int (*pkg_analyse_init)(const char *stage) = pkg_analyse_init_elf;
+
	int (*pkg_analyse)(const bool developer_mode, struct pkg *pkg,
+
	    const char *fpath) = pkg_analyse_elf;
+
	int (*pkg_analyse_close)() = pkg_analyse_close_elf;

	if (tll_length(pkg->shlibs_required) != 0) {
		tll_free_and_free(pkg->shlibs_required, free);
@@ -219,12 +248,12 @@ pkg_analyse_files(struct pkgdb *db __unused, struct pkg *pkg, const char *stage)
	/* Assume no architecture dependence, for contradiction */
	if (ctx.developer_mode)
		pkg->flags &= ~(PKG_CONTAINS_ELF_OBJECTS |
-
				PKG_CONTAINS_STATIC_LIBS |
-
				PKG_CONTAINS_LA);
+
		    PKG_CONTAINS_STATIC_LIBS | PKG_CONTAINS_LA);

	while (pkg_files(pkg, &file) == EPKG_OK) {
		if (stage != NULL)
-
			snprintf(fpath, sizeof(fpath), "%s/%s", stage, file->path);
+
			snprintf(fpath, sizeof(fpath), "%s/%s", stage,
+
			    file->path);
		else
			strlcpy(fpath, file->path, sizeof(fpath));

@@ -237,9 +266,11 @@ pkg_analyse_files(struct pkgdb *db __unused, struct pkg *pkg, const char *stage)
	/*
	 * Do not depend on libraries that a package provides itself
	 */
-
	tll_foreach(pkg->shlibs_required, s) {
+
	tll_foreach(pkg->shlibs_required, s)
+
	{
		if (stringlist_contains(&pkg->shlibs_provided, s->item)) {
-
			pkg_debug(2, "remove %s from required shlibs as the "
+
			pkg_debug(2,
+
			    "remove %s from required shlibs as the "
			    "package %s provides this library itself",
			    s->item, pkg->name);
			tll_remove_and_free(pkg->shlibs_required, s, free);
@@ -249,11 +280,13 @@ pkg_analyse_files(struct pkgdb *db __unused, struct pkg *pkg, const char *stage)
		while (pkg_files(pkg, &file) == EPKG_OK) {
			if ((lib = strstr(file->path, s->item)) != NULL &&
			    strlen(lib) == strlen(s->item) && lib[-1] == '/') {
-
				pkg_debug(2, "remove %s from required shlibs as "
+
				pkg_debug(2,
+
				    "remove %s from required shlibs as "
				    "the package %s provides this file itself",
				    s->item, pkg->name);

-
				tll_remove_and_free(pkg->shlibs_required, s, free);
+
				tll_remove_and_free(pkg->shlibs_required, s,
+
				    free);
				break;
			}
		}
modified libpkg/pkg_abi_macho.c
@@ -1,41 +1,21 @@
/*-
 * Copyright (c) 2024 Keve Müller <kevemueller@users.github.com>
 *
-
 * Redistribution and use in source and binary forms, with or without
-
 * modification, are permitted provided that the following conditions
-
 * are met:
-
 * 1. Redistributions of source code must retain the above copyright
-
 *    notice, this list of conditions and the following disclaimer
-
 *    in this position and unchanged.
-
 * 2. Redistributions in binary form must reproduce the above copyright
-
 *    notice, this list of conditions and the following disclaimer in the
-
 *    documentation and/or other materials provided with the distribution.
-
 *
-
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
-
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
-
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
-
 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
-
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
-
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
-
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <errno.h>

#include "private/binfmt_macho.h"
-

#include "private/pkg.h"
#include "private/event.h"

-

/**
 * Routines to support pkg_abi.c functions when dealing with Mach-O files.
-
 * Supports getting ABI and ALTABI from the binary's load commands. Cave: picks first binary in FAT collection.
-
 * Supports getting shared libary information. Picks right binary in FAT collection based on ABI.
-
 * Supports FreeBSD naming of architectures.
+
 * Supports getting ABI and ALTABI from the binary's load commands. Cave: picks
+
 * first binary in FAT collection. Supports getting shared libary information.
+
 * Picks right binary in FAT collection based on ABI. Supports FreeBSD naming of
+
 * architectures.
 */

/**** CPU -> FreeBSD MACHINE_ARCH conversion ****/
@@ -90,12 +70,138 @@ cputype_to_freebsd_machine_arch(const cpu_type_subtype_t cpu)
	}
}

+
static cpu_type_subtype_t
+
freebsd_machine_arch_to_cputype(const char *archname) {
+
	cpu_type_subtype_t cpu = { 0 };
+

+
	if (!strcmp("aarch64", archname)) {
+
		cpu.type = CPU_TYPE_ARM;
+
		cpu.type_is64 = true;
+
	} else if (!strcmp("amd64", archname)) {
+
		cpu.type = CPU_TYPE_X86;
+
		cpu.type_is64 = true;
+
		cpu.subtype_x86 = CPU_SUBTYPE_X86_ALL;
+
	} else if (!strcmp("arm", archname)) {
+
		cpu.type = CPU_TYPE_ARM;
+
		cpu.subtype_arm = CPU_SUBTYPE_ARM_ALL;
+
	} else if (!strcmp("armeb", archname)) {
+
		cpu.type = CPU_TYPE_ARM;
+
		cpu.subtype_arm = CPU_SUBTYPE_ARM_V5;
+
	} else if (!strcmp("armv6", archname)) {
+
		cpu.type = CPU_TYPE_ARM;
+
		cpu.subtype_arm = CPU_SUBTYPE_ARM_V6;
+
	} else if (!strcmp("armv7", archname)) {
+
		cpu.type = CPU_TYPE_ARM;
+
		cpu.subtype_arm = CPU_SUBTYPE_ARM_V7;
+
	} else if (!strcmp("i386", archname)) {
+
		cpu.type = CPU_TYPE_X86;
+
		cpu.subtype_x86 = CPU_SUBTYPE_X86_ALL;
+
	} else if (!strcmp("powerpc", archname)) {
+
		cpu.type = CPU_TYPE_POWERPC;
+
		cpu.subtype_ppc = CPU_SUBTYPE_POWERPC_ALL;
+
	} else if (!strcmp("powerpc64", archname)) {
+
		cpu.type = CPU_TYPE_POWERPC;
+
		cpu.type_is64 = true;
+
		cpu.subtype_ppc = CPU_SUBTYPE_POWERPC_ALL;
+
	} else {
+
		// alpha
+
		// ia64
+
		// mips*
+
		// pc98
+
		// sparc64
+
		cpu.type = CPU_TYPE_ANY;
+
	}
+
	return cpu;
+
}
+

+

+
/**
+
 * Using the passed mf descriptor, match the best entry using oi->name as a hint.
+
 * No hint or no architecture in hint -> first entry. Debug1 warning if this is not precise match (there were multiple to choose from)
+
 * Hint -> always match, even if single architecture in file. Notice if match fails and return null.
+
 */
+
static const fat_arch_t *
+
match_entry(macho_file_t *mf, struct os_info *oi)
+
{
+
	const fat_arch_t *p = mf->arch;
+
	// we can change the content of oi->abi freely
+
	char *abisep = oi->abi;
+
	/*const char *osname = */strsep(&abisep, ":");
+
	/*const char *version_str = */ strsep(&abisep, ":");
+
	const char *archname = strsep(&abisep, ":");
+
	if (archname) {	
+
		const cpu_type_subtype_t cpu_hint = freebsd_machine_arch_to_cputype(archname);
+
		const fat_arch_t *p_end = p + mf->narch;
+
		while (p < p_end) {
+
			// do not match cpu_hint.type == CPU_TYPE_ANY which is used if the 
+
			// archname hint was not recognized
+
			if (p->cpu.type == cpu_hint.type &&
+
			    p->cpu.type_is64 == cpu_hint.type_is64) {
+
				switch (cpu_hint.type) {
+
				case CPU_TYPE_ARM:
+
					if (p->cpu.subtype_arm ==
+
						CPU_SUBTYPE_ARM_ALL ||
+
					    cpu_hint.subtype_arm ==
+
						CPU_SUBTYPE_ARM_ALL ||
+
					    p->cpu.subtype_arm ==
+
						cpu_hint.subtype_arm) {
+
						return p;
+
					}
+
					break;
+
				case CPU_TYPE_POWERPC:
+
					if (p->cpu.subtype_ppc ==
+
						CPU_SUBTYPE_POWERPC_ALL ||
+
					    cpu_hint.subtype_ppc ==
+
						CPU_SUBTYPE_POWERPC_ALL ||
+
					    p->cpu.subtype_ppc ==
+
						cpu_hint.subtype_ppc) {
+
						return p;
+
					}
+
					break;
+
				case CPU_TYPE_X86:
+
					if (p->cpu.subtype_x86 ==
+
						CPU_SUBTYPE_X86_ALL ||
+
					    cpu_hint.subtype_x86 ==
+
						CPU_SUBTYPE_X86_ALL ||
+
					    p->cpu.subtype_x86 ==
+
						cpu_hint.subtype_x86) {
+
						return p;
+
					}
+
					break;
+
				default:
+
					break;
+
				}
+
			}
+
			pkg_debug(1, "Looking for %s, did not match %s",
+
		    archname, cputype_to_freebsd_machine_arch(p->cpu));
+
			p++;
+
		}
+
		pkg_emit_notice("Scanned %d entr%s, found none matching selector %s",
+
			mf->narch, mf->narch > 1 ? "ies" : "y", archname);
+
		return 0;	
+
	} else if (mf->narch > 1 ) {
+
		pkg_debug(1,"Found %d entries in universal binary, picking first",
+
			mf->narch);
+
	}
+
	return p;
+
}
+

+
/**
+
 * With a not-null, potentially pre-populated os_info structure, fill
+
 * all members of os_info except altabi with values obtained by parsing the Mach-O
+
 * file passed with file descriptor.
+
 *
+
 * Third (architecture) component of oi->abi is used to determine the fat entry to be parsed
+
 * in a universal binary. when not set, the first entry is used.
+
 *
+
 * Returns EPKG_OK if all went fine, EPKG_FATAL if anything went wrong.
+
 * Seeks the file descriptor to an arbitrary position.
+
 */
int
pkg_get_myarch_macho(int fd, struct os_info *oi)
{
	ssize_t x;
-
	char *dest = oi->abi;
-
	size_t sz = sizeof(oi->abi);
+
	pkg_error_t ret = EPKG_FATAL;

	macho_file_t *mf = 0;
	build_version_t *bv = 0;
@@ -104,13 +210,11 @@ pkg_get_myarch_macho(int fd, struct os_info *oi)
		goto cleanup;
	}

-
	if (0 == mf->narch) {
+
	const fat_arch_t *p = match_entry(mf, oi);
+

+
	if (!p) {
		goto cleanup;
	}
-
	if (mf->narch > 1) {
-
		pkg_debug(1, "Found %d entries, picking first", mf->narch);
-
	}
-
	fat_arch_t *p = mf->arch;

	if (-1 == (x = lseek(fd, p->offset, SEEK_SET))) {
		goto cleanup;
@@ -123,96 +227,100 @@ pkg_get_myarch_macho(int fd, struct os_info *oi)
	const bool swap = mh.swap;
	n = 0;
	for (uint32_t ui = mh.ncmds; ui-- > 0;) {
-
			size_t n0 = n;
-
			uint32_t loadcmdtype;
-
			uint32_t loadcmdsize;
-
			READ(u32, loadcmdtype);
-
			READ(u32, loadcmdsize);
-
			enum MachOLoadCommand loadcmd = loadcmdtype &
-
			    ~LC_REQ_DYLD;
-
			switch (loadcmd) {
-
			case LC_BUILD_VERSION:
-
				if (bv) { // overwrite previous LC_VERSION_MIN_X
-
					  // values
-
					free(bv);
-
					bv = 0;
-
				}
-
				READ(build_version, bv);
-
				break;
-
			case LC_VERSION_MIN_IPHONEOS:
-
			case LC_VERSION_MIN_MACOSX:
-
			case LC_VERSION_MIN_TVOS:
-
			case LC_VERSION_MIN_WATCHOS:
-
				if (!bv) {
-
					if ((x = read_min_version(fd, swap,
-
						 loadcmd, &bv)) < 0) {
-
						goto cleanup;
-
					}
-
					n += x;
-
					break;
-
				}
-
				// have seen the more precise
-
				// LC_BUILD_VERSION already
-
				// fall through and disregard this
-
			default:
-
				if (-1 ==
-
				    (x = lseek(fd, loadcmdsize - 8,
-
					 SEEK_CUR))) {
+
		size_t n0 = n;
+
		uint32_t loadcmdtype;
+
		uint32_t loadcmdsize;
+
		READ(u32, loadcmdtype);
+
		READ(u32, loadcmdsize);
+
		enum MachOLoadCommand loadcmd = loadcmdtype & ~LC_REQ_DYLD;
+
		switch (loadcmd) {
+
		case LC_BUILD_VERSION:
+
			if (bv) { // overwrite previous LC_VERSION_MIN_X
+
				  // values
+
				free(bv);
+
				bv = 0;
+
			}
+
			READ(build_version, bv);
+
			break;
+
		case LC_VERSION_MIN_IPHONEOS:
+
		case LC_VERSION_MIN_MACOSX:
+
		case LC_VERSION_MIN_TVOS:
+
		case LC_VERSION_MIN_WATCHOS:
+
			if (!bv) {
+
				if ((x = read_min_version(fd, swap, loadcmd,
+
					 &bv)) < 0) {
					goto cleanup;
				}
-
				n += loadcmdsize - 8;
+
				n += x;
				break;
			}
-
			if (n - n0 != loadcmdsize) {
-
				printf("unprecise read %u != %zu", n - n0,
-
				    loadcmdsize);
-
				errno = EINVAL;
-
				goto cleanup;
-
			}
-
			if (n > mh.sizeofcmds) {
-
				printf("long read %u > %u", n, mh.sizeofcmds);
-
				errno = EINVAL;
-
				goto cleanup;
-
			}
+
			// have seen the more precise
+
			// LC_BUILD_VERSION already
+
			// fall through and disregard this
+
		default:
+
			break;
+
		}
+
		const uint32_t fill = loadcmdsize - (n - n0);
+
		if (fill && -1 == (x = lseek(fd, fill, SEEK_CUR))) {
+
			goto cleanup;
+
		}
+
		n += fill;
+
		if (n > mh.sizeofcmds) {
+
			// we passed the frame boundary of the load commands
+
			pkg_emit_error("Mach-O structure misread.");
+
			errno = EINVAL;
+
			goto cleanup;
+
		}
	}

	if (bv) {
		macho_version_t darwin;
		map_platform_to_darwin(&darwin, bv->platform, bv->minos);
-
		snprintf(dest, sz, "Darwin:%d:%s", darwin.major, cputype_to_freebsd_machine_arch(mh.cpu));
-
		if (oi) {
-
			oi->name = xstrdup("Darwin");
-
			oi->osversion = darwin.major * 100000 + darwin.minor * 1000 + darwin.patch;
-
			if (darwin.patch) {
-
				xasprintf(&oi->version, "%d.%d.%d", darwin.major, darwin.minor, darwin.patch);
-
			} else {
-
				xasprintf(&oi->version, "%d.%d", darwin.major, darwin.minor);
-
			}
-
			xasprintf(&oi->version_major, "%d", darwin.major);
-
			xasprintf(&oi->version_minor, "%d", darwin.minor);
-
			oi->arch = xstrdup(cputype_to_freebsd_machine_arch(
-
				mh.cpu));
+

+
		oi->osversion = darwin.major * 100000 + darwin.minor * 1000 +
+
		    darwin.patch;
+
		oi->ostype = OS_MACOS;
+
		free(oi->name);
+
		oi->name = xstrdup("Darwin");
+
		free(oi->version);
+
		if (darwin.patch) {
+
			xasprintf(&oi->version, "%d.%d.%d", darwin.major,
+
			    darwin.minor, darwin.patch);
+
		} else {
+
			xasprintf(&oi->version, "%d.%d", darwin.major,
+
			    darwin.minor);
		}
-
		return EPKG_OK;
-
	}
+
		free(oi->version_major);
+
		xasprintf(&oi->version_major, "%d", darwin.major);
+
		free(oi->version_minor);
+
		xasprintf(&oi->version_minor, "%d", darwin.minor);
+
		free(oi->arch);
+
		oi->arch = xstrdup(cputype_to_freebsd_machine_arch(mh.cpu));
+
		snprintf(oi->abi, sizeof(oi->abi), "Darwin:%d:%s", darwin.major, cputype_to_freebsd_machine_arch(mh.cpu)); 
+
		// not populating oi->altabi, derived later by caller.
+
		snprintf(oi->str_osversion, sizeof(oi->str_osversion), "%d",
+
		    oi->osversion);

+
		ret = EPKG_OK;
+
	} else {
+
		pkg_emit_notice("No OS version information found in binary.");
+
	}

cleanup:
-
	if (bv) {
-
		free(bv);
-
	}
-
	if (mf) {
-
		free(mf);
-
	}
-
	return EPKG_FATAL;
+
	free(bv);
+
	free(mf);
+
	return ret;
}

-

-
int pkg_analyse_init_macho(__unused const char* stage) {
+
int
+
pkg_analyse_init_macho(__unused const char *stage)
+
{
	return EPKG_OK;
}

-
int pkg_analyse_macho(const bool developer_mode, struct pkg *pkg, const char *fpath) {
+
int
+
pkg_analyse_macho(const bool developer_mode, __unused struct pkg *pkg, __unused const char *fpath)
+
{
	int ret = EPKG_OK;
	// int ret = analyse_macho(pkg, fpath);
	if (developer_mode) {
@@ -223,6 +331,8 @@ int pkg_analyse_macho(const bool developer_mode, struct pkg *pkg, const char *fp
	return ret;
}

-
int pkg_analyse_close_macho() {
+
int
+
pkg_analyse_close_macho()
+
{
	return EPKG_OK;
}
modified libpkg/private/binfmt.h
@@ -1,5 +1,13 @@
+
/*-
+
 * Copyright (c) 2024 Keve Müller <kevemueller@users.github.com>
+
 *
+
 * SPDX-License-Identifier: BSD-2-Clause
+
 */
+

#pragma once

+
#include "private/pkg.h"
+

int pkg_get_myarch_elfparse(int fd, struct os_info *oi);
int pkg_analyse_init_elf(const char* stage);
int pkg_analyse_elf(const bool developer_mode, struct pkg *pkg, const char *fpath);
modified libpkg/private/binfmt_macho.h
@@ -1,26 +1,7 @@
/*-
 * Copyright (c) 2024 Keve Müller <kevemueller@users.github.com>
 *
-
 * Redistribution and use in source and binary forms, with or without
-
 * modification, are permitted provided that the following conditions
-
 * are met:
-
 * 1. Redistributions of source code must retain the above copyright
-
 *    notice, this list of conditions and the following disclaimer
-
 *    in this position and unchanged.
-
 * 2. Redistributions in binary form must reproduce the above copyright
-
 *    notice, this list of conditions and the following disclaimer in the
-
 *    documentation and/or other materials provided with the distribution.
-
 *
-
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
-
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
-
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
-
 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
-
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
-
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
-
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
 * SPDX-License-Identifier: BSD-2-Clause
 */

#ifndef _PKG_BINFMT_MACHO_H
modified tests/frontend/abi.sh
@@ -13,17 +13,25 @@ native_body() {
	if [ "$thisarch" = "unknown" -o "${OS}" = "Darwin" ]; then
		thisarch=$(uname -m)
	fi
-
	if [ "${OS}" = "Linux" ]; then
-
		version=$(readelf -n /bin/uname  | awk '/ABI: / { split($NF, a, "."); print a[1]"."a[2] }')
-
	else
-
		version=$(uname -r | cut -d. -f1)
-
	fi
-
	if [ "${OS}" = "FreeBSD" ]; then
-
		thisarch=$(echo "${thisarch}" | sed s/x86_64/amd64/)
-
		thisabi=$(echo "${thisarch}" | sed s/amd64/x86:64/)
-
	else
-
		thisabi=$thisarch
-
	fi
+
	thisabi=$thisarch
+
	case "${OS}" in
+
		Linux)
+
			version=$(readelf -n /bin/uname  | awk '/ABI: / { split($NF, a, "."); print a[1]"."a[2] }')
+
			;;
+
		Darwin)
+
			# without a hint, the first arch is selected, which happens to be consistently x86_64
+
			thisarch="x86_64"
+
			version=$(uname -r | cut -d. -f1)
+
			;;
+
		FreeBSD)
+
			version=$(uname -r | cut -d. -f1)
+
			thisarch=$(echo "${thisarch}" | sed s/x86_64/amd64/)
+
			thisabi=$(echo "${thisarch}" | sed s/amd64/x86:64/)
+
			;;
+
		*)
+
			version=$(uname -r | cut -d. -f1)
+
			;;
+
	esac
	_expected="${OS}:${version}:${thisarch}\n"
	atf_check \
		-o inline:"${_expected}" \
@@ -114,6 +122,7 @@ machoparse_body() {
		-o inline:"${_expected}" \
		pkg -o IGNORE_OSMAJOR=1 -o ABI_FILE=$(atf_get_srcdir)/macos150.bin config altabi

+
	# macosfat.bin has amd64 as its first entry
	_expected="Darwin:24:amd64\n"
	atf_check \
		-o inline:"${_expected}" \
@@ -123,4 +132,92 @@ machoparse_body() {
	atf_check \
		-o inline:"${_expected}" \
		pkg -o IGNORE_OSMAJOR=1 -o ABI_FILE=$(atf_get_srcdir)/macosfat.bin config altabi
+

+
	# explicitely select an existing fat entry
+
	_expected="Darwin:24:amd64\n"
+
	atf_check \
+
		-o inline:"${_expected}" \
+
		pkg -o IGNORE_OSMAJOR=1 -o ABI_FILE=$(atf_get_srcdir)/macosfat.bin#amd64 config abi
+

+
	_expected="darwin:24:x86:64\n"
+
	atf_check \
+
		-o inline:"${_expected}" \
+
		pkg -o IGNORE_OSMAJOR=1 -o ABI_FILE=$(atf_get_srcdir)/macosfat.bin#amd64 config altabi
+

+
	_expected="Darwin:24:aarch64\n"
+
	atf_check \
+
		-o inline:"${_expected}" \
+
		pkg -o IGNORE_OSMAJOR=1 -o ABI_FILE=$(atf_get_srcdir)/macosfat.bin#aarch64 config abi
+

+
	_expected="darwin:24:aarch64:64\n"
+
	atf_check \
+
		-o inline:"${_expected}" \
+
		pkg -o IGNORE_OSMAJOR=1 -o ABI_FILE=$(atf_get_srcdir)/macosfat.bin#aarch64 config altabi
+

+
	# explicitely select a fat entry that is not in the ABI_FILE
+
	_expected="Scanned 2 entries, found none matching selector i386\n"
+
	atf_check \
+
		-s exit:1 \
+
		-o inline:"${_expected}" \
+
		-e match:"Unable to determine ABI" \
+
		pkg -o IGNORE_OSMAJOR=1 -o ABI_FILE=$(atf_get_srcdir)/macosfat.bin#i386 config abi
+

+
	atf_check \
+
		-s exit:1 \
+
		-o inline:"${_expected}" \
+
		-e match:"Unable to determine ABI" \
+
		pkg -o IGNORE_OSMAJOR=1 -o ABI_FILE=$(atf_get_srcdir)/macosfat.bin#i386 config altabi
+

+
	# explicitely select a fat entry that is not a valid architecture, hence not in the ABI_FILE
+
	_expected="Scanned 2 entries, found none matching selector abc\n"
+
	atf_check \
+
		-s exit:1 \
+
		-o inline:"${_expected}" \
+
		-e match:"Unable to determine ABI" \
+
		pkg -o IGNORE_OSMAJOR=1 -o ABI_FILE=$(atf_get_srcdir)/macosfat.bin#abc config abi
+

+
	atf_check \
+
		-s exit:1 \
+
		-o inline:"${_expected}" \
+
		-e match:"Unable to determine ABI" \
+
		pkg -o IGNORE_OSMAJOR=1 -o ABI_FILE=$(atf_get_srcdir)/macosfat.bin#abc config altabi
+

+
	# if the binary is not universal, selecting the first entry is not commentable
+
	_expected="Darwin:24:aarch64\n"
+
	atf_check \
+
		-o inline:"${_expected}" \
+
		-e not-match:"picking first" \
+
		pkg -d -o IGNORE_OSMAJOR=1 -o ABI_FILE=$(atf_get_srcdir)/macos.bin config abi
+

+
	_expected="darwin:24:aarch64:64\n"
+
	atf_check \
+
		-o inline:"${_expected}" \
+
		-e not-match:"picking first" \
+
		pkg -d -o IGNORE_OSMAJOR=1 -o ABI_FILE=$(atf_get_srcdir)/macos.bin config altabi
+

+
	_expected="Scanned 1 entry, found none matching selector abc\n"
+
	atf_check \
+
		-s exit:1 \
+
		-o inline:"${_expected}" \
+
		-e match:"Unable to determine ABI" \
+
		pkg -d -o IGNORE_OSMAJOR=1 -o ABI_FILE=$(atf_get_srcdir)/macos.bin#abc config abi
+

+
	atf_check \
+
		-s exit:1 \
+
		-o inline:"${_expected}" \
+
		-e match:"Unable to determine ABI" \
+
		pkg -d -o IGNORE_OSMAJOR=1 -o ABI_FILE=$(atf_get_srcdir)/macos.bin#abc config altabi
+

+
	# if the binary is universal, selecting the first entry is to be commented
+
	_expected="Darwin:24:amd64\n"
+
	atf_check \
+
		-o inline:"${_expected}" \
+
		-e match:"picking first" \
+
		pkg -d -o IGNORE_OSMAJOR=1 -o ABI_FILE=$(atf_get_srcdir)/macosfat.bin config abi
+

+
	_expected="darwin:24:x86:64\n"
+
	atf_check \
+
		-o inline:"${_expected}" \
+
		-e match:"picking first" \
+
		pkg -d -o IGNORE_OSMAJOR=1 -o ABI_FILE=$(atf_get_srcdir)/macosfat.bin config altabi
}