summaryrefslogtreecommitdiff
path: root/src/initrd/mdadm-dup.sh
blob: 70163a5c6c531e1753c6e746a21d978a5885a91e (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
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
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 _ crypt_dev _
                case "$snapshot" in
                    snapshot)
                        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"      || true
                        ;;
                    crypt)
                        cryptsetup remove "$dev" || exit 1
                        losetup -d /dev/block/"$crypt_dev" || true
                        ;;
                esac
            ) || 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'
}

cryptsetup_temp()
{
    local sectors="$1" cryptname="$2" temp_file="$3" parms=$- secret
    set +x
    # Add 4096 sectors for LUKS header
    truncate -s $(((sectors + 4096) * 512)) "$temp_file" || return
    cleartext_dev=$(LoSetup -f --show "$temp_file")      || return
    secret="$(head -c256 /dev/urandom)"                  || return
    printf %s "$secret" |
        cryptsetup luksFormat "$cleartext_dev" - || return
    printf %s "$secret" |
        cryptsetup --key-file - luksOpen "$cleartext_dev" "$cryptname" || return
    unset secret
    set "$parms"

    wait_for_dm_device /dev/mapper/"$cryptname"
    rm "$temp_file"
    echo /dev/mapper/"$cryptname"
}

mdadm_copy_eject_crypt()
{
    local md_dev="$1" temp_file="$2"

    [ -b "$md_dev" ]                                  || return

    local output_dev sectors

    old_subdev=$(mdadm_subdevices "$md_dev"|head -n1) || return
    [ -b "$old_subdev" ]                              || return
    # TODO: truncate to the ISO fs size if the device is larger
    sectors=$(blockdev --getsz "$md_dev")             || return

    output_dev=$(cryptsetup_temp "$sectors" samizdatiso "$temp_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_copy_eject()
{
    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
}