libvirt/virCommand / swtpm

把管道两端和缓冲区一起交给命令对象

virCommandSetSendBuffer 统一接管数据和管道,让虚拟 TPM 调用方只负责取得口令、传入缓冲区并组装参数。

v10.10.0并发与一致性核验 2026-10-09
相关概念

工程问题#

启动 swtpm 前要把口令经管道送给子进程。旧调用方既创建管道,又传递写端和缓冲区,还要在多处失败分支关闭两端、清空已转移指针,容易把资源归属判断分散到不同层。

实际实现#

接收双指针后立即偷取缓冲区并清空调用方指针,本地 g_autofree 接住失败早退;成功后再把缓冲区和管道两端转交命令对象管理。错误记录到 cmd->has_error,统一由命令执行路径报告。

处理流程#

  1. 入口消费缓冲区,使调用方指针立刻变为 NULL
  2. 创建管道并准备非阻塞写端,失败时本地释放并登记错误
  3. 成功后转交 sendBuffers 和 pass-fd 列表,由命令对象负责后续 I/O 与清理

源码片段#

固定版本 · 原文摘录
src/util/vircommand.cv10.10.0 · L1716–L1737
查看原文件
1716virCommandSetSendBuffer(virCommand *cmd,1717                        unsigned char **buffer,1718                        size_t buflen)1719{1720    g_autofree unsigned char *localbuf = g_steal_pointer(buffer);1721    int pipefd[2] = { -1, -1 };1722    size_t i;17231724    if (virCommandHasError(cmd))1725        return -1;17261727    if (virPipeQuiet(pipefd) < 0) {1728        cmd->has_error = errno;1729        return -1;1730    }17311732    if (fcntl(pipefd[1], F_SETFL, O_NONBLOCK) < 0) {1733        cmd->has_error = errno;1734        VIR_FORCE_CLOSE(pipefd[0]);1735        VIR_FORCE_CLOSE(pipefd[1]);1736        return -1;1737    }
virCommandSetSendBuffer

入口通过双指针消费调用方缓冲区,把原指针置空;本地 g_autofree 在早退时负责释放。pipe 或 fcntl 失败写入命令对象的错误状态,fcntl 失败还显式关闭两端 FD。

src/util/vircommand.cv10.10.0 · L1739–L1752
查看原文件
1739    i = virCommandGetNumSendBuffers(cmd);1740    VIR_REALLOC_N(cmd->sendBuffers, i + 1);17411742    cmd->sendBuffers[i].fd = pipefd[1];1743    cmd->sendBuffers[i].buffer = g_steal_pointer(&localbuf);1744    cmd->sendBuffers[i].buflen = buflen;1745    cmd->sendBuffers[i].offset = 0;17461747    cmd->numSendBuffers++;17481749    virCommandPassFD(cmd, pipefd[0], VIR_COMMAND_PASS_FD_CLOSE_PARENT);17501751    return pipefd[0];1752}
virCommandSetSendBuffer · ownership transfer

成功后缓冲区与写端转给 sendBuffers,读端通过 virCommandPassFD 登记。第二次 g_steal_pointer 取消本地自动释放,返回的 FD 仅用于组装参数,调用者不再负责关闭它。

src/qemu/qemu_tpm.cv10.10.0 · L259–L271
查看原文件
259    conn = virGetConnectSecret();260    if (!conn)261        return -1;262263    memcpy(seclookupdef.u.uuid, secretuuid, sizeof(seclookupdef.u.uuid));264    if (virSecretGetSecretString(conn, &seclookupdef,265                                 VIR_SECRET_USAGE_TYPE_VTPM,266                                 &secret, &secret_len) < 0)267        return -1;268269    *fd = virCommandSetSendBuffer(cmd, &secret, secret_len);270    return 0;271}
qemuTPMSetupEncryption

虚拟 TPM 准备取得口令后,把 &secret 直接交给命令对象。函数前部的 secret 声明使用 g_autofree;转移后被置空。SetSendBuffer 内部错误延迟到 virCommand 执行阶段报告,因此此处返回 0 只表示准备流程继续,并不代表管道已成功写完。

两个片段展示同一函数的失败和成功分支,另附 swtpm 准备阶段的实际调用;没有省略号拼接。

收益与取舍#

工程分析

带来的收益

  • 调用方在成功与失败路径遵守同一种缓冲区所有权规则
  • 创建、传递和回收管道收敛到同一个抽象,TPM 调用方不再自行拼接这些步骤

付出的代价

  • 消费式接口不能在失败后保留原始缓冲区重试;调用者必须知道传入后指针会清空
  • 准备步骤把错误留在命令对象中,必须继续检查 virCommandRun 等最终执行结果;g_autofree 也不等于安全擦除

可比较的选择

调用方创建 pipe 并分步转交libvirt 2021 年前的真实实现;每个调用方要协调缓冲区、读端和写端的不同释放时机。

只在成功时接管缓冲区这是可行的 API 约定,但调用方需要根据返回结果切换资源归属;此实现选择无条件消费以减少这种分支。

版本与适用范围#

固定于 libvirt v10.10.0 的非 Windows 实现及 QEMU 虚拟 TPM 调用路径。这是所有权 API 与错误处理写法,不把 GLib 自动清理机制说成 libvirt 的发明。

参考来源#

源码、官方文档与原始论文
01
virCommandSetSendBuffer

src/util/vircommand.c · virCommandSetSendBuffer

源码v10.10.0
02
virCommandSetSendBuffer · ownership transfer

src/util/vircommand.c · virCommandSetSendBuffer · ownership transfer

源码v10.10.0
03
qemuTPMSetupEncryption

src/qemu/qemu_tpm.c · qemuTPMSetupEncryption

源码v10.10.0
04源码ef4c325f25c59b4922cab5b045091d09a5b94635
05源码5c1b5f208a34a62fa9e560d4996d13cd5433214a
06官方文档GLib API 2.0,自 2.44 提供

相关实现与比较#

3 个相关案例
6 个匹配案例