· 7 years ago · Dec 23, 2018, 05:02 PM
1#!/usr/bin/env bash
2# A Linux program to create bootable Windows USB stick from a real Windows DVD or an image
3# Copyright © 2013 Colin GILLE / congelli501
4# Copyright © 2018 slacka et. al.
5#
6# This file is part of WoeUSB.
7#
8# WoeUSB is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# WoeUSB is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with WoeUSB If not, see <http://www.gnu.org/licenses/>.
20#
21# Notes:
22# * Use `--strip` instead of `--no-symlinks` for realpath(1) for compatibility of Ubuntu 14.04(EoL: April 2019)
23#
24# We use indirections and primitive variables, which is false positive of this rule
25# shellcheck disable=SC2034
26
27## Makes debuggers' life easier - Unofficial Bash Strict Mode
28## BASHDOC: Shell Builtin Commands - Modifying Shell Behavior - The Set Builtin
29set \
30 -o errexit \
31 -o errtrace \
32 -o nounset \
33 -o pipefail
34
35## Enable aliases for easy access to functions
36shopt -s expand_aliases
37
38for critical_command in realpath basename dirname mktemp; do
39 if ! command -v "${critical_command}" &> /dev/null; then
40 echo 'Fatal: This program requires GNU Coreutils in the executable search path'
41 exit 1
42 fi
43done
44
45## Non-overridable Primitive Variables
46## BASHDOC: Shell Variables » Bash Variables
47## BASHDOC: Basic Shell Features » Shell Parameters » Special Parameters
48if [ -v 'BASH_SOURCE[0]' ]; then
49 RUNTIME_EXECUTABLE_PATH="$(realpath --strip "${BASH_SOURCE[0]}")"
50 RUNTIME_EXECUTABLE_FILENAME="$(basename "${RUNTIME_EXECUTABLE_PATH}")"
51 RUNTIME_EXECUTABLE_NAME="${RUNTIME_EXECUTABLE_FILENAME%.*}"
52 RUNTIME_EXECUTABLE_DIRECTORY="$(dirname "${RUNTIME_EXECUTABLE_PATH}")"
53 RUNTIME_COMMANDLINE_BASECOMMAND="${0}"
54 # Keep these partially unused variables declared
55 # shellcheck disable=SC2034
56 declare -r \
57 RUNTIME_EXECUTABLE_PATH \
58 RUNTIME_EXECUTABLE_FILENAME \
59 RUNTIME_EXECUTABLE_NAME \
60 RUNTIME_EXECUTABLE_DIRECTORY \
61 RUNTIME_COMMANDLINE_BASECOMMAND
62fi
63declare -ar RUNTIME_COMMANDLINE_PARAMETERS=("${@}")
64
65## Global Parameters
66## Only set parameters global when there's no other way around(like passing in function as a function argument), usually when the variable is directly or indirectly referenced by traps
67## Even when the parameter is set global, you should pass it in as function argument when it's possible for better code reusability.
68## TODO: Global parameter cleanup
69### Doing GUI-specific stuff when set to `true`, set by --only-for-gui
70### Requires to be set to global due to indirectly referenced by trap
71declare global_only_for_gui=false
72
73### Increase verboseness, provide more information when required
74declare verbose=false
75
76### Disable message coloring when set to `true`, set by --no-color
77declare no_color=false
78
79declare -ir DD_BLOCK_SIZE="((4 * 1024 * 1024))" # 4MiB
80
81## NOTE: Need to pass to traps, so need to be global
82declare \
83 source_fs_mountpoint \
84 target_fs_mountpoint \
85 target_device
86
87## FIXME: No documentation for this non-trivial parameter
88declare -i pulse_current_pid=0
89
90## Execution state for cleanup functions to determine if clean up is required
91## VALUE: Dash-seperated, lowercased words, no quoting
92## NOTE: Need to pass to traps, so need to be global
93declare current_state=pre-init
94
95## Supported filesystems
96## A associated array with supported filesystem's identifier as key and its description as value
97declare -A ENUM_SUPPORTED_FILESYSTEMS=(
98 [FS_FAT]='File Allocation Table(FAT)'
99 [FS_NTFS]='New Technology File System(NTFS)'
100)
101
102## For some reason alias won't be recognized in function if it's definition's LINENO is greater then it's reference in function, so we define it here:
103alias \
104 echo_with_color=util_echo_with_color \
105 switch_terminal_text_color=util_switch_terminal_text_color \
106 shift_array=util_shift_array \
107 is_target_busy=check_is_target_device_busy \
108 printf_with_color=util_printf_with_color
109
110declare temp_directory; temp_directory=$(
111 # COMPATIBILITY: Use short option for Slackware 14.2
112 # -d: --directory
113 # -t: --tmpdir
114 mktemp \
115 -d \
116 -t \
117 WoeUSB.XXXXXX.tempdir
118); declare -r temp_directory
119
120## Entry point of the main code
121init(){
122 util_check_function_parameters_quantity 2 $#
123 local -r runtime_executable_name="$1"; shift
124 local -n only_for_gui_ref="$1"
125
126 declare -r application_name='WoeUSB'
127 declare -r application_version='3.2.10-3-g289edfe'
128
129 declare -r DEFAULT_NEW_FS_LABEL='Windows USB'
130
131 current_state=enter-init
132
133 local \
134 flag_print_help=false \
135 flag_print_version=false \
136 flag_print_about=false \
137
138 local -r \
139 application_site_url='https://github.com/slacka/WoeUSB' \
140 application_copyright_declaration="Copyright © Colin GILLE / congelli501 2013\\nCopyright © slacka et.al. 2017" \
141 application_copyright_notice="${application_name} is free software licensed under the GNU General Public License version 3(or any later version of your preference) that gives you THE 4 ESSENTIAL FREEDOMS\\nhttps://www.gnu.org/philosophy/"
142 local install_mode
143
144 # source_media may be a optical disk drive or a disk image
145 # target_media may be an entire usb storage device or just a partition
146 local \
147 source_media \
148 target_media
149
150 local target_partition
151
152 local workaround_bios_boot_flag=false
153
154 local target_filesystem_type=FS_FAT
155
156 source_fs_mountpoint="/media/woeusb_source_$(date +%s)_$$"
157 target_fs_mountpoint="/media/woeusb_target_$(date +%s)_$$"
158
159 # Parameters that needs to be determined in runtime
160 # due to different names in distributions
161 declare \
162 command_mkdosfs \
163 command_mkntfs \
164 command_grubinstall \
165 name_grub_prefix
166
167 declare new_file_system_label="${DEFAULT_NEW_FS_LABEL}"
168
169 if ! check_runtime_dependencies \
170 "${application_name}" \
171 command_mkdosfs \
172 command_mkntfs \
173 command_grubinstall \
174 name_grub_prefix; then
175 exit 1
176 fi
177
178 if ! \
179 process_commandline_parameters \
180 application_name \
181 flag_print_help \
182 flag_print_version \
183 flag_print_about \
184 install_mode \
185 source_media \
186 target_media \
187 new_file_system_label \
188 workaround_bios_boot_flag \
189 target_filesystem_type \
190 global_only_for_gui; then
191 print_help \
192 "${application_name}" \
193 "${application_version}" \
194 "${runtime_executable_name}"
195 exit 1
196
197 fi
198
199 process_miscellaneous_requests \
200 "${flag_print_help}" \
201 "${flag_print_version}" \
202 "${flag_print_about}" \
203 "${application_name}" \
204 "${application_version}" \
205 "${application_site_url}" \
206 "${application_copyright_declaration}" \
207 "${application_copyright_notice}" \
208 "${runtime_executable_name}"
209
210 printf -- \
211 '%s\n%s\n' \
212 "${application_name} v${application_version}" \
213 '=============================='
214
215 check_permission \
216 "${application_name}"
217
218 if ! check_runtime_parameters \
219 install_mode \
220 source_media \
221 target_media \
222 "${new_file_system_label}"; then
223 print_help \
224 "${application_name}" \
225 "${application_version}" \
226 "${runtime_executable_name}"
227 exit 1
228 fi
229
230 # FIXME: Why `trigger_wxGenericProgressDialog_pulse on` here?
231 trigger_wxGenericProgressDialog_pulse \
232 on \
233 "${only_for_gui_ref}"
234
235 determine_target_parameters \
236 "${install_mode}" \
237 "${target_media}" \
238 target_device \
239 target_partition
240
241 check_source_and_target_not_busy \
242 "${install_mode}" \
243 "${source_media}" \
244 "${target_device}" \
245 "${target_partition}"
246
247 current_state=mount-source-filesystem
248
249 if ! mount_source_filesystem \
250 "${source_media}" \
251 "${source_fs_mountpoint}"; then
252 echo_with_color red 'Error: Unable to mount source filesystem'
253 exit 1
254 fi
255
256 if [ "${target_filesystem_type}" == FS_FAT ]; then
257 if ! check_fat32_filesize_limitation \
258 "${source_fs_mountpoint}"; then
259 exit 1
260 fi
261 fi
262
263 if [ "${install_mode}" = device ]; then
264 wipe_existing_partition_table_and_filesystem_signatures \
265 "${target_device}"
266 create_target_partition_table \
267 "${target_device}" \
268 legacy
269 create_target_partition \
270 "${target_partition}" \
271 "${target_filesystem_type}" \
272 "${new_file_system_label}" \
273 "${command_mkdosfs}" \
274 "${command_mkntfs}"
275
276 if [ "${target_filesystem_type}" == FS_NTFS ]; then
277 create_uefi_ntfs_support_partition \
278 "${target_device}"
279 install_uefi_ntfs_support_partition \
280 "${target_device}2" \
281 "${temp_directory}" \
282 "${target_device}"
283 fi
284 fi
285
286 if [ "${install_mode}" = partition ]; then
287 check_target_partition \
288 "${target_partition}" \
289 "${install_mode}" \
290 "${target_device}"
291 fi
292
293 current_state=mount-target-filesystem
294
295 if ! mount_target_filesystem \
296 "${target_partition}" \
297 "${target_fs_mountpoint}" \
298 "${target_filesystem_type}"; then
299 echo_with_color red 'Error: Unable to mount target filesystem'
300 exit 1
301 fi
302
303 check_target_filesystem_free_space \
304 "${target_fs_mountpoint}" \
305 "${source_fs_mountpoint}" \
306 || exit 1
307
308 current_state=copying-filesystem
309
310 workaround_linux_make_writeback_buffering_not_suck \
311 apply
312
313 copy_filesystem_files \
314 "${source_fs_mountpoint}" \
315 "${target_fs_mountpoint}" \
316 "${only_for_gui_ref}"
317
318 workaround_support_windows_7_uefi_boot \
319 "${source_fs_mountpoint}" \
320 "${target_fs_mountpoint}"
321
322 install_legacy_pc_bootloader_grub \
323 "${target_fs_mountpoint}" \
324 "${target_device}" \
325 "${command_grubinstall}"
326
327 install_legacy_pc_bootloader_grub_config \
328 "${target_fs_mountpoint}" \
329 "${name_grub_prefix}"
330
331 if [ "${workaround_bios_boot_flag}" == true ]; then
332 workaround_buggy_motherboards_that_ignore_disks_without_boot_flag_toggled \
333 "${target_device}"
334 fi
335
336 current_state=finished
337
338 trigger_wxGenericProgressDialog_pulse \
339 off \
340 "${only_for_gui_ref}"
341
342 exit 0
343}; declare -fr init
344
345print_help(){
346 util_check_function_parameters_quantity 3 "${#}"
347
348 local -r application_name="$1"; shift 1
349 local -r application_version="$1"; shift 1
350 local -r runtime_executable_name="$1"
351
352 set +o xtrace
353 echo -e "${application_name} ${application_version} Help Information"
354 echo -e "======================================"
355 echo -e "${application_name} can create a bootable Microsoft Windows(R) USB storage device from an existing Windows optical disk or an ISO disk image."
356 echo -e
357 echo -e 'Currently two creation methods are supported:'
358 echo -e
359 echo -e ' --device, -d'
360 echo -e
361 echo -e ' ''Completely WIPE the entire USB storage device, then build a bootable Windows USB device from scratch.'
362 echo -e ' ''WARNING: All previous data on the device will be gone!'
363 echo -e
364 echo -e ' '"${runtime_executable_name} --device <source media path> <device>"
365 echo -e ' ''Examples:'
366 echo -e ' '"- ${runtime_executable_name} --device Windows7_x64.iso /dev/sdX"
367 echo -e ' '"- ${runtime_executable_name} --device /dev/sr0 /dev/sdX"
368 echo -e
369 echo -e ' --partition, -p'
370 echo -e
371 echo -e ' ''Copy Windows files to an existing partition of a USB storage device and make it bootable. This allows files to coexist as long as no filename conflict exists.'
372 echo -e ' ''WARNING: All files that has the same name will be overwritten!'
373 echo -e
374 echo -e ' '"${runtime_executable_name} --partition <source media path> <partition>"
375 echo -e ' ''Examples:'
376 echo -e ' '"- ${runtime_executable_name} --partition Windows7_x64.iso /dev/sdX1"
377 echo -e ' '"- ${runtime_executable_name} --partition /dev/sr0 /dev/sdX1"
378 echo -e
379 echo -e '## ''Command-line Options'' ##'
380 echo -e ' --verbose, -v'
381 echo -e ' ''Verbose mode'
382 echo -e
383 echo -e ' --help, -h'
384 echo -e ' ''Show this help message and exit'
385 echo -e
386 echo -e ' --version, -V'
387 echo -e ' ''Print application version'
388 echo -e
389 echo -e ' --about, -ab'
390 echo -e ' ''Show info about this application'
391 echo -e
392 echo -e ' --no-color'
393 echo -e ' ''Disable message coloring'
394 echo -e
395 echo -e ' --debug'
396 echo -e ' ''Enable script debugging'
397 echo -e
398 echo -e ' --label, -l <filesystem_label>'
399 echo -e ' ''Specify label for the newly created file system in --device creation method'
400 echo -e ' ''Note that the label is not verified for validity and may be illegal for the filesystem'
401 echo -e
402 echo -e ' --workaround-bios-boot-flag'
403 echo -e ' '"Workaround BIOS bug that won't include the device in boot menu if non of the partition's boot flag is toggled"
404 echo -e
405 echo -e ' --debugging-internal-function-call <function name> (function_argument)...'
406 echo -e ' ''Development option for developers to test certain function without running the entire build'
407 echo -e
408 echo -e ' --target-filesystem, --tgt-fs <filesystem name>'
409 echo -e ' '"Specify the filesystem to use as the target partition's filesystem."
410 echo -e ' ''Currently supported: FAT(default)/NTFS'
411 echo -e
412}; declare -fr print_help
413
414## Print application version then exit, for debugging purpose.
415## application_version: Version of the application
416print_version(){
417 util_check_function_parameters_quantity 1 $#
418
419 local -r application_version="$1"
420
421 printf -- \
422 '%s\n' \
423 "${application_version}"
424}; declare -fr print_version
425
426print_application_info(){
427 util_check_function_parameters_quantity 5 $#
428 local -r application_name="$1"; shift
429 local -r application_version="$1"; shift
430 local -r application_site_url="$1"; shift
431 local -r application_copyright_declaration="$1"; shift
432 local -r application_copyright_notice="$1"
433
434 echo "${application_name} ${application_version}"
435 echo -e "${application_site_url}"
436 echo -e "${application_copyright_declaration}"
437 echo -e "${application_copyright_notice}"
438
439}; declare -fr print_application_info
440
441process_commandline_parameters(){
442 util_check_function_parameters_quantity 11 "${#}"
443 local -r application_name="${1}"; shift
444 local -n flag_print_help_ref="${1}"; shift
445 local -n flag_print_version_ref="${1}"; shift
446 local -n flag_print_about_ref="${1}"; shift
447 local -n install_mode_ref="${1}"; shift
448 local -n source_media_ref="${1}"; shift
449 local -n target_media_ref="${1}"; shift
450 local -n new_file_system_label_ref="${1}"; shift
451 local -n workaround_bios_boot_flag_ref="${1}"; shift
452 local -n target_filesystem_type_ref="${1}"; shift
453 local -n only_for_gui_ref="${1}"
454
455 if [ "${#RUNTIME_COMMANDLINE_PARAMETERS[@]}" -eq 0 ]; then
456 flag_print_help_ref=true
457 return 0
458 fi
459
460 local -a parameters=("${RUNTIME_COMMANDLINE_PARAMETERS[@]}")
461 local \
462 enable_debug=false \
463 enable_device=false \
464 enable_partition=false \
465 enable_label=false \
466 enable_workaround_bios_boot_flag=false \
467 enable_debugging_internal_function_call=false \
468 enable_target_filesystem=false
469
470 # Inputs that requires sanitation
471 local target_filesystem_type_input
472
473 while [ "${#parameters[@]}" -ne 0 ]; do
474 case "${parameters[0]}" in
475 --help \
476 |-h)
477 flag_print_help_ref=true
478 return 0
479 ;;
480 --version \
481 |-V)
482 flag_print_version_ref=true
483 return 0
484 ;;
485 --about \
486 |-ab)
487 flag_print_about_ref=true
488 return 0
489 ;;
490 --debug)
491 enable_debug=true
492 ;;
493 --partition \
494 |-p)
495 enable_partition=true
496 install_mode_ref=partition
497 shift_array parameters
498 if [ "${#parameters[@]}" -lt 2 ]; then
499 echo_with_color \
500 red \
501 "${FUNCNAME[0]}: Error: --partition option requires 2 arguments!"
502 return 1
503 fi
504 source_media_ref="${parameters[0]}"
505 shift_array parameters
506 target_media_ref="${parameters[0]}"
507 ;;
508 --device \
509 |-d)
510 enable_device=true
511 # Limitation of ShellCheck to detect usage of indirection variables
512 # https://github.com/koalaman/shellcheck/wiki/SC2034
513 # shellcheck disable=SC2034
514 install_mode_ref=device
515 shift_array parameters
516 if [ "${#parameters[@]}" -lt 2 ]; then
517 echo_with_color \
518 red \
519 "${FUNCNAME[0]}: Error: --device option requires 2 arguments!"
520 return 1
521 fi
522 source_media_ref="${parameters[0]}"
523 shift_array parameters
524 target_media_ref="${parameters[0]}"
525 ;;
526 --verbose|-v)
527 verbose=true
528 ;;
529 --for-gui)
530 no_color=true
531 only_for_gui_ref=true
532 ;;
533 --no-color)
534 no_color=true
535 ;;
536 --label|-l)
537 enable_label=true
538 shift_array parameters
539 if [ ${#parameters[@]} -lt 1 ]; then
540 printf_with_color \
541 red \
542 '%s: %s\n' \
543 "${FUNCNAME[0]}" \
544 'ERROR: --label option requires 1 argument.'
545 return 1
546 fi
547 new_file_system_label_ref="${parameters[0]}"
548 ;;
549 --workaround-bios-boot-flag)
550 enable_workaround_bios_boot_flag=true
551 workaround_bios_boot_flag_ref=true
552 ;;
553 --debugging-internal-function-call)
554 enable_debugging_internal_function_call=true
555 shift_array parameters
556
557 if [ ${#parameters[@]} -lt 1 ]; then
558 printf_with_color \
559 red \
560 '%s: %s\n' \
561 "${FUNCNAME[0]}" \
562 'ERROR: --debugging-internal-function-call option requires at least 1 argument.'
563 return 1
564 fi
565 "${parameters[@]}"
566 exit ${?}
567 ;;
568 --target-filesystem \
569 |--tgt-fs)
570 enable_target_filesystem=true
571
572 shift_array parameters
573 if [ ${#parameters[@]} -lt 1 ]; then
574 printf_with_color \
575 red \
576 '%s: %s\n' \
577 "${FUNCNAME[0]}" \
578 'ERROR: --target-filesystem option requires 1 argument.'
579 return 1
580 fi
581 target_filesystem_type_input="$(
582 # Normalize input to uppercase
583 tr \
584 '[:lower:]' \
585 '[:upper:]' \
586 <<< "${parameters[0]}"
587 )"
588 ;;
589 *)
590 echo_with_color red "ERROR: Unknown command-line argument \"${parameters[0]}\"" >&2
591 return 1
592 ;;
593 esac
594 shift_array parameters
595 done
596
597 # None useful action is specified
598 if \
599 [ "${enable_device}" == false ] \
600 && [ "${enable_partition}" == false ] \
601 && [ "${enable_debugging_internal_function_call}" == false ]; then
602 # intentionally don't mention about the internal function call option
603 echo_with_color \
604 red \
605 "${FUNCNAME[0]}: Error: No creation method specified!" >&2
606 return 1
607 elif \
608 [ "${enable_device}" == true ] \
609 && [ "${enable_partition}" == true ]; then
610 echo_with_color red "${FUNCNAME[0]}: Error: --device and --partition creation method are mutual-exclusive." >&2
611 return 1
612 fi
613
614 # --label option is no use with --partition creation method
615 if \
616 [ "${enable_partition}" == true ] \
617 && [ "${enable_label}" == true ]; then
618 echo_with_color red "${FUNCNAME[0]}: Error: --label option only can be used with --device creation method"
619 return 1
620 fi
621
622 ## --target-filesystem option is no use with --partition creation method
623 if \
624 [ "${enable_target_filesystem}" == true ] \
625 && [ "${enable_partition}" == true ]; then
626 echo_with_color red "${FUNCNAME[0]}: Error: --target-filesystem option only can be used with --device creation method"
627 return 1
628 fi
629
630 ## --target-filesystem should be supported
631 if \
632 [ "${enable_target_filesystem}" = true ]; then
633 case "${target_filesystem_type_input}" in
634 FAT)
635 target_filesystem_type_ref=FS_FAT
636 ;;
637 NTFS)
638 target_filesystem_type_ref=FS_NTFS
639 ;;
640 *)
641 printf \
642 -- \
643 '%s: Error: Target filesystem not supported.\n' \
644 "${FUNCNAME[0]}" \
645 >&2
646 return 1
647 ;;
648 esac
649 fi
650
651 if [ "${verbose}" = true ] && [ "${enable_debug}" != true ]; then
652 trap 'trap_return "${FUNCNAME[0]}"' RETURN
653
654 # Disabled due to FIXME
655 # trap 'trap_debug "${BASH_COMMAND}"' DEBUG
656 fi
657
658 # Always be the last condition so that less debug message from this function will be printed
659 if [ "${enable_debug}" = true ]; then
660 set -o xtrace
661 fi
662 return 0
663}; declare -fr process_commandline_parameters
664
665process_miscellaneous_requests(){
666 util_check_function_parameters_quantity 9 $#
667 local -r flag_print_help="${1}"; shift
668 local -r flag_print_version="${1}"; shift
669 local -r flag_print_about="${1}"; shift
670 local -r application_name="${1}"; shift
671 local -r application_version="${1}"; shift
672 local -r application_site_url="${1}"; shift
673 local -r application_copyright_declaration="${1}"; shift
674 local -r application_copyright_notice="${1}"; shift
675 local -r runtime_executable_name="${1}"; shift
676
677 if [ "${flag_print_help}" == true ]; then
678 print_help \
679 "${application_name}" \
680 "${application_version}" \
681 "${runtime_executable_name}"
682 exit 0
683 fi
684
685 if [ "${flag_print_version}" == true ]; then
686 print_version \
687 "${application_version}"
688 exit 0
689 fi
690
691 if [ "${flag_print_about}" == true ]; then
692 print_application_info \
693 "${application_name}" \
694 "${application_version}" \
695 "${application_site_url}" \
696 "${application_copyright_declaration}" \
697 "${application_copyright_notice}"
698 exit 0
699 fi
700
701}; declare -fr process_miscellaneous_requests
702
703check_runtime_dependencies(){
704 util_check_function_parameters_quantity 5 $#
705 local -r application_name="$1"; shift
706 local -n command_mkdosfs_ref="$1"; shift
707 local -n command_mkntfs_ref="$1"; shift
708 local -n command_grubinstall_ref="$1"; shift
709 local -n name_grub_prefix_ref="$1"
710
711 local result=unknown
712
713 for required_command in \
714 awk \
715 blockdev \
716 dd \
717 df \
718 du \
719 find \
720 grep \
721 id \
722 lsblk \
723 mkdir \
724 mktemp \
725 mount \
726 parted \
727 partprobe \
728 readlink \
729 rm \
730 stat \
731 wget \
732 wipefs
733 do
734 if ! command -v "${required_command}" >/dev/null; then
735 echo_with_color red "${FUNCNAME[0]}: Error: ${application_name} requires ${required_command} command in the executable search path, but it is not found."
736 result=failed
737 fi
738 done; unset required_command
739
740 if command -v mkdosfs &> /dev/null; then
741 command_mkdosfs_ref=mkdosfs
742 elif command -v mkfs.msdos &> /dev/null; then
743 command_mkdosfs_ref=mkfs.msdos
744 elif command -v mkfs.vfat &>/dev/null; then
745 command_mkdosfs_ref=mkfs.vfat
746 elif command -v mkfs.fat &>/dev/null; then
747 command_mkdosfs_ref=mkfs.fat
748 else
749 echo_with_color red \
750 "${FUNCNAME[0]}: Error: mkdosfs/mkfs.msdos/mkfs.vfat/mkfs.fat command not found!" >&2
751 echo_with_color red \
752 "${FUNCNAME[0]}: Error: Please make sure that dosfstools is properly installed!" >&2
753 result='failed'
754 fi
755
756 if command -v mkntfs &>/dev/null; then
757 command_mkntfs_ref=mkntfs
758 else
759 printf_with_color red \
760 '%s\n%s\n' \
761 "${FUNCNAME[0]}: Error: mkntfs command not found!" \
762 "${FUNCNAME[0]}: Error: Please make sure that ntfs-3g is properly installed!"
763 result=failed
764 fi
765
766 if command -v grub-install &> /dev/null; then
767 command_grubinstall_ref=grub-install
768 name_grub_prefix_ref=grub
769 elif command -v grub2-install &> /dev/null; then
770 command_grubinstall_ref=grub2-install
771 name_grub_prefix_ref=grub2
772 else
773 echo_with_color red "${FUNCNAME[0]}: Error: grub-install or grub2-install command not found!" >&2
774 echo_with_color red "${FUNCNAME[0]}: Error: Please make sure that GNU GRUB is properly installed!" >&2
775 result=failed
776 fi
777
778 if [ "${result}" == failed ]; then
779 return 1
780 else
781 return 0
782 fi
783}; declare -fr check_runtime_dependencies
784
785check_permission(){
786 util_check_function_parameters_quantity 1 $#
787 local -r application_name="$1"
788
789 if [ ! "$(id --user)" = 0 ]; then
790 printf_with_color \
791 yellow \
792 '%s\n%s\n' \
793 "Warning: You are not running ${application_name} as root!" \
794 'Warning: This might be the reason of the following failure.' >&2
795 fi
796 return 0
797}; declare -fr check_permission
798
799check_runtime_parameters(){
800 util_check_function_parameters_quantity 4 $#
801 local -n install_mode_ref="${1}"; shift
802 local -n source_media_ref="${1}"; shift
803 local -n target_media_ref="${1}"
804
805 if [ ! -f "${source_media_ref}" ] \
806 && [ ! -b "${source_media_ref}" ]; then
807 echo_with_color red "${FUNCNAME[0]}: Error: source media \"${source_media_ref}\" not found or not a regular file or a block device file!" >&2
808 return 1
809 fi
810
811 if ! [ -b "${target_media_ref}" ]; then
812 echo_with_color red "${FUNCNAME[0]}: Error: Target media \"${target_media_ref}\" is not a block device file!" >&2
813 return 1
814 fi
815
816 if [ "${install_mode_ref}" = device ] \
817 && [[ "${target_media}" =~ .*[0-9] ]]
818 then
819 echo_with_color red "${FUNCNAME[0]}: Error: Target media \"${target_media_ref}\" is not an entire storage device!"
820 return 1
821 fi
822
823 if [ "${install_mode_ref}" = partition ] \
824 && ! [[ "${target_media}" =~ .*[0-9] ]]
825 then
826 echo_with_color red "${FUNCNAME[0]}: Error: Target media \"${target_media_ref}\" is not an partition!"
827 return 1
828 fi
829
830 return 0
831}; declare -fr check_runtime_parameters
832
833determine_target_parameters(){
834 util_check_function_parameters_quantity 4 $#
835 local install_mode="${1}"; shift
836 local target_media="${1}"; shift
837 local -n target_device_ref="${1}"; shift
838 local -n target_partition_ref="${1}"; shift
839
840 if [ "${install_mode}" = partition ]; then
841 target_partition_ref="${target_media}"
842 # BASHDOC: Basic Shell Features » Shell Expansions » Shell Parameter Expansion(`${PARAMETER/PATTERN/STRING}')
843 target_device_ref="${target_media/%[0-9]/}"
844 else # install_mode = device
845 target_device_ref="${target_media}"
846 target_partition_ref="${target_device}1"
847 fi
848
849 if [ "${verbose}" = true ]; then
850 echo "${FUNCNAME[0]}: Info: Target device is '${target_device_ref}'."
851 echo "${FUNCNAME[0]}: Info: Target partition is '${target_partition_ref}'."
852 fi
853 return 0
854}; declare -fr determine_target_parameters
855
856check_is_target_device_busy(){
857 util_check_function_parameters_quantity 1 $#
858 local device="${1}"
859
860 if [ "$(mount \
861 | grep \
862 --count \
863 --fixed-strings\
864 "${device}"
865 )" -ne 0 ]; then
866 return 0
867 else
868 return 1
869 fi
870}; declare -fr check_is_target_device_busy
871
872check_source_and_target_not_busy(){
873 util_check_function_parameters_quantity 4 $#
874 local install_mode="$1"; shift
875 local source_media="$1"; shift
876 local target_device="$1"; shift
877 local target_partition="$1"
878
879 if [ "$(mount \
880 | grep \
881 --count \
882 --fixed-strings \
883 "${source_media}"
884 )" != 0 ]; then
885 echo_with_color red "Error: Source media is currently mounted, unmount the partition then try again"
886 exit 1
887 fi
888
889 if [ "${install_mode}" = partition ]; then
890 if [ "$(mount \
891 | grep \
892 --count \
893 --fixed-strings \
894 "${target_partition}"
895 )" != 0 ]; then
896 echo_with_color red "Error: Target partition is currently mounted, unmount the partition then try again"
897 exit 1
898 fi
899 else # When install_mode = device, all target partitions needs to by unmounted
900 if is_target_busy "${target_device}"; then
901 echo_with_color red "Error: Target device is currently busy, unmount all mounted partitions in target device then try again"
902 exit 1
903 fi
904 fi
905}; declare -fr check_source_and_target_not_busy
906
907wipe_existing_partition_table_and_filesystem_signatures(){
908 util_check_function_parameters_quantity 1 $#
909 local -r target_device="${1}"
910
911 echo_with_color green "Wiping all existing partition table and filesystem signatures in ${target_device}..."
912 wipefs --all "${target_device}"
913
914 check_if_the_drive_is_really_wiped \
915 "${target_device}"
916
917 return $?
918}; declare -fr wipe_existing_partition_table_and_filesystem_signatures
919
920## Some broken locked-down flash drive will appears to be successfully wiped but actually nothing is written into it and will shown previous partition scheme afterwards. This is the detection of the case and will bail out if such things happened
921## target_device: The target device file to be checked
922check_if_the_drive_is_really_wiped(){
923 util_check_function_parameters_quantity 1 $#
924 local -r target_device="$1"
925
926 printf_with_color \
927 green \
928 'Ensure that %s is really wiped...\n' \
929 "${target_device}"
930
931 if [ \
932 "$(lsblk \
933 --pairs \
934 --output NAME,TYPE \
935 "${target_device}" \
936 | grep \
937 --count \
938 --fixed-strings \
939 ' TYPE="part"'
940 )" -ne 0 ]; then
941 printf_with_color \
942 red \
943 'Error: %s: Partition is still detected after wiping all signatures, this indicates that the drive might be locked into readonly mode due to end of lifespan.\n' \
944 "${FUNCNAME[0]}"
945 return 1
946 else
947 return 0
948 fi
949}; declare -fr check_if_the_drive_is_really_wiped
950
951create_target_partition_table(){
952 util_check_function_parameters_quantity 2 $#
953 local -r target_device="${1}"; shift
954 local -r partition_table_type="${1}"
955
956 echo_with_color green "Creating new partition table on ${target_device}..."
957
958 local parted_partiton_table_argument
959
960 case "${partition_table_type}" in
961 legacy|msdos|mbr|pc)
962 parted_partiton_table_argument=msdos
963 ;;
964 gpt|guid)
965 parted_partiton_table_argument=gpt
966 echo_with_color \
967 red \
968 "${FUNCNAME[0]}: Error: Currently GUID partition table is not supported."
969 return 2
970 ;;
971 *)
972 echo_with_color \
973 red \
974 "${FUNCNAME[0]}: Error: Partition table not supported."
975 return 2
976 ;;
977 esac
978
979 # Create partition table(and overwrite the old one, whatever it was)
980 parted --script \
981 "${target_device}" \
982 mklabel \
983 "${parted_partiton_table_argument}"
984}; declare -fr create_target_partition_table
985
986# NOTE: This really should be done automatically by GNU Parted after every operation
987workaround_make_system_realize_partition_table_changed(){
988 util_check_function_parameters_quantity 1 $#
989 local -r target_device="$1"
990
991 printf_with_color \
992 green \
993 '%s\n' \
994 'Making system realize that partition table has changed...'
995
996 blockdev \
997 --rereadpt \
998 "${target_device}" \
999 || true
1000 echo 'Wait 3 seconds for block device nodes to populate...'
1001 sleep 3
1002
1003}; declare -fr workaround_make_system_realize_partition_table_changed
1004
1005create_target_partition(){
1006 util_check_function_parameters_quantity 5 $#
1007 local -r target_partition="$1"; shift
1008
1009 # ENUM_SUPPORTED_FILESYSTEMS
1010 local -r filesystem_type="$1"; shift
1011
1012 local -r filesystem_label="$1"; shift
1013 local -r command_mkdosfs="$1"; shift
1014 local -r command_mkntfs="$1"
1015
1016 # Refer: GNU Parted's (info) manual: Using Parted » Command explanations » mkpart
1017 # Refer: sudo parted --script /dev/sda help mkpart
1018 local parted_mkpart_fs_type
1019 case "${filesystem_type}" in
1020 FS_FAT)
1021 parted_mkpart_fs_type=fat32
1022 ;;
1023 FS_NTFS)
1024 parted_mkpart_fs_type=ntfs
1025 ;;
1026 *)
1027 echo_with_color red "${FUNCNAME[0]}: Error: Filesystem not supported"
1028 return 2
1029 ;;
1030 esac
1031
1032 printf_with_color green \
1033 '%s\n' \
1034 'Creating target partition...'
1035
1036 # Create target partition
1037 # We start at 4MiB for grub (it needs a post-mbr gap for its code) and alignment of flash memery block erase segment in general, for details see http://www.gnu.org/software/grub/manual/grub.html#BIOS-installation and http://lwn.net/Articles/428584/
1038 # If NTFS filesystem is used we leave a 512KiB partition at the end for installing UEFI:NTFS partition for NTFS support
1039 case "${parted_mkpart_fs_type}" in
1040 fat32)
1041 parted --script \
1042 "${target_device}" \
1043 mkpart \
1044 primary \
1045 "${parted_mkpart_fs_type}" \
1046 4MiB \
1047 -- -1s # last sector of the disk
1048 ;;
1049 ntfs)
1050 # Major partition for storing user files
1051 # NOTE: Microsoft Windows has a bug that only recognize the first partition for removable storage devices, that's why this partition should always be the first one
1052 parted --script \
1053 "${target_device}" \
1054 mkpart \
1055 primary \
1056 "${parted_mkpart_fs_type}" \
1057 4MiB \
1058 -- -1025s # Leave 512KiB==1024sector in traditional 512bytes/sector disk, disks with sector with more than 512bytes only result in partition size greater than 512KiB and is intentionally let-it-be.
1059 # FIXME: Leave exact 512KiB in all circumstances is better, but the algorithm to do so is quite brainkilling.
1060 ;;
1061 *)
1062 printf_with_color \
1063 red \
1064 '%s\n' \
1065 "${FUNCNAME[0]}: FATAL: Illegal parted_mkpart_fs_type, please report bug."
1066 ;;
1067 esac
1068 unset parted_mkpart_fs_type
1069
1070 workaround_make_system_realize_partition_table_changed \
1071 "${target_device}"
1072
1073 # Format target partition's filesystem
1074 case "${filesystem_type}" in
1075 FS_FAT)
1076 "${command_mkdosfs}" \
1077 -F 32 \
1078 -n "${filesystem_label}" \
1079 "${target_partition}"
1080 ;;
1081 FS_NTFS)
1082 "${command_mkntfs}" \
1083 --quick \
1084 --label "${filesystem_label}" \
1085 "${target_partition}"
1086 ;;
1087 *)
1088 echo_with_color red "${FUNCNAME[0]}: FATAL: Shouldn't be here"
1089 exit 1
1090 ;;
1091 esac
1092}; declare -fr create_target_partition
1093
1094## Create UEFI:NTFS partition to support booting UEFI bootloader in NTFS filesystem where some UEFI firmwares are not able to do so
1095## https://github.com/pbatard/uefi-ntfs
1096## This routine assumes that there's only one partition on the disk, and the trailing 512KiB space is not partitioned
1097## This routine should be run after create_target_partition and only on target partition's filesystem is NTFS
1098## target_device: The target device's entire deice file
1099create_uefi_ntfs_support_partition(){
1100 util_check_function_parameters_quantity 1 $#
1101
1102 local -r target_device="$1"
1103
1104 # FIXME: The partition type should be `fat12` but `fat12` isn't recognized by Parted...
1105 # NOTE: The --align is set to none because this partition is indeed misaligned, but ignored due to it's small size
1106 parted \
1107 --align none \
1108 --script \
1109 "${target_device}" \
1110 mkpart \
1111 primary \
1112 fat16 \
1113 -- \
1114 -1024s \
1115 -1s
1116
1117 return "$?"
1118}; declare -fr create_uefi_ntfs_support_partition
1119
1120## Install UEFI:NTFS partition by writing the partition image into the created partition
1121## FIXME: Currently this requires internet access to download the image from GitHub directly, it should be replaced by including the image in our datadir
1122## uefi_ntfs_partition: The previously allocated partition for installing UEFI:NTFS, requires at least 512KiB
1123## download_directory: The temporary directory for downloading UEFI:NTFS image from GitHub
1124## target_device: For workaround_make_system_realize_partition_table_changed
1125install_uefi_ntfs_support_partition(){
1126 util_check_function_parameters_quantity 3 $#
1127
1128 local -r uefi_ntfs_partition="$1"; shift
1129 local -r download_directory="$1"; shift
1130 local -r target_device="$1"
1131
1132 if ! wget \
1133 --directory-prefix="${download_directory}" \
1134 https://github.com/pbatard/rufus/raw/master/res/uefi/uefi-ntfs.img; then
1135 printf_with_color yellow \
1136 '%s: %s\n' \
1137 "${FUNCNAME[0]}" \
1138 "Warning: Unable to download UEFI:NTFS partition image from GitHub, installation skipped. Target device might not be bootable if the UEFI firmware doesn't support NTFS filesystem."
1139 return 0
1140 fi
1141
1142 # Write partition image to partition
1143 dd \
1144 if="${download_directory}/uefi-ntfs.img" \
1145 of="${uefi_ntfs_partition}"
1146
1147}; declare -fr install_uefi_ntfs_support_partition
1148
1149## Some buggy BIOSes won't put detected device with valid MBR but no partitions with boot flag toggled into the boot menu, workaround this by setting the first partition's boot flag(which partition doesn't matter as GNU GRUB doesn't depend on it anyway
1150workaround_buggy_motherboards_that_ignore_disks_without_boot_flag_toggled(){
1151 util_check_function_parameters_quantity 1 $#
1152
1153 local -r target_device="$1"
1154
1155 printf_with_color \
1156 yellow \
1157 '%s\n' \
1158 'Applying workaround for buggy motherboards that will ignore disks with no partitions with the boot flag toggled'
1159 parted --script \
1160 "${target_device}" \
1161 set 1 boot on
1162
1163 return $?
1164}; declare -fr workaround_buggy_motherboards_that_ignore_disks_without_boot_flag_toggled
1165
1166## Check every file in source directory for files bigger than max fat32 file size (~4GB)
1167check_fat32_filesize_limitation(){
1168 util_check_function_parameters_quantity 1 $#
1169 local -r source_fs_mountpoint="${1}"
1170
1171 while IFS= read -r -d '' file; do
1172 if (( "$(stat -c '%s' "${file}")" > 4294967295 )); then # Max fat32 file size is 2^32 - 1 bytes
1173 printf_with_color \
1174 red \
1175 'Error: File "%s" in source image has exceed the FAT32 Filesystem 4GiB Single File Size Limitation and cannot be installed. You must specify a different --target-filesystem.\n' \
1176 "${file}"
1177 printf_with_color \
1178 red \
1179 'Refer: https://github.com/slacka/WoeUSB/wiki/Limitations#fat32-filesystem-4gib-single-file-size-limitation for more info.\n'
1180 return 1
1181 fi
1182 done < <(find "${source_fs_mountpoint}" -type f -print0)
1183 return 0
1184}; declare -fr check_fat32_filesize_limitation
1185
1186## Check target partition for potential problems before mounting them for --partition creation mode as we don't know about the existing partition
1187## target_partition: The target partition to check
1188## install_mode: The usb storage creation method to be used
1189## target_device: The parent device of the target partition, this is passed in to check UEFI:NTFS filesystem's existence on check_uefi_ntfs_support_partition
1190check_target_partition(){
1191 util_check_function_parameters_quantity 3 $#
1192 local target_partition="${1}"; shift
1193 local install_mode="${1}"; shift
1194 local target_device="${1}"
1195
1196 local target_filesystem
1197 target_filesystem="$(
1198 lsblk \
1199 --output FSTYPE \
1200 --noheadings \
1201 "${target_partition}"
1202 )"
1203
1204 case "${target_filesystem}" in
1205 vfat)
1206 : # supported
1207 ;;
1208 ntfs)
1209 check_uefi_ntfs_support_partition \
1210 "${target_device}"
1211 ;;
1212 *)
1213 printf_with_color red \
1214 '%s\n' \
1215 "${FUNCNAME[0]}: Error: Target filesystem not supported, currently supported filesystem: FAT, NTFS."
1216 return 1
1217 ;;
1218 esac
1219 return 0
1220}; declare -fr check_target_partition
1221
1222## Check if the UEFI:NTFS support partition exists
1223## Currently it depends on the fact that this partition has a label of "UEFI_NTFS"
1224## target_device: The UEFI:NTFS partition residing entier device file
1225check_uefi_ntfs_support_partition(){
1226 util_check_function_parameters_quantity 1 $#
1227
1228 local -r target_device="$1"
1229
1230 if \
1231 ! \
1232 lsblk \
1233 --output LABEL \
1234 --noheadings \
1235 "${target_device}" \
1236 | grep \
1237 --fixed-strings \
1238 --silent \
1239 'UEFI_NTFS'; then
1240 printf_with_color yellow \
1241 '%s: %s\n' \
1242 "${FUNCNAME[0]}" \
1243 "Warning: Your device doesn't seems to have an UEFI:NTFS partition, UEFI booting will fail if the motherboard firmware itself doesn't support NTFS filesystem!"
1244 printf -- \
1245 '%s: %s\n' \
1246 "${FUNCNAME[0]}" \
1247 'Info: You may recreate disk with an UEFI:NTFS partition by using the --device creation method'
1248 fi
1249}; declare -fr check_uefi_ntfs_support_partition
1250
1251mount_source_filesystem(){
1252 util_check_function_parameters_quantity 2 $#
1253 local source_media="$1"; shift
1254 local source_fs_mountpoint="$1"
1255
1256 echo_with_color green 'Mounting source filesystem...'
1257
1258 mkdir \
1259 --parents \
1260 "${source_fs_mountpoint}" \
1261 || (
1262 echo_with_color red "${FUNCNAME[0]}: Error: Unable to create \"${source_fs_mountpoint}\" mountpoint directory"
1263 return 1
1264 )
1265 if [ -f "${source_media}" ]; then # ${source_media} is an ISO image
1266 mount \
1267 --options loop,ro \
1268 --types udf,iso9660 \
1269 "${source_media}" \
1270 "${source_fs_mountpoint}" \
1271 || (
1272 echo_with_color red "${FUNCNAME[0]}: Error: Unable to mount source media"
1273 return 1
1274 )
1275 else # ${source_media} is a real optical disk drive (block device)
1276 mount \
1277 --options ro \
1278 "${source_media}" \
1279 "${source_fs_mountpoint}" \
1280 || (
1281 echo_with_color red "${FUNCNAME[0]}: Error: Unable to mount source media"
1282 return 1
1283 )
1284 fi
1285}; declare -fr mount_source_filesystem
1286
1287# Mount target filesystem to existing path as mountpoint
1288# target_partition: The partition device file target filesystem resides, for example /dev/sdX1
1289# target_fs_mountpoint: The existing directory used as the target filesystem's mountpoint, for example /mnt/target_filesystem
1290# target_fs_type: The filesystem of the target filesystem currently supports: FAT, NTFS
1291mount_target_filesystem(){
1292 util_check_function_parameters_quantity 3 $#
1293 local target_partition="$1"; shift
1294 local target_fs_mountpoint="$1"; shift
1295
1296 # ENUM_SUPPORTED_FILESYSTEMS
1297 local target_fs_type="$1"
1298
1299 local mount_options='defaults'
1300
1301 echo_with_color green 'Mounting target filesystem...'
1302
1303 mkdir \
1304 --parents \
1305 "${target_fs_mountpoint}" \
1306 || (
1307 echo_with_color red "${FUNCNAME[0]}: Error: Unable to create \"${target_fs_mountpoint}\" mountpoint directory"
1308 return 1
1309 )
1310
1311 # Determine proper mount options according to filesystem type
1312 case "${target_fs_type}" in
1313 FS_FAT)
1314 mount_options='utf8=1'
1315 ;;
1316 FS_NTFS)
1317 :
1318 ;;
1319 *)
1320 printf_with_color \
1321 red \
1322 'Fatal: %s: Unsupported target_fs_type, please report bug.\n' \
1323 "${FUNCNAME[0]}"
1324 exit 1
1325 ;;
1326 esac
1327
1328 mount \
1329 --options "${mount_options}" \
1330 "${target_partition}" \
1331 "${target_fs_mountpoint}" \
1332 || (
1333 echo_with_color red "${FUNCNAME[0]}: Error: Unable to mount target partition"
1334 return 1
1335 )
1336}; declare -fr mount_target_filesystem
1337
1338check_target_filesystem_free_space(){
1339 util_check_function_parameters_quantity 2 $#
1340 local -r target_fs_mountpoint="${1}"; shift
1341 local -r source_fs_mountpoint="${1}"
1342
1343 free_space=$(
1344 df \
1345 --block-size=1 \
1346 "${target_fs_mountpoint}" \
1347 | grep \
1348 --fixed-strings \
1349 "${target_fs_mountpoint}" \
1350 | awk '{print $4}'
1351 )
1352 free_space_human_readable=$(
1353 df \
1354 --human-readable \
1355 "${target_fs_mountpoint}" \
1356 | grep \
1357 --fixed-strings \
1358 "${target_fs_mountpoint}" \
1359 | awk '{print $4}'
1360 )
1361 needed_space=$(
1362 du \
1363 --summarize \
1364 --bytes \
1365 "${source_fs_mountpoint}" \
1366 | awk '{print $1}'
1367 )
1368 needed_space_human_readable=$(
1369 du \
1370 --summarize \
1371 --human-readable \
1372 "${source_fs_mountpoint}" \
1373 | awk '{print $1}'
1374 )
1375 additional_space_required_for_grub_installation="$((1000 * 1000 * 10))" # 10MiB
1376 ((needed_space = needed_space + additional_space_required_for_grub_installation))
1377
1378 if [ "${needed_space}" -gt "${free_space}" ]; then
1379 echo 'Error: Not enough free space on target partition!' >&2
1380 echo "Error: We required ${needed_space_human_readable}(${needed_space} bytes) but '${target_partition}' only has ${free_space_human_readable}(${free_space} bytes)." >&2
1381 return 1
1382 fi
1383}; declare -fr check_target_filesystem_free_space
1384
1385## Copying all files from one filesystem to another, with progress reporting
1386copy_filesystem_files(){
1387 util_check_function_parameters_quantity 3 "${#}"
1388 local source_fs_mountpoint="${1}"; shift
1389 local target_fs_mountpoint="${1}"; shift
1390 local -r only_for_gui="${1}"
1391
1392 local -i total_size
1393 total_size=$(
1394 du \
1395 --summarize \
1396 --bytes \
1397 "${source_fs_mountpoint}" \
1398 | awk '{print $1}'
1399 )
1400
1401 # FIXME: Why do we `trigger_wxGenericProgressDialog_pulse off` and on here?
1402 trigger_wxGenericProgressDialog_pulse \
1403 off \
1404 "${only_for_gui}"
1405
1406 echo_with_color green 'Copying files from source media...'
1407
1408 pushd "${source_fs_mountpoint}" >/dev/null
1409
1410 local -i copied_size=0 percentage; while IFS='' read -r -d '' source_file; do
1411 dest_file="${target_fs_mountpoint}/${source_file}"
1412
1413 source_file_size=$(
1414 stat \
1415 --format=%s \
1416 "${source_file}"
1417 )
1418
1419 if [ -d "${source_file}" ]; then
1420 mkdir --parents "${dest_file}"
1421 elif [ -f "${source_file}" ]; then
1422 if [ "${verbose}" = true ]; then
1423 echo -e "\\nINFO: Copying \"${source_file}\"..."
1424 fi
1425 if [ "${source_file_size}" -lt "${DD_BLOCK_SIZE}" ]; then
1426 cp "${source_file}" "${dest_file}"
1427 else
1428 copy_large_file \
1429 "${source_file}" \
1430 "${dest_file}" \
1431 "${copied_size}" \
1432 "${total_size}"
1433 fi
1434 else
1435 echo_with_color red "${FUNCNAME[0]}: Error: Unknown type of '${source_file}'!" >&2
1436 exit 1
1437 fi
1438
1439 # Calculate and report progress
1440 # BASHDOC: Basic Shell Features » Shell Expansions » Arithmetic Expansion
1441 # BASHDOC: Bash Features » Shell Arithmetic
1442 ((copied_size = copied_size + source_file_size)) || true
1443 ((percentage = (copied_size * 100) / total_size)) || true
1444 echo -en "${percentage}%\\r"
1445 done < <( \
1446 find \
1447 . \
1448 -not -path "." \
1449 -print0 \
1450 ); unset source_file dest_file source_file_size copied_size percentage
1451
1452 popd >/dev/null
1453
1454 trigger_wxGenericProgressDialog_pulse \
1455 on \
1456 "${only_for_gui}"
1457
1458 return 0
1459}; declare -fr copy_filesystem_files
1460
1461## Companion function of copy_filesystem_files for copying large files
1462## Copy source_file to dest_file, overwrite file if dest_file exists
1463## Also report copy_filesystem_files progress during operation
1464copy_large_file(){
1465 util_check_function_parameters_quantity 4 "${#}"
1466 local -r source_file="${1}"; shift
1467 local -r dest_file="${1}"; shift
1468 local -ir caller_copied_size="${1}"; shift
1469 local -ir caller_total_size="${1}"
1470
1471 local -i source_file_size
1472 source_file_size=$(
1473 stat \
1474 --format=%s \
1475 "${source_file}"
1476 )
1477
1478 # block count of the source file
1479 local -i block_number
1480 ((block_number = source_file_size / DD_BLOCK_SIZE + 1))
1481 unset source_file_size
1482
1483 if [ -f "${dest_file}" ]; then
1484 rm "${dest_file}"
1485 fi
1486
1487 # Copy file block by block
1488 local -i i=0 copied_size_total percentage; while [ "${i}" -lt "${block_number}" ]; do
1489 dd \
1490 if="${source_file}" \
1491 bs="${DD_BLOCK_SIZE}" \
1492 skip="${i}" \
1493 seek="${i}" \
1494 of="${dest_file}" \
1495 count=1 \
1496 2> /dev/null
1497 ((i = i + 1))
1498
1499 # Calculate and report progress
1500 # BASHDOC: Basic Shell Features » Shell Expansions » Arithmetic Expansion
1501 # BASHDOC: Bash Features » Shell Arithmetic
1502 ((copied_size_total = caller_copied_size + DD_BLOCK_SIZE * i)) || true
1503 ((percentage = (copied_size_total * 100) / caller_total_size)) || true
1504 echo -en "${percentage}%\\r"
1505 done; unset i copied_size_total percentage
1506
1507 return 0
1508}; declare -fr copy_large_file
1509
1510## As Windows 7's installation media doesn't place the required EFI
1511## bootloaders in the right location, we extract them from the
1512## system image manually
1513## TODO: Functionize Windows 7 checking
1514workaround_support_windows_7_uefi_boot(){
1515 util_check_function_parameters_quantity 2 "${#}"
1516 local source_fs_mountpoint="${1}"; shift
1517 local target_fs_mountpoint="${1}"
1518
1519 # Apply workaround only if the source is based on Windows 7, and EFI version of bootmgr is in place
1520 if ! grep \
1521 --extended-regexp \
1522 --quiet \
1523 '^MinServer=7[0-9]{3}\.[0-9]' \
1524 "${source_fs_mountpoint}/sources/cversion.ini" \
1525 || ! [ -f "${source_fs_mountpoint}/bootmgr.efi" ]; then
1526 return 0
1527 fi
1528
1529 echo_with_color yellow 'Source media seems to be Windows 7-based with EFI support, applying workaround to make it support UEFI booting'
1530 if ! command -v '7z' >/dev/null 2>&1; then
1531 echo_with_color yellow "Warning: '7z' utility not found, workaround is not applied." >&2
1532 return 0
1533 fi
1534
1535 # Detect **case-insensitive** existing efi directories according to UEFI spec
1536 local \
1537 test_efi_directory \
1538 efi_directory
1539 test_efi_directory="$(
1540 find \
1541 "${target_fs_mountpoint}" \
1542 -ipath "${target_fs_mountpoint}/efi"
1543 )"
1544 if [ -z "${test_efi_directory}" ]; then
1545 efi_directory="${target_fs_mountpoint}/efi"
1546 if [ "${verbose}" = true ]; then
1547 printf -- \
1548 "%s: DEBUG: Can't find efi directory, use %s.\\n" \
1549 "${FUNCNAME[0]}" \
1550 "${efi_directory}"
1551 fi
1552 else # efi directory(case don't care) exists
1553 efi_directory="${test_efi_directory}"
1554 if [ "${verbose}" = true ]; then
1555 printf -- \
1556 '%s: DEBUG: %s detected.\n' \
1557 "${FUNCNAME[0]}" \
1558 "${efi_directory}"
1559 fi
1560 fi
1561 unset test_efi_directory
1562
1563 local \
1564 test_efi_boot_directory \
1565 efi_boot_directory
1566 test_efi_boot_directory="$(
1567 find \
1568 "${target_fs_mountpoint}" \
1569 -ipath "${efi_directory}/boot"
1570 )"
1571 if [ -z "${test_efi_boot_directory}" ]; then
1572 efi_boot_directory="${efi_directory}/boot"
1573 if [ "${verbose}" = true ]; then
1574 printf -- \
1575 "%s: DEBUG: Can't find efi/boot directory, use %s.\\n" \
1576 "${FUNCNAME[0]}" \
1577 "${efi_boot_directory}"
1578 fi
1579 else # boot directory(case don't care) exists
1580 efi_boot_directory="${test_efi_boot_directory}"
1581 if [ "${verbose}" = true ]; then
1582 printf -- \
1583 '%s: DEBUG: %s detected.\n' \
1584 "${FUNCNAME[0]}" \
1585 "${efi_boot_directory}"
1586 fi
1587 fi
1588 unset \
1589 efi_directory \
1590 test_efi_boot_directory
1591
1592 # If there's already an EFI bootloader existed, skip the workaround
1593 local test_efi_bootloader
1594 test_efi_bootloader="$(
1595 find \
1596 "${target_fs_mountpoint}" \
1597 -ipath "${target_fs_mountpoint}/efi/boot/boot*.efi"
1598 )"
1599 if [ -n "${test_efi_bootloader}" ]; then
1600 printf -- \
1601 'INFO: Detected existing EFI bootloader, workaround skipped.\n'
1602 return 0
1603 fi
1604
1605 mkdir \
1606 --parents \
1607 "${efi_boot_directory}"
1608
1609 # Skip workaround if EFI bootloader already exist in efi_boot_directory
1610 7z \
1611 e \
1612 -so \
1613 "${source_fs_mountpoint}/sources/install.wim" \
1614 "Windows/Boot/EFI/bootmgfw.efi" \
1615 > "${efi_boot_directory}/bootx64.efi"
1616}; declare -fr workaround_support_windows_7_uefi_boot
1617
1618## Currently WoeUSB indirectly causes severely unresponsive system on 64-bit architecture with large primary memory during file copy process due to a flaw of the writeback buffer size handling in Linux kernel, workaround it before it is fixed
1619## Refer:
1620## - System lagging while copying data · Issue #113 · slacka/WoeUSB <https://github.com/slacka/WoeUSB/issues/113>
1621## - The pernicious USB-stick stall problem [LWN.net] <https://lwn.net/Articles/572911/>
1622workaround_linux_make_writeback_buffering_not_suck(){
1623 util_check_function_parameters_quantity 1 "${#}"
1624 local -r mode="${1}"
1625
1626 local -ir VM_DIRTY_BACKGROUND_BYTES=$((16*1024*1024)) # 16MiB
1627 local -ir VM_DIRTY_BYTES=$((48*1024*1024)) # 48MiB
1628
1629 case "${mode}" in
1630 apply)
1631 echo_with_color \
1632 yellow \
1633 'Applying workaround to prevent 64-bit systems with big primary memory from being unresponsive during copying files.'
1634 echo "${VM_DIRTY_BACKGROUND_BYTES}" > /proc/sys/vm/dirty_background_bytes
1635 echo "${VM_DIRTY_BYTES}" > /proc/sys/vm/dirty_bytes
1636 ;;
1637 reset)
1638 echo_with_color \
1639 yellow \
1640 'Resetting workaround to prevent 64-bit systems with big primary memory from being unresponsive during copying files.'
1641 echo 0 > /proc/sys/vm/dirty_background_bytes
1642 echo 0 > /proc/sys/vm/dirty_bytes
1643 ;;
1644 *)
1645 printf_with_color \
1646 red \
1647 'Fatal: %s: Unexpected *mode* encountered, please report bug.\n' \
1648 "${FUNCNAME[0]}"
1649 ;;
1650 esac
1651}; declare -fr workaround_linux_make_writeback_buffering_not_suck
1652
1653install_legacy_pc_bootloader_grub(){
1654 util_check_function_parameters_quantity 3 "${#}"
1655 local -r target_fs_mountpoint="${1}"; shift 1
1656 local -r target_device="${1}"; shift 1
1657 local -r command_grubinstall="${1}"
1658
1659 echo_with_color green 'Installing GRUB bootloader for legacy PC booting support...'
1660 "${command_grubinstall}" \
1661 --target=i386-pc \
1662 --boot-directory="${target_fs_mountpoint}" \
1663 --force "${target_device}"
1664
1665
1666}; declare -fr install_legacy_pc_bootloader_grub
1667
1668## Install a GRUB config file to chainload Microsoft Windows's bootloader in Legacy PC bootmode
1669## target_fs_mountpoint: Target filesystem's mountpoint(where GRUB is installed)
1670## name_grub_prefix: May be different between distributions, so need to be specified (grub/grub2)
1671install_legacy_pc_bootloader_grub_config(){
1672 util_check_function_parameters_quantity 2 $#
1673
1674 local -r target_fs_mountpoint="$1"; shift
1675 local -r name_grub_prefix="$1"
1676
1677 echo_with_color green 'Installing custom GRUB config for legacy PC booting...'
1678 local -r grub_cfg="${target_fs_mountpoint}/${name_grub_prefix}/grub.cfg"
1679 local target_fs_uuid; target_fs_uuid=$(
1680 lsblk \
1681 --noheadings \
1682 --raw \
1683 --output UUID,MOUNTPOINT \
1684 | grep \
1685 --extended-regexp \
1686 "${target_fs_mountpoint}"$ \
1687 | cut \
1688 --fields=1 \
1689 --delimiter=' '
1690 )
1691
1692 mkdir --parents "$(dirname "${grub_cfg}")"
1693 {
1694 cat <<- END_OF_FILE
1695 ntldr /bootmgr
1696 boot
1697 END_OF_FILE
1698 } > "${grub_cfg}"; unset target_fs_uuid
1699}; declare -fr install_legacy_pc_bootloader_grub_config
1700
1701## Unmount mounted filesystem and clean-up mountpoint before exiting program
1702## return_value: 1 - Failed to unmount / 2 - Failed to remove mountpoint
1703cleanup_mountpoint(){
1704 util_check_function_parameters_quantity 2 "${#}"
1705 local -r fs_mountpoint="${1}"; shift
1706 local -r only_for_gui="${1}"; shift
1707
1708 # In copy_filesystem_files, we use `pushd` to changed the working directory into source_fs_mountpoint in order to get proper source and target file path, proactively `popd` to ensure we are not in source_fs_mountpoint and preventing source filesystem to unmount
1709 popd &>/dev/null \
1710 || true
1711
1712 if [ -e "${fs_mountpoint}" ]; then
1713 printf_with_color \
1714 green \
1715 'Unmounting and removing "%s"...\n' \
1716 "${fs_mountpoint}"
1717 if ! umount "${fs_mountpoint}"; then
1718 printf_with_color \
1719 yellow \
1720 '%s: Warning: Unable to unmount "%s".\n' \
1721 "${FUNCNAME[0]}" \
1722 "${fs_mountpoint}"
1723 return 1
1724 fi
1725
1726 if ! rmdir "${fs_mountpoint}"; then
1727 printf_with_color \
1728 yellow \
1729 '%s: Warning: Unable to remove "%s".\n' \
1730 "${FUNCNAME[0]}" \
1731 "${fs_mountpoint}"
1732 return 2
1733 fi
1734 fi
1735
1736 return 0
1737}; declare -fr cleanup_mountpoint
1738
1739## This function continuously moves the GUI creation dialog's progressing bar bit by bit, by running `echo pulse` every 0.05 seconds until the next call chooses to turn off
1740## Refer src/MainPanel.cpp MainPanel::OnInstall(wxCommandEvent& event) method for more info
1741## http://docs.wxwidgets.org/trunk/classwx_generic_progress_dialog.html#ab5ecf227eebaa1aadb5f5c553e4a4ee5
1742## switch: To enable or not enable this function(on/off)
1743trigger_wxGenericProgressDialog_pulse(){
1744 util_check_function_parameters_quantity 2 $#
1745
1746 local switch="$1"; shift
1747 local -r only_for_gui="$1"
1748
1749 if [ "${only_for_gui}" = false ]; then
1750 return 0
1751 fi
1752
1753 case "${switch}" in
1754 on)
1755 # Don't do anything if it is already turned on
1756 if [ "${pulse_current_pid}" -ne 0 ]; then
1757 return 0
1758 fi
1759
1760 while true; do
1761 sleep 0.05
1762 echo pulse
1763 done &
1764 pulse_current_pid="$!"
1765 disown
1766 ;;
1767 off)
1768 # Don't do anything if it is already turned off
1769 if [ "${pulse_current_pid}" -eq 0 ]; then
1770 return 0
1771 fi
1772
1773 kill "${pulse_current_pid}"
1774 wait "${pulse_current_pid}" 2>/dev/null || true
1775 pulse_current_pid=0
1776 ;;
1777 *)
1778 echo "${FUNCNAME[0]}: FATAL: Illegal parameter \"$1\"" >&2
1779 exit 1
1780 esac
1781 return 0
1782}; declare -fr trigger_wxGenericProgressDialog_pulse
1783
1784## Traps: Functions that are triggered when certain condition occurred
1785## Shell Builtin Commands » Bourne Shell Builtins » trap
1786trap_errexit(){
1787 echo_with_color red "The command \"${BASH_COMMAND}\" failed with exit status \"${?}\", program is prematurely aborted" 1>&2
1788
1789 return 0
1790}; declare -fr trap_errexit
1791
1792trap_exit(){
1793 # Mountpoints aren't successfully removed
1794 local flag_unclean=false
1795
1796 # Target filesystem failed to unmount
1797 local flag_unsafe=false
1798
1799 # FIXME: Why `trigger_wxGenericProgressDialog_pulse off` here?
1800 trigger_wxGenericProgressDialog_pulse \
1801 off \
1802 "${global_only_for_gui}"
1803
1804 case "${current_state}" in
1805 copying-filesystem|finished)
1806 workaround_linux_make_writeback_buffering_not_suck \
1807 reset
1808 ;;
1809 esac
1810
1811 if util_is_parameter_set_and_not_empty \
1812 source_fs_mountpoint; then
1813 if ! cleanup_mountpoint \
1814 "${source_fs_mountpoint}" \
1815 "${global_only_for_gui}"; then
1816 flag_unclean=true
1817 fi
1818 fi
1819
1820 if util_is_parameter_set_and_not_empty \
1821 target_fs_mountpoint; then
1822 if ! cleanup_mountpoint \
1823 "${target_fs_mountpoint}" \
1824 "${global_only_for_gui}"; then
1825 local -i return_value="${?}"
1826
1827 flag_unclean=true
1828
1829 if [ "${return_value}" = 1 ]; then
1830 flag_unsafe=true
1831 fi
1832 fi
1833 fi
1834
1835 if [ "${flag_unclean}" = true ]; then
1836 echo_with_color \
1837 yellow \
1838 'Some mountpoints are not unmount/cleaned successfully and must be done manually'
1839 fi
1840
1841 if [ "${flag_unsafe}" = true ]; then
1842 echo_with_color \
1843 yellow \
1844 'We unable to unmount target filesystem for you, please make sure target filesystem is unmounted before detaching to prevent data corruption'
1845 fi
1846
1847 unset \
1848 flag_unclean \
1849 flag_unsafe
1850
1851 if \
1852 util_is_parameter_set_and_not_empty\
1853 target_device; then
1854 if is_target_busy "${target_device}"; then
1855 echo_with_color yellow 'Target device is busy, please make sure you unmount all filesystems on target device or shutdown the computer before detaching it.'
1856 else
1857 echo_with_color green 'You may now safely detach the target device'
1858 fi
1859 fi
1860
1861 rm \
1862 --recursive \
1863 "${temp_directory}"
1864
1865 if [ "${current_state}" = finished ]; then
1866 echo_with_color green 'Done :)'
1867 echo_with_color green 'The target device should be bootable now'
1868 fi
1869
1870 return 0
1871}; declare -fr trap_exit
1872
1873trap_interrupt(){
1874 printf '\n' # Separate message with previous output
1875 echo_with_color yellow 'Recieved SIGINT, program is interrupted.' 1>&2
1876 return 1
1877}; declare -fr trap_interrupt
1878
1879trap_return(){
1880 util_check_function_parameters_quantity 1 "${#}"
1881 local returning_function="${1}"
1882
1883 for ignored_function in \
1884 util_check_function_parameters_quantity \
1885 util_is_parameter_set_and_not_empty \
1886 switch_terminal_text_color \
1887 echo_with_color; do
1888 if [ "${returning_function}" == "${ignored_function}" ]; then
1889 return 0
1890 fi
1891 done
1892
1893 echo_with_color green "${FUNCNAME[0]}: INFO: returning from ${returning_function}" 1>&2
1894}; declare -fr trap_return
1895
1896## FIXME: Debug trap never work as expected, it always
1897## prints two identical lines somehow.
1898trap_debug(){
1899 util_check_function_parameters_quantity 1 $#
1900 local -r command_to_be_executed="${1}"
1901
1902 local -r command_base="${command_to_be_executed%% *}"
1903
1904 for ignored_command in util_check_function_parameters_quantity util_is_parameter_set_and_not_empty echo_with_color switch_terminal_text_color tput printf_with_color; do
1905 if [ "${command_base}" = "${ignored_command}" ]; then
1906 return 0
1907 fi
1908 done
1909
1910 case "$(type -t "${command_base}")" in
1911 file)
1912 echo_with_color green "${FUNCNAME[0]}: INFO: Executing ${command_to_be_executed}"
1913 ;;
1914 function)
1915 echo_with_color green "${FUNCNAME[0]}: INFO: Calling ${command_base}"
1916 ;;
1917 *)
1918 :
1919 ;;
1920 esac
1921
1922 return 0
1923}; declare -fr trap_debug
1924
1925## An utility function for inhibiting command call output and
1926## only show them to user when error occurred
1927util_call_external_command(){
1928 local -ar command=("${@}")
1929
1930 local command_output
1931 local -i command_exit_status
1932 if command_output="$( "${command[@]}" 2>&1 )"; then
1933 command_exit_status=0
1934 else
1935 command_exit_status="$?"
1936 fi
1937
1938 if [ "${command_exit_status}" -ne 0 ]; then
1939 echo_with_color red "Error occurred while running command \"${command[*]}\" (exit status: ${command_exit_status})!" >&2
1940
1941 local -r read_prompt="Read command output (Y/n)?"
1942 printf '%s' "${read_prompt}"
1943
1944 local answer=y
1945
1946 while true; do
1947 read -r answer
1948
1949 if [ "${answer}" == y ] || [ "${answer}" == Y ]; then
1950 echo "${command_output}"
1951 break
1952 elif [ "${answer}" == n ] || [ "${answer}" == N ]; then
1953 break
1954 else
1955 printf '%s' "${read_prompt}"
1956 fi
1957 done
1958
1959 echo_with_color red 'Press ENTER to continue' >&2
1960 read -r # catch enter key
1961 fi
1962
1963 return "${command_exit_status}"
1964}; declare -fr util_call_external_command
1965
1966## Configure the terminal to print future messages with certain color
1967## Parameters:
1968## - color: color of the next message, or `none` to reset to default color
1969util_switch_terminal_text_color(){
1970 util_check_function_parameters_quantity 1 $#
1971 local -r color="$1"
1972
1973 case "${color}" in
1974 black)
1975 echo -en '\033[0;30m'
1976 ;;
1977 red)
1978 echo -en '\033[0;31m'
1979 ;;
1980 green)
1981 echo -en '\033[0;32m'
1982 ;;
1983 yellow)
1984 echo -en '\033[0;33m'
1985 ;;
1986 blue)
1987 echo -en '\033[0;34m'
1988 ;;
1989 white)
1990 echo -en '\033[0;37m'
1991 ;;
1992 none)
1993 tput sgr0
1994 ;;
1995 *)
1996 printf -- \
1997 'Fatal: %s: Illegal parameter, please report bug.' \
1998 "${FUNCNAME[0]}" \
1999 1>&2
2000 exit 1
2001 ;;
2002 esac
2003}; declare -fr util_switch_terminal_text_color
2004
2005## Print message with color
2006util_echo_with_color(){
2007 util_check_function_parameters_quantity 2 "${#}"
2008 local -r message_color="${1}"; shift
2009 local -r message_body="${1}"
2010
2011 if [ "${no_color}" = true ]; then
2012 echo -e "${message_body}"
2013 else
2014 switch_terminal_text_color "${message_color}"
2015 echo -e "${message_body}"
2016 switch_terminal_text_color none
2017 fi
2018}; declare -fr util_echo_with_color
2019
2020## Print formatted message with color
2021util_printf_with_color(){
2022 if [ ${#} -lt 2 ]; then
2023 if [ "${no_color}" == false ]; then
2024 switch_terminal_text_color red
2025 fi
2026 printf -- \
2027 'Fatal: %s: Parameter quantity illegal, please report bug.\n' \
2028 "${FUNCNAME[0]}"
2029 if [ "${no_color}" == false ]; then
2030 switch_terminal_text_color none
2031 fi
2032 exit 1
2033 fi
2034 local -r color="${1}"; shift
2035 local -ar printf_parameters=("${@}")
2036
2037 if [ "${no_color}" == true ]; then
2038 # False positive: not format string(ShellCheck #1028)
2039 # shellcheck disable=SC2059
2040 printf -- \
2041 "${printf_parameters[@]}"
2042 else # no_color = false
2043 switch_terminal_text_color "${color}"
2044 # False positive: not format string(ShellCheck #1028)
2045 # shellcheck disable=SC2059
2046 printf -- \
2047 "${printf_parameters[@]}"
2048 switch_terminal_text_color none
2049 fi
2050}; declare -fr util_printf_with_color
2051
2052util_shift_array(){
2053 util_check_function_parameters_quantity 1 "${#}"
2054
2055 local -n array_ref="${1}"
2056
2057 if [ "${#array_ref[@]}" -eq 0 ]; then
2058 printf '%s: FATAL: array is empty!\n' "${FUNCNAME[0]}" 1>&2
2059 exit 1
2060 fi
2061
2062 # Unset the 1st element
2063 unset 'array_ref[0]'
2064
2065 # Repack array if element still available in array
2066 if [ "${#array_ref[@]}" -ne 0 ]; then
2067 array_ref=("${array_ref[@]}")
2068 fi
2069
2070 return 0
2071}; declare -fr util_shift_array
2072
2073util_is_parameter_set_and_not_empty(){
2074 util_check_function_parameters_quantity 1 $#
2075
2076 local parameter_name="${1}"
2077
2078 if [ ! -v "${parameter_name}" ]; then
2079 return 1
2080 else
2081 declare -n parameter_ref
2082 parameter_ref="${parameter_name}"
2083
2084 if [ -z "${parameter_ref}" ]; then
2085 return 1
2086 else
2087 return 0
2088 fi
2089 fi
2090}; declare -fr util_is_parameter_set_and_not_empty
2091
2092## Utility function to check if function parameters quantity is legal
2093## NOTE: non-static function parameter quantity(e.g. either 2 or 3) is not supported
2094util_check_function_parameters_quantity(){
2095 if [ "${#}" -ne 2 ]; then
2096 printf_with_color \
2097 red \
2098 '%s: FATAL: Function requires %u parameters, but %u is given\n' \
2099 "${FUNCNAME[0]}" \
2100 2 \
2101 "${#}"
2102 exit 1
2103 fi
2104
2105 # The expected given quantity
2106 local -i expected_parameter_quantity="${1}"; shift
2107 # The actual given parameter quantity, simply pass "${#}" will do
2108 local -i given_parameter_quantity="${1}"
2109
2110 if [ "${given_parameter_quantity}" -ne "${expected_parameter_quantity}" ]; then
2111 printf_with_color \
2112 red \
2113 '%s: FATAL: Function requires %u parameters, but %u is given\n' \
2114 "${FUNCNAME[1]}" \
2115 "${expected_parameter_quantity}" \
2116 "${given_parameter_quantity}" \
2117 1>&2
2118 exit 1
2119 fi
2120 return 0
2121}; declare -fr util_check_function_parameters_quantity
2122
2123trap trap_exit EXIT
2124trap trap_errexit ERR
2125trap trap_interrupt INT
2126
2127init \
2128 "${RUNTIME_EXECUTABLE_NAME}" \
2129 global_only_for_gui