libvirt:统一管道与缓冲区的资源所有权
由命令对象接管管道与缓冲区,统一调用方在成功、失败路径上的资源归属。
1问题背景#
启动 swtpm 前要把口令经管道送给子进程。旧调用方既创建管道,又传递写端和缓冲区,还要在多处失败分支关闭两端、清空已转移指针,容易把资源归属判断分散到不同层。
2原有方案#
原来的 TPM 调用方自行创建 pipe、传递写端,并在失败标签关闭两端;成功后还要手动清空 secret,登记读端。资源转移分散在多次调用之间。
3设计方案#
virCommandSetSendBuffer 将管道创建与资源移交封装为消费式 API:入口先清空调用方的缓冲区指针,失败时由局部自动清理负责,成功时将数据和两端 FD 交给 virCommand。
4改进分析#
调用后无论成功失败,调用方都不再拥有缓冲区,返回的读端 FD 仅用于组装子进程参数。调用方无须逐步判断资源是否已转移;原提交中的清理分支也随之减少。
5源码分析#
5.1缓冲区消费与失败清理#
入口即消费 buffer。即使命令对象已有错误或创建 pipe 失败,调用方也不会再次释放它。
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 }入口通过双指针消费调用方缓冲区,把原指针置空;本地 g_autofree 在早退时负责释放。pipe 或 fcntl 失败写入命令对象的错误状态,fcntl 失败还显式关闭两端 FD。
5.2缓冲区与文件描述符的移交#
数据与写端进入 sendBuffers,读端登记到 pass-fd 列表。局部指针置空后,自动清理不会释放已经移交的缓冲区。
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}成功后缓冲区与写端转给 sendBuffers,读端通过 virCommandPassFD 登记。第二次 g_steal_pointer 取消本地自动释放,返回的 FD 仅用于组装参数,调用者不再负责关闭它。
5.3TPM 调用方的职责简化#
TPM 准备路径传入 &secret,取得用于子进程参数的 FD,无须自行管理管道清理。命令准备阶段的错误保存在 virCommand 中,最终执行时仍须检查结果。
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}虚拟 TPM 准备取得口令后,把 &secret 直接交给命令对象。函数前部的 secret 声明使用 g_autofree;转移后被置空。SetSendBuffer 内部错误延迟到 virCommand 执行阶段报告,因此此处返回 0 只表示准备流程继续,并不代表管道已成功写完。
6适用范围与限制#
失败后原缓冲区不再可用;错误可能留到命令执行阶段才报告。自动释放需要支持 cleanup 属性的编译器,且不代替敏感数据擦除。
固定于 libvirt v10.10.0 的非 Windows 实现及 QEMU 虚拟 TPM 调用路径。这是所有权 API 与错误处理写法,不把 GLib 自动清理机制说成 libvirt 的发明。
7参考资料#
virCommandSetSendBuffer: Provide saner semantics
对比的是 libvirt 原提交中的真实旧实现。这里评价资源归属与错误分支的可读性,不宣称性能提升。