[RFT/RFA] bashjar patch

Ralf Wildenhues Ralf.Wildenhues@gmx.de
Tue May 2 19:20:00 GMT 2006


[ please Cc: me on replies ]

Hi Paolo,

* Paolo Bonzini wrote on Fri, Apr 28, 2006 at 11:31:01PM CEST:
> 
> Testing is appreciated with shells other than bash.

Some comments (untested):

> Index: scripts/jar.in
> ===================================================================
> --- scripts/jar.in	(revision 0)
> +++ scripts/jar.in	(revision 0)
> @@ -0,0 +1,503 @@
> +#! /bin/sh
> +# Copyright (C) 2006  Free Software Foundation
> +# Written by Paolo Bonzini.
> +#
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published
> +# by the Free Software Foundation; either version 2 of the License,
> +# or (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful, but
> +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
> +# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
> +# for more details.
> +#
> +# You should have received a copy of the GNU General Public License along
> +# with this program; if not, write to the Free Software Foundation, Inc.,
> +# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
> +
> +
> +# POSIX and NLS nuisances, taken from autoconf.
> +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
*snip*

Be encouraged to pick the AS_BOURNE_COMPATIBLE contents from
Autoconf-2.59c (which contain a fix for the OpenBSD and OSF shells).
Also, add
  (unset CDPATH) >/dev/null 2>&1 && unset CDPATH

to avoid some possible clutter.

> +progname="$0"

Will the script ever be invoked as $shell $path/to/jar?  Then this can
be wrong.

> +# Usage: copy SRC DEST
> +# Copy file SRC to directory DEST, which is the staging area of the jar file.
> +# Fail if it is already present or if it is not a regular file.
> +copy () {
> +  if test -f "$1"; then
> +    # A simple optimization.  Optimistically assuming that ln will work
> +    # cuts 60% of the run-time!
> +    if ln "$1" "$2"/"$1" > /dev/null 2>&1; then
> +      return 0
> +    fi
> +
> +    if test -e "$2"/"$1"; then
> +      error "$1": Duplicate entry.
> +    fi
> +    $mkdir_p "$2"/`dirname "$1"`
> +    ln "$1" "$2"/"$1" > /dev/null 2>&1 || cp "$1" "$2"/"$1"
> +  elif test -e "$1"; then
> +    error "$1": Invalid file type.
> +  else
> +    error "$1": File not found.
> +  fi
> +}
> +
> +# Make a temporary directory and store its name in the JARTMP variable.
> +make_tmp () {

Be encouraged to use the code documented in
  info Autoconf "Limitations of Usual Tools"

under "mktemp" (again, 2.59c) instead (AS_TMPDIR).

> +    trap 'rm -rf $JARTMP' EXIT

I think EXIT is not portable, but 0 is.  Since with mktemp you'll still
need a trap or similar cleanup mechanism, and the exit status of the
script matters, you'll need to change all instances "exit N" to
  (exit N); exit N

(like AS_EXIT) to portably set the exit status.  Otherwise, e.g. on some
HP-UX you may falsely get a zero exit status.

Also, although this may be wrongly-guided or just plain FUD, I'd always
rather write
  test -n "$JARTMP" && rm -rf "$JARTMP"

for safety.

> +# Usage: make_manifest destfile kind [source-manifest]
> +# Create a manifest file and store it in destfile.  KIND can be "default",
> +# or "user", in which case SOURCE-MANIFEST must be specified as well.
> +make_manifest () {
> +  $mkdir_p `dirname "$1"`
> +  case $2 in
> +    default)
> +      cat > "$1" <<\EOF
> +Manifest-Version: 1.0
> +Created-By: @VERSION@
> +
> +EOF
> +      ;;
> +    user)
> +      cp "$3" "$1"
> +      ;;
> +  esac
> +}
> +
> +# Usage: set_var var [value]
> +# Exit with an error if set_var was already called for the same VAR.  Else
> +# set the variable VAR to the value VALUE (or the empty value if no parameter
> +# is given).
> +set_var () {
> +  if eval test x\$set_$1 = xset; then
> +    error Incompatible or repeated options.
> +  else
> +    eval $1='"$2"'

This can be simplified to
       eval $1=\$2

> +    eval set_$1=set
> +  fi
> +}
> +
> +# Process the arguments, including -C options, and copy the whole tree
> +# to $JARTMP/files so that zip can be invoked later from there.
> +make_files () {
> +  change=false
> +  if $process_response_files; then
> +    if test $# = 0; then
> +      while read arg; do
> +        make_files_1 "$arg"
> +      done
> +    else
> +      for infile
> +      do
> +        exec 5<&0
> +        exec 0< $infile
> +        while read arg; do
> +          make_files_1 "$arg"
> +        done
> +        exec 0<&5

I think it's ok to close 5 here:
           exec 5<&-

> +      done
> +    fi
> +  else
> +    for arg
> +    do
> +      make_files_1 "$arg"
> +    done
> +  fi
> +  cd "$old_dir"
> +}
> +
> +# Usage: make_files_1 ARG
> +# Process one argument, ARG.
> +make_files_1 () {
> +  if $change; then
> +    change=false
> +    cd "$1" || exit 1
> +    return
> +  fi
> +  case "$1" in
> +    -C)
> +      change=:
> +      ;;
> +    -C*)
> +      cd `expr "$1" : '-C\(.*\)' `
> +      return
> +      ;;
> +    *)
> +      if test -d "$1"; then
> +        $mkdir_p $JARTMP/files/"$1"
> +        find "$1" | while read file; do
> +          if test -d "$file"; then
> +            $mkdir_p $JARTMP/files/"$file"
> +          else
> +            copy "$file" $JARTMP/files
> +          fi
> +        done
> +      else
> +        copy "$1" $JARTMP/files
> +      fi
> +      ;;
> +  esac
> +  cd "$old_dir"
> +}
> +
> +# Same as "jar tf $1".
> +jar_list () {
> +  $UNZIP -l "$1" | \
> +    sed '1,/^ ----/d;/^ ----/,$d;s/^ *[0-9]*  ..-..-.. ..:..   //'
> +}
> +
> +# Same as "jar tvf $1".
> +jar_list_verbose () {
> +  $UNZIP -l "$1" | \
> +    @AWK@ 'BEGIN { yes = 0 }
> +	 /^ ----/ { yes = !yes; next }
> +	 yes {
> +	   size=$1
> +	   split ($2, d, "-")
> +	   split ($3, t, ":")
> +	   d[3] += (d[3] < 80) ? 2000 : 1900
> +	   timestamp=d[3] " " d[2] " " d[1] " " t[1] " " t[2] " 00"
> +	   gsub (/^ *[0-9]*  ..-..-.. ..:..   /, "")
> +	   printf "%6d %s %s\n", size, strftime ("%+", mktime (timestamp)), $0
> +	 }'
> +}

