]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/bc/scripts/functions.sh
zfs: merge openzfs/zfs@2e6b3c4d9
[FreeBSD/FreeBSD.git] / contrib / bc / scripts / functions.sh
1 #! /bin/sh
2 #
3 # SPDX-License-Identifier: BSD-2-Clause
4 #
5 # Copyright (c) 2018-2023 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 }