common.sh.in 34.1 KB
Newer Older
1
# Copyright (C) 2011-2015 GRNET S.A. and individual contributors
2
# Copyright (C) 2007, 2008, 2009 Google Inc.
Nikos Skalkotos's avatar
Nikos Skalkotos committed
3
#
4
5
6
7
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
Nikos Skalkotos's avatar
Nikos Skalkotos committed
8
#
9
10
11
12
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
Nikos Skalkotos's avatar
Nikos Skalkotos committed
13
#
14
15
16
17
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
Nikos Skalkotos's avatar
Nikos Skalkotos committed
18

19
PROGNAME=$(basename $0)
Nikos Skalkotos's avatar
Nikos Skalkotos committed
20

Nikos Skalkotos's avatar
Nikos Skalkotos committed
21
22
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin

Nikos Skalkotos's avatar
Nikos Skalkotos committed
23
24
# Programs
XMLSTARLET=xmlstarlet
25
TUNE2FS=tune2fs
Nikos Skalkotos's avatar
Nikos Skalkotos committed
26
RESIZE2FS=resize2fs
27
PARTED=parted
28
SFDISK=sfdisk
Nikos Skalkotos's avatar
Nikos Skalkotos committed
29
30
MKSWAP=mkswap
BLKID=blkid
31
BLOCKDEV=blockdev
32
SGDISK=sgdisk
Nikos Skalkotos's avatar
Nikos Skalkotos committed
33
GROWFS_UFS=growfs.ufs
34
DUMPFS_UFS=dumpfs.ufs
35
36
GROWFS_OPENBSD=growfs.openbsd
DUMPFS_OPENBSD=dumpfs.openbsd
37
DATE="date -u" # Time in UTC
38
EATMYDATA=eatmydata
39
MOUNT="mount -n"
40
41
42
43
HIVEXGET=hivexget
HIVEXREGEDIT=hivexregedit
BTRFS=btrfs
XFS_GROWFS=xfs_growfs
44
BASE64=base64
45
NTFSINFO=ntfsinfo
46
47
NTFSRESIZE=ntfsresize
NTFSFIX=ntfsfix
Nikos Skalkotos's avatar
Nikos Skalkotos committed
48
49

CLEANUP=( )
50
51
ERRORS=( )
WARNINGS=( )
Nikos Skalkotos's avatar
Nikos Skalkotos committed
52

53
54
55
56
MSG_TYPE_TASK_START="TASK_START"
MSG_TYPE_TASK_END="TASK_END"

STDERR_LINE_SIZE=10
57

Nikos Skalkotos's avatar
Nikos Skalkotos committed
58
59
60
61
62
add_cleanup() {
    local cmd=""
    for arg; do cmd+=$(printf "%q " "$arg"); done
    CLEANUP+=("$cmd")
}
Dimitris Aragiorgis's avatar
Dimitris Aragiorgis committed
63

Nikos Skalkotos's avatar
Nikos Skalkotos committed
64
65
66
67
close_fd() {
    local fd=$1

    exec {fd}>&-
Dimitris Aragiorgis's avatar
Dimitris Aragiorgis committed
68
69
}

70
71
72
73
74
75
76
77
78
79
80
81
82
send_result_kvm() {
    echo "$@" > /dev/ttyS1
}

send_monitor_message_kvm() {
    echo "$@" > /dev/ttyS2
}

send_result_xen() {
    xenstore-write /local/domain/0/snf-image-helper/$DOMID "$*"
}

send_monitor_message_xen() {
83
    #Broadcast the message
84
85
    echo "$@" \
        | socat "STDIO" "UDP-DATAGRAM:${BROADCAST}:${MONITOR_PORT},broadcast"
86
87
}

88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
start_debug_shell_kvm() {
    local dev="/dev/ttyS3"

    echo "Starting a debug shell on \`$dev'"
    echo "See Ganeti logs for /dev/pts/X device to use on host."
    bash <$dev >$dev 2>&1
    echo "Returned from debug shell, resuming..."
}

start_debug_shell_xen() {
    echo "Cannot start a debug shell on Xen. Feature not yet supported."
}

start_debug_shell() {
    start_debug_shell_${HYPERVISOR}
}

Nikos Skalkotos's avatar
Nikos Skalkotos committed
105
106
prepare_helper() {
	local cmdline item key val hypervisor domid
Dimitris Aragiorgis's avatar
Dimitris Aragiorgis committed
107

Nikos Skalkotos's avatar
Nikos Skalkotos committed
108
109
	read -a cmdline	 < /proc/cmdline
	for item in "${cmdline[@]}"; do
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
            key=$(cut -d= -f1 <<< "$item")
            val=$(cut -d= -f2 <<< "$item")
            if [ "$key" = "hypervisor" ]; then
                hypervisor="$val"
            fi
            if [ "$key" = "rules_dev" ]; then
                export RULES_DEV="$val"
            fi
            if [ "$key" = "helper_ip" ]; then
                export IP="$val"
                export NETWORK="$IP/24"
                export BROADCAST="${IP%.*}.255"
            fi
            if [ "$key" = "monitor_port" ]; then
                export MONITOR_PORT="$val"
            fi
Nikos Skalkotos's avatar
Nikos Skalkotos committed
126
127
128
129
	done

    case "$hypervisor" in
    kvm)
130
        HYPERVISOR=kvm
131
        ;;
Nikos Skalkotos's avatar
Nikos Skalkotos committed
132
    xen-hvm|xen-pvm)
133
134
135
136
137
138
139
140
        if [ -z "$IP" ]; then
            echo "ERROR: \`helper_ip' not defined or empty" >&2
            exit 1
        fi
        if [ -z "$MONITOR_PORT" ]; then
            echo "ERROR: \`monitor_port' not defined or empty" >&2
            exit 1
        fi
141
        $MOUNT -t xenfs xenfs /proc/xen
142
        ip addr add "$NETWORK" dev eth0
143
        ip link set eth0 up
144
        ip route add default dev eth0
145
146
        export DOMID=$(xenstore-read domid)
        HYPERVISOR=xen