I think this requires a non-traditional awk, and assumes that
AC_PROG_AWK will find one.  (I don't know whether the install
documentation states this.)

> +
> +# mkdir -p emulation based on the mkinstalldirs script.
> +mkdir_p ()
> +{
> +  for file
> +  do
> +    case $file in
> +      /*) pathcomp=/ ;;
> +      *)  pathcomp= ;;
> +    esac
> +    oIFS=$IFS
> +    IFS=/
> +    set fnord $file
> +    shift
> +    IFS=$oIFS

Ah, this is a bug (which is in mkinstalldirs as well; I'll report it
upstream):
IFS is allowed to be unset at shell startup; when it is, everything is
fine, as Posix requires things to work as if IFS contained space, tab,
newline.  However, after this sequence, IFS is set but empty; in this
case, field splitting is turned off.  (Some ash versions expose this.)

To fix, initialize IFS with space, tab, newline (in that order!) at
script startup.

> +
> +    for d
> +    do
> +      test "x$d" = x && continue
> +      pathcomp=$pathcomp$d
> +      case $pathcomp in
> +        -*) pathcomp=./$pathcomp ;;
> +      esac
> +
> +      if test ! -d "$pathcomp"; then
> +        mkdir "$pathcomp" || lasterr=$?
> +        test -d "$pathcomp" || errstatus=$lasterr
> +      fi
> +      pathcomp=$pathcomp/
> +    done
> +  done
> +  return "$errstatus"
> +}
> +
> +# Detect mkdir -p
> +# On NextStep and OpenStep, the `mkdir' command does not
> +# recognize any option.  It will interpret all options as
> +# directories to create, and then abort because `.' already
> +# exists.
> +if mkdir -p --version . >/dev/null 2>&1 && test ! -d ./--version; then
> +  mkdir_p='mkdir -p'
> +else
> +  mkdir_p='mkdir_p'
> +  test -d ./-p && rmdir ./-p
> +  test -d ./--version && rmdir ./--version
> +fi
> +
> +# Process the first command line option.
> +case "$1" in
> +  -*) commands=`echo X"$1" | sed 's/^X-//' ` ;;
> +  *) commands="$1"
> +esac
> +shift
> +
> +# Operation to perform on the JAR file
> +mode=unknown
> +
> +# First -C option on the command line
> +cur_dir=.
> +
> +# Base directory for -C options
> +old_dir=`pwd`
> +# JAR file to operate on
> +jarfile=
> +
> +# default for no {m,M} option, user for "m" option, none for "M" option
> +manifest_kind=default
> +
> +# "-0" if the "0" option was given
> +store=
> +
> +# true if the "v" option was given
> +verbose=false
> +
> +# true if the non-standard "@" option was given
> +process_response_files=false
> +
> +# An exec command if we need to redirect the zip/unzip commands' output
> +out_redirect=:
> +
> +while test -n "$commands"; do
> +  # Process a letter at a time
> +  command=`expr "$commands" : '\(.\)'`
> +  commands=`expr "$commands" : '.\(.*\)'`
> +  case "$command" in
> +    c)
> +      set_var mode create
> +      ;;
> +    t)
> +      set_var mode list
> +      ;;
> +    x)
> +      set_var mode extract
> +      ;;
> +    u)
> +      set_var mode update
> +      ;;
> +
> +    f)
> +      test $# = 0 && usage
> +      # Multiple "f" options are accepted by Sun's JAR tool.
> +      jarfile="$1"
> +      test -z "$jarfile" && usage
> +      shift
> +      ;;
> +    m)
> +      test $# = 0 && usage
> +      # Multiple "m" options are accepted by Sun's JAR tool, but
> +      # M always overrides m.
> +      test "$manifest_kind" = default && manifest_kind=user
> +      manifest_file="$1"
> +      test -z "$manifest_file" && usage
> +      shift
> +      ;;
> +    0)
> +      store=-0
> +      ;;
> +    v)
> +      verbose=:
> +      ;;
> +    i)
> +      # Not yet implemented, and probably never will.
> +      ;;
> +    M)
> +      manifest_kind=none
> +      ;;
> +    C)
> +      test $# = 0 && usage
> +      cur_dir="$1"
> +      shift
> +      ;;
> +    @)
> +      process_response_files=: ;;
> +    *)
> +      usage ;;
> +  esac
> +done
> +
> +set -e

If you're going to use "set -e", be sure to go over code that has AND
lists (&&) at least, see this thread:
http://lists.gnu.org/archive/html/autoconf-patches/2006-05/msg00005.html

> +
> +case "X$jarfile" in
> +  X)
> +    # Work on stdin/stdout.  Messages go to stderr, and if we need an input
> +    # JAR file we save it temporarily in the temporary directory.
> +    make_tmp
> +    $mkdir_p $JARTMP/out
> +    jarfile=$JARTMP/out/tmp-stdin.jar
> +    out_redirect='exec >&2'
> +    case $mode in
> +      update|extract|list)
> +        if $process_response_files && test $# = 0; then
> +	  error Cannot use stdin for response file.
> +	fi
> +	cat > $JARTMP/out/tmp-stdin.jar
> +	;;
> +    esac
> +    ;;
> +
> +  X*/*)
> +    # Make an absolute path.
> +    dir=`dirname "$jarfile"`
> +    jarfile=`cd $dir && pwd`/`basename "$jarfile"`
> +    ;;
> +
> +  X*)
> +    # Make an absolute path from a filename in the current directory.
> +    jarfile=`pwd`/`basename "$jarfile"`
> +    ;;
> +esac
> +
> +# Perform a -C option if given right away.
> +cd "$cur_dir"
> +
> +case $mode in
> +  unknown)
> +    usage
> +    ;;
> +
> +  extract)
> +    make_tmp
> +
> +    # Extract the list of files in the JAR file
> +    jar_list "$jarfile" > $JARTMP/list
> +
> +    # If there are files on the command line, expand directories and skip -C
> +    # command line arguments
> +    for arg
> +    do
> +      $skip && skip=false && continue
> +      case "$arg" in
> +	-C) skip=: ;;
> +	-C*) ;;
> +	*)
> +	  escaped=`echo "$arg" | sed -n 's/[][.^$\*]/\\&/' `
> +	  grep -e "^list/" >> $JARTMP/chosen || :
> +	  grep -e "^list$" >> $JARTMP/chosen || :

