From b05badee231d3f5f3a8ad808397aa9ece272dbc4 Mon Sep 17 00:00:00 2001 From: sergio Date: Sat, 13 Jun 2026 19:35:01 -0400 Subject: [PATCH] =?UTF-8?q?selfhost-verify:=20pieza=203=20(linux-headers?= =?UTF-8?q?=20swap)=20=E2=80=94=20SWAP=5FLINUX=5FHEADERS=3D1,=20byte-id?= =?UTF-8?q?=C3=A9ntico=20a=20Alpine=20en=20host?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tercera pieza del toolchain hammer-from-source (variante b, SDD 11 §7.2b): los headers UAPI del kernel 6.16.12 que musl/busybox #include, hoy tomados del paquete linux-headers de Alpine. - recipes/linux-headers.toml: `make headers` (no `headers_install` — su rsync final falta en el toolchain hermético) + unifdef vía HOSTCC=zig cc; .tar.gz (busybox-tar lo descomprime sin xz). Sella los 13 subdirs kernel-owned de usr/include. - assemble_builder: el swap ahora soporta rel_path de DIRECTORIO (reemplaza el árbol entero: remove + copy), no sólo binarios. Cubierto por test nuevo (builder_rootfs_swaps_toolchain_header_tree_from_source, incl. borrado de huérfanos). - recipes/linux-headers-alpine-compat.patch: el paquete de Alpine no es el `make headers` crudo. Aporta content-pinned 5 archivos que difieren de la salida vainilla; 3 son REQUERIDOS (scsi/{scsi,scsi_ioctl,sg}.h — legacy userspace, NO UAPI) porque el applet `eject` de busybox los incluye y sin ellos no compila. install los pisa sobre /out y limpia el junk (.cmd/Makefile/headers_check.pl/drm) que `cp -a` arrastra. - scripts/selfhost-verify.sh: SWAP_LINUX_HEADERS=1 construye la receta y emite un --swap por subdir kernel-owned (auto-derivado del artefacto sellado). Criterio de éxito fuerte: `diff -r` del header-tree de hammer contra el de Alpine = VACÍO ⇒ el toolchain swapeado es byte-idéntico al baseline ⇒ reproducibilidad por construcción. Ensamblado end-to-end en host OK (13 swaps, musl bits/sys intactos). 133 tests verdes. Pendiente: corrida in-VM para el sello ✓ REPRODUCIBLE. Co-Authored-By: Claude Opus 4.8 --- crates/hammer-bootstrap/src/lib.rs | 71 ++- docs/10-roadmap.md | 18 +- recipes/linux-headers-alpine-compat.patch | 531 ++++++++++++++++++++++ recipes/linux-headers.toml | 72 +++ scripts/selfhost-verify.sh | 17 + 5 files changed, 693 insertions(+), 16 deletions(-) create mode 100644 recipes/linux-headers-alpine-compat.patch create mode 100644 recipes/linux-headers.toml diff --git a/crates/hammer-bootstrap/src/lib.rs b/crates/hammer-bootstrap/src/lib.rs index 581cb28c..9ba82bd2 100644 --- a/crates/hammer-bootstrap/src/lib.rs +++ b/crates/hammer-bootstrap/src/lib.rs @@ -730,29 +730,37 @@ fn assemble_builder(spec: &BuilderSpec, store: &Store, staging: &Path) -> Result } link_or_copy_tree(&spec.toolchain_src, &staging.join("toolchain"), None)?; - // 3b) Swaps de variante (b): herramientas construidas por hammer desde fuente pisan su versión - // Alpine en /toolchain. El binario sellado es estático musl ⇒ corre sin el shim del loader. + // 3b) Swaps de variante (b): piezas construidas por hammer desde fuente pisan su versión Alpine + // en /toolchain. Un `rel_path` puede apuntar a un BINARIO (estático musl ⇒ corre sin el shim + // del loader; p.ej. make, busybox) o a un DIRECTORIO (un árbol de headers; p.ej. los + // `usr/include/{linux,asm,…}` de linux-headers). En el caso directorio se reemplaza el árbol + // entero (remove + copy) para no dejar archivos Alpine huérfanos. for swap in &spec.swaps { let art_dir = store.path_of(&swap.artifact, &swap.name); let src = art_dir.join(&swap.rel_path); - if !src.is_file() { + let dst = staging.join("toolchain").join(&swap.rel_path); + if src.is_dir() { + let _ = std::fs::remove_dir_all(&dst); + std::fs::create_dir_all(&dst)?; + link_or_copy_tree(&src, &dst, None)?; + } else if src.is_file() { + if let Some(parent) = dst.parent() { + std::fs::create_dir_all(parent)?; + } + let _ = std::fs::remove_file(&dst); + if std::fs::hard_link(&src, &dst).is_err() { + std::fs::copy(&src, &dst)?; + } + set_executable(&dst)?; + } else { return Err(Error::Other(format!( - "swap '{}': '{}' no existe en el artefacto sellado {} ({})", + "swap '{}': '{}' no existe (ni archivo ni directorio) en el artefacto sellado {} ({})", swap.name, swap.rel_path, swap.artifact, src.display(), ))); } - let dst = staging.join("toolchain").join(&swap.rel_path); - if let Some(parent) = dst.parent() { - std::fs::create_dir_all(parent)?; - } - let _ = std::fs::remove_file(&dst); - if std::fs::hard_link(&src, &dst).is_err() { - std::fs::copy(&src, &dst)?; - } - set_executable(&dst)?; } // 4) Semilla → /store/-seed-, tal cual está sellada afuera, para que el `hammer` de @@ -1474,7 +1482,42 @@ mod tests { }]; let out = tmp.path().join("builder-out"); let err = builder_rootfs(&spec, &store, &out).unwrap_err().to_string(); - assert!(err.contains("no existe en el artefacto sellado"), "{err}"); + assert!(err.contains("no existe") && err.contains("artefacto sellado"), "{err}"); + } + + #[test] + fn builder_rootfs_swaps_toolchain_header_tree_from_source() { + let tmp = tempfile::tempdir().unwrap(); + let (store, mut spec) = builder_fixture(tmp.path()); + + // Artefacto sellado de un `linux-headers` con un árbol `usr/include/linux` propio. + let headers = seal_component(&store, "linux-headers", "abcd", |w| { + std::fs::create_dir_all(w.join("usr/include/linux")).unwrap(); + std::fs::write(w.join("usr/include/linux/version.h"), b"#define HAMMER 1\n").unwrap(); + std::fs::write(w.join("usr/include/linux/stat.h"), b"struct hammer_stat;\n").unwrap(); + }); + // El toolchain Alpine tiene un linux/ con un archivo huérfano que el swap debe borrar. + let tc_linux = spec.toolchain_src.join("usr/include/linux"); + std::fs::create_dir_all(&tc_linux).unwrap(); + std::fs::write(tc_linux.join("version.h"), b"#define ALPINE 1\n").unwrap(); + std::fs::write(tc_linux.join("orphan.h"), b"alpine-only\n").unwrap(); + + spec.swaps = vec![ToolchainSwap { + name: "linux-headers".into(), + artifact: headers, + rel_path: "usr/include/linux".into(), + }]; + + let out = tmp.path().join("builder-out"); + builder_rootfs(&spec, &store, &out).unwrap(); + + let v = std::fs::read(out.join("toolchain/usr/include/linux/version.h")).unwrap(); + assert_eq!(v, b"#define HAMMER 1\n", "el swap pisó el version.h de Alpine"); + assert!(out.join("toolchain/usr/include/linux/stat.h").is_file(), "copió el árbol entero"); + assert!( + !out.join("toolchain/usr/include/linux/orphan.h").exists(), + "el swap-directorio borró el huérfano de Alpine" + ); } #[test] diff --git a/docs/10-roadmap.md b/docs/10-roadmap.md index 522f5eb3..1cd0bf29 100644 --- a/docs/10-roadmap.md +++ b/docs/10-roadmap.md @@ -256,8 +256,22 @@ Diseño completo en [SDD 11 — Bootstrap from-scratch](11-bootstrap.md). Resume reconstruyó los 4/4 dentro de la VM con el `/toolchain` hammerizado (make `fbad44ac…` + busybox `56664d70…` pisando los de Alpine) y el `of_tree(stage1')` **igualó** la referencia `9adefb82…` ⇒ `✓ REPRODUCIBLE: stage1' == stage1`, `DRIVER_RC=0` (~54 min, arje-zero cu=1 in-VM). **La procedencia - del builder ya no es "todo Alpine" y el auto-alojamiento sigue bit-a-bit.** **Pendiente:** seguir - reemplazando piezas — linux-headers → bwrap → rust/llvm (cada una swapeada y re-verificada igual). + del builder ya no es "todo Alpine" y el auto-alojamiento sigue bit-a-bit.** **Pieza 3 — linux-headers + (`recipes/linux-headers.toml`, kernel 6.16.12, validada en host):** los headers UAPI que musl/busybox + `#include`. Construidos con `make headers` (NO `headers_install`: su `rsync` final falta en el toolchain + hermético) + `unifdef` vía HOSTCC=zig cc. El swap es de **directorio**, no de binario: se extendió + `assemble_builder` para reemplazar árboles enteros (los 13 subdirs kernel-owned `usr/include/{linux,asm, + asm-generic,cxl,fwctl,misc,mtd,rdma,regulator,scsi,sound,video,xen}`), dejando intactos los de musl + (bits/sys/net…). Hallazgo clave: el paquete de Alpine NO es el `make headers` crudo — trae 5 archivos + distintos a la salida vainilla; 3 de ellos (`scsi/{scsi,scsi_ioctl,sg}.h`, legacy userspace, NO UAPI) + son **requeridos** porque el applet `eject` de busybox los incluye y sin ellos no compila. Se aportan + content-pinned vía `recipes/linux-headers-alpine-compat.patch` (+ 2 cosméticos: kernel.h/if_tunnel.h) y + se pisan sobre la salida. Criterio de éxito fuerte alcanzado: **`diff -r` del header-tree hammer contra + el de Alpine = vacío** ⇒ el toolchain swapeado es byte-idéntico al baseline ⇒ todo lo que compile abajo + reproduce de por sí (garantía por construcción, más fuerte que un rebuild). Expuesto con + `SWAP_LINUX_HEADERS=1`; ensamblado end-to-end en host OK. **Pendiente:** la corrida in-VM + (`KVM=1 MEM=24576 SWAP_LINUX_HEADERS=1 ./scripts/selfhost-verify.sh`) para el sello `✓ REPRODUCIBLE`, + y seguir con bwrap → rust/llvm (cada una swapeada y re-verificada igual). - ⏭️ **También pendiente (Stage 1):** bus único (B.2: exponer el `CRASHED` a la capa de IA) y atestación arje (A1/A2). diff --git a/recipes/linux-headers-alpine-compat.patch b/recipes/linux-headers-alpine-compat.patch new file mode 100644 index 00000000..345f4c39 --- /dev/null +++ b/recipes/linux-headers-alpine-compat.patch @@ -0,0 +1,531 @@ +--- /dev/null ++++ b/alpine-overlay/linux/kernel.h +@@ -0,0 +1,10 @@ ++/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ ++#ifndef _LINUX_KERNEL_H ++#define _LINUX_KERNEL_H ++ ++#ifdef __GLIBC__ ++#include ++#endif ++#include ++ ++#endif /* _LINUX_KERNEL_H */ +--- /dev/null ++++ b/alpine-overlay/linux/if_tunnel.h +@@ -0,0 +1,216 @@ ++/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ ++#ifndef _IF_TUNNEL_H_ ++#define _IF_TUNNEL_H_ ++ ++#include ++#include ++ ++ ++#define SIOCGETTUNNEL (SIOCDEVPRIVATE + 0) ++#define SIOCADDTUNNEL (SIOCDEVPRIVATE + 1) ++#define SIOCDELTUNNEL (SIOCDEVPRIVATE + 2) ++#define SIOCCHGTUNNEL (SIOCDEVPRIVATE + 3) ++#define SIOCGETPRL (SIOCDEVPRIVATE + 4) ++#define SIOCADDPRL (SIOCDEVPRIVATE + 5) ++#define SIOCDELPRL (SIOCDEVPRIVATE + 6) ++#define SIOCCHGPRL (SIOCDEVPRIVATE + 7) ++#define SIOCGET6RD (SIOCDEVPRIVATE + 8) ++#define SIOCADD6RD (SIOCDEVPRIVATE + 9) ++#define SIOCDEL6RD (SIOCDEVPRIVATE + 10) ++#define SIOCCHG6RD (SIOCDEVPRIVATE + 11) ++ ++#define GRE_CSUM __cpu_to_be16(0x8000) ++#define GRE_ROUTING __cpu_to_be16(0x4000) ++#define GRE_KEY __cpu_to_be16(0x2000) ++#define GRE_SEQ __cpu_to_be16(0x1000) ++#define GRE_STRICT __cpu_to_be16(0x0800) ++#define GRE_REC __cpu_to_be16(0x0700) ++#define GRE_ACK __cpu_to_be16(0x0080) ++#define GRE_FLAGS __cpu_to_be16(0x0078) ++#define GRE_VERSION __cpu_to_be16(0x0007) ++ ++#define GRE_IS_CSUM(f) ((f) & GRE_CSUM) ++#define GRE_IS_ROUTING(f) ((f) & GRE_ROUTING) ++#define GRE_IS_KEY(f) ((f) & GRE_KEY) ++#define GRE_IS_SEQ(f) ((f) & GRE_SEQ) ++#define GRE_IS_STRICT(f) ((f) & GRE_STRICT) ++#define GRE_IS_REC(f) ((f) & GRE_REC) ++#define GRE_IS_ACK(f) ((f) & GRE_ACK) ++ ++#define GRE_VERSION_0 __cpu_to_be16(0x0000) ++#define GRE_VERSION_1 __cpu_to_be16(0x0001) ++#define GRE_PROTO_PPP __cpu_to_be16(0x880b) ++#define GRE_PPTP_KEY_MASK __cpu_to_be32(0xffff) ++ ++struct ip_tunnel_parm { ++ char name[IFNAMSIZ]; ++ int link; ++ __be16 i_flags; ++ __be16 o_flags; ++ __be32 i_key; ++ __be32 o_key; ++ struct iphdr iph; ++}; ++ ++enum { ++ IFLA_IPTUN_UNSPEC, ++ IFLA_IPTUN_LINK, ++ IFLA_IPTUN_LOCAL, ++ IFLA_IPTUN_REMOTE, ++ IFLA_IPTUN_TTL, ++ IFLA_IPTUN_TOS, ++ IFLA_IPTUN_ENCAP_LIMIT, ++ IFLA_IPTUN_FLOWINFO, ++ IFLA_IPTUN_FLAGS, ++ IFLA_IPTUN_PROTO, ++ IFLA_IPTUN_PMTUDISC, ++ IFLA_IPTUN_6RD_PREFIX, ++ IFLA_IPTUN_6RD_RELAY_PREFIX, ++ IFLA_IPTUN_6RD_PREFIXLEN, ++ IFLA_IPTUN_6RD_RELAY_PREFIXLEN, ++ IFLA_IPTUN_ENCAP_TYPE, ++ IFLA_IPTUN_ENCAP_FLAGS, ++ IFLA_IPTUN_ENCAP_SPORT, ++ IFLA_IPTUN_ENCAP_DPORT, ++ IFLA_IPTUN_COLLECT_METADATA, ++ IFLA_IPTUN_FWMARK, ++ __IFLA_IPTUN_MAX, ++}; ++#define IFLA_IPTUN_MAX (__IFLA_IPTUN_MAX - 1) ++ ++enum tunnel_encap_types { ++ TUNNEL_ENCAP_NONE, ++ TUNNEL_ENCAP_FOU, ++ TUNNEL_ENCAP_GUE, ++ TUNNEL_ENCAP_MPLS, ++}; ++ ++#define TUNNEL_ENCAP_FLAG_CSUM (1<<0) ++#define TUNNEL_ENCAP_FLAG_CSUM6 (1<<1) ++#define TUNNEL_ENCAP_FLAG_REMCSUM (1<<2) ++ ++/* SIT-mode i_flags */ ++#define SIT_ISATAP 0x0001 ++ ++struct ip_tunnel_prl { ++ __be32 addr; ++ __u16 flags; ++ __u16 __reserved; ++ __u32 datalen; ++ __u32 __reserved2; ++ /* data follows */ ++}; ++ ++/* PRL flags */ ++#define PRL_DEFAULT 0x0001 ++ ++struct ip_tunnel_6rd { ++ struct in6_addr prefix; ++ __be32 relay_prefix; ++ __u16 prefixlen; ++ __u16 relay_prefixlen; ++}; ++ ++enum { ++ IFLA_GRE_UNSPEC, ++ IFLA_GRE_LINK, ++ IFLA_GRE_IFLAGS, ++ IFLA_GRE_OFLAGS, ++ IFLA_GRE_IKEY, ++ IFLA_GRE_OKEY, ++ IFLA_GRE_LOCAL, ++ IFLA_GRE_REMOTE, ++ IFLA_GRE_TTL, ++ IFLA_GRE_TOS, ++ IFLA_GRE_PMTUDISC, ++ IFLA_GRE_ENCAP_LIMIT, ++ IFLA_GRE_FLOWINFO, ++ IFLA_GRE_FLAGS, ++ IFLA_GRE_ENCAP_TYPE, ++ IFLA_GRE_ENCAP_FLAGS, ++ IFLA_GRE_ENCAP_SPORT, ++ IFLA_GRE_ENCAP_DPORT, ++ IFLA_GRE_COLLECT_METADATA, ++ IFLA_GRE_IGNORE_DF, ++ IFLA_GRE_FWMARK, ++ IFLA_GRE_ERSPAN_INDEX, ++ IFLA_GRE_ERSPAN_VER, ++ IFLA_GRE_ERSPAN_DIR, ++ IFLA_GRE_ERSPAN_HWID, ++ __IFLA_GRE_MAX, ++}; ++ ++#define IFLA_GRE_MAX (__IFLA_GRE_MAX - 1) ++ ++/* VTI-mode i_flags */ ++#define VTI_ISVTI ((__be16)0x0001) ++ ++enum { ++ IFLA_VTI_UNSPEC, ++ IFLA_VTI_LINK, ++ IFLA_VTI_IKEY, ++ IFLA_VTI_OKEY, ++ IFLA_VTI_LOCAL, ++ IFLA_VTI_REMOTE, ++ IFLA_VTI_FWMARK, ++ __IFLA_VTI_MAX, ++}; ++ ++#define IFLA_VTI_MAX (__IFLA_VTI_MAX - 1) ++ ++/* Historically, tunnel flags have been defined as __be16 and now there are ++ * no free bits left. It is strongly advised to switch the already existing ++ * userspace code to the new *_BIT definitions from down below, as __be16 ++ * can't be simply cast to a wider type on LE systems. All new flags and ++ * code must use *_BIT only. ++ */ ++ ++#define TUNNEL_CSUM __cpu_to_be16(0x01) ++#define TUNNEL_ROUTING __cpu_to_be16(0x02) ++#define TUNNEL_KEY __cpu_to_be16(0x04) ++#define TUNNEL_SEQ __cpu_to_be16(0x08) ++#define TUNNEL_STRICT __cpu_to_be16(0x10) ++#define TUNNEL_REC __cpu_to_be16(0x20) ++#define TUNNEL_VERSION __cpu_to_be16(0x40) ++#define TUNNEL_NO_KEY __cpu_to_be16(0x80) ++#define TUNNEL_DONT_FRAGMENT __cpu_to_be16(0x0100) ++#define TUNNEL_OAM __cpu_to_be16(0x0200) ++#define TUNNEL_CRIT_OPT __cpu_to_be16(0x0400) ++#define TUNNEL_GENEVE_OPT __cpu_to_be16(0x0800) ++#define TUNNEL_VXLAN_OPT __cpu_to_be16(0x1000) ++#define TUNNEL_NOCACHE __cpu_to_be16(0x2000) ++#define TUNNEL_ERSPAN_OPT __cpu_to_be16(0x4000) ++#define TUNNEL_GTP_OPT __cpu_to_be16(0x8000) ++ ++#define TUNNEL_OPTIONS_PRESENT \ ++ (TUNNEL_GENEVE_OPT | TUNNEL_VXLAN_OPT | TUNNEL_ERSPAN_OPT | \ ++ TUNNEL_GTP_OPT) ++ ++enum { ++ IP_TUNNEL_CSUM_BIT = 0U, ++ IP_TUNNEL_ROUTING_BIT, ++ IP_TUNNEL_KEY_BIT, ++ IP_TUNNEL_SEQ_BIT, ++ IP_TUNNEL_STRICT_BIT, ++ IP_TUNNEL_REC_BIT, ++ IP_TUNNEL_VERSION_BIT, ++ IP_TUNNEL_NO_KEY_BIT, ++ IP_TUNNEL_DONT_FRAGMENT_BIT, ++ IP_TUNNEL_OAM_BIT, ++ IP_TUNNEL_CRIT_OPT_BIT, ++ IP_TUNNEL_GENEVE_OPT_BIT, /* OPTIONS_PRESENT */ ++ IP_TUNNEL_VXLAN_OPT_BIT, /* OPTIONS_PRESENT */ ++ IP_TUNNEL_NOCACHE_BIT, ++ IP_TUNNEL_ERSPAN_OPT_BIT, /* OPTIONS_PRESENT */ ++ IP_TUNNEL_GTP_OPT_BIT, /* OPTIONS_PRESENT */ ++ ++ IP_TUNNEL_VTI_BIT, ++ IP_TUNNEL_SIT_ISATAP_BIT = IP_TUNNEL_VTI_BIT, ++ ++ /* Flags starting from here are not available via the old UAPI */ ++ IP_TUNNEL_PFCP_OPT_BIT, /* OPTIONS_PRESENT */ ++ ++ __IP_TUNNEL_FLAG_NUM, ++}; ++ ++#endif /* _IF_TUNNEL_H_ */ +--- /dev/null ++++ b/alpine-overlay/scsi/scsi.h +@@ -0,0 +1,150 @@ ++#ifndef _SCSI_SCSI_H ++#define _SCSI_SCSI_H ++ ++#define TEST_UNIT_READY 0x00 ++#define REZERO_UNIT 0x01 ++#define REQUEST_SENSE 0x03 ++#define FORMAT_UNIT 0x04 ++#define READ_BLOCK_LIMITS 0x05 ++#define REASSIGN_BLOCKS 0x07 ++#define READ_6 0x08 ++#define WRITE_6 0x0a ++#define SEEK_6 0x0b ++#define READ_REVERSE 0x0f ++#define WRITE_FILEMARKS 0x10 ++#define SPACE 0x11 ++#define INQUIRY 0x12 ++#define RECOVER_BUFFERED_DATA 0x14 ++#define MODE_SELECT 0x15 ++#define RESERVE 0x16 ++#define RELEASE 0x17 ++#define COPY 0x18 ++#define ERASE 0x19 ++#define MODE_SENSE 0x1a ++#define START_STOP 0x1b ++#define RECEIVE_DIAGNOSTIC 0x1c ++#define SEND_DIAGNOSTIC 0x1d ++#define ALLOW_MEDIUM_REMOVAL 0x1e ++#define SET_WINDOW 0x24 ++#define READ_CAPACITY 0x25 ++#define READ_10 0x28 ++#define WRITE_10 0x2a ++#define SEEK_10 0x2b ++#define WRITE_VERIFY 0x2e ++#define VERIFY 0x2f ++#define SEARCH_HIGH 0x30 ++#define SEARCH_EQUAL 0x31 ++#define SEARCH_LOW 0x32 ++#define SET_LIMITS 0x33 ++#define PRE_FETCH 0x34 ++#define READ_POSITION 0x34 ++#define SYNCHRONIZE_CACHE 0x35 ++#define LOCK_UNLOCK_CACHE 0x36 ++#define READ_DEFECT_DATA 0x37 ++#define MEDIUM_SCAN 0x38 ++#define COMPARE 0x39 ++#define COPY_VERIFY 0x3a ++#define WRITE_BUFFER 0x3b ++#define READ_BUFFER 0x3c ++#define UPDATE_BLOCK 0x3d ++#define READ_LONG 0x3e ++#define WRITE_LONG 0x3f ++#define CHANGE_DEFINITION 0x40 ++#define WRITE_SAME 0x41 ++#define READ_TOC 0x43 ++#define LOG_SELECT 0x4c ++#define LOG_SENSE 0x4d ++#define MODE_SELECT_10 0x55 ++#define RESERVE_10 0x56 ++#define RELEASE_10 0x57 ++#define MODE_SENSE_10 0x5a ++#define PERSISTENT_RESERVE_IN 0x5e ++#define PERSISTENT_RESERVE_OUT 0x5f ++#define MOVE_MEDIUM 0xa5 ++#define READ_12 0xa8 ++#define WRITE_12 0xaa ++#define WRITE_VERIFY_12 0xae ++#define SEARCH_HIGH_12 0xb0 ++#define SEARCH_EQUAL_12 0xb1 ++#define SEARCH_LOW_12 0xb2 ++#define READ_ELEMENT_STATUS 0xb8 ++#define SEND_VOLUME_TAG 0xb6 ++#define WRITE_LONG_2 0xea ++#define GOOD 0x00 ++#define CHECK_CONDITION 0x01 ++#define CONDITION_GOOD 0x02 ++#define BUSY 0x04 ++#define INTERMEDIATE_GOOD 0x08 ++#define INTERMEDIATE_C_GOOD 0x0a ++#define RESERVATION_CONFLICT 0x0c ++#define COMMAND_TERMINATED 0x11 ++#define QUEUE_FULL 0x14 ++#define STATUS_MASK 0x3e ++#define NO_SENSE 0x00 ++#define RECOVERED_ERROR 0x01 ++#define NOT_READY 0x02 ++#define MEDIUM_ERROR 0x03 ++#define HARDWARE_ERROR 0x04 ++#define ILLEGAL_REQUEST 0x05 ++#define UNIT_ATTENTION 0x06 ++#define DATA_PROTECT 0x07 ++#define BLANK_CHECK 0x08 ++#define COPY_ABORTED 0x0a ++#define ABORTED_COMMAND 0x0b ++#define VOLUME_OVERFLOW 0x0d ++#define MISCOMPARE 0x0e ++#define TYPE_DISK 0x00 ++#define TYPE_TAPE 0x01 ++#define TYPE_PROCESSOR 0x03 ++#define TYPE_WORM 0x04 ++#define TYPE_ROM 0x05 ++#define TYPE_SCANNER 0x06 ++#define TYPE_MOD 0x07 ++#define TYPE_MEDIUM_CHANGER 0x08 ++#define TYPE_ENCLOSURE 0x0d ++#define TYPE_NO_LUN 0x7f ++#define COMMAND_COMPLETE 0x00 ++#define EXTENDED_MESSAGE 0x01 ++#define EXTENDED_MODIFY_DATA_POINTER 0x00 ++#define EXTENDED_SDTR 0x01 ++#define EXTENDED_EXTENDED_IDENTIFY 0x02 ++#define EXTENDED_WDTR 0x03 ++#define SAVE_POINTERS 0x02 ++#define RESTORE_POINTERS 0x03 ++#define DISCONNECT 0x04 ++#define INITIATOR_ERROR 0x05 ++#define ABORT 0x06 ++#define MESSAGE_REJECT 0x07 ++#define NOP 0x08 ++#define MSG_PARITY_ERROR 0x09 ++#define LINKED_CMD_COMPLETE 0x0a ++#define LINKED_FLG_CMD_COMPLETE 0x0b ++#define BUS_DEVICE_RESET 0x0c ++#define INITIATE_RECOVERY 0x0f ++#define RELEASE_RECOVERY 0x10 ++#define SIMPLE_QUEUE_TAG 0x20 ++#define HEAD_OF_QUEUE_TAG 0x21 ++#define ORDERED_QUEUE_TAG 0x22 ++#define SCSI_IOCTL_GET_IDLUN 0x5382 ++#define SCSI_IOCTL_TAGGED_ENABLE 0x5383 ++#define SCSI_IOCTL_TAGGED_DISABLE 0x5384 ++#define SCSI_IOCTL_PROBE_HOST 0x5385 ++#define SCSI_IOCTL_GET_BUS_NUMBER 0x5386 ++ ++struct ccs_modesel_head { ++ unsigned char _r1; ++ unsigned char medium; ++ unsigned char _r2; ++ unsigned char block_desc_length; ++ unsigned char density; ++ unsigned char number_blocks_hi; ++ unsigned char number_blocks_med; ++ unsigned char number_blocks_lo; ++ unsigned char _r3; ++ unsigned char block_length_hi; ++ unsigned char block_length_med; ++ unsigned char block_length_lo; ++}; ++ ++#endif ++ +--- /dev/null ++++ b/alpine-overlay/scsi/scsi_ioctl.h +@@ -0,0 +1,11 @@ ++#ifndef _SCSI_IOCTL_H ++#define _SCSI_IOCTL_H ++#define SCSI_IOCTL_SEND_COMMAND 1 ++#define SCSI_IOCTL_TEST_UNIT_READY 2 ++#define SCSI_IOCTL_BENCHMARK_COMMAND 3 ++#define SCSI_IOCTL_SYNC 4 ++#define SCSI_IOCTL_START_UNIT 5 ++#define SCSI_IOCTL_STOP_UNIT 6 ++#define SCSI_IOCTL_DOORLOCK 0x5380 ++#define SCSI_IOCTL_DOORUNLOCK 0x5381 ++#endif +--- /dev/null ++++ b/alpine-overlay/scsi/sg.h +@@ -0,0 +1,129 @@ ++#ifndef _SCSI_SG_H ++#define _SCSI_SG_H ++ ++#define SG_DXFER_NONE -1 ++#define SG_DXFER_TO_DEV -2 ++#define SG_DXFER_FROM_DEV -3 ++#define SG_DXFER_TO_FROM_DEV -4 ++#define SG_FLAG_DIRECT_IO 1 ++#define SG_FLAG_LUN_INHIBIT 2 ++#define SG_FLAG_NO_DXFER 0x10000 ++#define SG_INFO_OK_MASK 0x1 ++#define SG_INFO_OK 0x0 ++#define SG_INFO_CHECK 0x1 ++#define SG_INFO_DIRECT_IO_MASK 0x6 ++#define SG_INFO_INDIRECT_IO 0x0 ++#define SG_INFO_DIRECT_IO 0x2 ++#define SG_INFO_MIXED_IO 0x4 ++#define SG_EMULATED_HOST 0x2203 ++#define SG_SET_TRANSFORM 0x2204 ++#define SG_GET_TRANSFORM 0x2205 ++#define SG_SET_RESERVED_SIZE 0x2275 ++#define SG_GET_RESERVED_SIZE 0x2272 ++#define SG_GET_SCSI_ID 0x2276 ++#define SG_SET_FORCE_LOW_DMA 0x2279 ++#define SG_GET_LOW_DMA 0x227a ++#define SG_SET_FORCE_PACK_ID 0x227b ++#define SG_GET_PACK_ID 0x227c ++#define SG_GET_NUM_WAITING 0x227d ++#define SG_GET_SG_TABLESIZE 0x227F ++#define SG_GET_VERSION_NUM 0x2282 ++#define SG_SCSI_RESET 0x2284 ++#define SG_SCSI_RESET_NOTHING 0 ++#define SG_SCSI_RESET_DEVICE 1 ++#define SG_SCSI_RESET_BUS 2 ++#define SG_SCSI_RESET_HOST 3 ++#define SG_IO 0x2285 ++#define SG_GET_REQUEST_TABLE 0x2286 ++#define SG_SET_KEEP_ORPHAN 0x2287 ++#define SG_GET_KEEP_ORPHAN 0x2288 ++#define SG_SCATTER_SZ (8 * 4096) ++#define SG_DEFAULT_RETRIES 1 ++#define SG_DEF_FORCE_LOW_DMA 0 ++#define SG_DEF_FORCE_PACK_ID 0 ++#define SG_DEF_KEEP_ORPHAN 0 ++#define SG_DEF_RESERVED_SIZE SG_SCATTER_SZ ++#define SG_MAX_QUEUE 16 ++#define SG_BIG_BUFF SG_DEF_RESERVED_SIZE ++#define SG_MAX_SENSE 16 ++#define SG_SET_TIMEOUT 0x2201 ++#define SG_GET_TIMEOUT 0x2202 ++#define SG_GET_COMMAND_Q 0x2270 ++#define SG_SET_COMMAND_Q 0x2271 ++#define SG_SET_DEBUG 0x227e ++#define SG_NEXT_CMD_LEN 0x2283 ++#define SG_DEFAULT_TIMEOUT (60*100) /* 60*HZ */ ++#define SG_DEF_COMMAND_Q 0 ++#define SG_DEF_UNDERRUN_FLAG 0 ++ ++typedef struct sg_iovec { ++ void *iov_base; ++ unsigned long iov_len; ++} sg_iovec_t; ++ ++typedef struct sg_io_hdr { ++ int interface_id; ++ int dxfer_direction; ++ unsigned char cmd_len; ++ unsigned char mx_sb_len; ++ unsigned short iovec_count; ++ unsigned dxfer_len; ++ void *dxferp; ++ unsigned char *cmdp; ++ unsigned char *sbp; ++ unsigned timeout; ++ unsigned flags; ++ int pack_id; ++ void *usr_ptr; ++ unsigned char status; ++ unsigned char masked_status; ++ unsigned char msg_status; ++ unsigned char sb_len_wr; ++ unsigned short host_status; ++ unsigned short driver_status; ++ int resid; ++ unsigned int duration; ++ unsigned int info; ++} sg_io_hdr_t; ++ ++struct sg_scsi_id { ++ int host_no; ++ int channel; ++ int scsi_id; ++ int lun; ++ int scsi_type; ++ short h_cmd_per_lun; ++ short d_queue_depth; ++ int unused[2]; ++}; ++ ++typedef struct sg_req_info { ++ char req_state; ++ char orphan; ++ char sg_io_owned; ++ char problem; ++ int pack_id; ++ void *usr_ptr; ++ unsigned duration; ++ int unused; ++} sg_req_info_t; ++ ++typedef struct sg_io_hdr Sg_io_hdr; ++typedef struct sg_io_vec Sg_io_vec; ++typedef struct sg_scsi_id Sg_scsi_id; ++typedef struct sg_req_info Sg_req_info; ++ ++struct sg_header { ++ int pack_len; ++ int reply_len; ++ int pack_id; ++ int result; ++ unsigned twelve_byte:1; ++ unsigned target_status:5; ++ unsigned host_status:8; ++ unsigned driver_status:8; ++ unsigned other_flags:10; ++ unsigned char sense_buffer[SG_MAX_SENSE]; ++}; ++ ++#endif diff --git a/recipes/linux-headers.toml b/recipes/linux-headers.toml new file mode 100644 index 00000000..aaab2aa3 --- /dev/null +++ b/recipes/linux-headers.toml @@ -0,0 +1,72 @@ +# Linux kernel UAPI headers 6.16.12 — tercera pieza del toolchain hammer-from-source (SDD 11 §7.2b). +# +# Tras make (pieza 1) y busybox (pieza 2), reemplazamos los headers del kernel que hoy el builder +# toma del paquete `linux-headers` de Alpine (`/toolchain/usr/include/{linux,asm,asm-generic,…}`). +# musl y busybox los `#include` al compilar (syscalls, ioctls, structs UAPI), así que son parte real +# del toolchain, no mero relleno. +# +# Versión clavada a la del toolchain Alpine actual: `usr/include/linux/version.h` declara 6.16.12 +# (LINUX_VERSION_CODE 397324). Construir la MISMA versión es lo que mantiene musl/busybox bit-a-bit: +# un header distinto (un struct/constante que cambió entre versiones) cambiaría el binario resultante. +# +# A diferencia de make/busybox esto NO es un binario: `make headers_install` no compila nada del +# kernel, sólo pasa los headers UAPI por `unifdef` (un toolito host que el propio target construye con +# HOSTCC) y los copia saneados a INSTALL_HDR_PATH. El árbol resultante (`usr/include/*/`) se sella y +# el swap-directorio del builder (variante b) lo monta sobre los de Alpine. +# +# Tarball .tar.gz (no .xz): `tar` lo autodetecta por cabecera y el busybox-tar de adentro descomprime +# gzip nativo, sin depender de un `xz` en el rootfs del Stage 1. El sha256 clava el contenido (ADR 0006). + +name = "linux-headers" +version = "6.16.12" + +[source] +tarball = "https://mirrors.edge.kernel.org/pub/linux/kernel/v6.x/linux-6.16.12.tar.gz" +sha256 = "ffc6af80b014ddebd55e116aa29a9f7a5256c87a29a8a9dd97270b6d49625109" +# Overlay de compatibilidad con el paquete linux-headers de Alpine (ADR 0006: contenido clavado en el +# patch, hasheado al artefacto). El paquete de Alpine NO es el `make headers` crudo: trae 5 archivos +# que difieren de la salida vainilla y que cualquier consumidor userspace (aquí, busybox) necesita +# byte-a-byte para reproducir. El patch los CREA bajo `alpine-overlay/` (no toca el árbol del kernel); +# el `install` los pisa sobre /out tras `make headers`. Por qué overlay y no patch-al-source: `make +# headers` corre unifdef (renombra los guards `_UAPI…`, borra `__force`/`#ifndef __KERNEL__`), así que +# parchear el source daría una salida distinta a la de Alpine; pisar la salida final garantiza igualdad. +# • linux/kernel.h — Alpine envuelve `#include ` en `#ifdef __GLIBC__` (musl ya +# define `struct sysinfo`). Cosmético aquí (nadie del 4/4 lo incluye), pero lo +# igualamos para que `diff -r` contra Alpine quede limpio. +# • linux/if_tunnel.h — Alpine quita 3 `#include ` (chocan con musl). Cosmético. +# • scsi/{scsi,scsi_ioctl,sg}.h — REQUERIDOS: el applet `eject` de busybox hace `#include ` +# y ``. NO son UAPI (no salen de `make headers`); los del kernel en +# `include/scsi/` son `__KERNEL__`-only (no compilan en userspace). Alpine trae +# versiones legacy userspace-limpias; sin ellas busybox no compila ⇒ divergiría. +patches = ["linux-headers-alpine-compat.patch"] + +[build] +compiler = "zig-cc" +target = "x86_64-linux-musl" +link = "static" + +[build.phases] +# El árbol del kernel detecta como BuildSys::Make ⇒ el default de compile sería `make -j$(nproc)`, +# que construiría el kernel ENTERO. No queremos eso: los headers no necesitan compilarse ni config. +# Override a no-op; todo el trabajo va en `install`. +compile = "true" +# El target `headers` (NO `headers_install`) construye `scripts/unifdef` con HOSTCC (zig cc) y satura +# los headers UAPI por unifdef hacia `./usr/include` (las líneas `HDRINST …`). Evitamos +# `headers_install` a propósito: su paso final hace un `rsync` de `usr/include` → INSTALL_HDR_PATH, y +# rsync NO está en el toolchain hermético (ADR 0003) — `headers_install` aborta con "rsync: not found". +# En su lugar copiamos el árbol ya saturado con `cp -a` (determinista: of_tree ignora timestamps y +# write-bit, los headers son 0644 sin exec). ARCH=x86 produce los `asm/` de x86_64. Sella +# `usr/include/{linux,asm,asm-generic,cxl,fwctl,misc,mtd,rdma,regulator,scsi,sound,video,xen}`, +# exactamente los subdirs que el paquete linux-headers de Alpine posee. +# 1) `make headers` satura los UAPI en ./usr/include. 2) copiamos a /out. 3) limpiamos lo que el +# rsync de `headers_install` filtra y que `cp -a` arrastró: los `.*.cmd` de kbuild, el Makefile y +# headers_check.pl del propio árbol, y drm/ (Alpine no lo empaqueta). 4) pisamos el overlay de Alpine +# (los 5 archivos del patch) para casar byte-a-byte. Resultado: `diff -r` contra Alpine = vacío. +install = ''' +make ARCH=x86 HOSTCC='zig cc -mcpu=baseline' headers && \ +mkdir -p /out/usr && cp -a usr/include /out/usr/ && \ +find /out/usr/include -name '.*.cmd' -delete && \ +rm -f /out/usr/include/Makefile /out/usr/include/headers_check.pl && \ +rm -rf /out/usr/include/drm && \ +cp -a alpine-overlay/. /out/usr/include/ +''' diff --git a/scripts/selfhost-verify.sh b/scripts/selfhost-verify.sh index 5a58d893..c2345abd 100755 --- a/scripts/selfhost-verify.sh +++ b/scripts/selfhost-verify.sh @@ -135,6 +135,23 @@ if [[ "${SWAP_BUSYBOX:-0}" == 1 ]]; then say "busybox hammer: $BB_HASH" SWAP_ARGS+=(--swap "busybox=$BB_HASH:bin/busybox") fi +if [[ "${SWAP_LINUX_HEADERS:-0}" == 1 ]]; then + # Pieza 3: los headers UAPI del kernel. NO es un binario sino un árbol — el swap-directorio + # (assemble_builder) reemplaza cada subdir entero. Swapeamos exactamente los subdirs que el + # artefacto sellado produce (linux/asm/asm-generic/cxl/fwctl/misc/mtd/rdma/regulator/scsi/sound/ + # video/xen), que son justo los que el paquete linux-headers de Alpine posee; los de musl + # (bits/sys/net/…) quedan intactos. + say "variante b — construir linux-headers desde fuente y swapear /toolchain/usr/include/{linux,asm,…}" + LH_HASH="$("$HAMMER" --store "$STORE" build recipes/linux-headers.toml | grep -oE 'b3:[0-9a-f]{64}' | tail -1)" + [[ "$LH_HASH" == b3:* ]] || die "no obtuve el hash sellado de linux-headers" + say "linux-headers hammer: $LH_HASH" + LH_INC="$(ls -d "$STORE"/*-linux-headers/usr/include 2>/dev/null | head -1)" + [[ -d "$LH_INC" ]] || die "no encuentro usr/include en el linux-headers sellado" + for d in "$LH_INC"/*/; do + sub="$(basename "$d")" + SWAP_ARGS+=(--swap "linux-headers=${LH_HASH}:usr/include/$sub") + done +fi for s in ${SWAPS:-}; do SWAP_ARGS+=(--swap "$s"); done # 4) Ensamblar el builder con la referencia embebida.