Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/bc/scripts/functions.sh
39492 views
1
#! /bin/sh
2
#
3
# SPDX-License-Identifier: BSD-2-Clause
4
#
5
# Copyright (c) 2018-2025 Gavin D. Howard and contributors.
6
#
7
# Redistribution and use in source and binary forms, with or without
8
# modification, are permitted provided that the following conditions are met:
9
#
10
# * Redistributions of source code must retain the above copyright notice, this
11
# list of conditions and the following disclaimer.
12
#
13
# * Redistributions in binary form must reproduce the above copyright notice,
14
# this list of conditions and the following disclaimer in the documentation
15
# and/or other materials provided with the distribution.
16
#
17
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
30
# This script is NOT meant to be run! It is meant to be sourced by other
31
# scripts.
32
33
# Reads and follows a link until it finds a real file. This is here because the
34
# readlink utility is not part of the POSIX standard. Sigh...
35
# @param f The link to find the original file for.
36
readlink() {
37
38
_readlink_f="$1"
39
shift
40
41
_readlink_arrow="-> "
42
_readlink_d=$(dirname "$_readlink_f")
43
44
_readlink_lsout=""
45
_readlink_link=""
46
47
_readlink_lsout=$(ls -dl "$_readlink_f")
48
_readlink_link=$(printf '%s' "${_readlink_lsout#*$_readlink_arrow}")
49
50
while [ -z "${_readlink_lsout##*$_readlink_arrow*}" ]; do
51
_readlink_f="$_readlink_d/$_readlink_link"
52
_readlink_d=$(dirname "$_readlink_f")
53
_readlink_lsout=$(ls -dl "$_readlink_f")
54
_readlink_link=$(printf '%s' "${_readlink_lsout#*$_readlink_arrow}")
55
done
56
57
printf '%s' "${_readlink_f##*$_readlink_d/}"
58
}
59
60
# Quick function for exiting with an error.
61
# @param 1 A message to print.
62
# @param 2 The exit code to use.
63
err_exit() {
64
65
if [ "$#" -ne 2 ]; then
66
printf 'Invalid number of args to err_exit\n'
67
exit 1
68
fi
69
70
printf '%s\n' "$1"
71
exit "$2"
72
}
73
74
# Function for checking the "d"/"dir" argument of scripts. This function expects
75
# a usage() function to exist in the caller.
76
# @param 1 The argument to check.
77
check_d_arg() {
78
79
if [ "$#" -ne 1 ]; then
80
printf 'Invalid number of args to check_d_arg\n'
81
exit 1
82
fi
83
84
_check_d_arg_arg="$1"
85
shift
86
87
if [ "$_check_d_arg_arg" != "bc" ] && [ "$_check_d_arg_arg" != "dc" ]; then
88
_check_d_arg_msg=$(printf 'Invalid d arg: %s\nMust be either "bc" or "dc".\n\n' \
89
"$_check_d_arg_arg")
90
usage "$_check_d_arg_msg"
91
fi
92
}
93
94
# Function for checking the boolean arguments of scripts. This function expects
95
# a usage() function to exist in the caller.
96
# @param 1 The argument to check.
97
check_bool_arg() {
98
99
if [ "$#" -ne 1 ]; then
100
printf 'Invalid number of args to check_bool_arg\n'
101
exit 1
102
fi
103
104
_check_bool_arg_arg="$1"
105
shift
106
107
if [ "$_check_bool_arg_arg" != "0" ] && [ "$_check_bool_arg_arg" != "1" ]; then
108
_check_bool_arg_msg=$(printf 'Invalid bool arg: %s\nMust be either "0" or "1".\n\n' \
109
"$_check_bool_arg_arg")
110
usage "$_check_bool_arg_msg"
111
fi
112
}
113
114
# Function for checking the executable arguments of scripts. This function
115
# expects a usage() function to exist in the caller.
116
# @param 1 The argument to check.
117
check_exec_arg() {
118
119
if [ "$#" -ne 1 ]; then
120
printf 'Invalid number of args to check_exec_arg\n'
121
exit 1
122
fi
123
124
_check_exec_arg_arg="$1"
125
shift
126
127
if [ ! -x "$_check_exec_arg_arg" ]; then
128
if ! command -v "$_check_exec_arg_arg" >/dev/null 2>&1; then
129
_check_exec_arg_msg=$(printf 'Invalid exec arg: %s\nMust be an executable file.\n\n' \
130
"$_check_exec_arg_arg")
131
usage "$_check_exec_arg_msg"
132
fi
133
fi
134
}
135
136
# Function for checking the file arguments of scripts. This function expects a
137
# usage() function to exist in the caller.
138
# @param 1 The argument to check.
139
check_file_arg() {
140
141
if [ "$#" -ne 1 ]; then
142
printf 'Invalid number of args to check_file_arg\n'
143
exit 1
144
fi
145
146
_check_file_arg_arg="$1"
147
shift
148
149
if [ ! -f "$_check_file_arg_arg" ]; then
150
_check_file_arg_msg=$(printf 'Invalid file arg: %s\nMust be a file.\n\n' \
151
"$_check_file_arg_arg")
152
usage "$_check_file_arg_msg"
153
fi
154
}
155
156
# Check the return code on a test and exit with a fail if it's non-zero.
157
# @param d The calculator under test.
158
# @param err The return code.
159
# @param name The name of the test.
160
checktest_retcode() {
161
162
_checktest_retcode_d="$1"
163
shift
164
165
_checktest_retcode_err="$1"
166
shift
167
168
_checktest_retcode_name="$1"
169
shift
170
171
if [ "$_checktest_retcode_err" -ne 0 ]; then
172
printf 'FAIL!!!\n'
173
err_exit "$_checktest_retcode_d failed test '$_checktest_retcode_name' with error code $_checktest_retcode_err" 1
174
fi
175
}
176
177
# Check the result of a test. First, it checks the error code using
178
# checktest_retcode(). Then it checks the output against the expected output
179
# and fails if it doesn't match.
180
# @param d The calculator under test.
181
# @param err The error code.
182
# @param name The name of the test.
183
# @param test_path The path to the test.
184
# @param results_name The path to the file with the expected result.
185
checktest() {
186
187
_checktest_d="$1"
188
shift
189
190
_checktest_err="$1"
191
shift
192
193
_checktest_name="$1"
194
shift
195
196
_checktest_test_path="$1"
197
shift
198
199
_checktest_results_name="$1"
200
shift
201
202
checktest_retcode "$_checktest_d" "$_checktest_err" "$_checktest_name"
203
204
_checktest_diff=$(diff "$_checktest_test_path" "$_checktest_results_name")
205
206
_checktest_err="$?"
207
208
if [ "$_checktest_err" -ne 0 ]; then
209
printf 'FAIL!!!\n'
210
printf '%s\n' "$_checktest_diff"
211
err_exit "$_checktest_d failed test $_checktest_name" 1
212
fi
213
}
214
215
# Die. With a message.
216
# @param d The calculator under test.
217
# @param msg The message to print.
218
# @param name The name of the test.
219
# @param err The return code from the test.
220
die() {
221
222
_die_d="$1"
223
shift
224
225
_die_msg="$1"
226
shift
227
228
_die_name="$1"
229
shift
230
231
_die_err="$1"
232
shift
233
234
_die_str=$(printf '\n%s %s on test:\n\n %s\n' "$_die_d" "$_die_msg" "$_die_name")
235
236
err_exit "$_die_str" "$_die_err"
237
}
238
239
# Check that a test did not crash and die if it did.
240
# @param d The calculator under test.
241
# @param error The error code.
242
# @param name The name of the test.
243
checkcrash() {
244
245
_checkcrash_d="$1"
246
shift
247
248
_checkcrash_error="$1"
249
shift
250
251
_checkcrash_name="$1"
252
shift
253
254
255
if [ "$_checkcrash_error" -gt 127 ]; then
256
die "$_checkcrash_d" "crashed ($_checkcrash_error)" \
257
"$_checkcrash_name" "$_checkcrash_error"
258
fi
259
}
260
261
# Check that a test had an error or crash.
262
# @param d The calculator under test.
263
# @param error The error code.
264
# @param name The name of the test.
265
# @param out The file that the test results were output to.
266
# @param exebase The name of the executable.
267
checkerrtest()
268
{
269
_checkerrtest_d="$1"
270
shift
271
272
_checkerrtest_error="$1"
273
shift
274
275
_checkerrtest_name="$1"
276
shift
277
278
_checkerrtest_out="$1"
279
shift
280
281
_checkerrtest_exebase="$1"
282
shift
283
284
checkcrash "$_checkerrtest_d" "$_checkerrtest_error" "$_checkerrtest_name"
285
286
if [ "$_checkerrtest_error" -eq 0 ]; then
287
die "$_checkerrtest_d" "returned no error" "$_checkerrtest_name" 127
288
fi
289
290
# This is to check for memory errors with Valgrind, which is told to return
291
# 100 on memory errors.
292
if [ "$_checkerrtest_error" -eq 100 ]; then
293
294
_checkerrtest_output=$(cat "$_checkerrtest_out")
295
_checkerrtest_fatal_error="Fatal error"
296
297
if [ "${_checkerrtest_output##*$_checkerrtest_fatal_error*}" ]; then
298
printf "%s\n" "$_checkerrtest_output"
299
die "$_checkerrtest_d" "had memory errors on a non-fatal error" \
300
"$_checkerrtest_name" "$_checkerrtest_error"
301
fi
302
fi
303
304
if [ ! -s "$_checkerrtest_out" ]; then
305
die "$_checkerrtest_d" "produced no error message" "$_checkerrtest_name" "$_checkerrtest_error"
306
fi
307
308
# To display error messages, uncomment this line. This is useful when
309
# debugging.
310
#cat "$_checkerrtest_out"
311
}
312
313
# Replace a substring in a string with another. This function is the *real*
314
# workhorse behind configure.sh's generation of a Makefile.
315
#
316
# This function uses a sed call that uses exclamation points `!` as delimiters.
317
# As a result, needle can never contain an exclamation point. Oh well.
318
#
319
# @param str The string that will have any of the needle replaced by
320
# replacement.
321
# @param needle The needle to replace in str with replacement.
322
# @param replacement The replacement for needle in str.
323
substring_replace() {
324
325
_substring_replace_str="$1"
326
shift
327
328
_substring_replace_needle="$1"
329
shift
330
331
_substring_replace_replacement="$1"
332
shift
333
334
_substring_replace_result=$(printf '%s\n' "$_substring_replace_str" | \
335
sed -e "s!$_substring_replace_needle!$_substring_replace_replacement!g")
336
337
printf '%s' "$_substring_replace_result"
338
}
339
340
# Generates an NLS path based on the locale and executable name.
341
#
342
# This is a monstrosity for a reason.
343
#
344
# @param nlspath The $NLSPATH
345
# @param locale The locale.
346
# @param execname The name of the executable.
347
gen_nlspath() {
348
349
_gen_nlspath_nlspath="$1"
350
shift
351
352
_gen_nlspath_locale="$1"
353
shift
354
355
_gen_nlspath_execname="$1"
356
shift
357
358
# Split the locale into its modifier and other parts.
359
_gen_nlspath_char="@"
360
_gen_nlspath_modifier="${_gen_nlspath_locale#*$_gen_nlspath_char}"
361
_gen_nlspath_tmplocale="${_gen_nlspath_locale%%$_gen_nlspath_char*}"
362
363
# Split the locale into charset and other parts.
364
_gen_nlspath_char="."
365
_gen_nlspath_charset="${_gen_nlspath_tmplocale#*$_gen_nlspath_char}"
366
_gen_nlspath_tmplocale="${_gen_nlspath_tmplocale%%$_gen_nlspath_char*}"
367
368
# Check for an empty charset.
369
if [ "$_gen_nlspath_charset" = "$_gen_nlspath_tmplocale" ]; then
370
_gen_nlspath_charset=""
371
fi
372
373
# Split the locale into territory and language.
374
_gen_nlspath_char="_"
375
_gen_nlspath_territory="${_gen_nlspath_tmplocale#*$_gen_nlspath_char}"
376
_gen_nlspath_language="${_gen_nlspath_tmplocale%%$_gen_nlspath_char*}"
377
378
# Check for empty territory and language.
379
if [ "$_gen_nlspath_territory" = "$_gen_nlspath_tmplocale" ]; then
380
_gen_nlspath_territory=""
381
fi
382
383
if [ "$_gen_nlspath_language" = "$_gen_nlspath_tmplocale" ]; then
384
_gen_nlspath_language=""
385
fi
386
387
# Prepare to replace the format specifiers. This is done by wrapping the in
388
# pipe characters. It just makes it easier to split them later.
389
_gen_nlspath_needles="%%:%L:%N:%l:%t:%c"
390
391
_gen_nlspath_needles=$(printf '%s' "$_gen_nlspath_needles" | tr ':' '\n')
392
393
for _gen_nlspath_i in $_gen_nlspath_needles; do
394
_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "$_gen_nlspath_i" "|$_gen_nlspath_i|")
395
done
396
397
# Replace all the format specifiers.
398
_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%%" "%")
399
_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%L" "$_gen_nlspath_locale")
400
_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%N" "$_gen_nlspath_execname")
401
_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%l" "$_gen_nlspath_language")
402
_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%t" "$_gen_nlspath_territory")
403
_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%c" "$_gen_nlspath_charset")
404
405
# Get rid of pipe characters.
406
_gen_nlspath_nlspath=$(printf '%s' "$_gen_nlspath_nlspath" | tr -d '|')
407
408
# Return the result.
409
printf '%s' "$_gen_nlspath_nlspath"
410
}
411
412
ALL=0
413
NOSKIP=1
414
SKIP=2
415
416
# Filters text out of a file according to the build type.
417
# @param in File to filter.
418
# @param out File to write the filtered output to.
419
# @param type Build type.
420
filter_text() {
421
422
_filter_text_in="$1"
423
shift
424
425
_filter_text_out="$1"
426
shift
427
428
_filter_text_buildtype="$1"
429
shift
430
431
# Set up some local variables.
432
_filter_text_status="$ALL"
433
_filter_text_last_line=""
434
435
# We need to set IFS, so we store it here for restoration later.
436
_filter_text_ifs="$IFS"
437
438
# Remove the file- that will be generated.
439
rm -rf "$_filter_text_out"
440
441
# Here is the magic. This loop reads the template line-by-line, and based on
442
# _filter_text_status, either prints it to the markdown manual or not.
443
#
444
# Here is how the template is set up: it is a normal markdown file except
445
# that there are sections surrounded tags that look like this:
446
#
447
# {{ <build_type_list> }}
448
# ...
449
# {{ end }}
450
#
451
# Those tags mean that whatever build types are found in the
452
# <build_type_list> get to keep that section. Otherwise, skip.
453
#
454
# Obviously, the tag itself and its end are not printed to the markdown
455
# manual.
456
while IFS= read -r _filter_text_line; do
457
458
# If we have found an end, reset the status.
459
if [ "$_filter_text_line" = "{{ end }}" ]; then
460
461
# Some error checking. This helps when editing the templates.
462
if [ "$_filter_text_status" -eq "$ALL" ]; then
463
err_exit "{{ end }} tag without corresponding start tag" 2
464
fi
465
466
_filter_text_status="$ALL"
467
468
# We have found a tag that allows our build type to use it.
469
elif [ "${_filter_text_line#\{\{* $_filter_text_buildtype *\}\}}" != "$_filter_text_line" ]; then
470
471
# More error checking. We don't want tags nested.
472
if [ "$_filter_text_status" -ne "$ALL" ]; then
473
err_exit "start tag nested in start tag" 3
474
fi
475
476
_filter_text_status="$NOSKIP"
477
478
# We have found a tag that is *not* allowed for our build type.
479
elif [ "${_filter_text_line#\{\{*\}\}}" != "$_filter_text_line" ]; then
480
481
if [ "$_filter_text_status" -ne "$ALL" ]; then
482
err_exit "start tag nested in start tag" 3
483
fi
484
485
_filter_text_status="$SKIP"
486
487
# This is for normal lines. If we are not skipping, print.
488
else
489
if [ "$_filter_text_status" -ne "$SKIP" ]; then
490
if [ "$_filter_text_line" != "$_filter_text_last_line" ]; then
491
printf '%s\n' "$_filter_text_line" >> "$_filter_text_out"
492
fi
493
_filter_text_last_line="$_filter_text_line"
494
fi
495
fi
496
497
done < "$_filter_text_in"
498
499
# Reset IFS.
500
IFS="$_filter_text_ifs"
501
}
502
503