Nikos Skalkotos's avatar
Nikos Skalkotos committed
147
148
149
150
        ;;
    *)
        echo "ERROR: Unknown hypervisor: \`$hypervisor'" >&2
        exit 1
151
152
        ;;
    esac
Dimitris Aragiorgis's avatar
Dimitris Aragiorgis committed
153

154
    export HYPERVISOR
155
156
}

157
report_error() {
158
    msg=""
159
160
    if [ ${#ERRORS[*]} -eq 0 ]; then
        # No error message. Print stderr
161
162
163
164
        local lines stderr
        stderr="$(tail --lines=${STDERR_LINE_SIZE} "$STDERR_FILE")"
        lines=$(wc -l <<< "$stderr")
        msg="STDERR:${lines}:$stderr"
165
166
    else
        for line in "${ERRORS[@]}"; do
167
            msg+="ERROR:$line"$'\n'
168
169
        done
    fi
170
171

    send_monitor_message_${HYPERVISOR} "$msg"
172
173
}

Nikos Skalkotos's avatar
Nikos Skalkotos committed
174
log_error() {
175
    ERRORS+=("$*")
176

177
    send_result_${HYPERVISOR} "ERROR: $@"
178
179
180
181

    # Use return instead of exit. The set -x options will terminate the script
    # but will also trigger ERR traps if defined.
    return 1
Nikos Skalkotos's avatar
Nikos Skalkotos committed
182
183
}

184
185
warn() {
    echo "Warning: $@" >&2
186
    send_monitor_message_${HYPERVISOR} "WARNING: $@"
187
188
}

189
report_task_start() {
190
    send_monitor_message_${HYPERVISOR} "$MSG_TYPE_TASK_START:${PROGNAME:2}"
191
192
}

193
report_task_end() {
194
    send_monitor_message_${HYPERVISOR} "$MSG_TYPE_TASK_END:${PROGNAME:2}"
195
196
}

197
system_poweroff() {
198
199
200
201
202
203
    # Sync everything to disk and sleep for a second before powering off.
    # Do this to ensure data has hit the disk, since we are about to
    # kill the helper VM with a sysrq call.
    sync
    sleep 1

204
205
206
207
208
    while [ 1 ]; do
        # Credits to psomas@grnet.gr for this ...
        echo o > /proc/sysrq-trigger
        sleep 1
    done
209
210
}

Nikos Skalkotos's avatar
Nikos Skalkotos committed
211
212
213
get_base_distro() {
    local root_dir=$1

214
215
216
    if [ -d "$root_dir/etc/coreos" ]; then
        echo "coreos"
    elif [ -e "$root_dir/etc/debian_version" ]; then
Nikos Skalkotos's avatar
Nikos Skalkotos committed
217
218
219
220
221
        echo "debian"
    elif [ -e "$root_dir/etc/redhat-release" ]; then
        echo "redhat"
    elif [ -e "$root_dir/etc/slackware-version" ]; then
        echo "slackware"
222
    elif [ -e "$root_dir/etc/SuSE-release" ]; then
Nikos Skalkotos's avatar
Nikos Skalkotos committed
223
        echo "suse"
224
    elif [ -e "$root_dir/etc/gentoo-release" ]; then
Nikos Skalkotos's avatar
Nikos Skalkotos committed
225
        echo "gentoo"
Nikos Skalkotos's avatar
Nikos Skalkotos committed
226
227
    elif [ -e "$root_dir/etc/arch-release" ]; then
        echo "arch"
Nikos Skalkotos's avatar
Nikos Skalkotos committed
228
229
    elif [ -e "$root_dir/etc/freebsd-update.conf" ]; then
        echo "freebsd"
230
    elif [ -e "$root_dir/etc/release" ]; then
231
        if grep -i netbsd "$root_dir/etc/release" &> /dev/null; then
232
233
234
235
            echo "netbsd"
        else
            warn "Unknown Unix flavor."
        fi
236
237
    elif [ -e "$root_dir/etc/motd" ]; then
        if grep -i ^openbsd <(head -1 "$root_dir/etc/motd") &> /dev/null; then
238
239
240
241
            echo "openbsd"
        else
            warn "Unknown Unix flavor"
        fi
242
243
    else
        warn "Unknown base distro."
Nikos Skalkotos's avatar
Nikos Skalkotos committed
244
245
246
247
    fi
}

get_distro() {
Nikos Skalkotos's avatar
Nikos Skalkotos committed
248
249
    local root_dir distro
    root_dir=$1
Nikos Skalkotos's avatar
Nikos Skalkotos committed
250
251
252
253

    if [ -e "$root_dir/etc/debian_version" ]; then
        distro="debian"
        if [ -e ${root_dir}/etc/lsb-release ]; then
254
            ID=$(grep ^DISTRIB_ID= ${root_dir}/etc/lsb-release | cut -d= -f2)
Nikos Skalkotos's avatar
Nikos Skalkotos committed
255
256
257
258
259
260
261
            if [ "x$ID" = "xUbuntu" ]; then
                distro="ubuntu"
            fi
        fi
        echo "$distro"
    elif [ -e "$root_dir/etc/fedora-release" ]; then
        echo "fedora"
Nikos Skalkotos's avatar
Nikos Skalkotos committed
262
    elif [ -e "$root_dir/etc/centos-release" ]; then
Nikos Skalkotos's avatar
Nikos Skalkotos committed
263
264
265
266
267
        echo "centos"
    elif [ -e "$root_dir/etc/redhat-release" ]; then
        echo "redhat"
    elif [ -e "$root_dir/etc/slackware-version" ]; then
        echo "slackware"
268
    elif [ -e "$root_dir/etc/SuSE-release" ]; then
Nikos Skalkotos's avatar
Nikos Skalkotos committed
269
        echo "suse"
270
    elif [ -e "$root_dir/etc/gentoo-release" ]; then
Nikos Skalkotos's avatar
Nikos Skalkotos committed
271
        echo "gentoo"
Nikos Skalkotos's avatar
Nikos Skalkotos committed
272
273
    elif [ -e "$root_dir/etc/arch-release" ]; then
        echo "arch"
Nikos Skalkotos's avatar
Nikos Skalkotos committed
274
275
    elif [ -e "$root_dir/etc/freebsd-update.conf" ]; then
        echo "freebsd"
276
277
278
279
280
281
    elif [ -e "$root_dir/etc/release" ]; then
        if grep -in netbsd "$root_dir/etc/release" &> /dev/null; then
            echo "netbsd"
        else
            warn "Unknown Unix flavor"
        fi
282
283
    elif [ -e "$root_dir/etc/motd" ]; then
        if grep -i ^openbsd <(head -1 "$root_dir/etc/motd") &> /dev/null; then
284
285
286
287
            echo "openbsd"
        else
            warn "Unknown Unix flavor"
        fi
288
289
    else
        warn "Unknown distro."
Nikos Skalkotos's avatar
Nikos Skalkotos committed
290
291
292
    fi
}

293
get_partition_table() {
Nikos Skalkotos's avatar
Nikos Skalkotos committed
294
295
    local dev output
    dev="$1"
296
297
298
    # If the partition table is GUID and the secondary GPT header is not at the
    # end of the disk, parted will raise an error and will also print a warning
    # about the "Last Usable LBA" entry of the header.
299
300
    if ! output="$("$PARTED" -s -m "$dev" unit s print \
                   | grep -E -v "^(Warning|Error): ")"; then
301
        log_error "Unable to read partition table for device \`${dev}'. The image seems corrupted."
302
303
    fi

304
305
306
307
    echo "$output"
}

get_partition_table_type() {
Nikos Skalkotos's avatar
Nikos Skalkotos committed
308
309
    local ptable dev field
    ptable="$1"
310

Nikos Skalkotos's avatar
Nikos Skalkotos committed
311
    dev="$(sed -n 2p <<< "$ptable")"
312
313
314
315
316
317
318
319
320
    IFS=':' read -ra field <<< "$dev"

    echo "${field[5]}"
}

get_partition_count() {
    local ptable="$1"

    expr $(echo "$ptable" | wc -l) - 2
321
322
}

323
get_partition_by_num() {
324
325
326
327
328
329
    local ptable="$1"
    local id="$2"

    grep "^$id:" <<< "$ptable"
}

330
get_last_partition() {
331
    local ptable="$1"
332

333
    echo "$ptable" | tail -1
334
335
}

336
is_extended_partition() {
337
    local dev="$1"
338
    local part_num="$2"
339

340
    id=$($SFDISK --force --print-id "$dev" "$part_num")
341
    if [ "$id" = "5" -o "$id" = "f" ]; then
342
343
344
345
        echo "yes"
    else
        echo "no"
    fi
346
347
}

348
get_extended_partition() {
Nikos Skalkotos's avatar
Nikos Skalkotos committed
349
350
351
    local ptable dev
    ptable="$1"
    dev="$(echo "$ptable" | sed -n 2p | cut -d':' -f1)"
352

353
354
355
356
357
358
359
360
361
362
363
    tail -n +3 <<< "$ptable" | while read line; do
        part_num=$(cut -d':' -f1 <<< "$line")
        if [ $(is_extended_partition "$dev" "$part_num") == "yes" ]; then
            echo "$line"
            return 0
        fi
    done
    echo ""
}

get_logical_partitions() {
Nikos Skalkotos's avatar
Nikos Skalkotos committed
364
365
    local ptable part_num
    ptable="$1"
366
367
368
369
370
371
372
373
374
375
376
377

    tail -n +3 <<< "$ptable" | while read line; do
        part_num=$(cut -d':' -f1 <<< "$line")
        if [ $part_num -ge 5 ]; then
            echo "$line"
        fi
    done

    return 0
}

get_last_primary_partition() {
Nikos Skalkotos's avatar
Nikos Skalkotos committed
378
379
380
    local ptable dev output
    ptable="$1"
    dev=$(echo "ptable" | sed -n 2p | cut -d':' -f1)
381
382
383
384
385
386
387
388
389
390

    for i in 4 3 2 1; do
        if output=$(grep "^$i:" <<< "$ptable"); then
            echo "$output"
            return 0
        fi
    done
    echo ""
}

391
get_partition_to_resize() {
Nikos Skalkotos's avatar
Nikos Skalkotos committed
392
393
394
    local dev table table_type last_part last_part_num extended last_primary \
        ext_num prim_num
    dev="$1"
395
396

    table=$(get_partition_table "$dev")
397
398
399
    if [ -z "$table" ]; then
        return 0
    fi
400
401
402
403
404
405
406
407
408
409
410
411
412

    if [ $(get_partition_count "$table") -eq 0 ]; then
        return 0
    fi

    table_type=$(get_partition_table_type "$table")
    last_part=$(get_last_partition "$table")
    last_part_num=$(cut -d: -f1 <<< "$last_part")

    if [ "$table_type" == "msdos" -a $last_part_num -gt 4 ]; then
        extended=$(get_extended_partition "$table")
        last_primary=$(get_last_primary_partition "$table")
        ext_num=$(cut -d: -f1 <<< "$extended")
413
        last_prim_num=$(cut -d: -f1 <<< "$last_primary")
414
415
416
417
418
419
420
421
422
423
424

        if [ "$ext_num" != "$last_prim_num" ]; then
            echo "$last_prim_num"
        else
            echo "$last_part_num"
        fi
    else
        echo "$last_part_num"
    fi
}

425
create_partition() {
426
427
428
    local device="$1"
    local part="$2"
    local ptype="$3"
429

Nikos Skalkotos's avatar
Nikos Skalkotos committed
430
    local fields=()
431
    IFS=":;" read -ra fields <<< "$part"
432
433
434
435
436
437
438
439
    local id="${fields[0]}"
    local start="${fields[1]}"
    local end="${fields[2]}"
    local size="${fields[3]}"
    local fs="${fields[4]}"
    local name="${fields[5]}"
    local flags="${fields[6]//,/ }"

440
441
442
443
444
445
    if [ "$ptype" = "primary" -o "$ptype" = "logical" -o "$ptype" = "extended" ]; then
        $PARTED -s -m -- $device mkpart "$ptype" $fs "$start" "$end"
        for flag in $flags; do
            $PARTED -s -m $device set "$id" "$flag" on
        done
    else
446
        # For GPT
447
448
        start=${start:0:${#start}-1} # remove the s at the end
        end=${end:0:${#end}-1} # remove the s at the end
449
        $SGDISK -a 1 -n "$id":"$start":"$end" -t "$id":"$ptype" -c "$id":"$name" "$device"
450
    fi
451
452
453
}

enlarge_partition() {
Nikos Skalkotos's avatar
Nikos Skalkotos committed
454
455
456
457
458
    local device part ptype new_end fields new_part table logical id
    device="$1"
    part="$2"
    ptype="$3"
    new_end="$4"
459
460
461
462

    if [ -z "$new_end" ]; then
        new_end=$(cut -d: -f 3 <<< "$(get_last_free_sector "$device")")
    fi
463

Nikos Skalkotos's avatar
Nikos Skalkotos committed
464
    fields=()
465
466
467
    IFS=":;" read -ra fields <<< "$part"
    fields[2]="$new_end"

Nikos Skalkotos's avatar
Nikos Skalkotos committed
468
    new_part=""
469
470
471
472
473
474
475
476
    for ((i = 0; i < ${#fields[*]}; i = i + 1)); do
        new_part="$new_part":"${fields[$i]}"
    done
    new_part=${new_part:1}

    # If this is an extended partition, removing it will also remove the
    # logical partitions it contains. We need to save them for later.
    if [ "$ptype" = "extended" ]; then
Nikos Skalkotos's avatar
Nikos Skalkotos committed
477
478
        table="$(get_partition_table "$device")"
        logical="$(get_logical_partitions "$table")"
479
480
481
482
    fi

    id=${fields[0]}

483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
    # Newer versions of parted have a resizepart command that can be used to
    # extend a partition. If this command is absent, we will have to remove and
    # recreate a partition. This is not safe because there is no way to retain
    # the partition number if the numbering contains gaps. In order to
    # determine if parted supports resizepart we print the command's usage. If
    # the output is not empty, the command is there.
    supports_resizepart=$($PARTED -s -m "$device" help resizepart)

    if [ -n "$supports_resizepart" ]; then
        $PARTED -s -m -- "$device" resizepart "$id" "$new_end"
    else
        $PARTED -s -m "$device" rm "$id"
        create_partition "$device" "$new_part" "$ptype"

        if [ "$ptype" = "extended" ]; then
            # Recreate logical partitions
            echo "$logical" | while read logical_part; do
                create_partition "$device" "$logical_part" "logical"
            done
        fi
503
    fi
504
505
506
}

get_last_free_sector() {
Nikos Skalkotos's avatar
Nikos Skalkotos committed
507
508
509
    local dev unit last_line ptype
    dev="$1"
    unit="$2"
510
511
512
513
514

    if [ -n "$unit" ]; then
        unit="unit $unit"
    fi

Nikos Skalkotos's avatar
Nikos Skalkotos committed
515
516
    last_line="$($PARTED -s -m "$dev" "$unit" print free | tail -1)"
    ptype="$(cut -d: -f 5 <<< "$last_line")"
517

518
    if [ "$ptype" = "free;" ]; then
519
        echo "$last_line"
520
521
522
    fi
}

523
524
get_sysprepinf() {
    local target
Nikos Skalkotos's avatar
Nikos Skalkotos committed
525
    target="$1"
526

527
528
529
530
531
    # Return position of old-style (XP / Server 2003) answer file,
    # C:\SYSPREP\SYSPREP.INF, if found.
    sysprepinf="$target"/sysprep/sysprep.inf
    if [ ! -f "$sysprepinf" ]; then
        sysprepinf=""
532
    fi
533
534
535
    echo "$sysprepinf"
}

536
get_unattend() {
537
    local target
Nikos Skalkotos's avatar
Nikos Skalkotos committed
538
    target="$1"
539

540
541
542
543
544
545
546
547
    # TODO: Take into account the precise answer file search order defined in
    # https://technet.microsoft.com/en-us/library/cc749415(v=ws.10).aspx
    for unattend in "$target/unattend.xml" "$target/autounattend.xml"; do
        if [ -f "$unattend" ]; then
            echo "$unattend"
            break
        fi
    done
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
create_unattend_component() {
    local unattend pass component arch args settings component
    unattend="$1"
    pass="$2"
    name="$3"
    arch="$4"

    settings='/_:unattend/_:settings[@pass="'"$pass"'"]'

    # Create the 'settings' node if missing
    if ! $XMLSTARLET sel -t -v "$settings" "$unattend" &>/dev/null; then
        $XMLSTARLET ed -L -s '/_:unattend' -t elem -n settings -i \
            '/_:unattend/_:settings[not(@pass)]' -t attr -n pass -v "$pass" \
            "$unattend"
    fi

    component=$settings'/_:component[@name="'"$name"'" and @processorArchitecture="'"$arch"'"]'

    # Create the 'component' node if missing
    if ! $XMLSTARLET sel -t -v "$component" "$unattend" &>/dev/null; then
        $XMLSTARLET ed -L -s "$settings" -t elem -n component "$unattend"
        $XMLSTARLET ed -L -i "$settings"'/_:component[not(@name)]' -t attr \
            -n name -v "$name" "$unattend"
        $XMLSTARLET ed -L -i \
            "$settings"'/_:component[@name="'"$name"'" and not(@processorArchitecture)]' \
            -t attr -n processorArchitecture -v "$arch" "$unattend"
        $XMLSTARLET ed -L \
            -i "$component" -t attr -n publicKeyToken -v 31bf3856ad364e35 \
            -i "$component" -t attr -n language -v neutral \
            -i "$component" -t attr -n versionScope -v nonSxS \
            -i "$component" -t attr -n xmlns:wcm -v "http://schemas.microsoft.com/WMIConfig/2002/State" \
            -i "$component" -t attr -n xmlns:xsi -v "http://www.w3.org/2001/XMLSchema-instance" \
            "$unattend"
    fi
584
585

    echo "$component"
586
587
}

588
589
590
591
592
593
594
595
596
597
windows_add_synchronous_command() {
    local unattend arch description order path component run_synchronous \
        token rsc
    unattend="$1"
    arch="$2"
    description="$3"
    order="$4"
    path="$5"

    # Create 'Microsoft-Windows-Deployment' if missing
598
    component="$(create_unattend_component "$unattend" specialize 'Microsoft-Windows-Deployment' "$arch")"
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616

    if ! $XMLSTARLET sel -t -v "$component/_:RunSynchronous" "$unattend" &>/dev/null; then
        $XMLSTARLET ed -L -s "$component" -t elem -n RunSynchronous "$unattend"
    fi

    token=$(cat /proc/sys/kernel/random/uuid)
    $XMLSTARLET ed -L -s "$component/_:RunSynchronous" -t elem -n RunSynchronousCommand "$unattend"
    $XMLSTARLET ed -L -s "($component/_:RunSynchronous/_:RunSynchronousCommand[count(@*)=0])[1]" -t attr -n randomToken -v "$token" "$unattend"

    rsc="$component/_:RunSynchronous/_:RunSynchronousCommand[@randomToken=\"$token\"]"
    $XMLSTARLET ed -L \
        -s "$rsc" -t elem -n Description -v "$description" \
        -s "$rsc" -t elem -n Order -v "$order" \
        -s "$rsc" -t elem -n Path -v "$path" \
        -i "$rsc" -t attr -n wcm:action -v add \
        -d "$rsc/@randomToken" "$unattend"
}

617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
windows_update_unattend_node() {
    local unattend pass component arch name value xpath
    unattend="$1"
    pass=$2
    component=$3
    arch="$4"
    name="$5"
    value="$6"

    # Create component if missing
    xpath="$(create_unattend_component "$unattend" "$pass" "$component" "$arch")"

    if $XMLSTARLET sel -t -v "$xpath/_:$name" "$unattend" &> /dev/null; then
        # The node exists. Updating its value.
        $XMLSTARLET ed -L -u "$xpath/_:$name" -v "$value" "$unattend"
    else
        # Creating the node.
        $XMLSTARLET ed -L -s "$xpath" -t elem -n $name -v "$value" "$unattend"
    fi
}

638
639
bsd2linux() {
    local partition device
Nikos Skalkotos's avatar
Nikos Skalkotos committed
640
    partition="$1"
641
    device="$2"
Nikos Skalkotos's avatar
Nikos Skalkotos committed
642

643
644
    linux_part=$(@scriptsdir@/disklabel.py --get-partitions-mapping "$device" \
                 | grep ^"$partition": | cut -d" " -f2)
645
646
647
648
649

    if [[ "$linux_part" =~ ^[1-9][0-9]*$ ]]; then
        echo "$linux_part"
        return 0
    fi
Nikos Skalkotos's avatar
Nikos Skalkotos committed
650

651
    log_error "Couldn't find out mapping for BSD partition: \`$partition' in \`$device'"
Nikos Skalkotos's avatar
Nikos Skalkotos committed
652
653
}

654
655
656
657
658
659
660
661
662
663
664
665
666
667
cidr2mask() {
    local zeroes=$((32-$1))
    local shifts
    local IFS=.
    shift

    for i in 1 2 3 4; do
        ((shifts = zeroes > 8 ? 8 : zeroes))
        zeroes=$((zeroes - shifts))
        set $((255 >> shifts << shifts)) "$@"
    done
    echo "$*"
}

668
669
670
671
672
673
674
675
676
677
678
679
680
find_mount_target() {
    local device
    device="$1"

    while read entry; do
        set $entry
        if [ "$device" = "$1" -o "$device" = "$(readlink -f "$1")" ]; then
            echo "$2"
            break
        fi
    done <<< "$(cat /proc/mounts)"
}

Nikos Skalkotos's avatar
Nikos Skalkotos committed
681
682
683
684
685
686
687
688
mount_all() {
    local osfamily target fs device fstab entry duid opts types num
    osfamily="$1"
    device="$2"
    target="$3"

    case "$osfamily" in
    linux)
689
        fs="ext[234]|msdos|vfat|ntfs|btrfs|xfs"
Nikos Skalkotos's avatar
Nikos Skalkotos committed
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
        ;;
    freebsd)
        fs="ufs|msdosfs|ntfs"
        ;;
    openbsd)
        fs="ffs|msdos|ntfs|ext2fs"
        ;;
    netbsd)
        fs="ffs|ufs|msdos|ext2fs|ntfs"
        ;;
    *)
        log_error "Unsupported osfamily: \`$osfamily'"
        ;;
    esac

705
    fstab="$(grep -v ^\# "${target}/etc/fstab" \
706
             | gawk "{ if (match(\$3, \"$fs\")) { print \$2,\$1,\$3 } }" \
707
             | sort -bd)"
Nikos Skalkotos's avatar
Nikos Skalkotos committed
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
    # <mpoint> <device> <fs>
    while read -ra entry; do
        # Skip root. It is already mounted
        if [ "${entry[0]}" = "/" ]; then
            continue
        fi

        opts="rw"
        types="auto"

        if [ "$osfamily" = linux ]; then
            # Linux persistent block device naming
            if [[ ${entry[1]} =~ ^(LABEL|UUID)= ]]; then
                entry[1]=$(findfs "${entry[1]}")
            else
                log_error "fstab contains non-persistent block device names"
            fi
        else
            if [[ "$osfamily" =~ ^(open|net)bsd$ ]]; then
                # OpenBSD DUIDs device naming
                if [[ "${entry[1]}" =~ ^[a-f0-9]{16}\.[a-p]$ ]]; then
Nikos Skalkotos's avatar
Nikos Skalkotos committed
729
                    duid="$(@scriptsdir@/disklabel.py --get-duid "$device")"
Nikos Skalkotos's avatar
Nikos Skalkotos committed
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

                    if [[ ! "${entry[1]}" =~ ^$duid ]]; then
                        warn "fstab refers to unknown DUID: \`$duid'"
                        continue
                    fi
                fi
                num="$(disklabel2linux "${entry[1]: -1}")"
                if [ "${entry[2]}" = ffs -o "$entry[2]" = ufs ]; then
                    types="ufs"
                    opts="ufstype=44bsd,rw"
                fi
            else # FreeBSD
                # We do not support FreeBSD labels for now
                if [[ "${entry[1]}" =~ ^/dev/(ufs|label)/ ]]; then
                    log_error "fstab contains FreeBSD labels. We currently don't support them"
                fi
                num="${entry[1]: -1}"
                if [ "${entry[2]}" = ufs ]; then
                    types="ufs"
                    opts="ufstype=ufs2,rw"
                fi
            fi
            entry[1]="${device}${num}"
        fi

        $MOUNT -t "$types" -o "$opts" "${entry[1]}" "${target}${entry[0]}"
        # In many cases when you try to mount a UFS file system read-write, the
        # mount command returns SUCCESS and a message like this gets printed:
        #
        #   mount: warning: <target> seems to be mounted read-only.
        #
        # remounting does the trick
        if [ "$types" = ufs ]; then
            $MOUNT -o remount,rw "${entry[1]}"
        fi

    done <<< "$fstab"
}

769
770
771
772
773
umount_all() {
    local target mpoints
    target="$1"

    # Unmount file systems mounted under directory `target'
774
    mpoints="$({ gawk "{ if (match(\$2, \"^$target\")) { print \$2 } }" < /proc/mounts; } \
775
               | sort -rbd | uniq)"
776
777
778
779
780
781

    for mpoint in $mpoints; do
        umount $mpoint
    done
}

782
get_ufstype() {
783
    local device ufs pipefail
784
785

    device="$1"
786
787
788
789
790

    pipefail=$(set -o | grep pipefail | cut -f2)
    if [ "$pipefail" = on ]; then
        set +o pipefail
    fi
791
    ufs="$($DUMPFS_UFS "$device" | head -1 \
792
           | gawk -F "[()]" '{ for (i=2; i<NF; i+=2) print $i }')"
793
794
795
    if [ "$pipefail" = on ]; then
        set -o pipefail
    fi
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810

    case "$ufs" in
        UFS1)
            echo 44bsd
            ;;
        UFS2)
            echo ufs2
            ;;
        *)
            log_error "Unsupported UFS type: \`$ufs' in device $device"
            echo ""
            ;;
    esac
}

811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
get_windows_architecture() {
    local target hive current
    target="$1"

    hive="$target/windows/system32/config/system"

    current=$($HIVEXGET "$hive" Select Current)
    if [ "$current" = "" ]; then
        log_error "Unable to find CurrentControlSet in the registry"
    fi

    # Pad the value with zeros
    current=$(printf "%03d" "$current")

   $HIVEXGET "$hive" 'ControlSet'${current}'\Control\Session Manager\Environment' \
       | grep PROCESSOR_ARCHITECTURE \
       | cut -d= -f2 \
       | sed 's/"//g' \
       | tr [A-Z] [a-z]
}

832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
get_windows_nt_version() {
    local target hive current_version
    target="$1"

    hive="$target/windows/system32/config/software"
    if [ ! -f "$hive" ]; then
        log_error "File: \`$hive' does not exist"
    fi

    current_version=$($HIVEXGET "$hive" 'Microsoft\Windows NT\CurrentVersion' \
                      | grep ^'"CurrentVersion"=')

    if [[ "$current_version" =~ \"CurrentVersion\"=\"([0-9]+)\.([0-9]+)\" ]]; then
        echo ${BASH_REMATCH[1]} ${BASH_REMATCH[2]}
    else
        log_error "Can't decode CurrentVersion registry key: $current_version"
    fi
}

851
check_if_root() {
852
    local os fd device is_root args windows_fs linux_fs freebsd_fs netbsd_fs \
853
        openbsd_fs supported_fs major minor
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

    osfamily="$1"
    fs="$2"
    device="$3"

    windows_fs="(ntfs|ntfs-3g|vfat|msdos|fat|fuse|fuseblk)"
    linux_fs="(ext[234]|xfs|jfs|zfs|btrfs|reiserfs|reiser4)"
    freebsd_fs="(ufs|zfs)"
    netbsd_fs="(ufs|zfs)"
    openbsd_fs="ufs"

    supported_fs="${osfamily}_fs"

    if [ "$fs" = "" ]; then
        warn "Can't detect a file system on device: \`$device'"
        return 0
    fi

    if [[ ! "$fs" =~ ${!supported_fs} ]]; then
        warn "Ignoring device: \`$device' with file system: \`$fs' when checking for a $osfamily root"
        return 0
    fi

    if [ "$fs" = ufs ]; then
        args="-t ufs -o ufstype=$(get_ufstype "$device"),ro"
879
880
    elif $NTFSINFO -m "$device" &> /dev/null; then
        args="-t lowntfs-3g -o ignore_case,windows_names,norecover,ro"
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
    else
        args="-o ro"
    fi

    is_root=no
    $MOUNT "$device" "$SNF_IMAGE_TARGET" $args || {
        warn "Unable to mount: \`$device' with file system: \`$fs'"
        set -e
        return 0
    }
    case "$osfamily" in
        freebsd|linux)
            if [ -f "$SNF_IMAGE_TARGET/etc/fstab" ]; then
                is_root=yes
            fi
            ;;
        windows)
898
899
900
901
            if [ -d "$SNF_IMAGE_TARGET/windows/system32" ]; then
                read major minor <<<$(get_windows_nt_version "$SNF_IMAGE_TARGET")
                if [ $major -lt 6 ]; then
                    osfamily="windows-legacy"
902
                fi
903
                is_root=yes
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
            fi
            ;;
    esac
    umount "$SNF_IMAGE_TARGET"

    if [ "$is_root" = yes ]; then
        export DETECTED_OSFAMILY="$osfamily"
    fi
}

examine_msdos_partition() {
    local dev part
    dev="$1"
    part="$2"

    num=$(cut -f1 -d: <<< "$part")
    id=$($SFDISK --force --print-id "$dev" "$num") || true
    fs=$($BLKID -s TYPE -o value "$dev$num") || true

    case "$id" in
        a5)
            export DETECTED_OSFAMILY=freebsd
            ;;
        a6)
            export DETECTED_OSFAMILY=openbsd
            ;;
        a9)
            export DETECTED_OSFAMILY=netbsd
            ;;
        7) # looks like Windows
            check_if_root windows "$fs" "$dev$num"
            ;;
        83) # looks like Linux
            check_if_root linux "$fs" "$dev$num"
            ;;
    esac

    if [ -n "$DETECTED_OSFAMILY" ]; then
        export DETECTED_ROOT_PARTITION="$num"
    fi
}

