Repository navigation
Expand file tree
/
Copy pathboot-partition.sh
More file actions
executable file
·288 lines (262 loc) · 12.2 KB
/
Copy pathboot-partition.sh
File metadata and controls
executable file
·288 lines (262 loc) · 12.2 KB
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
#!/usr/bin/env bash
#
# Test de resolucion de la particion de arranque (restore hook, v3.6).
#
# QUE TESTEA
# ----------
# resolve_esp_mount() aceptaba el PRIMER mountpoint de /boot, /efi, /boot/efi
# sin comprobar nada. Con dos vfat montadas (el layout de dual-boot) y la
# particion que tiene los UKIs sin montar, elegia la otra; y como el destino se
# creaba con `mkdir -p`, los UKIs acababan ahi y el error quedaba "confirmado"
# para siempre. Con PRUNE_UKIS=true ademas se borraban los .efi que no
# estuvieran en el respaldo de esa particion.
#
# Desde v3.6 una particion solo es candidata si esta montada y tiene el
# directorio EFI/Linux (que es donde kernel-install deja los UKIs). Si no hay
# ninguna, el hook aborta en vez de adivinar.
#
# COMO LO TESTEA
# --------------
# Con un namespace de usuario se levanta un chroot minimo (bind mounts de
# /usr, /dev, /sys y /proc) donde /boot y /efi se manipulan a voluntad:
# montarlas o dejarlas como directorios planos, y poner los UKIs en una u
# otra. Asi se ejercita el codigo de produccion tal cual, sin variables de
# entorno ni seams que solo usen los tests; los unicos overrides son
# TSUKI_BACKUP_DIR y TSUKI_LOG_FILE, que ya existen para esto.
#
# Hace falta el chroot porque en el namespace no se puede DESMONTAR el /boot
# real (los montajes heredados van bloqueados), de modo que "particion de los
# UKIs sin montar" no se puede simular de otra forma.
#
# Si las user namespaces no estan habilitadas, el test FALLA en vez de
# saltarse en silencio: un test que se salta solo no es cobertura.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(dirname "$SCRIPT_DIR")"
RESTORE_HOOK="$ROOT/hooks.d/restore/90-restore-uki"
# Re-ejecutarse dentro de un namespace de usuario con montajes privados y su
# propio /proc (necesario para que mountpoint y findmnt sean fiables).
if [ "${TSUKI_TEST_NS:-}" != "1" ]; then
if ! unshare -r -m -p -f --mount-proc true 2>/dev/null; then
echo "FALLO: no se pueden crear namespaces de usuario con /proc propio." >&2
echo " El test de particion de arranque no puede ejecutarse." >&2
exit 1
fi
exec unshare -r -m -p -f --mount-proc env TSUKI_TEST_NS=1 bash "$0" "$@"
fi
MID="$(cat /etc/machine-id)"
KVER="$(uname -r)"
CUR_EFI="${MID}-${KVER}.efi"
TMP="$(mktemp -d)"
R="$TMP/root"
LOG="$R/tmp/restore.log"
ERR="$R/tmp/restore.err"
cleanup() {
umount "$R/boot" "$R/efi" 2>/dev/null || true
umount -R "$R" 2>/dev/null || true
chmod -R u+w "$TMP" 2>/dev/null || true
rm -rf "$TMP" 2>/dev/null || true
}
trap cleanup EXIT
fail() {
echo " FALLO: $*" >&2
echo " --- log del hook ---" >&2
sed 's/^/ | /' "$LOG" >&2 2>/dev/null || echo " | (sin log)" >&2
# El stderr del hook se conserva: sin el, un fallo como un rc=127 no dice
# nada sobre la causa.
if [ -s "$ERR" ]; then
echo " --- stderr del hook ---" >&2
sed 's/^/ ! /' "$ERR" >&2
fi
exit 1
}
# --- chroot minimo ---------------------------------------------------------
# Se replica el layout de la raiz real en vez de asumir el de una distro
# concreta: en Arch /lib64 es un symlink a usr/lib, en Ubuntu es un directorio
# (o symlink a usr/lib64). Assume-rlo dejaba el chroot sin el loader de ELF y
# todo fallaba con rc=127.
link_or_bind() {
local src="$1" dst="$R$1"
mkdir -p "$(dirname "$dst")"
if [ -L "$src" ]; then
ln -s "$(readlink "$src")" "$dst"
elif [ -d "$src" ]; then
mkdir -p "$dst"
mount --rbind "$src" "$dst"
else
mkdir -p "$dst"
fi
}
build_chroot() {
mkdir -p "$R"/usr "$R"/dev "$R"/sys "$R"/etc "$R"/proc "$R"/tmp "$R"/backup "$R"/hooks.d/restore
mount --rbind /usr "$R/usr"
mount --rbind /dev "$R/dev"
mount --rbind /sys "$R/sys"
mount --rbind /proc "$R/proc"
# /etc real, no un /etc minimo: en Debian/Ubuntu binarios como awk son
# symlinks via /etc/alternatives (/usr/bin/awk -> /etc/alternatives/awk ->
# /usr/bin/mawk), asi que con un /etc de mentira el symlink queda roto y
# el chroot pierde el comando. Lo que si hay que neutralizar es fstab, para
# que el hook no pueda montar nada real por su cuenta.
mount --rbind /etc "$R/etc"
: > "$TMP/empty-fstab"
mount --bind "$TMP/empty-fstab" "$R/etc/fstab"
for d in /bin /sbin /lib /lib64 /lib32; do
[ -e "$d" ] || [ -L "$d" ] || continue
link_or_bind "$d"
done
cp "$RESTORE_HOOK" "$R/hooks.d/restore/90-restore-uki"
chmod +x "$R/hooks.d/restore/90-restore-uki"
# Fallo temprano y legible si el chroot no esta bien montado. Se comprueban
# TODOS los comandos externos que usan los hooks, no solo algunos: si el
# chroot se monta mal, quiero un "falta X" claro y no un rc=127 sin
# contexto blamed al hook. awk ademas se EJECUTA, no solo se localiza:
# puede existir como symlink roto a traves de /etc/alternatives, que es
# justo lo que pasaba con un /etc de mentira.
missing=$(chroot "$R" /bin/bash -c '
for c in awk basename cp date df findmnt head lsblk mkdir mktemp mount \
mountpoint mv readlink sha256sum stat tail umount; do
command -v "$c" >/dev/null 2>&1 || echo "$c"
done
printf x | awk "{print}" >/dev/null 2>&1 || echo "awk (presente pero no se ejecuta)"' 2>/dev/null)
if [ -n "$missing" ]; then
echo "FALLO: el chroot no tiene estas utilidades: $(echo $missing)" >&2
exit 1
fi
}
# --- Escenarios ------------------------------------------------------------
# Un UKI en el arbol de la particion indicada.
put_uki() {
mkdir -p "$1/EFI/Linux"
printf 'UKI' > "$1/EFI/Linux/$CUR_EFI"
}
# setup <donde viven los UKIs> <que particiones estan montadas>
# ukis_on: boot | efi | both | none
# mounted: boot | efi | both | none
# Lo que NO esta montado simplemente no se ve desde dentro del chroot, que es
# justo lo que pasa cuando el usuario restaura desde un Live USB sin montar la
# particion de arranque.
setup() {
umount "$R/boot" "$R/efi" 2>/dev/null || true
rm -rf "$TMP/src-boot" "$TMP/src-efi" "${R:?}/boot" "${R:?}/efi"
mkdir -p "$TMP/src-boot" "$TMP/src-efi" "$R/boot" "$R/efi"
# Una particion sin UKIs tiene su arbol habitual pero NO EFI/Linux.
# /efi lleva ademas el arbol de arranque de Windows.
mkdir -p "$TMP/src-boot/EFI/BOOT" "$TMP/src-efi/EFI/Microsoft/Boot"
case "$1" in
boot) put_uki "$TMP/src-boot" ;;
efi) put_uki "$TMP/src-efi" ;;
both) put_uki "$TMP/src-boot"; put_uki "$TMP/src-efi" ;;
none) ;;
esac
case "$2" in
boot) mount --bind "$TMP/src-boot" "$R/boot" ;;
efi) mount --bind "$TMP/src-efi" "$R/efi" ;;
both) mount --bind "$TMP/src-boot" "$R/boot"
mount --bind "$TMP/src-efi" "$R/efi" ;;
esac
}
run_restore() {
: > "$LOG"
: > "$ERR"
set +e
chroot "$R" /bin/bash -c \
"TSUKI_BACKUP_DIR=/backup TSUKI_LOG_FILE=/tmp/restore.log /hooks.d/restore/90-restore-uki" \
>"$ERR" 2>&1
echo $?
set -e
}
# El UKI de respaldo. El formato del .sha256 es el que escribe el backup hook
# (hash pelado, sin nombre); el formato `sha256sum` se prueba aparte en el
# caso F.
mkdir -p "$R/backup"
printf 'UKI' > "$R/backup/$CUR_EFI"
bare_sha="$(sha256sum "$R/backup/$CUR_EFI" | awk '{print $1}')"
printf '%s\n' "$bare_sha" > "$R/backup/$CUR_EFI.sha256"
build_chroot
# --- A: UKIs en /boot, /boot montada -> usa /boot --------------------------
setup boot boot
rc=$(run_restore)
[ "$rc" -eq 0 ] || fail "caso A: se esperaba exito, rc=$rc"
grep -q "Particion de arranque (UKIs) en: /boot" "$LOG" || fail "caso A: no eligio /boot"
[ -f "$TMP/src-boot/EFI/Linux/$CUR_EFI" ] || fail "caso A: el UKI no quedo en /boot"
[ ! -e "$TMP/src-efi/EFI/Linux" ] || fail "caso A: toco /efi, que no tiene UKIs"
echo " A: UKIs en /boot, montada -> usa /boot, no toca /efi"
# --- B: UKIs en /boot SIN montar, /efi montada (sin EFI/Linux) -------------
# Este es el bug. UKIs en la particion de arranque no montada, ESP de Windows
# montada: el hook NO puede elegir /efi ni fabricarse alli el directorio.
setup boot efi
rc=$(run_restore)
[ "$rc" -ne 0 ] || fail "caso B: deberia abortar, no restaurar en la particion equivocada"
grep -q "No se encontro la particion de arranque" "$LOG" \
|| fail "caso B: no dio el error de particion de arranque"
[ ! -e "$TMP/src-efi/EFI/Linux" ] \
|| fail "caso B: creo /efi/EFI/Linux en la particion equivocada (el bug)"
echo " B: UKIs en /boot sin montar, /efi -> ABORTA, ESP de Windows intacta"
# --- C: espejo de B: UKIs en /efi sin montar, /boot montada ---------------
# Al reves: /boot es la primera de la lista, asi que el bug de v3.5 lo sufria
# sin tan siquiera tener que mirar mas.
setup efi boot
rc=$(run_restore)
[ "$rc" -ne 0 ] || fail "caso C: deberia abortar, no restaurar en /boot"
[ ! -e "$TMP/src-boot/EFI/Linux" ] \
|| fail "caso C: creo /boot/EFI/Linux en la particion equivocada (el bug)"
echo " C: UKIs en /efi sin montar, /boot -> ABORTA, /boot intacto"
# --- D: UKIs en /efi, /efi montada -> usa /efi ----------------------------
setup efi efi
rc=$(run_restore)
[ "$rc" -eq 0 ] || fail "caso D: se esperaba exito, rc=$rc"
grep -q "Particion de arranque (UKIs) en: /efi" "$LOG" || fail "caso D: no eligio /efi"
[ -f "$TMP/src-efi/EFI/Linux/$CUR_EFI" ] || fail "caso D: el UKI no quedo en /efi"
[ ! -e "$TMP/src-boot/EFI/Linux" ] || fail "caso D: toco /boot, que no tiene UKIs"
echo " D: UKIs en /efi, montada -> usa /efi, no toca /boot"
# --- E: UKIs en las dos, /boot montada -> preferencia de /boot -------------
setup both boot
rc=$(run_restore)
[ "$rc" -eq 0 ] || fail "caso E: se esperaba exito, rc=$rc"
grep -q "Particion de arranque (UKIs) en: /boot" "$LOG" || fail "caso E: deberia preferir /boot"
echo " E: UKIs en ambas, /boot montada -> prefiere /boot"
# --- F: ninguna particion tiene EFI/Linux -> ABORTA, no adivina ------------
setup none none
rc=$(run_restore)
[ "$rc" -ne 0 ] || fail "caso F: deberia abortar, no restaurar en ninguna parte"
grep -q "No se encontro la particion de arranque" "$LOG" \
|| fail "caso F: no dio el error de particion de arranque"
[ ! -e "$TMP/src-boot/EFI/Linux" ] || fail "caso F: creo /boot/EFI/Linux (no deberia)"
[ ! -e "$TMP/src-efi/EFI/Linux" ] || fail "caso F: creo /efi/EFI/Linux (no deberia)"
echo " F: ninguna particion sirve -> ABORTA sin crear nada"
# --- G: UKIs de un kernel no instalado no se restauran de mas -------------
# Solo hay un UKI en el respaldo y es el vigente: nada mas que copiar.
setup boot boot
printf 'UKI-VIEJO' > "$R/backup/obsoleto-1.0.efi"
old_sha="$(sha256sum "$R/backup/obsoleto-1.0.efi" | awk '{print $1}')"
printf '%s\n' "$old_sha" > "$R/backup/obsoleto-1.0.efi.sha256"
rc=$(run_restore)
[ "$rc" -eq 0 ] || fail "caso G: se esperaba exito, rc=$rc"
[ -f "$TMP/src-boot/EFI/Linux/obsoleto-1.0.efi" ] \
|| fail "caso G: no restauro un UKI que si estaba en el respaldo"
rm -f "$R/backup/obsoleto-1.0.efi" "$R/backup/obsoleto-1.0.efi.sha256"
echo " G: UKI con kernel desconocido -> tambien se restaura"
# --- H: .sha256 en formato `sha256sum` ("hash nombre") --------------------
# El backup hook escribe solo el hash, pero un .sha256 hecho a mano con
# `sha256sum UKI > UKI.sha256` (o cualquier tooling que use ese formato) trae
# el nombre pegado. Antes de v3.6 el hook comparaba el fichero entero contra
# el hash pelado, con lo que ese formato hacia fallar la verificacion y ABORTAR
# la restauracion con un hash perfectamente correcto.
setup boot boot
printf '%s %s\n' "$bare_sha" "$CUR_EFI" > "$R/backup/$CUR_EFI.sha256"
rc=$(run_restore)
[ "$rc" -eq 0 ] \
|| fail "caso H: un .sha256 en formato sha256sum deberia aceptarse, rc=$rc"
grep -q "Checksum verificado para $CUR_EFI" "$LOG" \
|| fail "caso H: no verifico el checksum (hash correcto, formato sha256sum)"
echo " H: .sha256 formato 'sha256sum' -> se acepta y verifica"
# --- I: .sha256 corrupto -> ABORTA, no restaura basura ---------------------
setup boot boot
printf '%064d\n' 0 > "$R/backup/$CUR_EFI.sha256"
rc=$(run_restore)
[ "$rc" -ne 0 ] || fail "caso I: deberia abortar con checksum incorrecto"
grep -q "Checksum fallo para $CUR_EFI" "$LOG" \
|| fail "caso I: no reporto el fallo de checksum"
echo " I: .sha256 corrupto -> ABORTA en vez de restaurar basura"
echo " OK: 9/9 casos (6 de particion de arranque + 3 de checksum)"