Clang (22.1.8 on the host, 14.0.6 in the Debian VM) now builds the tree
warning-free with --enable-warnings, in-tree and out-of-tree, with -j, and
the full VM battery passes with CC=clang: 12/12. ASan/UBSan builds run the
real tool paths (mkfs, check, tune, resize, rollback save/restore/reject)
with halt_on_error and report zero errors. Documented in tests/vm/RESULTS.md;
the CI gained clang+warnings, out-of-tree and sanitizer jobs.
Fixes found by this work:
- reiserfscore/Makefile.am: reiserfs_err.c and reiserfs_err.h shared one
recipe. With -j, make runs the recipe once per stale target, so two
compile_et invocations race: one deletes reiserfs_err.c ("no input
files") while another job compiles it. Attached the recipe to
reiserfs_err.c only; reiserfs_err.h merely depends on it. Reproduced
with clang+ASan, stable across 5 clang / 3 asan / 3 gcc parallel builds.
- reiserfscore/Makefile.am: the rule read the source-local
reiserfs_err.et, so VPATH builds could not find it. Use $(srcdir)/.
make distcheck now passes.
- reiserfscore/reiserfslib.c: reiserfs_close() did fsync(fs->fs_dev) after
reiserfs_free() released fs -- a 4-byte heap-use-after-free, flagged
exactly at reiserfslib.c:406/407 by ASan (found by clang+ASan; gcc never
caught it because the freed heap usually still holds the old value).
Moved fsync before the free; reiserfs_flush() has already written
journal and dirty buffers at that point and free_buffers() never
writes, so the ordering preserves durability semantics.
- fsck/semantic_check.c, fsck/semantic_rebuild.c: three %K arguments passed
&tmp_deh->deh2_dir_id where a struct reiserfs_key * is expected. It
printed the right values only because deh2_dir_id sits at offset 4 and
the key is read from the pointer as if dir_id sat at offset 0. Build a
proper local key with set_key_dirid()/set_key_objectid() (the same
idiom debugreiserfs/scan.c already uses). Verified -Waddress-of-packed-
member (clang-only) is gone and output is unchanged.
- reiserfscore/hashes.c: four unreachable *(int *)0 = 0 stores (guarded by
len >= 16/12/8/4 tests that cannot hold after the while (len >= 16)
loop, len <= 15). Removed; clang's -Wnull-dereference goes quiet.
Behavior verified: keyed_hash/yura_hash/r5_hash outputs for 16 inputs
from 0 to 17 bytes are byte-identical before and after.
run_in_vm.sh: propagate CC into the guest, force a remount of /mnt/data
(the host rewrites the backing file under the guest), exclude generated
reiserfs_err.[ch] from the source tar. run_tests.sh: report which compiler
the battery actually used.
8.1 KiB
Resultados dos testes em VM
Bateria executada com tests/vm/run_in_vm.sh em duas distribuições, ambas em
KVM (qemu:///session), com o código de 293e6a7.
Ambiente
| Debian 12 (bookworm) | Alpine 3.20.3 | |
|---|---|---|
| Kernel | 6.1.0-54-cloud-amd64 | 6.6.49-0-virt |
| libc | glibc 2.36 | musl 1.2.5 |
| gcc | 12.2.0 | 13.2.1 |
| init | systemd + cloud-init | OpenRC + cloud-init |
Resultado por item
| # | Teste | Debian | Alpine |
|---|---|---|---|
| 1 | autogen.sh |
passa | passa |
| 1 | configure --enable-warnings |
passa | falha (ver abaixo) |
| 1 | make -j |
passa, 0 warnings | não alcançado |
| 2 | mkreiserfs |
passa | não alcançado |
| 3 | reiserfsck --check |
passa | não alcançado |
| 4 | debugreiserfs -S |
passa | não alcançado |
| 5 | reiserfstune -l |
passa | não alcançado |
| 6 | resize_reiserfs 128→256 MiB |
passa | não alcançado |
| 7 | fsck pós-resize |
passa | não alcançado |
| 8 | rollback: grava | passa | não alcançado |
| 8 | rollback: restaura | passa | não alcançado |
| 8 | rollback: magic inválida rejeitada | passa | não alcançado |
| 8 | rollback: arquivo inexistente rejeitado | passa | não alcançado |
Debian: 12/12. Alpine: bloqueado no item 1.
Achado 1 (bloqueante): não compila em musl
O configure do Alpine falha com:
checking for register_printf_modifier... no
checking for register_printf_specifier... no
configure: error: reiserfsprogs requires a method to add printf modifiers
Causa: reiserfscore/prints.c usa register_printf_specifier(3), extensão
glibc-only (<printf.h>). O musl não fornece printf.h nem essas
funções. O configure.ac trata isso como erro fatal explícito — o
comportamento é correto, mas o projeto não compila fora da glibc.
Alcance medido:
| Especificador | Uso |
|---|---|
%k |
139 |
%K |
106 |
%H |
52 |
%M |
25 |
%b |
9 |
%U |
4 |
%y |
2 |
%30H, %20y, %20k, %20K |
4 |
| total | 341 |
Todos passam por reiserfs_warning() / reiserfs_info(). Portar exigiria
substituir o mecanismo por funções de formatação explícitas (por exemplo
char *format_key(const struct reiserfs_key *) com %s), uma mudança de API
interna ampla — não um ajuste local.
Impacto prático: reiserfsprogs fica restrito à glibc. Como o único uso atual da ferramenta é resgate de dados, isso importa: live images de recuperação baseadas em Alpine/musl não conseguem compilar o projeto.
Achado 2 (corrigido nesta rodada): warning real no gcc 12
O gcc 12.2 do Debian acusou, duas vezes:
../include/reiserfs_fs.h:63:54: warning: ‘relocated_ih’ may be used uninitialized
O gcc 16 do host não acusa. Investigação:
fsck/semantic_check.c:620passa&relocated_ihparacheck_semantic_pass();- o bloco que escreveria
*new_ihestá comentado (linhas 471–486); - a chamada em
check_check_regular_file()(linha 466) passaNULLcomonew_ih, com o comentário/* relocate ? new_ih : */ NULL; - portanto
RELOCATEDnunca é retornado por esse caminho ecase RELOCATED:(linha 665) leriarelocated_ihnão inicializado.
Não é alcançável hoje, mas é uma leitura potencial de lixo de pilha atrás de
um #case que o compilador não consegue descartar. Correção: inicialização
explícita = { 0 } com comentário explicando por que permanece zero.
Verificado: 0 warnings no gcc 12.2 após a correção.
Em fsck/semantic_rebuild.c o caminho equivalente é seguro: o bloco
if (relocate) { ... *new_ih = *ih; retval = RELOCATED; } está ativo
(linhas 808–822), então new_ih é escrito antes do case RELOCATED:
(linha 999).
Validação com clang
Ferramentaria: Fedora 44 (host) com clang 22.1.8, Debian 12 na VM com
clang 14.0.6. Todos os builds abaixo usam --enable-warnings
(-Wall -Wextra).
| Build | Resultado |
|---|---|
| host clang in-tree, -j | 0/5 falhas, 0 warnings |
| host clang out-of-tree, -j | passa, 0 warnings |
| host clang + ASan/UBSan out-of-tree, -j | 0/3 falhas, 0 warnings |
| host gcc out-of-tree, -j | 3/3 limpo |
host make distcheck |
passa (VPATH + install/uninstall + tarballs) |
| VM Debian clang 14.0.6, bateria completa | 12/12, 0 warnings |
O que clang expôs (e foi corrigido)
-
Corrida de build paralela em
compile_et(reiserfscore/Makefile.am):reiserfs_err.cereiserfs_err.heram alvos de UMA mesma receita; com-jo make executa a receita duas vezes em paralelo e um processo apaga o.cenquanto o outro compila →clang: error: no input files. Reproduzido com clang+ASan; a correção vale para qualquer compilador. A receita ficou atrelada apenas areiserfs_err.c, ereiserfs_err.h: reiserfs_err.capenas ordena os dois. 5/5 builds paralelos limpos após a correção. -
Build out-of-tree quebrado (pré-existente, também com gcc): a regra referenciava
reiserfs_err.etsem$(srcdir)/. Corrigido junto. -
-Waddress-of-packed-member(só o clang aponta; 3 sítios):fsck/semantic_check.cefsck/semantic_rebuild.cpassavam&tmp_deh->deh2_dir_idcomo argumento%K, que esperastruct reiserfs_key *. Funcionava por coincidência de offsets (deh2_dir_idem 4 lido comok2_dir_idem 0 do cast). Agora montam umareiserfs_keylocal viaset_key_dirid/set_key_objectid— saída idêntica, sem depender de coincidência. -
-Wnull-dereferenceemreiserfscore/hashes.c: quatro stores mortos*(int *)0 = 0protegidos por condições inalcançáveis (após o loopwhile (len >= 16),len <= 15; os ramos testavamlen >= 16/12/8/4). Removidos. Comportamento verificado: saída dekeyed_hash,yura_hasher5_hashpara 16 entradas (vazio a 17 bytes) byte a byte idêntica antes e depois (diff vazio). -
heap-use-after-free real, encontrado por ASan com clang:
reiserfs_close()chamavafsync(fs->fs_dev)DEPOIS dereiserfs_free(), que libera ofs— leitura pendente de 4 bytes em memória libertada (o ASan apontou exatamentereiserfslib.c:406/407; o gcc nunca acusou porque o heap ainda "devolve" o valor velho). Corrigido movendo ofsyncpara antes do free — a escrita já está no dispositivo neste ponto (reiserfs_flush()despeja journal e buffers; osfree_buffers()dereiserfs_free()apenas validam e libertam, sem escrever). Revalidado com ASan+UBSan emhalt_on_error: mkfs, check, tune, debugreiserfs, resize 64→128 MiB e rollback completo — zero erros; bateria completa no host gcc continua 12/12.
Não-bugs registrados
- LeakSanitizer: 136 bytes, 1 alocação em
debugreiserfs(getmemnomain, de vida de processo; oexitrecolhe). Aceitável para ferramenta one-shot; por isso o job de CI com sanitizadores roda comdetect_leaks=0. AddressSanitizer: WARNING: unexpected format specifier ... %U: o interceptor do ASan não conhece os especificadores customizados do projeto. Informativo, uma vez por processo.
Achado 3: limitação do ambiente (não do código)
Nas imagens cloud o cloud-init não aplicou a senha do usuário: o datasource
NoCloud exige o rótulo cidata e o CD correto, e o sshd demora a subir.
O console serial, porém, fica disponível assim que o getty sobe — por isso o
tests/vm/guestcmd.py dirige o pty e é o canal de automação usado. Isso não
afeta o produto; afeta apenas como os testes são conduzidos.
Além disso, a rede user-mode (passt) não entrega DHCP nem DNS por padrão: a
interface sobe como enp1s0 mas fica sem IP. Foi necessário configurar IP e
resolver manualmente para instalar pacotes nas VMs.
Como reproduzir
cd tests/vm
./setup_env.sh # imagens + VMs (uma vez)
./run_in_vm.sh rfs-debian # gcc: build + bateria completa
CC=clang ./run_in_vm.sh rfs-debian # clang no guest (instalar antes: ver README)
./run_in_vm.sh rfs-alpine # deve falhar no configure (esperado)
./teardown_env.sh
# host: builds paralelos in-tree e out-of-tree, distcheck
make distcheck
CC=clang ./configure --enable-warnings && make -j"$(nproc)"
Detalhes do ambiente, topologia e limitações: tests/vm/README.md.