为LibVirt添加新的API

LibVirt 是一套用于控制虚拟化的API,除了提供了一套无关具体虚拟化细节的API 之外,还提供了一个daemon(libvirtd) 和一个控制台工具(virsh)。本文演示了如何在LibVirt 中新加一个API,并且在libvirtdvirsh 中使用新的API 完成新的功能。

为了方便说明,在文章的示例中只演示了添加一个API,如果要看完整的示例,可以查看项目Arondight/libvirt-add-new-api-demo,这是一个相对完整的示例,项目中新API 的说明以及Patch 的使用可以参见其中的README.txt

构建开发环境

首先你需要有一套可以编译的LibVirt 源码,在本文的示例中我们使用了v2.5.0 版本的源码,你可以通过以下指令来得到它。

1
2
git clone https://github.com/libvirt/libvirt.git
pushd libvirt && git checkout v2.5.0 && popd

LibVirt 的编译需要Gnulib 的源码,不过因为网络的原因在墙内其Git 仓库很难获取,所以这里使用GitHub 上的镜像仓库,并通过环境变量引入。你可以设置好这一切并编译一遍源码。在上面的指令执行成功后执行。

1
2
3
4
5
6
git clone https://github.com/coreutils/gnulib.git
export GNULIB_SRCDIR=$(readlink -f ./gnulib)
cd ./libvirt
./autogen.sh
make -j8
make check -j8

如果你的编译依赖完备的话,LibVirt 可以正确编译并通过测试。如果你没有得到预期的结果,请检查你的编译环境并安装缺失的软件包。

示例中我们添加的API 为virConnectGetMagicFileContent,功能为获取运行虚拟化的机器上某个文件内容的最多前32 个字节。

添加公共API

首先要做的是为LibVirt 添加公共API,这个API 也是LibVirt 为用户展现的API。此后通过一连串调用,我们会在libvirtdvirsh 中通过调用这个公共API 来完成新功能。这里需要修改的文件有如下几个。

  1. include/libvirt/libvirt-*.h: 这里需要完成公共API 的声明,此后通过包含头文件include/libvirt/libvirt.h 可调用此API。
  2. src/libvirt_public.syms: 这里需要将新API 导出为全局符号,这样公共API 得以允许被其他函数访问,如果你在步骤[1] 中定义了一个需要被其他函数访问的数据结构,同样你也需要将它导出为全局符号。
  3. src/libvirt-*.c: 这里需要实现步骤[1] 中声明的API,一般来说这里只调用驱动提供的API 即可,具体功能需要在每个hypervisor 的驱动中单独实现。

API 的注释

首先要说明的是,公共API 必须要有合乎规范的注释。在编译时,docs/apibuild.py 会检查宏和公共API 的注释是否符合要求,如果发现不合格的注释,将中断整个编译过程。注释在声明和定义处皆可。

对于一个宏,注释的格式如下。

1
2
3
4
5
6
/**
* MACRO_NAME:
*
* macro's comment.
*/
#define MACRO_NAME (SOMETHING_HERE)

对于一个API,注释的格式如下。

1
2
3
4
5
6
7
8
9
10
11
/**
* apiName:
*
* @arg: arg's comment
*
* synopsis for this api.
*
* Returns what.
*/
ret_type
apiName(arg_type arg) { }

注意:API 注释中的单词Returns 标明了这是返回值的注释,不能随意修改。

声明公共API

