Skip to content

Commit 47c9066

Browse files
Merge pull request #5 from Awesome-Embedded-Learning-Studio/feat/phase3
feat: more applets
2 parents 9542277 + f20d968 commit 47c9066

47 files changed

Lines changed: 3023 additions & 34 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CMakeLists.txt

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ include(cmake/third_party/CPM.cmake)
1515
# ── Compiler flags ────────────────────────────────────────────
1616
include(cmake/compile/CompilerFlag.cmake)
1717

18+
# ── Dependencies ──────────────────────────────────────────────
19+
CPMAddPackage(
20+
NAME zlib
21+
GITHUB_REPOSITORY madler/zlib
22+
GIT_TAG v1.3.1
23+
OPTIONS "CMAKE_POSITION_INDEPENDENT_CODE ON"
24+
)
25+
1826
# ── Applet configuration ─────────────────────────────────────
1927
include(cmake/Config.cmake)
2028

@@ -26,11 +34,15 @@ if(CFBOX_ENABLE_SH)
2634
file(GLOB_RECURSE SH_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/applets/sh/*.cpp)
2735
list(APPEND CFBOX_APPLET_SOURCES ${SH_SOURCES})
2836
endif()
37+
if(CFBOX_ENABLE_AWK)
38+
file(GLOB_RECURSE AWK_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/applets/awk/*.cpp)
39+
list(APPEND CFBOX_APPLET_SOURCES ${AWK_SOURCES})
40+
endif()
2941

3042
# Single-file applets
3143
foreach(applet IN LISTS CFBOX_APPLETS)
3244
string(TOUPPER "${applet}" APPLET_UPPER)
33-
if(NOT applet STREQUAL "sh" AND CFBOX_ENABLE_${APPLET_UPPER})
45+
if(NOT applet STREQUAL "sh" AND NOT applet STREQUAL "awk" AND CFBOX_ENABLE_${APPLET_UPPER})
3446
list(APPEND CFBOX_APPLET_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/applets/${applet}.cpp)
3547
endif()
3648
endforeach()
@@ -40,6 +52,13 @@ add_executable(cfbox src/main.cpp ${CFBOX_APPLET_SOURCES})
4052
target_include_directories(cfbox PUBLIC include)
4153
target_include_directories(cfbox PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/include)
4254
target_link_libraries(cfbox PRIVATE cfbox_compiler_flags)
55+
if(zlib_ADDED)
56+
target_link_libraries(cfbox PRIVATE zlibstatic)
57+
target_include_directories(cfbox SYSTEM PRIVATE ${zlib_SOURCE_DIR} ${zlib_BINARY_DIR})
58+
else()
59+
find_package(ZLIB REQUIRED)
60+
target_link_libraries(cfbox PRIVATE ZLIB::ZLIB)
61+
endif()
4362

4463
# ── GTest via CPM (FetchContent) ──────────────────────────────
4564
if(NOT CMAKE_CROSSCOMPILING)
@@ -62,6 +81,12 @@ if(GTest_ADDED)
6281
cfbox_compiler_flags
6382
GTest::gtest_main
6483
)
84+
if(zlib_ADDED)
85+
target_link_libraries(cfbox_tests PRIVATE zlibstatic)
86+
target_include_directories(cfbox_tests SYSTEM PRIVATE ${zlib_SOURCE_DIR} ${zlib_BINARY_DIR})
87+
else()
88+
target_link_libraries(cfbox_tests PRIVATE ZLIB::ZLIB)
89+
endif()
6590

6691
include(GoogleTest)
6792
gtest_discover_tests(cfbox_tests)

Roadmap.md

Lines changed: 73 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@ CFBox 是一个 C++23 BusyBox 替代品,当前版本有 78 个 applet。项目
1717
| 0 | 构建系统现代化 ✅ | 0 | CMake 配置、help 系统、UTF-8、彩色输出 | 17 |
1818
| 1 | POSIX Shell + Coreutils I ✅ | ~17 | Shell 引擎、进程管理、信号处理 | ~34 |
1919
| 2 | Coreutils II + findutils ✅ | ~44 | 流处理管线、校验和框架 | ~78 |
20-
| 3 | 编辑器 + 归档 + 压缩 | ~15 | 终端抽象、压缩框架 | ~93 |
21-
| 4 | 进程/Init + util-linux | ~38 | /proc 解析器、TUI 框架 | ~131 |
22-
| 5 | 网络 + 登录 + 日志 | ~35 | Socket 抽象、HTTP 解析、shadow 密码 | ~166 |
23-
| 6 | 剩余组件 + 集成验证 | ~40+ | POSIX 验证、容器替换测试 | ~200+ |
20+
| 3 | 归档 + 压缩 + 文本处理 ✅ | ~15 | 终端抽象、压缩框架 | ~93 |
21+
| 4 | vi 可视化编辑器 | 1 | TUI 框架、屏幕渲染、键盘映射 | ~94 |
22+
| 5 | 进程/Init + util-linux | ~38 | /proc 解析器 | ~132 |
23+
| 6 | 网络 + 登录 + 日志 | ~35 | Socket 抽象、HTTP 解析、shadow 密码 | ~167 |
24+
| 7 | 剩余组件 + 集成验证 | ~40+ | POSIX 验证、容器替换测试 | ~200+ |
2425

25-
**当前状态**:Phase 0-2 已完成,78 个 applet,246 单元测试 + 49 集成测试全部通过。
26+
**当前状态**:Phase 0-3 已完成,93 个 applet,259 单元测试 + 54 集成测试全部通过。
2627

2728
---
2829

@@ -121,43 +122,82 @@ Shell 已实现为第一个多文件 applet(`src/applets/sh/`,8 个模块,
121122

122123
---
123124

124-
## Phase 3:编辑器 + 归档 + 压缩
125+
## Phase 3:归档 + 压缩 + 文本处理 ✅
125126

126127
**目标**:实现让 CFBox 能够独立进行系统管理的重量级组件。
127128

128-
### 编辑器
129-
- **awk**`src/applets/awk/`):完整 AWK 实现——词法/语法/解释器,模式-动作、BEGIN/END、字段、关联数组、printf、用户函数、正则匹配。目标 POSIX awk 兼容。第二复杂组件。
130-
- **vi**`src/applets/vi/`):完整可视化编辑器——命令模式、插入模式、ex 命令行、缓冲区管理、光标移动、搜索替换、撤销。
131-
- **diff** + **cmp**:文件比较,支持 unified/context/normal 格式
132-
- **patch**:应用 diff 输出
133-
- **ed**:行编辑器
129+
### 文本处理
130+
- **awk**`src/applets/awk/`~1266 行)✅:完整 AWK 实现——词法/语法/解释器,模式-动作、BEGIN/END、字段、关联数组、printf、用户函数、正则匹配、内置函数(length/substr/split/gsub/match/sprintf)。
131+
- **diff** ✅ + **cmp** ✅:LCS 文件比较,支持 normal 和 unified (-u) 格式
132+
- **patch** ✅:支持 normal 和 unified diff 输入,diff | patch 往返验证通过
133+
- **ed** ✅:行编辑器,支持 a/i/d/p/n/w/q/$ 及带文件名的 w 命令
134134

135135
### 归档
136-
- **tar**`src/applets/tar/`):创建/提取/列出,支持 ustar/GNU/pax 格式
137-
- **cpio**:initramfs 必需,支持 newc/odc 格式
138-
- **ar**:静态库管理
139-
- **unzip**:ZIP 提取
140-
- **dpkg_deb** / **rpm**:基本包提取
136+
- **tar** ✅(`src/applets/tar.cpp`~180 行):创建/提取/列出,ustar 格式,支持 `-C` 目录切换、目录递归
137+
- **cpio** ✅:newc 格式,initramfs 就绪
138+
- **ar** ✅:静态库管理,创建/列出/提取
139+
- **unzip** ✅:ZIP 提取,支持 stored 和 deflated 方法
141140

142-
### 压缩(`include/cfbox/compress/`
143-
- **gzip/gunzip**:DEFLATE 算法
144-
- **xz/unxz**:LZMA2 格式(初期可链接 liblzma)
145-
- **zstd**:(初期可链接 libzstd)
141+
### 压缩
142+
- **gzip/gunzip** ✅:基于 zlib 的 DEFLATE 压缩/解压,文件和 stdin/stdout 双模式
143+
144+
### 基础设施 ✅
145+
- **终端抽象** `include/cfbox/terminal.hpp` ✅:RawMode RAII、终端大小检测、光标控制、备用屏幕缓冲区、视频属性
146+
- **压缩框架** `include/cfbox/compress.hpp` ✅:gzip 压缩/解压接口
147+
148+
### 验证 ✅
149+
- `echo "hello" | gzip | gunzip` 往返正确 ✅
150+
- `tar -cf archive.tar -C DIR files && tar -xf archive.tar` 创建/提取/列出正确 ✅
151+
- `awk '{print $1}' file` / `awk '{s+=$1}END{print s}'` 输出正确 ✅
152+
- `diff file1 file2 | patch file1` 往返正确(normal 和 unified 格式) ✅
153+
- 259 单元测试 + 54 集成测试全部通过 ✅
154+
155+
---
156+
157+
## Phase 4:vi 可视化编辑器
158+
159+
**目标**:实现完整的 vi 可视化编辑器——CFBox 中最复杂的单一组件,需要独立的终端交互框架。
160+
161+
### 核心功能
162+
- **命令模式**:光标移动(h/j/k/l/w/b/e/G/gg/0/$)、删除(x/dd/dw/d$)、复制粘贴(yy/p/P)、撤销/重做(u/Ctrl-R)
163+
- **插入模式**:i/a/I/A/o/O 进入,Esc 退出,文本输入
164+
- **ex 命令行**:w/:q/:wq/:q!/:e/:r/:%s/:%g/:Number/:%!(外部命令过滤)
165+
- **搜索替换**:/pattern 向前搜索、?pattern 向后搜索、n/N 重复、:%s/old/new/g 替换
166+
- **可视化模式**:v 字符选择、V 行选择、基于选择的操作
167+
168+
### 屏幕渲染
169+
- **双缓冲区渲染**:维护逻辑屏幕和物理屏幕,diff 驱动最小更新
170+
- **状态栏**:底行显示文件名、光标位置、模式指示、修改标记
171+
- **滚动**:半页/整页滚动(Ctrl-D/Ctrl-U/Ctrl-F/Ctrl-B),长行折行显示
146172

147173
### 基础设施
148-
- **终端抽象** `include/cfbox/terminal.hpp`:termios raw 模式、转义序列、终端大小、备用屏幕缓冲区
149-
- **压缩框架**:流式压缩/解压接口,允许 tar 透明处理 .tar.gz/.tar.xz
174+
- **TUI 框架** `include/cfbox/tui.hpp`:全屏终端应用抽象,vi/top/less 共用
175+
- 屏幕缓冲区管理、增量渲染
176+
- 键盘事件映射(普通键 + 转义序列解析)
177+
- 信号处理(SIGWINCH 终端大小变化)
178+
179+
### 文件结构
180+
```
181+
src/applets/vi/
182+
├── vi.hpp # 共享类型、ViState、模式枚举
183+
├── vi_buffer.cpp # 文本缓冲区管理(行存储、插入/删除、撤销栈)
184+
├── vi_render.cpp # 屏幕渲染、diff 驱动更新
185+
├── vi_normal.cpp # 命令模式处理
186+
├── vi_insert.cpp # 插入模式处理
187+
├── vi_ex.cpp # ex 命令行解析和执行
188+
├── vi_search.cpp # 搜索替换引擎
189+
└── vi_main.cpp # 入口、事件循环
190+
```
150191

151192
### 验证
152-
- `echo "hello" | gzip | gunzip` 往返正确
153-
- `tar cf - /etc | tar tf -` 列出文件
154-
- `awk '{print $1}' file` 输出正确
155-
- vi 能打开、编辑、保存文件(自动化测试)
156-
- `diff file1 file2 | patch file1` 往返正确
193+
- vi 能打开文件、编辑文本、保存退出
194+
- 自动化测试:通过管道注入按键序列 + 验证输出文件
195+
- 多文件编辑、大文件(10MB+)性能可接受
196+
- 终端大小变化(SIGWINCH)正确响应
157197

158198
---
159199

160-
## Phase 4:进程管理 + Init 系统 + util-linux
200+
## Phase 5:进程管理 + Init 系统 + util-linux
161201

162202
**目标**:构建让 CFBox 适合作为完整 init 环境的系统级工具。
163203

@@ -178,7 +218,7 @@ Shell 已实现为第一个多文件 applet(`src/applets/sh/`,8 个模块,
178218

179219
### 基础设施
180220
- **`/proc` 解析器** `include/cfbox/proc.hpp`:集中解析 /proc/meminfo, /proc/stat, /proc/[pid]/stat 等
181-
- **TUI 框架** `include/cfbox/tui.hpp`:全屏终端应用抽象(top, vi, less 共用)
221+
- TUI 框架已在 Phase 4 实现,top/less 可直接复用
182222

183223
### 验证
184224
- CFBox 作为 PID 1 在 QEMU 中启动,运行 inittab,生成 getty,处理关机
@@ -187,7 +227,7 @@ Shell 已实现为第一个多文件 applet(`src/applets/sh/`,8 个模块,
187227

188228
---
189229

190-
## Phase 5:网络 + 登录管理 + 系统日志
230+
## Phase 6:网络 + 登录管理 + 系统日志
191231

192232
**目标**:添加网络工具和用户/会话管理,使 CFBox 适用于网络启动系统和多用户环境。
193233

@@ -220,7 +260,7 @@ Shell 已实现为第一个多文件 applet(`src/applets/sh/`,8 个模块,
220260

221261
---
222262

223-
## Phase 6:剩余组件 + 全面集成验证
263+
## Phase 7:剩余组件 + 全面集成验证
224264

225265
**目标**:完成所有剩余 BusyBox applet 类别,进行 POSIX 合规验证和集成测试。
226266

@@ -257,7 +297,7 @@ Shell 已实现为第一个多文件 applet(`src/applets/sh/`,8 个模块,
257297
|------|------|---------|
258298
| **Shell 复杂度** | 最高——5000+ 行代码 | 增量构建:先非交互 → 行编辑 → 作业控制,从第一天开始测试 POSIX shell 测试套件 |
259299
| **AWK 复杂度** | 高——第二复杂组件 | 从 POSIX awk 子集开始,实现解释器而非编译器 |
260-
| **vi 复杂度** | 高——终端处理微妙 | 使用 Phase 0 的终端抽象,自动化按键注入测试 |
300+
| **vi 复杂度** | 高——终端处理微妙、独立 Phase 4 | 使用 Phase 3 的终端抽象 + 自建 TUI 框架,自动化按键注入测试,双缓冲 diff 驱动渲染 |
261301
| **二进制体积膨胀** | 中——200+ applet | Phase 0 的 CMake 配置允许裁剪,LTO 和死代码消除,每阶段监控体积 |
262302
| **跨平台边界情况** | 中——ioctl、/proc 格式差异 | 平台特性抽象到 `include/cfbox/` 头文件,每阶段三平台测试 |
263303
| **网络安全** | 中——wget/httpd 需要 TLS | 先做 HTTP-only,HTTPS 通过可选 mbedTLS 依赖,作为 CMake 选项 |

cmake/Config.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ set(CFBOX_APPLETS
2121
timeout nice nohup df
2222
expr tsort
2323
xargs
24+
gzip gunzip diff cmp patch ed tar cpio ar unzip
25+
awk
2426
)
2527

2628
foreach(applet IN LISTS CFBOX_APPLETS)

include/cfbox/applet_config.hpp.in

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,14 @@
8282
#cmakedefine01 CFBOX_ENABLE_EXPR
8383
#cmakedefine01 CFBOX_ENABLE_TSORT
8484
#cmakedefine01 CFBOX_ENABLE_XARGS
85+
#cmakedefine01 CFBOX_ENABLE_GZIP
86+
#cmakedefine01 CFBOX_ENABLE_GUNZIP
87+
#cmakedefine01 CFBOX_ENABLE_DIFF
88+
#cmakedefine01 CFBOX_ENABLE_CMP
89+
#cmakedefine01 CFBOX_ENABLE_PATCH
90+
#cmakedefine01 CFBOX_ENABLE_ED
91+
#cmakedefine01 CFBOX_ENABLE_TAR
92+
#cmakedefine01 CFBOX_ENABLE_CPIO
93+
#cmakedefine01 CFBOX_ENABLE_AR
94+
#cmakedefine01 CFBOX_ENABLE_UNZIP
95+
#cmakedefine01 CFBOX_ENABLE_AWK

include/cfbox/applets.hpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,39 @@ extern auto tsort_main(int argc, char* argv[]) -> int;
238238
#if CFBOX_ENABLE_XARGS
239239
extern auto xargs_main(int argc, char* argv[]) -> int;
240240
#endif
241+
#if CFBOX_ENABLE_GZIP
242+
extern auto gzip_main(int argc, char* argv[]) -> int;
243+
#endif
244+
#if CFBOX_ENABLE_GUNZIP
245+
extern auto gunzip_main(int argc, char* argv[]) -> int;
246+
#endif
247+
#if CFBOX_ENABLE_DIFF
248+
extern auto diff_main(int argc, char* argv[]) -> int;
249+
#endif
250+
#if CFBOX_ENABLE_CMP
251+
extern auto cmp_main(int argc, char* argv[]) -> int;
252+
#endif
253+
#if CFBOX_ENABLE_PATCH
254+
extern auto patch_main(int argc, char* argv[]) -> int;
255+
#endif
256+
#if CFBOX_ENABLE_ED
257+
extern auto ed_main(int argc, char* argv[]) -> int;
258+
#endif
259+
#if CFBOX_ENABLE_TAR
260+
extern auto tar_main(int argc, char* argv[]) -> int;
261+
#endif
262+
#if CFBOX_ENABLE_CPIO
263+
extern auto cpio_main(int argc, char* argv[]) -> int;
264+
#endif
265+
#if CFBOX_ENABLE_AR
266+
extern auto ar_main(int argc, char* argv[]) -> int;
267+
#endif
268+
#if CFBOX_ENABLE_UNZIP
269+
extern auto unzip_main(int argc, char* argv[]) -> int;
270+
#endif
271+
#if CFBOX_ENABLE_AWK
272+
extern auto awk_main(int argc, char* argv[]) -> int;
273+
#endif
241274

242275
// registry — one line per applet, conditionally compiled
243276
constexpr auto APPLET_REGISTRY = std::to_array<cfbox::applet::AppEntry>({
@@ -476,4 +509,37 @@ constexpr auto APPLET_REGISTRY = std::to_array<cfbox::applet::AppEntry>({
476509
#if CFBOX_ENABLE_XARGS
477510
{"xargs", xargs_main, "build and execute command lines from stdin"},
478511
#endif
512+
#if CFBOX_ENABLE_GZIP
513+
{"gzip", gzip_main, "compress files"},
514+
#endif
515+
#if CFBOX_ENABLE_GUNZIP
516+
{"gunzip", gunzip_main, "decompress files"},
517+
#endif
518+
#if CFBOX_ENABLE_DIFF
519+
{"diff", diff_main, "compare files line by line"},
520+
#endif
521+
#if CFBOX_ENABLE_CMP
522+
{"cmp", cmp_main, "compare two files byte by byte"},
523+
#endif
524+
#if CFBOX_ENABLE_PATCH
525+
{"patch", patch_main, "apply a diff file to an original"},
526+
#endif
527+
#if CFBOX_ENABLE_ED
528+
{"ed", ed_main, "line-oriented text editor"},
529+
#endif
530+
#if CFBOX_ENABLE_TAR
531+
{"tar", tar_main, "create, extract, or list tar archives"},
532+
#endif
533+
#if CFBOX_ENABLE_CPIO
534+
{"cpio", cpio_main, "copy files to and from archives"},
535+
#endif
536+
#if CFBOX_ENABLE_AR
537+
{"ar", ar_main, "create, modify, and extract from archives"},
538+
#endif
539+
#if CFBOX_ENABLE_UNZIP
540+
{"unzip", unzip_main, "list, test and extract compressed files in a ZIP archive"},
541+
#endif
542+
#if CFBOX_ENABLE_AWK
543+
{"awk", awk_main, "pattern-directed scanning and processing language"},
544+
#endif
479545
});

include/cfbox/compress.hpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#pragma once
2+
3+
#include <cstdint>
4+
#include <string>
5+
#include <string_view>
6+
#include <vector>
7+
#include <zlib.h>
8+
9+
namespace cfbox::compress {
10+
11+
inline auto gzip_compress(std::string_view data) -> std::string {
12+
z_stream strm{};
13+
deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY);
14+
15+
std::string output;
16+
output.resize(data.size() + data.size() / 10 + 256);
17+
18+
strm.next_in = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(data.data()));
19+
strm.avail_in = static_cast<uInt>(data.size());
20+
strm.next_out = reinterpret_cast<Bytef*>(output.data());
21+
strm.avail_out = static_cast<uInt>(output.size());
22+
23+
deflate(&strm, Z_FINISH);
24+
output.resize(strm.total_out);
25+
deflateEnd(&strm);
26+
return output;
27+
}
28+
29+
inline auto gzip_decompress(std::string_view data) -> std::string {
30+
z_stream strm{};
31+
inflateInit2(&strm, 15 + 16);
32+
33+
std::string output;
34+
output.resize(data.size() * 4 + 4096);
35+
36+
strm.next_in = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(data.data()));
37+
strm.avail_in = static_cast<uInt>(data.size());
38+
39+
int ret;
40+
do {
41+
if (output.size() - strm.total_out < 4096) {
42+
output.resize(output.size() * 2);
43+
}
44+
strm.next_out = reinterpret_cast<Bytef*>(output.data() + strm.total_out);
45+
strm.avail_out = static_cast<uInt>(output.size() - strm.total_out);
46+
ret = inflate(&strm, Z_NO_FLUSH);
47+
} while (ret == Z_OK);
48+
49+
output.resize(strm.total_out);
50+
inflateEnd(&strm);
51+
return output;
52+
}
53+
54+
} // namespace cfbox::compress

0 commit comments

Comments
 (0)