Major overhaul -- most scripts rewritten or updated.
[pkgusr] / usr / lib / pkgusr / install
index 78f7aeb..1e721af 100755 (executable)
@@ -1,14 +1,92 @@
 #!/bin/bash
+# Original...
 # Copyright (c) 2000,2004 Matthias S. Benkmann <article AT winterdrache DOT de>
 # You may do everything with this code except misrepresent its origin.
 # PROVIDED `AS IS' WITH ABSOLUTELY NO WARRANTY OF ANY KIND!
 
-manpagesowner=man-pages
-localedir=/usr/share/locale
-cmdline="$@"
+# Copyright (C) 2014 Steve Youngs <steve@steveyoungs.com>
+#
+#  Actually there's not much left of Matt's original script... pretty
+#  much a complete re-write here. :)
 
-DAISY_CHAIN=""
+### What this is and what it does:
+#
+#  It is a wrapper around the install binary that comes with the
+#  coreutils package.  It is designed to catch the most common
+#  problems that pkgusr will face during a package install.
+#
+#  By far, the most common thing this script catches are setuid/gid
+#  installs.  What we do is strip the setuid/gid bit off of the --mode
+#  option and then go ahead with the install.  So the file is still
+#  installed, there'll be no error, it will just not be setuid/gid.
+#
+#  It also sanitises the --owner and --group options.  With the former,
+#  it changes it to the name of the pkgusr, and with the latter it
+#  tests if the group is one of the groups that pkgusr belongs to, and
+#  if it isn't, change the option to the pkgusr's current active group.
+#
+#  It only allows pkgusrs to create directories that don't already
+#  exist.  This stops those packages that try to reset permissions on
+#  main system directories.
+#
+#  It optionally suppresses installing anything into /**/share/locale.
+#  See `## locale suppression' below for more details.
+#
+#  ******************************
+#  *** S P E C I A L  N O T E ***
+#  ******************************
+#
+#  Whenever this script changes something on the install command line
+#  it is reported in the build logs.  Everything is prefixed with
+#  '***' so always grep your logs for that after any and every package
+#  install to see if there is anything you need to take action on.
+#
+###
+
+### Where this differs from Matt's original script:
+#
+#  setuid etc: Matt's script only tested for 4755, 4775, 4711, and
+#  nothing else.  This script tests and does something about _ALL_
+#  file mode possibilities, including modes set via symbols instead of
+#  octal code.
+#
+#  owner/group: Matt simply dropped these options if they were present.
+#  This script tries to keep them if possible by resetting to a safer
+#  alternative.  Both names and UIDs/GIDs are supported.
+#
+#  locale directories: Matt's approach was to convert any newly created
+#  directory under /usr/share/locale to an "install" directory.  The
+#  problem with that is the directories were not owned by root because
+#  they were set by the pkgusr. That would allow one pkgusr to delete
+#  another's files in that directory.
+#
+#  My approach to locale directories is simple... Just don't install
+#  them.  There are three situations where they are needed...
+#
+#    1. You're multi-lingual and like switching between languages.
+#    2. English is not your 1st or preferred language. (or you can't
+#       tollerate en_US)
+#    3. To satisfy glibc's test suite.
+#
+#  For me, #3 is the only one that is relevant.  But don't worry,
+#  it is configurable via an environment variable.  You can turn on
+#  locale installs either globally or for individual packages.  See
+#  `## locale suppression' below and in /etc/pkgusr/bash_profile.
+#
+#  man pages: Matt tried to catch dups going into /usr/share/man/man?/.
+#  I'm not bothering with this as it was too much of a "one-off" type
+#  of occurance to have it scripted.
+#
+#  Running the script as root:  Matt allows it, I don't.
+#
+###
+
+## Preserve the original command line.
+pristinecmd=($@)
+cmdopts=""
 
+## Find the real install.
+DAISY_CHAIN=""
 for p in $(type -ap install) ; do
     if [ ! $p -ef $0 ]; then
        DAISY_CHAIN=$p