examine_gpt_partition() {
    local dev part ufs_type
    dev="$1"
    part="$2"

    # It's OK if some of those fail
    num=$(cut -f1 -d: <<< "$part")
    fs=$($BLKID -s TYPE -o value "$dev$num") || true
    id=$($SGDISK -i "$num" "$dev" | \
         grep "^Partition GUID code:" | \
         cut -f2 -d: | \
957
         gawk '{print $1}') || true
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

    case "$id" in
        516E7CB6-6ECF-11D6-8FF8-00022D09712B) # FreeBSD
            check_if_root freebsd "$fs" "$dev$num"
            ;;
        EBD0A0A2-B9E5-4433-87C0-68B6B72699C7) # Windows
            check_if_root windows "$fs" "$dev$num"
            ;;
        *) # Linux does not respect the Partition GUID code at all
            check_if_root linux "$fs" "$dev$num"
            ;;
    esac

    if [ -n "$DETECTED_OSFAMILY" ]; then
        export DETECTED_ROOT_PARTITION="$num"
    fi
}

detect_image_properties() {
    local dev table
    dev="$1"

    table=$(get_partition_table "$dev")
    pttype=$(get_partition_table_type "$table")

    partitions=$(tail -n +3 <<< "$table")
    for part in $partitions; do
        "examine_${pttype}_partition" "$dev" "$part"
        if [ -n "$DETECTED_OSFAMILY" -a -n "$DETECTED_ROOT_PARTITION" ]; then
            break
        fi
    done
}

