#! /usr/bin/env bash # icecc -- A simple distributed compiler system # # Copyright (C) 2004 by the Icecream Authors # GPL target_paths= target_aliases= # Always prints, optionally to a log file print_output () { if test -n "$log_path"; then echo "$@" | tee -a "$log_path" else echo "$@" fi } # Only prints if the debug flag is specified print_debug () { if test -n "$debug"; then print_output "$@" fi } is_dynamic_elf () { # Is the file an dynamically linked ELF executable? (file -L "$1" | grep 'ELF' > /dev/null 2>&1) && (! file -L "$1" | grep 'static' > /dev/null 2>&1) } fix_rpath () { # Patches the RPATH for a file. When the program is executed in the chroot # be iceccd, /proc is not mounted. As such, $ORIGIN can't be resolved. To # work around this, replace all instances of $ORIGIN in RPATH with the # known chroot path to the executables directory local path="$1" local origin="$2" if ! is_dynamic_elf "$path"; then return fi local old_rpath="`$PATCHELF --print-rpath "$path"`" local new_rpath="`echo "$old_rpath" | \ sed 's/.*\[\(.*\)\]/\1/g' | \ sed "s,\\\$ORIGIN,/$origin,g"`" if test -n "$new_rpath"; then print_debug "Converting RPATH '$old_rpath' -> '$new_rpath'" $PATCHELF --set-rpath "$new_rpath" "$path" fi } add_path () { case " $target_paths " in *" $1 "*) return 1 ;; *) target_paths="$target_paths $1" return 0 ;; esac } add_alias () { if test "$1" != "$2"; then local alias="$1=$2" case " $target_aliases " in *" $alias "*) ;; *) print_debug "Adding alias '$2' -> '$1'" target_aliases="$target_aliases $alias" ;; esac fi } normalize_path () { # Normalizes the path to a file or directory, removing all "." and ".." # entries. Use pwd -L to explicitly prevent symlink expansion local path=$1 if test -f "$path"; then pushd $(dirname $path) > /dev/null 2>&1 dir_path=$(pwd -L) path=$dir_path/$(basename $path) popd > /dev/null 2>&1 elif test -d "$path"; then pushd $path > /dev/null 2>&1 path=$(pwd -L) popd > /dev/null 2>&1 fi echo $path } add_file_common() { local p="$1" local path="$2" local alias="$3" add_alias "$path" "$p" if test -n "$alias"; then add_alias "$path" "$alias" fi add_path "$path" || return 1 print_debug "Adding file '$path'" return 0 } add_deps() { local path="$1" local interp="$2" if test -n "$interp" && test -x "$interp"; then # Use the dynamic loaders --list argument to list the # dependencies. The program may have a different program # interpreter (typical when using uninative tarballs), which is # why we can't just call ldd. deps="`$interp --list "$path"`" else deps="`ldd "$path"`" fi print_debug "Dependencies are:" print_debug "$deps" if test -n "$deps"; then for lib in $deps; do # ldd now outputs ld as /lib/ld-linux.so.xx on current nptl # based glibc this regexp parse the outputs like: # ldd /usr/bin/gcc # linux-gate.so.1 => (0xffffe000) # libc.so.6 => /lib/tls/libc.so.6 (0xb7e81000) # /lib/ld-linux.so.2 (0xb7fe8000) # covering both situations ( with => and without ) lib="`echo "$lib" | sed -n 's,^[^/]*\(/[^ ]*\).*,\1,p'`" test -f "$lib" || continue # Check whether the same library also exists in the parent # directory, and prefer that on the assumption that it is a # more generic one. local baselib=`echo "$lib" | sed 's,\(/[^/]*\)/.*\(/[^/]*\)$,\1\2,'` test -f "$baselib" && lib=$baselib add_dependency "$lib" "$interp" done fi } add_dependency() { local p=`normalize_path $1` # readlink is required for Yocto, so we can use it local path=`readlink -f "$p"` local interp="$2" add_file_common "$p" "$path" || return if test -x "$path" && is_dynamic_elf "$path"; then add_deps "$path" "$interp" fi } add_file () { local p=`normalize_path $1` # readlink is required for Yocto, so we can use it local path=`readlink -f "$p"` add_file_common "$p" "$path" "$2" || return if test -x "$path" && is_dynamic_elf "$path"; then # Request the program interpeter (dynamic loader) interp=`readelf -W -l "$path" | grep "Requesting program interpreter:" | sed "s/\s*\[Requesting program interpreter:\s*\(.*\)\]/\1/g"` print_debug "Interpreter is '$interp'" add_deps "$path" "$interp" fi } while test -n "$1"; do case "$1" in --respect-path) # Ignore for backward compatability ;; --debug) debug=1 ;; --log) do_log=1 ;; --extra=*) extra_tools="$extra_tools ${1#--extra=}" ;; *) break ;; esac shift done added_gcc=$1 shift added_gxx=$1 shift added_as=$1 shift archive_name=$1 if test -n "$do_log"; then log_path="$archive_name.log" rm -f "$log_path" fi if test -z "$PATCHELF"; then PATCHELF=`which patchelf 2> /dev/null` fi if test -z "$PATCHELF"; then PATCHELF=`which patchelf-uninative 2> /dev/null` fi if test -z "$PATCHELF"; then print_output "patchelf is required" exit 1 fi if test -z "$added_gcc" || test -z "$added_gxx" ; then print_output "usage: $0 " exit 1 fi if ! test -x "$added_gcc" ; then print_output "'$added_gcc' is not executable." exit 1 fi if ! test -x "$added_gxx" ; then print_output "'$added_gcc' is not executable." exit 1 fi add_file $added_gcc /usr/bin/gcc add_file $added_gxx /usr/bin/g++ if test -z "$added_as" ; then add_file /usr/bin/as /usr/bin/as else if ! test -x "$added_as" ; then print_output "'$added_as' is not executable." exit 1 fi add_file $added_as /usr/bin/as fi add_file `$added_gcc -print-prog-name=cc1` /usr/bin/cc1 add_file `$added_gxx -print-prog-name=cc1plus` /usr/bin/cc1plus specfile=`$added_gcc -print-file-name=specs` if test -n "$specfile" && test -e "$specfile"; then add_file "$specfile" fi ltofile=`$added_gcc -print-prog-name=lto1` pluginfile=`normalize_path "${ltofile%lto1}liblto_plugin.so"` if test -r "$pluginfile" then add_file $pluginfile ${pluginfile#*usr} add_file $pluginfile /usr${pluginfile#*usr} fi # for testing the environment is usable at all if test -x /bin/true; then add_file /bin/true elif test -x /usr/bin/true; then add_file /usr/bin/true /bin/true else print_output "'true' not found" exit 1 fi for extra in $extra_tools; do if test -x "$extra"; then add_file "$extra" else print_output "'$extra' not found" exit 1 fi done link_rel () { local target="$1" local name="$2" local base="$3" local prefix=`dirname $name` prefix=`echo $prefix | sed 's,[^/]\+,..,g' | sed 's,^/*,,g'` ln -s $prefix/$target $base/$name } tempdir=`mktemp -d /tmp/iceccenvXXXXXX` target_files= for path in $target_paths; do mkdir -p $tempdir/`dirname $path` cp -pH $path $tempdir/$path if test -f $tempdir/$path -a -x $tempdir/$path; then strip -s $tempdir/$path 2>/dev/null fi fix_rpath $tempdir/$path `dirname $path` target_files="$target_files $path" done for i in $target_aliases; do target=`echo $i | cut -d= -f1` link_name=`echo $i | cut -d= -f2` mkdir -p $tempdir/`dirname $link_name` # Relative links are used because the files are checked for being # executable outside the chroot link_rel $target $link_name $tempdir link_name=`echo $link_name | cut -b2-` target_files="$target_files $link_name" done #sort the files target_files=`for i in $target_files; do echo $i; done | sort` #test if an archive name was supplied #if not use the md5 of all files as the archive name if test -z "$archive_name"; then md5sum=NONE for file in /usr/bin/md5sum /bin/md5 /usr/bin/md5; do if test -x $file; then md5sum=$file break fi done #calculate md5 and use it as the archive name archive_name=`for i in $target_files; do test -f $tempdir/$i && $md5sum $tempdir/$i; done | sed -e 's/ .*$//' | $md5sum | sed -e 's/ .*$//'`.tar.gz || { print_output "Couldn't compute MD5 sum." exit 2 } mydir=`pwd` else mydir="`dirname "$archive_name"`" #check if we have a full path or only a filename if test "$mydir" = "." ; then mydir=`pwd` else mydir="" fi fi print_output "creating $archive_name" cd $tempdir # Add everything in the temp directory. Tar doesn't like to be given files with # ".." in them, which frequently happens in $target_files, and will strip off # the path prefix past the offending "..". This makes the archive generate # incorrectly tar -czf "$mydir/$archive_name" . || { print_output "Couldn't create archive" exit 3 } cd .. rm -rf $tempdir