1guestfs-examples(3) Virtualization Support guestfs-examples(3)
2
3
4
6 guestfs-examples - Examples of using libguestfs from C
7
9 #include <guestfs.h>
10
11 guestfs_h *g = guestfs_create ();
12 guestfs_add_drive_ro (g, "disk.img");
13 guestfs_launch (g);
14
15 cc prog.c -o prog -lguestfs
16 or:
17 cc prog.c -o prog `pkg-config libguestfs --cflags --libs`
18
20 This manual page contains examples of calling libguestfs from the C
21 programming language. If you are not familiar with using libguestfs,
22 you also need to read guestfs(3).
23
25 /* Example showing how to create a disk image. */
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <fcntl.h>
31 #include <unistd.h>
32 #include <guestfs.h>
33
34 int
35 main (int argc, char *argv[])
36 {
37 guestfs_h *g;
38 size_t i;
39
40 g = guestfs_create ();
41 if (g == NULL) {
42 perror ("failed to create libguestfs handle");
43 exit (EXIT_FAILURE);
44 }
45
46 /* Set the trace flag so that we can see each libguestfs call. */
47 guestfs_set_trace (g, 1);
48
49 /* Create a raw-format sparse disk image, 512 MB in size. */
50 if (guestfs_disk_create (g, "disk.img", "raw", UINT64_C(512)*1024*1024,
51 -1) == -1)
52 exit (EXIT_FAILURE);
53
54 /* Add the disk image to libguestfs. */
55 if (guestfs_add_drive_opts (g, "disk.img",
56 GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw", /* raw format */
57 GUESTFS_ADD_DRIVE_OPTS_READONLY, 0, /* for write */
58 -1) /* this marks end of optional arguments */
59 == -1)
60 exit (EXIT_FAILURE);
61
62 /* Run the libguestfs back-end. */
63 if (guestfs_launch (g) == -1)
64 exit (EXIT_FAILURE);
65
66 /* Get the list of devices. Because we only added one drive
67 * above, we expect that this list should contain a single
68 * element.
69 */
70 char **devices = guestfs_list_devices (g);
71 if (devices == NULL)
72 exit (EXIT_FAILURE);
73 if (devices[0] == NULL || devices[1] != NULL) {
74 fprintf (stderr, "error: expected a single device from list-devices\n");
75 exit (EXIT_FAILURE);
76 }
77
78 /* Partition the disk as one single MBR partition. */
79 if (guestfs_part_disk (g, devices[0], "mbr") == -1)
80 exit (EXIT_FAILURE);
81
82 /* Get the list of partitions. We expect a single element, which
83 * is the partition we have just created.
84 */
85 char **partitions = guestfs_list_partitions (g);
86 if (partitions == NULL)
87 exit (EXIT_FAILURE);
88 if (partitions[0] == NULL || partitions[1] != NULL) {
89 fprintf (stderr, "error: expected a single partition from list-partitions\n");
90 exit (EXIT_FAILURE);
91 }
92
93 /* Create a filesystem on the partition. */
94 if (guestfs_mkfs (g, "ext4", partitions[0]) == -1)
95 exit (EXIT_FAILURE);
96
97 /* Now mount the filesystem so that we can add files. */
98 if (guestfs_mount (g, partitions[0], "/") == -1)
99 exit (EXIT_FAILURE);
100
101 /* Create some files and directories. */
102 if (guestfs_touch (g, "/empty") == -1)
103 exit (EXIT_FAILURE);
104 const char *message = "Hello, world\n";
105 if (guestfs_write (g, "/hello", message, strlen (message)) == -1)
106 exit (EXIT_FAILURE);
107 if (guestfs_mkdir (g, "/foo") == -1)
108 exit (EXIT_FAILURE);
109
110 /* This one uploads the local file /etc/resolv.conf into
111 * the disk image.
112 */
113 if (guestfs_upload (g, "/etc/resolv.conf", "/foo/resolv.conf") == -1)
114 exit (EXIT_FAILURE);
115
116 /* Because we wrote to the disk and we want to detect write
117 * errors, call guestfs_shutdown. You don't need to do this:
118 * guestfs_close will do it implicitly.
119 */
120 if (guestfs_shutdown (g) == -1)
121 exit (EXIT_FAILURE);
122
123 guestfs_close (g);
124
125 /* Free up the lists. */
126 for (i = 0; devices[i] != NULL; ++i)
127 free (devices[i]);
128 free (devices);
129 for (i = 0; partitions[i] != NULL; ++i)
130 free (partitions[i]);
131 free (partitions);
132
133 exit (EXIT_SUCCESS);
134 }
135
137 /* Inspect a disk image and display operating systems it may contain. */
138
139 #include <stdio.h>
140 #include <stdlib.h>
141 #include <string.h>
142 #include <guestfs.h>
143
144 static int
145 compare_keys_len (const void *p1, const void *p2)
146 {
147 const char *key1 = * (char * const *) p1;
148 const char *key2 = * (char * const *) p2;
149 return strlen (key1) - strlen (key2);
150 }
151
152 static size_t
153 count_strings (char *const *argv)
154 {
155 size_t c;
156
157 for (c = 0; argv[c]; ++c)
158 ;
159 return c;
160 }
161
162 int
163 main (int argc, char *argv[])
164 {
165 guestfs_h *g;
166 const char *disk;
167 char **roots, *root, *str, **mountpoints, **lines;
168 size_t i, j;
169
170 if (argc != 2) {
171 fprintf (stderr, "usage: inspect_vm disk.img\n");
172 exit (EXIT_FAILURE);
173 }
174 disk = argv[1];
175
176 g = guestfs_create ();
177 if (g == NULL) {
178 perror ("failed to create libguestfs handle");
179 exit (EXIT_FAILURE);
180 }
181
182 /* Attach the disk image read-only to libguestfs. */
183 if (guestfs_add_drive_opts (g, disk,
184 /* GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw", */
185 GUESTFS_ADD_DRIVE_OPTS_READONLY, 1,
186 -1) /* this marks end of optional arguments */
187 == -1)
188 exit (EXIT_FAILURE);
189
190 /* Run the libguestfs back-end. */
191 if (guestfs_launch (g) == -1)
192 exit (EXIT_FAILURE);
193
194 /* Ask libguestfs to inspect for operating systems. */
195 roots = guestfs_inspect_os (g);
196 if (roots == NULL)
197 exit (EXIT_FAILURE);
198 if (roots[0] == NULL) {
199 fprintf (stderr, "inspect_vm: no operating systems found\n");
200 exit (EXIT_FAILURE);
201 }
202
203 for (j = 0; roots[j] != NULL; ++j) {
204 root = roots[j];
205
206 printf ("Root device: %s\n", root);
207
208 /* Print basic information about the operating system. */
209 str = guestfs_inspect_get_product_name (g, root);
210 if (str)
211 printf (" Product name: %s\n", str);
212 free (str);
213
214 printf (" Version: %d.%d\n",
215 guestfs_inspect_get_major_version (g, root),
216 guestfs_inspect_get_minor_version (g, root));
217
218 str = guestfs_inspect_get_type (g, root);
219 if (str)
220 printf (" Type: %s\n", str);
221 free (str);
222 str = guestfs_inspect_get_distro (g, root);
223 if (str)
224 printf (" Distro: %s\n", str);
225 free (str);
226
227 /* Mount up the disks, like guestfish -i.
228 *
229 * Sort keys by length, shortest first, so that we end up
230 * mounting the filesystems in the correct order.
231 */
232 mountpoints = guestfs_inspect_get_mountpoints (g, root);
233 if (mountpoints == NULL)
234 exit (EXIT_FAILURE);
235
236 qsort (mountpoints, count_strings (mountpoints) / 2, 2 * sizeof (char *),
237 compare_keys_len);
238 for (i = 0; mountpoints[i] != NULL; i += 2) {
239 /* Ignore failures from this call, since bogus entries can
240 * appear in the guest's /etc/fstab.
241 */
242 guestfs_mount_ro (g, mountpoints[i+1], mountpoints[i]);
243 free (mountpoints[i]);
244 free (mountpoints[i+1]);
245 }
246 free (mountpoints);
247
248 /* If /etc/issue.net file exists, print up to 3 lines. */
249 if (guestfs_is_file (g, "/etc/issue.net") > 0) {
250 printf ("--- /etc/issue.net ---\n");
251 lines = guestfs_head_n (g, 3, "/etc/issue.net");
252 if (lines == NULL)
253 exit (EXIT_FAILURE);
254 for (i = 0; lines[i] != NULL; ++i) {
255 printf ("%s\n", lines[i]);
256 free (lines[i]);
257 }
258 free (lines);
259 }
260
261 /* Unmount everything. */
262 if (guestfs_umount_all (g) == -1)
263 exit (EXIT_FAILURE);
264
265 free (root);
266 }
267 free (roots);
268
269 guestfs_close (g);
270
271 exit (EXIT_SUCCESS);
272 }
273
275 /* Example showing how to enable debugging, and capture it into any
276 * custom logging system (syslog in this example, but any could be
277 * used). Note this uses the event API which is also available in
278 * non-C language bindings.
279 */
280
281 #include <stdio.h>
282 #include <stdlib.h>
283 #include <string.h>
284 #include <unistd.h>
285 #include <syslog.h>
286 #include <guestfs.h>
287
288 static void message_callback (guestfs_h *g, void *opaque, uint64_t event, int event_handle, int flags, const char *buf, size_t buf_len, const uint64_t *array, size_t array_len);
289
290 /* Events we are interested in. This bitmask covers all trace and
291 * debug messages.
292 */
293 static const uint64_t event_bitmask =
294 GUESTFS_EVENT_LIBRARY |
295 GUESTFS_EVENT_WARNING |
296 GUESTFS_EVENT_APPLIANCE |
297 GUESTFS_EVENT_TRACE;
298
299 int
300 main (int argc, char *argv[])
301 {
302 guestfs_h *g;
303
304 g = guestfs_create ();
305 if (g == NULL) {
306 perror ("failed to create libguestfs handle");
307 exit (EXIT_FAILURE);
308 }
309
310 /* By default, debugging information is printed on stderr. To
311 * capture it somewhere else you have to set up an event handler
312 * which will be called back as debug messages are generated. To do
313 * this use the event API.
314 *
315 * For more information see EVENTS in guestfs(3).
316 */
317 if (guestfs_set_event_callback (g, message_callback,
318 event_bitmask, 0, NULL) == -1)
319 exit (EXIT_FAILURE);
320
321 /* This is how debugging is enabled:
322 *
323 * Setting the 'trace' flag in the handle means that each libguestfs
324 * call is logged (name, parameters, return). This flag is useful
325 * to see how libguestfs is being used by a program.
326 *
327 * Setting the 'verbose' flag enables a great deal of extra
328 * debugging throughout the system. This is useful if there is a
329 * libguestfs error which you don't understand.
330 *
331 * Note that you should set the flags early on after creating the
332 * handle. In particular if you set the verbose flag after launch
333 * then you won't see all messages.
334 *
335 * For more information see:
336 * http://libguestfs.org/guestfs-faq.1.html#debugging-libguestfs
337 *
338 * Error messages raised by APIs are *not* debugging information,
339 * and they are not affected by any of this. You may have to log
340 * them separately.
341 */
342 guestfs_set_trace (g, 1);
343 guestfs_set_verbose (g, 1);
344
345 /* Do some operations which will generate plenty of trace and debug
346 * messages.
347 */
348 if (guestfs_add_drive (g, "/dev/null") == -1)
349 exit (EXIT_FAILURE);
350
351 printf ("There is no output from this program. "
352 "Take a look in your system log file,\n"
353 "eg. /var/log/messages.\n");
354
355 if (guestfs_launch (g) == -1)
356 exit (EXIT_FAILURE);
357
358 guestfs_close (g);
359
360 exit (EXIT_SUCCESS);
361 }
362
363 /* This function is called back by libguestfs whenever a trace or
364 * debug message is generated.
365 *
366 * For the classes of events we have registered above, 'array' and
367 * 'array_len' will not be meaningful. Only 'buf' and 'buf_len' will
368 * be interesting and these will contain the trace or debug message.
369 *
370 * This example simply redirects these messages to syslog, but
371 * obviously you could do something more advanced here.
372 */
373 static void
374 message_callback (guestfs_h *g, void *opaque,
375 uint64_t event, int event_handle,
376 int flags,
377 const char *buf, size_t buf_len,
378 const uint64_t *array, size_t array_len)
379 {
380 const int priority = LOG_USER|LOG_INFO;
381 char *event_name, *msg;
382
383 if (buf_len > 0) {
384 event_name = guestfs_event_to_string (event);
385 msg = strndup (buf, buf_len);
386 syslog (priority, "[%s] %s", event_name, msg);
387 free (msg);
388 free (event_name);
389 }
390 }
391
393 /* This example inspects a guest using libguestfs inspection (see
394 * "INSPECTION" in guestfs(3)), and if possible displays a
395 * representative icon or logo for the guest's operating system.
396 */
397
398 #include <stdio.h>
399 #include <stdlib.h>
400 #include <string.h>
401 #include <guestfs.h>
402
403 static int
404 compare_keys_len (const void *p1, const void *p2)
405 {
406 const char *key1 = * (char * const *) p1;
407 const char *key2 = * (char * const *) p2;
408 return strlen (key1) - strlen (key2);
409 }
410
411 static size_t
412 count_strings (char *const *argv)
413 {
414 size_t c;
415
416 for (c = 0; argv[c]; ++c)
417 ;
418 return c;
419 }
420
421 int
422 main (int argc, char *argv[])
423 {
424 guestfs_h *g;
425 const char *disk;
426 char **roots, *root, **mountpoints, *icon;
427 size_t i, j, icon_size;
428 FILE *fp;
429
430 if (argc != 2) {
431 fprintf (stderr, "usage: display-icon disk.img\n");
432 exit (EXIT_FAILURE);
433 }
434 disk = argv[1];
435
436 g = guestfs_create ();
437 if (g == NULL) {
438 perror ("failed to create libguestfs handle");
439 exit (EXIT_FAILURE);
440 }
441
442 /* Attach the disk image read-only to libguestfs. */
443 if (guestfs_add_drive_opts (g, disk,
444 /* GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw", */
445 GUESTFS_ADD_DRIVE_OPTS_READONLY, 1,
446 -1) /* this marks end of optional arguments */
447 == -1)
448 exit (EXIT_FAILURE);
449
450 /* Run the libguestfs back-end. */
451 if (guestfs_launch (g) == -1)
452 exit (EXIT_FAILURE);
453
454 /* Ask libguestfs to inspect for operating systems. */
455 roots = guestfs_inspect_os (g);
456 if (roots == NULL)
457 exit (EXIT_FAILURE);
458 if (roots[0] == NULL) {
459 fprintf (stderr, "display-icon: no operating systems found\n");
460 exit (EXIT_FAILURE);
461 }
462
463 for (j = 0; roots[j] != NULL; ++j) {
464 root = roots[j];
465
466 /* Mount up the disks, like guestfish -i.
467 *
468 * Sort keys by length, shortest first, so that we end up
469 * mounting the filesystems in the correct order.
470 */
471 mountpoints = guestfs_inspect_get_mountpoints (g, root);
472 if (mountpoints == NULL)
473 exit (EXIT_FAILURE);
474
475 qsort (mountpoints, count_strings (mountpoints) / 2, 2 * sizeof (char *),
476 compare_keys_len);
477 for (i = 0; mountpoints[i] != NULL; i += 2) {
478 /* Ignore failures from this call, since bogus entries can
479 * appear in the guest's /etc/fstab.
480 */
481 guestfs_mount_ro (g, mountpoints[i+1], mountpoints[i]);
482 free (mountpoints[i]);
483 free (mountpoints[i+1]);
484 }
485 free (mountpoints);
486
487 /* Get the icon.
488 * This function returns a buffer ('icon'). Normally it is a png
489 * file, returned as a string, but it can also be a zero length
490 * buffer which has a special meaning, or NULL which means there
491 * was an error.
492 */
493 icon = guestfs_inspect_get_icon (g, root, &icon_size, -1);
494 if (!icon) /* actual libguestfs error */
495 exit (EXIT_FAILURE);
496 if (icon_size == 0) /* no icon available */
497 fprintf (stderr, "%s: %s: no icon available for this operating system\n",
498 disk, root);
499 else {
500 /* Display the icon. */
501 fp = popen ("display -", "w");
502 if (fp == NULL) {
503 perror ("display");
504 exit (EXIT_FAILURE);
505 }
506 if (fwrite (icon, 1, icon_size, fp) != icon_size) {
507 perror ("write");
508 exit (EXIT_FAILURE);
509 }
510 if (pclose (fp) == -1) {
511 perror ("pclose");
512 exit (EXIT_FAILURE);
513 }
514 }
515 free (icon);
516
517 /* Unmount everything. */
518 if (guestfs_umount_all (g) == -1)
519 exit (EXIT_FAILURE);
520
521 free (root);
522 }
523 free (roots);
524
525 guestfs_close (g);
526
527 exit (EXIT_SUCCESS);
528 }
529
531 /* Example of using the libvirt authentication event-driven API.
532 *
533 * See "LIBVIRT AUTHENTICATION" in guestfs(3).
534 */
535
536 #include <stdio.h>
537 #include <stdlib.h>
538 #include <string.h>
539 #include <unistd.h>
540
541 #include <guestfs.h>
542
543 static void
544 usage (void)
545 {
546 fprintf (stderr,
547 "Usage:\n"
548 "\n"
549 " libvirt-auth URI domain\n"
550 "\n"
551 "where:\n"
552 "\n"
553 " URI is the libvirt URI, eg. qemu+libssh2://USER@localhost/system\n"
554 " domain is the name of the guest\n"
555 "\n"
556 "Example:\n"
557 "\n"
558 " libvirt-auth 'qemu+libssh2://USER@localhost/system' 'foo'\n"
559 "\n"
560 "would connect (read-only) to libvirt URI given and open the guest\n"
561 "called 'foo' and list some information about its filesystems.\n"
562 "\n"
563 "The important point of this example is that any libvirt authentication\n"
564 "required to connect to the server should be done.\n"
565 "\n");
566 }
567
568 static void auth_callback (guestfs_h *g, void *opaque, uint64_t event, int event_handle, int flags, const char *buf, size_t buf_len, const uint64_t *array, size_t array_len);
569
570 int
571 main (int argc, char *argv[])
572 {
573 const char *uri, *dom;
574 guestfs_h *g;
575 const char *creds[] = { "authname", "passphrase",
576 "echoprompt", "noechoprompt", NULL };
577 int r, eh;
578 char **filesystems;
579 size_t i;
580
581 if (argc != 3) {
582 usage ();
583 exit (EXIT_FAILURE);
584 }
585 uri = argv[1];
586 dom = argv[2];
587
588 g = guestfs_create ();
589 if (!g)
590 exit (EXIT_FAILURE);
591
592 r = guestfs_set_libvirt_supported_credentials (g, (char **) creds);
593 if (r == -1)
594 exit (EXIT_FAILURE);
595
596 /* Set up the event handler. */
597 eh = guestfs_set_event_callback (g, auth_callback,
598 GUESTFS_EVENT_LIBVIRT_AUTH, 0, NULL);
599 if (eh == -1)
600 exit (EXIT_FAILURE);
601
602 /* Add the named domain. */
603 r = guestfs_add_domain (g, dom,
604 GUESTFS_ADD_DOMAIN_LIBVIRTURI, uri,
605 -1);
606 if (r == -1)
607 exit (EXIT_FAILURE);
608
609 /* Launch and do some simple inspection. */
610 r = guestfs_launch (g);
611 if (r == -1)
612 exit (EXIT_FAILURE);
613
614 filesystems = guestfs_list_filesystems (g);
615 if (filesystems == NULL)
616 exit (EXIT_FAILURE);
617
618 for (i = 0; filesystems[i] != NULL; i += 2) {
619 printf ("%s:%s is a %s filesystem\n",
620 dom, filesystems[i], filesystems[i+1]);
621 free (filesystems[i]);
622 free (filesystems[i+1]);
623 }
624 free (filesystems);
625
626 exit (EXIT_SUCCESS);
627 }
628
629 static void
630 auth_callback (guestfs_h *g,
631 void *opaque,
632 uint64_t event,
633 int event_handle,
634 int flags,
635 const char *buf, size_t buf_len,
636 const uint64_t *array, size_t array_len)
637 {
638 char **creds;
639 size_t i;
640 char *prompt;
641 char *reply = NULL;
642 size_t allocsize = 0;
643 char *pass;
644 ssize_t len;
645 int r;
646
647 printf ("libvirt-auth.c: authentication required for libvirt URI '%s'\n\n",
648 buf);
649
650 /* Ask libguestfs what credentials libvirt is demanding. */
651 creds = guestfs_get_libvirt_requested_credentials (g);
652 if (creds == NULL)
653 exit (EXIT_FAILURE);
654
655 /* Now ask the user for answers. */
656 for (i = 0; creds[i] != NULL; ++i)
657 {
658 printf ("libvirt-auth.c: credential '%s'\n", creds[i]);
659
660 if (strcmp (creds[i], "authname") == 0 ||
661 strcmp (creds[i], "echoprompt") == 0) {
662 prompt = guestfs_get_libvirt_requested_credential_prompt (g, i);
663 if (prompt && strcmp (prompt, "") != 0)
664 printf ("%s: ", prompt);
665 free (prompt);
666
667 len = getline (&reply, &allocsize, stdin);
668 if (len == -1) {
669 perror ("getline");
670 exit (EXIT_FAILURE);
671 }
672 if (len > 0 && reply[len-1] == '\n')
673 reply[--len] = '\0';
674
675 r = guestfs_set_libvirt_requested_credential (g, i, reply, len);
676 if (r == -1)
677 exit (EXIT_FAILURE);
678 } else if (strcmp (creds[i], "passphrase") == 0 ||
679 strcmp (creds[i], "noechoprompt") == 0) {
680 prompt = guestfs_get_libvirt_requested_credential_prompt (g, i);
681 if (prompt && strcmp (prompt, "") != 0)
682 printf ("%s: ", prompt);
683 free (prompt);
684
685 pass = getpass ("");
686 if (pass == NULL) {
687 perror ("getpass");
688 exit (EXIT_FAILURE);
689 }
690 len = strlen (pass);
691
692 r = guestfs_set_libvirt_requested_credential (g, i, pass, len);
693 if (r == -1)
694 exit (EXIT_FAILURE);
695 }
696
697 free (creds[i]);
698 }
699
700 free (reply);
701 free (creds);
702 }
703
705 /* Demonstrate the use of the 'mount-local' API.
706 *
707 * Run this program as (eg) mount-local /tmp/test.img. Note that
708 * '/tmp/test.img' is created or overwritten. Follow the instructions
709 * on screen.
710 *
711 * See "MOUNT LOCAL" in guestfs(3).
712 */
713
714 #include <stdio.h>
715 #include <stdlib.h>
716 #include <string.h>
717 #include <unistd.h>
718 #include <fcntl.h>
719 #include <sys/wait.h>
720
721 #include <guestfs.h>
722
723 #ifndef O_CLOEXEC
724 #define O_CLOEXEC 0
725 #endif
726
727 /* Define a list of filesystem mount options (used on the libguestfs
728 * side, nothing to do with FUSE). An empty string may be used here
729 * instead.
730 */
731 #define MOUNT_OPTIONS "acl,user_xattr"
732
733 /* Size of the disk (megabytes). */
734 #define SIZE_MB 512
735
736 static void
737 usage (void)
738 {
739 fprintf (stderr,
740 "Usage: mount-local disk.img\n"
741 "\n"
742 "NOTE: disk.img will be created or overwritten.\n"
743 "\n");
744 }
745
746 int
747 main (int argc, char *argv[])
748 {
749 guestfs_h *g;
750 int r;
751 char tempdir[] = "/tmp/mlXXXXXX";
752 pid_t pid;
753 char *shell, *p;
754
755 if (argc != 2) {
756 usage ();
757 exit (EXIT_FAILURE);
758 }
759
760 if (argv[1][0] == '-') {
761 usage ();
762 exit (EXIT_FAILURE);
763 }
764
765 printf ("\n"
766 "This is the 'mount-local' demonstration program. Follow the\n"
767 "instructions on screen.\n"
768 "\n"
769 "Creating and formatting the disk image, please wait a moment ...\n");
770 fflush (stdout);
771
772 /* Guestfs handle. */
773 g = guestfs_create ();
774 if (g == NULL) {
775 perror ("could not create libguestfs handle");
776 exit (EXIT_FAILURE);
777 }
778
779 /* Create the output disk image: raw sparse. */
780 if (guestfs_disk_create (g, argv[1], "raw", SIZE_MB * 1024 * 1024, -1) == -1)
781 exit (EXIT_FAILURE);
782
783 /* Create the disk image and format it with a partition and a filesystem. */
784 if (guestfs_add_drive_opts (g, argv[1],
785 GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw",
786 -1) == -1)
787 exit (EXIT_FAILURE);
788
789 if (guestfs_launch (g) == -1)
790 exit (EXIT_FAILURE);
791
792 if (guestfs_part_disk (g, "/dev/sda", "mbr") == -1)
793 exit (EXIT_FAILURE);
794
795 if (guestfs_mkfs (g, "ext2", "/dev/sda1") == -1)
796 exit (EXIT_FAILURE);
797
798 /* Mount the empty filesystem. */
799 if (guestfs_mount_options (g, MOUNT_OPTIONS, "/dev/sda1", "/") == -1)
800 exit (EXIT_FAILURE);
801
802 /* Create a file in the new filesystem. */
803 if (guestfs_touch (g, "/PUT_FILES_AND_DIRECTORIES_HERE") == -1)
804 exit (EXIT_FAILURE);
805
806 /* Create a temporary mount directory. */
807 if (mkdtemp (tempdir) == NULL) {
808 perror ("mkdtemp");
809 exit (EXIT_FAILURE);
810 }
811
812 /* Mount the filesystem. */
813 if (guestfs_mount_local (g, tempdir, -1) == -1)
814 exit (EXIT_FAILURE);
815
816 /* Fork the shell for the user. */
817 pid = fork ();
818 if (pid == -1) {
819 perror ("fork");
820 exit (EXIT_FAILURE);
821 }
822
823 if (pid == 0) { /* Child. */
824 if (chdir (tempdir) == -1) {
825 perror (tempdir);
826 _exit (EXIT_FAILURE);
827 }
828
829 printf ("\n"
830 "The *current directory* is a FUSE filesystem backed by the disk\n"
831 "image which is managed by libguestfs. Any files or directories\n"
832 "you copy into here (up to %d MB) will be saved into the disk\n"
833 "image. You can also delete files, create certain special files\n"
834 "and so on.\n"
835 "\n"
836 "When you have finished adding files, hit ^D or type 'exit' to\n"
837 "exit the shell and return to the mount-local program.\n"
838 "\n",
839 SIZE_MB);
840
841 shell = getenv ("SHELL");
842 if (!shell)
843 r = system ("/bin/sh");
844 else {
845 /* Set a magic prompt. We only know how to do this for bash. */
846 p = strrchr (shell, '/');
847 if (p && strcmp (p+1, "bash") == 0) {
848 const size_t len = 64 + strlen (shell);
849 char *buf;
850
851 buf = malloc (len);
852 if (buf == NULL) {
853 perror ("malloc");
854 _exit (EXIT_FAILURE);
855 }
856 snprintf (buf, len, "PS1='mount-local-shell> ' %s --norc -i", shell);
857 r = system (buf);
858 free (buf);
859 } else
860 r = system (shell);
861 }
862 if (r == -1) {
863 fprintf (stderr, "error: failed to run sub-shell (%s) "
864 "(is $SHELL set correctly?)\n",
865 shell);
866 //FALLTHROUGH
867 }
868
869 if (chdir ("/") == -1)
870 perror ("chdir: /");
871 guestfs_umount_local (g, GUESTFS_UMOUNT_LOCAL_RETRY, 1, -1);
872 _exit (EXIT_SUCCESS);
873 }
874
875 /* Note that we are *not* waiting for the child yet. We want to
876 * run the FUSE code in parallel with the subshell.
877 */
878
879 /* We're going to hide libguestfs errors here, but in a real program
880 * you would probably want to log them somewhere.
881 */
882 guestfs_push_error_handler (g, NULL, NULL);
883
884 /* Now run the FUSE thread. */
885 if (guestfs_mount_local_run (g) == -1)
886 exit (EXIT_FAILURE);
887
888 guestfs_pop_error_handler (g);
889
890 waitpid (pid, NULL, 0);
891
892 /* Shutdown the handle explicitly so write errors can be detected. */
893 if (guestfs_shutdown (g) == -1)
894 exit (EXIT_FAILURE);
895
896 guestfs_close (g);
897
898 printf ("\n"
899 "Any files or directories that you copied in have been saved into\n"
900 "the disk image called '%s'.\n"
901 "\n"
902 "Try opening the disk image with guestfish to see those files:\n"
903 "\n"
904 " guestfish -a %s -m /dev/sda1\n"
905 "\n",
906 argv[1], argv[1]);
907
908 exit (EXIT_SUCCESS);
909 }
910
912 /* Copy a directory from one libvirt guest to another.
913 *
914 * This is a more substantial example of using the libguestfs API,
915 * demonstrating amongst other things:
916 *
917 * - using multiple handles with threads
918 * - upload and downloading (using a pipe between handles)
919 * - inspection
920 */
921
922 #include <stdio.h>
923 #include <stdlib.h>
924 #include <stdint.h>
925 #include <inttypes.h>
926 #include <string.h>
927 #include <unistd.h>
928 #include <fcntl.h>
929 #include <errno.h>
930 #include <sys/time.h>
931
932 #include <pthread.h>
933
934 #include <guestfs.h>
935
936 struct threaddata {
937 const char *src;
938 const char *srcdir;
939 int fd;
940 pthread_t mainthread;
941 };
942
943 static void *start_srcthread (void *);
944 static int open_guest (guestfs_h *g, const char *dom, int readonly);
945 static int64_t timeval_diff (const struct timeval *x, const struct timeval *y);
946 static int compare_keys_len (const void *p1, const void *p2);
947 static size_t count_strings (char *const *argv);
948
949 static void
950 usage (void)
951 {
952 fprintf (stderr,
953 "Usage: copy-over source srcdir dest destdir\n"
954 "\n"
955 " source : the source domain (a libvirt guest name)\n"
956 " srcdir : the directory to copy from the source guest\n"
957 " dest : the destination domain (a libvirt guest name)\n"
958 " destdir : the destination directory (must exist at destination)\n"
959 "\n"
960 "eg: copy-over Src /home/rjones Dest /tmp/dir\n"
961 "would copy /home/rjones from Src to /tmp/dir on Dest\n"
962 "\n"
963 "The destination guest cannot be running.\n");
964 }
965
966 int
967 main (int argc, char *argv[])
968 {
969 const char *src, *srcdir, *dest, *destdir;
970 guestfs_h *destg;
971 int fd[2];
972 pthread_t srcthread;
973 struct threaddata threaddata;
974 int err;
975 char fdname[128];
976 struct timeval start_t, end_t;
977 int64_t ms;
978
979 if (argc != 5) {
980 usage ();
981 exit (EXIT_FAILURE);
982 }
983
984 src = argv[1];
985 srcdir = argv[2];
986 dest = argv[3];
987 destdir = argv[4];
988
989 /* Instead of downloading to local disk and uploading, we are going
990 * to connect the source download and destination upload using a
991 * pipe. Create that pipe.
992 */
993 if (pipe (fd) == -1) {
994 perror ("pipe");
995 exit (EXIT_FAILURE);
996 }
997
998 /* We don't want the pipe to be passed to subprocesses. */
999 if (fcntl (fd[0], F_SETFD, FD_CLOEXEC) == -1 ||
1000 fcntl (fd[1], F_SETFD, FD_CLOEXEC) == -1) {
1001 perror ("fcntl");
1002 exit (EXIT_FAILURE);
1003 }
1004
1005 /* The libguestfs API is synchronous, so if we want to use two
1006 * handles concurrently, then we have to have two threads. In this
1007 * case the main thread (this one) is handling the destination
1008 * domain (uploading), and we create one more thread to handle the
1009 * source domain (downloading).
1010 */
1011 threaddata.src = src;
1012 threaddata.srcdir = srcdir;
1013 threaddata.fd = fd[1];
1014 threaddata.mainthread = pthread_self ();
1015 err = pthread_create (&srcthread, NULL, start_srcthread, &threaddata);
1016 if (err != 0) {
1017 fprintf (stderr, "pthread_create: %s\n", strerror (err));
1018 exit (EXIT_FAILURE);
1019 }
1020
1021 /* Open the destination domain. */
1022 destg = guestfs_create ();
1023 if (!destg) {
1024 perror ("failed to create libguestfs handle");
1025 pthread_cancel (srcthread);
1026 exit (EXIT_FAILURE);
1027 }
1028 if (open_guest (destg, dest, 0) == -1) {
1029 pthread_cancel (srcthread);
1030 exit (EXIT_FAILURE);
1031 }
1032
1033 gettimeofday (&start_t, NULL);
1034
1035 /* Begin the upload. */
1036 snprintf (fdname, sizeof fdname, "/dev/fd/%d", fd[0]);
1037 if (guestfs_tar_in (destg, fdname, destdir) == -1) {
1038 pthread_cancel (srcthread);
1039 exit (EXIT_FAILURE);
1040 }
1041
1042 /* Close our end of the pipe. The other thread will close the
1043 * other side of the pipe.
1044 */
1045 close (fd[0]);
1046
1047 /* Wait for the other thread to finish. */
1048 err = pthread_join (srcthread, NULL);
1049 if (err != 0) {
1050 fprintf (stderr, "pthread_join: %s\n", strerror (err));
1051 exit (EXIT_FAILURE);
1052 }
1053
1054 /* Clean up. */
1055 if (guestfs_shutdown (destg) == -1)
1056 exit (EXIT_FAILURE);
1057 guestfs_close (destg);
1058
1059 gettimeofday (&end_t, NULL);
1060
1061 /* Print the elapsed time. */
1062 ms = timeval_diff (&start_t, &end_t);
1063 printf ("copy finished, elapsed time (excluding launch) was "
1064 "%" PRIi64 ".%03" PRIi64 " s\n",
1065 ms / 1000, ms % 1000);
1066
1067 exit (EXIT_SUCCESS);
1068 }
1069
1070 static void *
1071 start_srcthread (void *arg)
1072 {
1073 struct threaddata *threaddata = arg;
1074 guestfs_h *srcg;
1075 char fdname[128];
1076
1077 /* Open the source domain. */
1078 srcg = guestfs_create ();
1079 if (!srcg) {
1080 perror ("failed to create libguestfs handle");
1081 pthread_cancel (threaddata->mainthread);
1082 exit (EXIT_FAILURE);
1083 }
1084 if (open_guest (srcg, threaddata->src, 1) == -1) {
1085 pthread_cancel (threaddata->mainthread);
1086 exit (EXIT_FAILURE);
1087 }
1088
1089 /* Begin the download. */
1090 snprintf (fdname, sizeof fdname, "/dev/fd/%d", threaddata->fd);
1091 if (guestfs_tar_out (srcg, threaddata->srcdir, fdname) == -1) {
1092 pthread_cancel (threaddata->mainthread);
1093 exit (EXIT_FAILURE);
1094 }
1095
1096 /* Close the pipe; this will cause the receiver to finish the upload. */
1097 if (close (threaddata->fd) == -1) {
1098 pthread_cancel (threaddata->mainthread);
1099 exit (EXIT_FAILURE);
1100 }
1101
1102 /* Clean up. */
1103 guestfs_close (srcg);
1104
1105 return NULL;
1106 }
1107
1108 /* This function deals with the complexity of adding the domain,
1109 * launching the handle, and mounting up filesystems. See
1110 * 'examples/inspect-vm.c' to understand how this works.
1111 */
1112 static int
1113 open_guest (guestfs_h *g, const char *dom, int readonly)
1114 {
1115 char **roots, *root, **mountpoints;
1116 size_t i;
1117
1118 /* Use libvirt to find the guest disks and add them to the handle. */
1119 if (guestfs_add_domain (g, dom,
1120 GUESTFS_ADD_DOMAIN_READONLY, readonly,
1121 -1) == -1)
1122 return -1;
1123
1124 if (guestfs_launch (g) == -1)
1125 return -1;
1126
1127 /* Inspect the guest, looking for operating systems. */
1128 roots = guestfs_inspect_os (g);
1129 if (roots == NULL)
1130 return -1;
1131
1132 if (roots[0] == NULL || roots[1] != NULL) {
1133 fprintf (stderr, "copy-over: %s: no operating systems or multiple operating systems found\n", dom);
1134 return -1;
1135 }
1136
1137 root = roots[0];
1138
1139 /* Mount up the filesystems (like 'guestfish -i'). */
1140 mountpoints = guestfs_inspect_get_mountpoints (g, root);
1141 if (mountpoints == NULL)
1142 return -1;
1143
1144 qsort (mountpoints, count_strings (mountpoints) / 2, 2 * sizeof (char *),
1145 compare_keys_len);
1146 for (i = 0; mountpoints[i] != NULL; i += 2) {
1147 /* Ignore failures from this call, since bogus entries can
1148 * appear in the guest's /etc/fstab.
1149 */
1150 (readonly ? guestfs_mount_ro : guestfs_mount)
1151 (g, mountpoints[i+1], mountpoints[i]);
1152 free (mountpoints[i]);
1153 free (mountpoints[i+1]);
1154 }
1155
1156 free (mountpoints);
1157
1158 free (root);
1159 free (roots);
1160
1161 /* Everything ready, no error. */
1162 return 0;
1163 }
1164
1165 /* Compute Y - X and return the result in milliseconds.
1166 * Approximately the same as this code:
1167 * http://www.mpp.mpg.de/~huber/util/timevaldiff.c
1168 */
1169 static int64_t
1170 timeval_diff (const struct timeval *x, const struct timeval *y)
1171 {
1172 int64_t msec;
1173
1174 msec = (y->tv_sec - x->tv_sec) * 1000;
1175 msec += (y->tv_usec - x->tv_usec) / 1000;
1176 return msec;
1177 }
1178
1179 static int
1180 compare_keys_len (const void *p1, const void *p2)
1181 {
1182 const char *key1 = * (char * const *) p1;
1183 const char *key2 = * (char * const *) p2;
1184 return strlen (key1) - strlen (key2);
1185 }
1186
1187 static size_t
1188 count_strings (char *const *argv)
1189 {
1190 size_t c;
1191
1192 for (c = 0; argv[c]; ++c)
1193 ;
1194 return c;
1195 }
1196
1198 /* This is a more significant example of a tool which can grab the
1199 * DHCP address from some types of virtual machine. Since there are
1200 * so many possible ways to do this, without clarity on which is the
1201 * best way, I don't want to make this into an official virt tool.
1202 *
1203 * For more information, see:
1204 *
1205 * https://rwmj.wordpress.com/2010/10/26/tip-find-the-ip-address-of-a-virtual-machine/
1206 * https://rwmj.wordpress.com/2011/03/30/tip-another-way-to-get-the-ip-address-of-a-virtual-machine/
1207 */
1208
1209 #include <stdio.h>
1210 #include <stdlib.h>
1211 #include <string.h>
1212 #include <errno.h>
1213 #include <unistd.h>
1214 #include <assert.h>
1215
1216 #include <guestfs.h>
1217
1218 static int compare_keys_len (const void *p1, const void *p2);
1219 static size_t count_strings (char *const *argv);
1220 static void free_strings (char **argv);
1221 static void mount_disks (guestfs_h *g, char *root);
1222 static void print_dhcp_address (guestfs_h *g, char *root);
1223 static void print_dhcp_address_linux (guestfs_h *g, char *root, const char *logfile);
1224 static void print_dhcp_address_windows (guestfs_h *g, char *root);
1225
1226 int
1227 main (int argc, char *argv[])
1228 {
1229 guestfs_h *g;
1230 size_t i;
1231 char **roots, *root;
1232
1233 if (argc < 2) {
1234 fprintf (stderr,
1235 "Usage: virt-dhcp-address disk.img [disk.img [...]]\n"
1236 "Note that all disks must come from a single virtual machine.\n");
1237 exit (EXIT_FAILURE);
1238 }
1239
1240 g = guestfs_create ();
1241 if (g == NULL) {
1242 perror ("failed to create libguestfs handle");
1243 exit (EXIT_FAILURE);
1244 }
1245
1246 for (i = 1; i < (size_t) argc; ++i) {
1247 /* Attach the disk image(s) read-only to libguestfs. */
1248 if (guestfs_add_drive_opts (g, argv[i],
1249 /* GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw", */
1250 GUESTFS_ADD_DRIVE_OPTS_READONLY, 1,
1251 -1) /* this marks end of optional arguments */
1252 == -1)
1253 exit (EXIT_FAILURE);
1254 }
1255
1256 /* Run the libguestfs back-end. */
1257 if (guestfs_launch (g) == -1)
1258 exit (EXIT_FAILURE);
1259
1260 /* Ask libguestfs to inspect for operating systems. */
1261 roots = guestfs_inspect_os (g);
1262 if (roots == NULL)
1263 exit (EXIT_FAILURE);
1264 if (roots[0] == NULL) {
1265 fprintf (stderr, "virt-dhcp-address: no operating systems found\n");
1266 exit (EXIT_FAILURE);
1267 }
1268 if (count_strings (roots) > 1) {
1269 fprintf (stderr, "virt-dhcp-address: multi-boot operating system\n");
1270 exit (EXIT_FAILURE);
1271 }
1272
1273 root = roots[0];
1274
1275 /* Mount up the guest's disks. */
1276 mount_disks (g, root);
1277
1278 /* Print DHCP address. */
1279 print_dhcp_address (g, root);
1280
1281 /* Close handle and exit. */
1282 guestfs_close (g);
1283 free_strings (roots);
1284
1285 exit (EXIT_SUCCESS);
1286 }
1287
1288 static void
1289 mount_disks (guestfs_h *g, char *root)
1290 {
1291 char **mountpoints;
1292 size_t i;
1293
1294 /* Mount up the disks, like guestfish -i.
1295 *
1296 * Sort keys by length, shortest first, so that we end up
1297 * mounting the filesystems in the correct order.
1298 */
1299 mountpoints = guestfs_inspect_get_mountpoints (g, root);
1300 if (mountpoints == NULL)
1301 exit (EXIT_FAILURE);
1302
1303 qsort (mountpoints, count_strings (mountpoints) / 2, 2 * sizeof (char *),
1304 compare_keys_len);
1305
1306 for (i = 0; mountpoints[i] != NULL; i += 2) {
1307 /* Ignore failures from this call, since bogus entries can
1308 * appear in the guest's /etc/fstab.
1309 */
1310 guestfs_mount_ro (g, mountpoints[i+1], mountpoints[i]);
1311 }
1312
1313 free_strings (mountpoints);
1314 }
1315
1316 static void
1317 print_dhcp_address (guestfs_h *g, char *root)
1318 {
1319 char *guest_type, *guest_distro;
1320
1321 /* Depending on the guest type, try to get the DHCP address. */
1322 guest_type = guestfs_inspect_get_type (g, root);
1323 if (guest_type == NULL)
1324 exit (EXIT_FAILURE);
1325
1326 if (strcmp (guest_type, "linux") == 0) {
1327 guest_distro = guestfs_inspect_get_distro (g, root);
1328 if (guest_distro == NULL)
1329 exit (EXIT_FAILURE);
1330
1331 if (strcmp (guest_distro, "fedora") == 0 ||
1332 strcmp (guest_distro, "rhel") == 0 ||
1333 strcmp (guest_distro, "redhat-based") == 0) {
1334 print_dhcp_address_linux (g, root, "/var/log/messages");
1335 }
1336 else if (strcmp (guest_distro, "debian") == 0 ||
1337 strcmp (guest_distro, "ubuntu") == 0) {
1338 print_dhcp_address_linux (g, root, "/var/log/syslog");
1339 }
1340 else {
1341 fprintf (stderr, "virt-dhcp-address: don't know how to get DHCP address from '%s'\n",
1342 guest_distro);
1343 exit (EXIT_FAILURE);
1344 }
1345
1346 free (guest_distro);
1347 }
1348 else if (strcmp (guest_type, "windows") == 0) {
1349 print_dhcp_address_windows (g, root);
1350 }
1351 else {
1352 fprintf (stderr, "virt-dhcp-address: don't know how to get DHCP address from '%s'\n",
1353 guest_type);
1354 exit (EXIT_FAILURE);
1355 }
1356
1357 free (guest_type);
1358 }
1359
1360 /* Look for dhclient messages in logfile.
1361 */
1362 static void
1363 print_dhcp_address_linux (guestfs_h *g, char *root, const char *logfile)
1364 {
1365 char **lines, *p;
1366 size_t len;
1367
1368 lines = guestfs_grep_opts (g, "dhclient.*: bound to ", logfile,
1369 GUESTFS_GREP_OPTS_EXTENDED, 1,
1370 -1);
1371 if (lines == NULL)
1372 exit (EXIT_FAILURE);
1373
1374 len = count_strings (lines);
1375 if (len == 0) {
1376 fprintf (stderr, "virt-dhcp-address: cannot find DHCP address for this guest.\n");
1377 exit (EXIT_FAILURE);
1378 }
1379
1380 /* Only want the last message. */
1381 p = strstr (lines[len-1], "bound to ");
1382 assert (p);
1383 p += 9;
1384 len = strcspn (p, " ");
1385 p[len] = '\0';
1386
1387 printf ("%s\n", p);
1388
1389 free_strings (lines);
1390 }
1391
1392 /* Download the Windows SYSTEM hive and find DHCP configuration in there. */
1393 static void
1394 print_dhcp_address_windows (guestfs_h *g, char *root_fs)
1395 {
1396 char *system_path;
1397 int64_t root, node, value;
1398 struct guestfs_hivex_node_list *nodes;
1399 char *controlset;
1400 size_t i;
1401 char *p;
1402
1403 /* Locate the SYSTEM hive. */
1404 system_path = guestfs_inspect_get_windows_system_hive (g, root_fs);
1405 if (!system_path)
1406 exit (EXIT_FAILURE);
1407
1408 /* Open the hive to parse it. Note that before libguestfs 1.19.35
1409 * you had to download the file and parse it using hivex(3). Since
1410 * libguestfs 1.19.35, parts of the hivex(3) API are now exposed
1411 * through libguestfs, and that is what we'll use here because it is
1412 * more convenient and avoids having to download the hive.
1413 */
1414 if (guestfs_hivex_open (g, system_path, -1) == -1)
1415 exit (EXIT_FAILURE);
1416
1417 free (system_path);
1418
1419 root = guestfs_hivex_root (g);
1420 if (root == -1)
1421 exit (EXIT_FAILURE);
1422
1423 /* Get ControlSetXXX\Services\Tcpip\Parameters\Interfaces. */
1424 controlset = guestfs_inspect_get_windows_current_control_set (g, root_fs);
1425 if (controlset == NULL)
1426 exit (EXIT_FAILURE);
1427 const char *path[] = { controlset, "Services", "Tcpip", "Parameters",
1428 "Interfaces" };
1429 node = root;
1430 for (i = 0; node > 0 && i < sizeof path / sizeof path[0]; ++i)
1431 node = guestfs_hivex_node_get_child (g, node, path[i]);
1432
1433 if (node == -1)
1434 exit (EXIT_FAILURE);
1435
1436 if (node == 0) {
1437 fprintf (stderr, "virt-dhcp-address: HKLM\\System\\%s\\Services\\Tcpip\\Parameters\\Interfaces not found.", controlset);
1438 exit (EXIT_FAILURE);
1439 }
1440
1441 free (controlset);
1442
1443 /* Look for a node under here which has a "DhcpIPAddress" entry in it. */
1444 nodes = guestfs_hivex_node_children (g, node);
1445 if (nodes == NULL)
1446 exit (EXIT_FAILURE);
1447
1448 value = 0;
1449 for (i = 0; value == 0 && i < nodes->len; ++i) {
1450 value = guestfs_hivex_node_get_value (g, nodes->val[i].hivex_node_h,
1451 "DhcpIPAddress");
1452 if (value == -1)
1453 exit (EXIT_FAILURE);
1454 }
1455
1456 if (value == 0) {
1457 fprintf (stderr, "virt-dhcp-address: cannot find DHCP address for this guest.\n");
1458 exit (EXIT_FAILURE);
1459 }
1460
1461 guestfs_free_hivex_node_list (nodes);
1462
1463 /* Get the string and use libguestfs's auto-conversion to convert it
1464 * to UTF-8 for output.
1465 */
1466 p = guestfs_hivex_value_string (g, value);
1467 if (!p)
1468 exit (EXIT_FAILURE);
1469
1470 printf ("%s\n", p);
1471
1472 free (p);
1473
1474 /* Close the hive handle. */
1475 guestfs_hivex_close (g);
1476 }
1477
1478 static int
1479 compare_keys_len (const void *p1, const void *p2)
1480 {
1481 const char *key1 = * (char * const *) p1;
1482 const char *key2 = * (char * const *) p2;
1483 return strlen (key1) - strlen (key2);
1484 }
1485
1486 static size_t
1487 count_strings (char *const *argv)
1488 {
1489 size_t c;
1490
1491 for (c = 0; argv[c]; ++c)
1492 ;
1493 return c;
1494 }
1495
1496 static void
1497 free_strings (char **argv)
1498 {
1499 size_t i;
1500
1501 for (i = 0; argv[i]; ++i)
1502 free (argv[i]);
1503 free (argv);
1504 }
1505
1507 guestfs(3), guestfs-erlang(3), guestfs-golang(3), guestfs-java(3),
1508 guestfs-lua(3), guestfs-ocaml(3), guestfs-perl(3), guestfs-python(3),
1509 guestfs-recipes(1), guestfs-ruby(3), http://libguestfs.org/.
1510
1512 Richard W.M. Jones ("rjones at redhat dot com")
1513
1515 Copyright (C) 2010-2020 Red Hat Inc.
1516
1518 This manual page contains examples which we hope you will use in your
1519 programs. The examples may be freely copied, modified and distributed
1520 for any purpose without any restrictions.
1521
1523 To get a list of bugs against libguestfs, use this link:
1524 https://bugzilla.redhat.com/buglist.cgi?component=libguestfs&product=Virtualization+Tools
1525
1526 To report a new bug against libguestfs, use this link:
1527 https://bugzilla.redhat.com/enter_bug.cgi?component=libguestfs&product=Virtualization+Tools
1528
1529 When reporting a bug, please supply:
1530
1531 · The version of libguestfs.
1532
1533 · Where you got libguestfs (eg. which Linux distro, compiled from
1534 source, etc)
1535
1536 · Describe the bug accurately and give a way to reproduce it.
1537
1538 · Run libguestfs-test-tool(1) and paste the complete, unedited output
1539 into the bug report.
1540
1541
1542
1543libguestfs-1.44.0 2021-01-05 guestfs-examples(3)