summaryrefslogtreecommitdiff
path: root/old-school/mdadm-dup.sh
blob: 16e3dfd2648ceb86c296716756c20161c1cb1621 (plain)
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
LoSetup()
{
    local losetup_binary="$(which LoSetup)"
    if [ "$losetup_binary" ]; then
        "$losetup_binary" "$@"
    else
        losetup "$@"
    fi
}

dm_snapshot()
{
    # TODO: eliminate duplication; this function exists elsewhere in a less generalized form
    local ro_file rw_file cutoff_size
    ro_file=$1
    rw_file=$2
    cutoff_size=$3

    local ro_dev rw_dev size new_dev_name persist chunksize

    if [ -b "$ro_file" ];
    then ro_dev=$ro_file
    else ro_dev=$(LoSetup -r -f --show "$ro_file") || return
    fi

    if [ -b "$rw_file" ];
    then rw_dev=$rw_file
    else rw_dev=$(LoSetup -f --show "$rw_file") || return
    fi

    if [ "$cutoff_size" -a "$cutoff_size" -gt 0 ]; then
        size=$cutoff_size
    else
        size=$(blockdev --getsz "$ro_dev") || return
    fi

    new_dev_name=${ro_dev##*/}
    persist=p
    chunksize=16
    dmsetup create "$new_dev_name" --table "0 $size snapshot $ro_dev $rw_dev $persist $chunksize" || return
    wait_for_dm_device /dev/mapper/"$new_dev_name";
    echo /dev/mapper/"$new_dev_name"
}

dm_snapshot_teardown()
{
    local dev="$1"
    case "$dev" in
        /dev/dm-*)
            dmsetup table "$dev" | (
                read _ _ snapshot ro_dev rw_dev _ _
                [ "$snapshot" = snapshot ]      || exit 1
                dmsetup remove "$dev"           || exit 1
                # errors ignored because the loop dev can be configured to be
                # automatically removed upon disuse
                losetup -d /dev/block/"$rw_dev" || true
                eject /dev/block/"$ro_dev"      || exit 1
            ) || return
            ;;
        *) return 1 ;;
    esac
}

wait_for_dm_device()
{
    # TODO: improve
    while ! [ -e "$1" ]; do
        sleep 1
    done
}

dup_mount_cdrom()
{
    local cdrom_dev="$1" mountpoint="$2"

    local sectors md_dev=/dev/md55 cdrom_rw_file=/"${cdrom_dev##*/}".rw

    sectors=$(get_cdrom_sizelimit "$cdrom_dev") || return

    # TODO: do we even need this backing file? We do need to trick mdadm into
    # thinking that this is a RW device, but previously we got away with just
    # creating a loopback device.
    dd if=/dev/zero of="$cdrom_rw_file" bs=1K count=32 || return
    cdrom_rw_dev=$(dm_snapshot "$cdrom_dev" "$cdrom_rw_file" "$sectors") || return
    mdadm_dup "$cdrom_rw_dev" "$md_dev" "$sectors" || return
    mount -t iso9660 -r $md_dev "$mountpoint"
}

get_cdrom_sizelimit()
{
    # returns 512-byte sectors
    local dev="$1" sectors
    sectors=$(blockdev --getsz "$dev") || return

    # Check if we can read the last 8 sectors. With a TAO CDROM, we can't --
    # these sectors are faux, and not part of the ISO fs. If mdadm is allowed to
    # read them, it will mark the device failed.
    if dd count=2 if="$dev" bs=2048 skip=$((sectors/4 - 2)) of=/dev/null 2>/dev/null; then
        echo $sectors
    else
        echo $((sectors - 8))
    fi
}

mdadm_dup()
{
    local input_dev="$1" md_name="$2" sectors="$3"

    mdadm --build $md_name ${sectors:+--size=$((sectors / 2))} \
          --level=1 --raid-devices=1 --force --write-mostly "$input_dev" || return
}

mdadm_subdevices()
{
    local md_dev="$1"
    mdadm -D "$md_dev" -Y | sed -ne 's/^MD_DEVICE_.*_DEV=//p'
}

mdadm_copy_eject() # NOT INITRD; uses non-busybox "losetup"
{
    local md_dev="$1" output_file="$2"

    [ -b "$md_dev" ]                                  || return
    [ ! -e "$output_file" ]                           || return

    local output_dev sectors

    old_subdev=$(mdadm_subdevices "$md_dev"|head -n1) || return
    [ -b "$old_subdev" ]                              || return
    sectors=$(blockdev --getsz "$md_dev")             || return

    truncate -s $((sectors * 512)) "$output_file"     || return
    output_dev=$(LoSetup -f --show "$output_file")    || return

    mdadm "$md_dev" --add "$output_dev"               || return
    mdadm "$md_dev" --grow -n2                        || return

    mdadm_wait_remove "$md_dev" "$old_subdev"         || return

    mdadm "$md_dev" --grow -n1 --force                || return
    dm_snapshot_teardown "$old_subdev"
}

mdadm_wait_remove()
{
    # We should perhaps use mdadm --monitor's RebuildFinished event.

    local dev="$1" disk="$2" tries
    if ! mdadm --wait "$dev"; then
        tries=1000
        while ! mdadm --detail --test "$dev"; do
            [ $tries -gt 0 ] || return 1
            sleep 1
            tries=$((tries-1))
        done
    fi

    mdadm "$dev" --fail "$disk" || return 1
    tries=100
    while ! mdadm "$dev" --remove "$disk"; do
        [ $tries -gt 0 ] || return 1
        sleep 1
        tries=$((tries-1))
    done
    return 0
}