多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

nvme-cli 的 shared 静态库:跨组件复用、按主题拆分、`shr_` 前缀约定的通用工具层

nvme-cli 的 shared 静态库:跨组件复用、按主题拆分、`shr_` 前缀约定的通用工具层 CLI存储【免费下载链接】nvme-cliNVMe management command line interface.项目地址https://gitcode.com/gh_mirrors/nv/nvme-cli点击查看免费下载导读shared/是 nvme-cli 仓库中一个没有自然归属地的代码收容层它把那些既不属于 nvme-cli 本体、也不属于 libnvme、nvme-discoverd 或 Python 绑定的通用小工具按主题拆分成一个文件只解决一个问题的独立单元string-util.h、array-util.h、ini-util.h等供四个消费方共同链接复用。本文以 shared/README.md 为骨架结合该目录下各头文件与实现源码完整讲解 shared 库的设计动机、命名空间约定、构建集成方式以及 INI 解析、NQN 校验、字符串工具、指针数组、布尔/CSV 数字解析等代表性模块的实现细节与调用约束帮助读者在 nvme-cli 及 libnvme 生态中正确使用并理解这套小而专的工具层。一、设计动机为什么需要 shared 这一层1.1 四个消费方共享一套工具代码nvme-cli 仓库本身是一个多组件工程CLI 本体src/、NVMe 规范库 libnvme/、后台发现守护进程 nvme-discoverddiscoverd/以及 Python 绑定。这些组件之间会存在大量谁都可能用到、但单独放哪都不合适的基础工具——例如字符串修剪、INI 配置解析、NQN 格式校验、大小写转换、指针数组追加等。如果这些代码散落在各个组件里必然产生重复实现、行为不一致、bug 修复需要多处同步的问题。shared/目录的定位正是解决这个矛盾它收纳在任何单一组件中都没有自然归属地的代码成为四个消费方共用的静态库。1.2 按主题拆分拒绝万能头文件与常见的把所有工具塞进一个utils.h的做法不同shared 库明确采用一个文件只负责一个关注点的组织方式string-util.h只管字符串、array-util.h只管可增长指针数组、ini-util.h只管 INI 语法解析、nqn-util.h只管 NQN 与 Host Identifier 校验。README 指出这种风格松散地模仿了 systemd 的src/basic/——systemd 内部同样以string-util.h、parse-util.h等按主题拆分的头文件组织基础工具。这一设计带来两个直接好处依赖面最小化消费方只需要包含自己真正用到的那一个头文件不会因为引入一个巨型头文件而被迫链接无关代码职责边界清晰每个头文件的自包含注释就是该主题的完整文档维护者改动某一主题时不会误伤其他主题。1.3 通用性红线不得依赖任何单一消费方shared 库有一条硬性约束README 原文强调这里的一切代码必须与任何单一消费方无关——不出现 NVMe 规范类型、不包含 CLI 的打印/显示逻辑、不触碰 discoverd 内部实现。只有这样libnvme 才能安全地链接它而不产生反向依赖四个消费方也才能共享同一份实现。这条红线也解释了 shared 库中为什么会有crypto-util、sha256-util、base64-util这类看似与 NVMe 管理无关的工具——它们服务于 DHCHAP 密钥生成、TLS 预共享密钥等 fabric 安全功能但实现本身是纯通用的因此归入 shared 而非任何单一组件。二、命名空间与包含约定shr_前缀与无伞头文件2.1shr_前缀C 全局符号空间的防碰撞策略C 语言只有一个扁平的全局符号命名空间。shared 库中每一个公开的函数、类型、宏都以shr_前缀开头例如shr_ptrarray_append()、shr_streq0()、enum shr_ini_event。README 明确说明这样做是为了关闭与仓库内其他一切符号包括 libc 本身的碰撞。作为对比仅在一个文件内部使用、没有外部链接的static符号不需要前缀——因为它本来就不存在于全局符号空间不存在碰撞的可能。例如 shared/ini-util.c 中的ini_line()、parse_lines()等内部辅助函数都没有shr_前缀。命名约定与使用示例/* 带前缀的公开 API */ bool shr_nqn_valid(const char *nqn); int shr_ini_parse_buf(const char *text, shr_ini_parse_fn cb, void *ud); /* 文件内 static 辅助函数无前缀 */ static int ini_line(char *s, char **section, unsigned int line, ...);2.2 没有伞头文件按需包含具体头文件shared 库不提供汇总用的伞头文件不存在shared.h。正确用法是#include string-util.h /* 需要字符串工具时 */ #include array-util.h /* 需要指针数组时 */ #include ini-util.h /* 需要 INI 解析时 */并且从 shared 目录外部包含时也不需要shared/前缀——构建系统已经把 shared 目录加入 include path详见下一节直接写string-util.h即可。2.3 构建集成shared_dep依赖对象在仓库根目录的 meson.build 中subdir(shared)声明了shared_dep依赖对象同文件 meson.build 附近可以看到它被作为子目录依赖引入顶层构建。而在 shared/meson.build 中这个依赖对象被定义为shared_dep declare_dependency( include_directories: .., # 把 shared 目录加入 include path link_with: libshared, # 链接 shared 静态库 )其中libshared是一个install: false的静态库shared/meson.build即不安装到系统、仅用于仓库内部链接。这正是 README 中从外部包含时不需要shared/前缀这一说法的构建层面依据。另外shared 库在 Windows 与非 Windows 平台会编译不同的平台相关源文件fs-util-win.c/crypto-util-win.c/proc-util-win.c/sig-util-win.c对应 Windowsfs-util-linux.c/machine-id-util-linux.c/net-util-linux.c/crypto-util-linux.c/proc-util-linux.c/sig-util-linux.c对应其他平台非 Windows 平台还额外依赖 OpenSSLopenssl_dep供 crypto/sha256 等模块使用shared/meson.build。三、许可与依赖策略shared 库采用LGPL-2.1-or-later许可证shared/README.md 及各源文件头部均有 SPDX 标识。README 明确指出这与 libnvme 的许可证保持一致目的就是让 libnvme 也能链接它——如果许可不一致跨组件静态链接会在法律层面产生障碍。外部依赖方面shared 库在构建时依赖仓库自带的 ccan 工具库ccan_dep非 Windows 平台追加 OpenSSL 依赖这保证了诸如 CRC32、SHA-256、base64 等密码学/校验模块无需重复造轮子。四、代表性模块源码解析以下按主题深入几个最具代表性的模块结合头文件与实现源码说明其核心行为。4.1 字符串工具string-util.hstring-util.h 是一个纯头文件模块全部为static inline函数没有对应.c提供了一批 NULL 安全的字符串原语被其他模块广泛复用函数行为关键点shr_streq0()NULL 安全字符串相等比较两个 NULL 相等、一 NULL 一非 NULL 不等、否则等价于strcmp()0消除调用方判空比较样板代码shr_streqcase0()大小写不敏感的shr_streq0()变体底层用strcasecmp()shr_xstrdup()带分配检查的strdup()NULL 输入返回 NULL不触发abort()的安全复制shr_strtolower()原地转小写逐字节tolower()shr_rtrim()/shr_ltrim()/shr_trim()去尾部/头部/两侧空白shr_trim()原地修改覆盖字符串末尾字节为\0shr_buf2str()把定长、不一定以 NUL 结尾的线上字段拷贝为以 NUL 结尾、右修剪的新字符串用%.*s限定读取长度即使字段内含 NUL 也不会越界shr_name_char()/shr_valid_name()/shr_sanitize_name()判断/校验/清洗可作名字的字符字母数字、_、-常用于把设备提供的序列号等字段转成可安全用于文件名的字符串非法字符替换为_shr_startswith()前缀匹配命中则返回越过前缀的指针比strncmp更语义化shr_kv_strip()/shr_kv_keymatch()处理keyvalue行剥空白与#注释整词匹配 key 并返回 value 起始指针轻量 INI/键值行解析的拼图之一shr_linelen()返回不含换行的行长不修改原串此外该头文件在HAVE_STRSEP未定义时还会提供strsep()的内联回退实现注释说明 mingw/MSVC 运行时缺失该函数shared/string-util.h保证跨平台可编译。4.2 可增长指针数组array-util.h/array-util.carray-util.h 定义了struct shr_ptrarray一个容量翻倍增长、起始容量 8的无类型指针数组。它只管理后备数组本身不拥有、也不释放所指向的对象——对象生命周期由调用方负责shr_ptrarray_free()只释放后备数组。核心 APIshr_ptrarray_append()追加元素容量不足时按容量为 0 则设为 8否则翻倍的策略realloc见 shared/array-util.c返回0表示成功-ENOMEM表示分配失败shr_ptrarray_free()释放后备数组并把结构清零shared/array-util.c。构造 NULL 结尾数组的约定如果需要把数组交给期待 NULL 结尾的调用方先追加所有真实元素最后再追加一次NULL——最后一次追加会自动预留额外槽位与普通追加行为完全一致shared/array-util.h。类型安全包装宏SHR_PTRARRAY_DEFINE(name, type)是这一模块最有设计巧思的部分。它一次宏调用同时完成两件事SHR_PTRARRAY_DEFINE(tid_list, struct libnvmf_tid); /* 等价生成 * struct tid_list { struct libnvmf_tid **items; size_t len, cap; }; * int tid_list_append(struct tid_list *a, struct libnvmf_tid *item); * void tid_list_free(struct tid_list *a); */生成的struct name与struct shr_ptrarray字段顺序和类型完全一致因此name##_append()/name##_free()可以通过强制转换直接调用底层实现宏内部还用BUILD_ASSERT(sizeof(struct name) sizeof(struct shr_ptrarray))做编译期校验shared/array-util.h。注释点明由于布局定义与生成强转出自同一次宏展开两者永远不会像两份手写的独立结构体那样发生漂移。4.3 INI 配置解析ini-util.h/ini-util.cini-util.h 是一个最小 INI 语法读取器被 libnvme 的nvme-fabrics.conf和 nvme-cli 的nvme-cli.conf共同使用。它只处理 INI 语法本身不解释任何配置语义——段落与键值对的含义完全交给调用方的回调函数。事件模型解析器通过枚举enum shr_ini_event与回调函数向调用方报告三类事件事件触发条件回调参数含义SHR_INI_SECTION遇到[section]头key为段落名section更新为新段落名SHR_INI_KV遇到key value行空值被区分出来value 为而非当作缺失键SHR_INI_JUNK畸形行key为修剪后的原文回调签名shared/ini-util.htypedef int (*shr_ini_parse_fn)(enum shr_ini_event event, const char *section, const char *key, const char *value, unsigned int line, void *user_data);其中section在首个段落头之前为 NULL畸形段落头会清空当前段落使后续行以section NULL上报避免被错误归属到上一个段落shared/ini-util.h。回调返回非零值会立即终止解析并作为shr_ini_parse_buf()/shr_ini_parse_file()的返回值向上传递。实现要点shared/ini-util.cshr_ini_parse_buf()在私有副本上解析text本身不被修改解析采用逐行处理而非定长行缓冲因此没有行长限制行号始终准确shared/ini-util.cshr_ini_parse_file()对每个配置文件施加1 MiB 大小上限INI_FILE_MAXshared/ini-util.c超限返回-EFBIG文件缺失返回-ENOENT便于调用方把配置不存在当作空配置处理而非错误平台兼容细节在调用fopen()之前先stat()检测目录并在失败后用stat()重新归类错误从而在所有平台含 Windows 的fopen()对目录直接报EACCES的场景统一返回-EISDIRshared/ini-util.c内容安全文件内出现内嵌 NUL 字节会被判定为输入有歧义返回-EINVAL拒绝非文本内容shared/ini-util.c。4.4 NQN 与 Host Identifier 校验nqn-util.h/nqn-util.cnqn-util.h 面向 NVMe fabric 场景提供三类校验/规范化函数其实现依据是 NVMe Base Specification 4.7 节NQN 规则与 5.2.26.1.32.2 节Host Identifier以及 RFC 9562、Boot Specification 1.4 3.1 节shr_nqn_valid()校验一个 NQN 是否可用规则包括shared/nqn-util.c长度不超过SHR_NQN_MAX_LEN223 字节不含结尾\0必须以nqn.前缀开头必须携带yyyy-mm日期码且月份在 01–12 之间日期码后必须跟.且剩余部分非空UUID 格式nqn.2014-08.org.nvmexpress:uuid:的 NQN 必须携带规范 UUID复用shr_uuid_str_valid()。该函数还有两处刻意偏离规范的宽松处理头文件注释明确说明不强制禁止org.nvmexpress作为 format 1 反向域名——因为这种形态的 NQN 已被广泛部署为主机 NQN规范允许任意 UTF-8但实现只接受不含空格的可见 ASCII——实践中所有 NQN 都由反向域名和厂商字符串构成均为 ASCII该限制零成本却能捕获截断读取或尾部空白shared/nqn-util.h。shr_hostid_valid()校验 Host Identifier必须是规范 UUID 字符串且不能是全零 UUID——全零值在规范中没有意义不能用来标识主机shared/nqn-util.c。shr_nqn_normalize()专门处理 UUID 格式 NQN 的大小写归一化若 NQN 是nqn.2014-08.org.nvmexpress:uuid:形态则原地把 UUID 十六进制数字转为小写其他形态的 NQN 保持不动。实现刻意避开tolower()locale 相关手写A–F到a–f的转换shared/nqn-util.c。头文件注释强调归一化应发生在值进入系统时例如 libnvme 刚从固件提供的 NBFT 数据读出主机 NQN 之后这样之后的所有比较都可以保持纯字节级strcmp()符合规范对NQN 比较不得依赖 locale 文本处理的要求。4.5 布尔与 CSV 数字解析parse-util.hparse-util.h 提供两个高频实用解析器shr_parse_bool()遵循 systemd 的parse_boolean()约定大小写不敏感地接受真值假值1、yes、y、true、t、on0、no、n、false、f、off成功返回 0 并写*out两种列表都不匹配返回-EINVAL。这让配置文件中是/否的写法变得非常宽容用户写on或yes效果相同。shr_parse_csv_int()系列把逗号分隔的数字列表解析进val数组最多max_length项每个数字用strtoumax()解析并做目标类型范围检查空字符串视为成功的空操作返回解析到的条目数出错返回-1。系列覆盖uchar/ushort/int/uint/ulonglong/uint128后者在NVME_HAVE_INT128下可用。头文件还定义了shr_parse_csv_u8/u16/u32/u64/u128这组固定宽度别名宏用BUILD_ASSERT_OR_ZERO()在编译期校验*val的宽度与目标类型一致防止把 64 位值塞进 32 位变量这类静默误解析。头文件注释特别解释了__u64的陷阱它在某些 64 位架构如 ppc64le上是unsigned long、在另一些如 x86_64上是unsigned long long并不必然等于uint64_t而unsigned long long是所有平台都恰好 64 位的类型因此shr_parse_csv_u64一律经由shr_parse_csv_ulonglong()路由编译期拒绝宽度不符的调用shared/parse-util.h。五、测试覆盖shared/tests/shared 库配有与模块一一对应的单元测试目录 shared/tests/通过根目录 Meson 配置中的want_tests选项按需构建shared/meson.build。目录内 28 个test-*.c文件与各工具模块对齐例如test-array-util.c 覆盖shr_ptrarray_append()的增长策略、容量翻倍与 NULL 结尾约定test-ini-util.c 覆盖三种事件分类、畸形段落头清空段落、1 MiB 上限与目录/非文本文件报错路径test-nqn-util.c 覆盖 NQN 长度/前缀/日期码/月份、UUID 形态 NQN、全零 Host Identifier 拒绝及大小写归一化test-string-util.c、test-parse-util.c 分别验证字符串原语的 NULL 安全行为与布尔/CSV 数字解析边界。从这些测试文件的存在可以推断shared 库把跨组件复用与可独立验证绑定在一起——每个工具模块都有直接对应的测试任何消费方nvme-cli、libnvme、discoverd、Python 绑定引入的改动都可以在 shared 层先被回归测试兜住。六、使用指引与边界6.1 在哪个文件里找什么需求包含的头文件字符串比较/修剪/大小写/名字清洗string-util.h可增长指针数组含类型安全包装宏array-util.hINI 配置文件解析ini-util.hNQN / Host Identifier 校验与归一化nqn-util.h布尔解析、CSV 数字解析含固定宽度宏parse-util.hCRC32 / SHA-256 / base64 / hexcrc32-util.h、sha256-util.h、base64-util.h、hex-util.h文件系统、时间、UUID、进度显示、表格式化、内存/IO/MMIO 辅助对应*-util.h按需包含6.2 三条使用纪律不要包含伞头文件shared 没有也不会有shared.h按需#include string-util.h这类具体头文件公开符号必须带shr_前缀新增公开 API 时遵循前缀约定文件内static辅助函数例外这样做才能兑现与 libc 及全仓库符号零碰撞的承诺不得引入单一消费方依赖shared 里的代码不能出现 NVMe 规范类型、CLI 打印逻辑或 discoverd 内部实现否则会破坏 libnvme 链接它的前提。七、小结shared/是 nvme-cli 多组件工程里一块精心设计的公共底座以 LGPL-2.1-or-later 许可保证 libnvme 可链接以按主题一文件一关注点的组织保证依赖面最小以shr_前缀保证全局符号零碰撞以shared_dep集成进 Meson 构建并为每个工具模块配备对应单元测试。无论你是要在自己的消费方里复用shr_ini_parse_file()解析 fabric 配置还是借助shr_nqn_valid()校验主机 NQN都可以从 shared/README.md 出发顺着各头文件顶部的注释找到最精确的 API 语义与调用边界。赞分享CLI存储【免费下载链接】nvme-cliNVMe management command line interface.项目地址https://gitcode.com/gh_mirrors/nv/nvme-cli点击查看免费下载相关推荐nvme-cli工具在RHEL 9.2下的NVMe发现功能问题分析nvme cli工具在RHEL 9.2下的NVMe发现功能问题分析 在RHEL 9.2操作系统环境下使用nvme cli 2.11版本时用户遇到了NVMe发现CLI存储PowerShell-Suite实用工具详解从Calculate-Hash到Get-LimitChildItemPowerShell Suite实用工具详解从Calculate Hash到Get LimitChildItem PowerShell Suite是一个功能强如何利用Fluent UI打造高性能微前端共享库组件跨应用复用的终极指南如何利用Fluent UI打造高性能微前端共享库组件跨应用复用的终极指南 在现代前端开发中微前端架构已成为构建大型应用的主流方案但组件跨应用复用始终是开发前端UI组件设计系统上一篇如何快速安装和使用猫抓浏览器嗅探插件新手完整指南下一篇IINA3个简单步骤让Mac视频播放体验升级到专业级创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表