]> sourceware.org Git - systemtap.git/blob - stapvirt.c
Fix PR19954 by avoiding "suspicious RCU usage" message.
[systemtap.git] / stapvirt.c
1 // stapvirt - systemtap libvirt helper
2 // Copyright (C) 2013 Red Hat Inc.
3 //
4 // This file is part of systemtap, and is free software. You can
5 // redistribute it and/or modify it under the terms of the GNU General
6 // Public License (GPL); either version 2, or (at your option) any
7 // later version.
8 //
9
10 #include "config.h"
11
12 #include <unistd.h>
13 #include <stdlib.h>
14 #include <stdio.h>
15 #include <string.h>
16 #include <limits.h>
17 #include <fcntl.h>
18 #include <errno.h>
19 #include <signal.h>
20
21 #include <libvirt/libvirt.h>
22 #include <libvirt/virterror.h>
23
24 #include <libxml/parser.h>
25 #include <libxml/xpath.h>
26 #include <libxml/xpathInternals.h>
27
28 int verbose = 0;
29
30 #define eprintf(format, args...) do { \
31 fprintf (stderr, format, ## args); } while (0)
32
33 #define dbug(verb, format, args...) do { \
34 if (verb > verbose) break; \
35 eprintf("stapvirt:%s:%d " format, \
36 __FUNCTION__, __LINE__, ## args); } while (0)
37
38 #define err(format, args...) do { \
39 dbug(2, "ERROR: " format, ## args); \
40 eprintf("stapvirt: ERROR: " format, ## args); } while (0)
41
42 /* Converts a virDomainState into a human-friendly string */
43 static const char*
44 domainStateStr(virDomainState state)
45 {
46 switch (state) {
47 case VIR_DOMAIN_NOSTATE:
48 return "no state";
49 case VIR_DOMAIN_RUNNING:
50 return "running";
51 case VIR_DOMAIN_BLOCKED:
52 return "blocked";
53 case VIR_DOMAIN_PAUSED :
54 return "paused";
55 case VIR_DOMAIN_SHUTDOWN:
56 return "shutting down";
57 case VIR_DOMAIN_SHUTOFF:
58 return "shutoff";
59 case VIR_DOMAIN_CRASHED:
60 return "crashed";
61 case VIR_DOMAIN_PMSUSPENDED:
62 return "suspended";
63 default:
64 break;
65 }
66
67 return "unknown state";
68 }
69
70 /* Used to silence libvirt error messages when necessary */
71 static void
72 silentErr(void __attribute__((__unused__)) *userdata, virErrorPtr err)
73 {
74 dbug(2, "libvirt: code %d: domain %d: %s\n", err->code, err->domain,
75 err->message);
76 fflush(stderr);
77 return;
78 }
79
80 /* Tries to find a domain by name, ID, or UUID on the given node */
81 static virDomainPtr
82 findDomain(virConnectPtr conn, const char *domStr)
83 {
84 virDomainPtr dom = NULL;
85
86 // Override the default libvirt error handler so we silently fail lookups
87 virSetErrorFunc(NULL, silentErr);
88
89 // Try as an ID
90 const char *end = domStr + strlen(domStr);
91 char *stopchar = NULL;
92 errno = 0;
93 int id = strtoul(domStr, &stopchar, 0);
94 if (errno == 0 && stopchar == end)
95 dom = virDomainLookupByID(conn, id);
96
97 // Try by name
98 if (dom == NULL)
99 dom = virDomainLookupByName(conn, domStr);
100
101 // Try by UUID
102 if (dom == NULL)
103 dom = virDomainLookupByUUIDString(conn, domStr);
104
105 virSetErrorFunc(NULL, NULL);
106 return dom;
107 }
108
109 /* Retrieves the list of active domains using the old API. Returns the number
110 * of domains collected in the ids array, or -1 on error. The array must be
111 * freed if any domains are found. */
112 static int
113 getActiveDomains(virConnectPtr conn, int **ids)
114 {
115 int idsn = 0;
116 *ids = NULL;
117
118 idsn = virConnectNumOfDomains(conn);
119 if (idsn <= 0)
120 return idsn;
121
122 *ids = (int*) calloc(sizeof(int), idsn);
123 if (*ids == NULL)
124 return -1;
125
126 idsn = virConnectListDomains(conn, *ids, idsn);
127 if (idsn < 0) {
128 free(*ids);
129 *ids = NULL;
130 return -1;
131 }
132 return idsn;
133 }
134
135 /* Retrieves the list of inactive domains using the old API. Returns the number
136 * of domains collected in the names array, or -1 on error. The array must be
137 * freed if any domains are found. */
138 static int
139 getInactiveDomains(virConnectPtr conn, char ***names)
140 {
141 int namesn = 0;
142 *names = NULL;
143
144 namesn = virConnectNumOfDefinedDomains(conn);
145 if (namesn <= 0)
146 return namesn;
147
148 *names = (char**) calloc(sizeof(char*), namesn);
149 if (*names == NULL)
150 return -1;
151
152 namesn = virConnectListDefinedDomains(conn, *names, namesn);
153 if (namesn < 0) {
154 free(*names);
155 *names = NULL;
156 return -1;
157 }
158 return namesn;
159 }
160
161 /* Retrieves the list of all domains using the old API. Returns the number of
162 * domains collected in the domains array, or -1 on error. The array must be
163 * freed if any domains are found. */
164 static int
165 getAllDomains(virConnectPtr conn, virDomainPtr **domains)
166 {
167 int *ids = NULL;
168 char **names = NULL;
169 int idsn, namesn, domainsn;
170 idsn = namesn = domainsn = 0;
171 *domains = NULL;
172
173 // We need to use the older functions
174 idsn = getActiveDomains(conn, &ids);
175 if (idsn < 0) {
176 err("Couldn't retrieve list of active domains\n");
177 return -1;
178 }
179 namesn = getInactiveDomains(conn, &names);
180 if (namesn < 0) {
181 err("Couldn't retrieve list of inactive domains\n");
182 domainsn = -1;
183 goto cleanup_ids;
184 }
185
186 // Make sure we got something to print
187 if (idsn + namesn == 0)
188 goto cleanup_names;
189
190 // Time to prepare virDomainPtr array
191 *domains = (virDomainPtr*) calloc(sizeof(virDomainPtr), idsn+namesn);
192 if (*domains == NULL) {
193 err("Can't allocate domains array\n");
194 goto cleanup_names;
195 }
196
197 int i;
198 for (i = 0; i < idsn; i++) {
199 virDomainPtr dom = virDomainLookupByID(conn, ids[i]);
200 if (dom == NULL)
201 continue;
202 (*domains)[domainsn++] = dom;
203 }
204 for (i = 0; i < namesn; i++) {
205 virDomainPtr dom = virDomainLookupByName(conn, names[i]);
206 if (dom == NULL)
207 continue;
208 (*domains)[domainsn++] = dom;
209 }
210
211 cleanup_names:
212 free(names);
213 cleanup_ids:
214 free(ids);
215 return domainsn;
216 }
217
218 /* Returns nonzero if the libvirtd version is greater than target. Be careful
219 * when using this function due to the double meaning of 0. */
220 static int
221 libvirtdCheckVers(virConnectPtr conn, unsigned long targetVer)
222 {
223 unsigned long version;
224 if (virConnectGetLibVersion(conn, &version) != 0) {
225 err("Couldn't retrieve libvirtd version\n");
226 return 0;
227 }
228 return (version >= targetVer);
229 }
230
231 /* Returns nonzero if libvirtd supports virDomainOpenChannel(). Be careful when
232 * using this function due to the double meaning of 0. */
233 static int
234 libvirtdCanOpenChannel(virConnectPtr conn)
235 {
236 // version = major * 1000000 + minor * 1000 + release
237 // libvirt v1.0.2
238 return libvirtdCheckVers(conn, 1*1000000+0*1000+2);
239 }
240
241 /* Returns nonzero if libvirtd supports virConnectListAllDomains(). Be careful
242 * when using this function due to the double meaning of 0. */
243 static int
244 libvirtdCanListAll(virConnectPtr conn)
245 {
246 // version = major * 1000000 + minor * 1000 + release
247 // libvirt v0.9.13
248 return libvirtdCheckVers(conn, 0*1000000+9*1000+13);
249 }
250
251 /* Returns nonzero if libvirtd supports hotplugging. Be careful when using this
252 * function due to the double meaning of 0. */
253 static int
254 libvirtdCanHotplug(virConnectPtr conn)
255 {
256 // version = major * 1000000 + minor * 1000 + release
257 // libvirt v1.1.1
258 return libvirtdCheckVers(conn, 1*1000000+1*1000+1);
259 }
260
261 /* Returns nonzero if libvirt supports hotplugging. Be careful when using this
262 * function due to the double meaning of 0. */
263 static int
264 libvirtCanHotplug(void)
265 {
266 unsigned long version;
267 if (virGetVersion(&version, NULL, NULL) != 0) {
268 err("Couldn't retrieve libvirt version\n");
269 return 0;
270 }
271
272 // version = major * 1000000 + minor * 1000 + release
273 // libvirt v1.1.1
274 return (version >= (1*1000000+1*1000+1));
275 }
276
277 /* Returns nonzero if qemu supports hotplugging. Be careful when using this
278 * function due to the double meaning of 0. */
279 static int
280 qemuCanHotplug(virConnectPtr conn)
281 {
282 const char *hv = virConnectGetType(conn);
283 if (hv == NULL) {
284 err("Couldn't retrieve hypervisor type\n");
285 return 0;
286 }
287
288 if (strcmp(hv, "QEMU") != 0)
289 return 0;
290
291 unsigned long version;
292 if (virConnectGetVersion(conn, &version) != 0) {
293 err("Couldn't retrieve hypervisor version\n");
294 return 0;
295 }
296
297 // version = major * 1000000 + minor * 1000 + release
298 // QEMU v0.10.0
299 return (version >= (10*1000));
300 }
301
302 /* Returns nonzero if hotplugging is supported. Be careful when using this
303 * function due to the double meaning of 0. */
304 static int
305 canHotplug(virConnectPtr conn)
306 {
307 // We need to check if both the local library as well as the remote daemon
308 // supports the holplugging API for virtio-serial
309 return libvirtCanHotplug()
310 && libvirtdCanHotplug(conn)
311 && qemuCanHotplug(conn);
312 }
313
314 /* Retrieves and parses the domain's XML. Returns NULL on error or an xmlDocPtr
315 * of the domain on success. Note that the xmlDocPtr returned must be freed by
316 * xmlFreeDoc() */
317 static xmlDocPtr
318 getDomainXML(virDomainPtr dom, int liveConfig)
319 {
320 virDomainXMLFlags flags = 0;
321 if (!liveConfig)
322 flags |= VIR_DOMAIN_XML_INACTIVE;
323
324 char *domXML = virDomainGetXMLDesc(dom, flags);
325 if (domXML == NULL) {
326 err("Couldn't retrieve the domain's XML\n");
327 return NULL;
328 }
329
330 xmlDocPtr domXMLDoc = xmlParseDoc(BAD_CAST domXML);
331 if (domXMLDoc == NULL)
332 err("Couldn't parse the domain's XML\n");
333
334 free(domXML);
335 return domXMLDoc;
336 }
337
338 /* Retrieves info from the port node. Used to retrieve either the path
339 * attribute or the name attribute by getXmlPortPath() and getXmlPortName().
340 * The advantage of this method over xmlGetProp() is that we don't have to
341 * worry about de-allocating memory after. */
342 static const xmlChar*
343 getXmlPortInfo(xmlNodePtr port, const xmlChar* type, const xmlChar* attr)
344 {
345 // find target type
346 xmlNodePtr child = port->children;
347 while (child != NULL && !xmlStrEqual(child->name, type))
348 child = child->next;
349
350 if (child == NULL) {
351 err("Couldn't find target type\n");
352 return NULL;
353 }
354
355 // find target attr
356 xmlAttrPtr prop = child->properties;
357 while (prop != NULL && !xmlStrEqual(prop->name, attr))
358 prop = prop->next;
359
360 if (prop == NULL || prop->children == NULL) {
361 err("Couldn't find target attr\n");
362 return NULL;
363 }
364
365 return prop->children->content;
366 }
367
368 static const char*
369 getXmlPortPath(xmlNodePtr port)
370 {
371 return (const char *)
372 getXmlPortInfo(port, BAD_CAST "source", BAD_CAST "path");
373 }
374
375 static const char*
376 getXmlPortName(xmlNodePtr port)
377 {
378 return (const char *)
379 getXmlPortInfo(port, BAD_CAST "target", BAD_CAST "name");
380 }
381
382 /* Returns nonzero if port is a valid SystemTap port */
383 static int
384 isValidPort(xmlNodePtr port, const char *basename)
385 {
386 const char *name = getXmlPortName(port);
387 if (name == NULL)
388 return 0;
389
390 // Check if it starts with basename
391 if (strstr(name, basename) != name)
392 return 0;
393
394 // Check that the dot after basename is the last dot
395 const char *lastdot = strrchr(name, '.');
396 if (lastdot == NULL || (size_t)(lastdot-name) != strlen(basename))
397 return 0;
398
399 // Check that there is something between the dot and the end
400 const char *end = name + strlen(name);
401 if (lastdot >= end-1)
402 return 0;
403
404 // Check that the stuff between dot and end is a valid positive number
405 errno = 0;
406 char *stopchar = NULL;
407 long num = strtol(lastdot+1, &stopchar, 10);
408 if (errno || stopchar != end || num < 0)
409 return 0;
410
411 return 1;
412 }
413
414 /* Retrieves the list of SystemTap ports from a domain's XML doc. Note that the
415 * xmlNodeSetPtr must be freed with xmlXPathFreeNodeSet() */
416 static xmlNodeSetPtr
417 getXmlPorts(xmlDocPtr domXMLDoc, const char *basename)
418 {
419 xmlXPathContextPtr ctxt = xmlXPathNewContext(domXMLDoc);
420 xmlXPathObjectPtr res = NULL;
421 xmlNodeSetPtr ports = NULL;
422
423 /* xpath expression is based on libguestfs code in src/libvirt-domain.c */
424
425 // Unfortunately, the XPath of libxml2 does not support the matches()
426 // function, which would allow us to use a regex to match the port name
427 // precisely. Instead as a first approximation, we only require that the
428 // port start with the basename. We then use isValidPort() to check for the
429 // exact pattern.
430 char *xpath;
431 int rc = asprintf(&xpath, "//devices/channel[@type=\"unix\" and "
432 "./source/@mode=\"bind\" and "
433 "./source/@path and "
434 "./target/@type=\"virtio\" and "
435 "./target/@name[starts-with(.,\"%s\")]]", basename);
436 if (rc == -1) {
437 err("Couldn't call asprintf for xpath\n");
438 goto cleanup;
439 }
440
441 res = xmlXPathEvalExpression(BAD_CAST xpath, ctxt);
442 free(xpath);
443
444 if (res == NULL) {
445 err("Couldn't parse SystemTap ports\n");
446 goto cleanup;
447 }
448
449 // We wait until now to initialize the return NodeSet since we know now that
450 // the XPath eval went well
451 ports = xmlXPathNodeSetCreate(NULL);
452
453 // Go through each port and add only the valid ones to the NodeSet
454 int i;
455 for (i = 0; i < res->nodesetval->nodeNr; i++) {
456 xmlNodePtr port = res->nodesetval->nodeTab[i];
457 if (isValidPort(port, basename))
458 xmlXPathNodeSetAdd(ports, port);
459 }
460
461 xmlXPathFreeObject(res);
462 cleanup:
463 xmlXPathFreeContext(ctxt);
464 return ports;
465 }
466
467 /* Retrieves the number of the port node. No error-checking is done since these
468 * ports are assumed to have gone through isValidPort() */
469 static int
470 getXmlPortNumber(xmlNodePtr port)
471 {
472 const char *name = getXmlPortName(port);
473 char *lastdot = strrchr(name, '.');
474 return strtol(lastdot+1, NULL, 10);
475 }
476
477 /* Shortcut to call getDomainXML and then getXmlPorts. Sets domXMLdoc and ports.
478 * Returns zero on success and nonzero on error (in which case, no de-allocation
479 * needs to be done by the caller). */
480 static int
481 getDomainPorts(virDomainPtr dom, int liveConfig, const char *basename,
482 xmlDocPtr *domXMLdoc, xmlNodeSetPtr *ports)
483 {
484 // We explicitly set domXMLdoc and ports to NULL in case of error so that
485 // callers don't try to free them even if we failed
486
487 *domXMLdoc = getDomainXML(dom, liveConfig);
488 if (*domXMLdoc == NULL) {
489 err("Couldn't get domain's XML\n");
490 *ports = NULL;
491 return -1;
492 }
493
494 *ports = getXmlPorts(*domXMLdoc, basename);
495 if (*ports == NULL) {
496 err("Couldn't search for SystemTap ports\n");
497 xmlFreeDoc(*domXMLdoc);
498 *domXMLdoc = NULL;
499 return -1;
500 }
501
502 return 0;
503 }
504
505 /* Sets maxPort to the port in the NodeSet with the highest port number. Returns
506 * zero on success, nonzero on error. */
507 static int
508 findPortWithMaxNum(xmlNodeSetPtr ports, xmlNodePtr *maxPort)
509 {
510 // Go through each port and remember the highest port number
511 // (libxml guarantees no particular order)
512 *maxPort = NULL;
513 int i, max = -1;
514 for (i = 0; i < ports->nodeNr; i++) {
515 xmlNodePtr port = ports->nodeTab[i];
516 int num = getXmlPortNumber(port);
517 if (num == -1)
518 return -1; // proper error msg already emitted
519 if (num > max) {
520 max = num;
521 *maxPort = port;
522 }
523 }
524
525 // maxPort is either NULL (no ports in NodeSet) or has the port with max num
526 return 0;
527 }
528
529 /* Creates an XML node for a SystemTap port with the given UNIX socket path and
530 * name. */
531 static xmlNodePtr
532 createXmlPort(const char* path, const char* name)
533 {
534 // No need for error-checking here, these functions cannot fail
535 xmlNodePtr channel, node;
536
537 channel = xmlNewNode(NULL, BAD_CAST "channel");
538 xmlNewProp(channel, BAD_CAST "type", BAD_CAST "unix");
539 node = xmlNewChild(channel, NULL, BAD_CAST "source", NULL);
540 xmlNewProp(node, BAD_CAST "mode", BAD_CAST "bind");
541 xmlNewProp(node, BAD_CAST "path", BAD_CAST path);
542 node = xmlNewChild(channel, NULL, BAD_CAST "target", NULL);
543 xmlNewProp(node, BAD_CAST "type", BAD_CAST "virtio");
544 xmlNewProp(node, BAD_CAST "name", BAD_CAST name);
545
546 return channel;
547 }
548
549 /* Looks at the list of ports and creates a new port with port number one higher
550 * than the maximum of the existing port numbers. Returns zero on success,
551 * nonzero on failure. */
552 static int
553 generateNextPort(xmlNodeSetPtr ports, virDomainPtr dom, const char *sockDir,
554 const char *basename, xmlNodePtr *newPort)
555 {
556 int ret = 0;
557 xmlNodePtr maxPort;
558 if (findPortWithMaxNum(ports, &maxPort) != 0)
559 return -1; // proper error msg already emitted
560
561 // maxPort == NULL means this will be the first port
562 int next = (maxPort == NULL) ? 0 : (getXmlPortNumber(maxPort)+1);
563
564 const char *domName = virDomainGetName(dom);
565 if (domName == NULL) {
566 err("Couldn't retrieve domain name\n");
567 return -1;
568 }
569
570 char *path;
571 if (asprintf(&path, "%s/%s.%s.%d.sock",
572 sockDir, domName, basename, next) == -1) {
573 err("Couldn't call asprintf for path\n");
574 return -1;
575 }
576
577 char *name;
578 if (asprintf(&name, "%s.%d", basename, next) == -1) {
579 err("Couldn't call asprintf for name\n");
580 ret = -1;
581 goto cleanup;
582 }
583
584 *newPort = createXmlPort(path, name);
585 free(name);
586
587 cleanup:
588 free(path);
589 return ret;
590 }
591
592 /* Adds an XML node representing a SystemTap port to the <devices> list. Returns
593 * zero on success, nonzero on failure. */
594 static int
595 addXmlPort(xmlDocPtr domXMLDoc, xmlNodePtr port)
596 {
597 int ret = 0;
598
599 // Find channel node
600 xmlXPathContextPtr ctxt = xmlXPathNewContext(domXMLDoc);
601 xmlXPathObjectPtr devres =
602 xmlXPathEvalExpression(BAD_CAST "//devices", ctxt);
603 if (devres == NULL) {
604 err("Couldn't search for <devices> node\n");
605 ret = -1;
606 goto cleanup_ctxt;
607 }
608
609 if (xmlXPathNodeSetIsEmpty(devres->nodesetval)) {
610 err("No <devices> node found in domain XML\n");
611 ret = -1;
612 goto cleanup_devres;
613 }
614
615 xmlNodePtr devices = devres->nodesetval->nodeTab[0];
616 if (xmlAddChild(devices, port) == NULL) {
617 err("Could not add port in XML\n");
618 ret = -1;
619 goto cleanup_devres;
620 }
621
622 cleanup_devres:
623 xmlXPathFreeObject(devres);
624 cleanup_ctxt:
625 xmlXPathFreeContext(ctxt);
626 return ret;
627 }
628
629 /* Redefines the domain defined by domXMLdoc at the hypervisor connected to
630 * conn. Returns zero on success and nonzero on failure. */
631 static int
632 redefineDomain(virConnectPtr conn, xmlDocPtr domXMLdoc)
633 {
634 xmlChar *buf;
635 xmlDocDumpMemoryEnc(domXMLdoc, &buf, NULL, "UTF-8");
636
637 virDomainPtr rcDom = virDomainDefineXML(conn, (const char*) buf);
638 xmlFree(buf);
639
640 if (rcDom == NULL) {
641 err("Couldn't redefine the domain\n");
642 return -1;
643 }
644 virDomainFree(rcDom);
645
646 return 0;
647 }
648
649 /* Attaches or detaches a port to a live domain. Returns zero on success and
650 * nonzero on failure. */
651 static int
652 hotplugPort(int attach, virDomainPtr dom,
653 xmlDocPtr domXMLdoc, xmlNodePtr port)
654 {
655 int ret = 0;
656 xmlBufferPtr buf = xmlBufferCreate();
657
658 if (xmlNodeDump(buf, domXMLdoc, port, 0, 0) == -1) {
659 err("Couldn't dump new port XML\n");
660 ret = -1;
661 goto cleanup_buf;
662 }
663
664 int rc = attach ? virDomainAttachDevice(dom, (const char *) buf->content)
665 : virDomainDetachDevice(dom, (const char *) buf->content);
666 if (rc != 0) {
667 err("Couldn't %s the device\n", attach ? "attach" : "detach");
668 ret = -1;
669 goto cleanup_buf;
670 }
671
672 cleanup_buf:
673 xmlBufferFree(buf);
674 return ret;
675 }
676
677 /* Tries to open one of the ports in the set on the target domain. Returns NULL
678 * if an error occurred. Note that the virStreamPtr object must be freed with
679 * virStreamFree(). */
680 static virStreamPtr
681 domainOpenChannel(virConnectPtr conn, virDomainPtr dom, xmlNodeSetPtr ports)
682 {
683 virStreamPtr stream = virStreamNew(conn, VIR_STREAM_NONBLOCK);
684 if (stream == NULL) {
685 err("Couldn't create a new stream object\n");
686 return NULL;
687 }
688
689 // Override libvirt default err handler so that we suppress error msgs when
690 // we try to connect to busy channels
691 virSetErrorFunc(NULL, silentErr);
692
693 int i;
694 for (i = 0; i < ports->nodeNr; i++) {
695 xmlNodePtr port = ports->nodeTab[i];
696 const char *name = getXmlPortName(port);
697 if (name == NULL)
698 continue;
699 if (virDomainOpenChannel(dom, name, stream, 0) == 0)
700 break;
701 dbug(1, "channel %s is already in use\n", name);
702 }
703
704 // Restore error handler to the default
705 virSetErrorFunc(NULL, NULL);
706
707 if (i == ports->nodeNr) {
708 virStreamFree(stream);
709 return NULL;
710 }
711
712 return stream;
713 }
714
715 static void
716 printName(virDomainPtr dom)
717 {
718 const char *name = virDomainGetName(dom);
719 printf("%s", name != NULL ? name : "<undefined>");
720 }
721
722 static void
723 printUUID(virDomainPtr dom)
724 {
725 char uuid[VIR_UUID_STRING_BUFLEN];
726 if (virDomainGetUUIDString(dom, (char *) &uuid) != 0)
727 strncpy(uuid, "INVALID_UUID", VIR_UUID_STRING_BUFLEN);
728 printf("%s", uuid);
729 }
730
731 static void
732 printState(virDomainPtr dom)
733 {
734 int state;
735 if (virDomainGetState(dom, &state, NULL, 0) != 0)
736 state = VIR_DOMAIN_NOSTATE;
737 printf("%s", domainStateStr(state));
738 }
739
740 static void
741 printID(virDomainPtr dom)
742 {
743 if (virDomainIsActive(dom) == 1)
744 printf("%u", virDomainGetID(dom));
745 else
746 printf("-");
747 }
748
749 static void
750 printType(virDomainPtr dom)
751 {
752 int pers = virDomainIsPersistent(dom);
753 if (pers == -1)
754 printf("<undefined>");
755 else
756 printf(pers ? "persistent" : "transient");
757 }
758
759 static void
760 printHotplug(virConnectPtr conn)
761 {
762 if (!canHotplug(conn))
763 printf("not ");
764 printf("supported");
765 }
766
767 static void
768 printPortNum(virDomainPtr dom, const char *basename)
769 {
770 xmlDocPtr domXMLdoc = NULL;
771 xmlNodeSetPtr ports = NULL;
772
773 if (getDomainPorts(dom, 0, basename, &domXMLdoc, &ports) != 0)
774 return; // proper error msg already emitted
775
776 printf("%u", (unsigned)ports->nodeNr);
777 xmlXPathFreeNodeSet(ports);
778 xmlFreeDoc(domXMLdoc);
779 }
780
781 /**************************************
782 *********** USER COMMANDS ************
783 **************************************
784 */
785
786 #define PORT_BASENAME "org.systemtap.stapsh"
787
788 static int cmd_list (void);
789 static int cmd_port_add (void);
790 static int cmd_port_remove (void);
791 static int cmd_query (void);
792 static int cmd_port_list (void);
793 static int cmd_connect (void);
794
795 typedef struct {
796 const char *name;
797 int need_domain;
798 int read_only;
799 int (*fn)(void);
800 } command;
801
802 const command commands[] = {
803 { "list", 0, 1, cmd_list },
804 { "port-add", 1, 0, cmd_port_add },
805 { "port-remove", 1, 0, cmd_port_remove },
806 { "query", 1, 1, cmd_query },
807 { "port-list", 1, 1, cmd_port_list },
808 { "connect", 1, 0, cmd_connect },
809 };
810 const unsigned ncommands = sizeof(commands) / sizeof(*commands);
811
812 char *sockDir = "/var/lib/libvirt/qemu";
813 char *virURI = NULL;
814 virConnectPtr conn = NULL;
815
816 char *targetDomStr = NULL;
817 virDomainPtr targetDom = NULL;
818
819 static int
820 cmd_list()
821 {
822 int domainsn = 0;
823 virDomainPtr *domains = NULL;
824
825 printf("Available domains on URI '%s':\n", virConnectGetURI(conn));
826 printf("ID\tState\tType\tName\n");
827
828 // virConnectListAllDomains is more safe, but was introduced in 0.9.13
829 domainsn = libvirtdCanListAll(conn)
830 ? virConnectListAllDomains(conn, &domains, 0)
831 : getAllDomains(conn, &domains);
832 if (domainsn < 0) {
833 err("Couldn't retrieve list of domains\n");
834 return -1;
835 }
836
837 int i;
838 for (i = 0; i < domainsn; i++) {
839 printID(domains[i]); printf("\t");
840 printState(domains[i]); printf("\t");
841 printType(domains[i]); printf("\t");
842 printName(domains[i]); printf("\n");
843 virDomainFree(domains[i]);
844 }
845
846 free(domains);
847 return 0;
848 }
849
850 static int
851 cmd_query()
852 {
853 // start with newlines because the virPrint* funcs don't put them
854 printf("\n Name: "); printName(targetDom);
855 printf("\n UUID: "); printUUID(targetDom);
856 printf("\n State: "); printState(targetDom);
857 printf("\n ID: "); printID(targetDom);
858 printf("\n Type: "); printType(targetDom);
859 printf("\n Permanent Ports: "); printPortNum(targetDom,PORT_BASENAME);
860 printf("\n Hotplugging: "); printHotplug(conn);
861 printf("\n\n");
862
863 return 0;
864 }
865
866 static int
867 cmd_port_add()
868 {
869 int rc = 0;
870 xmlDocPtr domXMLdoc = NULL;
871 xmlNodeSetPtr ports = NULL;
872 xmlNodePtr newPort = NULL;
873
874 if (getDomainPorts(targetDom, 0, PORT_BASENAME, &domXMLdoc, &ports) != 0)
875 return -1; // proper error msg already emitted
876 if (generateNextPort(ports, targetDom, sockDir,
877 PORT_BASENAME, &newPort) != 0)
878 goto error; // proper error msg already emitted
879 if (addXmlPort(domXMLdoc, newPort) != 0)
880 goto error; // proper error msg already emitted
881 if (redefineDomain(conn, domXMLdoc) != 0)
882 goto error; // proper error msg already emitted
883
884 printf("Added new port %s\n", getXmlPortName(newPort));
885 if (virDomainIsActive(targetDom) == 1) // helpful hint for user
886 printf("The domain must be powered off before changes take effect.\n");
887
888 goto cleanup;
889 error:
890 rc = -1;
891 cleanup:
892 xmlXPathFreeNodeSet(ports);
893 xmlFreeDoc(domXMLdoc);
894 return rc;
895 }
896
897 static int
898 cmd_port_remove()
899 {
900 int rc = 0;
901 xmlDocPtr domXMLdoc = NULL;
902 xmlNodeSetPtr ports = NULL;
903 xmlNodePtr maxPort = NULL;
904
905 if (getDomainPorts(targetDom, 0, PORT_BASENAME, &domXMLdoc, &ports) != 0)
906 return -1; // proper error msg already emitted
907 if (findPortWithMaxNum(ports, &maxPort) != 0)
908 goto error; // proper error msg already emitted
909
910 if (maxPort == NULL) {
911 err("No SystemTap port to remove\n");
912 goto error;
913 }
914
915 xmlUnlinkNode(maxPort);
916 if (redefineDomain(conn, domXMLdoc) != 0)
917 goto error; // proper error msg already emitted
918
919 printf("Removed port %s\n", getXmlPortName(maxPort));
920 if (virDomainIsActive(targetDom) == 1) // helpful hint for user
921 printf("The domain must be powered off before changes take effect.\n");
922
923 goto cleanup;
924 error:
925 rc = -1;
926 cleanup:
927 xmlXPathFreeNodeSet(ports);
928 xmlFreeDoc(domXMLdoc);
929 return rc;
930 }
931
932 static int
933 cmd_port_list()
934 {
935 xmlDocPtr domXMLdoc = NULL;
936 xmlNodeSetPtr ports = NULL;
937
938 if (getDomainPorts(targetDom, 0, PORT_BASENAME, &domXMLdoc, &ports) != 0)
939 return -1; // proper error msg already emitted
940
941 int i;
942 for (i = 0; i < ports->nodeNr; i++) {
943 xmlNodePtr port = ports->nodeTab[i];
944 printf("%s\n", getXmlPortPath(port));
945 }
946
947 xmlXPathFreeNodeSet(ports);
948 xmlFreeDoc(domXMLdoc);
949 return 0;
950 }
951
952 // The structure of the code for cmd_connect and the event callbacks is largely
953 // based on the "virsh console" command
954
955 int disconnect = 0;
956
957 typedef struct {
958 virStreamPtr st;
959 int stdin_w;
960 int stdout_w;
961 char termbuf[1024]; /* term to st */
962 size_t termbuf_off;
963 char stbuf[1024]; /* st to term */
964 size_t stbuf_off;
965 } event_context;
966
967 static void
968 stdin_event(__attribute__((unused)) int watch, int fd,
969 int events, void *opaque)
970 {
971 event_context *ctxt = opaque;
972
973 if ((events & VIR_EVENT_HANDLE_READABLE)
974 && (ctxt->termbuf_off < sizeof(ctxt->termbuf))) {
975 // if there's no space in the buffer, we need to wait until more has
976 // been written to the stream
977
978 int bytes_read = read(fd, ctxt->termbuf + ctxt->termbuf_off,
979 sizeof(ctxt->termbuf) - ctxt->termbuf_off);
980 if (bytes_read < 0) {
981 if (errno != EAGAIN)
982 disconnect = 1;
983 return;
984 }
985 if (bytes_read == 0) {
986 disconnect = 1;
987 virStreamFinish(ctxt->st);
988 return;
989 }
990
991 ctxt->termbuf_off += bytes_read;
992 }
993
994 if (ctxt->termbuf_off) { // we have stuff to write to the stream
995 virStreamEventUpdateCallback(ctxt->st, VIR_STREAM_EVENT_READABLE
996 | VIR_STREAM_EVENT_WRITABLE);
997 }
998
999 if (events & (VIR_EVENT_HANDLE_ERROR|VIR_EVENT_HANDLE_HANGUP))
1000 disconnect = 1;
1001 }
1002
1003 static void
1004 stdout_event(__attribute__((unused)) int watch, int fd,
1005 int events, void *opaque)
1006 {
1007 event_context *ctxt = opaque;
1008
1009 if (events & VIR_EVENT_HANDLE_WRITABLE && ctxt->stbuf_off) {
1010 ssize_t bytes_written = write(fd, ctxt->stbuf, ctxt->stbuf_off);
1011 if (bytes_written < 0) {
1012 if (errno != EAGAIN)
1013 disconnect = 1;
1014 return;
1015 }
1016 // shift down
1017 memmove(ctxt->stbuf, ctxt->stbuf + bytes_written,
1018 ctxt->stbuf_off - bytes_written);
1019 ctxt->stbuf_off -= bytes_written;
1020 }
1021
1022 if (ctxt->stbuf_off == 0) // there's nothing else to write to stdout
1023 virEventUpdateHandle(ctxt->stdout_w, 0);
1024
1025 if (events & (VIR_EVENT_HANDLE_ERROR|VIR_EVENT_HANDLE_HANGUP))
1026 disconnect = 1;
1027 }
1028
1029 static void
1030 stream_event(virStreamPtr st, int events, void *opaque)
1031 {
1032 event_context *ctxt = opaque;
1033
1034 if ((events & VIR_STREAM_EVENT_READABLE)
1035 && (ctxt->stbuf_off < sizeof(ctxt->stbuf))) {
1036 // if there's no space in the buffer, we need to wait until more has
1037 // been written to stdout
1038
1039 int bytes_recv = virStreamRecv(st, ctxt->stbuf + ctxt->stbuf_off,
1040 sizeof(ctxt->stbuf) - ctxt->stbuf_off);
1041 if (bytes_recv == -2)
1042 return; // EAGAIN
1043 if (bytes_recv <= 0) {
1044 if (bytes_recv == 0)
1045 virStreamFinish(st);
1046 disconnect = 1;
1047 return;
1048 }
1049 ctxt->stbuf_off += bytes_recv;
1050 }
1051
1052 if (ctxt->stbuf_off) // we have stuff to write to stdout
1053 virEventUpdateHandle(ctxt->stdout_w, VIR_EVENT_HANDLE_WRITABLE);
1054
1055 if (events & VIR_STREAM_EVENT_WRITABLE && ctxt->termbuf_off) {
1056 ssize_t bytes_sent = virStreamSend(st, ctxt->termbuf,
1057 ctxt->termbuf_off);
1058 if (bytes_sent == -2)
1059 return;
1060 if (bytes_sent < 0) {
1061 disconnect = 1;
1062 return;
1063 }
1064 // shift down
1065 memmove(ctxt->termbuf, ctxt->termbuf + bytes_sent,
1066 ctxt->termbuf_off - bytes_sent);
1067 ctxt->termbuf_off -= bytes_sent;
1068 }
1069
1070 if (!ctxt->termbuf_off) // there's nothing else to write to the stream
1071 virStreamEventUpdateCallback(st, VIR_STREAM_EVENT_READABLE);
1072
1073 if (events & (VIR_STREAM_EVENT_ERROR|VIR_STREAM_EVENT_HANGUP))
1074 disconnect = 1;
1075 }
1076
1077 static void
1078 on_child_exit(int sig)
1079 {
1080 if (sig == SIGCHLD) // sanity check
1081 disconnect = 1;
1082 }
1083
1084 static int
1085 cmd_connect()
1086 {
1087 int rc = 0;
1088 xmlDocPtr domXMLdoc = NULL;
1089 xmlNodeSetPtr ports = NULL;
1090 xmlNodePtr newPort = NULL;
1091 int hotplugged = 0;
1092
1093 event_context ctxt;
1094 memset(&ctxt, 0, sizeof(ctxt));
1095
1096 // check that libvirtd supports channels
1097 if (!libvirtdCanOpenChannel(conn)) {
1098 err("Channel support requires libvirtd v1.0.2+\n");
1099 return -1;
1100 }
1101
1102 if (virDomainIsActive(targetDom) != 1) {
1103 err("The domain is not running\n");
1104 return -1;
1105 }
1106
1107 // NB: setting liveConfig to 1
1108 if (getDomainPorts(targetDom, 1, PORT_BASENAME, &domXMLdoc, &ports) != 0)
1109 return -1; // proper error msg already emitted
1110
1111 // give a nice hint to the user
1112 if (ports->nodeNr == 0 && !canHotplug(conn)) {
1113 err("No SystemTap ports detected and hotplugging not available. Try "
1114 "using the command \'stapvirt port-add %s\' to add a port.\n",
1115 virDomainGetName(targetDom));
1116 goto error;
1117 }
1118
1119 // print an error if no channel found and we can't hotplug
1120 dbug(2, "trying to open a permanent port\n");
1121 ctxt.st = domainOpenChannel(conn, targetDom, ports);
1122 if (ctxt.st == NULL && !canHotplug(conn)) {
1123 err("Couldn't connect to any SystemTap port. Try using the command "
1124 "\'stapvirt port-add %s\' if more SystemTap ports are required.\n",
1125 virDomainGetName(targetDom));
1126 goto error;
1127 }
1128
1129 // let's try hotplugging!
1130 if (ctxt.st == NULL) {
1131 dbug(2, "trying to hotplug a port\n");
1132 if (generateNextPort(ports, targetDom, sockDir,
1133 PORT_BASENAME, &newPort) != 0)
1134 goto error; // proper error msg already emitted
1135 if (hotplugPort(1, targetDom, domXMLdoc, newPort) != 0)
1136 goto error; // proper error msg already emitted
1137 hotplugged = 1;
1138
1139 ctxt.st = virStreamNew(conn, VIR_STREAM_NONBLOCK);
1140 if (ctxt.st == NULL) {
1141 err("Couldn't create a new stream object\n");
1142 goto error;
1143 }
1144
1145 const char *name = getXmlPortName(newPort);
1146 if (virDomainOpenChannel(targetDom, name, ctxt.st, 0) != 0) {
1147 err("Couldn't open channel on hotplugged port\n");
1148 goto error_st;
1149 }
1150 dbug(2, "successfully hotplugged a port\n");
1151 } else {
1152 dbug(2, "successfully opened permanent port\n");
1153 }
1154
1155 // change stdin and stdout to O_NONBLOCK: even if libvirt uses poll(), it
1156 // can happen for read/write to block even though we got POLLIN/POLLOUT
1157 // (see the BUGS section of select(2))
1158 int flags = fcntl(STDIN_FILENO, F_GETFL);
1159 if (flags == -1 || fcntl(STDIN_FILENO, F_SETFL, flags|O_NONBLOCK) == -1) {
1160 err("Couldn't change stdin to non-blocking mode\n");
1161 goto error_st;
1162 }
1163 flags = fcntl(STDOUT_FILENO, F_GETFL);
1164 if (flags == -1 || fcntl(STDOUT_FILENO, F_SETFL, flags|O_NONBLOCK) == -1) {
1165 err("Couldn't change stdout to non-blocking mode\n");
1166 goto error_st;
1167 }
1168
1169 // install callbacks
1170 ctxt.stdin_w = virEventAddHandle(STDIN_FILENO, VIR_EVENT_HANDLE_READABLE,
1171 stdin_event, &ctxt, NULL);
1172 if (ctxt.stdin_w < 0) {
1173 err("Couldn't add handle for stdin\n");
1174 goto error_st;
1175 }
1176 ctxt.stdout_w = virEventAddHandle(STDOUT_FILENO, 0,
1177 stdout_event, &ctxt, NULL);
1178 if (ctxt.stdout_w < 0) {
1179 err("Couldn't add handle for stdout\n");
1180 goto error_st;
1181 }
1182 if (virStreamEventAddCallback(ctxt.st, VIR_STREAM_EVENT_READABLE,
1183 stream_event, &ctxt, NULL) < 0) {
1184 err("Couldn't add handle for stream\n");
1185 goto error_st;
1186 }
1187
1188 // install signal handler for SIGCHLD so that we stop the connection in case
1189 // the underlying transport app (e.g. ssh) exits (see also related note in
1190 // the libvirt_stapsh class in remote.cxx)
1191 struct sigaction sa;
1192 memset(&sa, 0, sizeof(sa));
1193 sa.sa_handler = on_child_exit;
1194 if (sigaction(SIGCHLD, &sa, NULL) != 0) {
1195 err("Couldn't add signal handler for SIGCHLD\n");
1196 goto error_st;
1197 }
1198
1199 // silence libvirt as some error messages might occur when child processes
1200 // such as ssh receive SIGINT
1201 virSetErrorFunc(NULL, silentErr);
1202
1203 dbug(2, "entering event loop\n");
1204 while (!disconnect) {
1205 if (virEventRunDefaultImpl() != 0)
1206 break;
1207 }
1208
1209 virStreamAbort(ctxt.st);
1210 virStreamFree(ctxt.st);
1211
1212 // restore error printing
1213 virSetErrorFunc(NULL, NULL);
1214
1215 goto cleanup;
1216
1217 error_st:
1218 virStreamFree(ctxt.st);
1219 error:
1220 rc = -1;
1221 cleanup:
1222 if (hotplugged && hotplugPort(0, targetDom, domXMLdoc, newPort) != 0)
1223 dbug(2, "could not unplug port\n");
1224 xmlXPathFreeNodeSet(ports);
1225 xmlFreeDoc(domXMLdoc);
1226 return rc;
1227 }
1228
1229 static void
1230 usage(int code)
1231 {
1232 // 80 screen width = 94 here --> put last printed char in col 93 at the V after this... V
1233 // The port-add command purposely does not also hotplug the port, even if
1234 // supported
1235 // - It would make it harder to figure out the final state
1236 // - If hotplugging is supported, users would not need to do port-add in
1237 // the first place, since they would let stap do the hotplugging
1238 eprintf("stapvirt v%s\n", VERSION);
1239 eprintf("Usage: stapvirt [-c URI] [-d PATH] [-v] COMMAND ARGUMENTS\n\n");
1240 eprintf(" -c Specify the libvirt driver URI to which to connect [default: NULL]\n");
1241 eprintf(" -d Specify the directory in which sockets should be placed [default:\n");
1242 eprintf(" /var/lib/libvirt/qemu]\n");
1243 eprintf(" -v Increase verbosity\n");
1244 eprintf("\n");
1245
1246 if (code != 0) {
1247 eprintf("Try the 'help' command for more information.\n");
1248 exit(code);
1249 }
1250
1251 eprintf("Available commands are:\n");
1252 eprintf("\n");
1253 eprintf(" help\n");
1254 eprintf(" Display this message.\n");
1255 eprintf(" list\n");
1256 eprintf(" List available domains.\n");
1257 eprintf(" port-add <domain>\n");
1258 eprintf(" Add a permanent SystemTap port to the domain's definition. If the\n");
1259 eprintf(" domain is currently running, it must be powered off before changes\n");
1260 eprintf(" take effect.\n");
1261 eprintf(" port-list <domain>\n");
1262 eprintf(" List the UNIX socket paths of the permanent SystemTap ports in the\n");
1263 eprintf(" domain's definition.\n");
1264 eprintf(" port-remove <domain>\n");
1265 eprintf(" Remove a permanent SystemTap port from the domain's definition. If\n");
1266 eprintf(" the domain is currently running, it must be powered off before\n");
1267 eprintf(" changes take effect.\n");
1268 eprintf(" query <domain>\n");
1269 eprintf(" Display the following information about the domain: its name, its\n");
1270 eprintf(" UUID, its state, the number of permanent SystemTap ports installed,\n");
1271 eprintf(" and whether hotplugging is supported.\n");
1272 /* UNDOCUMENTED COMMAND (for stap only)
1273 eprintf(" connect <domain>\n");
1274 eprintf(" Open up a connection to the domain. If successful, stapvirt becomes a\n");
1275 eprintf(" pass-through for stapsh. If no port found and hotplugging is\n");
1276 eprintf(" supported, then a new port is hotplugged for the duration of the\n");
1277 eprintf(" connection.\n");
1278 */
1279 eprintf("\n");
1280 eprintf("Domains can be specified using their name, UUID, or ID.\n");
1281 eprintf("\n");
1282 exit(code);
1283 }
1284
1285 static void
1286 parse_args(int argc, char* const argv[])
1287 {
1288 int c;
1289 while ((c = getopt(argc, argv, "c:d:hv")) != -1) {
1290 dbug(2, "received opt %c\n", c);
1291 switch (c) {
1292 case 'c':
1293 virURI = optarg;
1294 break;
1295 case 'd':
1296 sockDir = optarg;
1297 break;
1298 case 'h':
1299 usage(0); // no return
1300 case 'v':
1301 verbose++;
1302 break;
1303 default:
1304 usage(1); // no return
1305 }
1306 }
1307 }
1308
1309 static const command*
1310 lookupCommand(const char *cmd)
1311 {
1312 unsigned i;
1313 for (i = 0; i < ncommands; ++i) {
1314 if (strcmp(cmd, commands[i].name) == 0)
1315 return &commands[i];
1316 }
1317 return NULL;
1318 }
1319
1320 static int
1321 preCommand(const command *cmd)
1322 {
1323 // virConnect() may not be our first call, so let's explicitly initialize it
1324 // unconditionally
1325 if (virInitialize() != 0) {
1326 err("Couldn't initialize libvirt library\n");
1327 return -1;
1328 }
1329
1330 // For connect to work, we need to call virEventRegisterDefaultImpl()
1331 // before even connecting. This requirement may be because we don't
1332 // use threads
1333 if (strcmp(cmd->name, "connect") == 0) {
1334 if (virEventRegisterDefaultImpl() != 0) {
1335 err("Couldn't register the default event implementation\n");
1336 return -1;
1337 }
1338 }
1339
1340 dbug(1, "opening%s connection on URI '%s'\n",
1341 cmd->read_only ? " read-only" : "", virURI);
1342
1343 conn = virConnectOpenAuth(virURI, virConnectAuthPtrDefault,
1344 cmd->read_only ? VIR_CONNECT_RO : 0);
1345 if (conn == NULL) {
1346 err("Couldn't connect to %s\n", virURI);
1347 return -1;
1348 }
1349
1350 // if the command needs it, retrieve the domain
1351 if (cmd->need_domain && targetDomStr != NULL) {
1352 dbug(2, "searching for domain '%s'\n", targetDomStr);
1353 targetDom = findDomain(conn, targetDomStr);
1354 if (targetDom == NULL) {
1355 err("Couldn't find domain '%s' on URI '%s'\n",
1356 targetDomStr, virConnectGetURI(conn));
1357 virConnectClose(conn);
1358 return -1;
1359 }
1360 }
1361
1362 return 0;
1363 }
1364
1365 static void
1366 postCommand(void)
1367 {
1368 if (targetDom != NULL)
1369 virDomainFree(targetDom);
1370 virConnectClose(conn);
1371 }
1372
1373 int
1374 main(int argc, char* const argv[])
1375 {
1376 // initialize lib and make sure there's no version mismatch
1377 LIBXML_TEST_VERSION
1378
1379 parse_args(argc, argv);
1380
1381 // move to first non-option argument
1382 // getopt() puts them all at the end for us
1383 if (optind > 0) {
1384 argc -= optind;
1385 argv += optind;
1386 }
1387
1388 // check that we have at least a command
1389 if (argc <= 0)
1390 usage(1); // no return
1391 else if (strcmp(argv[0], "help") == 0)
1392 usage(0); // no return
1393
1394 dbug(2, "looking up command '%s'\n", argv[0]);
1395 const command *cmd = lookupCommand(argv[0]);
1396 if (cmd == NULL) {
1397 err("Invalid command '%s'\n", argv[0]);
1398 usage(1); // no return
1399 }
1400
1401 if (cmd->need_domain) {
1402 if (argc < 2) {
1403 err("Command %s requires a <domain> argument\n", cmd->name);
1404 usage(1); // no return
1405 } else {
1406 targetDomStr = argv[1];
1407 }
1408 }
1409
1410 if (preCommand(cmd) == -1)
1411 return EXIT_FAILURE; // preCommand printed an error already
1412
1413 dbug(2, "calling command %s\n", cmd->name);
1414 int rc = cmd->fn();
1415 postCommand();
1416
1417 return rc;
1418 }
1419
1420 /* vim: set sw=4 : */
This page took 0.129359 seconds and 5 git commands to generate.