#!/bin/sh -eu
# Copyright 2016-2018 Samuel Holland <samuel@sholland.org>
# SPDX-License-Identifier: 0BSD

ME=${0##*/}
VERSION=0.2.0

echo() {
	printf '%s\n' "$*"
}

echo_lines() {
	printf '%s\n' "$@"
}

msg() {
	printf >&2 '%s: %s\n' "$ME" "$*"
}

musl_arch() {
	$ROOT/usr/lib/libc.so 2>&1 | sed -n 's/^musl libc (\(.*\))$/\1/p'
}

musl_version() {
	$ROOT/usr/lib/libc.so 2>&1 | sed -n 's/^Version //p'
}

read_ldso_conf() {
	local conf="$*" d dir file glob line

	# Start with the default "trusted" directories
	set -- /lib /usr/lib
	for file in $conf; do
		test -r "$file" || continue
		$VERBOSE && msg "Reading ${file}"
		while read -r line; do
			line=$(tokenize ${line%%#*})
			if test "${line#include }" != "$line"; then
				glob=${file%/*}/${line#include }
				$VERBOSE && msg "Including ${glob}"
				line=$(read_ldso_conf $glob)
			fi
			for dir in $line; do
				# Ignore missing directories
				test -d "$ROOT$dir" || continue
				# Ignore duplicate directories
				for d; do test "$d" = "$dir" && continue 2; done
				set -- "$@" "$dir"
			done
		done < "$file"
	done

	echo_lines "$@"
}

tokenize() {
	echo "$*" | sed 's/=libc[456]//g;y/:,/  /' | xargs
}

LDSO_CACHE="/etc/ld.so.cache"
LDSO_CONF="/etc/ld.so.conf"
LIBRARY_MODE=false
ONLYARG_MODE=false
PRINT_CACHE=false
PRINT_VERSION=false
ROOT=
UPDATE_CACHE=true
UPDATE_LINKS=true
VERBOSE=false

while getopts ":c:C:Df:ilnNpr:vVX" OPTION; do
	case "$OPTION" in
	C)
		LDSO_CACHE=$OPTARG
		;;
	D)
		UPDATE_CACHE=false
		UPDATE_LINKS=false
		;;
	f)
		LDSO_CONF=$OPTARG
		;;
	l)
		LIBRARY_MODE=true
		;;
	n)
		ONLYARG_MODE=true
		;;
	N)
		UPDATE_CACHE=false
		;;
	p)
		PRINT_CACHE=true
		;;
	r)
		ROOT=$OPTARG
		if ! test -x $ROOT/usr/lib/libc.so; then
			msg "${ROOT} does not appear to be a valid root directory"
			exit 1
		fi
		;;
	v)
		VERBOSE=true
		;;
	V)
		PRINT_VERSION=true
		;;
	X)
		UPDATE_LINKS=false
		;;
	c|i)
		msg "Ignored option -${OPTION}"
		;;
	\?)
		msg "Invalid option -${OPTARG}"
		cat >&2 <<- EOF
		Usage: $ME [-DnNvX] [-C cache] [-f conf] [-r root] [dir...]
		       $ME [-v] -l lib...
		       $ME [-v] [-C cache] [-r root] -p
		       $ME -V
		EOF
		exit 1
		;;
	esac
done
shift $((OPTIND-1))

BANNER="ldconfig ${VERSION} for musl $(musl_version)"
LDSO_CACHE="$ROOT$LDSO_CACHE"
LDSO_CONF="$ROOT$LDSO_CONF"
LDSO_PATH="$ROOT/etc/ld-musl-$(musl_arch).path"
LDSO_PATH_TMP="$LDSO_PATH.$$"

if $PRINT_VERSION; then
	echo "$BANNER"
	exit 0
elif $PRINT_CACHE; then
	test -r "$LDSO_PATH" && cat "$LDSO_PATH"
	exit 0
fi

$VERBOSE && echo "$BANNER"

if $LIBRARY_MODE; then
	for lib; do
		soname=$(scanelf -qS "$ROOT$lib")
		soname=${soname%% *}
	done
	exit 0
fi

# Update musl's list of library paths
if ! test -w "${LDSO_PATH%/*}"; then
	msg "You do not have permission to update ${LDSO_PATH}"
	exit 0
fi
trap 'rm -f "$LDSO_PATH_TMP"' EXIT
read_ldso_conf "$LDSO_CONF" > "$LDSO_PATH_TMP"
$VERBOSE && msg "Writing ${LDSO_PATH}"
mv "$LDSO_PATH_TMP" "$LDSO_PATH"
trap - EXIT

# Read the updated list of library paths
$ONLYARG_MODE || set -- "$@" $(cat "$LDSO_PATH")

if $UPDATE_LINKS; then
	for dir; do
		# Packages are responsible for libraries in these directories
		if test "$dir" = /lib || test "$dir" = /usr/lib; then
			continue
		fi
		$VERBOSE && msg "Scanning ${ROOT}${dir}"
		scanelf -qS "$ROOT$dir" | while read soname file; do
			link=${file%/*}/$soname
			test -f "$link" && continue
			$VERBOSE && msg "Creating link ${link}"
			ln -fns "${file##*/}" "$link"
		done
	done
fi

if $UPDATE_CACHE && test $# -gt 0; then
	touch "$ROOT$LDSO_CACHE"
fi