Nikos Skalkotos's avatar
Nikos Skalkotos committed
992
993
994
995
996
997
998
999
1000
1001
1002
encode_array() {
    local array_name cnt tmp
    array_name="$1"

    cnt=0
    tmp="${array_name}[@]"
    for i in "${!tmp}"; do
        declare -g ${array_name}_$cnt=$i
        : $((cnt++))
    done

1003
    declare -g ${array_name}_COUNT=$cnt
Nikos Skalkotos's avatar
Nikos Skalkotos committed
1004
1005
}

Nikos Skalkotos's avatar
Nikos Skalkotos committed
1006
cleanup() {
1007
    # if something fails here, it shouldn't call cleanup again...
Nikos Skalkotos's avatar
Nikos Skalkotos committed
1008
1009
1010
1011
1012
1013
1014
1015
    trap - EXIT

    if [ ${#CLEANUP[*]} -gt 0 ]; then
        LAST_ELEMENT=$((${#CLEANUP[*]}-1))
        REVERSE_INDEXES=$(seq ${LAST_ELEMENT} -1 0)
        for i in $REVERSE_INDEXES; do
            # If something fails here, it's better to retry it for a few times
            # before we give up with an error. This is needed for kpartx when
1016
            # dealing with NTFS partitions mounted through fuse. umount is not
Nikos Skalkotos's avatar
Nikos Skalkotos committed
1017
            # synchronous and may return while the partition is still busy. A
1018
            # premature attempt to delete partition mappings through kpartx on
1019
            # a device that hosts previously mounted NTFS partition may fail
1020
1021
1022
            # with a `device-mapper: remove ioctl failed: Device or resource
            # busy' error. A sensible workaround for this is to wait for a
            # while and then try again.
Nikos Skalkotos's avatar
Nikos Skalkotos committed
1023
1024
1025
1026
1027
1028
1029
            local cmd=${CLEANUP[$i]}
            $cmd || for interval in 0.25 0.5 1 2 4; do
            echo "Command $cmd failed!"
            echo "I'll wait for $interval secs and will retry..."
            sleep $interval
            $cmd && break
        done
1030
1031
1032
1033
	if [ "$?" != "0" ]; then
            echo "Giving Up..."
            exit 1;
        fi
Nikos Skalkotos's avatar
Nikos Skalkotos committed
1034
1035
1036
1037
    done
  fi
}

1038
task_cleanup() {
Nikos Skalkotos's avatar
Nikos Skalkotos committed
1039
    local rc=$?
1040
1041

    if [ $rc -eq 0 ]; then
1042
       report_task_end
1043
1044
    else
       report_error
1045
1046
1047
1048
1049
       # If in debug mode, drop to a shell and let a developer figure it out
       if grep -q snf_image_debug_helper /proc/cmdline; then
           echo "Failed. In debug mode, dropping to a shell."
           start_debug_shell
       fi
1050
1051
1052
1053
    fi
    cleanup
}

1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
check_yes_no() {
    local name value
    name="${1}"

    if [ -z "${!name+dummy}" ]; then
        # variable is not defined at all
        return 1
    fi


    # lowercase value
    value="${!name,,}"

    # Remove the SNF_IMAGE_PROPERTY_ prefix from the name when displaying it.
    name="${name#SNF_IMAGE_PROPERTY_}"

    if [[ "$value" =~ ^(yes|true|on|1|set)$ ]]; then
        return 0
    elif  [[ "$value" =~ ^(no|false|off|0|unset)$ ]]; then
        return 1
    elif [ -z "$value" ]; then
        warn "Variable \`$name' defined but empty. Will treat this as a \`NO'"
        return 1
    else
        warn "Invalid value for variable: \`$name' (=$value). Will treat this as a \`YES'"
        return 0
    fi
}

1083
check_if_excluded() {
Nikos Skalkotos's avatar
Nikos Skalkotos committed
1084
1085
1086
    local name exclude
    name="$(tr [a-z] [A-Z] <<< ${PROGNAME:2})"
    exclude="SNF_IMAGE_PROPERTY_EXCLUDE_TASK_${name}"
1087
    if check_yes_no "$exclude"; then
1088
        warn "Task ${PROGNAME:2} was excluded and will not run."
1089
1090
1091
1092
1093
1094
        exit 0
    fi

    return 0
}

1095
check_if_mounted_excluded() {
1096
    if check_yes_no SNF_IMAGE_PROPERTY_EXCLUDE_MOUNTED_TASKS; then
1097
1098
1099
1100
1101
1102
1103
        warn "Task ${PROGNAME:2} was excluded and will not run."
        exit 0
    fi

    return 0
}

1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
check_if_overwritten() {
    local script ret

    if ! check_yes_no SNF_IMAGE_PROPERTY_ALLOW_MOUNTED_TASK_OVERWRITING; then
        return 0
    fi
    script=$(find -L $SNF_IMAGE_TARGET/root/snf-image/helper \
             -iname "overwrite_task_${PROGNAME:2}" -type f 2>/dev/null) || true
    if [ -x "$script" ]; then
        warn "Task ${PROGNAME:2} was overwritten by file: \`${script:${#SNF_IMAGE_TARGET}}'"
        export OVERWRITTEN_TASK="$script"
        set +e
        $OVERWRITTEN_TASK "pre-exec"
        ret=$?
        set -e
        if [ $ret -eq 101 ]; then
            # Reserve 101 for the case where the original task should be
            # executed after the task that overwrote it has finished
            warn "Running task ${PROGNAME:2} after \`${script:${#SNF_IMAGE_TARGET}}' returned 101"

            # Overwrite exit to make sure the script is executed again at the
            # end. This is error-prone, since you'll always have to use exit on
            # normal termination (which we actually did before this) but I
            # couldn't think of a better way. Adding it in the task_cleanup()
            # is not an option because you can't control the failures well.
            exit() {
                local rc=$1
                if [ $rc -eq 0 ]; then
                    warn "Running \`${OVERWRITTEN_TASK:${#SNF_IMAGE_TARGET}}' again after task ${PROGNAME:2}"
                    $OVERWRITTEN_TASK "post-exec"
                fi
                builtin exit $rc
            }
        else
            exit $ret
        fi
    fi
}

1143
return_success() {
1144
    send_result_${HYPERVISOR} "SUCCESS"
1145
1146
}

1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
networking_opts() {
    local usage="$0 [-i | -f | -n  <index>] [-4 (dhcp|static)] [-6 (dhcp|slaac|slaac_dhcp)]"
    ipv4=none
    ipv6=none
    index=
    initialize=no
    finalize=no

    while getopts "h?ifn:4:6:" opt; do
        case "$opt" in
        h|\?)
            echo $usage >&2
            exit 0
            ;;
        i)  initialize=yes
            ;;
        f)  finalize=yes
            ;;
        n)  index=$OPTARG
            ;;
        4)  ipv4=$OPTARG
            ;;
        6)  ipv6=$OPTARG
            ;;
        esac
    done

    if [ -z "$index" -a "$initialize" = no -a "$finalize" = no ]; then
        log_error "Either -i, -f or -n must be specified"
    fi

    if [ "$initialize" = yes -a "$finalize" = yes ]; then
        log_error "-i and -f should not be both specified"
    fi

    if [[ "$index" && ("$initialize" == yes || "$finalize" == yes) ]]; then
        log_error "-n cannot be combined with -i or -f"
    fi

    if [[ ! "$ipv4" =~ (dhcp|static|none) ]]; then
        log_error "invalid ipv4: $ipv4"
    fi

    if [[ ! "$ipv6" =~ (dhcp|slaac|slaac_dhcp|none) ]]; then
        log_error "invalid ipv6: $ipv6"
    fi

    if [[ (! "$index" =~ ^[0-9]+$) && "$prepare" = no ]]; then
        log_error "invalid index: $index"
    fi
}