目录include/libvirt 下有众多以libvirt- 开头的头文件,公共API 分散在其中。因为新的API 返回在运行虚拟化的主机上某个文件的某段内容,所以我们在头文件include/libvirt/libvirt-host.h 声明这个API。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
diff --git a/include/libvirt/libvirt-host.h b/include/libvirt/libvirt-host.h
index 07b5d1594..72db263d2 100644
--- a/include/libvirt/libvirt-host.h
+++ b/include/libvirt/libvirt-host.h
@@ -686,5 +686,27 @@ int virNodeAllocPages(virConnectPtr conn,
unsigned int cellCount,
unsigned int flags);
+/**
+ * VIR_CONNECT_MAGIC_FILE_PATH:
+ *
+ * This is the absolute path of file.
+ */
+#define VIR_CONNECT_MAGIC_FILE_PATH ("/var/run/libvirt/magic_file")
+
+/**
+ * VIR_CONNECT_MAGIC_FILE_FORBIDDEN_STR:
+ *
+ * If file's content match this, qemu driver will refused to boot VM
+ */
+#define VIR_CONNECT_MAGIC_FILE_FORBIDDEN_STR ("0xabadcafe")
+
+/**
+ * VIR_CONNECT_MAGIC_FILE_CONTENT_LEN:
+ *
+ * Max length of file.
+ */
+#define VIR_CONNECT_MAGIC_FILE_CONTENT_LEN (32)
+
+char *virConnectGetMagicFileContent(virConnectPtr conn);
#endif /* __VIR_LIBVIRT_HOST_H__ */

这个Patch 做的事情非常简单:定义了三个以后会用到的宏,并且声明了公共API。因为这个功能需要访问远程主机上的文件,所以公共API 需要一个参数virConnectPtr,通过这个指针我们可以调用具体的remote 或hypervisor 驱动(前者用于远程调用,后者是真正操纵虚拟化的驱动,例如QEMU 驱动)。

除了这个文件以外,还需要将公共API 在src/libvirt_public.syms 中导出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms
index e01604cad..4db27dc2b 100644
--- a/src/libvirt_public.syms
+++ b/src/libvirt_public.syms
@@ -746,4 +746,9 @@ LIBVIRT_2.2.0 {
virConnectNodeDeviceEventDeregisterAny;
} LIBVIRT_2.0.0;
+LIBVIRT_2.5.0 {
+ global:
+ virConnectGetMagicFileContent;
+} LIBVIRT_2.2.0;
+
# .... define new API here using predicted next version number ....

完成这一步工作之后,新的公共API 就可以被其他的函数所调用。

实现公共API

对应头文件include/libvirt/libvirt-host.h,我们需要在文件src/libvirt-host.c 中实现新API。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
diff --git a/src/libvirt-host.c b/src/libvirt-host.c
index 335798abf..0b8b41ca9 100644
--- a/src/libvirt-host.c
+++ b/src/libvirt-host.c
@@ -1482,3 +1482,36 @@ virNodeAllocPages(virConnectPtr conn,
virDispatchError(conn);
return -1;
}
+
+
+/**
+ * virConnectGetMagicFileContent:
+ *
+ * @conn: virConnect connection
+ *
+ * Get content of magic file, max length is VIR_CONNECT_MAGIC_FILE_CONTENT_LEN.
+ *
+ * Returns content of file if all succeed or NULL upon any failure.
+ */
+char *
+virConnectGetMagicFileContent(virConnectPtr conn)
+{
+ VIR_DEBUG("conn=%p", conn);
+
+ virResetLastError();
+
+ virCheckConnectReturn(conn, NULL);
+
+ if (conn->driver->connectGetMagicFileContent) {
+ char *ret = conn->driver->connectGetMagicFileContent(conn);
+ if (!ret)
+ goto error;
+ return ret;
+ }
+
+ virReportUnsupportedError();
+
+ error:
+ virDispatchError(conn);
+ return NULL;
+}

在这个Patch 里我们虽然实现了公共API,但是没有在其中做具体的操作,而是根据参数conn 调用了驱动connectGetMagicFileContent,具体的工作将由该驱动完成。现在我们无法直接判断该驱动是一个reomte 驱动还是hypervisor 驱动,通常来说如果你正在使用一个运行libvirtd 的远程主机,那么此处将是一个remote 驱动,否则将会直接调用hypervisor 驱动。

