guestfs-examples(3)
π NAME π
guestfs-examples β examples of using libguestfs from C. this is the guestfs-examples(3) page from Virtualization Support, and it's literally your bestie-approved tour of calling libguestfs from C. if you're not familiar with using libguestfs, you also need to read guestfs(3) β no skipping, iykyk.
β¨ SYNOPSIS β¨
#include <guestfs.h>
guestfs_h *g = guestfs_create ();
guestfs_add_drive_ro (g, "disk.img");
guestfs_launch (g);
cc prog.c -o prog -lguestfs
or:
cc prog.c -o prog `pkg-config libguestfs --cflags --libs`
πΈ DESCRIPTION πΈ
this manual page is packed with examples of calling libguestfs from the C programming language. if you're not familiar with using libguestfs, pls also read guestfs(3) β it's giving required reading, bestie. ok so let's get into it.
π EXAMPLE: CREATE A DISK IMAGE π
first up: creating a disk image. this example is a whole main character β it creates a sparse raw disk image, adds it, launches, partitions it, makes an ext4 filesystem, mounts it, and drops some files in. slay. here's the code, exactly as it should be:
/* Example showing how to create a disk image. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <guestfs.h>
int
main (int argc, char *argv[])
{
guestfs_h *g;
size_t i;
g = guestfs_create ();
if (g == NULL) {
perror ("failed to create libguestfs handle");
exit (EXIT_FAILURE);
}
/* Set the trace flag so that we can see each libguestfs call. */
guestfs_set_trace (g, 1);
/* Create a raw-format sparse disk image, 512 MB in size. */
if (guestfs_disk_create (g, "disk.img", "raw", UINT64_C(512)*1024*1024,
-1) == -1)
exit (EXIT_FAILURE);
/* Add the disk image to libguestfs. */
if (guestfs_add_drive_opts (g, "disk.img",
GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw", /* raw format */
GUESTFS_ADD_DRIVE_OPTS_READONLY, 0, /* for write */
-1) /* this marks end of optional arguments */
== -1)
exit (EXIT_FAILURE);
/* Run the libguestfs back-end. */
if (guestfs_launch (g) == -1)
exit (EXIT_FAILURE);
/* Get the list of devices. Because we only added one drive
* above, we expect that this list should contain a single
* element.
*/
char **devices = guestfs_list_devices (g);
if (devices == NULL)
exit (EXIT_FAILURE);
if (devices[0] == NULL || devices[1] != NULL) {
fprintf (stderr, "error: expected a single device from list-devices\n");
exit (EXIT_FAILURE);
}
/* Partition the disk as one single MBR partition. */
if (guestfs_part_disk (g, devices[0], "mbr") == -1)
exit (EXIT_FAILURE);
/* Get the list of partitions. We expect a single element, which
* is the partition we have just created.
*/
char **partitions = guestfs_list_partitions (g);
if (partitions == NULL)
exit (EXIT_FAILURE);
if (partitions[0] == NULL || partitions[1] != NULL) {
fprintf (stderr, "error: expected a single partition from list-partitions\n");
exit (EXIT_FAILURE);
}
/* Create a filesystem on the partition. */
if (guestfs_mkfs (g, "ext4", partitions[0]) == -1)
exit (EXIT_FAILURE);
/* Now mount the filesystem so that we can add files. */
if (guestfs_mount (g, partitions[0], "/") == -1)
exit (EXIT_FAILURE);
/* Create some files and directories. */
if (guestfs_touch (g, "/empty") == -1)
exit (EXIT_FAILURE);
const char *message = "Hello, world\n";
if (guestfs_write (g, "/hello", message, strlen (message)) == -1)
exit (EXIT_FAILURE);
if (guestfs_mkdir (g, "/foo") == -1)
exit (EXIT_FAILURE);
/* This one uploads the local file /etc/resolv.conf into
* the disk image.
*/
if (guestfs_upload (g, "/etc/resolv.conf", "/foo/resolv.conf") == -1)
exit (EXIT_FAILURE);
/* Because we wrote to the disk and we want to detect write
* errors, call guestfs_shutdown. You don't need to do this:
* guestfs_close will do it implicitly.
*/
if (guestfs_shutdown (g) == -1)
exit (EXIT_FAILURE);
guestfs_close (g);
/* Free up the lists. */
for (i = 0; devices[i] != NULL; ++i)
free (devices[i]);
free (devices);
for (i = 0; partitions[i] != NULL; ++i)
free (partitions[i]);
free (partitions);
exit (EXIT_SUCCESS);
}
btw the code comment is real: you don't need to callguestfs_shutdownbecauseguestfs_closewill do it implicitly, but if you want to detect write errors, that's the move. periodt.
π¦ EXAMPLE: INSPECT A VIRTUAL MACHINE DISK IMAGE π¦
next: inspecting a disk image and displaying operating systems it may contain. this one attaches the disk read-only, launches, asks libguestfs to inspect, then prints product/version/type/distro info. it also mounts the mountpoints sorted by key length, shortest first, so the filesystems end up mounted in the correct order β and if /etc/issue.net exists, it prints up to 3 lines. iconic. code time:
/* Inspect a disk image and display operating systems it may contain. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <guestfs.h>
static int
compare_keys_len (const void *p1, const void *p2)
{
const char *key1 = * (char * const *) p1;
const char *key2 = * (char * const *) p2;
return strlen (key1) - strlen (key2);
}
static size_t
count_strings (char *const *argv)
{
size_t c;
for (c = 0; argv[c]; ++c)
;
return c;
}
int
main (int argc, char *argv[])
{
guestfs_h *g;
const char *disk;
char **roots, *root, *str, **mountpoints, **lines;
size_t i, j;
if (argc != 2) {
fprintf (stderr, "usage: inspect_vm disk.img\n");
exit (EXIT_FAILURE);
}
disk = argv[1];
g = guestfs_create ();
if (g == NULL) {
perror ("failed to create libguestfs handle");
exit (EXIT_FAILURE);
}
/* Attach the disk image read-only to libguestfs. */
if (guestfs_add_drive_opts (g, disk,
/* GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw", */
GUESTFS_ADD_DRIVE_OPTS_READONLY, 1,
-1) /* this marks end of optional arguments */
== -1)
exit (EXIT_FAILURE);
/* Run the libguestfs back-end. */
if (guestfs_launch (g) == -1)
exit (EXIT_FAILURE);
/* Ask libguestfs to inspect for operating systems. */
roots = guestfs_inspect_os (g);
if (roots == NULL)
exit (EXIT_FAILURE);
if (roots[0] == NULL) {
fprintf (stderr, "inspect_vm: no operating systems found\n");
exit (EXIT_FAILURE);
}
for (j = 0; roots[j] != NULL; ++j) {
root = roots[j];
printf ("Root device: %s\n", root);
/* Print basic information about the operating system. */
str = guestfs_inspect_get_product_name (g, root);
if (str)
printf (" Product name: %s\n", str);
free (str);
printf (" Version: %d.%d\n",
guestfs_inspect_get_major_version (g, root),
guestfs_inspect_get_minor_version (g, root));
str = guestfs_inspect_get_type (g, root);
if (str)
printf (" Type: %s\n", str);
free (str);
str = guestfs_inspect_get_distro (g, root);
if (str)
printf (" Distro: %s\n", str);
free (str);
/* Mount up the disks, like guestfish -i.
*
* Sort keys by length, shortest first, so that we end up
* mounting the filesystems in the correct order.
*/
mountpoints = guestfs_inspect_get_mountpoints (g, root);
if (mountpoints == NULL)
exit (EXIT_FAILURE);
qsort (mountpoints, count_strings (mountpoints) / 2, 2 * sizeof (char *),
compare_keys_len);
for (i = 0; mountpoints[i] != NULL; i += 2) {
/* Ignore failures from this call, since bogus entries can
* appear in the guest's /etc/fstab.
*/
guestfs_mount_ro (g, mountpoints[i+1], mountpoints[i]);
free (mountpoints[i]);
free (mountpoints[i+1]);
}
free (mountpoints);
/* If /etc/issue.net file exists, print up to 3 lines. */
if (guestfs_is_file (g, "/etc/issue.net") > 0) {
printf ("--- /etc/issue.net ---\n");
lines = guestfs_head_n (g, 3, "/etc/issue.net");
if (lines == NULL)
exit (EXIT_FAILURE);
for (i = 0; lines[i] != NULL; ++i) {
printf ("%s\n", lines[i]);
free (lines[i]);
}
free (lines);
}
/* Unmount everything. */
if (guestfs_umount_all (g) == -1)
exit (EXIT_FAILURE);
free (root);
}
free (roots);
guestfs_close (g);
exit (EXIT_SUCCESS);
}
π EXAMPLE: ENABLE DEBUGGING AND LOGGING π
okay bestie, this one is all about turning on the debug tea β and catching it with the event API. The code comments are doing major lore-dropping here β especially about trace, verbose, and the fact that ordinary API errors are not debugging info. Note that this uses the event API, which is also available in non-C language bindings. β¨
/* Example showing how to enable debugging, and capture it into any
* custom logging system (syslog in this example, but any could be
* used). Note this uses the event API which is also available in
* non-C language bindings.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <syslog.h>
#include <guestfs.h>
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);
/* Events we are interested in. This bitmask covers all trace and
* debug messages.
*/
static const uint64_t event_bitmask =
GUESTFS_EVENT_LIBRARY |
GUESTFS_EVENT_WARNING |
GUESTFS_EVENT_APPLIANCE |
GUESTFS_EVENT_TRACE;
int
main (int argc, char *argv[])
{
guestfs_h *g;
g = guestfs_create ();
if (g == NULL) {
perror ("failed to create libguestfs handle");
exit (EXIT_FAILURE);
}
/* By default, debugging information is printed on stderr. To
* capture it somewhere else you have to set up an event handler
* which will be called back as debug messages are generated. To do
* this use the event API.
*
* For more information see EVENTS in guestfs(3).
*/
if (guestfs_set_event_callback (g, message_callback,
event_bitmask, 0, NULL) == -1)
exit (EXIT_FAILURE);
/* This is how debugging is enabled:
*
* Setting the 'trace' flag in the handle means that each libguestfs
* call is logged (name, parameters, return). This flag is useful
* to see how libguestfs is being used by a program.
*
* Setting the 'verbose' flag enables a great deal of extra
* debugging throughout the system. This is useful if there is a
* libguestfs error which you don't understand.
*
* Note that you should set the flags early on after creating the
* handle. In particular if you set the verbose flag after launch
* then you won't see all messages.
*
* For more information see:
* http://libguestfs.org/guestfs-faq.1.html#debugging-libguestfs
*
* Error messages raised by APIs are *not* debugging information,
* and they are not affected by any of this. You may have to log
* them separately.
*/
guestfs_set_trace (g, 1);
guestfs_set_verbose (g, 1);
/* Do some operations which will generate plenty of trace and debug
* messages.
*/
if (guestfs_add_drive (g, "/dev/null") == -1)
exit (EXIT_FAILURE);
printf ("There is no output from this program. "
"Take a look in your system log file,\n"
"eg. /var/log/messages.\n");
if (guestfs_launch (g) == -1)
exit (EXIT_FAILURE);
guestfs_close (g);
exit (EXIT_SUCCESS);
}
/* This function is called back by libguestfs whenever a trace or
* debug message is generated.
*
* For the classes of events we have registered above, 'array' and
* 'array_len' will not be meaningful. Only 'buf' and 'buf_len' will
* be interesting and these will contain the trace or debug message.
*
* This example simply redirects these messages to syslog, but
* obviously you could do something more advanced here.
*/
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)
{
const int priority = LOG_USER|LOG_INFO;
char *event_name, *msg;
if (buf_len > 0) {
event_name = guestfs_event_to_string (event);
msg = strndup (buf, buf_len);
syslog (priority, "[%s] %s", event_name, msg);
free (msg);
free (event_name);
}
}
π EXAMPLE: DISPLAY THE OPERATING SYSTEM ICON OF A GUEST π
this one is honestly such a serve π β it inspects a guest using libguestfs inspection (see "INSPECTION" in guestfs(3)), and if possible it displays a representative icon or logo for the guestβs operating system. The comments are also very real about the icon-buffer situation: a zero-length buffer means no icon is available, while NULL means there was an actual libguestfs error. π
/* This example inspects a guest using libguestfs inspection (see
* "INSPECTION" in guestfs(3)), and if possible displays a
* representative icon or logo for the guest's operating system.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <guestfs.h>
static int
compare_keys_len (const void *p1, const void *p2)
{
const char *key1 = * (char * const *) p1;
const char *key2 = * (char * const *) p2;
return strlen (key1) - strlen (key2);
}
static size_t
count_strings (char *const *argv)
{
size_t c;
for (c = 0; argv[c]; ++c)
;
return c;
}
int
main (int argc, char *argv[])
{
guestfs_h *g;
const char *disk;
char **roots, *root, **mountpoints, *icon;
size_t i, j, icon_size;
FILE *fp;
if (argc != 2) {
fprintf (stderr, "usage: display-icon disk.img\n");
exit (EXIT_FAILURE);
}
disk = argv[1];
g = guestfs_create ();
if (g == NULL) {
perror ("failed to create libguestfs handle");
exit (EXIT_FAILURE);
}
/* Attach the disk image read-only to libguestfs. */
if (guestfs_add_drive_opts (g, disk,
/* GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw", */
GUESTFS_ADD_DRIVE_OPTS_READONLY, 1,
-1) /* this marks end of optional arguments */
== -1)
exit (EXIT_FAILURE);
/* Run the libguestfs back-end. */
if (guestfs_launch (g) == -1)
exit (EXIT_FAILURE);
/* Ask libguestfs to inspect for operating systems. */
roots = guestfs_inspect_os (g);
if (roots == NULL)
exit (EXIT_FAILURE);
if (roots[0] == NULL) {
fprintf (stderr, "display-icon: no operating systems found\n");
exit (EXIT_FAILURE);
}
for (j = 0; roots[j] != NULL; ++j) {
root = roots[j];
/* Mount up the disks, like guestfish -i.
*
* Sort keys by length, shortest first, so that we end up
* mounting the filesystems in the correct order.
*/
mountpoints = guestfs_inspect_get_mountpoints (g, root);
if (mountpoints == NULL)
exit (EXIT_FAILURE);
qsort (mountpoints, count_strings (mountpoints) / 2, 2 * sizeof (char *),
compare_keys_len);
for (i = 0; mountpoints[i] != NULL; i += 2) {
/* Ignore failures from this call, since bogus entries can
* appear in the guest's /etc/fstab.
*/
guestfs_mount_ro (g, mountpoints[i+1], mountpoints[i]);
free (mountpoints[i]);
free (mountpoints[i+1]);
}
free (mountpoints);
/* Get the icon.
* This function returns a buffer ('icon'). Normally it is a png
* file, returned as a string, but it can also be a zero length
* buffer which has a special meaning, or NULL which means there
* was an error.
*/
icon = guestfs_inspect_get_icon (g, root, &icon_size, -1);
if (!icon) /* actual libguestfs error */
exit (EXIT_FAILURE);
if (icon_size == 0) /* no icon available */
fprintf (stderr, "%s: %s: no icon available for this operating system\n",
disk, root);
else {
/* Display the icon. */
fp = popen ("display -", "w");
if (fp == NULL) {
perror ("display");
exit (EXIT_FAILURE);
}
if (fwrite (icon, 1, icon_size, fp) != icon_size) {
perror ("write");
exit (EXIT_FAILURE);
}
if (pclose (fp) == -1) {
perror ("pclose");
exit (EXIT_FAILURE);
}
}
free (icon);
/* Unmount everything. */
if (guestfs_umount_all (g) == -1)
exit (EXIT_FAILURE);
free (root);
}
free (roots);
guestfs_close (g);
exit (EXIT_SUCCESS);
}
β¨ EXAMPLE: THE LIBVIRT AUTHENTICATION API β¨
okay so this next one is the libvirt authentication example, and it's giving event-driven API realness π
the header comment literally points you at "LIBVIRT AUTHENTICATION" in guestfs(3), so likeβ¦ go read that too bestie, it's the lore π«Ά
The whole vibe here is: you want to connect to a libvirt URI, and libvirt is gonna be like "umm actually, credentials please?" β and instead of you doing it manually, the library fires an event at you and you handle it in a callback. Chef's kiss. πΈ
/* Example of using the libvirt authentication event-driven API.
*
* See "LIBVIRT AUTHENTICATION" in guestfs(3).
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <guestfs.h>
static void
usage (void)
{
fprintf (stderr,
"Usage:\n"
"\n"
" libvirt-auth URI domain\n"
"\n"
"where:\n"
"\n"
" URI is the libvirt URI, eg. qemu+libssh2://USER@localhost/system\n"
" domain is the name of the guest\n"
"\n"
"Example:\n"
"\n"
" libvirt-auth 'qemu+libssh2://USER@localhost/system' 'foo'\n"
"\n"
"would connect (read-only) to libvirt URI given and open the guest\n"
"called 'foo' and list some information about its filesystems.\n"
"\n"
"The important point of this example is that any libvirt authentication\n"
"required to connect to the server should be done.\n"
"\n");
}
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);
int
main (int argc, char *argv[])
{
const char *uri, *dom;
guestfs_h *g;
const char *creds[] = { "authname", "passphrase",
"echoprompt", "noechoprompt", NULL };
int r, eh;
char **filesystems;
size_t i;
if (argc != 3) {
usage ();
exit (EXIT_FAILURE);
}
uri = argv[1];
dom = argv[2];
g = guestfs_create ();
if (!g)
exit (EXIT_FAILURE);
r = guestfs_set_libvirt_supported_credentials (g, (char **) creds);
if (r == -1)
exit (EXIT_FAILURE);
/* Set up the event handler. */
eh = guestfs_set_event_callback (g, auth_callback,
GUESTFS_EVENT_LIBVIRT_AUTH, 0, NULL);
if (eh == -1)
exit (EXIT_FAILURE);
/* Add the named domain. */
r = guestfs_add_domain (g, dom,
GUESTFS_ADD_DOMAIN_LIBVIRTURI, uri,
-1);
if (r == -1)
exit (EXIT_FAILURE);
/* Launch and do some simple inspection. */
r = guestfs_launch (g);
if (r == -1)
exit (EXIT_FAILURE);
filesystems = guestfs_list_filesystems (g);
if (filesystems == NULL)
exit (EXIT_FAILURE);
for (i = 0; filesystems[i] != NULL; i += 2) {
printf ("%s:%s is a %s filesystem\n",
dom, filesystems[i], filesystems[i+1]);
free (filesystems[i]);
free (filesystems[i+1]);
}
free (filesystems);
exit (EXIT_SUCCESS);
}
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)
{
char **creds;
size_t i;
char *prompt;
char *reply = NULL;
size_t allocsize = 0;
char *pass;
ssize_t len;
int r;
printf ("libvirt-auth.c: authentication required for libvirt URI '%s'\n\n",
buf);
/* Ask libguestfs what credentials libvirt is demanding. */
creds = guestfs_get_libvirt_requested_credentials (g);
if (creds == NULL)
exit (EXIT_FAILURE);
/* Now ask the user for answers. */
for (i = 0; creds[i] != NULL; ++i)
{
printf ("libvirt-auth.c: credential '%s'\n", creds[i]);
if (strcmp (creds[i], "authname") == 0 ||
strcmp (creds[i], "echoprompt") == 0) {
prompt = guestfs_get_libvirt_requested_credential_prompt (g, i);
if (prompt && strcmp (prompt, "") != 0)
printf ("%s: ", prompt);
free (prompt);
len = getline (&reply, &allocsize, stdin);
if (len == -1) {
perror ("getline");
exit (EXIT_FAILURE);
}
if (len > 0 && reply[len-1] == '\n')
reply[--len] = '\0';
r = guestfs_set_libvirt_requested_credential (g, i, reply, len);
if (r == -1)
exit (EXIT_FAILURE);
} else if (strcmp (creds[i], "passphrase") == 0 ||
strcmp (creds[i], "noechoprompt") == 0) {
prompt = guestfs_get_libvirt_requested_credential_prompt (g, i);
if (prompt && strcmp (prompt, "") != 0)
printf ("%s: ", prompt);
free (prompt);
pass = getpass ("");
if (pass == NULL) {
perror ("getpass");
exit (EXIT_FAILURE);
}
len = strlen (pass);
r = guestfs_set_libvirt_requested_credential (g, i, pass, len);
if (r == -1)
exit (EXIT_FAILURE);
}
free (creds[i]);
}
free (reply);
free (creds);
}
okay let's do a little vibe check on what's happening in there π
- the program takes exactly two args (
argc != 3βusage (), bye π): URI and domain. The usage text spells it out: URI is the libvirt URI, eg.qemu+libssh2://USER@localhost/system, and domain is the name of the guest. And the example run islibvirt-auth 'qemu+libssh2://USER@localhost/system' 'foo'β which would connect (read-only) to the libvirt URI given, open the guest called'foo', and list some information about its filesystems. π¦ - the credentials it declares it can handle are
"authname","passphrase","echoprompt","noechoprompt",NULLβ passed intoguestfs_set_libvirt_supported_credentials. If that returns-1, we're out. π - then
guestfs_set_event_callbackregistersauth_callbackforGUESTFS_EVENT_LIBVIRT_AUTHwith flags0and opaqueNULL. Returnseh, and-1means nope. guestfs_add_domaingets called withGUESTFS_ADD_DOMAIN_LIBVIRTURI, uri, -1. Thenguestfs_launch. Thenguestfs_list_filesystems, which comes back as an array of pairs, so the loop walks it withi += 2and prints"%s:%s is a %s filesystem\n"withdom,filesystems[i],filesystems[i+1]β freeing each pair as it goes, thenfree (filesystems).- in the callback: it announces
"libvirt-auth.c: authentication required for libvirt URI '%s'\n\n"withbuf, asks libguestfs what credentials libvirt is demanding viaguestfs_get_libvirt_requested_credentials, then loops over them printing"libvirt-auth.c: credential '%s'\n"."authname"or"echoprompt"β grab the prompt withguestfs_get_libvirt_requested_credential_prompt, print"%s: "if it's non-empty,freethe prompt, read withgetline (&reply, &allocsize, stdin), bail viaperror ("getline")on-1, strip the trailing'\n'if present (reply[--len] = '\0'), thenguestfs_set_libvirt_requested_credential (g, i, reply, len)."passphrase"or"noechoprompt"β same prompt dance, but thengetpass ("")(so it doesn't echo, iykyk π),perror ("getpass")onNULL,len = strlen (pass), andguestfs_set_libvirt_requested_credential (g, i, pass, len).- either way:
free (creds[i])per item, and at the endfree (reply)andfree (creds). no leaks, we love to see it π
the important point, straight from the usage text: any libvirt authentication required to connect to the server should be done. that's the whole assignment bestie, don't skip it β¨
and that's the auth one β keep scrolling, we're not done yet π
β¨ EXAMPLE: THE MOUNT LOCAL API β¨
okay bestie this one is the moment β it's the mount-local demo, and it's genuinely one of the coolest things libguestfs does π¦ basically you get a real live FUSE filesystem in a temp directory, backed by a disk image that libguestfs is managing, and you can justβ¦ drag files into it. in a shell. like a normal person. iconic behavior fr fr π
it also has a lot of moving parts, so let's just look at it, yeah?
/* Demonstrate the use of the 'mount-local' API.
*
* Run this program as (eg) mount-local /tmp/test.img. Note that
* '/tmp/test.img' is created or overwritten. Follow the instructions
* on screen.
*
* See "MOUNT LOCAL" in guestfs(3).
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <guestfs.h>
#ifndef O_CLOEXEC
#define O_CLOEXEC 0
#endif
/* Define a list of filesystem mount options (used on the libguestfs
* side, nothing to do with FUSE). An empty string may be used here
* instead.
*/
#define MOUNT_OPTIONS "acl,user_xattr"
/* Size of the disk (megabytes). */
#define SIZE_MB 512
static void
usage (void)
{
fprintf (stderr,
"Usage: mount-local disk.img\n"
"\n"
"NOTE: disk.img will be created or overwritten.\n"
"\n");
}
int
main (int argc, char *argv[])
{
guestfs_h *g;
int r;
char tempdir[] = "/tmp/mlXXXXXX";
pid_t pid;
char *shell, *p;
if (argc != 2) {
usage ();
exit (EXIT_FAILURE);
}
if (argv[1][0] == '-') {
usage ();
exit (EXIT_FAILURE);
}
printf ("\n"
"This is the 'mount-local' demonstration program. Follow the\n"
"instructions on screen.\n"
"\n"
"Creating and formatting the disk image, please wait a moment ...\n");
fflush (stdout);
/* Guestfs handle. */
g = guestfs_create ();
if (g == NULL) {
perror ("could not create libguestfs handle");
exit (EXIT_FAILURE);
}
/* Create the output disk image: raw sparse. */
if (guestfs_disk_create (g, argv[1], "raw", SIZE_MB * 1024 * 1024, -1) == -1)
exit (EXIT_FAILURE);
/* Create the disk image and format it with a partition and a filesystem. */
if (guestfs_add_drive_opts (g, argv[1],
GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw",
-1) == -1)
exit (EXIT_FAILURE);
if (guestfs_launch (g) == -1)
exit (EXIT_FAILURE);
if (guestfs_part_disk (g, "/dev/sda", "mbr") == -1)
exit (EXIT_FAILURE);
if (guestfs_mkfs (g, "ext2", "/dev/sda1") == -1)
exit (EXIT_FAILURE);
/* Mount the empty filesystem. */
if (guestfs_mount_options (g, MOUNT_OPTIONS, "/dev/sda1", "/") == -1)
exit (EXIT_FAILURE);
/* Create a file in the new filesystem. */
if (guestfs_touch (g, "/PUT_FILES_AND_DIRECTORIES_HERE") == -1)
exit (EXIT_FAILURE);
/* Create a temporary mount directory. */
if (mkdtemp (tempdir) == NULL) {
perror ("mkdtemp");
exit (EXIT_FAILURE);
}
/* Mount the filesystem. */
if (guestfs_mount_local (g, tempdir, -1) == -1)
exit (EXIT_FAILURE);
/* Fork the shell for the user. */
pid = fork ();
if (pid == -1) {
perror ("fork");
exit (EXIT_FAILURE);
}
if (pid == 0) { /* Child. */
if (chdir (tempdir) == -1) {
perror (tempdir);
_exit (EXIT_FAILURE);
}
printf ("\n"
"The *current directory* is a FUSE filesystem backed by the disk\n"
"image which is managed by libguestfs. Any files or directories\n"
"you copy into here (up to %d MB) will be saved into the disk\n"
"image. You can also delete files, create certain special files\n"
"and so on.\n"
"\n"
"When you have finished adding files, hit ^D or type 'exit' to\n"
"exit the shell and return to the mount-local program.\n"
"\n",
SIZE_MB);
shell = getenv ("SHELL");
if (!shell)
r = system ("/bin/sh");
else {
/* Set a magic prompt. We only know how to do this for bash. */
p = strrchr (shell, '/');
if (p && strcmp (p+1, "bash") == 0) {
const size_t len = 64 + strlen (shell);
char *buf;
buf = malloc (len);
if (buf == NULL) {
perror ("malloc");
_exit (EXIT_FAILURE);
}
snprintf (buf, len, "PS1='mount-local-shell> ' %s --norc -i", shell);
r = system (buf);
free (buf);
} else
r = system (shell);
}
if (r == -1) {
fprintf (stderr, "error: failed to run sub-shell (%s) "
"(is $SHELL set correctly?)\n",
shell);
//FALLTHROUGH
}
if (chdir ("/") == -1)
perror ("chdir: /");
guestfs_umount_local (g, GUESTFS_UMOUNT_LOCAL_RETRY, 1, -1);
_exit (EXIT_SUCCESS);
}
/* Note that we are *not* waiting for the child yet. We want to
* run the FUSE code in parallel with the subshell.
*/
/* We're going to hide libguestfs errors here, but in a real program
* you would probably want to log them somewhere.
*/
guestfs_push_error_handler (g, NULL, NULL);
/* Now run the FUSE thread. */
if (guestfs_mount_local_run (g) == -1)
exit (EXIT_FAILURE);
guestfs_pop_error_handler (g);
waitpid (pid, NULL, 0);
/* Shutdown the handle explicitly so write errors can be detected. */
if (guestfs_shutdown (g) == -1)
exit (EXIT_FAILURE);
guestfs_close (g);
printf ("\n"
"Any files or directories that you copied in have been saved into\n"
"the disk image called '%s'.\n"
"\n"
"Try opening the disk image with guestfish to see those files:\n"
"\n"
" guestfish -a %s -m /dev/sda1\n"
"\n",
argv[1], argv[1]);
exit (EXIT_SUCCESS);
}
π so what's actually happening here
ngl the first read is a lot, so let's vibe check it step by step π
- you run it like
mount-local /tmp/test.imgβ and heads up, that image gets created or overwritten, so pls don't point it at anything you love π - there's a
usage()function, and it bails if you give it the wrong number of args, or if the first arg starts with a-. #define MOUNT_OPTIONS "acl,user_xattr"β those are filesystem mount options used on the libguestfs side, nothing to do with FUSE. an empty string works here too.#define SIZE_MB 512β that's your disk size in megabytes. it's giving generous πguestfs_create()makes the handle; thenguestfs_disk_create (g, argv[1], "raw", SIZE_MB * 1024 * 1024, -1)makes the output disk image, raw and sparse.guestfs_add_drive_optsadds it withGUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw", thenguestfs_launch, thenguestfs_part_disk (g, "/dev/sda", "mbr"), thenguestfs_mkfs (g, "ext2", "/dev/sda1").guestfs_mount_optionsmounts it on/, and yes it usesMOUNT_OPTIONSβ no cap.guestfs_touch (g, "/PUT_FILES_AND_DIRECTORIES_HERE")drops a lil placeholder file in there. cute.mkdtemp (tempdir)on"/tmp/mlXXXXXX"makes the temp mount dir, thenguestfs_mount_local (g, tempdir, -1)does the actual magic β¨- then it
fork()s. the childchdirs intotempdir, prints the tutorial text (up to%dMB β that'sSIZE_MB), and runs a shell:- if
$SHELLisn't set βsystem ("/bin/sh") - if
$SHELLends inbashβ it sets a magic prompt withPS1='mount-local-shell> ' %s --norc -i. like, only bash, they're being honest about it β "We only know how to do this for bash." - otherwise β just
system (shell) - if
systemreturns-1it printserror: failed to run sub-shell (%s) (is $SHELL set correctly?)β hence the//FALLTHROUGH - then
chdir ("/")andguestfs_umount_local (g, GUESTFS_UMOUNT_LOCAL_RETRY, 1, -1)and_exit (EXIT_SUCCESS)
- if
- the parent deliberately does not wait for the child yet β the FUSE code needs to run in parallel with the subshell, that's the whole point π§
guestfs_push_error_handler (g, NULL, NULL)hides libguestfs errors (the comment says a real program would probably want to log them somewhere π), thenguestfs_mount_local_run (g)runs the FUSE thread, thenguestfs_pop_error_handler (g).waitpid (pid, NULL, 0), thenguestfs_shutdown (g)β and the comment is very clear: shutdown the handle explicitly so write errors can be detected, thenguestfs_close (g).- finally it prints that your files got saved into
'%s', and tells you to go peek with:
guestfish -a %s -m /dev/sda1
fr fr: hit Ctrl+D or type exit when you're done adding files, or you'll just be sitting in that shell like π
lowkey obsessed with how much this does with so few lines. anywayβ
π EXAMPLE: MULTIPLE HANDLES AND THREADS π
okay so this next example is a whole main-character moment: it copies a directory from one libvirt guest to another. the source comment literally says it's a more substantial example of using the libguestfs API, and it's demonstrating, amongst other things, using multiple handles with threads, upload and downloading (using a pipe between handles), and inspection. no cap, it's giving βwe're doing real work nowβ energy π«Ά
quick vibe check: the destination guest cannot be running. that's in the usage text, so don't come for me π
here's the full code, exactly as it appears β every flag, string, function signature, and comment kept intact:
/* Copy a directory from one libvirt guest to another.
*
* This is a more substantial example of using the libguestfs API,
* demonstrating amongst other things:
*
* - using multiple handles with threads
* - upload and downloading (using a pipe between handles)
* - inspection
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/time.h>
#include <pthread.h>
#include <guestfs.h>
struct threaddata {
const char *src;
const char *srcdir;
int fd;
pthread_t mainthread;
};
static void *start_srcthread (void *);
static int open_guest (guestfs_h *g, const char *dom, int readonly);
static int64_t timeval_diff (const struct timeval *x, const struct timeval *y);
static int compare_keys_len (const void *p1, const void *p2);
static size_t count_strings (char *const *argv);
static void
usage (void)
{
fprintf (stderr,
"Usage: copy-over source srcdir dest destdir\n"
"\n"
" source : the source domain (a libvirt guest name)\n"
" srcdir : the directory to copy from the source guest\n"
" dest : the destination domain (a libvirt guest name)\n"
" destdir : the destination directory (must exist at destination)\n"
"\n"
"eg: copy-over Src /home/rjones Dest /tmp/dir\n"
"would copy /home/rjones from Src to /tmp/dir on Dest\n"
"\n"
"The destination guest cannot be running.\n");
}
int
main (int argc, char *argv[])
{
const char *src, *srcdir, *dest, *destdir;
guestfs_h *destg;
int fd[2];
pthread_t srcthread;
struct threaddata threaddata;
int err;
char fdname[128];
struct timeval start_t, end_t;
int64_t ms;
if (argc != 5) {
usage ();
exit (EXIT_FAILURE);
}
src = argv[1];
srcdir = argv[2];
dest = argv[3];
destdir = argv[4];
/* Instead of downloading to local disk and uploading, we are going
* to connect the source download and destination upload using a
* pipe. Create that pipe.
*/
if (pipe (fd) == -1) {
perror ("pipe");
exit (EXIT_FAILURE);
}
/* We don't want the pipe to be passed to subprocesses. */
if (fcntl (fd[0], F_SETFD, FD_CLOEXEC) == -1 ||
fcntl (fd[1], F_SETFD, FD_CLOEXEC) == -1) {
perror ("fcntl");
exit (EXIT_FAILURE);
}
/* The libguestfs API is synchronous, so if we want to use two
* handles concurrently, then we have to have two threads. In this
* case the main thread (this one) is handling the destination
* domain (uploading), and we create one more thread to handle the
* source domain (downloading).
*/
threaddata.src = src;
threaddata.srcdir = srcdir;
threaddata.fd = fd[1];
threaddata.mainthread = pthread_self ();
err = pthread_create (&srcthread, NULL, start_srcthread, &threaddata);
if (err != 0) {
fprintf (stderr, "pthread_create: %s\n", strerror (err));
exit (EXIT_FAILURE);
}
/* Open the destination domain. */
destg = guestfs_create ();
if (!destg) {
perror ("failed to create libguestfs handle");
pthread_cancel (srcthread);
exit (EXIT_FAILURE);
}
if (open_guest (destg, dest, 0) == -1) {
pthread_cancel (srcthread);
exit (EXIT_FAILURE);
}
gettimeofday (&start_t, NULL);
/* Begin the upload. */
snprintf (fdname, sizeof fdname, "/dev/fd/%d", fd[0]);
if (guestfs_tar_in (destg, fdname, destdir) == -1) {
pthread_cancel (srcthread);
exit (EXIT_FAILURE);
}
/* Close our end of the pipe. The other thread will close the
* other side of the pipe.
*/
close (fd[0]);
/* Wait for the other thread to finish. */
err = pthread_join (srcthread, NULL);
if (err != 0) {
fprintf (stderr, "pthread_join: %s\n", strerror (err));
exit (EXIT_FAILURE);
}
/* Clean up. */
if (guestfs_shutdown (destg) == -1)
exit (EXIT_FAILURE);
guestfs_close (destg);
gettimeofday (&end_t, NULL);
/* Print the elapsed time. */
ms = timeval_diff (&start_t, &end_t);
printf ("copy finished, elapsed time (excluding launch) was "
"%" PRIi64 ".%03" PRIi64 " s\n",
ms / 1000, ms % 1000);
exit (EXIT_SUCCESS);
}
static void *
start_srcthread (void *arg)
{
struct threaddata *threaddata = arg;
guestfs_h *srcg;
char fdname[128];
/* Open the source domain. */
srcg = guestfs_create ();
if (!srcg) {
perror ("failed to create libguestfs handle");
pthread_cancel (threaddata->mainthread);
exit (EXIT_FAILURE);
}
if (open_guest (srcg, threaddata->src, 1) == -1) {
pthread_cancel (threaddata->mainthread);
exit (EXIT_FAILURE);
}
/* Begin the download. */
snprintf (fdname, sizeof fdname, "/dev/fd/%d", threaddata->fd);
if (guestfs_tar_out (srcg, threaddata->srcdir, fdname) == -1) {
pthread_cancel (threaddata->mainthread);
exit (EXIT_FAILURE);
}
/* Close the pipe; this will cause the receiver to finish the upload. */
if (close (threaddata->fd) == -1) {
pthread_cancel (threaddata->mainthread);
exit (EXIT_FAILURE);
}
/* Clean up. */
guestfs_close (srcg);
return NULL;
}
/* This function deals with the complexity of adding the domain,
* launching the handle, and mounting up filesystems. See
* 'examples/inspect-vm.c' to understand how this works.
*/
static int
open_guest (guestfs_h *g, const char *dom, int readonly)
{
char **roots, *root, **mountpoints;
size_t i;
/* Use libvirt to find the guest disks and add them to the handle. */
if (guestfs_add_domain (g, dom,
GUESTFS_ADD_DOMAIN_READONLY, readonly,
-1) == -1)
return -1;
if (guestfs_launch (g) == -1)
return -1;
/* Inspect the guest, looking for operating systems. */
roots = guestfs_inspect_os (g);
if (roots == NULL)
return -1;
if (roots[0] == NULL || roots[1] != NULL) {
fprintf (stderr, "copy-over: %s: no operating systems or multiple operating systems found\n", dom);
return -1;
}
root = roots[0];
/* Mount up the filesystems (like 'guestfish -i'). */
mountpoints = guestfs_inspect_get_mountpoints (g, root);
if (mountpoints == NULL)
return -1;
qsort (mountpoints, count_strings (mountpoints) / 2, 2 * sizeof (char *),
compare_keys_len);
for (i = 0; mountpoints[i] != NULL; i += 2) {
/* Ignore failures from this call, since bogus entries can
* appear in the guest's /etc/fstab.
*/
(readonly ? guestfs_mount_ro : guestfs_mount)
(g, mountpoints[i+1], mountpoints[i]);
free (mountpoints[i]);
free (mountpoints[i+1]);
}
free (mountpoints);
free (root);
free (roots);
/* Everything ready, no error. */
return 0;
}
/* Compute Y - X and return the result in milliseconds.
* Approximately the same as this code:
* http://www.mpp.mpg.de/~huber/util/timevaldiff.c
*/
static int64_t
timeval_diff (const struct timeval *x, const struct timeval *y)
{
int64_t msec;
msec = (y->tv_sec - x->tv_sec) * 1000;
msec += (y->tv_usec - x->tv_usec) / 1000;
return msec;
}
static int
compare_keys_len (const void *p1, const void *p2)
{
const char *key1 = * (char * const *) p1;
const char *key2 = * (char * const *) p2;
return strlen (key1) - strlen (key2);
}
static size_t
count_strings (char *const *argv)
{
size_t c;
for (c = 0; argv[c]; ++c)
;
return c;
}
πΈ EXAMPLE: FETCH DHCP ADDRESS FROM A GUEST πΈ
Okay bestie, this is the big one β the full virt-dhcp-address-ish example, and it is giving main character energy. Every line of the C code stays exactly as it was, comments and all, because we do not mess with source. π
/* This is a more significant example of a tool which can grab the
* DHCP address from some types of virtual machine. Since there are
* so many possible ways to do this, without clarity on which is the
* best way, I don't want to make this into an official virt tool.
*
* For more information, see:
*
* https://rwmj.wordpress.com/2010/10/26/tip-find-the-ip-address-of-a-virtual-machine/
* https://rwmj.wordpress.com/2011/03/30/tip-another-way-to-get-the-ip-address-of-a-virtual-machine/
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <assert.h>
#include <guestfs.h>
static int compare_keys_len (const void *p1, const void *p2);
static size_t count_strings (char *const *argv);
static void free_strings (char **argv);
static void mount_disks (guestfs_h *g, char *root);
static void print_dhcp_address (guestfs_h *g, char *root);
static void print_dhcp_address_linux (guestfs_h *g, char *root, const char *logfile);
static void print_dhcp_address_windows (guestfs_h *g, char *root);
int
main (int argc, char *argv[])
{
guestfs_h *g;
size_t i;
char **roots, *root;
if (argc < 2) {
fprintf (stderr,
"Usage: virt-dhcp-address disk.img [disk.img [...]]\n"
"Note that all disks must come from a single virtual machine.\n");
exit (EXIT_FAILURE);
}
g = guestfs_create ();
if (g == NULL) {
perror ("failed to create libguestfs handle");
exit (EXIT_FAILURE);
}
for (i = 1; i < (size_t) argc; ++i) {
/* Attach the disk image(s) read-only to libguestfs. */
if (guestfs_add_drive_opts (g, argv[i],
/* GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw", */
GUESTFS_ADD_DRIVE_OPTS_READONLY, 1,
-1) /* this marks end of optional arguments */
== -1)
exit (EXIT_FAILURE);
}
/* Run the libguestfs back-end. */
if (guestfs_launch (g) == -1)
exit (EXIT_FAILURE);
/* Ask libguestfs to inspect for operating systems. */
roots = guestfs_inspect_os (g);
if (roots == NULL)
exit (EXIT_FAILURE);
if (roots[0] == NULL) {
fprintf (stderr, "virt-dhcp-address: no operating systems found\n");
exit (EXIT_FAILURE);
}
if (count_strings (roots) > 1) {
fprintf (stderr, "virt-dhcp-address: multi-boot operating system\n");
exit (EXIT_FAILURE);
}
root = roots[0];
/* Mount up the guest's disks. */
mount_disks (g, root);
/* Print DHCP address. */
print_dhcp_address (g, root);
/* Close handle and exit. */
guestfs_close (g);
free_strings (roots);
exit (EXIT_SUCCESS);
}
static void
mount_disks (guestfs_h *g, char *root)
{
char **mountpoints;
size_t i;
/* Mount up the disks, like guestfish -i.
*
* Sort keys by length, shortest first, so that we end up
* mounting the filesystems in the correct order.
*/
mountpoints = guestfs_inspect_get_mountpoints (g, root);
if (mountpoints == NULL)
exit (EXIT_FAILURE);
qsort (mountpoints, count_strings (mountpoints) / 2, 2 * sizeof (char *),
compare_keys_len);
for (i = 0; mountpoints[i] != NULL; i += 2) {
/* Ignore failures from this call, since bogus entries can
* appear in the guest's /etc/fstab.
*/
guestfs_mount_ro (g, mountpoints[i+1], mountpoints[i]);
}
free_strings (mountpoints);
}
static void
print_dhcp_address (guestfs_h *g, char *root)
{
char *guest_type, *guest_distro;
/* Depending on the guest type, try to get the DHCP address. */
guest_type = guestfs_inspect_get_type (g, root);
if (guest_type == NULL)
exit (EXIT_FAILURE);
if (strcmp (guest_type, "linux") == 0) {
guest_distro = guestfs_inspect_get_distro (g, root);
if (guest_distro == NULL)
exit (EXIT_FAILURE);
if (strcmp (guest_distro, "fedora") == 0 ||
strcmp (guest_distro, "rhel") == 0 ||
strcmp (guest_distro, "redhat-based") == 0) {
print_dhcp_address_linux (g, root, "/var/log/messages");
}
else if (strcmp (guest_distro, "debian") == 0 ||
strcmp (guest_distro, "ubuntu") == 0) {
print_dhcp_address_linux (g, root, "/var/log/syslog");
}
else {
fprintf (stderr, "virt-dhcp-address: don't know how to get DHCP address from '%s'\n",
guest_distro);
exit (EXIT_FAILURE);
}
free (guest_distro);
}
else if (strcmp (guest_type, "windows") == 0) {
print_dhcp_address_windows (g, root);
}
else {
fprintf (stderr, "virt-dhcp-address: don't know how to get DHCP address from '%s'\n",
guest_type);
exit (EXIT_FAILURE);
}
free (guest_type);
}
/* Look for dhclient messages in logfile.
*/
static void
print_dhcp_address_linux (guestfs_h *g, char *root, const char *logfile)
{
char **lines, *p;
size_t len;
lines = guestfs_grep_opts (g, "dhclient.*: bound to ", logfile,
GUESTFS_GREP_OPTS_EXTENDED, 1,
-1);
if (lines == NULL)
exit (EXIT_FAILURE);
len = count_strings (lines);
if (len == 0) {
fprintf (stderr, "virt-dhcp-address: cannot find DHCP address for this guest.\n");
exit (EXIT_FAILURE);
}
/* Only want the last message. */
p = strstr (lines[len-1], "bound to ");
assert (p);
p += 9;
len = strcspn (p, " ");
p[len] = '\0';
printf ("%s\n", p);
free_strings (lines);
}
/* Download the Windows SYSTEM hive and find DHCP configuration in there. */
static void
print_dhcp_address_windows (guestfs_h *g, char *root_fs)
{
char *system_path;
int64_t root, node, value;
struct guestfs_hivex_node_list *nodes;
char *controlset;
size_t i;
char *p;
/* Locate the SYSTEM hive. */
system_path = guestfs_inspect_get_windows_system_hive (g, root_fs);
if (!system_path)
exit (EXIT_FAILURE);
/* Open the hive to parse it. Note that before libguestfs 1.19.35
* you had to download the file and parse it using hivex(3). Since
* libguestfs 1.19.35, parts of the hivex(3) API are now exposed
* through libguestfs, and that is what we'll use here because it is
* more convenient and avoids having to download the hive.
*/
if (guestfs_hivex_open (g, system_path, -1) == -1)
exit (EXIT_FAILURE);
free (system_path);
root = guestfs_hivex_root (g);
if (root == -1)
exit (EXIT_FAILURE);
/* Get ControlSetXXX\Services\Tcpip\Parameters\Interfaces. */
controlset = guestfs_inspect_get_windows_current_control_set (g, root_fs);
if (controlset == NULL)
exit (EXIT_FAILURE);
const char *path[] = { controlset, "Services", "Tcpip", "Parameters",
"Interfaces" };
node = root;
for (i = 0; node > 0 && i < sizeof path / sizeof path[0]; ++i)
node = guestfs_hivex_node_get_child (g, node, path[i]);
if (node == -1)
exit (EXIT_FAILURE);
if (node == 0) {
fprintf (stderr, "virt-dhcp-address: HKLM\\System\\%s\\Services\\Tcpip\\Parameters\\Interfaces not found.", controlset);
exit (EXIT_FAILURE);
}
free (controlset);
/* Look for a node under here which has a "DhcpIPAddress" entry in it. */
nodes = guestfs_hivex_node_children (g, node);
if (nodes == NULL)
exit (EXIT_FAILURE);
value = 0;
for (i = 0; value == 0 && i < nodes->len; ++i) {
value = guestfs_hivex_node_get_value (g, nodes->val[i].hivex_node_h,
"DhcpIPAddress");
if (value == -1)
exit (EXIT_FAILURE);
}
if (value == 0) {
fprintf (stderr, "virt-dhcp-address: cannot find DHCP address for this guest.\n");
exit (EXIT_FAILURE);
}
guestfs_free_hivex_node_list (nodes);
/* Get the string and use libguestfs's auto-conversion to convert it
* to UTF-8 for output.
*/
p = guestfs_hivex_value_string (g, value);
if (!p)
exit (EXIT_FAILURE);
printf ("%s\n", p);
free (p);
/* Close the hive handle. */
guestfs_hivex_close (g);
}
static int
compare_keys_len (const void *p1, const void *p2)
{
const char *key1 = * (char * const *) p1;
const char *key2 = * (char * const *) p2;
return strlen (key1) - strlen (key2);
}
static size_t
count_strings (char *const *argv)
{
size_t c;
for (c = 0; argv[c]; ++c)
;
return c;
}
static void
free_strings (char **argv)
{
size_t i;
for (i = 0; argv[i]; ++i)
free (argv[i]);
free (argv);
}
π SEE ALSO π
guestfs(3)guestfs-erlang(3)guestfs-golang(3)guestfs-java(3)guestfs-lua(3)guestfs-ocaml(3)guestfs-perl(3)guestfs-python(3)guestfs-recipes(1)guestfs-ruby(3)http://libguestfs.org/
π AUTHORS π
Richard W.M. Jones ("rjones at redhat dot com")
π COPYRIGHT π
Copyright (C) 2010-2025 Red Hat Inc.
π LICENSE π
This manual page contains examples which we hope you will use in your programs. The examples may be freely copied, modified and distributed for any purpose without any restrictions.
π¦ BUGS π¦
To get a list of bugs against libguestfs, use this link:
https://bugzilla.redhat.com/buglist.cgi?component=libguestfs&product=Virtualization+Tools
To report a new bug against libguestfs, use this link:
https://bugzilla.redhat.com/enter_bug.cgi?component=libguestfs&product=Virtualization+Tools
When reporting a bug, please supply:
- The version of libguestfs.
- Where you got libguestfs (eg. which Linux distro, compiled from source, etc)
- Describe the bug accurately and give a way to reproduce it.
- Run
libguestfs-test-tool(1)and paste the complete, unedited output into the bug report.
And thatβs the whole page, bestie β footer says libguestfs-1.61.3, dated 2026-09-16, and itβs guestfs-examples(3). Obsessed. π
π¦ where this page comes from
- centos-stream 10: libguestfs-devel 1:1.61.3-1.el10 Β· LGPL-2.1-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms