Statistics
| Branch: | Tag: | Revision:

root / snf-image-helper / common.sh @ e777f9d5

History | View | Annotate | Download (12.4 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
DATE="date -u" # Time in UTC
35
EATMYDATA=eatmydata
36
MOUNT="mount -n"
37

    
38
CLEANUP=( )
39
ERRORS=( )
40
WARNINGS=( )
41

    
42
MSG_TYPE_TASK_START="TASK_START"
43
MSG_TYPE_TASK_END="TASK_END"
44

    
45
STDERR_LINE_SIZE=10
46

    
47
add_cleanup() {
48
    local cmd=""
49
    for arg; do cmd+=$(printf "%q " "$arg"); done
50
    CLEANUP+=("$cmd")
51
}
52

    
53
close_fd() {
54
    local fd=$1
55

    
56
    exec {fd}>&-
57
}
58

    
59
prepare_helper() {
60
	local cmdline item key val hypervisor domid
61

    
62
	read -a cmdline	 < /proc/cmdline
63
	for item in "${cmdline[@]}"; do
64
        key=$(cut -d= -f1 <<< "$item")
65
        val=$(cut -d= -f2 <<< "$item")
66
        if [ "$key" = "hypervisor" ]; then
67
            hypervisor="$val"
68
        fi
69
        if [ "$key" = "rules_dev" ]; then
70
            export RULES_DEV="$val"
71
        fi
72
	done
73

    
74
    case "$hypervisor" in
75
    kvm)
76
        exec {RESULT_FD}> /dev/ttyS1
77
        add_cleanup close_fd ${RESULT_FD}
78
        exec {MONITOR_FD}> /dev/ttyS2
79
        add_cleanup close_fd ${MONITOR_FD}
80
        ;;
81
    xen-hvm|xen-pvm)
82
		$MOUNT -t xenfs xenfs /proc/xen
83
		iptables -P OUTPUT DROP
84
		ip6tables -P OUTPUT DROP
85
		ip link set eth0 up
86
        domid=$(xenstore-read domid)
87
        exec {RESULT_FD}> >(xargs -l1 xenstore-write /local/domain/0/snf-image-helper/$domid)
88
        add_cleanup close_fd ${RESULT_FD}
89
        exec {MONITOR_FD}> >(socat STDIN INTERFACE:eth0)
90
        add_cleanup close_fd ${MONITOR_FD}
91
        ;;
92
    *)
93
        echo "ERROR: Unknown hypervisor: \`$hypervisor'" >&2
94
        exit 1
95
        ;;
96
    esac
97

    
98
    export RESULT_FD MONITOR_FD
99
}
100

    
101
report_error() {
102
    if [ ${#ERRORS[*]} -eq 0 ]; then
103
        # No error message. Print stderr
104
        local lines
105
        lines=$(tail --lines=${STDERR_LINE_SIZE} "$STDERR_FILE" | wc -l)
106
        echo -n "STDERR:${lines}:" >&${MONITOR_FD}
107
        tail --lines=$lines  "$STDERR_FILE" >&${MONITOR_FD}
108
    else
109
        for line in "${ERRORS[@]}"; do
110
            echo "ERROR:$line" >&${MONITOR_FD}
111
        done
112
    fi
113
}
114

    
115
log_error() {
116
    ERRORS+=("$*")
117
    echo "ERROR: $@" >&2
118
    echo "ERROR: $@" >&${MONITOR_FD}
119

    
120
    echo "ERROR" >&${RUSULT_FD}
121

    
122
    # Use return instead of exit. The set -x options will terminate the script
123
    # but will also trigger ERR traps if defined.
124
    return 1
125
}
126

    
127
warn() {
128
    echo "Warning: $@" >&2
129
	echo "WARNING: $@" >&${MONITOR_FD}
130
}
131

    
132
report_task_start() {
133
    echo "$MSG_TYPE_TASK_START:${PROGNAME:2}" >&${MONITOR_FD}
134
}
135

    
136
report_task_end() {
137
    echo "$MSG_TYPE_TASK_END:${PROGNAME:2}" >&${MONITOR_FD}
138
}
139

    
140
system_poweroff() {
141
    while [ 1 ]; do
142
        # Credits to psomas@grnet.gr for this ...
143
        echo o > /proc/sysrq-trigger
144
        sleep 1
145
    done
146
}
147

    
148
get_base_distro() {
149
    local root_dir=$1
150

    
151
    if [ -e "$root_dir/etc/debian_version" ]; then
152
        echo "debian"
153
    elif [ -e "$root_dir/etc/redhat-release" ]; then
154
        echo "redhat"
155
    elif [ -e "$root_dir/etc/slackware-version" ]; then
156
        echo "slackware"
157
    elif [ -e "$root_dir/etc/SuSE-release" ]; then
158
        echo "suse"
159
    elif [ -e "$root_dir/etc/gentoo-release" ]; then
160
        echo "gentoo"
161
    elif [ -e "$root_dir/etc/arch-release" ]; then
162
        echo "arch"
163
    else
164
        warn "Unknown base distro."
165
    fi
166
}
167

    
168
get_distro() {
169
    local root_dir distro
170
    root_dir=$1
171

    
172
    if [ -e "$root_dir/etc/debian_version" ]; then
173
        distro="debian"
174
        if [ -e ${root_dir}/etc/lsb-release ]; then
175
            ID=$(grep ^DISTRIB_ID= ${root_dir}/etc/lsb-release | cut -d= -f2)
176
            if [ "x$ID" = "xUbuntu" ]; then
177
                distro="ubuntu"
178
            fi
179
        fi
180
        echo "$distro"
181
    elif [ -e "$root_dir/etc/fedora-release" ]; then
182
        echo "fedora"
183
    elif [ -e "$root_dir/etc/centos-release" ]; then
184
        echo "centos"
185
    elif [ -e "$root_dir/etc/redhat-release" ]; then
186
        echo "redhat"
187
    elif [ -e "$root_dir/etc/slackware-version" ]; then
188
        echo "slackware"
189
    elif [ -e "$root_dir/etc/SuSE-release" ]; then
190
        echo "suse"
191
    elif [ -e "$root_dir/etc/gentoo-release" ]; then
192
        echo "gentoo"
193
    elif [ -e "$root_dir/etc/arch-release" ]; then
194
        echo "arch"
195
    else
196
        warn "Unknown distro."
197
    fi
198
}
199

    
200

    
201
get_partition_table() {
202
    local dev output
203
    dev="$1"
204
    # If the partition table is gpt then parted will raise an error if the
205
    # secondary gpt is not it the end of the disk, and a warning that has to
206
    # do with the "Last Usable LBA" entry in gpt.
207
    if ! output="$("$PARTED" -s -m "$dev" unit s print | grep -E -v "^(Warning|Error): ")"; then
208
        log_error "Unable to read partition table for device \`${dev}'. The image seems corrupted."
209
    fi
210

    
211
    echo "$output"
212
}
213

    
214
get_partition_table_type() {
215
    local ptable dev field
216
    ptable="$1"
217

    
218
    dev="$(sed -n 2p <<< "$ptable")"
219
    IFS=':' read -ra field <<< "$dev"
220

    
221
    echo "${field[5]}"
222
}
223

    
224
get_partition_count() {
225
    local ptable="$1"
226

    
227
    expr $(echo "$ptable" | wc -l) - 2
228
}
229

    
230
get_partition_by_num() {
231
    local ptable="$1"
232
    local id="$2"
233

    
234
    grep "^$id:" <<< "$ptable"
235
}
236

    
237
get_last_partition() {
238
    local ptable="$1"
239

    
240
    echo "$ptable" | tail -1
241
}
242

    
243
is_extended_partition() {
244
    local dev="$1"
245
    local part_num="$2"
246

    
247
    id=$($SFDISK --print-id "$dev" "$part_num")
248
    if [ "$id" = "5" -o "$id" = "f" ]; then
249
        echo "yes"
250
    else
251
        echo "no"
252
    fi
253
}
254

    
255
get_extended_partition() {
256
    local ptable dev
257
    ptable="$1"
258
    dev="$(echo "$ptable" | sed -n 2p | cut -d':' -f1)"
259

    
260
    tail -n +3 <<< "$ptable" | while read line; do
261
        part_num=$(cut -d':' -f1 <<< "$line")
262
        if [ $(is_extended_partition "$dev" "$part_num") == "yes" ]; then
263
            echo "$line"
264
            return 0
265
        fi
266
    done
267
    echo ""
268
}
269

    
270
get_logical_partitions() {
271
    local ptable part_num
272
    ptable="$1"
273

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

    
281
    return 0
282
}
283

    
284
get_last_primary_partition() {
285
    local ptable dev output
286
    ptable="$1"
287
    dev=$(echo "ptable" | sed -n 2p | cut -d':' -f1)
288

    
289
    for i in 4 3 2 1; do
290
        if output=$(grep "^$i:" <<< "$ptable"); then
291
            echo "$output"
292
            return 0
293
        fi
294
    done
295
    echo ""
296
}
297

    
298
get_partition_to_resize() {
299
    local dev table table_type last_part last_part_num extended last_primary \
300
        ext_num prim_num
301
    dev="$1"
302

    
303
    table=$(get_partition_table "$dev")
304

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

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

    
313
    if [ "$table_type" == "msdos" -a $last_part_num -gt 4 ]; then
314
        extended=$(get_extended_partition "$table")
315
        last_primary=$(get_last_primary_partition "$table")
316
        ext_num=$(cut -d: -f1 <<< "$extended")
317
        last_prim_num=$(cut -d: -f1 <<< "$last_primary")
318

    
319
        if [ "$ext_num" != "$last_prim_num" ]; then
320
            echo "$last_prim_num"
321
        else
322
            echo "$last_part_num"
323
        fi
324
    else
325
        echo "$last_part_num"
326
    fi
327
}
328

    
329
create_partition() {
330
    local device="$1"
331
    local part="$2"
332
    local ptype="$3"
333

    
334
    local fields=()
335
    IFS=":;" read -ra fields <<< "$part"
336
    local id="${fields[0]}"
337
    local start="${fields[1]}"
338
    local end="${fields[2]}"
339
    local size="${fields[3]}"
340
    local fs="${fields[4]}"
341
    local name="${fields[5]}"
342
    local flags="${fields[6]//,/ }"
343

    
344
    $PARTED -s -m -- $device mkpart "$ptype" $fs "$start" "$end"
345
    for flag in $flags; do
346
        $PARTED -s -m $device set "$id" "$flag" on
347
    done
348
}
349

    
350
enlarge_partition() {
351
    local device part ptype new_end fields new_part table logical id
352
    device="$1"
353
    part="$2"
354
    ptype="$3"
355
    new_end="$4"
356

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

    
361
    fields=()
362
    IFS=":;" read -ra fields <<< "$part"
363
    fields[2]="$new_end"
364

    
365
    new_part=""
366
    for ((i = 0; i < ${#fields[*]}; i = i + 1)); do
367
        new_part="$new_part":"${fields[$i]}"
368
    done
369
    new_part=${new_part:1}
370

    
371
    # If this is an extended partition, removing it will also remove the
372
    # logical partitions it contains. We need to save them for later.
373
    if [ "$ptype" = "extended" ]; then
374
        table="$(get_partition_table "$device")"
375
        logical="$(get_logical_partitions "$table")"
376
    fi
377

    
378
    id=${fields[0]}
379
    $PARTED -s -m "$device" rm "$id"
380
    create_partition "$device" "$new_part" "$ptype"
381

    
382
    if [ "$ptype" = "extended" ]; then
383
        # Recreate logical partitions
384
        echo "$logical" | while read logical_part; do
385
            create_partition "$device" "$logical_part" "logical"
386
        done
387
    fi
388
}
389

    
390
get_last_free_sector() {
391
    local dev unit last_line ptype
392
    dev="$1"
393
    unit="$2"
394

    
395
    if [ -n "$unit" ]; then
396
        unit="unit $unit"
397
    fi
398

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

    
402
    if [ "$ptype" = "free;" ]; then
403
        echo "$last_line"
404
    fi
405
}
406

    
407
get_unattend() {
408
    local target exists
409
    target="$1"
410

    
411
    # Workaround to search for $target/Unattend.xml in an case insensitive way.
412
    exists=$(find "$target"/ -maxdepth 1 -iname unattend.xml)
413
    if [ $(wc -l <<< "$exists") -gt 1 ]; then
414
        log_error "Found multiple Unattend.xml files in the image:" $exists
415
    fi
416

    
417
    echo "$exists"
418
}
419

    
420
umount_all() {
421
    local target mpoints
422
    target="$1"
423

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

    
427
    for mpoint in $mpoints; do
428
        umount $mpoint
429
    done
430
}
431

    
432
cleanup() {
433
    # if something fails here, it shouldn't call cleanup again...
434
    trap - EXIT
435

    
436
    if [ ${#CLEANUP[*]} -gt 0 ]; then
437
        LAST_ELEMENT=$((${#CLEANUP[*]}-1))
438
        REVERSE_INDEXES=$(seq ${LAST_ELEMENT} -1 0)
439
        for i in $REVERSE_INDEXES; do
440
            # If something fails here, it's better to retry it for a few times
441
            # before we give up with an error. This is needed for kpartx when
442
            # dealing with ntfs partitions mounted through fuse. umount is not
443
            # synchronous and may return while the partition is still busy. A
444
            # premature attempt to delete partition mappings through kpartx on
445
            # a device that hosts previously mounted ntfs partition may fail
446
            # with a `device-mapper: remove ioctl failed: Device or resource
447
            # busy' error. A sensible workaround for this is to wait for a
448
            # while and then try again.
449
            local cmd=${CLEANUP[$i]}
450
            $cmd || for interval in 0.25 0.5 1 2 4; do
451
            echo "Command $cmd failed!"
452
            echo "I'll wait for $interval secs and will retry..."
453
            sleep $interval
454
            $cmd && break
455
        done
456
	if [ "$?" != "0" ]; then
457
            echo "Giving Up..."
458
            exit 1;
459
        fi
460
    done
461
  fi
462
}
463

    
464
task_cleanup() {
465
    local rc=$?
466

    
467
    if [ $rc -eq 0 ]; then
468
       report_task_end
469
    else
470
       report_error
471
    fi
472

    
473
    cleanup
474
}
475

    
476
check_if_excluded() {
477
    local name exclude
478
    name="$(tr [a-z] [A-Z] <<< ${PROGNAME:2})"
479
    exclude="SNF_IMAGE_PROPERTY_EXCLUDE_TASK_${name}"
480
    if [ -n "${!exclude}" ]; then
481
        warn "Task ${PROGNAME:2} was excluded and will not run."
482
        exit 0
483
    fi
484

    
485
    return 0
486
}
487

    
488

    
489
return_success() {
490
    echo SUCCESS >&${RESULT_FD}
491
}
492

    
493
trap cleanup EXIT
494
set -o pipefail
495

    
496
STDERR_FILE=$(mktemp)
497
add_cleanup rm -f "$STDERR_FILE"
498
exec 2> >(tee -a "$STDERR_FILE" >&2)
499

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