1#!/bin/sh -
2#@ gen-uushar-wrapper creates a shar(1)-like shell archive (but with
3#@ uuencode(1)d content) of given files (read from STDIN or ARGV), that is
4#@ itself executable.
5#@ Execute it (the generated archive) to invoke any of the programs contained
6#@ therein.  On the first run with arguments, the wrapper will create a hidden
7#@ directory in your $TMPDIR to unpack the archive therein; without arguments
8#@ it will always print creation time and members shipped.
9#
10# Copyright (c) 2012 - 2021 Steffen Nurpmeso <steffen@sdaoden.eu>.
11#
12# Permission to use, copy, modify, and/or distribute this software for any
13# purpose with or without fee is hereby granted, provided that the above
14# copyright notice and this permission notice appear in all copies.
15#
16# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
17# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
18# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
19# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
21# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
22# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23
24##  --  >8  --  8<  --  ##
25ARGS=${@}
26
27echo 'So let us create an uuencoded shell archive of the given utilities.'
28echo 'Please answer the upcoming questions for this to work.'
29echo 'You can at any time interrupt with CTRL-C.'
30
31printf '\nWhat should be the name of the wrapper: [] '
32read SHAR
33if [ -z "${SHAR}" ]; then
34   echo >&2 'Cannot use the empty string for that, bailing out.'
35   exit 1
36fi
37SHAR="${SHAR}.sh"
38if [ -f "${SHAR}" ]; then
39   echo >&2 "\`${SHAR}' already exists, bailing out."
40   exit 1
41fi
42
43printf "Yay, it will be \`${SHAR}'.\n\n%s\n%s\n%s" \
44   "The special command \`+' should map to a subcommand?" \
45   "(As in: '\$ ${SHAR} + -h' -> '\$ SUBCOMMAND -h')?" \
46   'Enter desired command or the empty string otherwise [] '
47read SHAR_DEFEXEC
48if [ -n "${SHAR_DEFEXEC}" ]; then
49   SHAR_DEFEXEC="`basename \"${SHAR_DEFEXEC}\"`"
50   echo "..this is \`${SHAR_DEFEXEC}'"
51   NEED_DEFEXEC=0
52fi
53
54printf '\n%s\n%s' \
55   'Members can be compressed via compress(1), gzip(1), bzip2(1) and xz(1).' \
56   'Enter desired compressor or the empty string otherwise. [] '
57read COMPRESS
58if [ -n "${COMPRESS}" ]; then
59   x_compress='compress -cf'
60   ux_compress='uncompress -cf'
61   x_gzip='gzip -cf'
62   ux_gzip='gzip -cdf'
63   x_bzip2='bzip2 -cf'
64   ux_bzip2='bzip2 -cdf'
65   x_xz='xz -cf'
66   ux_xz='xz -cdf'
67
68   eval cfun='${x_'"$COMPRESS"'}'
69   eval xfun='${ux_'"$COMPRESS"'}'
70   if [ -z "${cfun}" ]; then
71      echo >&2 "Unsupported compression method, bailing out: ${COMPRESS}"
72      exit 1
73   fi
74fi
75
76echo
77echo 'Enter member programs to include, empty value to terminate list.'
78MEMBERS= MEMBER_BASENAMES=
79[ -n "${ARGS}" ] && set -- ${ARGS}
80while :; do
81   if [ -n "${ARGS}" ]; then
82      [ ${#} -eq 0 ] && break
83      i=${1}
84      shift
85      printf '\t- '
86   else
87      printf '\t - [] '
88      read i
89      [ -z "${i}" ] && break
90   fi
91
92   if [ -x "${i}" ]; then
93      :
94   else
95      echo >&2 "${i} does not exist or is not executable, bailing out."
96      exit 1
97   fi
98   if { j=${i}; echo ${j}; } | grep -q '"'; then
99      echo >&2 "Double-quotes in filenames are unsupported, bailing out: ${i}"
100      exit 1
101   fi
102
103   j=`basename "${i}"`
104   if [ -z "${MEMBERS}" ]; then
105      MEMBERS="${i}"
106      MEMBER_BASENAMES="${j}"
107   else
108      MEMBERS="${MEMBERS}, ${i}"
109      MEMBER_BASENAMES="${MEMBER_BASENAMES}, ${j}"
110   fi
111   [ "${j}" = "${SHAR_DEFEXEC}" ] && unset NEED_DEFEXEC
112   echo "${i}: ok"
113done
114
115if [ -n "${SHAR_DEFEXEC}" ] && [ -n "${NEED_DEFEXEC}" ]; then
116   echo >&2 "The default executable is missing, bailing out: ${SHAR_DEFEXEC}"
117   exit 1
118fi
119
120# Everything get.set.go, so write the actual shell archive
121# (Not backward compatible with Bourne [-C], atomicity problem: set -C)
122echo '..creating archive..'
123CREATION_DATE=`date -u`
124
125# Header
126cat <<\! > "${SHAR}"
127#!/bin/sh -
128#@ This file has been created by gen-uushar-wrapper, which is
129# Copyright (c) 2012 - 2021 Steffen Nurpmeso <steffen@sdaoden.eu>.
130#@ by means of the ISC license.
131#@ It contains a shell-archive-execution-environment.
132#@ Run it, and it will tell you which executable files it contains.
133#@ Run it with the name of such an executable, and it will create a
134#@ hidden directory in your $TMPDIR to expand the shipped executables.
135#@ From then on, it will redirect its invocations to those programs.
136#
137#@ Archives produced using this implementation of gen-uushar-wrapper
138#@ may be easily examined with the command:
139#@    $ grep '^[^X#]' shell-archive
140#
141!
142
143# Its indeed our $SHAR
144trap "rm -rf '${SHAR}'" 0
145
146# A shell archive is not a transparent thing either!
147echo >> "${SHAR}" "SHAR='${SHAR}'"
148echo >> "${SHAR}" "CREATION_DATE='${CREATION_DATE}'"
149echo >> "${SHAR}" "MEMBERS='${MEMBER_BASENAMES}'"
150echo >> "${SHAR}" "# Default expansion (if argument \${1} equals \`+'):"
151echo >> "${SHAR}" "DEFEXEC='${SHAR_DEFEXEC}'"
152cat <<\! >> "${SHAR}"
153#
154[ ${#} -eq 0 ] &&
155   { echo "Creation: ${CREATION_DATE}"; echo "Members: ${MEMBERS}"; exit 0; }
156#
157i="${TMPDIR}/.${SHAR}"
158if [ -d "${i}" ]; then
159   PATH="${i}:${PATH}"
160   export PATH
161   [ x"${1}" != 'x+' ] && DEFEXEC=${1}
162   shift
163   i="${i}/${DEFEXEC}"
164   if [ ! -f "${i}" ] || [ ! -x "${i}" ]; then
165      echo >&2 "Sorry, there is no program \`${DEFEXEC}' in this archive"
166      exit 54
167   fi
168   exec "${i}" ${@+"${@}"}
169   echo >&2 "Failed to execute <${DEFEXEC} ${@+\"${@}\"}>"
170   exit 71 # 71=EX_OSERR
171fi
172#
173printf "%s\n%s\n" "The directory \`${i}'" \
174   "does not exist: creating it and expanding this archive first"
175MYSELF="`pwd`/${0}"
176[ ${?} -ne 0 ] && { echo >&2 "Cannot detect current directory"; exit 1; }
177mkdir -p "${i}" || { echo >&2 "Cannot create directory \`${i}'"; exit 1; }
178cd "${i}" ||
179   { echo >&2 "Cannot cd into \`${i}', removing it"; rm -rf "${i}"; exit 1; }
180#
181!
182
183# uuencode(1)d and optionally compress the members to embed
184oifs=${IFS}
185IFS=', '
186set -- ${MEMBERS}
187IFS=${oifs}
188for i
189do
190   bi=`basename "${i}"`
191   echo >> "${SHAR}" "echo 'X - ${bi}'"
192   if [ -z "${cfun}" ]; then
193      echo >> "${SHAR}" \
194         "sed 's/^X//' << \! |\
195            uudecode -o /dev/stdout > \"${bi}\"; chmod 0755 \"${bi}\""
196      < "${i}" uuencode -m /dev/stdout | sed 's/^/X/' >> "${SHAR}"
197   else
198      echo >> "${SHAR}" \
199         "sed 's/^X//' << \! | uudecode -o /dev/stdout | ${xfun} > \"${bi}\";\
200          chmod 0755 \"${bi}\""
201      < "${i}" ${cfun} | uuencode -m /dev/stdout | sed 's/^/X/' >> "${SHAR}"
202   fi
203   echo >> "${SHAR}" '!'
204   echo >> "${SHAR}" "# END of ${bi}"
205done
206
207echo >> "${SHAR}" "echo 'Archive is expanded, rerunning command...'"
208echo >> "${SHAR}" 'exec "${MYSELF}" ${@+"${@}"}'
209
210# The end
211chmod 0755 "${SHAR}"
212trap : 0
213exit 0
214# vim:set fenc=utf-8 syntax=sh:s-it-mode
215