"grep -e" isn't portable.  Luckily you can just drop the "-e" here.

> +      esac
> +    done
> +    test -f $JARTMP/chosen || cp $JARTMP/list $JARTMP/chosen
> +
> +    # Really execute unzip
> +    if $verbose; then
> +      sort < $JARTMP/chosen | uniq | xargs $UNZIP -o "$jarfile" | \

Just curious: is there a reason not to use sort -u (two instances)?

> +	sed -ne 's/^   creating/  created/p' -e 's/^  inflating/extracted/p'
> +    else
> +      sort < $JARTMP/chosen | uniq | xargs $UNZIP -o "$jarfile" > /dev/null
> +    fi
> +    ;;
> +
> +  create)
> +    make_tmp
> +    $mkdir_p $JARTMP/out
> +    $mkdir_p $JARTMP/files
> +
> +    # Do not overwrite the JAR file if something goes wrong
> +    tmp_jarfile=$JARTMP/out/`basename "$jarfile"`
> +
> +    # Prepare the files in the temporary directory.  This is necessary to
> +    # support -C and still save relative paths in the JAR file.
> +    make_files "$@"

Since you used the zsh workaround above, you can use ${1+"$@"} here, to
avoid an error if no arguments are given.  (Another instance further
down.)

Hmm, some real old shells don't reset the positional parameters after
function calls, see here:
http://www.in-ulm.de/~mascheck/bourne/function_parameters.html
I think in practice there is no need to worry about this, I don't think
those systems will ever run jar; I don't have access to a system with
such a shell.