到现在为止,假设我们使用virsh get-magic 在标准输出上打印出文件的内容时,函数的调用链如下(假设直接调用hypervisor 驱动)。以后每一部分的工作结束后,我们都将重新整理这个调用链以方便理清我们都做了什么。

??? -> virConnectGetMagicFileContent@LibVirt -> connectGetMagicFileContent@hypervisor -> ???

实现hypervisor 驱动

LibVirt 可用的hypervisor 有很多,这里我们只为最常用的QEMU 编写驱动。

添加内部驱动API

因为LibVirt 在用户层面上提供了统一的API,而这个公共API 调用了一个确定的驱动API。因此我们需要在src/driver-hypervisor.h 中确定这个API 以提供给公共API 调用。后面我们会用到几个结构体变量将这个统一的驱动API 和具体的hypervisor 驱动函数关联起来,然后在hypervisor 驱动中具体的实现它,从而提供无关虚拟化细节的API。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
diff --git a/src/driver-hypervisor.h b/src/driver-hypervisor.h
index 51af73200..78de6b04a 100644
--- a/src/driver-hypervisor.h
+++ b/src/driver-hypervisor.h
@@ -1251,6 +1251,9 @@ typedef int
int state,
unsigned int flags);
+typedef char *
+(*virDrvConnectGetMagicFileContent)(virConnectPtr conn);
+
typedef struct _virHypervisorDriver virHypervisorDriver;
typedef virHypervisorDriver *virHypervisorDriverPtr;
@@ -1489,6 +1492,7 @@ struct _virHypervisorDriver {
virDrvDomainMigrateStartPostCopy domainMigrateStartPostCopy;
virDrvDomainGetGuestVcpus domainGetGuestVcpus;
virDrvDomainSetGuestVcpus domainSetGuestVcpus;
+ virDrvConnectGetMagicFileContent connectGetMagicFileContent;
};

这里我们声明了一个virDrvConnectGetMagicFileContent 类型的函数指针变量,并添加到了结构体类型_virHypervisorDriver 的声明当中,下面在QEMU 驱动中我们会将这个函数指针指向具体的驱动函数。从而完成LibVirt API 到QEMU 驱动函数的调用。

添加hypervisor 公共API

现在我们只需实现QEMU 的驱动函数,并在结构体变量qemuHypervisorDriver 中用新的驱动函数为上一节新加的函数指针赋值即可。这样虽然各个hypervisor 的驱动细节各不相同,但是在LibVirt 上却表现为一致的接口,从而为用于隐藏了具体的虚拟化细节。

