-
Notifications
You must be signed in to change notification settings - Fork 953
Expand file tree
/
Copy pathinstall.sub
More file actions
3750 lines (3193 loc) · 105 KB
/
Copy pathinstall.sub
File metadata and controls
3750 lines (3193 loc) · 105 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/bin/ksh
# $OpenBSD: install.sub,v 1.1289 2026/09/05 11:28:38 jan Exp $
#
# Copyright (c) 1997-2015 Todd Miller, Theo de Raadt, Ken Westerback
# Copyright (c) 2015, Robert Peichaer <rpe@openbsd.org>
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# Copyright (c) 1996 The NetBSD Foundation, Inc.
# All rights reserved.
#
# This code is derived from software contributed to The NetBSD Foundation
# by Jason R. Thorpe.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# OpenBSD install/upgrade script common subroutines and initialization code
# ------------------------------------------------------------------------------
# Misc functions
# ------------------------------------------------------------------------------
# Print error message to stderr and exit the script.
err_exit() {
print -u2 -- "$*"
exit 1
}
# Show usage of the installer script and exit.
usage() {
err_exit "usage: ${0##*/} [-ax] [-f filename]"
}
# Wait for the ftp(1) process started in start_cgiinfo() to end and extract
# various informations from the ftplist.cgi output.
wait_cgiinfo() {
local _l _s _key _val
if [ -f /tmp/cgipid ]; then
wait "$(</tmp/cgipid)" 2>/dev/null
rm -f /tmp/cgipid
fi
# Ensure, there is actual data to extract info from.
[[ -s $CGI_INFO ]] || return
# Extract the list of mirror servers.
sed -En 's,^https?://([[A-Za-z0-9:_][]A-Za-z0-9:._-]*),\1,p' \
$CGI_INFO >$HTTP_LIST 2>/dev/null
# Extract the previously selected mirror server (first entry in the
# ftplist.cgi output, if that has no location info).
read -r -- _s _l <$HTTP_LIST
[[ -z $_l ]] && : ${HTTP_SERVER:=${_s%%/*}}
# Extract the previously used install method, timezone information
# and a reference timestamp.
while IFS='=' read -r -- _key _val; do
case $_key=$_val in
method=+([a-z])*([0-9])) CGI_METHOD=$_val;;
TIME=+([0-9])) CGI_TIME=$_val;;
TZ=+([-_/+[:alnum:]])) CGI_TZ=$_val;;
esac
done <$CGI_INFO
}
# ------------------------------------------------------------------------------
# Utils functions
# ------------------------------------------------------------------------------
# Test the first argument against the remaining ones, return success on a match.
isin() {
local _a=$1 _b
shift
for _b; do
[[ $_a == "$_b" ]] && return 0
done
return 1
}
# Add first argument to list formed by the remaining arguments.
# Adds to the tail if the element does not already exist.
addel() {
local _a=$1
shift
isin "$_a" $* && echo "$*" || echo "${*:+$* }$_a"
}
# Remove all occurrences of first argument from list formed by the remaining
# arguments.
rmel() {
local _a=$1 _b _c
shift
for _b; do
[[ $_a != "$_b" ]] && _c="${_c:+$_c }$_b"
done
echo "$_c"
}
# Sort and print unique list of provided arguments.
bsort() {
local _a _l
set -s -- $@
for _a; do
_l=$(addel $_a $_l)
done
echo $_l
}
# If possible, print the timestamp received from the ftplist.cgi output,
# adjusted with the time elapsed since it was received.
http_time() {
local _sec=$(cat $HTTP_SEC 2>/dev/null)
[[ -n $_sec && -n $CGI_TIME ]] &&
echo $((CGI_TIME + SECONDS - _sec))
}
# Prints the supplied parameters properly escaped for future sh/ksh parsing.
# Quotes are added if needed, so you should not do that yourself.
quote() (
# Since this is a subshell we won't pollute the calling namespace.
for _a; do
alias Q=$_a; _a=$(alias Q); print -rn -- " ${_a#Q=}"
done | sed '1s/ //'
echo
)
# Show a list of ordered arguments (read line by line from stdin) in column
# output using ls.
show_cols() {
local _l _cdir=/tmp/i/cdir _clist
mkdir -p $_cdir
rm -rf -- $_cdir/*
while read _l; do
[[ -n $_l ]] || continue
mkdir -p /tmp/i/cdir/"$_l"
_clist[${#_clist[*]}]="$_l"
done
(cd $_cdir; ls -Cdf "${_clist[@]}")
rm -rf -- $_cdir
}
# Echo file $1 to stdout. Skip comment lines. Strip leading and trailing
# whitespace if IFS is set.
stripcom() {
local _file=$1 _line
[[ -f $_file ]] || return
set -o noglob
while read _line; do
[[ -n ${_line%%#*} ]] && echo $_line
done <$_file
set +o noglob
}
# Create a temporary directory based on the supplied directory name prefix.
tmpdir() {
local _i=1 _dir
until _dir="${1?}.$_i.$RANDOM" && mkdir -- "$_dir" 2>/dev/null; do
((++_i < 10000)) || return 1
done
echo "$_dir"
}
# Generate unique filename based on the supplied filename $1.
unique_filename() {
local _fn=$1 _ufn
while _ufn=${_fn}.$RANDOM && [[ -e $_ufn ]]; do :; done
print -- "$_ufn"
}
# Let rc.firsttime feed file $1 using $2 as subject to whatever mail system we
# have at hand by then.
prep_root_mail() {
local _fn=$1 _subject=$2 _ufn
[[ -s $_fn ]] || return
_ufn=$(unique_filename /mnt/var/log/${_fn##*/})
cp $_fn $_ufn
chmod 600 $_ufn
_ufn=${_ufn#/mnt}
cat <<__EOT >>/mnt/etc/rc.firsttime
( /usr/bin/mail -s '$_subject' root <$_ufn && rm $_ufn ) >/dev/null 2>&1 &
__EOT
}
# Examine the contents of the dhcpleased lease file $1 for a line containing the
# field(s) provided as parameters and return the value of the first field found.
#
# Note that value strings are VIS_SAFE'd.
lease_value() {
local _lf=$1 _o _opt _val
[[ -s $_lf ]] || return
shift
for _o; do
while read -r _opt _val; do
[[ $_opt == ${_o}: ]] && echo "$_val" && return
done < "$_lf"
done
}
# Extract the latest boot's worth of dmesg.
dmesgtail() {
dmesg | sed -n 'H;/^OpenBSD/h;${g;p;}'
}
# ------------------------------------------------------------------------------
# Device related functions
# ------------------------------------------------------------------------------
# Show device name, info, NAA and size in gigabytes for the provided disks.
# Create device nodes as needed and cleanup afterwards.
diskinfo() {
local _d _i _n _s
for _d; do
# Extract disk information enclosed in <> from dmesg.
_i=$(dmesg | sed -n '/^'$_d' at /h;${g;s/^.*<\(.*\)>.*$/\1/p;}')
_i=${_i##+([[:space:],])}
_i=${_i%%+([[:space:],])}
# Extract Network Address Authority information from dmesg.
_n=$(dmesg | sed -En '/^'$_d' at /h;${g;s/^.* ([a-z0-9]+\.[a-zA-Z0-9_]+)$/\1/p;}')
# Extract disk size from disklabel output.
make_dev $_d
_s=$(disklabel -dpg $_d 2>/dev/null | sed -n '/.*# total bytes: \(.*\)/{s//(\1)/p;}')
rm -f /dev/{r,}$_d?
echo " $_d: $_i $_n $_s"
done
}
# Create devices passed as arguments.
make_dev() {
[[ -z $(cd /dev && sh MAKEDEV "$@" 2>&1) ]]
}
# Sort and print the current boot's information using sed expression $1.
scan_dmesg() {
bsort $(sed -n "$1" $DMESGBOOT)
}
# Extract device names from hw.disknames matching sed expression $1.
scan_disknames() {
bsort $(IFS=,
for _d in $(sysctl -n hw.disknames); do
echo "${_d%%:*} "
done | sed -n "$1")
}
# Return list of disks with softraid chunks, optionally limited to the volume $1.
get_softraid_chunks() {
local _device=${1:-softraid0}
[[ -x /sbin/bioctl ]] || return
bioctl $_device 2>/dev/null | sed -n 's/.*<\(.*\).>$/\1/p'
}
# Return list of softraid volumes.
get_softraid_volumes() {
bioctl softraid0 | sed -n 's/^softraid0.*\(sd[0-9]*\).*/\1/p'
}
# Return disk devices found in hw.disknames.
get_dkdevs() {
scan_disknames "${MDDKDEVS:-/^[sw]d[0-9][0-9]* /s/ .*//p}"
}
# Return CDROM devices found in hw.disknames.
get_cddevs() {
scan_disknames "${MDCDDEVS:-/^cd[0-9][0-9]* /s/ .*//p}"
}
# Return sorted list of disks not in DISKS_DONE which contains disks already
# initialized during installation.
# Ignore softraid chunks, they either auto-assembled at boot or were created
# manually in an installer shell.
get_dkdevs_uninitialized() {
local _disks=$(get_dkdevs) _d
for _d in $DISKS_DONE $(get_softraid_chunks); do
_disks=$(rmel "$_d" $_disks)
done
bsort $_disks
}
# Return list of valid root disks
get_dkdevs_root() {
local _disks=$(get_dkdevs) _d
if [[ $MODE == upgrade ]]; then
for _d in $_disks; do
is_rootdisk "$_d" || _disks=$(rmel "$_d" $_disks)
done
fi
echo $_disks
}
# Return list of all network devices, optionally limited by parameters to
# ifconfig. Filter out dynamically created network pseudo-devices except vlan.
get_ifs() {
local _if _if_list=$(rmel vlan $(ifconfig -C))
for _if in $(ifconfig "$@" 2>/dev/null | sed '/^[a-z]/!d;s/:.*//'); do
isin "${_if%%+([0-9])}" $_if_list || echo $_if
done
}
# Map an interface to its MAC address if it is unique.
if_name_to_lladdr() {
local _lladdr
_lladdr=$(ifconfig $1 2>/dev/null |
sed -n 's/^[[:space:]]*lladdr[[:space:]]//p')
[[ -n $_lladdr && -n $(ifconfig -M "$_lladdr") ]] && echo $_lladdr
}
# Print a list of interface names and unique lladdr.
get_ifs_and_lladdrs() {
local _if
for _if in $(get_ifs); do
echo $_if
if_name_to_lladdr $_if
done
}
# Return the device name of the disk device $1, which may be a disklabel UID.
get_dkdev_name() {
local _dev=${1#/dev/} _d
_dev=${_dev%.[${PARTITIONS}]}
((${#_dev} < 16)) && _dev=${_dev%[${PARTITIONS}]}
local IFS=,
for _d in $(sysctl -n hw.disknames); do
[[ $_dev == @(${_d%:*}|${_d#*:}) ]] && echo ${_d%:*} && break
done
}
# Inspect disk $1 if it has a partition-table of type $2 and optionally
# if it has a partition of type $3.
disk_has() {
local _disk=$1 _pttype=$2 _part=$3 _cmd _p_pttype _p_part
[[ -n $_disk && -n $_pttype ]] || exit
# Commands to inspect disk. Default: "fdisk $_disk"
local _c_hfs="pdisk -l $_disk"
# Patterns for partition-table-types and partition-types.
local _p_gpt='Usable LBA:'
local _p_gpt_openbsd='^[ *]...: OpenBSD '
local _p_gpt_apfsisc='^[ *]...: APFS ISC '
local _p_gpt_biosboot='^[ *]...: BIOS Boot '
local _p_gpt_efisys='^[ *]...: EFI Sys '
local _p_hfs='^Partition map '
local _p_hfs_openbsd=' OpenBSD OpenBSD '
local _p_mbr='Signature: 0xAA55'
local _p_mbr_openbsd='^..: A6 '
local _p_mbr_dos='^..: 06 '
local _p_mbr_dos_active='^\*.: 06 '
local _p_mbr_linux='^..: 83 '
# Compose command and patterns based on the parameters.
eval "_cmd=\"\$_c_${_pttype}\""
eval "_p_pttype=\"\$_p_${_pttype}\""
eval "_p_part=\"\$_p_${_pttype}_${_part}\""
# Set the default command if none was defined before.
_cmd=${_cmd:-fdisk $_disk}
# Abort in case of undefined patterns.
[[ -z $_p_pttype ]] && exit
[[ -n $_part && -z $_p_part ]] && exit
if [[ -z $_p_part ]]; then
$_cmd 2>/dev/null | grep -Eq "$_p_pttype"
else
$_cmd 2>/dev/null | grep -Eq "$_p_pttype" &&
$_cmd 2>/dev/null | grep -Eq "$_p_part"
fi
}
# Handle disklabel auto-layout for the root disk $1 during interactive install
# and autopartitioning during unattended install by asking for and downloading
# autopartitioning template. Write the resulting fstab to $2. Abort unattended
# installation if autopartitioning fails.
disklabel_autolayout() {
local _disk=$1 _f=$2 _dl=/tmp/i/disklabel.auto _op _qst
# Skip disklabel auto-layout for any disk except the root disk.
[[ $_disk != $ROOTDISK ]] && return
while $AI; do
ask "URL to autopartitioning template for disklabel?" none
[[ $resp == none ]] && break
if ! $FTP_TLS && [[ $resp == https://* ]]; then
err_exit "https not supported on this platform."
fi
echo "Fetching $resp"
if unpriv ftp -Vo - "$resp" >$_dl && [[ -s $_dl ]]; then
disklabel -T $_dl -F $_f -w -A $_disk && return
err_exit "Autopartitioning failed."
else
err_exit "No autopartitioning template found."
fi
done
_qst="Use (A)uto layout, (E)dit auto layout, or create (C)ustom layout?"
while :; do
echo "The auto-allocated layout for $_disk is:"
disklabel -h -A $_disk | egrep "^# |^ [${PARTITIONS}]:"
ask "$_qst" a
case $resp in
[aA]*) _op=-w;;
[eE]*) _op=-E;;
[cC]*) return 0;;
*) continue;;
esac
disklabel -F $_f $_op -A $_disk
return
done
}
# Create a partition table and configure the partition layout for disk $1.
configure_disk() {
local _disk=$1 _fstab=/tmp/i/fstab.$1 _opt
make_dev $_disk || return
# Deal with disklabels, including editing the root disklabel
# and labeling additional disks. This is machine-dependent since
# some platforms may not be able to provide this functionality.
# /tmp/i/fstab.$_disk is created here with 'disklabel -F'.
rm -f /tmp/i/*.$_disk
md_prep_disklabel $_disk || return
# Make sure a '/' mount point exists on the root disk.
if ! grep -qs ' / ffs ' /tmp/i/fstab.$ROOTDISK; then
echo "'/' must be configured!"
$AI && exit 1 || return 1
fi
if [[ -f $_fstab ]]; then
# Avoid duplicate mount points on different disks.
while read _pp _mp _rest; do
# Multiple swap partitions are ok.
if [[ $_mp == none ]]; then
echo "$_pp $_mp $_rest" >>/tmp/i/fstab
continue
fi
# Non-swap mountpoints must be in only one file.
if [[ $_fstab != $(grep -l " $_mp " /tmp/i/fstab.*) ]]; then
_rest=$_disk
_disk=
break
fi
done <$_fstab
# Duplicate mountpoint.
if [[ -z $_disk ]]; then
# Allow disklabel(8) to read back mountpoint info
# if it is immediately run against the same disk.
cat /tmp/i/fstab.$_rest >/etc/fstab
rm /tmp/i/fstab.$_rest
set -- $(grep -h " $_mp " /tmp/i/fstab.*[0-9])
echo "$_pp and $1 can't both be mounted at $_mp."
$AI && exit 1 || return 1
fi
# Add ffs filesystems to list after newfs'ing them. Ignore
# other filesystems.
while read _pp _mp _fstype _rest; do
[[ $_fstype == ffs ]] || continue
# Default to FFS2 unless otherwise directed.
if [[ $_mp == / ]]; then
_opt=${MDROOTFSOPT:--O2}
else
_opt=${MDFSOPT:--O2}
fi
newfs -q $_opt ${_pp##/dev/}
# N.B.: '!' is lexically < '/'.
# That is required for correct sorting of mount points.
FSENT="$FSENT $_mp!$_pp"
done <$_fstab
fi
return 0
}
# ------------------------------------------------------------------------------
# Functions for the dmesg listener
# ------------------------------------------------------------------------------
# Acquire lock.
lock() {
while ! mkdir /tmp/i/lock 2>/dev/null && sleep .1; do :; done
}
# Release lock.
unlock() {
rm -df /tmp/i/lock 2>/dev/null
}
# Add a trap to kill the dmesg listener co-process on exit of the installer.
retrap() {
trap '
if [[ -f /tmp/cppid ]]; then
kill -KILL -"$(</tmp/cppid)" 2>/dev/null
rm -f /tmp/cppid
fi
echo
stty echo
exit 0
' INT EXIT TERM
}
# Start a listener process looking for dmesg changes which indicates a possible
# plug-in/-out of devices (e.g. usb disks, cdroms, etc.). This is used to abort
# and redraw question prompts, especially in ask_which().
start_dmesg_listener() {
local _update=/tmp/i/update
# Ensure the lock is initially released and that no update files exists.
unlock
rm -f $_update
# Do not start the listener if in non-interactive mode.
$AI && return
# To ensure that only one dmesg listener instance can be active, run it
# in a co-process subshell of which there can always only be one active.
set -m
(
while :; do
lock
# The dmesg listener will continuously check for the existence of
# the update file and sends a signal to the parent process (that
# is the installer script) if the dmesg output differs from the
# contents of that file.
if [[ -e $_update && "$(dmesgtail)" != "$(<$_update)" ]]; then
dmesgtail >$_update
rm -f /tmp/cppid
kill -TERM 2>/dev/null $$ || exit 1
fi
unlock
sleep .5
done
) |&
# Save the co-process pid so it can be used in the retrap() function which
# adds a trap to kill the co-process on exit of the installer script.
echo $! > /tmp/cppid
set +m
retrap
}
# ------------------------------------------------------------------------------
# Functions to ask (or auto-answer) questions
# ------------------------------------------------------------------------------
# Log installer questions and answers so that the resulting file can be used as
# response file for an unattended install/upgrade.
log_answers() {
if [[ -n $1 && -n $2 ]]; then
print -r -- "${1%%'?'*} = $2" >>/tmp/i/$MODE.resp
fi
}
# Fetch response file for autoinstall.
get_responsefile() {
local _rf _if _lf _path _aifile
export AI_HOSTNAME= AI_MAC= AI_MODE= AI_SERVER=
[[ -f /auto_upgrade.conf ]] && _rf=/auto_upgrade.conf AI_MODE=upgrade
[[ -f /auto_install.conf ]] && _rf=/auto_install.conf AI_MODE=install
[[ -f $_rf ]] && cp $_rf /tmp/ai/ai.$AI_MODE.conf && return
for _if in ''; do
[[ -x /sbin/dhcpleased ]] || break
# Select a network interface for initial dhcp request.
# Prefer the interface the system netbooted from.
set -- $(get_ifs netboot)
(($# == 0)) && set -- $(get_ifs)
(($# == 1)) && _if=$1
# Ask if multiple were found and system was not netbooted.
while (($# > 1)); do
ask_which "network interface" \
"should be used for the initial DHCP request" \
"$*"
isin "$resp" $* && _if=$resp && break
done
# Issue initial dhcp request via the found interface.
[[ -n $_if ]] && ifconfig $_if inet autoconf || break
_lf=/var/db/dhcpleased/$_if
if ! wait_for_dhcp_info $_if 30; then
echo "No dhcp address on interface $_if in 30 seconds."
continue
fi
# Extract installer mode and response file path from lease file.
_aifile=$(lease_value $_lf filename)
[[ $_aifile == ?(*/)auto_@(install|upgrade) ]] || _aifile=
_path=${_aifile%auto_@(install|upgrade)}
AI_MODE=${_aifile##*?(/)auto_}
# Extract install server ip address from lease file.
AI_SERVER=$(lease_value $_lf next-server)
# Prime hostname with host-name option from lease file.
AI_HOSTNAME=$(lease_value $_lf host-name)
hostname "$AI_HOSTNAME"
done
# Try to fetch mac-mode.conf, then hostname-mode.conf, and finally
# mode.conf if install server and mode are known, otherwise tell which
# one was missing.
if [[ -n $AI_SERVER && -n $AI_MODE ]]; then
AI_MAC=$(ifconfig $_if | sed 's/.*lladdr \(.*\)/\1/p;d')
for _rf in {$AI_MAC-,${AI_HOSTNAME:+$AI_HOSTNAME-,}}$AI_MODE; do
# Append HTTP_SETDIR as parameter to _url which can be
# used by the webserver to return dynamically created
# response files.
_url="http://$AI_SERVER/$_path$_rf.conf?path=$HTTP_SETDIR"
echo "Fetching $_url"
if unpriv ftp -Vo - "$_url" \
>"/tmp/ai/ai.$AI_MODE.conf" 2>/dev/null; then
ifconfig $_if inet -autoconf delete down \
2>/dev/null
rm /var/db/dhcpleased/$_if
return 0
fi
done
else
[[ -z $AI_SERVER ]] && echo "Could not determine auto server."
[[ -z $AI_MODE ]] && echo "Could not determine auto mode."
fi
# Ask for url or local path to response file. Provide a default url if
# server was found in lease file.
while :; do
ask "Response file location?" \
"${AI_SERVER:+http://$AI_SERVER/install.conf}"
[[ -n $resp ]] && _rf=$resp && break
done
# Ask for the installer mode only if auto-detection failed.
AI_MODE=$(echo "$_rf" | sed -En 's/^.*(install|upgrade).conf$/\1/p')
while [[ -z $AI_MODE ]]; do
ask "(I)nstall or (U)pgrade?"
[[ $resp == [iI]* ]] && AI_MODE=install
[[ $resp == [uU]* ]] && AI_MODE=upgrade
done
echo "Fetching $_rf"
[[ -f $_rf ]] && _rf="file://$_rf"
if unpriv ftp -Vo - "$_rf" >"/tmp/ai/ai.$AI_MODE.conf" 2>/dev/null; then
ifconfig $_if inet -autoconf delete down 2>/dev/null
return 0
fi
return 1
}
# Find a response to question $1 in $AI_RESPFILE and return it via $resp.
# Return default answer $2 if provided and none is found in the file.
#
# Move the existing ai.conf file to a tmp file, read from it line by line
# and write a new ai.conf file skipping the line containing the response.
#
# 1) skip empty and comment lines and lines without =
# 2) split question (_key) and answer (_val) at leftmost =
# 3) strip leading/trailing blanks
# 4) compare questions case insensitive (typeset -l)
#
_autorespond() {
typeset -l _q=$1 _key
local _def=$2 _l _val
[[ -f $AI_RESPFILE && -n $_q ]] || return
mv /tmp/ai/ai.conf /tmp/ai/ai.conf.tmp
while IFS=' ' read -r _l; do
[[ $_l == [!#=]*=?* ]] || continue
_key=${_l%%*([[:blank:]])=*}
_val=${_l##*([!=])=*([[:blank:]])}
[[ $_q == @(|*[[:blank:]])"$_key"@([[:blank:]?]*|) ]] &&
resp=$_val && cat && return
print -r " $_l"
done </tmp/ai/ai.conf.tmp >/tmp/ai/ai.conf
[[ -n $_def ]] && resp=$_def && return
err_exit "\nQuestion has no answer in response file: \"$_q\""
}
# Capture user response either by issuing an interactive read or by searching
# the response file and store the response in the global variable $resp.
#
# Optionally present a question $1 and a default answer $2 shown in [].
#
# If the dmesg output is changed while waiting for the interactive response,
# the current read will be aborted and the function will return a non-zero
# value. Normally, the caller function will then reprint any prompt and call
# the function again.
_ask() {
local _q=$1 _def=$2 _int _redo=0 _pid
lock; dmesgtail >/tmp/i/update; unlock
echo -n "${_q:+$_q }${_def:+[$_def] }"
_autorespond "$_q" "$_def" && echo "$resp" && return
trap "_int=1" INT
trap "_redo=1" TERM
read resp
lock; rm /tmp/i/update; unlock
if ((_redo)); then
stty raw
stty -raw
else
case $resp in
!) echo "Type 'exit' to return to install."
ksh
_redo=1
;;
!*) eval "${resp#?}"
_redo=1
;;
esac
fi
retrap
((_int)) && kill -INT $$
: ${resp:=$_def}
return $_redo
}
# Ask for user response to question $1 with an optional default answer $2.
# Write the question and the answer to a logfile.
ask() {
# Prompt again in case the dmesg listener detected a change.
while ! _ask "$1" "$2"; do :; done
log_answers "$1" "$resp"
}
# Ask the user a yes/no question $1 with 'no' as default answer unless $2 is
# set to 'yes' and insist on 'y', 'yes', 'n' or 'no' as response.
# Return response via $resp as 'y' with exit code 0 or 'n' with exit code 1.
ask_yn() {
local _q=$1 _a=${2:-no}
typeset -l _resp
while :; do
ask "$_q" "$_a"
_resp=$resp
case $_resp in
y|yes) resp=y; return 0;;
n|no) resp=n; return 1;;
esac
echo "'$resp' is not a valid choice."
$AI && exit 1
done
}
# Ask for the user to select one value from a list, or 'done'.
# At exit $resp holds selected item, or 'done'.
#
# Parameters:
#
# $1 = name of the list items (disk, cd, etc.)
# $2 = question to ask
# $3 = list of valid choices
# $4 = default choice, if it is not specified use the first item in $3
#
# N.B.! $3 and $4 will be "expanded" using eval, so be sure to escape them
# if they contain spooky stuff
ask_which() {
local _name=$1 _query=$2 _list=$3 _def=$4 _dynlist _dyndef _key _q
_key=$(echo "$_name" | sed 's/[^[:alnum:]]/_/g')
while :; do
eval "_dynlist=\"$_list\""
eval "_dyndef=\"$_def\""
# Clean away whitespace and determine the default.
set -o noglob
set -- $_dyndef; _dyndef="$1"
set -- $_dynlist; _dynlist="$*"
set +o noglob
(($# < 1)) && resp=done && return
: ${_dyndef:=$1}
echo "Available ${_name}s are: $_dynlist."
_q="Which $_name $_query?"
echo -n "$_q (or 'done') ${_dyndef:+[$_dyndef] }"
_autorespond "$_q" "${_dyndef-done}" && echo "$resp" \
|| _ask || continue
[[ -z $resp ]] && resp="$_dyndef"
# Quote $resp to prevent user from confusing isin() by
# entering something like 'a a'.
if isin "$resp" $_dynlist done; then
log_answers "$_q" "$resp"
break
fi
echo "'$resp' is not a valid choice."
$AI && [[ -n $AI_RESPFILE ]] && exit 1
done
}
# Ask for user response to question $1 with an optional default answer $2
# until a non-empty reply is entered.
ask_until() {
resp=
while :; do
ask "$1" "$2"
[[ -n $resp ]] && break
echo "A response is required."
$AI && exit 1
done
}
# Capture a user password and save it in $resp, optionally showing prompt $1.
#
# 1) *Don't* allow the '!' options that ask does.
# 2) *Don't* echo input.
# 3) *Don't* interpret "\" as escape character.
# 4) Preserve whitespace in input
#
ask_pass() {
stty -echo
IFS= read -r resp?"$1 "
stty echo
echo
}
# Ask for a password twice showing prompt $1. Ensure both inputs are identical
# and save it in $_password.
ask_password() {
local _q=$1
if $AI; then
echo -n "$_q "
_autorespond "$_q"
echo '<provided>'
_password=$resp
return
fi
while :; do
ask_pass "$_q (will not echo)"
_password=$resp
ask_pass "$_q (again)"
[[ $resp == "$_password" ]] && break
echo "Passwords do not match, try again."
done
}
# Ask for a passphrase once showing prompt $1. Ensure input is not empty
# and save it in $_passphrase.
ask_passphrase() {
local _q=$1
if $AI; then
echo -n "$_q "
_autorespond "$_q"
echo '<provided>'
_passphrase=$resp
return
fi
while :; do
IFS= read -r _passphrase?"$_q (will echo) "
[[ -n $_passphrase ]] && break
echo "Empty passphrase, try again."
done
}
# ------------------------------------------------------------------------------
# Support functions for donetconfig()
# ------------------------------------------------------------------------------
# Issue a DHCP request to configure interface $1 and add it to group 'dhcp' to
# later be able to identify DHCP configured interfaces.
dhcp_request() {
local _if=$1
echo "lookup file bind" >>/etc/resolv.conf
ifconfig $_if group dhcp >/dev/null 2>&1
if [[ -x /sbin/dhcpleased ]]; then
ifconfig $_if inet autoconf
if ! wait_for_dhcp_info $_if 30; then
echo "No dhcp address on interface $_if in 30 seconds."
fi
else
echo "DHCP leases not available during install."
fi
}
# Obtain and output the inet information related to interface $1.
# Outputs:
# <flags>\n<addr> <netmask> <rest of inet line>[\n<more inet lines>]
v4_info() {
ifconfig $1 inet | sed -n '
1s/.*flags=.*<\(.*\)>.*/\1/p
/inet/s/netmask //
/.inet /s///p'
}
# Wait until dhcp has configured interface $1 within timeout $2 seconds.
wait_for_dhcp_info() {
local _if=$1 _secs=$2
# Wait until $_if has a V4 address.
while (( _secs > 0 )); do
set -- $(v4_info $_if)
[[ -n $2 ]] && break
sleep 1
(( _secs-- ))
done
# Wait until there is a leases file to parse.
while (( _secs > 0 )); do
[[ -s /var/db/dhcpleased/$_if ]] && break
sleep 1
(( _secs-- ))
done
return $(( _secs <= 0 ))
}
# Convert a netmask in hex format ($1) to dotted decimal format.
hextodec() {
set -- $(echo ${1#0x} | sed 's/\(..\)/0x\1 /g')
echo $(($1)).$(($2)).$(($3)).$(($4))
}