> +    if test $manifest_kind != none; then
> +      make_manifest $JARTMP/files/META-INF/MANIFEST.MF $manifest_kind $manifest_file

manifest_file should be double-quoted (two instances).

> +    fi
> +
> +    # Really execute zip
> +    if $verbose; then
> +      (eval $out_redirect; cd $JARTMP/files && $ZIP -rv "$tmp_jarfile" $store .)
> +    else
> +      (cd "$JARTMP/files" && $ZIP -r "$tmp_jarfile" $store . > /dev/null)
> +    fi
> +    test "$jarfile" = "$tmp_jarfile" || mv "$tmp_jarfile" "$jarfile"
> +    ;;
> +
> +  update)
> +    make_tmp
> +    $mkdir_p $JARTMP/files
> +    make_files "$@"
> +
> +    # Same as above, but zip takes care of not overwriting the file
> +    case $manifest_kind in
> +      none)
> +	$verbose && (eval $out_redirect; echo removing manifest)
> +	$ZIP -d "$jarfile" META-INF/MANIFEST.MF > /dev/null 2>&1 || :
> +	;;
> +      *)
> +	make_manifest $JARTMP/files/META-INF/MANIFEST.MF $manifest_kind $manifest_file
> +	;;
> +    esac
> +    if $verbose; then
> +      (eval $out_redirect; cd $JARTMP/files && $ZIP -ruv "$jarfile" $store .)
> +    else
> +      (cd $JARTMP/files && $ZIP -ru "$jarfile" $store . > /dev/null)
> +    fi
> +    ;;
> +
> +  list)
> +    # Everything's done in the functions
> +    if $verbose; then
> +      jar_list_verbose "$jarfile"
> +    else
> +      jar_list "$jarfile"
> +    fi ;;
> +esac
> +
> +if test "$out_redirect" != :; then
> +  # Cat back to stdout if necessary
> +  case $mode in
> +    create|update) cat $JARTMP/out/tmp-stdin.jar ;;
> +  esac
> +fi
> +exit 0



More information about the Gcc-patches mailing list