aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorK. Y. Srinivasan <kys@microsoft.com>2013-03-15 15:30:06 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2013-03-15 15:12:36 -0400
commit96dd86fa588169b745a71aedf2070e80f4943623 (patch)
tree3affd55b169f7c3030b5ecb1d4caf700d31b28c2
parentc87059793dd02390b504b0292bdb024ffd68b822 (diff)
Drivers: hv: Add a new driver to support host initiated backup
This driver supports host initiated backup of the guest. On Windows guests, the host can generate application consistent backups using the Windows VSS framework. On Linux, we ensure that the backup will be file system consistent. This driver allows the host to initiate a "Freeze" operation on all the mounted file systems in the guest. Once the mounted file systems in the guest are frozen, the host snapshots the guest's file systems. Once this is done, the guest's file systems are "thawed". This driver has a user-level component (daemon) that invokes the appropriate operation on all the mounted file systems in response to the requests from the host. The duration for which the guest is frozen is very short - a few seconds. During this interval, the diff disk is comitted. In this version of the patch I have addressed the feedback from Olaf Herring. Also, some of the connector related issues have been fixed. Signed-off-by: K. Y. Srinivasan <kys@microsoft.com> Reviewed-by: Haiyang Zhang <haiyangz@microsoft.com> Cc: Evgeniy Polyakov <zbr@ioremap.net> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--drivers/hv/Makefile2
-rw-r--r--drivers/hv/hv_snapshot.c287
-rw-r--r--drivers/hv/hv_util.c10
-rw-r--r--include/linux/hyperv.h69
-rw-r--r--include/uapi/linux/connector.h5
-rw-r--r--tools/hv/hv_vss_daemon.c220
6 files changed, 591 insertions, 2 deletions
diff --git a/drivers/hv/Makefile b/drivers/hv/Makefile
index e6abfa02d8b7..0a74b5661186 100644
--- a/drivers/hv/Makefile
+++ b/drivers/hv/Makefile
@@ -5,4 +5,4 @@ obj-$(CONFIG_HYPERV_BALLOON) += hv_balloon.o
5hv_vmbus-y := vmbus_drv.o \ 5hv_vmbus-y := vmbus_drv.o \
6 hv.o connection.o channel.o \ 6 hv.o connection.o channel.o \
7 channel_mgmt.o ring_buffer.o 7 channel_mgmt.o ring_buffer.o
8hv_utils-y := hv_util.o hv_kvp.o 8hv_utils-y := hv_util.o hv_kvp.o hv_snapshot.o
diff --git a/drivers/hv/hv_snapshot.c b/drivers/hv/hv_snapshot.c
new file mode 100644
index 000000000000..8ad5653ce447
--- /dev/null
+++ b/drivers/hv/hv_snapshot.c
@@ -0,0 +1,287 @@
1/*
2 * An implementation of host initiated guest snapshot.
3 *
4 *
5 * Copyright (C) 2013, Microsoft, Inc.
6 * Author : K. Y. Srinivasan <kys@microsoft.com>
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License version 2 as published
10 * by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
15 * NON INFRINGEMENT. See the GNU General Public License for more
16 * details.
17 *
18 */
19#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
20
21#include <linux/net.h>
22#include <linux/nls.h>
23#include <linux/connector.h>
24#include <linux/workqueue.h>
25#include <linux/hyperv.h>
26
27
28
29/*
30 * Global state maintained for transaction that is being processed.
31 * Note that only one transaction can be active at any point in time.
32 *
33 * This state is set when we receive a request from the host; we
34 * cleanup this state when the transaction is completed - when we respond
35 * to the host with the key value.
36 */
37
38static struct {
39 bool active; /* transaction status - active or not */
40 int recv_len; /* number of bytes received. */
41 struct vmbus_channel *recv_channel; /* chn we got the request */
42 u64 recv_req_id; /* request ID. */
43 struct hv_vss_msg *msg; /* current message */
44} vss_transaction;
45
46
47static void vss_respond_to_host(int error);
48
49static struct cb_id vss_id = { CN_VSS_IDX, CN_VSS_VAL };
50static const char vss_name[] = "vss_kernel_module";
51static __u8 *recv_buffer;
52
53static void vss_send_op(struct work_struct *dummy);
54static DECLARE_WORK(vss_send_op_work, vss_send_op);
55
56/*
57 * Callback when data is received from user mode.
58 */
59
60static void
61vss_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp)
62{
63 struct hv_vss_msg *vss_msg;
64
65 vss_msg = (struct hv_vss_msg *)msg->data;
66
67 if (vss_msg->vss_hdr.operation == VSS_OP_REGISTER) {
68 pr_info("VSS daemon registered\n");
69 vss_transaction.active = false;
70 if (vss_transaction.recv_channel != NULL)
71 hv_vss_onchannelcallback(vss_transaction.recv_channel);
72 return;
73
74 }
75 vss_respond_to_host(vss_msg->error);
76}
77
78
79static void vss_send_op(struct work_struct *dummy)
80{
81 int op = vss_transaction.msg->vss_hdr.operation;
82 struct cn_msg *msg;
83 struct hv_vss_msg *vss_msg;
84
85 msg = kzalloc(sizeof(*msg) + sizeof(*vss_msg), GFP_ATOMIC);
86 if (!msg)
87 return;
88
89 vss_msg = (struct hv_vss_msg *)msg->data;
90
91 msg->id.idx = CN_VSS_IDX;
92 msg->id.val = CN_VSS_VAL;
93
94 vss_msg->vss_hdr.operation = op;
95 msg->len = sizeof(struct hv_vss_msg);
96
97 cn_netlink_send(msg, 0, GFP_ATOMIC);
98 kfree(msg);
99
100 return;
101}
102
103/*
104 * Send a response back to the host.
105 */
106
107static void
108vss_respond_to_host(int error)
109{
110 struct icmsg_hdr *icmsghdrp;
111 u32 buf_len;
112 struct vmbus_channel *channel;
113 u64 req_id;
114
115 /*
116 * If a transaction is not active; log and return.
117 */
118
119 if (!vss_transaction.active) {
120 /*
121 * This is a spurious call!
122 */
123 pr_warn("VSS: Transaction not active\n");
124 return;
125 }
126 /*
127 * Copy the global state for completing the transaction. Note that
128 * only one transaction can be active at a time.
129 */
130
131 buf_len = vss_transaction.recv_len;
132 channel = vss_transaction.recv_channel;
133 req_id = vss_transaction.recv_req_id;
134 vss_transaction.active = false;
135
136 icmsghdrp = (struct icmsg_hdr *)
137 &recv_buffer[sizeof(struct vmbuspipe_hdr)];
138
139 if (channel->onchannel_callback == NULL)
140 /*
141 * We have raced with util driver being unloaded;
142 * silently return.
143 */
144 return;
145
146 icmsghdrp->status = error;
147
148 icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION | ICMSGHDRFLAG_RESPONSE;
149
150 vmbus_sendpacket(channel, recv_buffer, buf_len, req_id,
151 VM_PKT_DATA_INBAND, 0);
152
153}
154
155/*
156 * This callback is invoked when we get a VSS message from the host.
157 * The host ensures that only one VSS transaction can be active at a time.
158 */
159
160void hv_vss_onchannelcallback(void *context)
161{
162 struct vmbus_channel *channel = context;
163 u32 recvlen;
164 u64 requestid;
165 struct hv_vss_msg *vss_msg;
166
167
168 struct icmsg_hdr *icmsghdrp;
169 struct icmsg_negotiate *negop = NULL;
170
171 if (vss_transaction.active) {
172 /*
173 * We will defer processing this callback once
174 * the current transaction is complete.
175 */
176 vss_transaction.recv_channel = channel;
177 return;
178 }
179
180 vmbus_recvpacket(channel, recv_buffer, PAGE_SIZE * 2, &recvlen,
181 &requestid);
182
183 if (recvlen > 0) {
184 icmsghdrp = (struct icmsg_hdr *)&recv_buffer[
185 sizeof(struct vmbuspipe_hdr)];
186
187 if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
188 vmbus_prep_negotiate_resp(icmsghdrp, negop,
189 recv_buffer, MAX_SRV_VER, MAX_SRV_VER);
190 /*
191 * We currently negotiate the highest number the
192 * host has presented. If this version is not
193 * atleast 5.0, reject.
194 */
195 negop = (struct icmsg_negotiate *)&recv_buffer[
196 sizeof(struct vmbuspipe_hdr) +
197 sizeof(struct icmsg_hdr)];
198
199 if (negop->icversion_data[1].major < 5)
200 negop->icframe_vercnt = 0;
201 } else {
202 vss_msg = (struct hv_vss_msg *)&recv_buffer[
203 sizeof(struct vmbuspipe_hdr) +
204 sizeof(struct icmsg_hdr)];
205
206 /*
207 * Stash away this global state for completing the
208 * transaction; note transactions are serialized.
209 */
210
211 vss_transaction.recv_len = recvlen;
212 vss_transaction.recv_channel = channel;
213 vss_transaction.recv_req_id = requestid;
214 vss_transaction.active = true;
215 vss_transaction.msg = (struct hv_vss_msg *)vss_msg;
216
217 switch (vss_msg->vss_hdr.operation) {
218 /*
219 * Initiate a "freeze/thaw"
220 * operation in the guest.
221 * We respond to the host once
222 * the operation is complete.
223 *
224 * We send the message to the
225 * user space daemon and the
226 * operation is performed in
227 * the daemon.
228 */
229 case VSS_OP_FREEZE:
230 case VSS_OP_THAW:
231 schedule_work(&vss_send_op_work);
232 return;
233
234 case VSS_OP_HOT_BACKUP:
235 vss_msg->vss_cf.flags =
236 VSS_HBU_NO_AUTO_RECOVERY;
237 vss_respond_to_host(0);
238 return;
239
240 case VSS_OP_GET_DM_INFO:
241 vss_msg->dm_info.flags = 0;
242 vss_respond_to_host(0);
243 return;
244
245 default:
246 vss_respond_to_host(0);
247 return;
248
249 }
250
251 }
252
253 icmsghdrp->icflags = ICMSGHDRFLAG_TRANSACTION
254 | ICMSGHDRFLAG_RESPONSE;
255
256 vmbus_sendpacket(channel, recv_buffer,
257 recvlen, requestid,
258 VM_PKT_DATA_INBAND, 0);
259 }
260
261}
262
263int
264hv_vss_init(struct hv_util_service *srv)
265{
266 int err;
267
268 err = cn_add_callback(&vss_id, vss_name, vss_cn_callback);
269 if (err)
270 return err;
271 recv_buffer = srv->recv_buffer;
272
273 /*
274 * When this driver loads, the user level daemon that
275 * processes the host requests may not yet be running.
276 * Defer processing channel callbacks until the daemon
277 * has registered.
278 */
279 vss_transaction.active = true;
280 return 0;
281}
282
283void hv_vss_deinit(void)
284{
285 cn_del_callback(&vss_id);
286 cancel_work_sync(&vss_send_op_work);
287}
diff --git a/drivers/hv/hv_util.c b/drivers/hv/hv_util.c
index 1d4cbd8e8261..2f561c5dfe24 100644
--- a/drivers/hv/hv_util.c
+++ b/drivers/hv/hv_util.c
@@ -49,6 +49,12 @@ static struct hv_util_service util_kvp = {
49 .util_deinit = hv_kvp_deinit, 49 .util_deinit = hv_kvp_deinit,
50}; 50};
51 51
52static struct hv_util_service util_vss = {
53 .util_cb = hv_vss_onchannelcallback,
54 .util_init = hv_vss_init,
55 .util_deinit = hv_vss_deinit,
56};
57
52static void perform_shutdown(struct work_struct *dummy) 58static void perform_shutdown(struct work_struct *dummy)
53{ 59{
54 orderly_poweroff(true); 60 orderly_poweroff(true);
@@ -339,6 +345,10 @@ static const struct hv_vmbus_device_id id_table[] = {
339 { HV_KVP_GUID, 345 { HV_KVP_GUID,
340 .driver_data = (unsigned long)&util_kvp 346 .driver_data = (unsigned long)&util_kvp
341 }, 347 },
348 /* VSS GUID */
349 { HV_VSS_GUID,
350 .driver_data = (unsigned long)&util_vss
351 },
342 { }, 352 { },
343}; 353};
344 354
diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h
index df77ba9a8166..95d0850584da 100644
--- a/include/linux/hyperv.h
+++ b/include/linux/hyperv.h
@@ -27,6 +27,63 @@
27 27
28#include <linux/types.h> 28#include <linux/types.h>
29 29
30
31/*
32 * Implementation of host controlled snapshot of the guest.
33 */
34
35#define VSS_OP_REGISTER 128
36
37enum hv_vss_op {
38 VSS_OP_CREATE = 0,
39 VSS_OP_DELETE,
40 VSS_OP_HOT_BACKUP,
41 VSS_OP_GET_DM_INFO,
42 VSS_OP_BU_COMPLETE,
43 /*
44 * Following operations are only supported with IC version >= 5.0
45 */
46 VSS_OP_FREEZE, /* Freeze the file systems in the VM */
47 VSS_OP_THAW, /* Unfreeze the file systems */
48 VSS_OP_AUTO_RECOVER,
49 VSS_OP_COUNT /* Number of operations, must be last */
50};
51
52
53/*
54 * Header for all VSS messages.
55 */
56struct hv_vss_hdr {
57 __u8 operation;
58 __u8 reserved[7];
59} __attribute__((packed));
60
61
62/*
63 * Flag values for the hv_vss_check_feature. Linux supports only
64 * one value.
65 */
66#define VSS_HBU_NO_AUTO_RECOVERY 0x00000005
67
68struct hv_vss_check_feature {
69 __u32 flags;
70} __attribute__((packed));
71
72struct hv_vss_check_dm_info {
73 __u32 flags;
74} __attribute__((packed));
75
76struct hv_vss_msg {
77 union {
78 struct hv_vss_hdr vss_hdr;
79 int error;
80 };
81 union {
82 struct hv_vss_check_feature vss_cf;
83 struct hv_vss_check_dm_info dm_info;
84 };
85} __attribute__((packed));
86
30/* 87/*
31 * An implementation of HyperV key value pair (KVP) functionality for Linux. 88 * An implementation of HyperV key value pair (KVP) functionality for Linux.
32 * 89 *
@@ -1253,6 +1310,14 @@ void vmbus_driver_unregister(struct hv_driver *hv_driver);
1253 } 1310 }
1254 1311
1255/* 1312/*
1313 * VSS (Backup/Restore) GUID
1314 */
1315#define HV_VSS_GUID \
1316 .guid = { \
1317 0x29, 0x2e, 0xfa, 0x35, 0x23, 0xea, 0x36, 0x42, \
1318 0x96, 0xae, 0x3a, 0x6e, 0xba, 0xcb, 0xa4, 0x40 \
1319 }
1320/*
1256 * Common header for Hyper-V ICs 1321 * Common header for Hyper-V ICs
1257 */ 1322 */
1258 1323
@@ -1356,6 +1421,10 @@ int hv_kvp_init(struct hv_util_service *);
1356void hv_kvp_deinit(void); 1421void hv_kvp_deinit(void);
1357void hv_kvp_onchannelcallback(void *); 1422void hv_kvp_onchannelcallback(void *);
1358 1423
1424int hv_vss_init(struct hv_util_service *);
1425void hv_vss_deinit(void);
1426void hv_vss_onchannelcallback(void *);
1427
1359/* 1428/*
1360 * Negotiated version with the Host. 1429 * Negotiated version with the Host.
1361 */ 1430 */
diff --git a/include/uapi/linux/connector.h b/include/uapi/linux/connector.h
index 8761a0349c74..4cb283505e45 100644
--- a/include/uapi/linux/connector.h
+++ b/include/uapi/linux/connector.h
@@ -44,8 +44,11 @@
44#define CN_VAL_DRBD 0x1 44#define CN_VAL_DRBD 0x1
45#define CN_KVP_IDX 0x9 /* HyperV KVP */ 45#define CN_KVP_IDX 0x9 /* HyperV KVP */
46#define CN_KVP_VAL 0x1 /* queries from the kernel */ 46#define CN_KVP_VAL 0x1 /* queries from the kernel */
47#define CN_VSS_IDX 0xA /* HyperV VSS */
48#define CN_VSS_VAL 0x1 /* queries from the kernel */
47 49
48#define CN_NETLINK_USERS 10 /* Highest index + 1 */ 50
51#define CN_NETLINK_USERS 11 /* Highest index + 1 */
49 52
50/* 53/*
51 * Maximum connector's message size. 54 * Maximum connector's message size.
diff --git a/tools/hv/hv_vss_daemon.c b/tools/hv/hv_vss_daemon.c
new file mode 100644
index 000000000000..95269952aa92
--- /dev/null
+++ b/tools/hv/hv_vss_daemon.c
@@ -0,0 +1,220 @@
1/*
2 * An implementation of the host initiated guest snapshot for Hyper-V.
3 *
4 *
5 * Copyright (C) 2013, Microsoft, Inc.
6 * Author : K. Y. Srinivasan <kys@microsoft.com>
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License version 2 as published
10 * by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
15 * NON INFRINGEMENT. See the GNU General Public License for more
16 * details.
17 *
18 */
19
20
21#include <sys/types.h>
22#include <sys/socket.h>
23#include <sys/poll.h>
24#include <linux/types.h>
25#include <stdio.h>
26#include <stdlib.h>
27#include <unistd.h>
28#include <string.h>
29#include <ctype.h>
30#include <errno.h>
31#include <arpa/inet.h>
32#include <linux/connector.h>
33#include <linux/hyperv.h>
34#include <linux/netlink.h>
35#include <syslog.h>
36
37static char vss_recv_buffer[4096];
38static char vss_send_buffer[4096];
39static struct sockaddr_nl addr;
40
41#ifndef SOL_NETLINK
42#define SOL_NETLINK 270
43#endif
44
45
46static int vss_operate(int operation)
47{
48 char *fs_op;
49 char cmd[512];
50 char buf[512];
51 FILE *file;
52 char *p;
53 char *x;
54 int error;
55
56 switch (operation) {
57 case VSS_OP_FREEZE:
58 fs_op = "-f ";
59 break;
60 case VSS_OP_THAW:
61 fs_op = "-u ";
62 break;
63 }
64
65 file = popen("mount | awk '/^\/dev\// { print $3}'", "r");
66 if (file == NULL)
67 return;
68
69 while ((p = fgets(buf, sizeof(buf), file)) != NULL) {
70 x = strchr(p, '\n');
71 *x = '\0';
72 if (!strncmp(p, "/", sizeof("/")))
73 continue;
74
75 sprintf(cmd, "%s %s %s", "fsfreeze ", fs_op, p);
76 syslog(LOG_INFO, "VSS cmd is %s\n", cmd);
77 error = system(cmd);
78 }
79 pclose(file);
80
81 sprintf(cmd, "%s %s %s", "fsfreeze ", fs_op, "/");
82 syslog(LOG_INFO, "VSS cmd is %s\n", cmd);
83 error = system(cmd);
84
85 return error;
86}
87
88static int netlink_send(int fd, struct cn_msg *msg)
89{
90 struct nlmsghdr *nlh;
91 unsigned int size;
92 struct msghdr message;
93 char buffer[64];
94 struct iovec iov[2];
95
96 size = NLMSG_SPACE(sizeof(struct cn_msg) + msg->len);
97
98 nlh = (struct nlmsghdr *)buffer;
99 nlh->nlmsg_seq = 0;
100 nlh->nlmsg_pid = getpid();
101 nlh->nlmsg_type = NLMSG_DONE;
102 nlh->nlmsg_len = NLMSG_LENGTH(size - sizeof(*nlh));
103 nlh->nlmsg_flags = 0;
104
105 iov[0].iov_base = nlh;
106 iov[0].iov_len = sizeof(*nlh);
107
108 iov[1].iov_base = msg;
109 iov[1].iov_len = size;
110
111 memset(&message, 0, sizeof(message));
112 message.msg_name = &addr;
113 message.msg_namelen = sizeof(addr);
114 message.msg_iov = iov;
115 message.msg_iovlen = 2;
116
117 return sendmsg(fd, &message, 0);
118}
119
120int main(void)
121{
122 int fd, len, nl_group;
123 int error;
124 struct cn_msg *message;
125 struct pollfd pfd;
126 struct nlmsghdr *incoming_msg;
127 struct cn_msg *incoming_cn_msg;
128 int op;
129 struct hv_vss_msg *vss_msg;
130
131 daemon(1, 0);
132 openlog("Hyper-V VSS", 0, LOG_USER);
133 syslog(LOG_INFO, "VSS starting; pid is:%d", getpid());
134
135 fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
136 if (fd < 0) {
137 syslog(LOG_ERR, "netlink socket creation failed; error:%d", fd);
138 exit(EXIT_FAILURE);
139 }
140 addr.nl_family = AF_NETLINK;
141 addr.nl_pad = 0;
142 addr.nl_pid = 0;
143 addr.nl_groups = 0;
144
145
146 error = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
147 if (error < 0) {
148 syslog(LOG_ERR, "bind failed; error:%d", error);
149 close(fd);
150 exit(EXIT_FAILURE);
151 }
152 nl_group = CN_VSS_IDX;
153 setsockopt(fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &nl_group, sizeof(nl_group));
154 /*
155 * Register ourselves with the kernel.
156 */
157 message = (struct cn_msg *)vss_send_buffer;
158 message->id.idx = CN_VSS_IDX;
159 message->id.val = CN_VSS_VAL;
160 message->ack = 0;
161 vss_msg = (struct hv_vss_msg *)message->data;
162 vss_msg->vss_hdr.operation = VSS_OP_REGISTER;
163
164 message->len = sizeof(struct hv_vss_msg);
165
166 len = netlink_send(fd, message);
167 if (len < 0) {
168 syslog(LOG_ERR, "netlink_send failed; error:%d", len);
169 close(fd);
170 exit(EXIT_FAILURE);
171 }
172
173 pfd.fd = fd;
174
175 while (1) {
176 struct sockaddr *addr_p = (struct sockaddr *) &addr;
177 socklen_t addr_l = sizeof(addr);
178 pfd.events = POLLIN;
179 pfd.revents = 0;
180 poll(&pfd, 1, -1);
181
182 len = recvfrom(fd, vss_recv_buffer, sizeof(vss_recv_buffer), 0,
183 addr_p, &addr_l);
184
185 if (len < 0 || addr.nl_pid) {
186 syslog(LOG_ERR, "recvfrom failed; pid:%u error:%d %s",
187 addr.nl_pid, errno, strerror(errno));
188 close(fd);
189 return -1;
190 }
191
192 incoming_msg = (struct nlmsghdr *)vss_recv_buffer;
193
194 if (incoming_msg->nlmsg_type != NLMSG_DONE)
195 continue;
196
197 incoming_cn_msg = (struct cn_msg *)NLMSG_DATA(incoming_msg);
198 vss_msg = (struct hv_vss_msg *)incoming_cn_msg->data;
199 op = vss_msg->vss_hdr.operation;
200 error = HV_S_OK;
201
202 switch (op) {
203 case VSS_OP_FREEZE:
204 case VSS_OP_THAW:
205 error = vss_operate(op);
206 if (error)
207 error = HV_E_FAIL;
208 break;
209 default:
210 syslog(LOG_ERR, "Illegal op:%d\n", op);
211 }
212 vss_msg->error = error;
213 len = netlink_send(fd, incoming_cn_msg);
214 if (len < 0) {
215 syslog(LOG_ERR, "net_link send failed; error:%d", len);
216 exit(EXIT_FAILURE);
217 }
218 }
219
220}