Statistics
| Branch: | Tag: | Revision:

root / snf-image-helper / common.sh @ 365b2ed3

History | View | Annotate | Download (14.9 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
umount_all() {
491
    local target mpoints
492
    target="$1"
493

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

    
497
    for mpoint in $mpoints; do
498
        umount $mpoint
499
    done
500
}
501

    
502
get_ufstype() {
503
    local device ufs
504

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

    
508
    case "$ufs" in
509
        UFS1)
510
            echo 44bsd
511
            ;;
512
        UFS2)
513
            echo ufs2
514
            ;;
515
        *)
516
            log_error "Unsupported UFS type: \`$ufs' in device $device"
517
            echo ""
518
            ;;
519
    esac
520
}
521

    
522
cleanup() {
523
    # if something fails here, it shouldn't call cleanup again...
524
    trap - EXIT
525

    
526
    if [ ${#CLEANUP[*]} -gt 0 ]; then
527
        LAST_ELEMENT=$((${#CLEANUP[*]}-1))
528
        REVERSE_INDEXES=$(seq ${LAST_ELEMENT} -1 0)
529
        for i in $REVERSE_INDEXES; do
530
            # If something fails here, it's better to retry it for a few times
531
            # before we give up with an error. This is needed for kpartx when
532
            # dealing with ntfs partitions mounted through fuse. umount is not
533
            # synchronous and may return while the partition is still busy. A
534
            # premature attempt to delete partition mappings through kpartx on
535
            # a device that hosts previously mounted ntfs partition may fail
536
            # with a `device-mapper: remove ioctl failed: Device or resource
537
            # busy' error. A sensible workaround for this is to wait for a
538
            # while and then try again.
539
            local cmd=${CLEANUP[$i]}
540
            $cmd || for interval in 0.25 0.5 1 2 4; do
541
            echo "Command $cmd failed!"
542
            echo "I'll wait for $interval secs and will retry..."
543
            sleep $interval
544
            $cmd && break
545
        done
546
	if [ "$?" != "0" ]; then
547
            echo "Giving Up..."
548
            exit 1;
549
        fi
550
    done
551
  fi
552
}
553

    
554
task_cleanup() {
555
    local rc=$?
556

    
557
    if [ $rc -eq 0 ]; then
558
       report_task_end
559
    else
560
       report_error
561
    fi
562

    
563
    cleanup
564
}
565

    
566
check_if_excluded() {
567
    local name exclude
568
    name="$(tr [a-z] [A-Z] <<< ${PROGNAME:2})"
569
    exclude="SNF_IMAGE_PROPERTY_EXCLUDE_TASK_${name}"
570
    if [ -n "${!exclude}" ]; then
571
        warn "Task ${PROGNAME:2} was excluded and will not run."
572
        exit 0
573
    fi
574

    
575
    return 0
576
}
577

    
578
return_success() {
579
    send_result_${HYPERVISOR} "SUCCESS"
580
}
581

    
582
trap cleanup EXIT
583
set -o pipefail
584

    
585
STDERR_FILE=$(mktemp)
586
add_cleanup rm -f "$STDERR_FILE"
587
exec 2> >(tee -a "$STDERR_FILE" >&2)
588

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