Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmake/Config.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ set(CFBOX_APPLETS
dmesg hexdump more rev cal renice
clear which mount mountpoint mdev chmod chown chgrp
umount swapoff reboot
nc ifconfig ip route
)

foreach(applet IN LISTS CFBOX_APPLETS)
Expand Down
31 changes: 27 additions & 4 deletions document/ai/PLAN.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# CFBox — 当前焦点(批级进度)

> Tier 3(批级,易变)。单一事实源(批级)。全树见 [ROADMAP.md](ROADMAP.md),铁律见 [DIRECTIVES.md](DIRECTIVES.md)。
> **Phase 1.5 代码质量审查 ✅ 完成**(体积 -14%、消 iostream/stoi、统一错误宏、fs 封装扩展,379 测试全绿)
> **v0.3.0 已发布**:L2 rootfs 启动骨架(init/mount/mdev/umount/swapoff/reboot/poweroff,117→123 applet)+ tail -f —— cfbox 在 i.MX6ULL 上作为 PID 1 替代 BusyBox。基线 399 测试 / 418 KB / 123 applet。
> **Phase 2 全部完成**(cp/test/ls/grep/find + sh 全收 8 项)。下一站:Phase 3 网络最小闭环
> Tier 3(批级,易变)。单一事实源(批级)。全树见 [ROADMAP.md](ROADMAP.md),铁律见 [DIRECTIVES.md](DIRECTIVES.md);结构标尺见 [STRUCTURE-TASTE.md](STRUCTURE-TASTE.md),性能标尺见 [PERFORMANCE.md](PERFORMANCE.md)
> **v0.3.0 已发布**:L2 rootfs 启动骨架(init/mount/mdev/umount/swapoff/reboot/poweroff,117→123 applet)+ tail -f —— cfbox 在 i.MX6ULL 上作为 PID 1 替代 BusyBox
> ✅ **Phase 2 全部完成**(cp/test/ls/grep/find + sh 全收 8 项)+ ✅ **结构与性能标尺横切批**(PR#17/#18)—— STRUCTURE-TASTE + banned-pattern/layering gate、PERFORMANCE + io/tar/cmp/md5sum/sed 流式化 + google-benchmark 脚手架。当前基线 **436 GTest / 439 KB size-opt / 123 applet**
> 🔄 **Phase 3 网络最小闭环进行中**:批1(`socket.hpp` + `nc`)✅、批2(`net_util.hpp` + `ifconfig` 显示)✅、批3(`ip`/`route`/`hostname` 深化)✅ —— 当前基线 **452 GTest / 463 KB / 127 applet**。详见 [phase-2-network.md](../todo/phases/phase-2-network.md)
> 状态:✅ DONE / 🔄 NEXT / ⏳ PENDING / ⛔ BLOCKED。每批≈一 commit,完成门 `cmake --build build -j$(nproc) && ctest --test-dir build --output-on-failure` 全绿 + `bash tests/integration/run_all.sh`。

## ✅ Phase 1.5(代码质量审查)已完成 — 2026-05-26
Expand Down Expand Up @@ -45,6 +45,29 @@

> 新增 6 applet(mount/mdev/umount/swapoff/reboot/poweroff),117→123。完整故事见 [changelogs/v0.3.0.md](../../changelogs/v0.3.0.md)。

## ✅ 结构与性能标尺(横切批)— 已合并 PR#17 / PR#18(2026-07)

> Phase 2 收尾后的两轮横切改进,建立两条季级标尺并落地首波优化。非 ROADMAP 某个 Phase,是跨 Phase 的工艺/性能基线(对标 Phase 1.5 的代码质量审查,但聚焦结构与性能维度)。

| 批 | 范围 | 状态 | Commit | 测试 |
|----|------|------|--------|------|
| 结构标尺(PR#17 `feat/test-floor-gates`) | [STRUCTURE-TASTE.md](STRUCTURE-TASTE.md) 季级标尺(职责/DRY/边界/机械护栏)+ 清 banned-pattern + 合并 helper + [tests/check_structure_gates.sh](../../tests/check_structure_gates.sh) banned-pattern/layering gate(CI 守护) | ✅ | a1c1028 / 1ef38be / 623fca7(merge fefcbc2) | 436/0(gate 独立于 GTest,CI 跑) |
| 性能基线(PR#18 `feat/performance`) | [PERFORMANCE.md](PERFORMANCE.md) 季级标尺(wall-clock 不动输出/4 步闭环)+ google-benchmark harness + io/tar/cmp/md5sum/sed 流式化(line reader ~7x、tar O(1) 内存、sed 预编译 ~4x、cmp 早退)+ end-to-end timing 脚本 + armhf `-Wconversion`/charconv 修 | ✅ | e229f05 … 4f154e9(merge f979b8f) | 436/0(benchmark 独立于 GTest) |

> 批级记录见 [notes/2026-07-06-structure-performance.md](../notes/2026-07-06-structure-performance.md)。两批均未增删 applet,GTest 基线沿用 Phase 2 末 436;size-opt 体积 v0.3.0 的 418 KB → 439 KB(+21 KB,主因 io/tar 流式缓冲与 benchmark 链接产物,仍在 ≤ 550 KB 预算内)。

## 🔄 Phase 3(网络最小闭环)— 进行中

> 目标:socket/http/net_util/icmp 基础设施 + ip/ifconfig/route/netstat/ping/traceroute/nslookup/wget/nc/tftp + hostname 深化(11 applet)。详见 [phase-2-network.md](../todo/phases/phase-2-network.md)。每批≈一 commit + 完成门。

| 批 | 范围 | 状态 | Commit | 测试 |
|----|------|------|--------|------|
| 批1(Wave 0) | `include/cfbox/socket.hpp` 基础设施(复用 `io::unique_fd`:make/resolve/dial/listen_on/accept_one/format_addr,双栈、header-only)+ `nc` applet(connect/listen 模式 + poll 双向 relay stdin→sock/sock→stdout + SHUT_WR 半关) | ✅ | 10f811f | 440/1 |
| 批2(Wave 1a) | `include/cfbox/net_util.hpp` 基础设施(`read_interfaces` 解析 /proc/net/dev + ioctl SIOCGIF* 取 flags/mtu/hwaddr/ipv4;`format_ifconfig` BusyBox 多行格式;`ipv4_from_ioctl` memcpy 解 cast-align)+ `ifconfig` applet(`-a`/`IFACE` 只读显示) | ✅ | f4279d6 | 446/1 |
| 批3(Wave 1b) | `net_util.hpp` 扩展(`read_routes` 解析 /proc/net/route tab-hex + `format_route_table` BusyBox route -n + `hex_to_ipv4`/`prefix_len`)+ `ip addr show`(iproute2 风格 index/flags/mtu/link/inet)+ `route -n`(显示)+ `hostname` 深化(`-i`/`-f`/`-d` getaddrinfo + memcpy 解 cast-align) | ✅ | f0ff0db | 452/2 |

> 下一批:Wave 1c `ifconfig` 写操作(ADDR/netmask/up/down/mtu,SIOCSIF*,需 root)+ `netstat`(依赖 net_util `read_sockets` 待建);或转 Wave 2(`ping`/`traceroute`,需 `icmp.hpp` raw socket)。批级记录见 [notes/2026-07-06-phase3-socket-nc.md](../notes/2026-07-06-phase3-socket-nc.md) / [notes/2026-07-06-phase3-ifconfig.md](../notes/2026-07-06-phase3-ifconfig.md) / [notes/2026-07-06-phase3-ip-route-hostname.md](../notes/2026-07-06-phase3-ip-route-hostname.md)。

## OPEN GOTCHAS(跨批陷阱,改前必看)

1. **APPLET_REGISTRY + CMake 开关双改**:新增/裁剪 applet 必须**同时**改 [include/cfbox/applets.hpp](../../include/cfbox/applets.hpp) 注册表与 CMake 的 `CFBOX_ENABLE_*`(生成于 [applet_config.hpp.in](../../include/cfbox/applet_config.hpp.in));漏一处 → 编译期静默跳过或链接错。
Expand Down
2 changes: 1 addition & 1 deletion document/ai/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
- 兼容性裁决见 [compatibility-policy.md](../todo/compatibility-policy.md);v1.0 验收边界见 [v1-production-criteria.md](../todo/v1-production-criteria.md)。

## 当前焦点
**Phase 2 核心命令深化 ✅ 全部完成**(2026-06-28)。批2-5c:`cp -a`、`test` POSIX、`ls -R`/`--color`、`grep -A/-B/-C`、`find` 布尔、`sh` 全收(算术/case/函数/here-doc/高级`${}`/break N/read/trap)。基线 **436 GTest + 57 集成(sh)/ 438 KB** size-opt(v0.3.0 基线 399/418)。批级记录见 [PLAN.md](PLAN.md) 与 [notes/](../notes/)。
**Phase 2 核心命令深化 ✅ 全部完成**(2026-06-28)+ **结构与性能标尺横切批 ✅ 完成**(2026-07,PR#17 / PR#18)。批2-5c:`cp -a`、`test` POSIX、`ls -R`/`--color`、`grep -A/-B/-C`、`find` 布尔、`sh` 全收(算术/case/函数/here-doc/高级`${}`/break N/read/trap);横切批落 STRUCTURE-TASTE + banned-pattern/layering gate(CI 守护)、PERFORMANCE + io/tar/cmp/md5sum/sed 流式化(line reader ~7x、tar O(1)、sed 预编译 ~4x)+ google-benchmark 脚手架。基线 **436 GTest + 57 集成(sh)/ 439 KB** size-opt(v0.3.0 基线 399/418)。批级记录见 [PLAN.md](PLAN.md) 与 [notes/](../notes/)。
> **下一站**:Phase 3 网络最小闭环(基础网络配置/诊断/下载)。

## 当前焦点之后下一个可启动的
Expand Down
31 changes: 31 additions & 0 deletions document/notes/2026-07-06-phase3-ifconfig.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 2026-07-06 — Phase 3 批2:net_util.hpp + ifconfig 显示(Wave 1a)

## 背景
Phase 3 Wave 1 第一锹。`net_util.hpp` 是 Wave 1(ifconfig/ip/route/netstat)共享的接口查询层;`ifconfig -a` 是第一个 read-only 消费者,闭环可集成测(lo 必有)。

## 设计决策

- **net_util 复用 `io::read_all` + `io::split_lines`** 解 /proc/net/dev(对齐 proc.hpp 的 /proc 解析风格),手写 `parse_dev_fields` 解析 16 列计数器——避免 `<sstream>` 撑体积([STRUCTURE-TASTE](../ai/STRUCTURE-TASTE.md) 体积敏感)。
- **ioctl control socket**:一个 `AF_INET/SOCK_DGRAM` fd(`io::unique_fd` 守护)跑所有 SIOCGIF* 查询(flags/mtu/hwaddr/addr/netmask/brdaddr)。
- **`format_ifconfig` BusyBox 多行格式**:name 列宽 10、Link encap/HWaddr、inet addr/Bcast/Mask、flags + MTU、RX/TX counters、bytes 人类可读(KB/MB/GB,1024 进制 BusyBox 标签)。
- **本批只做 read**:`net_util` 只实现 `read_interfaces` + `format_ifconfig`。文档 [phase-2-network.md](../todo/phases/phase-2-network.md) 1.3 设计的 `read_routes`/`read_sockets`/写操作(interface_up/set_addr/add_route)留 B3+。ifconfig 写操作(ADDR/up/down)同批或下批补。

## 踩坑

1. **`-Wcast-align` armhf 32 位盲区**([memory local-cross-compile-selfcheck]):`reinterpret_cast<sockaddr_in*>(&ifr.ifr_addr)` 在 x86-64 静默,armhf `-Werror=cast-align` 报——sockaddr(2 字节对齐)→ sockaddr_in(4 字节对齐)增加对齐。解:`ipv4_from_ioctl` helper 用 `memcpy` 拷到局部 sockaddr_in(无指针 cast,对齐安全)。**这是 memory 警告的 32 位盲区首次真命中**,证明本地 armhf 交叉核查的价值。注意 `sockaddr_storage*` → `sockaddr*`(bound_port/listen_on)方向对齐减小,不报。
2. **lo 显示 `Bcast:0.0.0.0`**:SIOCGIFBRDADDR 对 loopback 返回 0.0.0.0 而非空。BusyBox ifconfig 对 loopback 不显示 Bcast(语义:loopback 无广播)。`format_ifconfig` 加 `!it.loopback()` 条件。
3. **collisions 行重复 `Metric:1`**:初版写 `collisions:0 Metric:1`,BusyBox 是 `collisions:0 txqueuelen:1000`。改 `txqueuelen:1000`(默认值;完整需 SIOCGIFTXQLEN,后续批补)。

## 验证
- GTest **446/446**(+6:NetUtilTest ReadInterfacesHasLoopback/LoopbackHasLocalAddress/ParseDevFields/ParseDevFieldsEmpty/FormatIfconfigLoopback/FormatIfconfigEthernet)。
- 集成 **56 脚本**全绿(test_ifconfig.sh 新增:-a/no-arg/named/bogus-exit)。
- structure gate PASS。
- size-opt **455 KB**(基线 451 → +4 KB,ifconfig + net_util.hpp)。
- armhf static **1243 KB**(B1 后 1235 → +8 KB);qemu-arm-static `ifconfig lo` 输出正确(32 位 ioctl + memcpy 解工作)。
- commit: `f4279d6`

## 陷阱(留给后续批/维护者)
- **只读**:`ifconfig ADDR up/down` 等写操作未实现(HELP 只列 `-a`,避免名实不符)。Wave 1b 补 SIOCSIF*(需 root/CAP_NET_ADMIN)。
- **net_util 未完**:`read_routes`/`read_sockets`(route/netstat 要)未实现,下批按需加。
- **cast-align 教训**:网络代码里 `sockaddr*` → `sockaddr_in*` 的 reinterpret_cast 一律走 memcpy helper,别直接 cast。后续 ip/route/netstat/wget 若遇同模式,复用 `ipv4_from_ioctl` 或同理。
- **Metric:1 / txqueuelen:1000 是写死默认**:真实值需额外 ioctl(SIOCGIFMETRIC/SIOCGIFTXQLEN),下批可补,但对显示兼容非阻塞。
33 changes: 33 additions & 0 deletions document/notes/2026-07-06-phase3-ip-route-hostname.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 2026-07-06 — Phase 3 批3:ip + route + hostname 深化(Wave 1b)

## 背景
Wave 1b:`ip addr show` + `route -n` + `hostname` 深化(`-i`/`-f`/`-d`)。三个都消费 `net_util.hpp`(read_interfaces / read_routes)或 `<netdb.h>`。Wave 1 显示路径基本完整;写操作 + netstat 留 1c。

## 设计决策

- **net_util 扩展 read_routes**:`/proc/net/route` 是 tab 分隔、地址为 host-endian hex。手写 tab-split + `hex_to_ipv4`(strtoul→uint32→直接赋 s_addr,little-endian 目标)。`format_route_table` BusyBox route -n 列布局(Destination/Gateway/Genmask/Flags/Metric/Ref/Use/Iface),flags 位→字母(U/G/H/R/D/M)。
- **prefix_len** 从 netmask 算(inet_pton → ntohl → 数高位连续 1)给 ip 的 `/24` 用。
- **ip addr show**:iproute2 风格(`N: name: <FLAGS> mtu N` + `link/encap` + `inet addr/prefix [brd] scope name`)。flags_angle 把 IFF_* 转 `UP,LOOPBACK,LOWER_UP,...`。本批只 `addr` 子树(`a` 缩写支持),link/route/add 后续。
- **route**:只显示(-n 是默认行为,cfbox 永远 numeric 不查 DNS)。add/del 显式拒绝(exit 2 + 报错)。
- **hostname -i/-f/-d**:`getaddrinfo` 解析本机名得 IPv4 列表(`-i`)/ canonical name(`-f`,`AI_CANONNAME`);`-d` = FQDN 去短名部分。

## 踩坑

1. **`strtoul` 返回 `unsigned long`→`uint32_t` 截断**(IDE `-Wshorten-64-to-32` 抓到):x86-64 `unsigned long` 64 位,赋 uint32 隐式截断。`hex_to_ipv4` 加 `static_cast<std::uint32_t>`。32 位 armhf 无此问题(unsigned long 32 位)但显式 cast 两端安全。
2. **getaddrinfo `ai_addr` cast-align**:`sockaddr*`→`sockaddr_in*` 同 [批2 ipv4_from_ioctl](2026-07-06-phase3-ifconfig.md) 的盲区。hostname 的 `resolve_ipv4` 用 `memcpy(&sa, p->ai_addr, sizeof(sa))` 而非 reinterpret_cast,对齐安全。**复用同一模式**。

## 验证
- GTest **452/452**(+9:NetUtil HexToIpv4RoundTrip/PrefixLen/FormatRouteTableHasHeader + IpTest AddrShowListsLoopback/AddrAbbrevWorks/NoSubcommandExits2)。
- 集成 **58 脚本**全绿(test_ip.sh + test_route.sh 新增)。
- structure gate PASS。
- size-opt **463 KB**(455 → +8 KB,hostname 深化 + ip + route + net_util route 扩展)。
- armhf static **1247 KB**(1243 → +4 KB);qemu-arm-static `ip addr show lo` / `route -n` / `hostname -s` 输出正确。
- commit: `f0ff0db`

## 陷阱(留给后续批/维护者)
- **ip 只 addr 子树**:`ip link`/`ip route`/`ip addr add` 未实现(HELP 只列 addr show)。Wave 1c 或后续补。多文件拆分(文档建议 `src/applets/ip/`)目前单文件够用。
- **route 只读**:add/del(SIOCADDRT/SIOCDELRT)未实现,exit 2。需 root。
- **ifconfig 写操作仍未做**:Wave 1a 留的 gap,1c 补(SIOCSIFADDR/FLAGS,需 root)。
- **hostname -i 依赖 /etc/hosts 解析**:WSL 上返回 127.0.1.1(Debian/Ubuntu 习惯),板子上若 /etc/hosts 配置不同会不同。非 bug,行为同 BusyBox。
- **netstat 未做**:需 net_util `read_sockets`(/proc/net/tcp/udp 解析),Wave 1c。
- **cast-align/shorten 模式复用**:后续 netstat/wget/ping 遇 `sockaddr*`→`sockaddr_in*` 或长整型→uint32 一律 memcpy + static_cast,别直接 cast。
30 changes: 30 additions & 0 deletions document/notes/2026-07-06-phase3-socket-nc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# 2026-07-06 — Phase 3 批1:socket.hpp 基础设施 + nc(Wave 0)

## 背景
Phase 3 网络最小闭环的第一锹。按 [phase-2-network.md](../todo/phases/phase-2-network.md) 依赖图,`socket.hpp` 是所有 Wave 的地基;选 `nc` 作为第一个消费者形成"基础设施 + 可用 applet"闭环(对应文档 3.2 的 nc echo 测试场景)。

## 设计决策

- **socket.hpp 复用 `cfbox::io::unique_fd`,不另造 `SocketFd` 类**(DRY,[STRUCTURE-TASTE](../ai/STRUCTURE-TASTE.md) §一.2)。文档 1.1 建议的 `SocketFd` 是 pre-code 设计;落地时 `io::unique_fd` 已是通用 fd RAII,socket 也是 fd,再包一层是死抽象。落成 header-only inline 自由函数(`make/resolve/dial/listen_on/accept_one/bound_port/format_addr`),对齐 io.hpp 风格。
- **nc relay 单进程 poll(2),不 fork**([PLAN GOTCHA #6](../ai/PLAN.md) 多调用 binary 全局状态)。fork 会污染同进程后续 applet 调用;poll 双向中继无副作用,退出干净。
- **SHUT_WR 半关**:stdin EOF 后 `shutdown(sock, SHUT_WR)`,让 request/response peer 仍能回复(传统 netcat 语义)。
- **双栈**:dial 走 `getaddrinfo(AF_UNSPEC)` 自动 v4/v6;listen_on 按 addr 含 `:` 判 v6。format_addr 用 `inet_ntop`(不用 sprintf,过 structure gate 门 1)。

## 踩坑(本批 3 个,都修了)

1. **`CFBOX_TRY` 不能在返回 `int` 的 applet 入口用**:宏展开是 `return std::unexpected(...)`,而 `nc_main` 返回 int → 编译错 "cannot convert unexpected<Error> to int"。`CFBOX_TRY` 只在返回 `Result<T>` 的函数里用(如 socket.hpp 内部)。applet 入口手动 `auto x = parse_int(...); if (!x) { CFBOX_ERR(...); return 2; }`,对齐 hostname.cpp。
2. **relay 初版把 `local` 当既读又写的 fd**:`relay(STDIN_FILENO, conn)` 里读到的 socket 数据 `write(local=fd0)` —— 写到 fd 0(stdin),数据丢失(手动 echo 复测 `out=''`)。netcat 是 stdin(0)→sock、sock→stdout(1) **两个方向四个 fd**。改成 `relay(sock_fd)` 内部固定 stdin/stdout,server 收到的数据正确落到重定向的 out 文件。
3. **test_nc.sh 的 `kill` 在 `set -e` 下炸**:server 在 client 退出后 relay 自然结束、进程已死,`kill $server` 返回非 0 触发 `set -e`,trap rm 跑了但 `echo`/`[[ ]]` 没跑(exit 1 无输出)。加 `|| true`。

## 验证
- GTest **440/440**(+4:SocketTest LoopbackEcho/ResolveLoopback/FormatAddrV4/FormatAddrV6)。
- 集成 **55 脚本**全绿(test_nc.sh 新增)。
- structure gate PASS(0 sprintf/stoi/裸 fopen/新 layering)。
- size-opt **451 KB**(基线 439 → +12 KB,nc + socket.hpp,≤ 550 KB 预算)。
- commit: `10f811f`

## 陷阱(留给后续批/维护者)
- **nc 是最小版**:只支持 `-l/-p/-s`。文档列的 `-w`(timeout)/`-e`(exec)/`-z`(scan)/UDP 模式均未实现(HELP 只列已实现的,避免名实不符)。Wave 3 收尾或后续批补。
- **socket.hpp 无超时/DNS 缓存/keepalive**:后续 wget/traceroute 按需扩。`dial` 是 happy-eyeballs-lite(逐个试 resolved addr),非并行。
- **armhf 已验证(C 阶段)**:`/opt/arm-gnu-toolchain`(arm-none-linux-gnueabihf gcc 15.2)static 编译,32 位 `-Wconversion` 干净;`qemu-arm-static` 冒烟 nc loopback echo 端到端通过(`armhf-nc-works`,server+client 双进程)。armhf static 体积 1235 KB(v0.3.0 基线 1195 → +40 KB,nc + socket.hpp)。
- **net_util.hpp 还没建**:Wave 1(ifconfig/ip/route/netstat)依赖它读 `/proc/net/*` + ioctl,是下一批的前置。
Loading
Loading