@@ -17,119 +95,230 @@ for p in $(type -ap install) ; do
 done
 
 if [ ! -n "$DAISY_CHAIN" ]; then
-    echo Cannot find real ${0##*/} command 
+    echo 1>&2 '***' Cannot find real ${0##*/} command 
     exit 1
 fi
 
+## root has no business installing things here!!
 if [ $UID == 0 ]; then
-    exec $DAISY_CHAIN "$@"
+    echo 1>&2 '***' $(dirname $0) should not be in root\'s \$PATH
+    echo 1>&2 '***' call '"'$DAISY_CHAIN ${pristinecmd[*]'"' directly
+    exit 1
 fi
 
-#kill unused -c parameter if we get it
-if [ z"$1" = z"-c" ]; then shift 1 ; fi
-
-       #********** test if we create directories ********************
-if [ \( z"$1" = z"-d" \) -o \( z"$1" = z"-m" -a z"$3" = z"-d" \) ]; then  
-    locdirs=""
-    notify=0
-    havedir=0
-    for((i=$#; $i>0; ))
-    do
-        a="$1"
-       shift 1; i=$(($i-1))
-       case "$a" in
-           -o|-g|--owner|--group)
-                 notify=1 
-               shift 1; i=$(($i-1))
-               set -- "$@" 
-               ;;
-           $localedir/*)
-                if [ ! -d "$a" ]; then
-                   locdirs="$locdirs ""$(expr $a : "$localedir/\(.*\)")" 
-                   set -- "$@" "$a"
-                   havedir=1
-               else
-                   notify=1
-                   set -- "$@"
-               fi  
-               ;;
-            */*|/sbin)
-                if [ ! -d "$a" ]; then 
-                   set -- "$@" "$a" 
-                   havedir=1
-               else
-                   notify=1
-                   set -- "$@"
-               fi             
-               ;;
-           *) set -- "$@" "$a" ;;
-        esac
-    done
-  
-    test $notify -eq 1 -o z"$locdirs" != z && \
-       echo 1>&2 '***' install "$cmdline"
-
-    test $havedir -eq 0 && exit 0
-
-    $DAISY_CHAIN "$@" || exit $?
-  
-    test z"$locdirs" != z &&
-    for dir in $locdirs ; do
-       cumuldir=""
-       for d in $(echo $locdirs | sed 's#/# #g' -) ; do
-           cumuldir=$cumuldir$d/
-           if [ -d $localedir/$cumuldir ]; then
-               chgrp install $localedir/$cumuldir
-               chmod g+w,o+t $localedir/$cumuldir
-           fi  
-       done  
-    done
+## Report
+#  When we change something, note what the original command was in the
+#  logs.
+report()
+{
+       echo 1>&2 '***' install ${pristinecmd[*]}
+       return
+}
 
-else  #if "$1" != "-d"  ,i.e. we do not create directories *****************
-    notify=0
-    for((i=$# ; $i>0; ))
-    do
-        a="$1"
-       shift 1; i=$(($i-1))
-       case "$a" in
-            -m)
-               set -- "$@" "$a" 
-               a="$1"
-               shift 1; i=$(($i-1))
-               case "$a" in
-                   4755) notify=1 ; set -- "$@" "755" ;;
-                   4775) notify=1 ; set -- "$@" "755" ;;
-                   4711) notify=1 ; set -- "$@" "711" ;;
-                   *) set -- "$@" "$a"  ;;
-               esac
-               ;;
-            -m4755) notify=1 ; set -- "$@" "-m755" ;;
-           -m4775) notify=1 ; set -- "$@" "-m755" ;;
-            -m4711) notify=1 ; set -- "$@" "-m711" ;;
-            -o|-g|--owner|--group)
-               notify=1 
-               shift 1; i=$(($i-1))
-               set -- "$@" 
-               ;;
-            */man/man?/*) 
-                if [ -e "$a" -a ! -O "$a" ]; then
-                   if [ $(find "$a" -printf \%u) = $manpagesowner ]; then
-                       notify=1
-                       set -- "$@" not_installed
-                   else
-                       set -- "$@" "$a"
-                   fi  
-                else
-                   set -- "$@" "$a"
-                fi
-               ;;    
-            *) set -- "$@" "$a" ;;
-        esac
+## locale suppression
+#  $SUPPRESSLOCALEDIR is set in the pkgusr's environment.  It defaults
+#  to `1' (on) which means: DO NOT install locale stuff.  To override
+#  it set SUPPRESSLOCALEDIR=0 in the pkgusr's ~/.pkgusrrc.
+#
+#  For me, at least, the only package I turn off the suppression is
+#  glibc, and that is only to satisfy glibc's test suite.
+if [ ${SUPPRESSLOCALEDIR} -eq 1 ]; then
+    case "${pristinecmd[-1]}" in
+       (*/share/locale/*)
+       echo 1>&2 '***' Suppressed locale installation for: ${pristinecmd[-1]}
+       report
+       exit 0
+       ;;
+    esac
+fi
+
+## Directories
+#  Only allow the creation of directories that don't already exist.
+_dirs()
+{
+    local myarray=($@)
+    local dir=${myarray[-1]}
+
+    if [ -d ${dir} ]; then
+       report
+    else
+       cmdopts="$cmdopts -d"
+    fi
+    return
+}
+
+## Leading directories (install option -D)
+#  The same as for -d, only create if it doesn't exist.  But we need
+#  to do a little more this time because if we decide to NOT create
+#  the directory we will need to alter the command line further...
+#
+#    install -Dm755 myprog /usr/bin/myprog
+#  To
+#    install -m755 myprog /usr/bin
+_leading_dirs()
+{
+    local myarray=($@)
+    local dir=$(dirname ${myarray[-1]})
+    local last=$((${#myarray[*]}-1))
+
+    if [ -d ${dir} ]; then
+       report
+       myarray[${last}]=${dir}
+       set --
+       set -- "$@" "${myarray[*]}"
+    else
+       cmdopts="$cmdopts -D"
+    fi
+    return
+}
+
+## Group
+#  If $group is one of the groups we belong to, use it, otherwise set
+#  -g to our currently active group and report it.
+_group()
+{
+    local GRP_CHAIN=""
+    local GRP_LIST
+
+    # GID or name?
+    if [ $group -ge 0 2>/dev/null ]; then
+       GRP_LIST=$(id -G)
+    else
+       GRP_LIST=$(id -Gn)
+    fi
+
+    for g in ${GRP_LIST}; do
+       if [ $group == "$g" ]; then
+           GRP_CHAIN=$g
+           break
+       fi
     done
 
-    test $notify -eq 1 && echo 1>&2 '***' install "$cmdline"
+    if [ -z "$GRP_CHAIN" ]; then
+       report
+       cmdopts="$cmdopts -g$(id -gn)"
+    else
+       cmdopts="$cmdopts -g$group"
+    fi
+    return
+}
 
-    $DAISY_CHAIN "$@" || exit $?
-fi
+## Owner
+#  Set -u to our username and report if it wasn't already our name.
+_owner()
+{
+    local MYNAME
+
+    # UID or name?
+    if [ $owner -ge 0 2>/dev/null ]; then
+       MYNAME=$(id -u)
+    else
+       MYNAME=$(id -un)
+    fi
+
+    if [ $owner != "$MYNAME" ]; then
+       report
+    fi
+    cmdopts="$cmdopts -u$(id -un)"
+    return
+}
 
+## Mode
+#  If the file perms are being set to a 4-digit number they are
+#  trying to do something funky like setuid.  In that case, truncate
+#  the 1st digit and set -m to what's left, and report it.  If perms
+#  are symbolic, convert them to numerical first.
+_perms()
+{
+    local p
+    ### HACK-O-MATIC:
+    # Convert symbolic permissions to numerical.  This works via
+    # side-effect because if $perm is text it causes the if to error
+    # and you come out on the else branch.
+    if [ $perm -ge 0 2>/dev/null ]; then
+       p=$perm
+    else
+       touch /tmp/hack-o-matic-9000
+       chmod $perm /tmp/hack-o-matic-9000
+       p=$(stat --printf "%a" /tmp/hack-o-matic-9000)
+       rm -f /tmp/hack-o-matic-9000
+    fi
+    # Catch the funky shit
+    if [ $p -gt 999 ]; then
+       report
+       cmdopts="$cmdopts -m${p/?/}"
+    else
+       cmdopts="$cmdopts -m$p"
+    fi
+    return
+}
+
+## Parse the command line.
+#   All we really care about here is -d, -D, -o, -g, and -m.  -c is
+#   silently dropped.  Unrecognised options cause the script to exit
+#   with a non-zero return code.
+#
+#   Any other legal options get passed on without alteration.
+#
+# NOTE: The following long options will most likely cause this script
+#       to fail (hopefully they are obscure enough to not worry about)
+#
+#         --preserve-timestamps (short opt '-p' is OK)
+#         --strip-program
+#         --target-directory (short opt '-t' is OK)
+#         --no-target-directory (short opt '-T' is OK)
+#         --preserve-context
+#
+args=bcCdDpsTvg:m:o:S:t:Z:-:
+while getopts $args opts; do
+    case $opts in
+       (-)
+       # long opts
+       case $OPTARG in
+           (backup*) cmdopts="$cmdopts --${OPTARG}" ;;
+           (compare) cmdopts="$cmdopts -C" ;;
+           (directory) _dirs ;;
+           (group*) group=${OPTARG/group[=[:space:]]/}; _group ;;
+           (mode*) perm=${OPTARG/mode[=[:space:]]/}; _perms ;;
+           (owner*) owner=${OPTARG/owner[=[:space:]]/}; _owner ;;
+           (strip) cmdopts="$cmdopts -s" ;;
+           (suffix*) cmdopts="$cmdopts -S${OPTARG/suffix[=[:space:]]/}" ;;
+           (verbose) cmdopts="$cmdopts -v" ;;
+           (context*) cmdopts="$cmdopts -Z${OPTARG/context[=[:space:]]/}" ;;
+           (help) cmdopts="$cmdopts --help" ;;
+           (version) cmdopts="$cmdopts --version" ;;
+           (*) echo 1>&2 '***' Illegal option -- ${OPTARG}
+               exit 1
+               ;;
+       esac
+       ;;
+       # short opts
+       (b) cmdopts="$cmdopts -b" ;;
+       (C) cmdopts="$cmdopts -C" ;;
+       (c) ;;                  # no-op
+       (d) _dirs ;;
+       (D) _leading_dirs ;;
+       (g) group=${OPTARG}; _group ;;
+       (m) perm=${OPTARG}; _perms ;;
+       (o) owner=${OPTARG}; _owner ;;
+       (p) cmdopts="$cmdopts -p" ;;
+       (s) cmdopts="$cmdopts -s" ;;
+       (S) cmdopts="$cmdopts -S${OPTARG}" ;;
+       (t) cmdopts="$cmdopts -t${OPTARG}" ;;
+       (T) cmdopts="$cmdopts -T" ;;
+       (v) cmdopts="$cmdopts -v" ;;
+       (Z) cmdopts="$cmdopts -Z${OPTARG}" ;;
+       (*) exit 1 ;;
+    esac
+done
+shift $(( $OPTIND - 1 ))
+
+# We've done all we can, now lets run install
+$DAISY_CHAIN "$cmdopts $@" || exit $?
 exit 0
+
+### End install
+
+# Local variables:
+# sh-basic-offset: 4
+# End: