Libguestfs - Acceder y modificar imágenes VM desde código

En una entrada anterior vimos que podíamos usar guestfish para acceder y modificar imágenes de disco sin cargarlas en una máquina virtual (VM) desde la línea de comandos o desde scripts de shell.Vamos a ver en esta entrada como usar la librería libguestfs en la que se basa guestfish directamente desde código.

Tenemos que tener la librería instalada, en entornos Debian/Ubuntu:

❯ sudo apt install libguestfs-dev

Ejemplo en C

El primer ejemplo lo vamos a hacer en C. La librería esta desarrollada en C a si que su uso desde este lenguaje es muy directo.

El programa en concreto recibe por la línea de comandos una ruta con una imagen de una VM como parámetro y comprueba los sistemas operativos que están instalados en el disco. En el caso de encontrar alguno, muestra la información básica del sistema y el espacio ocupado y libre en el disco:

df-vmc.c
/*  
 * df-vmc.c  
 *  
 * (c) Author: David Quiroga  
 * e-mail: david (at) clibre (dot) io  
 *  
 ***************************************************************  
 * Descripción:  
 *  
 * Uso de la libreria Libguestfs para acceder y modificar imágenes de VM 
 *  
 * SPDX-License-Identifier: GPL-3.0  
 *  
 * gcc -Wall df-vmc.c -o df-vmc `pkg-config libguestfs --cflags --libs` 
*/ 
  
#define _GNU_SOURCE  
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <stddef.h>  
#include <errno.h>  
#include <locale.h> 
#include <guestfs.h> 
  
static FILE *error_log = NULL;  
  
/* Macro para la gestión de errores */  
#define ifError(assertion, ...) ({\ 
    if(assertion) {\ 
        int errsv = errno;\ 
        fprintf (error_log ? error_log : stderr, "[%s:%d]:"\ 
            " \n [+] ", __FILE__, __LINE__);\ 
        fprintf (error_log ? error_log : stderr, __VA_ARGS__ );\ 
        if (errsv != 0)\ 
            fprintf (error_log ? error_log : stderr,\ 
                        " [+] errno: [%s]\n", strerror(errsv));\ 
    exit(EXIT_FAILURE);\ 
    }\ 
})  
  
int 
main (int argc, char *argv[]) 
{ 
    guestfs_h *g; 
    const char *disk; 
    char *disk_space = NULL; 
    char **roots, *root, *str; 
     
    setlocale(LC_ALL, "");  
    errno=0; 
     
    ifError (2 != argc, "Uso: %s disk.img\n", argv[0]); 
  
    disk = argv[1]; 
  
    g = guestfs_create (); 
    ifError (g == NULL, "Fallo al crear el manejador de libguestfs\n"); 
  
    /* Añadimos los parametros para libguestfs: solo-lectura */ 
    ifError (guestfs_add_drive_opts (g, disk, 
                GUESTFS_ADD_DRIVE_OPTS_READONLY, 1, 
                -1)  
                == -1, "Error en guestfs_add_drive_opts\n"); 
 
    /* Corremos back-end de libguestfs */ 
    ifError (guestfs_launch (g) == -1, "Error en guestfs_launch\n"); 
      
    /* inspeccionamos el sistema operativo */ 
    roots = guestfs_inspect_os (g); 
    ifError (roots == NULL, "Error en guestfs_inspect_os\n"); 
    ifError (roots[0] == NULL, "No se ha encontrado el systema operativo\n");  
 
    for (int j = 0; roots[j] != NULL; ++j) { 
     root = roots[j]; 
  
     printf ("Dispositivo Raíz: %s\n", root); 
     
     /* Mostramos información básica del systema operativo */ 
     str = guestfs_inspect_get_product_name (g, root); 
     if (str) 
        printf ("\tProduct name:\t %s\n", str); 
     free (str); 
  
     printf ("\tVersión:\t %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 ("\tTipo: \t %s\n", str); 
     free (str); 
     str = guestfs_inspect_get_distro (g, root); 
     if (str) 
        printf ("\tDistro: \t %s\n", str); 
     free (str); 
     
     /* Montamos el sistema de ficheros para poder acceder */ 
     ifError (guestfs_mount (g, root, "/") == -1,  
                    "Error en guestfs_mount\n");  
  
     /* Ver el espacio libre del disco */ 
     disk_space = guestfs_df_h (g); 
     ifError (disk_space == NULL, "Error en guestfs_df\n"); 
     
     printf ("%s\n", disk_space); 
     free (disk_space);  
     
     /* Desmontamos todo */ 
     ifError (guestfs_umount_all (g) == -1, "Error en guestfs_umount_all\n"); 
     
     free (root); 
    } 
    free (roots); 
  
    guestfs_close (g); 
  
    exit (EXIT_SUCCESS); 
} 

Construimos el ejecutable:

❯ gcc -Wall df-vmc.c -o df-vmc `pkg-config libguestfs --cflags --libs`

y lo probamos contra varias máquinas virtuales:

❯ ./df-vmc /home/david/almacen/virt-img/ubuntu18.04.qcow2 
Dispositivo Raíz: /dev/sda1
    Product name:     Ubuntu 18.04.3 LTS
    Versión:     18.4
    Tipo:        linux
    Distro:      ubuntu
Filesystem      Size  Used Avail Use% Mounted on
/dev/root       4.0G  579M  3.2G  16% /
tmpfs           146M  136K  146M   1% /run
/dev            361M     0  361M   0% /dev
shmfs           365M     0  365M   0% /dev/shm
/dev/sda1        89G  7.2G   77G   9% /sysroot

❯ ./df-vmc /home/david/almacen/virt-img/win10.qcow2
Dispositivo Raíz: /dev/sda2
    Product name:     Windows 10 Pro
    Versión:     10.0
    Tipo:        windows
    Distro:      windows
Filesystem      Size  Used Avail Use% Mounted on
/dev/root       4.0G  579M  3.2G  16% /
tmpfs           146M  144K  146M   1% /run
/dev            361M     0  361M   0% /dev
shmfs           365M     0  365M   0% /dev/shm
/dev/sda2       120G   19G  102G  16% /sysroot

Tenemos que tener en cuenta que necesitamos tener permisos de lectura a las imágenes del kernel en /boot/vmlinuz-*:

❯ sudo chmod 0644 /boot/vmlinuz-*

Recuerda que si se actualiza el kernel del sistema tendrás que volver a asignar estos permisos

Otro error que podemos encontrar es:

libguestfs: warning: current user is not a member of the KVM group (group ID 108).
This user cannot access /dev/kvm, so libguestfs may run very slowly.
It is recommended that you 'chmod 0666 /dev/kvm' or add the current user to the KVM group
(you might need to log out and log in again)

En este caso vamos a añadir nuestro usuario al grupo KVM:

❯ sudo usermod -a -G kvm david

Tendremos que reiniciar la sesión para que los cambios tengan efecto.  

libguestfsguestfish no requieren privilegios de acceso root. Solo es necesario ejecutarlos como root si la imagen del disco a la que se accede necesita root para leer o escribir

Ejemplo en Vala

Vamos a ver ahora como usar la librería desde Vala. Necesitamos instalar libguestfs-gobject-dev que contiene los encabezados de desarrollo y la documentación para los enlaces de GObject:

❯ sudo apt install libguestfs-gobject-dev

Para usar la librería desde Vala necesitamos el fichero .vapi corespondiente. No lo instala ninguna de las librerias que he visto por lo que podemos generarlo con vapigen:

❯ vapigen --pkg=gio-2.0 --library libguestfs-gobject-1.0 /usr/share/gir-1.0/Guestfs-1.0.gir

En mi caso en el fichero generado he tenido que cambiar la cabecera Guestfs-1.0.h por guestfs-gobject.h:

❯ sed -i 's/Guestfs-1.0.h/guestfs-gobject.h/g' libguestfs-gobject-1.0.vapi

Y lo copio a la ruta corespondiente:

❯ sudo cp libguestfs-gobject-1.0.vapi /usr/share/vala/vapi/

Ahora sí, nuestra aplicación en Vala similar a la anterior:

df-vmvala.vala
/*  
 * df-vmvala.vala 
 *  
 * (c) Author: David Quiroga  
 * e-mail: david (at) clibre (dot) io  
 *  
 ***************************************************************  
 * Descripción:  
 *  
 * Uso de la libreria Libguestfs para acceder y modificar imágenes de VM 
 *  
 * SPDX-License-Identifier: GPL-3.0  
 *  
 * valac --pkg libguestfs-gobject-1.0 --pkg gio-2.0 df-vmvala.vala 
*/ 
 
using Guestfs; 
 
public int main (string[] args){ 
 
    Intl.setlocale (ALL); 
     
    string disk = args[1]; 
    if (disk == null) { 
        warning ("Error en parametros\n"); 
        return 1; 
    } 
 
    var g = new Guestfs.Session (); 
    try { 
        var opt = new AddDrive(); 
        opt.readonly = Tristate.TRUE; 
        g.add_drive (disk, opt); 
        g.launch (); 
         
        string[] op_system = g.inspect_os(); 
        foreach (string op in op_system) { 
            print ("Dispositivo Raíz: %s\n", op);  
            print ("\tProduct Name:\t %s\n", g.inspect_get_product_name (op)); 
            print ("\tVersión:\t %d.%d\n",  
                         g.inspect_get_major_version (op), 
                         g.inspect_get_minor_version (op) ); 
            print ("\tTipo: \t %s\n\tDistro: \t %s\n",  
                         g.inspect_get_type (op), 
                         g.inspect_get_distro (op) );  
            g.mount (op, "/"); 
            string disk_space = g.df_h (); 
            print ("%s\n", disk_space); 
            g.umount ("/", null); 
        } 
    } catch (GLib.Error e) 
    { 
        critical (e.message); 
    } 
     
    return 0; 
} 

Recibe por la línea de comandos una ruta con una imagen de una VM como parámetro y comprueba los sistemas operativos que están instalados en el disco, mostrando información básica del sistema y la información del espacio en disco.

Construimos el ejecutable:

❯ valac --pkg libguestfs-gobject-1.0 --pkg gio-2.0 df-vmvala.vala

Lo ejecutamos como el caso anterior y sí, obtenemos el mismo resultado:

❯ ./df-vmvala /home/david/almacen/virt-img/u1804Desktop.qcow2 
Dispositivo Raíz: /dev/sda1
    Product Name:     Ubuntu 18.04 LTS
    Versión:     18.4
    Tipo:        linux
    Distro:      ubuntu
Filesystem      Size  Used Avail Use% Mounted on
/dev/root       4.0G  579M  3.2G  16% /
tmpfs           146M  136K  146M   1% /run
/dev            361M     0  361M   0% /dev
shmfs           365M     0  365M   0% /dev/shm
/dev/sda1        98G  9.5G   84G  11% /sysroot

❯ ./df-vmvala /home/david/almacen/virt-img/win10.qcow2
Dispositivo Raíz: /dev/sda2
    Product Name:     Windows 10 Pro
    Versión:     10.0
    Tipo:        windows
    Distro:      windows
Filesystem      Size  Used Avail Use% Mounted on
/dev/root       4.0G  579M  3.2G  16% /
tmpfs           146M  144K  146M   1% /run
/dev            361M     0  361M   0% /dev
shmfs           365M     0  365M   0% /dev/shm
/dev/sda2       120G   19G  102G  16% /sysroot

Conclusión

Si trabajas habitualmente con máquinas virtuales sin duda se puede sacar mucho partido a esta librería y a las herramientas de guestfish basadas en ella. Comprobar o monitorizar el espacio de algún disco, leer un log, añadir un recurso, comprobar el estado de un sistema/aplicación son solo algunos de los posibles usos que podemos darle a esta libreía. Tenemos más ejemplos de su uso en la web de guestfish que recomiendo revisar (por supuesto también se puede usar la librería con otros lenguajes de programación). Espero que pueda resultar de utilidad.

 

(2 votos)

Deja un comentario

Asegúrese de introducir toda la información requerida, indicada por un asterisco (*). No se permite código HTML.