X-Git-Url: http://git.ozlabs.org/?p=petitboot;a=blobdiff_plain;f=utils%2Fpb-plugin;h=e107f9648ba2e165aa31d6223e69eec5d8f8812f;hp=4e4165257e21b6bfa11b215c1ace2fa79317d220;hb=7460bbb60ff7070c6884b700fc6370cd58703c4b;hpb=dcc406653405c0b97c9474d99a66ac896557734c diff --git a/utils/pb-plugin b/utils/pb-plugin index 4e41652..e107f96 100755 --- a/utils/pb-plugin +++ b/utils/pb-plugin @@ -2,11 +2,12 @@ __dest=/ __pb_mount_dir=/var/petitboot/mnt/dev/ -__plugin_basedir=/tmp/ -plugin_file=pb-plugin.cpio.gz +plugin_abi=1 +plugin_ext=pb-plugin plugin_meta=pb-plugin.conf plugin_meta_dir=etc/preboot-plugins/ plugin_meta_path=$plugin_meta_dir$plugin_meta +plugin_wrapper_dir=/var/lib/pb-plugins/bin usage() { @@ -14,9 +15,10 @@ usage() Usage: $0 Where is one of: - run - run plugin from FILE/URL - scan - look for available plugins on attached devices - create - create a new plugin archive from DIR + install - install plugin from FILE/URL + scan - look for available plugins on attached devices + create - create a new plugin archive from DIR + lint - perform a pre-distribution check on FILE EOF } @@ -62,41 +64,95 @@ plugin_info() echo " (version $PLUGIN_VERSION)" } -__run_init() +parse_meta() { - local base dir + local file name value IFS + + file=$1 + + IFS='=' + while read -r name value + do + # Ensure we have a sensible variable name + echo "$name" | grep -q '^PLUGIN_[A-Z_]*$' || continue + + # we know that $name has no quoting/expansion chars, but we + # may need to do some basic surrounding-quote removal for + # $value, without evaluating it + value=$(echo "$value" | sed "s/^\([\"']\)\(.*\)\1\$/\2/g") + + export $name="$value" + done < $file +} + +# How the ABI versioning works: +# +# - This script has an ABI defined ($plugin_abi) +# +# - Plugins have a current ABI number ($PLUGIN_ABI), and a minimum supported +# ABI number ($PLUGIN_ABI_MIN). +# +# - A plugin is OK to run if: +# - the plugin's ABI matches the script ABI, or +# - the plugin's minimum ABI is lower than or equal to the script ABI +plugin_abi_check() +{ + [ -n "$PLUGIN_ABI" ] && + ( [ $PLUGIN_ABI -eq $plugin_abi ] || + [ $PLUGIN_ABI_MIN -le $plugin_abi ] ) +} + +do_wrap() +{ + local base binary dir base=$1 + binary=$2 + shift 2 - for dir in etc dev sys proc + for dir in etc dev sys proc var do - mkdir -p $base/$dir + [ -e "$base/$dir" ] || mkdir -p "$base/$dir" done cp /etc/resolv.conf $base/etc mount -o bind /dev $base/dev mount -o bind /sys $base/sys mount -o bind /proc $base/proc + mount -o bind /var $base/var + + chroot "$base" "$binary" "$@" + + umount $base/dev + umount $base/sys + umount $base/proc + umount $base/var } -__run_cleanup() +__create_wrapper() { - local base + local base binary wrapper base=$1 + binary=$2 + wrapper=$plugin_wrapper_dir/$(basename $binary) + + mkdir -p $plugin_wrapper_dir - [ -e $base/dev/null ] && umount $base/dev - [ -e $base/sys/kernel ] && umount $base/sys - [ -e $base/proc/stat ] && umount $base/proc - rm -rf $base + cat < $wrapper +#!/bin/sh + +exec $(realpath $0) __wrap '$base' '$binary' "\$@" +EOF + + chmod a+x $wrapper } -do_run() +do_install() { - local url executable + local url name file __dest url=$1 - executable=$2 if [ -z "$url" ] then @@ -129,7 +185,8 @@ do_run() echo sha256sum "$file" | cut -f1 -d' ' echo - echo "Do you want to run this plugin? (y/N)" + + echo "Do you want to install this plugin? (y/N)" read resp case $resp in @@ -151,39 +208,39 @@ do_run() exit 1 fi - . $__dest/$plugin_meta_path - - ( - executable=${PLUGIN_EXECUTABLE:-$executable} - - __run_init $__dest + parse_meta $__dest/$plugin_meta_path - printf "Entering plugin\n" - plugin_info - - chroot $__dest $executable + if ! plugin_abi_check + then + echo "Plugin at $url is incompatible with this firmware," \ + "exiting." + rm -rf $__dest + exit 1 + fi - printf "\nExiting plugin & cleaning up\n" - __run_cleanup $__dest - ) + for binary in ${PLUGIN_EXECUTABLES} + do + __create_wrapper "$__dest" "$binary" + done + echo "Plugin installed" + plugin_info } -do_scan() +do_scan_mount() { - local found dev plugin_path __meta_tmp - found=0 - for mnt in $__pb_mount_dir/* - do - dev=$(basename $mnt) - plugin_path="$mnt/$plugin_file" + local mnt dev plugin_path __meta_tmp + mnt=$1 + dev=$(basename $mnt) + for plugin_path in $mnt/*.$plugin_ext + do [ -e "$plugin_path" ] || continue # extract plugin metadata to a temporary directory __meta_tmp=$(mktemp -d) [ -d $__meta_tmp ] || continue - gunzip -c "$plugin_path" | + gunzip -c "$plugin_path" 2>/dev/null | (cd $__meta_tmp && cpio -i -d $plugin_meta_path 2>/dev/null) if ! [ $? = 0 -a -e "$plugin_path" ] @@ -193,17 +250,32 @@ do_scan() fi ( - . $__meta_tmp/$plugin_meta_path + parse_meta $__meta_tmp/$plugin_meta_path + + plugin_abi_check || exit 1 printf "Plugin found on %s:\n" $dev plugin_info printf "\n" printf "To run this plugin:\n" - printf " $0 run $plugin_path\n" + printf " $0 install $plugin_path\n" printf "\n" ) + if [ $? = 0 ] + then + found=1 + fi rm -rf $__meta_tmp - found=1 + done +} + +do_scan() +{ + local found mnt + found=0 + for mnt in $__pb_mount_dir/* + do + do_scan_mount $mnt done if [ "$found" = 0 ] @@ -259,21 +331,28 @@ EOF cat < $file +PLUGIN_ABI='$plugin_abi' +PLUGIN_ABI_MIN='1' PLUGIN_VENDOR='$vendorname' +PLUGIN_VENDOR_ID='$vendorshortname' PLUGIN_NAME='$pluginname' +PLUGIN_ID='$pluginshortname' PLUGIN_VERSION='$version' PLUGIN_DATE='$date' -PLUGIN_EXECUTABLE='$executable' +PLUGIN_EXECUTABLES='$executables' EOF } @@ -313,38 +392,19 @@ do_create() fi # Sanity check metadata file - ( - . $meta_file - if [ ! -n "$PLUGIN_VENDOR" ] - then - echo "error: no PLUGIN_VENDOR defined in metadata" &>2 - exit 1 - fi - if [ ! -n "$PLUGIN_NAME" ] - then - echo "error: no PLUGIN_NAME defined in metadata" &>2 - exit 1 - fi - if [ ! -n "$PLUGIN_VERSION" ] - then - echo "error: no PLUGIN_VERSION defined in metadata" &>2 - exit 1 - fi - if [ ! -n "$PLUGIN_DATE" ] - then - echo "error: no PLUGIN_DATE defined in metadata" &>2 - exit 1 - fi - if [ ! -n "$PLUGIN_EXECUTABLE" ] - then - echo "error: no PLUGIN_EXECUTABLE defined in metadata" \ - &>2 - exit 1 - fi + parse_meta $meta_file - ) || exit 1 + errors=0 + warnings=0 + + lint_metadata + + if [ $errors -ne 0 ] + then + exit 1 + fi - outfile=$plugin_file + outfile=${PLUGIN_ID}-${PLUGIN_VERSION}.${plugin_ext} ( cd $src @@ -357,24 +417,145 @@ do_create() echo echo "User-visible metadata:" - - ( - . $meta_file - plugin_info | sed -e 's/^/ /' - ) + plugin_info | sed -e 's/^/ /' echo - cat < /dev/null 2>&1 || + lint_fatal "Plugin can't be gunzipped" + + gunzip -c "$plugin_file" 2>/dev/null | cpio -t >/dev/null 2>&1 || + lint_fatal "Plugin can't be cpioed" + + __dest=$(mktemp -d) + gunzip -c "$plugin_file" | ( cd $__dest && cpio -i -d 2>/dev/null) + + [ -e "$__dest/$plugin_meta_path" ] || + lint_fatal "No metadata file present (expecting" \ + "$plugin_meta_path)" + + parse_meta "$__dest/$plugin_meta_path" + lint_metadata + + for executable in ${PLUGIN_EXECUTABLES} + do + exec_path="$__dest/$executable" + [ -e "$exec_path" ] || { + lint_err "PLUGIN_EXECUTABLES item $executable" \ + "doesn't exist" + continue + } + + [ -x "$exec_path" ] || + lint_err "PLUGIN_EXECUTABLES item $executable" \ + "isn't executable" + done + + for dir in dev sys proc var + do + [ -e "$__dest/$dir" ] || continue + + [ -d "$__dest/$dir" ] || + lint_err "/$dir exists, but isn't a directory" + + [ "$(find $__dest/$dir -mindepth 1)" ] && + lint_warn "/$dir contains files/directories," \ + "these will be lost during chroot setup" + done + + printf '%s: %d errors, %d warnings\n' $plugin_file $errors $warnings + rm -rf $__dest + [ $errors = 0 ] +} + test_http_download() { local tmp ref @@ -411,23 +592,126 @@ test_ftp_download() cmp -s "$ref" "$tmp" } +test_abi_check() +{ + ( + plugin_abi=$1 + PLUGIN_ABI=$2 + PLUGIN_ABI_MIN=$3 + plugin_abi_check + ) +} + test_scan() { __pb_mount_dir="$test_tmpdir/mnt" mnt_dir="$__pb_mount_dir/sda" mkdir -p $mnt_dir/$plugin_meta_dir ( + echo "PLUGIN_ABI=$plugin_abi" echo "PLUGIN_NAME=test" echo "PLUGIN_VERSION=1" - echo "PLUGIN_EXECUTABLE=/bin/sh" + echo "PLUGIN_EXECUTABLES=/bin/sh" ) > $mnt_dir/$plugin_meta_path ( cd $mnt_dir; find -mindepth 1 | cpio -o -Hnewc 2>/dev/null - ) | gzip -c > $mnt_dir/$plugin_file + ) | gzip -c > $mnt_dir/test.$plugin_ext - do_scan | grep -q 'test 1' - rc=$? + do_scan | grep -q 'test' +} + +test_scan_nogzip() +{ + __pb_mount_dir="$test_tmpdir/mnt" + mnt_dir="$__pb_mount_dir/sda" + stderr_file="$test_tmpdir/stderr" + + mkdir -p $mnt_dir + echo "invalid" > $mnt_dir/nogzip.$plugin_ext + + do_scan 2>$stderr_file | grep -q 'No plugins' + + [ $? = 0 ] || return 1 + + if [ -s "$stderr_file" ] + then + echo "Scan with invalid (non-gzip) file produced error output" \ + >&2 + cat "$stderr_file" + return 1 + fi + true +} + +test_scan_nocpio() +{ + __pb_mount_dir="$test_tmpdir/mnt" + mnt_dir="$__pb_mount_dir/sda" + stderr_file="$test_tmpdir/stderr" + + mkdir -p $mnt_dir + echo "invalid" | gzip -c > $mnt_dir/nogzip.$plugin_ext + + do_scan 2>$stderr_file | grep -q 'No plugins' + + [ $? = 0 ] || return 1 + + if [ -s "$stderr_file" ] + then + echo "Scan with invalid (non-cpio) file produced error output" \ + >&2 + cat "$stderr_file" + return 1 + fi + true +} + +test_scan_multiple() +{ + __pb_mount_dir="$test_tmpdir/mnt" + mnt_dir="$__pb_mount_dir/sda" + outfile=$test_tmpdir/scan.out + + for i in 1 2 + do + mkdir -p $mnt_dir/$plugin_meta_dir + ( + echo "PLUGIN_ABI=$plugin_abi" + echo "PLUGIN_NAME=test-$i" + echo "PLUGIN_VERSION=1" + echo "PLUGIN_EXECUTABLES=/bin/sh" + ) > $mnt_dir/$plugin_meta_path + ( + cd $mnt_dir; + find -mindepth 1 | cpio -o -Hnewc 2>/dev/null + ) | gzip -c > $mnt_dir/test-${i}.$plugin_ext + rm -rf $mnt_dir/$plugin_meta_dir + done + + do_scan >$outfile + + grep -q 'test-1' $outfile && grep -q 'test-2' $outfile +} + +test_scan_wrongabi() +{ + __pb_mount_dir="$test_tmpdir/mnt" + mnt_dir="$__pb_mount_dir/sda" + mkdir -p $mnt_dir/$plugin_meta_dir + ( + echo "PLUGIN_ABI=$(($plugin_abi + 1))" + echo "PLUGIN_ABI_MIN=$(($plugin_abi + 1))" + echo "PLUGIN_NAME=test" + echo "PLUGIN_VERSION=1" + echo "PLUGIN_EXECUTABLES=/bin/sh" + ) > $mnt_dir/$plugin_meta_path + ( + cd $mnt_dir; + find -mindepth 1 | cpio -o -Hnewc 2>/dev/null + ) | gzip -c > $mnt_dir/test.$plugin_ext + + do_scan | grep -q 'No plugins' } test_empty_scan() @@ -494,7 +778,18 @@ do_tests() do_test is_url "git+ssh://example.com/path" do_test test_http_download do_test test_ftp_download + do_test ! test_abi_check + do_test ! test_abi_check 1 + do_test test_abi_check 1 1 + do_test test_abi_check 1 1 1 + do_test test_abi_check 1 2 0 + do_test test_abi_check 1 2 1 + do_test ! test_abi_check 1 2 2 do_test test_scan + do_test test_scan_nogzip + do_test test_scan_nocpio + do_test test_scan_multiple + do_test test_scan_wrongabi do_test test_empty_scan if [ $test_failed = 0 ] @@ -502,15 +797,16 @@ do_tests() echo "$n tests passed" else echo "Tests failed" - false fi rm -rf "$tests_tmpdir" + + [ $test_failed = 0 ] } case "$1" in -run) +install) shift - do_run $@ + do_install $@ ;; scan) shift @@ -520,6 +816,14 @@ create) shift do_create $@ ;; +lint) + shift + do_lint $@ + ;; +__wrap) + shift + do_wrap $@ + ;; __test) shift do_tests $@ @@ -530,7 +834,7 @@ __test) exit 1 ;; *) - echo "Invalid command: $s" >&2 + echo "Invalid command: $1" >&2 usage exit 1 esac