注意通常来说驱动具体的功能并不在此实现,而是在qemu/qemu_capabilities.h 中提供一个QEMU 驱动内可见的API,并在qemu/qemu_capabilities.c 中通过一系列函数调用完成驱动的具体功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 3517aa2be..4e108e96a 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -20273,6 +20273,31 @@ qemuDomainSetGuestVcpus(virDomainPtr dom,
}
+static char *
+qemuConnectGetMagicFileContent(virConnectPtr conn)
+{
+ virQEMUDriverPtr driver = conn->privateData;
+ char *ret = NULL;
+ virCapsPtr caps = NULL;
+
+ if (virConnectGetMagicFileContentEnsureACL (conn) < 0) {
+ return NULL;
+ }
+
+ if (!(caps = virQEMUDriverGetCapabilities(driver, false))) {
+ goto cleanup;
+ }
+
+ if (!(ret = virQEMUCapsGetMagicFileContent(caps))) {
+ goto cleanup;
+ }
+
+ cleanup:
+ virObjectUnref(caps);
+ return ret;
+}
+
+
static virHypervisorDriver qemuHypervisorDriver = {
.name = QEMU_DRIVER_NAME,
.connectOpen = qemuConnectOpen, /* 0.2.0 */
@@ -20486,6 +20511,7 @@ static virHypervisorDriver qemuHypervisorDriver = {
.domainMigrateStartPostCopy = qemuDomainMigrateStartPostCopy, /* 1.3.3 */
.domainGetGuestVcpus = qemuDomainGetGuestVcpus, /* 2.0.0 */
.domainSetGuestVcpus = qemuDomainSetGuestVcpus, /* 2.0.0 */
+ .connectGetMagicFileContent = qemuConnectGetMagicFileContent, /* 2.5.0 */
};

这里用到一个权限检查函数virConnectGetMagicFileContentEnsureACL,目前为止我们还没见过它,而它将在我们编写remote 驱动时由src/rpc/gendispatch.pl 生成。

完成hypervisor 驱动的功能

现在我们可以在src/qemu/qemu_capabilities.c 中实现QEMU 驱动具体的功能并在src/qemu/qemu_capabilities.c 中对内部提供一个接口了。这个接口要在src/qemu/qemu_capabilities.h 中声明以便被QEMU 驱动使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c
index 45ab5bbb6..8bf4efc7b 100644
--- a/src/qemu/qemu_capabilities.c
+++ b/src/qemu/qemu_capabilities.c
@@ -5222,3 +5222,45 @@ virQEMUCapsFillDomainCaps(virCapsPtr caps,
return -1;
return 0;
}
+
+
+char *
+virQEMUCapsGetMagicFileContent(virCapsPtr caps ATTRIBUTE_UNUSED)
+{
+ FILE *fh = NULL;
+ char *content = NULL;
+ char *ret = NULL;
+
+ if (-1 == access(VIR_CONNECT_MAGIC_FILE_PATH, R_OK)) {
+ return NULL;
+ }
+
+ if (!(fh = fopen(VIR_CONNECT_MAGIC_FILE_PATH, "r"))) {
+ virReportSystemError(errno, _("failed to open file %s"),
+ VIR_CONNECT_MAGIC_FILE_PATH);
+ return NULL;
+ }
+
+ if (VIR_ALLOC_N(content, VIR_CONNECT_MAGIC_FILE_CONTENT_LEN) < 0) {
+ ret = NULL;
+ goto cleanup;
+ }
+
+ memset (content, 0, VIR_CONNECT_MAGIC_FILE_CONTENT_LEN);
+
+ if (!fgets(content, VIR_CONNECT_MAGIC_FILE_CONTENT_LEN, fh)) {
+ virReportSystemError(errno, _("failed to read file %s"),
+ VIR_CONNECT_MAGIC_FILE_PATH);
+ ret = NULL;
+ goto cleanup;
+ }
+
+ ret = content;
+
+cleanup:
+ if (VIR_FCLOSE (fh) < 0) {
+ virReportSystemError(errno, _("failed to close file %d"), fileno (fh));
+ }
+
+ return ret;
+}
diff --git a/src/qemu/qemu_capabilities.h b/src/qemu/qemu_capabilities.h
index ee4bbb329..4efd31e38 100644
--- a/src/qemu/qemu_capabilities.h
+++ b/src/qemu/qemu_capabilities.h
@@ -525,4 +525,6 @@ int virQEMUCapsFillDomainCaps(virCapsPtr caps,
virFirmwarePtr *firmwares,
size_t nfirmwares);
+char *virQEMUCapsGetMagicFileContent(virCapsPtr caps);
+
#endif /* __QEMU_CAPABILITIES_H__*/

这一部分结束后,直接实现功能的那一部分代码就已经完成了。

现在调用链如下。

??? -> virConnectGetMagicFileContent@LibVirt -> remoteConnectGetMagicFileContent@remote -> qemuConnectGetMagicFileContent@QEMU -> virQEMUCapsGetMagicFileContent@QEMU

实现remote 驱动

remote 协议由两台主机的LibVirt 交换信息所用,当LibVirt 连接到远程主机时(例如virsh -c),之前实现的公共API 中通过conn->driver 结构体变量调用的函数会由remote 驱动处理。本机的LibVirt 将会请求远程的LibVirt 执行公共API,进而执行远程主机具体的hypervisor 驱动,然后得到返回的数据。既然有信息交换,就必须定义协议。

协议的定义涉及到几个文件,其中需要手动修改的文件如下。

  1. src/remote/remote_driver.c: 定义了客户端的remote 驱动处理函数。
  2. src/remote/remote_protocol.x: 协议格式。
  3. src/remote_protocol-structs: 协议格式。

以上文件的前两个会被脚本src/rpc/gendispatch.pl 处理,进而生成以下四个文件。

  1. src/remote/remote_client_bodies.h: 实现了remote 驱动客户端API。
  2. daemon/remote_dispatch.h: 实现了remote 驱动服务器端API。
  3. src/access/viraccessapicheck.h:声明了API 权限检查函数。
  4. src/access/viraccessapicheck.c:实现了API 权限检查函数。

remote 驱动的函数体就实现在前两个头文件中,客户端的API 经过一系列API 调用,最终由函数virNetClientProgramCall 完成信息的交互,其中两个类型为void * 的参数保存了传递给服务器端remote 驱动的参数和服务器端返回的数据,这两个参数的类型由两个类型为xdrproc_t 的参数确定。

实现客户端驱动

src/remote/remote_driver.c 中,我们只要简单的修改结构体变量hypervisor_driver 即可。

1
2
3
4
5
6
7
8
9
10
11
12
diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c
index 888052045..65afda6fb 100644
--- a/src/remote/remote_driver.c
+++ b/src/remote/remote_driver.c
@@ -8205,6 +8205,7 @@ static virHypervisorDriver hypervisor_driver = {
.domainMigrateStartPostCopy = remoteDomainMigrateStartPostCopy, /* 1.3.3 */
.domainGetGuestVcpus = remoteDomainGetGuestVcpus, /* 2.0.0 */
.domainSetGuestVcpus = remoteDomainSetGuestVcpus, /* 2.0.0 */
+ .connectGetMagicFileContent = remoteConnectGetMagicFileContent, /* 2.5.0 */
};
static virNetworkDriver network_driver = {

这里我们只是简单的为结构体变量增加了一个元素,这个元素的类型为函数指针virDrvConnectGetMagicFileContent,在定义内部API 时添加到了类型struct _virHypervisorDriver 的声明当中,值为remoteConnectGetMagicFileContent,这是src/rpc/gendispatch.pl 输出到src/remote/remote_client_bodies.h 中的函数名。

定义协议格式

根据之前说的数据交换方式,我们这里需要定义具体的类型给函数virNetClientProgramCall 的两个xdrproc_t 的参数使用。这里针对每个API 需要定义两个结构体,其名字可以参考其他的结构体和对应的API。后跟_args 的结构体为API 的参数,_ret 的则为返回值,virNetClientProgramCall 会将两个void * 类型的参数分别解释为两个结构体类型,并通过这两个参数完成和远程主机的交互。如果remote 驱动不需要参数,那么可以省略以_args 结尾的结构体。

假设这里我们定义了如下两个结构体。

1
2
3
4
5
6
7
struct remote_connect_abadcafe_args {
remote_nonnull_string str;
};
struct remote_connect_abadcafe_ret {
int need_results;
};

那么它会在文件src/remote/remote_client_bodies.h 中生成类似下面的函数。

1
2
static int
remoteConnectAbadcafe(virConnectPtr conn, const char *str) { }

除此之外,还需要阅读文件src/remote/remote_protocol.x 第403-426 行的注释,特别是insert@offset 相关的说明,你可能会需要它们的。

文件src/remote/remote_protocol.x 的Patch 如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x
index e8382dc51..e5c56220d 100644
--- a/src/remote/remote_protocol.x
+++ b/src/remote/remote_protocol.x
@@ -3341,6 +3341,9 @@ struct remote_domain_set_guest_vcpus_args {
unsigned int flags;
};
+struct remote_connect_get_magic_file_content_ret {
+ remote_nonnull_string content;
+};
/*----- Protocol. -----*/
@@ -5934,5 +5937,12 @@ enum remote_procedure {
* @generate: both
* @acl: none
*/
- REMOTE_PROC_NODE_DEVICE_EVENT_UPDATE = 377
+ REMOTE_PROC_NODE_DEVICE_EVENT_UPDATE = 377,
+
+ /**
+ * @generate: both
+ * @priority: high
+ * @acl: connect:read
+ */
+ REMOTE_PROC_CONNECT_GET_MAGIC_FILE_CONTENT = 378
};

除了之前提到的结构体之外,我们还修改了枚举类型remote_procedure,关于这个类型的具体修改请参阅文件src/remote/remote_protocol.x 第3355-3398 行的详尽注释。

根据设置的参数和返回值结构体,在编译过程中,以下函数会生成。

  1. remoteConnectGetMagicFileContent: remote 驱动客户端API,位于文件src/remote/remote_client_bodies.h
  2. spatchConnectGetMagicFileContent: remote 驱动服务器端API,位于文件daemon/remote_dispatch.h
  3. virConnectGetMagicFileContentEnsureACL:API 权限检查函数,位于文件src/access/viraccessapicheck.c(所以请仔细阅读关于@acl 的注释)。

更新remote_protocol-structs

在上面两个步骤做完之后,只需要更新一下src/remote_protocol-structs 即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs
index b71accc07..383a5361d 100644
--- a/src/remote_protocol-structs
+++ b/src/remote_protocol-structs
@@ -2791,6 +2791,9 @@ struct remote_domain_set_guest_vcpus_args {
int state;
u_int flags;
};
+struct remote_connect_get_magic_file_content_ret {
+ remote_nonnull_string content;
+};
enum remote_procedure {
REMOTE_PROC_CONNECT_OPEN = 1,
REMOTE_PROC_CONNECT_CLOSE = 2,
@@ -3169,4 +3172,5 @@ enum remote_procedure {
REMOTE_PROC_CONNECT_NODE_DEVICE_EVENT_DEREGISTER_ANY = 375,
REMOTE_PROC_NODE_DEVICE_EVENT_LIFECYCLE = 376,
REMOTE_PROC_NODE_DEVICE_EVENT_UPDATE = 377,
+ REMOTE_PROC_CONNECT_GET_MAGIC_FILE_CONTENT = 378
};

现在调用链如下,因为现在增加了客户端和服务端的概念,所以通过在其后增加@client@server 区分。

??? -> virConnectGetMagicFileContent@LibVirt@client -> remoteConnectGetMagicFileContent@remote@client -> remoteDispatchConnectGetMagicFileContent@remote@server -> virConnectGetMagicFileContent@LibVirt@server -> qemuConnectGetMagicFileContent@QEMU@server -> virQEMUCapsGetMagicFileContent@QEMU@server

在virsh 中实现功能

最后要做的就是在virsh 中添加一个命令行选项,完成之前实现的公共API 的调用,并且将API 返回的数据打印到屏幕上。

你需要修改tools/virsh-*.c 以接受新的命令行选项。对于一个新的参数,你需要在hostAndHypervisorCmds 结构体数组中添加新的元素,并根据这个结构体中元素的值来定义两个结构体数组,类型分别为vshCmdInfovshCmdOptDef,分别用来确定新选项的说明和参数。

针对我们实现公共API 的位置,这里我们在tools/virsh-host.c 中添加新的选项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
diff --git a/tools/virsh-host.c b/tools/virsh-host.c
index 2fd368662..ed0c39f5d 100644
--- a/tools/virsh-host.c
+++ b/tools/virsh-host.c
@@ -1379,6 +1379,41 @@ cmdNodeMemoryTune(vshControl *ctl, const vshCmd *cmd)
goto cleanup;
}
+/*
+ * "get-magic" command
+ */
+static const vshCmdInfo info_getmagic[] = {
+ {.name = "help",
+ .data = N_("Get magic file's content")
+ },
+ {.name = "desc",
+ .data = N_("Get magic file's content")
+ },
+ {.name = NULL}
+};
+
+static const vshCmdOptDef opts_getmagic[] = {
+ {.name = NULL}
+};
+
+static bool
+cmdGetMagic(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
+{
+ char *ret = NULL;
+ virshControlPtr priv = ctl->privData;
+
+ ret = virConnectGetMagicFileContent(priv->conn);
+ if (!ret) {
+ vshError(ctl, "%s", _("failed to get magic file's content"));
+ return false;
+ }
+
+ vshPrint(ctl, _("Magic file's content: %s"), ret);
+ VIR_FREE (ret);
+
+ return true;
+}
+
const vshCmdDef hostAndHypervisorCmds[] = {
{.name = "allocpages",
.handler = cmdAllocpages,
@@ -1482,5 +1517,11 @@ const vshCmdDef hostAndHypervisorCmds[] = {
.info = info_version,
.flags = 0
},
+ {.name = "get-magic",
+ .handler = cmdGetMagic,
+ .opts = opts_getmagic,
+ .info = info_getmagic,
+ .flags = 0
+ },
{.name = NULL}
};

最后修改一下tools/virsh.pod,这个文件将会被pod2man 处理成virsh(1) 的手册。POD 是源于Perl 的简单易用的标记语言,可以通过perldoc perlpod 来查看其语法的更多说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
diff --git a/tools/virsh.pod b/tools/virsh.pod
index 247d2357b..2d19df86b 100644
--- a/tools/virsh.pod
+++ b/tools/virsh.pod
@@ -611,6 +611,18 @@ specified, then the output will be single-quoted where needed, so that
it is suitable for reuse in a shell context. If I<--xml> is
specified, then the output will be escaped for use in XML.
+=item B<get-magic>
+
+Get magic file's content.
+
+=item B<set-magic> [I<content>]
+
+Set magic file's content.
+
+=item B<magic-status>
+
+Show if magic file can be read.
+
=back
=head1 DOMAIN COMMANDS

到现在已经完成了包括文档在内的所有工作,如果你要为LibVirt 添加一个新的功能,所需要做的大约就是这么多。

最终的调用链如下。

cmdGetMagic@virsh@client -> virConnectGetMagicFileContent@LibVirt@client -> remoteConnectGetMagicFileContent@remote@client -> remoteDispatchConnectGetMagicFileContent@remote@server -> virConnectGetMagicFileContent@LibVirt@server -> qemuConnectGetMagicFileContent@QEMU@server -> virQEMUCapsGetMagicFileContent@QEMU@server

Hello World!

现在我们已经完成了最后一步,可以最后编译一次源码并测试一下功能。

1
2
make -j8
make test -j8

如果编译无误的话,在一个新的终端里运行daemon/libvirtd

1
sudo ./run ./daemon/libvirtd

然后看一看新添加的API 是否工作正常。

1
2
echo 'Hello World!' | sudo tee /var/run/libvirt/magic_file
sudo ./run ./tools/virsh -c qemu:///system get-magic

如果一切顺利,现在你已经在终端里看到了刚才写入到文件的Hello World! :)

文章目录
  1. 1. 构建开发环境
  2. 2. 添加公共API
    1. 2.1. API 的注释
    2. 2.2. 声明公共API
    3. 2.3. 实现公共API
  3. 3. 实现hypervisor 驱动
    1. 3.1. 添加内部驱动API
    2. 3.2. 添加hypervisor 公共API
    3. 3.3. 完成hypervisor 驱动的功能
  4. 4. 实现remote 驱动
    1. 4.1. 实现客户端驱动
    2. 4.2. 定义协议格式
    3. 4.3. 更新remote_protocol-structs
  5. 5. 在virsh 中实现功能
  6. 6. Hello World!
|