-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathslurp.sh
executable file
Β·315 lines (292 loc) Β· 10.5 KB
/
slurp.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
#!/bin/sh
#
# Slurp an executable and its satellite files into a single binary.
#################################################
# Prints version information.
# Arguments:
# None
# Outputs:
# Writes version information to stdout.
#################################################
version() {
echo "slurp 1.1.0"
}
#################################################
# Prints a brief help message.
# Arguments:
# None
# Outputs:
# Writes the help message to stdout.
#################################################
help() {
echo "Usage: ${0} [-r <executable>] [-o <output-file>] [--] <input-directory>"
echo " ${0} --unpack [-o <output-directory>] [--] <input-file>"
echo
echo "Slurp an executable and its satellite files into a single binary."
echo
echo "Examples:"
echo " ${0} ./bin/ --run ./bin/autorun.sh --output ./app"
echo " ${0} ./bin/ --output ./app"
echo " ${0} ./bin/"
echo " ${0} ./app --unpack --output ./app.slurp/"
echo " ${0} ./app --unpack"
echo
echo "Arguments:"
echo " <input-directory>"
echo " The directory containing the files and subdirectories to be slurped."
echo
echo " <input-file>"
echo " The binary file to be unslurped."
echo
echo "Options:"
echo " -h, --help"
echo " Displays this help page."
echo
echo " -v, --version"
echo " Displays the application version."
echo
echo " -r, --run <executable>"
echo " Specifies the primary executable file within the input directory."
echo " If omitted, slurp will attempt to locate one by searching for it in the following order:"
echo " 1. An executable named \"autorun\""
echo " 2. An executable named \"autorun.sh\""
echo " 3. A sole executable file in the root of the input directory"
echo " (if the search yields zero or multiple results, no match is made)."
echo
echo " -o, --output <output-file>"
echo " Specifies the name of the resulting binary."
echo " If omitted, the binary name is derived automatically from the main executable:"
echo " - The main executable's basename is used, unless it is \"autorun\"."
echo " - If the main executable's basename is \"autorun\", the name of the input directory is used."
echo " - A numeric suffix (e.g., \".1\", \".2\", etc.) is appended if the chosen filename is already in use."
echo
echo " -o, --output <output-directory>"
echo " Specifies the name of the output directory."
echo " If omitted, defaults to \"<input-file>.slurp\"."
echo
echo " -u, --unpack, --unslurp"
echo " Unpack a binary created by slurp."
}
#################################################
# Formats and prints the provided error message.
# Arguments:
# $1. The error message to format and print.
# Outputs:
# Writes the formatted error message to stderr.
# Returns:
# Always returns 1.
#################################################
error() {
echo "${0}: ${1}" >& 2
return 1
}
#################################################
# Formats and prints the provided error message,
# displays the help page, and terminates the
# process.
# Arguments:
# $1. The error message to format and print.
# Outputs:
# Writes the formatted error message to stderr.
# Returns:
# Never returns (exits with a status of 1).
#################################################
fatal_error() {
error "${1}"
help >& 2
exit 1
}
#################################################
# Finds a single executable file
# in the provided directory.
# Arguments:
# $1. The directory to search in.
# Outputs:
# Writes the path to the executable file,
# if exactly one is found, to stdout.
# Returns:
# 0 if exactly one executable file is found;
# otherwise, a non-zero status.
#################################################
find_single_executable() {
local exec_filename=$(find "${1}" -maxdepth 1 -type f -executable -exec file {} + | grep -iE ":.+executable" | cut -d: -f1)
[ -n "${exec_filename}" ] && [ $(echo "${exec_filename}" | wc -l) -eq 1 ] && echo "${exec_filename}"
}
#################################################
# Determines the default executable file
# in the provided directory.
# Arguments:
# $1. The directory to search in.
# Outputs:
# Writes the path to the executable file,
# if found, to stdout.
# Returns:
# 0 if a default executable is found;
# otherwise, a non-zero status.
#################################################
find_default_executable() {
local default_exec_filename=""
if [ -f "${1}/autorun" ] && [ -x "${1}/autorun" ]; then
default_exec_filename="${1}/autorun"
elif [ -f "${1}/autorun.sh" ] && [ -x "${1}/autorun.sh" ]; then
default_exec_filename="${1}/autorun.sh"
else
default_exec_filename=$(find_single_executable "${1}")
fi
[ -n "${default_exec_filename}" ] && echo "${default_exec_filename}"
}
#################################################
# Determines the default output filename based on
# the input directory and the executable file.
# Arguments:
# $1. The input directory.
# $2. The main executable file.
# Outputs:
# Writes the output filename to stdout.
# Returns:
# Always returns 0.
#################################################
get_default_output_filename() {
local input_dirname="${1}"
local exec_filename="${2}"
local output_filename="$(basename "${exec_filename}" ".sh")"
[ "${output_filename}" = "autorun" ] && output_filename="$(basename "$(realpath -m "${input_dirname}")")"
if [ -e "${output_filename}" ]; then
output_filename_suffix=1
while [ -e "${output_filename}.${output_filename_suffix}" ]; do
output_filename_suffix=$((${output_filename_suffix} + 1))
done
output_filename="${output_filename}.${output_filename_suffix}"
fi
echo "${output_filename}"
}
#################################################
# Creates a preamble for a self-extracting
# executable archive.
# Arguments:
# $1. The path of the main script.
# $2. The byte size of the resulting preamble.
# Outputs:
# Writes the preamble script to stdout.
#################################################
create_preamble() {
local exec_filename="${1}"
local byte_count=${2:-"$(create_preamble "${exec_filename}" 0 | wc -c)"}
local archive_start=$((${byte_count} + 1))
[ -z "${2}" ] && archive_start=$((${archive_start} + ${#archive_start} - 1))
echo '#!/bin/sh'
echo '#?slurp'
echo 'TEMP_DIR=$(mktemp -d)'
echo 'trap '\''rm -rf "$TEMP_DIR"; trap - EXIT; exit'\'' EXIT INT HUP'
echo 'tail -c +'${archive_start}' "$0" | tar -xzC "$TEMP_DIR" && "$TEMP_DIR/'"${exec_filename}"'" "$@"'
echo 'exit'
}
#################################################
# Packages a directory into a self-extracting
# executable archive.
# Arguments:
# $1. The input directory.
# $2. The main executable file.
# Outputs:
# Writes the resulting archive to stdout.
# Returns:
# 0 if the operation succeeds;
# otherwise, a non-zero status.
#################################################
pack() {
create_preamble "${2}"
tar -czf - -C "${1}" .
}
#################################################
# Packages a directory into a self-extracting
# executable archive and saves it as a file.
# Arguments:
# $1. The input directory.
# $2. The output filename (optional).
# $3. The main executable file (optional).
# Outputs:
# If output filename is '-', writes
# the resulting archive to stdout.
# Returns:
# 0 if the operation succeeds;
# otherwise, a non-zero status.
#################################################
pack_into_file() {
local input_dirname="${1}"
[ -n "${input_dirname}" ] || error "missing operand" || return
local exec_filename=$(realpath -s --relative-to="${input_dirname}" "${3:-"$(find_default_executable "${1}")"}" 2> /dev/null)
[ -n "${exec_filename}" ] || error "missing file operand" || return
echo "${exec_filename}" | grep -qvE '^(\.\.|/)' || error "invalid file operand: '${3}' points outside of '${1}'" || return
local output_filename="${2:-"$(get_default_output_filename "${input_dirname}" "${exec_filename}")"}"
if [ "${output_filename}" = "-" ]; then
pack "${input_dirname}" "${exec_filename}"
else
pack "${input_dirname}" "${exec_filename}" > "${output_filename}" &&
chmod a+x "${output_filename}"
fi
}
#################################################
# Checks if a file is a valid slurp binary.
# Arguments:
# $1. The file to check.
# Returns:
# 0 if the file is a valid slurp binary;
# otherwise, a non-zero status.
#################################################
is_slurp_file() {
local expected_header="#!/bin/sh #?slurp"
local header=$(head -c "$(printf '%s' "${expected_header}" | wc -c)" "${1}" 2> /dev/null | sed 'N;s/\n/ /')
[ "${header}" = "${expected_header}" ]
}
#################################################
# Extracts contents of a slurp binary
# into a directory.
# Arguments:
# $1. The input file.
# $2. The output directory (optional).
# Returns:
# 0 if the operation succeeds;
# otherwise, a non-zero status.
#################################################
unpack() {
local input_filename="${1}"
[ -e "${input_filename}" ] || error "${input_filename}: Cannot open: No such file or directory" || return
is_slurp_file "${input_filename}" || error "${input_filename}: Is not a file produced by slurp" || return
local output_dirname="${2:-"$(basename "${input_filename}").slurp"}"
[ -d "${output_dirname}" ] || mkdir "${output_dirname}" || return
sed '1,/^exit$/ d' "${input_filename}" | tar -xzC "${output_dirname}"
}
#################################################
# The main entry point for the script.
# Arguments:
# ... A list of the command line arguments.
# Returns:
# 0 if the operation succeeds;
# otherwise, a non-zero status.
#################################################
main() {
local input=""
local output=""
local exec_filename=""
local command="pack_into_file"
# Parse the arguments and options.
while [ $# -gt 0 ]; do
case "${1}" in
-h|--help) help; exit 0 ;;
-v|--version) version; exit 0 ;;
-r|--run) exec_filename="${2}"; shift ;;
-o|--output) output="${2}"; shift ;;
-u|--unpack|--unslurp) command="unpack" ;;
--) shift; break ;;
-*) fatal_error "invalid option: '${1}'" ;;
*) [ -z "${input}" ] && input="${1}" || fatal_error "invalid argument: '${1}'" ;;
esac
shift 2> /dev/null || fatal_error "missing operand"
done
while [ $# -gt 0 ]; do
[ -z "${input}" ] && input="${1}" || fatal_error "invalid argument: '${1}'"
shift
done
"${command}" "${input}" "${output}" "${exec_filename}"
}
main "${@}"