package-notes/generate-package-notes.sh

288 lines
9.8 KiB
Bash
Executable File

#!/bin/sh
# SPDX-License-Identifier: CC0-1.0
#
# Generates a linker script to insert a .note.package section with a
# JSON payload. The contents are derived from the specified options and the
# os-release file. Use the output with -Wl,-dT,/path/to/output in $LDFLAGS.
#
# $ ./generate-package-notes.sh --type rpm --name systemd --version 248~rc2-1.fc34 --architecture x86_64 --osCpe 'cpe:/o:fedoraproject:fedora:33'
# SECTIONS
# {
# .note.package (READONLY) : ALIGN(4) {
# BYTE(0x04) BYTE(0x00) BYTE(0x00) BYTE(0x00) /* Length of Owner including NUL */
# BYTE(0x7c) BYTE(0x00) BYTE(0x00) BYTE(0x00) /* Length of Value including NUL */
# BYTE(0x7e) BYTE(0x1a) BYTE(0xfe) BYTE(0xca) /* Note ID */
# BYTE(0x46) BYTE(0x44) BYTE(0x4f) BYTE(0x00) /* Owner: 'FDO\x00' */
# BYTE(0x7b) BYTE(0x22) BYTE(0x74) BYTE(0x79) /* Value: '{"type":"rpm","name":"systemd","version":"248~rc2-1.fc34","architecture":"x86_64","osCpe":"cpe:/o:fedoraproject:fedora:33"}\x00' */
# BYTE(0x70) BYTE(0x65) BYTE(0x22) BYTE(0x3a)
# BYTE(0x22) BYTE(0x72) BYTE(0x70) BYTE(0x6d)
# BYTE(0x22) BYTE(0x2c) BYTE(0x22) BYTE(0x6e)
# BYTE(0x61) BYTE(0x6d) BYTE(0x65) BYTE(0x22)
# BYTE(0x3a) BYTE(0x22) BYTE(0x73) BYTE(0x79)
# BYTE(0x73) BYTE(0x74) BYTE(0x65) BYTE(0x6d)
# BYTE(0x64) BYTE(0x22) BYTE(0x2c) BYTE(0x22)
# BYTE(0x76) BYTE(0x65) BYTE(0x72) BYTE(0x73)
# BYTE(0x69) BYTE(0x6f) BYTE(0x6e) BYTE(0x22)
# BYTE(0x3a) BYTE(0x22) BYTE(0x32) BYTE(0x34)
# BYTE(0x38) BYTE(0x7e) BYTE(0x72) BYTE(0x63)
# BYTE(0x32) BYTE(0x2d) BYTE(0x31) BYTE(0x2e)
# BYTE(0x66) BYTE(0x63) BYTE(0x33) BYTE(0x34)
# BYTE(0x22) BYTE(0x2c) BYTE(0x22) BYTE(0x61)
# BYTE(0x72) BYTE(0x63) BYTE(0x68) BYTE(0x69)
# BYTE(0x74) BYTE(0x65) BYTE(0x63) BYTE(0x74)
# BYTE(0x75) BYTE(0x72) BYTE(0x65) BYTE(0x22)
# BYTE(0x3a) BYTE(0x22) BYTE(0x78) BYTE(0x38)
# BYTE(0x36) BYTE(0x5f) BYTE(0x36) BYTE(0x34)
# BYTE(0x22) BYTE(0x2c) BYTE(0x22) BYTE(0x6f)
# BYTE(0x73) BYTE(0x43) BYTE(0x70) BYTE(0x65)
# BYTE(0x22) BYTE(0x3a) BYTE(0x22) BYTE(0x63)
# BYTE(0x70) BYTE(0x65) BYTE(0x3a) BYTE(0x2f)
# BYTE(0x6f) BYTE(0x3a) BYTE(0x66) BYTE(0x65)
# BYTE(0x64) BYTE(0x6f) BYTE(0x72) BYTE(0x61)
# BYTE(0x70) BYTE(0x72) BYTE(0x6f) BYTE(0x6a)
# BYTE(0x65) BYTE(0x63) BYTE(0x74) BYTE(0x3a)
# BYTE(0x66) BYTE(0x65) BYTE(0x64) BYTE(0x6f)
# BYTE(0x72) BYTE(0x61) BYTE(0x3a) BYTE(0x33)
# BYTE(0x33) BYTE(0x22) BYTE(0x7d) BYTE(0x00)
# }
# }
# INSERT AFTER .note.gnu.build-id;
# /* HINT: add -Wl,-dT,/path/to/this/file to $LDFLAGS */
#
# See https://systemd.io/COREDUMP_PACKAGE_METADATA/ for details.
json=
readonly="(READONLY) "
root=
help() {
echo "Usage: $0 [OPTION]..."
echo "Generate a package notes linker script from specified metadata."
echo
echo " -h, --help display this help and exit"
echo " --readonly BOOL whether to add the READONLY attribute to script (default: true)"
echo " --root PATH when a file (eg: os-release) is parsed, open it relatively to this hierarchy (default: not set)"
echo " --cpe VALUE NIST CPE identifier of the vendor operating system, or 'auto' to parse from system-release-cpe or os-release"
echo " --package-type TYPE set the package type (e.g. 'rpm' or 'deb')"
echo " --package-name NAME set the package name"
echo " --package-version VERSION set the package version"
echo " --package-architecture ARCH set the package architecture"
echo " --NAME VALUE set an arbitrary name/value pair"
}
invalid_argument() {
printf 'ERROR: "%s" requires a non-empty option argument.\n' "${1}" >&2
exit 1
}
append_parameter() {
if [ -z "${2}" ]; then
invalid_argument "${1}"
fi
# Posix-compatible substring check
case "$json" in
*"\"${1}\":"*) echo "Duplicated argument: --${1}"; exit 1 ;;
esac
if [ -z "${json}" ]; then
json="{\"${1}\":\"${2}\""
else
json="${json},\"${1}\":\"${2}\""
fi
}
# Support the same fixed parameters as the python script
parse_options() {
cpe=
while :; do
case $1 in
-h|-\?|--help)
help
exit
;;
--readonly)
if [ -z "${2}" ]; then
invalid_argument "${1}"
fi
case $2 in
no|NO|No|false|FALSE|False|0)
readonly=""
;;
esac
shift
;;
--root)
if [ -z "${2}" ] || [ ! -d "${2}" ]; then
invalid_argument "${1}"
fi
root="${2}"
shift
;;
--package-type)
append_parameter "type" "${2}"
shift
;;
--package-name)
append_parameter "name" "${2}"
shift
;;
--package-version)
append_parameter "version" "${2}"
shift
;;
--package-architecture)
append_parameter "architecture" "${2}"
shift
;;
--cpe)
if [ -z "${2}" ]; then
invalid_argument "${1}"
fi
cpe="${2}"
shift
;;
--debug-info-url)
append_parameter "debugInfoUrl" "${2}"
shift
;;
--*)
# Allow passing arbitrary name/value pairs
append_parameter "$(echo "${1}" | cut -c 3-)" "${2}"
shift
;;
-*)
printf 'WARNING: Unknown option (ignored): %s\n' "${1}" >&2
;;
*)
break
esac
shift
done
# Parse at the end, so that --root can be used in any position
if [ "${cpe}" = "auto" ]; then
if [ -r "${root}/usr/lib/system-release-cpe" ]; then
cpe="$(cat "${root}/usr/lib/system-release-cpe")"
elif [ -r "${root}/etc/os-release" ]; then
# shellcheck disable=SC1090 disable=SC1091
cpe="$(. "${root}/etc/os-release" && echo "${CPE_NAME}")"
elif [ -r "${root}/usr/lib/os-release" ]; then
# shellcheck disable=SC1090 disable=SC1091
cpe="$(. "${root}/usr/lib/os-release" && echo "${CPE_NAME}")"
fi
if [ -z "${cpe}" ]; then
printf 'ERROR: --cpe auto but cannot read %s/usr/lib/system-release-cpe or parse CPE_NAME from %s/etc/os-release or %s/usr/lib/os-release.\n' "${root}" "${root}" "${root}" >&2
exit 1
fi
fi
if [ -n "${cpe}" ]; then
append_parameter "osCpe" "${cpe}"
fi
# Terminate the JSON object
if [ -n "${json}" ]; then
json="${json}}"
fi
return "$#"
}
pad_comment() {
for _ in $(seq "$1"); do
printf '\\x00'
done
}
pad_string() {
for i in $(seq "$1"); do
if [ $(( ( $2 + i - 1) % 4 )) -eq 0 ]; then
printf '\n%sBYTE(0x00)' "${3}"
else
printf ' BYTE(0x00)'
fi
done
}
write_string() {
text="$1"
prefix="$2"
label="$3"
total="$4"
# We always have at least the terminating NULL
if [ $(( total % 4)) -eq 0 ]; then
padding_nulls=1
else
padding_nulls="$(( 1 + 4 - (total % 4) ))"
fi
for i in $(seq ${#text}); do
if [ $(( i % 4)) -eq 1 ]; then
printf '\n%s' "$prefix"
else
printf ' '
fi
byte=$(echo "${text}" | cut -c "${i}")
printf 'BYTE(0x%02x)' "'${byte}"
# Print the json object as a comment after the first 4 bytes
# to match the output of the older script, including padding NUL.
if [ "${i}" -eq 4 ]; then
printf " /* %s: '%s" "$label" "$text"
pad_comment "${padding_nulls}"
printf "' */"
fi
done
pad_string "${padding_nulls}" "${#text}" "$prefix"
printf '\n'
}
write_script() {
# NULL terminator is included in the size, but not padding
value_len=$(( ${#1} + 1 ))
if [ "${value_len}" -gt 65536 ]; then
printf 'ERROR: "%s" is too long.\n' "${1}" >&2
exit 1
fi
printf 'SECTIONS\n{\n'
printf ' .note.package %s: ALIGN(4) {\n' "${readonly}"
# Note that for the binary fields we use the native 4 bytes type, to avoid
# endianness issues.
printf ' LONG(0x0004) /* Length of Owner including NUL */\n'
printf ' LONG(0x%04x) /* Length of Value including NUL */\n' \
${value_len}
printf ' LONG(0xcafe1a7e) /* Note ID */\n'
printf " BYTE(0x46) BYTE(0x44) BYTE(0x4f) BYTE(0x00) /* Owner: 'FDO\\\\x00' */" # newline will be added by write_string
write_string "$1" ' ' 'Value' "$value_len"
printf ' }\n}\n'
printf 'INSERT AFTER .note.gnu.build-id;\n'
# shellcheck disable=SC2016
printf '/* HINT: add -Wl,-dT,/path/to/this/file to $LDFLAGS */\n'
}
if ! parse_options "$@" && [ "$#" -gt 0 ]; then
# Not supported on every distro
if [ -r "${root}/usr/lib/system-release-cpe" ]; then
cpe="$(cat "${root}/usr/lib/system-release-cpe")"
json_cpe=",\"osCpe\":\"${cpe}\""
fi
# old-style invocation with positional parameters for backward compatibility
json="$(printf '{"type":"rpm","name":"%s","version":"%s","architecture":"%s"%s}' "$1" "$2" "$3" "$json_cpe")"
elif [ -z "${json}" ]; then
help
exit 1
fi
write_script "$json"