#!/bin/sh # Copyright (c) 2017 Jonas 'Sortie' Termansen. # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # # tix-check # Check the operating system installation. set -e collection="" sysroot="" existence=false owner=false permissions=false unknown_files=false dashdash= previous_option= for argument do if test -n "$previous_option"; then eval $previous_option=\$argument previous_option= shift continue fi case $argument in *=?*) parameter=$(expr "X$argument" : '[^=]*=\(.*\)' || true) ;; *=) parameter= ;; *) parameter=yes ;; esac case $dashdash$argument in --) dashdash=yes ;; --collection=*) collection=$parameter ;; --collection) previous_option=collection ;; --existence) existence=true ;; --owner) owner=true ;; --permissions) permissions=true ;; --sysroot) previous_option=sysroot ;; --sysroot=*) sysroot=$parameter ;; --unknown-files) unknown_files=true ;; -*) echo "$0: unrecognized option $argument" >&2 exit 1 ;; *) break ;; esac shift done if test -n "$previous_option"; then echo "$0: option '$argument' requires an argument" >&2 exit 1 fi # TODO: Reject additional operands. if [ -z "$collection" ]; then collection="$sysroot" fi if [ -n "$collection" ]; then collection=$(cd "$collection" && pwd) fi tmp= trap '[ -n "$tmp" ] && rm -rf "$tmp"' EXIT HUP INT QUIT TERM tmp=$(mktemp -dt tix-check.XXXXXX) mkdir -p "$tmp/tmp" export TMPDIR="$tmp/tmp" escape_extended_regex_sed() { printf "%s\n" "$1" | sed -E -e 's/[[$()*?\+.^{|}'"$2"']/\\\0/g' } # TODO: More portable way of doing this. getuid() { stat -- "$1" | grep -E '^UID: ' | sed -E -e 's/^UID: //' -e 's,/.*,,' } # TODO: More portable way of doing this. getgid() { stat -- "$1" | grep -E '^GID: ' | sed -E -e 's/^GID: //' -e 's,/.*,,' } # TODO: More portable way of doing this. getmode() { stat -- "$1" | grep -E '^Mode: ' | sed -E -e 's,^Mode: [^/]*/,,' } # TODO: Rewrite in C with the following model. # - Every file must be in one state of being tracked: # - Be tracked by exactly one manifest. # - (Other states?) # - Be a local file inside system directories (unknown file). # - Be a local file outside system directories. exit_code=0 # TODO: Store sha256sum of each file in metadata so we can do file # consistency checks. if $existence || $owner || $permissions; then for manifest in "$collection/tix/manifest/"*; do manifest_name=$(basename -- "$manifest") # TODO: Verify absolute paths. cat -- "$manifest" | \ while read -r path; do # TODO: Handle symbolic links that are intentionally dangling like # /share/vim/vimrc. if [ ! -e "$collection$path" ]; then if $existence; then printf "%s: %s: %s%s: Missing file\n" "$0" "$manifest_name" "$collection" "$path" >&2 exit_code=1 fi continue fi if $owner; then expected_uid=0 expected_gid=0 uid=$(getuid "$collection$path") gid=$(getgid "$collection$path") if [ "$uid" != "$expected_uid" ]; then printf "%s: %s: %s%s: Owned by uid %s instead of expected uid %s\n" "$0" "$manifest_name" "$collection" "$path" "$uid" "$expected_uid" >&2 exit_code=1 fi if [ "$gid" != "$expected_gid" ]; then printf "%s: %s: %s%s: Owned by gid %s instead of expected gid %s\n" "$0" "$manifest_name" "$collection" "$path" "$gid" "$expected_gid" >&2 exit_code=1 fi fi if $permissions; then if [ -L "$collection$path" ]; then expected_mode=lrwxrwxrwx elif [ -d "$collection$path" ]; then expected_mode=drwxr-xr-x else # TODO: Store the intended file permissions in tix metadata. case "$path" in /bin/*) expected_mode=-rwxr-xr-x ;; /boot/sortix.bin) expected_mode=-rwxr-xr-x ;; /etc/grub.d/README) expected_mode=-rw-r--r-- ;; /etc/grub.d/*) expected_mode=-rwxr-xr-x ;; /libexec/*) expected_mode=-rwxr-xr-x ;; /lib/grub/i386-pc/*.exec) expected_mode=-rwxr-xr-x ;; /lib/grub/i386-pc/*.image) expected_mode=-rwxr-xr-x ;; /lib/grub/i386-pc/*.module) expected_mode=-rwxr-xr-x ;; /sbin/*) expected_mode=-rwxr-xr-x ;; /src/*) expected_mode= ;; # TODO. *) expected_mode=-rw-r--r-- ;; esac fi mode=$(getmode "$collection$path") if [ -n "$expected_mode" ] && [ "$mode" != "$expected_mode" ]; then printf "%s: %s: %s%s: Mode is %s instead of expected mode %s\n" "$0" "$manifest_name" "$collection" "$path" "$mode" "$expected_mode" >&2 exit_code=1 fi fi done done fi if $unknown_files; then # TODO: More exclusion patterns. exclude_directories="/dev|/home|/mnt|/sysmerge|/tix|/tmp|/var/cache|/var/log|/var/run|/var/www" # Find all known directories. for manifest in "$collection/tix/manifest/"*; do # TODO: Verify absolute paths. cat -- "$manifest" | \ grep -Ev '^('"$exclude_directories"')($|/)' | while read -r path; do if [ -L "$collection$path" ]; then : elif [ -d "$collection$path" ]; then printf "%s\n" "$path" fi done # TODO: pipefail done | \ LC_ALL=C sort -u > "$tmp/directories" # Find all known files. for manifest in "$collection/tix/manifest/"*; do # TODO: Verify absolute paths. grep -Ev '^('"$exclude_directories"')/' -- "$manifest" # TODO: pipefail done | \ LC_ALL=C sort -u > "$tmp/expected" # List the contents of each system directory. cat -- "$tmp/directories" | while read -r directory; do (cd "${collection:-/}" && printf '%s\n' "$directory" && ls -1A -- ".$directory" | sed -E -e "s,^,$(escape_extended_regex_sed "$directory" ,)/," -e 's,//,/,') # TODO: pipefail done | \ LC_ALL=C sort -u \ > "$tmp/actual" # TODO: This requires diffutils in the minimal ports. diff -- "$tmp/expected" "$tmp/actual" || true cp -- "$tmp/directories" /tmp/directories cp -- "$tmp/expected" /tmp/expected cp -- "$tmp/actual" /tmp/actual fi exit $exit_code