1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
check_windows_computer_name() {
    local name length msg matched pipefail

    name="${1}"
    length=$(wc -c <<< "$name")

    # ComputerName restrictions are documented here:
    #   https://technet.microsoft.com/en-us/library/ff715676.aspx

    msg="Invalid Windows ComputerName: \`$name'."

    # Check if the length of the string exceeds the 15 bytes
    if [ $length -gt 15 ]; then
        log_error "$msg It's size is bigger (=$length) than 15 bytes."
    fi

1215
    pipefail=$(set -o | grep pipefail | gawk '{ print $2 }')
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
    set -o pipefail
    set +e
    # We only check for invalid ascii characters but this is OK. The name
    # derives from the instance's name and Ganeti seems to only allows ascii
    # characters for instance names.
    invalid_char="$(grep -o -P \
        '[\x00-\x2c\x2e-\x2f\x3a-\x40\x5b-\x5e\x60\x7b-\x7e]' <<< "$name" \
        | head -1)"
    # If grep fails, the pipefail option will make $matched host the return
    # code of grep
    matched=$?
    set -e
    if [ "$pipefail" = off ]; then
        set +o pipefail
    fi

    if [ $matched -eq 0 ]; then
        log_error "$msg Contains invalid character: \`$(printf "%q" "$invalid_char")'."
    fi

    if [[ "$name" =~ ^([0-9])+$ ]]; then
        log_error "$msg Contains only numbers (0-9)."
    fi
}

1241
trap cleanup EXIT
1242
set -o pipefail
1243

1244
1245
1246
1247
STDERR_FILE=$(mktemp)
add_cleanup rm -f "$STDERR_FILE"
exec 2> >(tee -a "$STDERR_FILE" >&2)

Nikos Skalkotos's avatar
Nikos Skalkotos committed
1248
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :