~ltrager/maas-images/add_ib

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
#!/bin/bash

VERBOSITY=1
TEMP_D=""
VALID_FORMATS=( auto img-tar root-image root-image-gz root-tar squashfs-image )
CLOUDIMG_LABEL="cloudimg-rootfs"

error() { echo "$@" 1>&2; }
fail() { [ $# -eq 0 ] || error "$@"; exit 1; }

Usage() {
    cat <<EOF
Usage: ${0##*/} [ options ] source-image output-image.gz

   convert 'source-image' into a ephemeral image and put
   output into output-image.gz.

   source-image can be url or file path that is either .tar.gz or .img
   or -root.tar.gz. or -root.tar.xz

   options:
         --no-gzip     do not gzip output image
    -k | --kernel K    keep/install the kernel package 'k' inside image
                       use 'none' to remove any existing kernels
    -p | --krd-pack P  create a kernel / ramdisk pack
                       P is ',' delimited: kernel-name,kernel,ramdisk[,flags]
         --manifest M  write the dpkg manifest to M
    -v | --verbose     increase verbosity
    -f | --format   F  source-image is of format F. default: auto.
                       must be one of:
                         auto: determine based on file and name heuristics
                         img-tar: tarball of root image (image file named .img)
                         root-image: filesytem (ext[234] in a file)
                         root-image-gz: root-image that is compressed with gzip
                         root-tar: tarball of / (supports .tar.xz .tar.gz)
                         squashfs-image: a squahsfs image (.squashfs)

   Example:
     ${0##*/} --kernel=linux-generic \\
        --krd-pack=linux-generic,output/kernel,output/initrd \\
        trusty-server-cloudimg-armhf.tar.gz \\
        output/root-image.gz
EOF
}

bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; return 1; }
cleanup() {
    [ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}"
}

ddebug() {
    local level="$1"; shift;
    [ "${level}" -gt "${VERBOSITY}" ] && return
    error "$(date -R):" "$@"
}

debug() {
    local level=${1}; shift;
    [ "${level}" -gt "${VERBOSITY}" ] && return
    error "${@}"
}

mkfs_ext4() {
    local out="" cmd="" feature_flags="" cfg="/etc/mke2fs.conf" fstype="ext4"
    case "$1" in
        --type=*) fstype=${1#--type=}; shift;;
        --type) fstype="$2"; shift 2;;
    esac

    local target="$1" label="$2" uuid="$3"
    if grep -q metadata_csum "$cfg"; then
        # disable metadata_csum as yakkety will create this by default
        # but kernels on some buildd cannot mount such a filesystem.
        feature_flags="^metadata_csum"
    fi
    cmd=( "mkfs.$fstype"
        ${label:+-L "$label"} ${uuid:+-U "$uuid"}
        ${feature_flags:+-O "$feature_flags"}
        -F "$target" )
    debug 1 "creating $fstype fs in '$target'. label=$label" \
            " feature_flags='${feature_flags}'"
    debug 2 "mkfs cmd: ${cmd[*]}"

    local ret=0
    out=$("${cmd[@]}" 2>&1)
    ret=$?
    if [ $ret -ne 0 ]; then
        error "failed [$ret]: ${cmd[*]}";
        error "$out";
    fi
    return $ret
}

get_img_from_tar() {
    local img="$1" out="$2" tempd="$3"
    local mtmp=$(mktemp -d "${tempd}/img_from_tar.XXXXXX")
    tar -C "$mtmp" -Sxf "$img" ||
        { error "failed to extract $img"; return 1; }
    local f="" found=""
    for f in "${mtmp}/"*; do
        [ ! -f "$f" -o "${f%.img}" = "$f" ] && continue
        [ -z "$found" ] ||
            { error "multiple .img found in $img"; return 1; }
        found="$f"
    done
    [ -n "$found" ] || { error "no .img in $img"; return 1; }
    mv "$found" "$out" && rm -Rf "$mtmp"
}

squashfs_to_image() {
    local squashimg="$1" output="$2" tempd="$3" size="4G"
    local mtmp=$(mktemp -d "${tempd}/root_tar_to_image.XXXXXX")
    local xout="" ret=""
    local srcmp="$mtmp/src" trgmp="$mtmp/target"
    command -v unsquashfs >/dev/null 2>&1 || {
        error "unsquashfs is not available. apt-get install squashfs-tools".
        return 1;
    }
    truncate "--size=$size" "$output" || {
        error "failed to create file of $size in $output"
        return 1
    }
    mkdir "$srcmp" "$trgmp" || {
        error "failed mkdir $srcmp $trgmp"
        return 1;
    }
        
    mkfs_ext4 "$output" "$CLOUDIMG_LABEL" || return
    debug 1 "turning squahsfs image in $squashimg to ext4 image in $output"
    sudo bash -ec 'src="$1"; img="$2"; trgmp="$3";
        mounts=""
        cleanup() { for m in $mounts; do umount "$m"; done; }
        trap cleanup EXIT
        mount -o loop "$img" "$trgmp"
        mounts="$trgmp"
        unsquashfs -force -xattrs -dest "$trgmp" "$src"' \
        "squashimg-to-image" "$squashimg" "$output" "$trgmp"
    ret=$?
    rm -Rf "$mtmp" || return
    return $ret
}

root_tar_to_image() {
    local tball="$1" output="$2" tempd="$3" size="4G"
    local mtmp=$(mktemp -d "${tempd}/root_tar_to_image.XXXXXX")
    local mp="$mtmp/mp" xout="" ret=""
    truncate "--size=$size" "$output" ||
        return "failed to create file of $size in $output"
    mkdir "$mp"
    mkfs_ext4 "$output" "$CLOUDIMG_LABEL" || return
    debug 1 "turning root tarball in $tball to image in $output"
    sudo bash -ec 'tball="$1"; img="$2"; mp="$3";
        mount -o loop "$img" "$mp"
        trap "umount $mp" EXIT
        tar -C "$mp" -xpSf "$tball" --numeric-owner \
            --xattrs "--xattrs-include=*"' \
        "root-tar-to-image" "$tball" "$output" "$mp"
    ret=$?
    rm -Rf "$mtmp" || return
    return $ret
}

get_img_file() {
    local fout="" fmt="$1" input="$2" output="$3" tempd="$4" ret=""
    if [ "$fmt" = "auto" ]; then
        fout=$(LANG=C file "$input") ||
            { error "failed: file $input"; return 1; }
        case "${fout#$input: }" in
            "gzip compressed"*)
                fout=$(zcat "$input" | file -)
                case "${fout#*: }" in
                    POSIX\ tar*) fmt="img-tar";;
                    *) fmt="root-image";;
                esac
                ;;
            "POSIX tar"*) fmt="img-tar";;
            *ext[234]\ filesystem*) fmt="root-image";;
            *[Ss]quashfs*) fmt="squashfs-image";;
            *)
                # if the above failed (on trusty a .tar.gz file was reported
                # as a Minux file system) then try filename based heuristics
                case "$input" in
                    *-root.t??|*-root.tar|*-root.tar.??) fmt="root-tar";;
                    *.tar.gz|*.tgz|*.tar) fmt="img-tar";;
                    *.gz) fmt="root-image-gz";;
                    *.squashfs) fmt="squashfs-image";;
                    *) 
                        error "WARN: file '$input' did not match name hueristics"
                        fmt="root-image";;
                esac
                debug 1 "guessing $input is $fmt based on name and 'file' [$fout]";;
        esac
        debug 1 "determined format is $fmt"
    fi
    case "$fmt" in
        img-tar)
            get_img_from_tar "$input" "$output" "$tempd";;
        root-image-gz)
            zcat -c "$input" > "$output";;
        root-image)
            ln -s "$(readlink -f "$input")" "$output";;
        root-tar)
            root_tar_to_image "$input" "$output" "$tempd";;
        squashfs-image)
            squashfs_to_image "$input" "$output" "$tempd";;
        *)
            error "Unknown format '$fmt'";
            return 1;;
    esac
    ret=$?
    [ $ret -eq 0 ] || {
        error "failed converting $fmt from $input to $output [$?]"
        return $ret
    }
    _RET="$fmt"
}

ensure_file_d() {
    local f="" d=""
    for f in "$@"; do
        d=$(dirname "$f")
        mkdir -p "$d" || return
    done
    return 0
}

human2bytes() {
    # converts size suitable for input to resize2fs to bytes
    # s:512 byte sectors, K:kilobytes, M:megabytes, G:gigabytes
    # none: block size of the image
    local input=${1} defunit=${2:-1024}
    local unit count;
    case "$input" in
        *s) count=${input%s}; unit=512;;
        *K) count=${input%K}; unit=1024;;
        *M) count=${input%M}; unit=$((1024*1024));;
        *G) count=${input%G}; unit=$((1024*1024*1024));;
        *)  count=${input}  ; unit=${defunit};;
    esac
   _RET=$((${count}*${unit}))
}

resize_image() {
    # set image size to 'size'
    #  if size is '+<size>', it will be grown by that size
    #  if '--allow-padding' provided, then size will be
    #    the minimum of "minimum_size + padding" or size
    local padding=""
    case "$1" in
        --allow-padding=*) padding="${1#--allow-padding=}"; shift;;
        --allow-padding) padding="$2"; shift 2;;
    esac
    local img="$1" size="$2" ret="" tok=":"
    local oimgsize="" oblocks="" out="" blksize=""
    local tbytes="" minblocks=""
    out=$(ls -l "$img") && oimgsize=$(echo "$out" | awk '{print $5}') &&
        [ -n "$oimgsize" ] ||
        { error "failed to get size of $img"; return 1; }

    case "$size" in
        +*) human2bytes "${size#+}" ||
                { error "failed convert '${size#+}' to bytes"; return 1; }
            tbytes=$(($oimgsize+$_RET))
            ;;
        *) human2bytes "$size" ||
                { error "failed convert '$size' to bytes"; return 1; }
            tbytes=${_RET}
            ;;
    esac

    if [ -n "$padding" ]; then
        human2bytes "$padding" || {
            error "failed convert '$padding' to bytes";
            return 1;
        }
        padbytes=$_RET
    fi

    out=$(e2fsck -fy "$img" 2>&1) ||
        { error "failed to e2fsck -fy '$img'"; error "$out"; return 1; }

    out=$(LC_ALL=C dumpe2fs "$img" 2>/dev/null) ||
        { error "failed 'dumpe2fs $img'"; return 1; }
    oblocks=$(printf "%s\n" "$out" |
        awk '$0 ~ /^Block count:/ { print $3 }') &&
        [ -n "$oblocks" ] || {
            error "failed to record blocks in $img"
            return
        }

    blksize=$(printf "%s\n" "$out" |
        awk '$0 ~ /^Block size:/ { print $3 }')

    out=$(LC_ALL=C resize2fs -P "$img" 2>/dev/null) ||
        { error "failed to get min image size"; return 1; }
    minblocks=$(printf "%s\n" "$out" | awk '$0 ~ /minimum/ { print $NF }')
    [ -n "$minblocks" ] ||
        { error "failed to get min blocks in '$img'"; return 1; }

    local tblocks=$(($tbytes/$blksize))
    local des_blocks=$tblocks
    if [ -n "$padding" ]; then
        if [ $(($minblocks+($padbytes/$blksize))) -gt $tblocks ]; then
            des_blocks=$((minblocks+($padbytes/$blksize)))
            error "WARN: '--allow-padding=$padding'."
            error "  minblocks=$minblocks target=$tblocks desired=$des_blocks"
        elif [ $((minblocks+$(($padbytes/$blksize)))) -lt $tblocks ]; then
            ddebug 1 "do not need pad, target=$tblocks > $minblocks+$padding"
        fi
    fi

    if [ $des_blocks -lt $minblocks ]; then
        error "target blocks '$tblocks' smaller than minblocks '$minblocks'"
        error "fail: Could not resize to $size. ($tblocks * $blksize)"
        error "  minblocks=$minblocks target=$tblocks desired=$des_blocks"
        return 1
    fi
    tbytes=$(($des_blocks*$blksize))

    ddebug 1 "targetting $tbytes ($des_blocks*$blksize) bytes."
    ddebug 1 "fs is currently $oblocks * $blksize."
    ddebug 1 "resize to $des_blocks. minblocks=$minblocks. target=$tblocks"

    if [ "$oimgsize" -lt "$tbytes" ]; then
        truncate --size=$tbytes "$img" ||
            { error "failed to resize image to $tbytes"; return 1; }
    fi

    out=$(e2fsck -fy "$img" 2>&1) ||
        { error "failed to e2fsck -fy '$img'"; error "$out"; return 1; }

    out=$(resize2fs "$img" "$des_blocks" 2>&1) ||
        { error "failed resize '$img'"; error "$out"; return 1; }

    if [ "$oimgsize" -gt "$tbytes" ]; then
        truncate --size=$tbytes "$img"
    fi

    out=$(e2fsck -fy "$img" 2>&1) ||
        { error "failed to e2fsck -fy '$img'"; error "$out"; return 1; }

    _RET="$oimgsize:$oblocks"
}

copy_filesystem_image() {
    local src_img="$1" target_img="$2" size="${3:-1400M}"
    local fstype="" label="" uuid="" tmpf="" out=""
    tmpf=$(mktemp "${TMPDIR:-/tmp}/${0##*/}-copyfs.XXXXXX") ||
        { error "mktemp failed"; return 1; }
    blkid -o export "${src_img}" > "$tmpf" ||
        { rm -f "$tmpf"; error "blkid ${src_img} failed"; return 1; }
    out=$(UUID=""; TYPE=""; LABEL=""; . "$tmpf" && echo "$TYPE $UUID $LABEL")
    [ $? -eq 0 ] || fail "failed reading blkid information for $src_img"
    set -- ${out}
    fstype="$1"
    uuid="$2"
    label="$3"
    rm -f "$tmpf"

    debug 1 "src image uuid=$uuid label=$label fstype=$fstype"
    truncate "--size=$size" "$target_img" ||
        { error "truncate $target_img $size failed"; return 1; }
    case "$fstype" in
        ext*) mkfs_ext4 "--type=$fstype" "$target_img" "$label" "$uuid";;
        *) error "unknown filesystem type '$fstype'";;
    esac

    sudo bash -ec '
        s_img=$1
        t_img=$2
        s_mp=""
        t_mp=""
        fail() { echo "$@" 1>&2; exit 1; }
        cleanup() {
            for t in "$t_mp" "$s_mp"; do [ -z "$t" ] || umount "$t"; done;
            rm -Rf "$tmpd";
        }

        tmpd=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") &&
            mkdir "$tmpd/src" "$tmpd/tgt" ||
            fail "failed making tmpdir"
        trap cleanup EXIT
        mount -o loop,ro "$s_img" "$tmpd/src" && s_mp="$tmpd/src" ||
            fail "failed mount $s_img";
        mount -o loop "$t_img" "$tmpd/tgt" && t_mp="$tmpd/tgt" ||
            fail "failed mount $t_img";
        rsync -aXHAS "${s_mp}/" "${t_mp}/" ||
            fail "failed copy data ${s_img} -> ${t_img}"' \
        copy-source-to-target "$src_img" "$target_img" || {
            error "copy data from $src_img -> $target_img failed"
            return 1;
        }
}

allow_resize_failure() {
    local arch="$1" bdist="" barch=""
    barch=${2:-$(uname -m)}
    bdist=${3:-$(lsb_release -sc)}

    local info="arch=$arch build-system=$bdist/$barch"
    if [ "$arch" = "arm64" ] &&
       [ "$barch" = "x86_64" -o -z "${barch#i?86}" ] &&
       [ "$bdist" != "trusty" ]; then
        error "***************************"
        error "WARN: resize2fs fail allowed for $info"
        error ""
        error "resize2fs is less aggresive in versions"
        error "newer than trusty: http://pad.lv/1415077"
        error "***************************"
        return 0
    else
        error "resize image failure not allowed for $info"
        return 1
    fi
}

handle_kpack() {
    local short_opts="v"
    local long_opts="dtb:,kihelper:,proposed"
    local getopt_out=""
    getopt_out=$(getopt --name "handle_kpack" \
        --options "${short_opts}" --long "${long_opts}" -- "$@") &&
        eval set -- "${getopt_out}" ||
        { bad_Usage; return; }

    local dtbs pt img outd ckpkg tkout tiout
    dtbs=( )
    pt=( )
    while [ $# -ne 0 ]; do
        cur="$1"; next="$2";
        case "$cur" in
               --proposed) pt[${#pt[@]}]="--proposed";;
               --dtb) dtbs[${#dtbs[@]}]="$next"; shift;;
               --kihelper) pt[${#pt[@]}]="$cur=$next"; shift;;
            --) shift; break;;
        esac
        shift;
    done
    # tkout, tiout = target kernel out, target initrd out
    img="$1"; ckpkg="$2"; tkout="$3"; tiout="$4"
    shift 4
    outd="${TEMP_D}/$ckpkg"
    mkdir -p "$outd" ||
        { error "failed tempdir for '$ckpkg'"; return 1; }

    local cmd=""
    cmd=( env kpack-from-image \
             $vflags "${pt[@]}" "$imgfile" "$ckpkg" "$outd" )
    ddebug 1 "$(date -R): starting ${cmd[*]}"
    "${cmd[@]}" ||
        { error "failed to get $ckpkg output"; return 1; }
    ensure_file_d "$tkout" "$tiout" ||
        { error "failed to make dirs for $tkout or $tiout"; return 1; }
    mv "$outd/kernel" "$tkout" &&
        mv "$outd/initrd" "$tiout" ||
        { error "failed copying files"; return 1; }

    local darg="" bname="" outf=""
    for darg in "${dtbs[@]}"; do
        # format is name=output. and kpack-from-image names
        # files dtb-<name>
        bname=${darg%%=*}
        outf=${darg#*=}
        ensure_file_d "$outf" || { error "failed mkdir for $outf"; return 1; }
        if [ -f "$outd/dtb-$bname" ]; then
            mv "$outd/dtb-$bname" "$outf" ||
                { error "failed moving dtb-$bname to $outf"; return 1; }
        elif [ -f "$outd/dtb-$bname.dtb" ]; then
            mv "$outd/dtb-$bname.dtb" "$outf" ||
                { error "failed moving dtb-$bname.dtb to $outf"; return 1; }
        else
            error "did not find dtb file $bname"
            return 1;
        fi
    done
}

inargs() {
    #inargs(needle, [haystack])
    # return true if needle is in haystack
    local needle="$1" i=""
    shift
    for i in "$@"; do
        [ "$i" = "$needle" ] && return 0
    done
    return 1
}

main() {
    local short_opts="a:fhk:p:v"
    local long_opts="arch:,help,format:,kernel:,krd-pack:,manifest:,no-gzip,proposed,verbose"
    local getopt_out=""
    getopt_out=$(getopt --name "${0##*/}" \
        --options "${short_opts}" --long "${long_opts}" -- "$@") &&
        eval set -- "${getopt_out}" ||
        { bad_Usage; return; }

    local cur="" next="" output="" gzip=true kpkg=""
    local kpacks="" src_in="" src="" arch="" vflags="" fmt="auto"
    local working_space="1024M" zero_img="true" fs_img_size="1400M"
    local manifest_out="" proposed_flag=""
    kpacks=( )

    while [ $# -ne 0 ]; do
        cur="$1"; next="$2";
        case "$cur" in
            -h|--help) Usage ; exit 0;;
            -a|--arch) arch="$next"; shift;;
            -f|--format) fmt="$next"; shift;;
            -k|--kernel) kpkg=$next; shift;;
               --manifest) manifest_out="$next";;
            -p|--krd-pack) kpacks[${#kpacks[@]}]="$next"; shift;;
               --no-gzip) gzip=false;;
               --proposed) proposed_flag="--proposed";;
            -v|--verbose) VERBOSITY=$((${VERBOSITY}+1)); vflags="${vflags}v";;
            --) shift; break;;
        esac
        shift;
    done

    [ $# -eq 2 ] || { bad_Usage "must provide source and target"; return; }
    src_in="$1"
    output="$2"
    [ -z "$vflags" ] || vflags="-$vflags"

    PATH="$(dirname "$0"):$PATH"
    command -v "maas-cloudimg2ephemeral" >/dev/null 2>&1 ||
        { error "do not have maas-cloudimg2ephemeral in path"; return 1; }

    inargs "$fmt" "${VALID_FORMATS[@]}" || {
        error "provided '--format=$fmt' not a valid format:" \
            "${VALID_FORMATS[*]}"
        return 1;
    }

    [ -z "$kpkg" ] && {
        error "no '--kernel' provided, choosing 'linux-generic'"
        kpkg="linux-generic"
    }

    TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") ||
        { error "failed to make tempdir"; return 1; }
    trap cleanup EXIT

    if [ -f "$src_in" ]; then
        src="$src_in"
    else
        case "$src_in" in
            http://*|https://*|ftp://*)
                ddebug 1 "getting source $src_in"
                src="${TEMP_D}/${src_in##*/}"
                wget "$src_in" --dot-style=mega -O "$src" ||
                    { error "failed download $src_in"; return 1; }
                ;;
            file://*)
                [ -f "${src_in#file://}" ] ||
                    { error "$src_in: not a file"; return 1; }
                src="${src_in#file://}"
                ;;
            *) error "Unable to handle src: $src_in"; return 1;;
        esac
    fi

    # now src is either a .tar.gz file, a -root.tar.gz, or a .img file
    local imgfile="${TEMP_D}/root.img"
    ddebug 1 "getting .img from $src fmt=$fmt"
    get_img_file "$fmt" "$src" "$imgfile" "${TEMP_D}" ||
        { error "failed to get image file from $src"; return 1; }
    fmt=${_RET}

    if [ "$fmt" = "root-tar" ]; then
        working_space=""
    fi

    if [ "$src" -ef "$imgfile" ]; then
        rm -f "$imgfile"
        ddebug 1 "copying $src_in to $imgfile"
        cp --sparse=always "$src" "$imgfile" ||
            { error "failed to copy $src_in to temp"; return 1; }
        chmod u+w "$imgfile" ||
            { error "failed to give write perms to $imgfile"; return 1; }
    fi

    ensure_file_d "$output" ||
        { error "failed to create dir for $output"; return 1; }

    local restore_img_info=""
    if [ -n "$working_space" ]; then
        ddebug 1 "getting '$working_space' in $imgfile"
        resize_image "$imgfile" "+$working_space" ||
            { error "failed to grow $imgfile by $working_space"; return 1; }
        restore_img_info="$_RET"
    fi

    # now imgfile has the root filesystem image to adjust
    local pkout="${TEMP_D}/$kpkg-kernel" piout="${TEMP_D}/$kpkg-initrd"
    local manif="${TEMP_D}/manifest"
    local mc2ephem=""
    mc2ephem=( maas-cloudimg2ephemeral $vflags ${arch:+"--arch=$arch"}
               $proposed_flag "$imgfile" "$kpkg" "$pkout" "$piout" "$manif" )
    ddebug 1 "starting ${mc2ephem[*]}"
    "${mc2ephem[@]}" ||
        { error "failed to turn $imgfile to ephemeral"; return 1; }
    [ -z "$manifest_out" ] || cp "$manif" "$manifest_out" ||
        { error "failed copying manifest to $manifest_out"; return 1; }

    local i="" oifs="$IFS"
    ddebug 1 "starting kpacks: ${kpacks[*]}"
    for i in "${kpacks[@]}"; do
        IFS=","; set -- $i; IFS="$oifs"
        handle_kpack "$imgfile" "$@" ||
            { error "handle_kpack '$imgfile' $* failed"; return 1; }
    done
    ddebug 1 "kpacks done"

    local pad="" tmp_img_size="$fs_img_size"
    if [ "$arch" = "ppc64el" ]; then
        ## ppc64el wily do not fit in 1400M. Instead of allowing this across
        ## the board, we're going to keep the 1400M standard other places.
        ## the 'pad' of 100M gives ~ 100M wiggle room in image.
        tmp_img_size="1600M"
        pad="100M"
    fi
    ddebug 1 "copying working img into new fs of $tmp_img_size (pad=$pad)"
    local timg="$imgfile.fs_img_size"
    copy_filesystem_image "$imgfile" "$timg" "$tmp_img_size" ||
        error "failed copying $imgfile to $timg"

    if [ -n "$pad" ]; then
        ddebug 1 "adjusting image to fit $fs_img_size with pad=$pad"
        resize_image "--allow-padding=$pad" "$timg" "$fs_img_size"
    fi

    if $zero_img; then
        ddebug 1 "zeroing img"
        e2fsck -fy "$imgfile" || { error "failed to e2fsck $imgfile"; return 1; }
        zerofree "$imgfile" || { error "failed zerofree $imgfile"; return 1; }
    fi

    if [ "$fmt" = "squashfs-image" ]; then
        local squash_out=""
        squash_out=$(dirname "$output")/$(basename "$src")
        if [ -n "${proposed_flag}" ]; then
            debug 1 "creating a new squashfs in $squash_out from $imgfile"
            sudo mount-image-callback "$imgfile" --read-only -- sh -ec '
                src="$1"; out="$2"; owner="$3"; tmp="$out.$$"
                trap "rm -f \"$tmp\"" EXIT
                mksquashfs "$src" "$tmp" -xattrs -comp xz
                chown "$owner" "$tmp"
                mv "$tmp" "$out"
                ' newsquash-img _MOUNTPOINT_ \
                    "${squash_out}" "$(id -u):$(id -g)" || {
                error "Failed mksquashfs from $imgfile"
                return 1
            }
        else
            cp "$src" "${squash_out}" || {
                error "copy SquashFS image from $src -> $squash_out failed"
                return 1;
            }
        fi
    fi

    if $gzip; then
        ddebug 1 "gzipping image file"
        local stime=$SECONDS
        gzip -9 --rsyncable "$timg" &&
            mv --force "$timg.gz" "$output" ||
            { error "failed gzip"; return 1; }
        ddebug 1 "gzip took $(($SECONDS-$stime))s"
    else
        mv --force "$timg" "$output" ||
            { error "failed move to $output"; return 1; }
    fi

    ddebug 1 "finished"
    return 0
}

main "$@"
# vi: ts=4 expandtab