Statistics
| Branch: | Tag: | Revision:

root / snf-image-helper / common.sh.in @ 1de1eff5

History | View | Annotate | Download (18 kB)

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

    
19
PROGNAME=$(basename $0)
20

    
21
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
22

    
23
# Programs
24
XMLSTARLET=xmlstarlet
25
TUNE2FS=tune2fs
26
RESIZE2FS=resize2fs
27
PARTED=parted
28
SFDISK=sfdisk
29
MKSWAP=mkswap
30
BLKID=blkid
31
BLOCKDEV=blockdev
32
REGLOOKUP=reglookup
33
CHNTPW=chntpw
34
SGDISK=sgdisk
35
GROWFS_UFS=growfs.ufs
36
DUMPFS_UFS=dumpfs.ufs
37
GROWFS_OPENBSD=growfs.openbsd
38
DUMPFS_OPENBSD=dumpfs.openbsd
39
DATE="date -u" # Time in UTC
40
EATMYDATA=eatmydata
41
MOUNT="mount -n"
42

    
43
CLEANUP=( )
44
ERRORS=( )
45
WARNINGS=( )
46

    
47
MSG_TYPE_TASK_START="TASK_START"
48
MSG_TYPE_TASK_END="TASK_END"
49

    
50
STDERR_LINE_SIZE=10
51

    
52
add_cleanup() {
53
    local cmd=""
54
    for arg; do cmd+=$(printf "%q " "$arg"); done
55
    CLEANUP+=("$cmd")
56
}
57

    
58
close_fd() {
59
    local fd=$1
60

    
61
    exec {fd}>&-
62
}
63

    
64
send_result_kvm() {
65
    echo "$@" > /dev/ttyS1
66
}
67

    
68
send_monitor_message_kvm() {
69
    echo "$@" > /dev/ttyS2
70
}
71

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

    
76
send_monitor_message_xen() {
77
    #Broadcast the message
78
    echo "$@" | socat "STDIO" "UDP-DATAGRAM:${BROADCAST}:${MONITOR_PORT},broadcast"
79
}
80

    
81
prepare_helper() {
82
	local cmdline item key val hypervisor domid
83

    
84
	read -a cmdline	 < /proc/cmdline
85
	for item in "${cmdline[@]}"; do
86
            key=$(cut -d= -f1 <<< "$item")
87
            val=$(cut -d= -f2 <<< "$item")
88
            if [ "$key" = "hypervisor" ]; then
89
                hypervisor="$val"
90
            fi
91
            if [ "$key" = "rules_dev" ]; then
92
                export RULES_DEV="$val"
93
            fi
94
            if [ "$key" = "helper_ip" ]; then
95
                export IP="$val"
96
                export NETWORK="$IP/24"
97
                export BROADCAST="${IP%.*}.255"
98
            fi
99
            if [ "$key" = "monitor_port" ]; then
100
                export MONITOR_PORT="$val"
101
            fi
102
	done
103

    
104
    case "$hypervisor" in
105
    kvm)
106
        HYPERVISOR=kvm
107
        ;;
108
    xen-hvm|xen-pvm)
109
        if [ -z "$IP" ]; then
110
            echo "ERROR: \`helper_ip' not defined or empty" >&2
111
            exit 1
112
        fi
113
        if [ -z "$MONITOR_PORT" ]; then
114
            echo "ERROR: \`monitor_port' not defined or empty" >&2
115
            exit 1
116
        fi
117
        $MOUNT -t xenfs xenfs /proc/xen
118
        ip addr add "$NETWORK" dev eth0
119
        ip link set eth0 up
120
        ip route add default dev eth0
121
        export DOMID=$(xenstore-read domid)
122
        HYPERVISOR=xen
123
        ;;
124
    *)
125
        echo "ERROR: Unknown hypervisor: \`$hypervisor'" >&2
126
        exit 1
127
        ;;
128
    esac
129

    
130
    export HYPERVISOR
131
}
132

    
133
report_error() {
134
    msg=""
135
    if [ ${#ERRORS[*]} -eq 0 ]; then
136
        # No error message. Print stderr
137
        local lines stderr
138
        stderr="$(tail --lines=${STDERR_LINE_SIZE} "$STDERR_FILE")"
139
        lines=$(wc -l <<< "$stderr")
140
        msg="STDERR:${lines}:$stderr"
141
    else
142
        for line in "${ERRORS[@]}"; do
143
            msg+="ERROR:$line"$'\n'
144
        done
145
    fi
146

    
147
    send_monitor_message_${HYPERVISOR} "$msg"
148
}
149

    
150
log_error() {
151
    ERRORS+=("$*")
152

    
153
    send_result_${HYPERVISOR} "ERROR: $@"
154

    
155
    # Use return instead of exit. The set -x options will terminate the script
156
    # but will also trigger ERR traps if defined.
157
    return 1
158
}
159

    
160
warn() {
161
    echo "Warning: $@" >&2
162
    send_monitor_message_${HYPERVISOR} "WARNING: $@"
163
}
164

    
165
report_task_start() {
166
    send_monitor_message_${HYPERVISOR} "$MSG_TYPE_TASK_START:${PROGNAME:2}"
167
}
168

    
169
report_task_end() {
170
    send_monitor_message_${HYPERVISOR} "$MSG_TYPE_TASK_END:${PROGNAME:2}"
171
}
172

    
173
system_poweroff() {
174
    while [ 1 ]; do
175
        # Credits to psomas@grnet.gr for this ...
176
        echo o > /proc/sysrq-trigger
177
        sleep 1
178
    done
179
}
180

    
181
get_base_distro() {
182
    local root_dir=$1
183

    
184
    if [ -e "$root_dir/etc/debian_version" ]; then
185
        echo "debian"
186
    elif [ -e "$root_dir/etc/redhat-release" ]; then
187
        echo "redhat"
188
    elif [ -e "$root_dir/etc/slackware-version" ]; then
189
        echo "slackware"
190
    elif [ -e "$root_dir/etc/SuSE-release" ]; then
191
        echo "suse"
192
    elif [ -e "$root_dir/etc/gentoo-release" ]; then
193
        echo "gentoo"
194
    elif [ -e "$root_dir/etc/arch-release" ]; then
195
        echo "arch"
196
    elif [ -e "$root_dir/etc/freebsd-update.conf" ]; then
197
        echo "freebsd"
198
    elif [ -e "$root_dir/etc/release" ]; then
199
        if grep -i netbsd "$root_dir/etc/release" &> /dev/null; then
200
            echo "netbsd"
201
        else
202
            warn "Unknown Unix flavor."
203
        fi
204
    elif [ -e "$root_dir/etc/motd" ]; then
205
        if grep -i ^openbsd <(head -1 "$root_dir/etc/motd") &> /dev/null; then
206
            echo "openbsd"
207
        else
208
            warn "Unknown Unix flavor"
209
        fi
210
    else
211
        warn "Unknown base distro."
212
    fi
213
}
214

    
215
get_distro() {
216
    local root_dir distro
217
    root_dir=$1
218

    
219
    if [ -e "$root_dir/etc/debian_version" ]; then
220
        distro="debian"
221
        if [ -e ${root_dir}/etc/lsb-release ]; then
222
            ID=$(grep ^DISTRIB_ID= ${root_dir}/etc/lsb-release | cut -d= -f2)
223
            if [ "x$ID" = "xUbuntu" ]; then
224
                distro="ubuntu"
225
            fi
226
        fi
227
        echo "$distro"
228
    elif [ -e "$root_dir/etc/fedora-release" ]; then
229
        echo "fedora"
230
    elif [ -e "$root_dir/etc/centos-release" ]; then
231
        echo "centos"
232
    elif [ -e "$root_dir/etc/redhat-release" ]; then
233
        echo "redhat"
234
    elif [ -e "$root_dir/etc/slackware-version" ]; then
235
        echo "slackware"
236
    elif [ -e "$root_dir/etc/SuSE-release" ]; then
237
        echo "suse"
238
    elif [ -e "$root_dir/etc/gentoo-release" ]; then
239
        echo "gentoo"
240
    elif [ -e "$root_dir/etc/arch-release" ]; then
241
        echo "arch"
242
    elif [ -e "$root_dir/etc/freebsd-update.conf" ]; then
243
        echo "freebsd"
244
    elif [ -e "$root_dir/etc/release" ]; then
245
        if grep -in netbsd "$root_dir/etc/release" &> /dev/null; then
246
            echo "netbsd"
247
        else
248
            warn "Unknown Unix flavor"
249
        fi
250
    elif [ -e "$root_dir/etc/motd" ]; then
251
        if grep -i ^openbsd <(head -1 "$root_dir/etc/motd") &> /dev/null; then
252
            echo "openbsd"
253
        else
254
            warn "Unknown Unix flavor"
255
        fi
256
    else
257
        warn "Unknown distro."
258
    fi
259
}
260

    
261
get_partition_table() {
262
    local dev output
263
    dev="$1"
264
    # If the partition table is gpt then parted will raise an error if the
265
    # secondary gpt is not it the end of the disk, and a warning that has to
266
    # do with the "Last Usable LBA" entry in gpt.
267
    if ! output="$("$PARTED" -s -m "$dev" unit s print | grep -E -v "^(Warning|Error): ")"; then
268
        log_error "Unable to read partition table for device \`${dev}'. The image seems corrupted."
269
    fi
270

    
271
    echo "$output"
272
}
273

    
274
get_partition_table_type() {
275
    local ptable dev field
276
    ptable="$1"
277

    
278
    dev="$(sed -n 2p <<< "$ptable")"
279
    IFS=':' read -ra field <<< "$dev"
280

    
281
    echo "${field[5]}"
282
}
283

    
284
get_partition_count() {
285
    local ptable="$1"
286

    
287
    expr $(echo "$ptable" | wc -l) - 2
288
}
289

    
290
get_partition_by_num() {
291
    local ptable="$1"
292
    local id="$2"
293

    
294
    grep "^$id:" <<< "$ptable"
295
}
296

    
297
get_last_partition() {
298
    local ptable="$1"
299

    
300
    echo "$ptable" | tail -1
301
}
302

    
303
is_extended_partition() {
304
    local dev="$1"
305
    local part_num="$2"
306

    
307
    id=$($SFDISK --force --print-id "$dev" "$part_num")
308
    if [ "$id" = "5" -o "$id" = "f" ]; then
309
        echo "yes"
310
    else
311
        echo "no"
312
    fi
313
}
314

    
315
get_extended_partition() {
316
    local ptable dev
317
    ptable="$1"
318
    dev="$(echo "$ptable" | sed -n 2p | cut -d':' -f1)"
319

    
320
    tail -n +3 <<< "$ptable" | while read line; do
321
        part_num=$(cut -d':' -f1 <<< "$line")
322
        if [ $(is_extended_partition "$dev" "$part_num") == "yes" ]; then
323
            echo "$line"
324
            return 0
325
        fi
326
    done
327
    echo ""
328
}
329

    
330
get_logical_partitions() {
331
    local ptable part_num
332
    ptable="$1"
333

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

    
341
    return 0
342
}
343

    
344
get_last_primary_partition() {
345
    local ptable dev output
346
    ptable="$1"
347
    dev=$(echo "ptable" | sed -n 2p | cut -d':' -f1)
348

    
349
    for i in 4 3 2 1; do
350
        if output=$(grep "^$i:" <<< "$ptable"); then
351
            echo "$output"
352
            return 0
353
        fi
354
    done
355
    echo ""
356
}
357

    
358
get_partition_to_resize() {
359
    local dev table table_type last_part last_part_num extended last_primary \
360
        ext_num prim_num
361
    dev="$1"
362

    
363
    table=$(get_partition_table "$dev")
364
    if [ -z "$table" ]; then
365
        return 0
366
    fi
367

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

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

    
376
    if [ "$table_type" == "msdos" -a $last_part_num -gt 4 ]; then
377
        extended=$(get_extended_partition "$table")
378
        last_primary=$(get_last_primary_partition "$table")
379
        ext_num=$(cut -d: -f1 <<< "$extended")
380
        last_prim_num=$(cut -d: -f1 <<< "$last_primary")
381

    
382
        if [ "$ext_num" != "$last_prim_num" ]; then
383
            echo "$last_prim_num"
384
        else
385
            echo "$last_part_num"
386
        fi
387
    else
388
        echo "$last_part_num"
389
    fi
390
}
391

    
392
create_partition() {
393
    local device="$1"
394
    local part="$2"
395
    local ptype="$3"
396

    
397
    local fields=()
398
    IFS=":;" read -ra fields <<< "$part"
399
    local id="${fields[0]}"
400
    local start="${fields[1]}"
401
    local end="${fields[2]}"
402
    local size="${fields[3]}"
403
    local fs="${fields[4]}"
404
    local name="${fields[5]}"
405
    local flags="${fields[6]//,/ }"
406

    
407
    if [ "$ptype" = "primary" -o "$ptype" = "logical" -o "$ptype" = "extended" ]; then
408
        $PARTED -s -m -- $device mkpart "$ptype" $fs "$start" "$end"
409
        for flag in $flags; do
410
            $PARTED -s -m $device set "$id" "$flag" on
411
        done
412
    else
413
        # For gpt
414
        start=${start:0:${#start}-1} # remove the s at the end
415
        end=${end:0:${#end}-1} # remove the s at the end
416
        $SGDISK -n "$id":"$start":"$end" -t "$id":"$ptype" "$device"
417
    fi
418
}
419

    
420
enlarge_partition() {
421
    local device part ptype new_end fields new_part table logical id
422
    device="$1"
423
    part="$2"
424
    ptype="$3"
425
    new_end="$4"
426

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

    
431
    fields=()
432
    IFS=":;" read -ra fields <<< "$part"
433
    fields[2]="$new_end"
434

    
435
    new_part=""
436
    for ((i = 0; i < ${#fields[*]}; i = i + 1)); do
437
        new_part="$new_part":"${fields[$i]}"
438
    done
439
    new_part=${new_part:1}
440

    
441
    # If this is an extended partition, removing it will also remove the
442
    # logical partitions it contains. We need to save them for later.
443
    if [ "$ptype" = "extended" ]; then
444
        table="$(get_partition_table "$device")"
445
        logical="$(get_logical_partitions "$table")"
446
    fi
447

    
448
    id=${fields[0]}
449
    $PARTED -s -m "$device" rm "$id"
450
    create_partition "$device" "$new_part" "$ptype"
451

    
452
    if [ "$ptype" = "extended" ]; then
453
        # Recreate logical partitions
454
        echo "$logical" | while read logical_part; do
455
            create_partition "$device" "$logical_part" "logical"
456
        done
457
    fi
458
}
459

    
460
get_last_free_sector() {
461
    local dev unit last_line ptype
462
    dev="$1"
463
    unit="$2"
464

    
465
    if [ -n "$unit" ]; then
466
        unit="unit $unit"
467
    fi
468

    
469
    last_line="$($PARTED -s -m "$dev" "$unit" print free | tail -1)"
470
    ptype="$(cut -d: -f 5 <<< "$last_line")"
471

    
472
    if [ "$ptype" = "free;" ]; then
473
        echo "$last_line"
474
    fi
475
}
476

    
477
get_unattend() {
478
    local target exists
479
    target="$1"
480

    
481
    # Workaround to search for $target/Unattend.xml in an case insensitive way.
482
    exists=$(find "$target"/ -maxdepth 1 -iname unattend.xml)
483
    if [ $(wc -l <<< "$exists") -gt 1 ]; then
484
        log_error "Found multiple Unattend.xml files in the image:" $exists
485
    fi
486

    
487
    echo "$exists"
488
}
489

    
490
disklabel2linux() {
491
    local partition i p
492
    partition="$1"
493

    
494
    i=4
495
    # Partition 'c' traditionally used to describe the entire disk is not
496
    # mapped to /dev/sda7 by the kernel
497
    for p in a b {d..p}; do
498
        let i++
499
        if [ "$p" = "$partition" ]; then
500
           echo "$i"
501
           return 0
502
        fi
503
    done
504

    
505
    log_error "Invalid BSD partition label: \`$partition'"
506
}
507

    
508
mount_all() {
509
    local osfamily target fs device fstab entry duid opts types num
510
    osfamily="$1"
511
    device="$2"
512
    target="$3"
513

    
514
    case "$osfamily" in
515
    linux)
516
        fs="ext[234]|msdos|vfat|ntfs"
517
        ;;
518
    freebsd)
519
        fs="ufs|msdosfs|ntfs"
520
        ;;
521
    openbsd)
522
        fs="ffs|msdos|ntfs|ext2fs"
523
        ;;
524
    netbsd)
525
        fs="ffs|ufs|msdos|ext2fs|ntfs"
526
        ;;
527
    *)
528
        log_error "Unsupported osfamily: \`$osfamily'"
529
        ;;
530
    esac
531

    
532
    fstab="$(grep -v ^\# "${target}/etc/fstab" | awk "{ if (match(\$3, \"$fs\")) { print \$2,\$1,\$3 } }" | sort -bd)"
533
    # <mpoint> <device> <fs>
534
    while read -ra entry; do
535
        # Skip root. It is already mounted
536
        if [ "${entry[0]}" = "/" ]; then
537
            continue
538
        fi
539

    
540
        opts="rw"
541
        types="auto"
542

    
543
        if [ "$osfamily" = linux ]; then
544
            # Linux persistent block device naming
545
            if [[ ${entry[1]} =~ ^(LABEL|UUID)= ]]; then
546
                entry[1]=$(findfs "${entry[1]}")
547
            else
548
                log_error "fstab contains non-persistent block device names"
549
            fi
550
        else
551
            if [[ "$osfamily" =~ ^(open|net)bsd$ ]]; then
552
                # OpenBSD DUIDs device naming
553
                if [[ "${entry[1]}" =~ ^[a-f0-9]{16}\.[a-p]$ ]]; then
554
                    duid="$(@scriptsdir@/disklabel.py --get-duid "$device")"
555

    
556
                    if [[ ! "${entry[1]}" =~ ^$duid ]]; then
557
                        warn "fstab refers to unknown DUID: \`$duid'"
558
                        continue
559
                    fi
560
                fi
561
                num="$(disklabel2linux "${entry[1]: -1}")"
562
                if [ "${entry[2]}" = ffs -o "$entry[2]" = ufs ]; then
563
                    types="ufs"
564
                    opts="ufstype=44bsd,rw"
565
                fi
566
            else # FreeBSD
567
                # We do not support FreeBSD labels for now
568
                if [[ "${entry[1]}" =~ ^/dev/(ufs|label)/ ]]; then
569
                    log_error "fstab contains FreeBSD labels. We currently don't support them"
570
                fi
571
                num="${entry[1]: -1}"
572
                if [ "${entry[2]}" = ufs ]; then
573
                    types="ufs"
574
                    opts="ufstype=ufs2,rw"
575
                fi
576
            fi
577
            entry[1]="${device}${num}"
578
        fi
579

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

    
591
    done <<< "$fstab"
592
}
593

    
594
umount_all() {
595
    local target mpoints
596
    target="$1"
597

    
598
    # Unmount file systems mounted under directory `target'
599
    mpoints="$({ awk "{ if (match(\$2, \"^$target\")) { print \$2 } }" < /proc/mounts; } | sort -rbd | uniq)"
600

    
601
    for mpoint in $mpoints; do
602
        umount $mpoint
603
    done
604
}
605

    
606
get_ufstype() {
607
    local device ufs
608

    
609
    device="$1"
610
    ufs="$($DUMPFS_UFS "$device" | head -1 | awk -F "[()]" '{ for (i=2; i<NF; i+=2) print $i }')"
611

    
612
    case "$ufs" in
613
        UFS1)
614
            echo 44bsd
615
            ;;
616
        UFS2)
617
            echo ufs2
618
            ;;
619
        *)
620
            log_error "Unsupported UFS type: \`$ufs' in device $device"
621
            echo ""
622
            ;;
623
    esac
624
}
625

    
626
cleanup() {
627
    # if something fails here, it shouldn't call cleanup again...
628
    trap - EXIT
629

    
630
    if [ ${#CLEANUP[*]} -gt 0 ]; then
631
        LAST_ELEMENT=$((${#CLEANUP[*]}-1))
632
        REVERSE_INDEXES=$(seq ${LAST_ELEMENT} -1 0)
633
        for i in $REVERSE_INDEXES; do
634
            # If something fails here, it's better to retry it for a few times
635
            # before we give up with an error. This is needed for kpartx when
636
            # dealing with ntfs partitions mounted through fuse. umount is not
637
            # synchronous and may return while the partition is still busy. A
638
            # premature attempt to delete partition mappings through kpartx on
639
            # a device that hosts previously mounted ntfs partition may fail
640
            # with a `device-mapper: remove ioctl failed: Device or resource
641
            # busy' error. A sensible workaround for this is to wait for a
642
            # while and then try again.
643
            local cmd=${CLEANUP[$i]}
644
            $cmd || for interval in 0.25 0.5 1 2 4; do
645
            echo "Command $cmd failed!"
646
            echo "I'll wait for $interval secs and will retry..."
647
            sleep $interval
648
            $cmd && break
649
        done
650
	if [ "$?" != "0" ]; then
651
            echo "Giving Up..."
652
            exit 1;
653
        fi
654
    done
655
  fi
656
}
657

    
658
task_cleanup() {
659
    local rc=$?
660

    
661
    if [ $rc -eq 0 ]; then
662
       report_task_end
663
    else
664
       report_error
665
    fi
666

    
667
    cleanup
668
}
669

    
670
check_if_excluded() {
671
    local name exclude
672
    name="$(tr [a-z] [A-Z] <<< ${PROGNAME:2})"
673
    exclude="SNF_IMAGE_PROPERTY_EXCLUDE_TASK_${name}"
674
    if [ -n "${!exclude}" ]; then
675
        warn "Task ${PROGNAME:2} was excluded and will not run."
676
        exit 0
677
    fi
678

    
679
    return 0
680
}
681

    
682
return_success() {
683
    send_result_${HYPERVISOR} "SUCCESS"
684
}
685

    
686
trap cleanup EXIT
687
set -o pipefail
688

    
689
STDERR_FILE=$(mktemp)
690
add_cleanup rm -f "$STDERR_FILE"
691
exec 2> >(tee -a "$STDERR_FILE" >&2)
692

    
693
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :