mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-21 22:53:37 +00:00
appliance: Use supermin >= 5.
This requires the new version of supermin (5.1.0).
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -37,7 +37,10 @@ Makefile.in
|
|||||||
/align/stamp-virt-alignment-scan.pod
|
/align/stamp-virt-alignment-scan.pod
|
||||||
/align/virt-alignment-scan
|
/align/virt-alignment-scan
|
||||||
/align/virt-alignment-scan.1
|
/align/virt-alignment-scan.1
|
||||||
/appliance/excludelist
|
/appliance/daemon.tar.gz
|
||||||
|
/appliance/excludefiles
|
||||||
|
/appliance/hostfiles
|
||||||
|
/appliance/init.tar.gz
|
||||||
/appliance/libguestfs-make-fixed-appliance
|
/appliance/libguestfs-make-fixed-appliance
|
||||||
/appliance/libguestfs-make-fixed-appliance.1
|
/appliance/libguestfs-make-fixed-appliance.1
|
||||||
/appliance/make.sh
|
/appliance/make.sh
|
||||||
@@ -45,6 +48,7 @@ Makefile.in
|
|||||||
/appliance/stamp-libguestfs-make-fixed-appliance.pod
|
/appliance/stamp-libguestfs-make-fixed-appliance.pod
|
||||||
/appliance/stamp-supermin
|
/appliance/stamp-supermin
|
||||||
/appliance/supermin.d
|
/appliance/supermin.d
|
||||||
|
/appliance/udev-rules.tar.gz
|
||||||
/autom4te.cache
|
/autom4te.cache
|
||||||
/bash/virt-builder
|
/bash/virt-builder
|
||||||
/bash/virt-cat
|
/bash/virt-cat
|
||||||
|
|||||||
5
README
5
README
@@ -61,12 +61,11 @@ The full requirements are described below.
|
|||||||
| | | | - virtio-block |
|
| | | | - virtio-block |
|
||||||
| | | | - virtio-net |
|
| | | | - virtio-net |
|
||||||
+--------------+-------------+---+-----------------------------------------+
|
+--------------+-------------+---+-----------------------------------------+
|
||||||
| supermin | 4.1.0 | R | This is required on all distros. |
|
| supermin | 5.1.0 | R | This is required on all distros. |
|
||||||
| febootstrap | 3.20 | | 'supermin' is the new name for |
|
| | | | 'supermin' is the new name for |
|
||||||
| | | | 'febootstrap'. |
|
| | | | 'febootstrap'. |
|
||||||
| | | | For alternatives, see: |
|
| | | | For alternatives, see: |
|
||||||
| | | | libguestfs.org/download/binaries/appliance/
|
| | | | libguestfs.org/download/binaries/appliance/
|
||||||
| | | | febootstrap 2.x WILL NOT WORK |
|
|
||||||
+--------------+-------------+---+-----------------------------------------+
|
+--------------+-------------+---+-----------------------------------------+
|
||||||
| glibc | | R | We use various glibc-isms. |
|
| glibc | | R | We use various glibc-isms. |
|
||||||
| | | | Also glibc provides XDR, rpcgen. |
|
| | | | Also glibc provides XDR, rpcgen. |
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# libguestfs
|
# libguestfs
|
||||||
# Copyright (C) 2009 Red Hat Inc.
|
# Copyright (C) 2009-2014 Red Hat Inc.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,8 +19,9 @@ include $(top_srcdir)/subdir-rules.mk
|
|||||||
|
|
||||||
EXTRA_DIST = \
|
EXTRA_DIST = \
|
||||||
99-guestfs-serial.rules \
|
99-guestfs-serial.rules \
|
||||||
excludelist.in \
|
excludefiles.in \
|
||||||
guestfsd.suppressions \
|
guestfsd.suppressions \
|
||||||
|
hostfiles.in \
|
||||||
init \
|
init \
|
||||||
libguestfs-make-fixed-appliance.in \
|
libguestfs-make-fixed-appliance.in \
|
||||||
libguestfs-make-fixed-appliance.pod \
|
libguestfs-make-fixed-appliance.pod \
|
||||||
@@ -32,24 +33,42 @@ superminfsdir = $(libdir)/guestfs/supermin.d
|
|||||||
|
|
||||||
fs_DATA =
|
fs_DATA =
|
||||||
superminfs_DATA = \
|
superminfs_DATA = \
|
||||||
supermin.d/init.img \
|
supermin.d/init.tar.gz \
|
||||||
supermin.d/udev-rules.img
|
supermin.d/udev-rules.tar.gz
|
||||||
|
|
||||||
if SUPERMIN_HELPER_COMPRESSED_CPIO
|
|
||||||
GZ = .gz
|
|
||||||
endif
|
|
||||||
|
|
||||||
if ENABLE_DAEMON
|
if ENABLE_DAEMON
|
||||||
superminfs_DATA += \
|
superminfs_DATA += \
|
||||||
supermin.d/daemon.img$(GZ)
|
supermin.d/daemon.tar.gz
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if ENABLE_APPLIANCE
|
if ENABLE_APPLIANCE
|
||||||
superminfs_DATA += \
|
superminfs_DATA += \
|
||||||
supermin.d/base.img$(GZ) \
|
supermin.d/base.tar.gz \
|
||||||
|
supermin.d/packages \
|
||||||
|
supermin.d/excludefiles \
|
||||||
supermin.d/hostfiles
|
supermin.d/hostfiles
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
supermin.d/base.tar.gz \
|
||||||
|
supermin.d/daemon.tar.gz \
|
||||||
|
supermin.d/excludefiles \
|
||||||
|
supermin.d/hostfiles \
|
||||||
|
supermin.d/init.tar.gz \
|
||||||
|
supermin.d/packages \
|
||||||
|
supermin.d/udev-rules.tar.gz: stamp-supermin
|
||||||
|
stamp-supermin: make.sh \
|
||||||
|
packagelist \
|
||||||
|
hostfiles \
|
||||||
|
excludefiles \
|
||||||
|
daemon.tar.gz \
|
||||||
|
init.tar.gz \
|
||||||
|
udev-rules.tar.gz
|
||||||
|
rm -f $@ supermin.d/base.tar.gz supermin.d/packages
|
||||||
|
./make.sh
|
||||||
|
cp -t supermin.d \
|
||||||
|
daemon.tar.gz excludefiles hostfiles init.tar.gz udev-rules.tar.gz
|
||||||
|
touch $@
|
||||||
|
|
||||||
# This used to be a configure-generated file. However config.status
|
# This used to be a configure-generated file. However config.status
|
||||||
# always touches the destination file, which means the appliance got
|
# always touches the destination file, which means the appliance got
|
||||||
# rebuilt too often.
|
# rebuilt too often.
|
||||||
@@ -65,57 +84,47 @@ if VALGRIND_DAEMON
|
|||||||
PACKAGELIST_CPP_FLAGS += -DVALGRIND_DAEMON=1
|
PACKAGELIST_CPP_FLAGS += -DVALGRIND_DAEMON=1
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
excludefiles: excludefiles.in Makefile
|
||||||
|
m4 $(PACKAGELIST_CPP_FLAGS) $< | \
|
||||||
|
grep -v '^[[:space:]]*$$' | grep -v '^#' > $@-t
|
||||||
|
cmp -s $@ $@-t || mv $@-t $@
|
||||||
|
rm -f $@-t
|
||||||
|
|
||||||
|
hostfiles: hostfiles.in Makefile
|
||||||
|
m4 $(PACKAGELIST_CPP_FLAGS) $< | \
|
||||||
|
grep -v '^[[:space:]]*$$' | grep -v '^#' > $@-t
|
||||||
|
cmp -s $@ $@-t || mv $@-t $@
|
||||||
|
rm -f $@-t
|
||||||
|
|
||||||
packagelist: packagelist.in Makefile
|
packagelist: packagelist.in Makefile
|
||||||
cpp -undef $(PACKAGELIST_CPP_FLAGS) < $< | \
|
m4 $(PACKAGELIST_CPP_FLAGS) $< | \
|
||||||
grep -v '^[[:space:]]*$$' | grep -v '^#' > $@-t
|
grep -v '^[[:space:]]*$$' | grep -v '^#' > $@-t
|
||||||
cmp -s $@ $@-t || mv $@-t $@
|
cmp -s $@ $@-t || mv $@-t $@
|
||||||
rm -f $@-t
|
rm -f $@-t
|
||||||
|
|
||||||
excludelist: excludelist.in Makefile
|
daemon.tar.gz: ../daemon/guestfsd guestfsd.suppressions
|
||||||
cpp -undef $(PACKAGELIST_CPP_FLAGS) < $< | \
|
rm -f $@ $@-t
|
||||||
grep -v '^[[:space:]]*$$' | grep -v '^#' > $@-t
|
|
||||||
cmp -s $@ $@-t || mv $@-t $@
|
|
||||||
rm -f $@-t
|
|
||||||
|
|
||||||
supermin.d/base.img$(GZ) supermin.d/hostfiles: stamp-supermin
|
|
||||||
stamp-supermin: make.sh packagelist excludelist
|
|
||||||
rm -f $@ supermin.d/base.img$(GZ) supermin.d/hostfiles
|
|
||||||
./make.sh
|
|
||||||
if SUPERMIN_HELPER_COMPRESSED_CPIO
|
|
||||||
gzip -9 supermin.d/base.img
|
|
||||||
endif
|
|
||||||
touch $@
|
|
||||||
|
|
||||||
supermin.d/daemon.img$(GZ): ../daemon/guestfsd guestfsd.suppressions
|
|
||||||
rm -f $@ $@-t $@-tt
|
|
||||||
rm -rf tmp-d
|
rm -rf tmp-d
|
||||||
mkdir -p tmp-d$(DAEMON_SUPERMIN_DIR) tmp-d/etc
|
mkdir -p tmp-d$(DAEMON_SUPERMIN_DIR) tmp-d/etc
|
||||||
ln ../daemon/guestfsd tmp-d$(DAEMON_SUPERMIN_DIR)/guestfsd
|
ln ../daemon/guestfsd tmp-d$(DAEMON_SUPERMIN_DIR)/guestfsd
|
||||||
ln $(srcdir)/guestfsd.suppressions tmp-d/etc/guestfsd.suppressions
|
ln $(srcdir)/guestfsd.suppressions tmp-d/etc/guestfsd.suppressions
|
||||||
( cd tmp-d && find | cpio --quiet -o -H newc ) > $@-t
|
( cd tmp-d && tar zcf - * ) > $@-t
|
||||||
rm -r tmp-d
|
rm -r tmp-d
|
||||||
if SUPERMIN_HELPER_COMPRESSED_CPIO
|
|
||||||
gzip -9 -c $@-t > $@-tt
|
|
||||||
mv $@-tt $@-t
|
|
||||||
endif
|
|
||||||
mv $@-t $@
|
mv $@-t $@
|
||||||
|
|
||||||
supermin.d/init.img: init
|
init.tar.gz: init
|
||||||
rm -rf init.tmp $@ $@-t
|
rm -f $@ $@-t
|
||||||
mkdir init.tmp
|
tar zcf $@-t init
|
||||||
cp $< init.tmp
|
|
||||||
(cd init.tmp; echo "init" | cpio --quiet -o -H newc) > $@-t
|
|
||||||
rm -r init.tmp
|
|
||||||
mv $@-t $@
|
mv $@-t $@
|
||||||
|
|
||||||
# We should put this file in /lib/udev/rules.d, but put it in /etc so
|
# We should put this file in /lib/udev/rules.d, but put it in /etc so
|
||||||
# we don't have to deal with all the UsrMove crap in Fedora.
|
# we don't have to deal with all the UsrMove crap in Fedora.
|
||||||
supermin.d/udev-rules.img: 99-guestfs-serial.rules
|
udev-rules.tar.gz: 99-guestfs-serial.rules
|
||||||
rm -f $@ $@-t
|
rm -f $@ $@-t
|
||||||
rm -rf tmp-u
|
rm -rf tmp-u
|
||||||
mkdir -p tmp-u/etc/udev/rules.d
|
mkdir -p tmp-u/etc/udev/rules.d
|
||||||
for f in $^; do ln $$f tmp-u/etc/udev/rules.d/$$(basename $$f); done
|
for f in $^; do ln $$f tmp-u/etc/udev/rules.d/$$(basename $$f); done
|
||||||
( cd tmp-u && find | cpio --quiet -o -H newc ) > $@-t
|
( cd tmp-u && tar zcf - etc ) > $@-t
|
||||||
rm -r tmp-u
|
rm -r tmp-u
|
||||||
mv $@-t $@
|
mv $@-t $@
|
||||||
|
|
||||||
@@ -144,7 +153,14 @@ stamp-libguestfs-make-fixed-appliance.pod: libguestfs-make-fixed-appliance.pod
|
|||||||
|
|
||||||
# Make clean.
|
# Make clean.
|
||||||
|
|
||||||
CLEANFILES = packagelist excludelist \
|
CLEANFILES = \
|
||||||
|
*~ \
|
||||||
|
daemon.tar.gz \
|
||||||
|
excludefiles \
|
||||||
|
hostfiles \
|
||||||
|
init.tar.gz \
|
||||||
libguestfs-make-fixed-appliance.1 \
|
libguestfs-make-fixed-appliance.1 \
|
||||||
|
packagelist \
|
||||||
stamp-libguestfs-make-fixed-appliance.pod \
|
stamp-libguestfs-make-fixed-appliance.pod \
|
||||||
supermin.d/*
|
supermin.d/* \
|
||||||
|
udev-rules.tar.gz
|
||||||
|
|||||||
29
appliance/excludefiles.in
Normal file
29
appliance/excludefiles.in
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
dnl This is the list of files excluded from the appliance, even if
|
||||||
|
dnl they appear in packagelist.in (or more likely, as dependencies of
|
||||||
|
dnl packages in packagelist.in).
|
||||||
|
dnl
|
||||||
|
dnl List is a list of wildcards, one per line, prefixed by a '-' character.
|
||||||
|
dnl
|
||||||
|
dnl This file is processed by m4 with one of the
|
||||||
|
dnl following symbols defined (depending on the distro):
|
||||||
|
dnl
|
||||||
|
dnl REDHAT=1 For Fedora, RHEL, EPEL and workalikes.
|
||||||
|
dnl DEBIAN=1 For Debian.
|
||||||
|
dnl UBUNTU=1 For Ubuntu.
|
||||||
|
dnl ARCHLINUX=1 For Archlinux.
|
||||||
|
dnl
|
||||||
|
dnl Note that any matching file will be dropped from the appliance.
|
||||||
|
dnl Of course, this may break the appliance, so be careful.
|
||||||
|
|
||||||
|
dnl The right kernel modules are added back by supermin.
|
||||||
|
-/boot/*
|
||||||
|
-/lib/modules/*
|
||||||
|
|
||||||
|
-/usr/lib/locale/*
|
||||||
|
-/usr/share/locale/*
|
||||||
|
-/usr/share/man/*
|
||||||
|
-/usr/share/doc/*
|
||||||
|
-/usr/share/info/*
|
||||||
|
-/usr/share/gnome/help/*
|
||||||
|
-/usr/share/cracklib/*
|
||||||
|
-/usr/share/i18n/*
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
/* This is the list of distro packages which are
|
|
||||||
* excluded from the appliance, even if they appear in
|
|
||||||
* packagelist.in (or more likely, as dependencies of
|
|
||||||
* packages in packagelist.in).
|
|
||||||
*
|
|
||||||
* List is a list of basic regular expressions, one per line.
|
|
||||||
*
|
|
||||||
* This file is processed by cpp with one of the
|
|
||||||
* following symbols defined (depending on the distro):
|
|
||||||
*
|
|
||||||
* REDHAT=1 For Fedora, RHEL, EPEL and workalikes.
|
|
||||||
* DEBIAN=1 For Debian.
|
|
||||||
* UBUNTU=1 For Ubuntu.
|
|
||||||
* ARCHLINUX=1 For Archlinux.
|
|
||||||
*
|
|
||||||
* Note that any file provided by one of these packages will
|
|
||||||
* be dropped from the appliance. Of course, this may break
|
|
||||||
* the appliance, so be careful. Other files are also dropped
|
|
||||||
* from the appliance such as docs and man pages: see 'make.sh.in'
|
|
||||||
* for the full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Basically the same with a few minor tweaks. */
|
|
||||||
#ifdef UBUNTU
|
|
||||||
#define DEBIAN 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Don't need any Perl or Python appearing in the appliance. */
|
|
||||||
^perl
|
|
||||||
^python
|
|
||||||
|
|
||||||
/* Plymouth is a graphical boot thing - not needed. */
|
|
||||||
^plymouth
|
|
||||||
|
|
||||||
/* Linux firmware. */
|
|
||||||
^linux-firmware
|
|
||||||
|
|
||||||
/* Keyboard maps - appliance is not interactive. */
|
|
||||||
^kbd-misc
|
|
||||||
|
|
||||||
#ifdef REDHAT
|
|
||||||
|
|
||||||
/* Linux kernel. febootstrap <= 3.18 used to exclude the kernel
|
|
||||||
* package (only) by default, but since 3.19 it doesn't do this any
|
|
||||||
* longer.
|
|
||||||
*/
|
|
||||||
^kernel
|
|
||||||
|
|
||||||
^fedora-logos
|
|
||||||
^redhat-logos
|
|
||||||
^dracut
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef DEBIAN
|
|
||||||
^file-rc
|
|
||||||
#endif
|
|
||||||
13
appliance/hostfiles.in
Normal file
13
appliance/hostfiles.in
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
dnl This is the list of extra files added to appliance.
|
||||||
|
dnl
|
||||||
|
dnl List is a list of wildcards, one per line.
|
||||||
|
dnl
|
||||||
|
dnl This file is processed by m4 with one of the
|
||||||
|
dnl following symbols defined (depending on the distro):
|
||||||
|
dnl
|
||||||
|
dnl REDHAT=1 For Fedora, RHEL, EPEL and workalikes.
|
||||||
|
dnl DEBIAN=1 For Debian.
|
||||||
|
dnl UBUNTU=1 For Ubuntu.
|
||||||
|
dnl ARCHLINUX=1 For Archlinux.
|
||||||
|
|
||||||
|
/usr/share/augeas/lenses/*.aug
|
||||||
@@ -105,7 +105,7 @@ guestfish -a /dev/null run
|
|||||||
# Find the location of the appliance.
|
# Find the location of the appliance.
|
||||||
cachedir="$(guestfish get-cachedir)"
|
cachedir="$(guestfish get-cachedir)"
|
||||||
euid="$(id -u)"
|
euid="$(id -u)"
|
||||||
appliancedir="$cachedir/.guestfs-$euid"
|
appliancedir="$cachedir/.guestfs-$euid/appliance.d"
|
||||||
|
|
||||||
cp "$appliancedir/kernel" "$outputdir/kernel"
|
cp "$appliancedir/kernel" "$outputdir/kernel"
|
||||||
cp "$appliancedir/initrd" "$outputdir/initrd"
|
cp "$appliancedir/initrd" "$outputdir/initrd"
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ be set using the C<LIBGUESTFS_PATH> environment variable.
|
|||||||
|
|
||||||
Normally a supermin appliance is located on this path (see
|
Normally a supermin appliance is located on this path (see
|
||||||
L<supermin(1)/SUPERMIN APPLIANCE>). libguestfs reconstructs this
|
L<supermin(1)/SUPERMIN APPLIANCE>). libguestfs reconstructs this
|
||||||
into a full appliance by running L<supermin-helper(1)>.
|
into a full appliance by running C<supermin --build>.
|
||||||
|
|
||||||
However, a simpler "fixed appliance" can also be used. libguestfs
|
However, a simpler "fixed appliance" can also be used. libguestfs
|
||||||
detects this by looking for a directory on the path containing four
|
detects this by looking for a directory on the path containing four
|
||||||
@@ -167,7 +167,6 @@ libguestfs, please see the L<guestfs(3)> manual page.
|
|||||||
|
|
||||||
L<guestfs(3)>,
|
L<guestfs(3)>,
|
||||||
L<supermin(1)>,
|
L<supermin(1)>,
|
||||||
L<supermin-helper(1)>,
|
|
||||||
L<xz(1)>,
|
L<xz(1)>,
|
||||||
L<http://libguestfs.org/>,
|
L<http://libguestfs.org/>,
|
||||||
L<http://qemu.org/>.
|
L<http://qemu.org/>.
|
||||||
|
|||||||
@@ -20,42 +20,14 @@ unset CDPATH
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Turn excludelist file into command line arguments.
|
# Run supermin.
|
||||||
exec 5<excludelist
|
|
||||||
while read regexp <&5; do
|
|
||||||
excludes="$excludes --exclude $regexp"
|
|
||||||
done
|
|
||||||
exec 5<&-
|
|
||||||
|
|
||||||
# Run supermin on the package list.
|
|
||||||
# NB: Keep using --yum-config (deprecated alias) here since both old
|
|
||||||
# and new supermin still support it.
|
|
||||||
if [ "x@SUPERMIN_PACKAGER_CONFIG@" != "xno" ]; then
|
if [ "x@SUPERMIN_PACKAGER_CONFIG@" != "xno" ]; then
|
||||||
extra="--yum-config @SUPERMIN_PACKAGER_CONFIG@"
|
extra="--packager-config @SUPERMIN_PACKAGER_CONFIG@"
|
||||||
fi
|
fi
|
||||||
if [ "x@SUPERMIN_EXTRA_OPTIONS@" != "xno" ]; then
|
if [ "x@SUPERMIN_EXTRA_OPTIONS@" != "xno" ]; then
|
||||||
extra="$extra @SUPERMIN_EXTRA_OPTIONS@"
|
extra="$extra @SUPERMIN_EXTRA_OPTIONS@"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo @SUPERMIN@ -v -o supermin.d --names $(< packagelist ) $excludes $extra
|
echo @SUPERMIN@ --prepare -v -o supermin.d $(< packagelist ) $extra
|
||||||
@SUPERMIN@ -v -o supermin.d --names $(< packagelist ) $excludes $extra
|
@SUPERMIN@ --prepare -v -o supermin.d $(< packagelist ) $extra
|
||||||
|
|
||||||
# Remove some things that we don't want in the appliance. This is
|
|
||||||
# copied from the old febootstrap-minimize. However minimization is
|
|
||||||
# not so important now that we are caching the appliance.
|
|
||||||
< supermin.d/hostfiles \
|
|
||||||
grep -v '^/usr/lib/locale' |
|
|
||||||
grep -v '^/usr/share/locale' |
|
|
||||||
grep -v '^/usr/share/man/' |
|
|
||||||
grep -v '^/usr/share/doc/' |
|
|
||||||
grep -v '^/usr/share/info/' |
|
|
||||||
grep -v '^/usr/share/gnome/help/' |
|
|
||||||
grep -v '^/usr/share/cracklib/' |
|
|
||||||
grep -v '^/usr/share/i18n/' > supermin.d/hostfiles-t
|
|
||||||
|
|
||||||
# Include any Augeas lenses from the host.
|
|
||||||
if grep -q /usr/share/augeas/lenses supermin.d/hostfiles-t; then
|
|
||||||
echo "/usr/share/augeas/lenses/*.aug" >> supermin.d/hostfiles-t
|
|
||||||
fi
|
|
||||||
|
|
||||||
mv supermin.d/hostfiles-t supermin.d/hostfiles
|
|
||||||
|
|||||||
@@ -1,34 +1,31 @@
|
|||||||
/* This is the list of distro packages which are
|
dnl This is the list of distro packages which are
|
||||||
* installed on the appliance.
|
dnl installed on the appliance.
|
||||||
*
|
dnl
|
||||||
* This file is processed by cpp with one of the
|
dnl This file is processed by m4 with one of the
|
||||||
* following symbols defined (depending on the distro):
|
dnl following symbols defined (depending on the distro):
|
||||||
*
|
dnl
|
||||||
* REDHAT=1 For Fedora, RHEL, EPEL and workalikes.
|
dnl REDHAT=1 For Fedora, RHEL, EPEL and workalikes.
|
||||||
* DEBIAN=1 For Debian.
|
dnl DEBIAN=1 For Debian.
|
||||||
* UBUNTU=1 For Ubuntu.
|
dnl UBUNTU=1 For Ubuntu.
|
||||||
* ARCHLINUX=1 For Archlinux.
|
dnl ARCHLINUX=1 For Archlinux.
|
||||||
*
|
dnl
|
||||||
* There is also a list of packages which are excluded if they appear
|
dnl There is also a list of packages which are excluded if they appear
|
||||||
* as dependencies of the packages below. See: excludelist.in
|
dnl as dependencies of the packages below. See: excludelist.in
|
||||||
*
|
dnl
|
||||||
* To add arbitrary extra packages, use:
|
dnl To add arbitrary extra packages, use:
|
||||||
*
|
dnl
|
||||||
* ./configure --with-extra-packages="gdb valgrind [etc]"
|
dnl ./configure --with-extra-packages="gdb valgrind [etc]"
|
||||||
*/
|
|
||||||
|
|
||||||
/* Basically the same with a few minor tweaks. */
|
dnl Basically the same with a few minor tweaks.
|
||||||
#ifdef UBUNTU
|
ifelse(UBUNTU,1,define(DEBIAN,1))
|
||||||
#define DEBIAN 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef REDHAT
|
ifelse(REDHAT,1,
|
||||||
augeas-libs
|
augeas-libs
|
||||||
btrfs-progs
|
btrfs-progs
|
||||||
cryptsetup
|
cryptsetup
|
||||||
cryptsetup-luks /* old name used before Fedora 17 */
|
cryptsetup-luks dnl old name used before Fedora 17
|
||||||
e2fsprogs
|
e2fsprogs
|
||||||
/* e4fsprogs only exists on RHEL 5, will be ignored everywhere else. */
|
dnl e4fsprogs only exists on RHEL 5, will be ignored everywhere else.
|
||||||
e4fsprogs
|
e4fsprogs
|
||||||
genisoimage
|
genisoimage
|
||||||
gfs-utils
|
gfs-utils
|
||||||
@@ -40,7 +37,7 @@
|
|||||||
iputils
|
iputils
|
||||||
kernel
|
kernel
|
||||||
libcap
|
libcap
|
||||||
libldm /* only Fedora has this for now, but we should add it to others later*/
|
libldm dnl only Fedora for now, others later
|
||||||
nilfs-utils
|
nilfs-utils
|
||||||
ntfsprogs
|
ntfsprogs
|
||||||
ntfs-3g
|
ntfs-3g
|
||||||
@@ -49,14 +46,14 @@
|
|||||||
reiserfs-utils
|
reiserfs-utils
|
||||||
libselinux
|
libselinux
|
||||||
syslinux-extlinux
|
syslinux-extlinux
|
||||||
systemd /* for /sbin/reboot and udevd */
|
systemd dnl for /sbin/reboot and udevd
|
||||||
vim-minimal
|
vim-minimal
|
||||||
xz
|
xz
|
||||||
yajl
|
yajl
|
||||||
zfs-fuse
|
zfs-fuse
|
||||||
#endif /* REDHAT */
|
)
|
||||||
|
|
||||||
#ifdef DEBIAN
|
ifelse(DEBIAN,1,
|
||||||
bsdmainutils
|
bsdmainutils
|
||||||
btrfs-tools
|
btrfs-tools
|
||||||
cryptsetup
|
cryptsetup
|
||||||
@@ -74,21 +71,21 @@
|
|||||||
libpcre3
|
libpcre3
|
||||||
libyajl2
|
libyajl2
|
||||||
linux-image
|
linux-image
|
||||||
/* syslinux 'suggests' mtools, but in reality it's a hard dependency: */
|
dnl syslinux 'suggests' mtools, but in reality it's a hard dependency:
|
||||||
mtools
|
mtools
|
||||||
nilfs-tools
|
nilfs-tools
|
||||||
ntfs-3g
|
ntfs-3g
|
||||||
ntfsprogs
|
ntfsprogs
|
||||||
openssh-client
|
openssh-client
|
||||||
reiserfsprogs
|
reiserfsprogs
|
||||||
sysvinit /* for /sbin/reboot */
|
sysvinit dnl for /sbin/reboot
|
||||||
ufsutils
|
ufsutils
|
||||||
vim-tiny
|
vim-tiny
|
||||||
xz-utils
|
xz-utils
|
||||||
zfs-fuse
|
zfs-fuse
|
||||||
#endif /* DEBIAN */
|
)
|
||||||
|
|
||||||
#ifdef ARCHLINUX
|
ifelse(ARCHLINUX,1,
|
||||||
augeas
|
augeas
|
||||||
btrfs-progs
|
btrfs-progs
|
||||||
cdrkit
|
cdrkit
|
||||||
@@ -111,9 +108,9 @@
|
|||||||
xz
|
xz
|
||||||
yajl
|
yajl
|
||||||
zfs-fuse
|
zfs-fuse
|
||||||
#endif /* ARCHLINUX */
|
)
|
||||||
|
|
||||||
#ifdef FRUGALWARE
|
ifelse(FRUGALWARE,1,
|
||||||
augeas
|
augeas
|
||||||
btrfs-progs
|
btrfs-progs
|
||||||
cryptsetup-luks
|
cryptsetup-luks
|
||||||
@@ -169,7 +166,7 @@
|
|||||||
tar
|
tar
|
||||||
util-linux
|
util-linux
|
||||||
xfsprogs
|
xfsprogs
|
||||||
#endif /* FRUGALWARE */
|
)
|
||||||
|
|
||||||
acl
|
acl
|
||||||
attr
|
attr
|
||||||
@@ -196,11 +193,9 @@ lvm2
|
|||||||
lzop
|
lzop
|
||||||
mdadm
|
mdadm
|
||||||
module-init-tools
|
module-init-tools
|
||||||
/*
|
dnl Enabling this pulls out 140 extra packages
|
||||||
Enabling this pulls out 140 extra packages
|
dnl into the appliance:
|
||||||
into the appliance:
|
dnl ocfs2-tools
|
||||||
ocfs2-tools
|
|
||||||
*/
|
|
||||||
parted
|
parted
|
||||||
procps
|
procps
|
||||||
procps-ng
|
procps-ng
|
||||||
@@ -214,17 +209,10 @@ tar
|
|||||||
udev
|
udev
|
||||||
util-linux
|
util-linux
|
||||||
util-linux-ng
|
util-linux-ng
|
||||||
#ifndef UBUNTU
|
|
||||||
/* on Ubuntu contains a file in /lib64 which conflicts with libc6 that has
|
|
||||||
* /lib64 as a symbolic link
|
|
||||||
*/
|
|
||||||
xfsprogs
|
xfsprogs
|
||||||
#endif
|
|
||||||
zerofree
|
zerofree
|
||||||
|
|
||||||
#ifdef VALGRIND_DAEMON
|
ifelse(VALGRIND_DAEMON,1,valgrind)
|
||||||
valgrind
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Define this by doing: ./configure --with-extra-packages="..." */
|
dnl Define this by doing: ./configure --with-extra-packages="..."
|
||||||
EXTRA_PACKAGES
|
EXTRA_PACKAGES
|
||||||
|
|||||||
79
configure.ac
79
configure.ac
@@ -447,45 +447,10 @@ AM_CONDITIONAL([ENABLE_APPLIANCE],[test "x$ENABLE_APPLIANCE" = "xyes"])
|
|||||||
AC_MSG_RESULT([$ENABLE_APPLIANCE])
|
AC_MSG_RESULT([$ENABLE_APPLIANCE])
|
||||||
AC_SUBST([ENABLE_APPLIANCE])
|
AC_SUBST([ENABLE_APPLIANCE])
|
||||||
|
|
||||||
dnl Check for supermin >= 4.1.0 or febootstrap >= 3.20.
|
dnl Check for supermin >= 5.1.0.
|
||||||
AC_CHECK_PROGS([SUPERMIN],
|
AC_CHECK_PROGS([SUPERMIN],[supermin],[no])
|
||||||
[supermin febootstrap],[no])
|
|
||||||
AC_PATH_PROGS([SUPERMIN_HELPER],
|
|
||||||
[supermin-helper febootstrap-supermin-helper],[no])
|
|
||||||
|
|
||||||
dnl supermin >= 4.1.4 supports compressed cpio images.
|
|
||||||
AC_MSG_CHECKING([for supermin-helper version])
|
|
||||||
supermin_helper_version=`$SUPERMIN_HELPER --version | awk '{print $2}'`
|
|
||||||
AC_MSG_RESULT([$supermin_helper_version])
|
|
||||||
AC_MSG_CHECKING([if supermin-helper supports compressed cpio images])
|
|
||||||
supermin_helper_version_int=`echo "$supermin_helper_version" | awk -F. '{print $1 * 1000000 + $2 * 1000 + $3}'`
|
|
||||||
echo supermin_helper_version_int=$supermin_helper_version_int >&AS_MESSAGE_LOG_FD
|
|
||||||
if test $supermin_helper_version_int -ge 4001004; then
|
|
||||||
supermin_helper_compressed_cpio=yes
|
|
||||||
else
|
|
||||||
supermin_helper_compressed_cpio=no
|
|
||||||
fi
|
|
||||||
AC_MSG_RESULT([$supermin_helper_compressed_cpio])
|
|
||||||
AM_CONDITIONAL([SUPERMIN_HELPER_COMPRESSED_CPIO],
|
|
||||||
[test "x$supermin_helper_compressed_cpio" = "xyes"])
|
|
||||||
|
|
||||||
dnl supermin >= 4.1.5 supports device trees and uses a new style command
|
|
||||||
dnl syntax.
|
|
||||||
AC_MSG_CHECKING([if supermin-helper supports device trees and new style command syntax])
|
|
||||||
if test $supermin_helper_version_int -ge 4001005; then
|
|
||||||
supermin_helper_new_style_syntax=yes
|
|
||||||
AC_DEFINE([SUPERMIN_HELPER_NEW_STYLE_SYNTAX],[1],
|
|
||||||
[Define to 1 if you have supermin-helper >= 4.1.5.])
|
|
||||||
else
|
|
||||||
supermin_helper_new_style_syntax=no
|
|
||||||
fi
|
|
||||||
AC_MSG_RESULT([$supermin_helper_new_style_syntax])
|
|
||||||
|
|
||||||
dnl Pass supermin --packager-config option.
|
dnl Pass supermin --packager-config option.
|
||||||
dnl
|
|
||||||
dnl Note that in febootstrap >= 3.21 / supermin >= 4.1.0, this option
|
|
||||||
dnl is now called --packager-config, although --yum-config can still
|
|
||||||
dnl be used as a deprecated alias.
|
|
||||||
SUPERMIN_PACKAGER_CONFIG=no
|
SUPERMIN_PACKAGER_CONFIG=no
|
||||||
|
|
||||||
AC_MSG_CHECKING([for --with-supermin-packager-config option])
|
AC_MSG_CHECKING([for --with-supermin-packager-config option])
|
||||||
@@ -496,22 +461,6 @@ AC_ARG_WITH([supermin-packager-config],
|
|||||||
AC_MSG_RESULT([$SUPERMIN_PACKAGER_CONFIG"])],
|
AC_MSG_RESULT([$SUPERMIN_PACKAGER_CONFIG"])],
|
||||||
[AC_MSG_RESULT([not set])])
|
[AC_MSG_RESULT([not set])])
|
||||||
|
|
||||||
AC_MSG_CHECKING([for --with-febootstrap-yum-config option (deprecated)])
|
|
||||||
AC_ARG_WITH([febootstrap-yum-config],
|
|
||||||
[AS_HELP_STRING([--with-febootstrap-yum-config=FILE],
|
|
||||||
[pass supermin --packager-config option @<:@default=no@:>@])],
|
|
||||||
[SUPERMIN_PACKAGER_CONFIG="$withval"
|
|
||||||
AC_MSG_RESULT([$SUPERMIN_PACKAGER_CONFIG"])],
|
|
||||||
[AC_MSG_RESULT([not set])])
|
|
||||||
|
|
||||||
AC_MSG_CHECKING([for --with-febootstrap-packager-config option (deprecated)])
|
|
||||||
AC_ARG_WITH([febootstrap-packager-config],
|
|
||||||
[AS_HELP_STRING([--with-febootstrap-packager-config=FILE],
|
|
||||||
[pass supermin --packager-config option @<:@default=no@:>@])],
|
|
||||||
[SUPERMIN_PACKAGER_CONFIG="$withval"
|
|
||||||
AC_MSG_RESULT([$SUPERMIN_PACKAGER_CONFIG"])],
|
|
||||||
[AC_MSG_RESULT([not set])])
|
|
||||||
|
|
||||||
AC_SUBST([SUPERMIN_PACKAGER_CONFIG])
|
AC_SUBST([SUPERMIN_PACKAGER_CONFIG])
|
||||||
|
|
||||||
dnl Pass additional supermin options.
|
dnl Pass additional supermin options.
|
||||||
@@ -528,21 +477,35 @@ AC_ARG_WITH([supermin-extra-options],
|
|||||||
AC_SUBST([SUPERMIN_EXTRA_OPTIONS])
|
AC_SUBST([SUPERMIN_EXTRA_OPTIONS])
|
||||||
|
|
||||||
if test "x$ENABLE_APPLIANCE" = "xyes"; then
|
if test "x$ENABLE_APPLIANCE" = "xyes"; then
|
||||||
|
supermin_major_min=5
|
||||||
|
supermin_minor_min=1
|
||||||
|
supermin_min=$supermin_major_min.$supermin_minor_min
|
||||||
|
|
||||||
test "x$SUPERMIN" = "xno" &&
|
test "x$SUPERMIN" = "xno" &&
|
||||||
AC_MSG_ERROR([supermin (formerly called febootstrap) must be installed])
|
AC_MSG_ERROR([supermin >= $supermin_min must be installed])
|
||||||
dnl febootstrap 2.x did not support the --version parameter
|
|
||||||
|
AC_MSG_CHECKING([supermin is new enough])
|
||||||
$SUPERMIN --version >&AS_MESSAGE_LOG_FD 2>&1 ||
|
$SUPERMIN --version >&AS_MESSAGE_LOG_FD 2>&1 ||
|
||||||
AC_MSG_ERROR([supermin (formerly called febootstrap) >= 3.20 must be installed, your version is too old])
|
AC_MSG_ERROR([supermin >= $supermin_min must be installed, your version is too old])
|
||||||
|
supermin_major="`$SUPERMIN --version | awk '{print $2}' | awk -F. '{print $1}'`"
|
||||||
|
supermin_minor="`$SUPERMIN --version | awk '{print $2}' | awk -F. '{print $2}'`"
|
||||||
|
AC_MSG_RESULT([$supermin_major.$supermin_minor])
|
||||||
|
|
||||||
|
if test "$supermin_major" -lt "$supermin_major_min" || \
|
||||||
|
( test "$supermin_major" -eq "$supermin_major_min" && test "$supermin_minor" -lt "$supermin_minor_min" ); then
|
||||||
|
AC_MSG_ERROR([supermin >= $supermin_min must be installed, your version is too old])
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
AC_DEFINE_UNQUOTED([SUPERMIN_HELPER],["$SUPERMIN_HELPER"],[Name of supermin-helper program.])
|
AC_DEFINE_UNQUOTED([SUPERMIN],["$SUPERMIN"],[Name of supermin program])
|
||||||
|
|
||||||
dnl Which distro?
|
dnl Which distro?
|
||||||
dnl
|
dnl
|
||||||
dnl This used to be Very Important but is now just used to select
|
dnl This used to be Very Important but is now just used to select
|
||||||
dnl which packages to install in the appliance, since the package
|
dnl which packages to install in the appliance, since the package
|
||||||
dnl names vary slightly across distros. (See
|
dnl names vary slightly across distros. (See
|
||||||
dnl appliance/packagelist.in and appliance/excludelist.in)
|
dnl appliance/packagelist.in, appliance/excludefiles.in,
|
||||||
|
dnl appliance/hostfiles.in)
|
||||||
AC_MSG_CHECKING([which Linux distro for package names])
|
AC_MSG_CHECKING([which Linux distro for package names])
|
||||||
DISTRO=REDHAT
|
DISTRO=REDHAT
|
||||||
if test -f /etc/debian_version; then
|
if test -f /etc/debian_version; then
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ L<libguestfs-make-fixed-appliance(1)>).
|
|||||||
|
|
||||||
In our testing we did not find that using a fixed appliance gave any
|
In our testing we did not find that using a fixed appliance gave any
|
||||||
measurable performance benefit, even when the appliance was located in
|
measurable performance benefit, even when the appliance was located in
|
||||||
memory (ie. on C</dev/shm>). However there are three points to
|
memory (ie. on C</dev/shm>). However there are two points to
|
||||||
consider:
|
consider:
|
||||||
|
|
||||||
=over 4
|
=over 4
|
||||||
@@ -166,14 +166,6 @@ times.
|
|||||||
|
|
||||||
=item 2.
|
=item 2.
|
||||||
|
|
||||||
By default libguestfs (or rather, L<supermin-helper(1)>)
|
|
||||||
searches over the root filesystem to find out if any host files have
|
|
||||||
changed and if it needs to rebuild the appliance. If these files are
|
|
||||||
not cached and the root filesystem is on an HDD, then this generates
|
|
||||||
lots of seeks. Using a fixed appliance avoids this.
|
|
||||||
|
|
||||||
=item 3.
|
|
||||||
|
|
||||||
The appliance is loaded on demand. A simple test such as:
|
The appliance is loaded on demand. A simple test such as:
|
||||||
|
|
||||||
time guestfish -a /dev/null run
|
time guestfish -a /dev/null run
|
||||||
@@ -567,7 +559,6 @@ bit.
|
|||||||
=head1 SEE ALSO
|
=head1 SEE ALSO
|
||||||
|
|
||||||
L<supermin(1)>,
|
L<supermin(1)>,
|
||||||
L<supermin-helper(1)>,
|
|
||||||
L<guestfish(1)>,
|
L<guestfish(1)>,
|
||||||
L<guestfs(3)>,
|
L<guestfs(3)>,
|
||||||
L<guestfs-examples(3)>,
|
L<guestfs-examples(3)>,
|
||||||
|
|||||||
@@ -1525,10 +1525,10 @@ the path of qemu/KVM.
|
|||||||
=item SUPERMIN_MODULES
|
=item SUPERMIN_MODULES
|
||||||
|
|
||||||
These two environment variables allow the kernel that libguestfs uses
|
These two environment variables allow the kernel that libguestfs uses
|
||||||
in the appliance to be selected. If C<$SUPERMIN_KERNEL> is not
|
in the appliance to be selected. If C<$SUPERMIN_KERNEL> is not set,
|
||||||
set, then the most recent host kernel is chosen. For more information
|
then the most recent host kernel is chosen. For more information
|
||||||
about kernel selection, see L<supermin-helper(1)>. This
|
about kernel selection, see L<supermin(1)>. This feature is only
|
||||||
feature is only available in supermin / febootstrap E<ge> 3.8.
|
available in supermin / febootstrap E<ge> 3.8.
|
||||||
|
|
||||||
=item TMPDIR
|
=item TMPDIR
|
||||||
|
|
||||||
@@ -1629,7 +1629,7 @@ L<virt-win-reg(1)>,
|
|||||||
L<libguestfs-tools.conf(5)>,
|
L<libguestfs-tools.conf(5)>,
|
||||||
L<display(1)>,
|
L<display(1)>,
|
||||||
L<hexedit(1)>,
|
L<hexedit(1)>,
|
||||||
L<supermin-helper(1)>.
|
L<supermin(1)>.
|
||||||
|
|
||||||
=head1 AUTHORS
|
=head1 AUTHORS
|
||||||
|
|
||||||
|
|||||||
693
src/appliance.c
693
src/appliance.c
@@ -52,18 +52,8 @@ static int dir_contains_files (const char *dir, ...);
|
|||||||
static int contains_old_style_appliance (guestfs_h *g, const char *path, void *data);
|
static int contains_old_style_appliance (guestfs_h *g, const char *path, void *data);
|
||||||
static int contains_fixed_appliance (guestfs_h *g, const char *path, void *data);
|
static int contains_fixed_appliance (guestfs_h *g, const char *path, void *data);
|
||||||
static int contains_supermin_appliance (guestfs_h *g, const char *path, void *data);
|
static int contains_supermin_appliance (guestfs_h *g, const char *path, void *data);
|
||||||
static char *calculate_supermin_checksum (guestfs_h *g, const char *supermin_path);
|
static int build_supermin_appliance (guestfs_h *g, const char *supermin_path, uid_t uid, char **kernel, char **dtb, char **initrd, char **appliance);
|
||||||
static int check_for_cached_appliance (guestfs_h *g, const char *supermin_path, const char *checksum, uid_t uid, char **kernel, char **dtb, char **initrd, char **appliance);
|
static int run_supermin_build (guestfs_h *g, const char *lockfile, const char *appliancedir, const char *supermin_path);
|
||||||
static int build_supermin_appliance (guestfs_h *g, const char *supermin_path, const char *checksum, uid_t uid, char **kernel, char **dtb, char **initrd, char **appliance);
|
|
||||||
static int hard_link_to_cached_appliance (guestfs_h *g, const char *cachedir, char **kernel, char **dtb, char **initrd, char **appliance);
|
|
||||||
static int run_supermin_helper (guestfs_h *g, const char *supermin_path, const char *cachedir);
|
|
||||||
|
|
||||||
/* RHBZ#790721: It makes no sense to have multiple threads racing to
|
|
||||||
* build the appliance from within a single process, and the code
|
|
||||||
* isn't safe for that anyway. Therefore put a thread lock around
|
|
||||||
* appliance building.
|
|
||||||
*/
|
|
||||||
gl_lock_define_initialized (static, building_lock);
|
|
||||||
|
|
||||||
/* Locate or build the appliance.
|
/* Locate or build the appliance.
|
||||||
*
|
*
|
||||||
@@ -83,60 +73,31 @@ gl_lock_define_initialized (static, building_lock);
|
|||||||
*
|
*
|
||||||
* (1) Look for the first element of g->path which contains a
|
* (1) Look for the first element of g->path which contains a
|
||||||
* supermin appliance skeleton. If no element has this, skip
|
* supermin appliance skeleton. If no element has this, skip
|
||||||
* straight to step (5).
|
* straight to step (3).
|
||||||
*
|
*
|
||||||
* (2) Calculate the checksum of this supermin appliance.
|
* (2) Call 'supermin --build' to build the full appliance (if it
|
||||||
|
* needs to be rebuilt). If this is successful, return the full
|
||||||
|
* appliance.
|
||||||
*
|
*
|
||||||
* (3) Check whether a cached appliance with the checksum calculated
|
* (3) Check each element of g->path, looking for a fixed appliance.
|
||||||
* in (2) exists and passes basic security checks. If so, return
|
|
||||||
* this appliance.
|
|
||||||
*
|
|
||||||
* (4) Try to build the supermin appliance. If this is successful,
|
|
||||||
* return it.
|
|
||||||
*
|
|
||||||
* (5) Check each element of g->path, looking for a fixed appliance.
|
|
||||||
* If one is found, return it.
|
* If one is found, return it.
|
||||||
*
|
*
|
||||||
* (6) Check each element of g->path, looking for an old-style appliance.
|
* (4) Check each element of g->path, looking for an old-style appliance.
|
||||||
* If one is found, return it.
|
* If one is found, return it.
|
||||||
*
|
*
|
||||||
* The supermin appliance cache directory lives in
|
* The supermin appliance cache directory lives in
|
||||||
* $TMPDIR/.guestfs-$UID/ and consists of up to five files:
|
* $TMPDIR/.guestfs-$UID/ and consists of up to five files:
|
||||||
*
|
*
|
||||||
* $TMPDIR/.guestfs-$UID/checksum - the checksum
|
* $TMPDIR/.guestfs-$UID/lock - the supermin lock file
|
||||||
* $TMPDIR/.guestfs-$UID/kernel - the kernel
|
* $TMPDIR/.guestfs-$UID/appliance.d/kernel - the kernel
|
||||||
* $TMPDIR/.guestfs-$UID/dtb - the device tree (on ARM)
|
* $TMPDIR/.guestfs-$UID/appliance.d/dtb - the device tree (on ARM)
|
||||||
* $TMPDIR/.guestfs-$UID/initrd - the supermin initrd
|
* $TMPDIR/.guestfs-$UID/appliance.d/initrd - the supermin initrd
|
||||||
* $TMPDIR/.guestfs-$UID/root - the appliance
|
* $TMPDIR/.guestfs-$UID/appliance.d/root - the appliance
|
||||||
*
|
*
|
||||||
* Since multiple instances of libguestfs with the same UID may be
|
* Multiple instances of libguestfs with the same UID may be racing to
|
||||||
* racing to create an appliance, we need to be careful when building
|
* create an appliance. However (since supermin >= 5) supermin
|
||||||
* and using the appliance.
|
* provides a --lock flag and atomic update of the appliance.d
|
||||||
*
|
* subdirectory.
|
||||||
* If a cached appliance with checksum exists (step (2) above) then we
|
|
||||||
* make a hard link to it with our current PID, so that we have a copy
|
|
||||||
* even if the appliance is replaced by another process building an
|
|
||||||
* appliance afterwards:
|
|
||||||
*
|
|
||||||
* $TMPDIR/.guestfs-$UID/kernel.$PID
|
|
||||||
* $TMPDIR/.guestfs-$UID/dtb.$PID
|
|
||||||
* $TMPDIR/.guestfs-$UID/initrd.$PID
|
|
||||||
* $TMPDIR/.guestfs-$UID/root.$PID
|
|
||||||
*
|
|
||||||
* A lock is taken on "checksum" while we perform the link.
|
|
||||||
*
|
|
||||||
* Linked files are deleted by a garbage collection sweep which can be
|
|
||||||
* initiated by any libguestfs process with the same UID when the
|
|
||||||
* corresponding PID no longer exists. (This is safe: the parent is
|
|
||||||
* always around in guestfs_launch() while qemu is starting up, and
|
|
||||||
* after that qemu will either have finished with the files or be
|
|
||||||
* holding them open, so we can unlink them).
|
|
||||||
*
|
|
||||||
* When building a new appliance (step (3)), it is built into randomly
|
|
||||||
* named temporary files in the $TMPDIR. Then a lock is acquired on
|
|
||||||
* $TMPDIR/.guestfs-$UID/checksum (this file being created if
|
|
||||||
* necessary), the files are renamed into their final location, and
|
|
||||||
* the lock is released.
|
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
guestfs___build_appliance (guestfs_h *g,
|
guestfs___build_appliance (guestfs_h *g,
|
||||||
@@ -145,14 +106,9 @@ guestfs___build_appliance (guestfs_h *g,
|
|||||||
char **initrd_rtn,
|
char **initrd_rtn,
|
||||||
char **appliance_rtn)
|
char **appliance_rtn)
|
||||||
{
|
{
|
||||||
int r;
|
|
||||||
char *kernel, *dtb, *initrd, *appliance;
|
char *kernel, *dtb, *initrd, *appliance;
|
||||||
|
|
||||||
gl_lock_lock (building_lock);
|
if (build_appliance (g, &kernel, &dtb, &initrd, &appliance) == -1)
|
||||||
r = build_appliance (g, &kernel, &dtb, &initrd, &appliance);
|
|
||||||
gl_lock_unlock (building_lock);
|
|
||||||
|
|
||||||
if (r == -1)
|
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
/* Don't assign these until we know we're going to succeed, to avoid
|
/* Don't assign these until we know we're going to succeed, to avoid
|
||||||
@@ -182,24 +138,12 @@ build_appliance (guestfs_h *g,
|
|||||||
if (r == -1)
|
if (r == -1)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (r == 1) {
|
if (r == 1)
|
||||||
/* Step (2): calculate checksum. */
|
/* Step (2): build supermin appliance. */
|
||||||
CLEANUP_FREE char *checksum =
|
return build_supermin_appliance (g, supermin_path, uid,
|
||||||
calculate_supermin_checksum (g, supermin_path);
|
|
||||||
if (checksum) {
|
|
||||||
/* Step (3): cached appliance exists? */
|
|
||||||
r = check_for_cached_appliance (g, supermin_path, checksum, uid,
|
|
||||||
kernel, dtb, initrd, appliance);
|
kernel, dtb, initrd, appliance);
|
||||||
if (r != 0)
|
|
||||||
return r == 1 ? 0 : -1;
|
|
||||||
|
|
||||||
/* Step (4): build supermin appliance. */
|
/* Step (3). */
|
||||||
return build_supermin_appliance (g, supermin_path, checksum, uid,
|
|
||||||
kernel, dtb, initrd, appliance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step (5). */
|
|
||||||
r = find_path (g, contains_fixed_appliance, NULL, &path);
|
r = find_path (g, contains_fixed_appliance, NULL, &path);
|
||||||
if (r == -1)
|
if (r == -1)
|
||||||
return -1;
|
return -1;
|
||||||
@@ -223,7 +167,7 @@ build_appliance (guestfs_h *g,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Step (6). */
|
/* Step (4). */
|
||||||
r = find_path (g, contains_old_style_appliance, NULL, &path);
|
r = find_path (g, contains_old_style_appliance, NULL, &path);
|
||||||
if (r == -1)
|
if (r == -1)
|
||||||
return -1;
|
return -1;
|
||||||
@@ -264,180 +208,6 @@ contains_supermin_appliance (guestfs_h *g, const char *path, void *data)
|
|||||||
return dir_contains_files (path, "supermin.d", NULL);
|
return dir_contains_files (path, "supermin.d", NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define MAX_CHECKSUM_LEN 256
|
|
||||||
|
|
||||||
static void
|
|
||||||
read_checksum (guestfs_h *g, void *checksumv, const char *line, size_t len)
|
|
||||||
{
|
|
||||||
char *checksum = checksumv;
|
|
||||||
|
|
||||||
if (len > MAX_CHECKSUM_LEN)
|
|
||||||
return;
|
|
||||||
strcpy (checksum, line);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
process_exists (int pid)
|
|
||||||
{
|
|
||||||
if (kill (pid, 0) == 0)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (errno == ESRCH)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Garbage collect appliance hard links. Files that match
|
|
||||||
* (kernel|dtb|initrd|root).$PID where the corresponding PID doesn't
|
|
||||||
* exist are deleted. Note that errors in this function don't matter.
|
|
||||||
* There may also be other libguestfs processes racing to do the same
|
|
||||||
* thing here.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
garbage_collect_appliances (const char *cachedir)
|
|
||||||
{
|
|
||||||
DIR *dir;
|
|
||||||
struct dirent *d;
|
|
||||||
int pid;
|
|
||||||
|
|
||||||
dir = opendir (cachedir);
|
|
||||||
if (dir == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
while ((d = readdir (dir)) != NULL) {
|
|
||||||
if (sscanf (d->d_name, "kernel.%d", &pid) == 1 &&
|
|
||||||
process_exists (pid) == 0)
|
|
||||||
unlinkat (dirfd (dir), d->d_name, 0);
|
|
||||||
else if (sscanf (d->d_name, "dtb.%d", &pid) == 1 &&
|
|
||||||
process_exists (pid) == 0)
|
|
||||||
unlinkat (dirfd (dir), d->d_name, 0);
|
|
||||||
else if (sscanf (d->d_name, "initrd.%d", &pid) == 1 &&
|
|
||||||
process_exists (pid) == 0)
|
|
||||||
unlinkat (dirfd (dir), d->d_name, 0);
|
|
||||||
else if (sscanf (d->d_name, "root.%d", &pid) == 1 &&
|
|
||||||
process_exists (pid) == 0)
|
|
||||||
unlinkat (dirfd (dir), d->d_name, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
closedir (dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
check_for_cached_appliance (guestfs_h *g,
|
|
||||||
const char *supermin_path, const char *checksum,
|
|
||||||
uid_t uid,
|
|
||||||
char **kernel, char **dtb,
|
|
||||||
char **initrd, char **appliance)
|
|
||||||
{
|
|
||||||
CLEANUP_FREE char *tmpdir = guestfs_get_cachedir (g);
|
|
||||||
|
|
||||||
/* len must be longer than the length of any pathname we can
|
|
||||||
* generate in this function.
|
|
||||||
*/
|
|
||||||
size_t len = strlen (tmpdir) + 128;
|
|
||||||
char cachedir[len];
|
|
||||||
snprintf (cachedir, len, "%s/.guestfs-%d", tmpdir, uid);
|
|
||||||
char filename[len];
|
|
||||||
snprintf (filename, len, "%s/checksum", cachedir);
|
|
||||||
|
|
||||||
ignore_value (mkdir (cachedir, 0755));
|
|
||||||
ignore_value (chmod (cachedir, 0755)); /* RHBZ#921292 */
|
|
||||||
|
|
||||||
/* See if the cache directory exists and passes some simple checks
|
|
||||||
* to make sure it has not been tampered with.
|
|
||||||
*/
|
|
||||||
struct stat statbuf;
|
|
||||||
if (lstat (cachedir, &statbuf) == -1)
|
|
||||||
return 0;
|
|
||||||
if (statbuf.st_uid != uid) {
|
|
||||||
error (g, _("security: cached appliance %s is not owned by UID %d"),
|
|
||||||
filename, uid);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (!S_ISDIR (statbuf.st_mode)) {
|
|
||||||
error (g, _("security: cached appliance %s is not a directory (mode %o)"),
|
|
||||||
filename, statbuf.st_mode);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if ((statbuf.st_mode & 0022) != 0) {
|
|
||||||
error (g, _("security: cached appliance %s is writable by group or other (mode %o)"),
|
|
||||||
cachedir, statbuf.st_mode);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
(void) utime (cachedir, NULL);
|
|
||||||
|
|
||||||
garbage_collect_appliances (cachedir);
|
|
||||||
|
|
||||||
/* Try to open and acquire a lock on the checksum file. */
|
|
||||||
int fd = open (filename, O_RDONLY|O_CLOEXEC);
|
|
||||||
if (fd == -1)
|
|
||||||
return 0;
|
|
||||||
#ifdef HAVE_FUTIMENS
|
|
||||||
(void) futimens (fd, NULL);
|
|
||||||
#else
|
|
||||||
(void) futimes (fd, NULL);
|
|
||||||
#endif
|
|
||||||
struct flock fl;
|
|
||||||
fl.l_type = F_RDLCK;
|
|
||||||
fl.l_whence = SEEK_SET;
|
|
||||||
fl.l_start = 0;
|
|
||||||
fl.l_len = 1;
|
|
||||||
again:
|
|
||||||
if (fcntl (fd, F_SETLKW, &fl) == -1) {
|
|
||||||
if (errno == EINTR)
|
|
||||||
goto again;
|
|
||||||
perrorf (g, "fcntl: F_SETLKW: %s", filename);
|
|
||||||
close (fd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Read the checksum file. */
|
|
||||||
size_t clen = strlen (checksum);
|
|
||||||
char checksum_on_disk[clen];
|
|
||||||
ssize_t rr = read (fd, checksum_on_disk, clen);
|
|
||||||
if (rr == -1) {
|
|
||||||
perrorf (g, "read: %s", filename);
|
|
||||||
close (fd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if ((size_t) rr != clen) {
|
|
||||||
close (fd);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (memcmp (checksum, checksum_on_disk, clen) != 0) {
|
|
||||||
close (fd);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* At this point, cachedir exists, and checksum matches, and we have
|
|
||||||
* a read lock on the checksum file. Make hard links to the files.
|
|
||||||
*/
|
|
||||||
if (hard_link_to_cached_appliance (g, cachedir,
|
|
||||||
kernel, dtb, initrd, appliance) == -1) {
|
|
||||||
close (fd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Releases the lock on checksum. */
|
|
||||||
if (close (fd) == -1) {
|
|
||||||
perrorf (g, "close");
|
|
||||||
/* Allocated in hard_link_to_cached_appliance above, must be
|
|
||||||
* freed along this error path.
|
|
||||||
*/
|
|
||||||
free (*kernel);
|
|
||||||
free (*dtb);
|
|
||||||
free (*initrd);
|
|
||||||
free (*appliance);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Exists! */
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Build supermin appliance from supermin_path to $TMPDIR/.guestfs-$UID.
|
/* Build supermin appliance from supermin_path to $TMPDIR/.guestfs-$UID.
|
||||||
*
|
*
|
||||||
* Returns:
|
* Returns:
|
||||||
@@ -446,222 +216,87 @@ check_for_cached_appliance (guestfs_h *g,
|
|||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
build_supermin_appliance (guestfs_h *g,
|
build_supermin_appliance (guestfs_h *g,
|
||||||
const char *supermin_path, const char *checksum,
|
const char *supermin_path,
|
||||||
uid_t uid,
|
uid_t uid,
|
||||||
char **kernel, char **dtb,
|
char **kernel, char **dtb,
|
||||||
char **initrd, char **appliance)
|
char **initrd, char **appliance)
|
||||||
{
|
{
|
||||||
CLEANUP_FREE char *tmpdir = guestfs_get_cachedir (g);
|
CLEANUP_FREE char *tmpdir = guestfs_get_cachedir (g);
|
||||||
|
struct stat statbuf;
|
||||||
size_t len;
|
size_t len;
|
||||||
|
|
||||||
if (g->verbose)
|
|
||||||
guestfs___print_timestamped_message (g, "begin building supermin appliance");
|
|
||||||
|
|
||||||
/* len must be longer than the length of any pathname we can
|
/* len must be longer than the length of any pathname we can
|
||||||
* generate in this function.
|
* generate in this function.
|
||||||
*/
|
*/
|
||||||
len = strlen (tmpdir) + 128;
|
len = strlen (tmpdir) + 128;
|
||||||
|
char cachedir[len];
|
||||||
|
snprintf (cachedir, len, "%s/.guestfs-%d", tmpdir, uid);
|
||||||
|
char lockfile[len];
|
||||||
|
snprintf (lockfile, len, "%s/lock", cachedir);
|
||||||
|
char appliancedir[len];
|
||||||
|
snprintf (appliancedir, len, "%s/appliance.d", cachedir);
|
||||||
|
|
||||||
/* Build the appliance into a temporary directory. */
|
ignore_value (mkdir (cachedir, 0755));
|
||||||
char tmpcd[len];
|
ignore_value (chmod (cachedir, 0755)); /* RHBZ#921292 */
|
||||||
snprintf (tmpcd, len, "%s/guestfs.XXXXXX", tmpdir);
|
|
||||||
|
|
||||||
if (mkdtemp (tmpcd) == NULL) {
|
/* See if the cache directory exists and passes some simple checks
|
||||||
perrorf (g, "mkdtemp");
|
* to make sure it has not been tampered with.
|
||||||
|
*/
|
||||||
|
if (lstat (cachedir, &statbuf) == -1)
|
||||||
|
return 0;
|
||||||
|
if (statbuf.st_uid != uid) {
|
||||||
|
error (g, _("security: cached appliance %s is not owned by UID %d"),
|
||||||
|
cachedir, uid);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!S_ISDIR (statbuf.st_mode)) {
|
||||||
|
error (g, _("security: cached appliance %s is not a directory (mode %o)"),
|
||||||
|
cachedir, statbuf.st_mode);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if ((statbuf.st_mode & 0022) != 0) {
|
||||||
|
error (g, _("security: cached appliance %s is writable by group or other (mode %o)"),
|
||||||
|
cachedir, statbuf.st_mode);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(void) utimes (cachedir, NULL);
|
||||||
if (g->verbose)
|
if (g->verbose)
|
||||||
guestfs___print_timestamped_message (g, "run supermin-helper");
|
guestfs___print_timestamped_message (g, "begin building supermin appliance");
|
||||||
|
|
||||||
int r = run_supermin_helper (g, supermin_path, tmpcd);
|
/* Build the appliance if it needs to be built. */
|
||||||
if (r == -1) {
|
if (g->verbose)
|
||||||
guestfs___recursive_remove_dir (g, tmpcd);
|
guestfs___print_timestamped_message (g, "run supermin");
|
||||||
|
|
||||||
|
if (run_supermin_build (g, lockfile, appliancedir, supermin_path) == -1)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
|
||||||
|
|
||||||
if (g->verbose)
|
if (g->verbose)
|
||||||
guestfs___print_timestamped_message (g, "finished building supermin appliance");
|
guestfs___print_timestamped_message (g, "finished building supermin appliance");
|
||||||
|
|
||||||
char cachedir[len];
|
/* Return the appliance filenames. */
|
||||||
snprintf (cachedir, len, "%s/.guestfs-%d", tmpdir, uid);
|
|
||||||
char filename[len];
|
|
||||||
char filename2[len];
|
|
||||||
snprintf (filename, len, "%s/checksum", cachedir);
|
|
||||||
|
|
||||||
/* Open and acquire write lock on checksum file. The file might
|
|
||||||
* not exist, in which case we want to create it.
|
|
||||||
*/
|
|
||||||
int fd = open (filename, O_WRONLY|O_CREAT|O_NOCTTY|O_CLOEXEC, 0644);
|
|
||||||
if (fd == -1) {
|
|
||||||
perrorf (g, "open: %s", filename);
|
|
||||||
guestfs___recursive_remove_dir (g, tmpcd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
struct flock fl;
|
|
||||||
fl.l_type = F_WRLCK;
|
|
||||||
fl.l_whence = SEEK_SET;
|
|
||||||
fl.l_start = 0;
|
|
||||||
fl.l_len = 1;
|
|
||||||
again:
|
|
||||||
if (fcntl (fd, F_SETLKW, &fl) == -1) {
|
|
||||||
if (errno == EINTR)
|
|
||||||
goto again;
|
|
||||||
perrorf (g, "fcntl: F_SETLKW: %s", filename);
|
|
||||||
close (fd);
|
|
||||||
guestfs___recursive_remove_dir (g, tmpcd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* At this point we have acquired a write lock on the checksum
|
|
||||||
* file so we go ahead and replace it with the new checksum, and
|
|
||||||
* rename in appliance files into this directory.
|
|
||||||
*/
|
|
||||||
size_t clen = strlen (checksum);
|
|
||||||
if (ftruncate (fd, clen) == -1) {
|
|
||||||
perrorf (g, "ftruncate: %s", filename);
|
|
||||||
close (fd);
|
|
||||||
guestfs___recursive_remove_dir (g, tmpcd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t rr = write (fd, checksum, clen);
|
|
||||||
if (rr == -1) {
|
|
||||||
perrorf (g, "write: %s", filename);
|
|
||||||
close (fd);
|
|
||||||
guestfs___recursive_remove_dir (g, tmpcd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if ((size_t) rr != clen) {
|
|
||||||
error (g, "partial write: %s", filename);
|
|
||||||
close (fd);
|
|
||||||
guestfs___recursive_remove_dir (g, tmpcd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
snprintf (filename, len, "%s/kernel", tmpcd);
|
|
||||||
snprintf (filename2, len, "%s/kernel", cachedir);
|
|
||||||
unlink (filename2);
|
|
||||||
if (rename (filename, filename2) == -1) {
|
|
||||||
perrorf (g, "rename: %s %s", filename, filename2);
|
|
||||||
close (fd);
|
|
||||||
guestfs___recursive_remove_dir (g, tmpcd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef DTB_WILDCARD
|
|
||||||
snprintf (filename, len, "%s/dtb", tmpcd);
|
|
||||||
snprintf (filename2, len, "%s/dtb", cachedir);
|
|
||||||
unlink (filename2);
|
|
||||||
if (rename (filename, filename2) == -1) {
|
|
||||||
perrorf (g, "rename: %s %s", filename, filename2);
|
|
||||||
close (fd);
|
|
||||||
guestfs___recursive_remove_dir (g, tmpcd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
snprintf (filename, len, "%s/initrd", tmpcd);
|
|
||||||
snprintf (filename2, len, "%s/initrd", cachedir);
|
|
||||||
unlink (filename2);
|
|
||||||
if (rename (filename, filename2) == -1) {
|
|
||||||
perrorf (g, "rename: %s %s", filename, filename2);
|
|
||||||
close (fd);
|
|
||||||
guestfs___recursive_remove_dir (g, tmpcd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
snprintf (filename, len, "%s/root", tmpcd);
|
|
||||||
snprintf (filename2, len, "%s/root", cachedir);
|
|
||||||
unlink (filename2);
|
|
||||||
if (rename (filename, filename2) == -1) {
|
|
||||||
perrorf (g, "rename: %s %s", filename, filename2);
|
|
||||||
close (fd);
|
|
||||||
guestfs___recursive_remove_dir (g, tmpcd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
guestfs___recursive_remove_dir (g, tmpcd);
|
|
||||||
|
|
||||||
/* Now finish off by linking to the cached appliance and returning it. */
|
|
||||||
if (hard_link_to_cached_appliance (g, cachedir,
|
|
||||||
kernel, dtb, initrd, appliance) == -1) {
|
|
||||||
close (fd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Releases the lock on checksum. */
|
|
||||||
if (close (fd) == -1) {
|
|
||||||
perrorf (g, "close");
|
|
||||||
/* Allocated in hard_link_to_cached_appliance above, must be
|
|
||||||
* freed along this error path.
|
|
||||||
*/
|
|
||||||
free (*kernel);
|
|
||||||
free (*dtb);
|
|
||||||
free (*initrd);
|
|
||||||
free (*appliance);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* NB: lock on checksum file must be held when this is called. */
|
|
||||||
static int
|
|
||||||
hard_link_to_cached_appliance (guestfs_h *g,
|
|
||||||
const char *cachedir,
|
|
||||||
char **kernel, char **dtb,
|
|
||||||
char **initrd, char **appliance)
|
|
||||||
{
|
|
||||||
pid_t pid = getpid ();
|
|
||||||
size_t len = strlen (cachedir) + 32;
|
|
||||||
|
|
||||||
*kernel = safe_malloc (g, len);
|
*kernel = safe_malloc (g, len);
|
||||||
|
#ifdef DTB_WILDCARD
|
||||||
*dtb = safe_malloc (g, len);
|
*dtb = safe_malloc (g, len);
|
||||||
|
#else
|
||||||
|
*dtb = NULL;
|
||||||
|
#endif
|
||||||
*initrd = safe_malloc (g, len);
|
*initrd = safe_malloc (g, len);
|
||||||
*appliance = safe_malloc (g, len);
|
*appliance = safe_malloc (g, len);
|
||||||
snprintf (*kernel, len, "%s/kernel.%d", cachedir, pid);
|
snprintf (*kernel, len, "%s/kernel", appliancedir);
|
||||||
snprintf (*dtb, len, "%s/dtb.%d", cachedir, pid);
|
#ifdef DTB_WILDCARD
|
||||||
snprintf (*initrd, len, "%s/initrd.%d", cachedir, pid);
|
snprintf (*dtb, len, "%s/dtb", appliancedir);
|
||||||
snprintf (*appliance, len, "%s/root.%d", cachedir, pid);
|
#endif
|
||||||
|
snprintf (*initrd, len, "%s/initrd", appliancedir);
|
||||||
|
snprintf (*appliance, len, "%s/root", appliancedir);
|
||||||
|
|
||||||
char filename[len];
|
/* Touch the files so they don't get deleted (as they are in /var/tmp). */
|
||||||
snprintf (filename, len, "%s/kernel", cachedir);
|
(void) utimes (*kernel, NULL);
|
||||||
(void) unlink (*kernel);
|
#ifdef DTB_WILDCARD
|
||||||
if (link (filename, *kernel) == -1) {
|
(void) utimes (*dtb, NULL);
|
||||||
perrorf (g, "link: %s %s", filename, *kernel);
|
#endif
|
||||||
goto error;
|
(void) utimes (*initrd, NULL);
|
||||||
}
|
|
||||||
(void) utimes (filename, NULL);
|
|
||||||
|
|
||||||
snprintf (filename, len, "%s/dtb", cachedir);
|
|
||||||
(void) unlink (*dtb);
|
|
||||||
if (link (filename, *dtb) == -1) {
|
|
||||||
if (errno == ENOENT) {
|
|
||||||
/* dtb doesn't exist -- this is OK */
|
|
||||||
free (*dtb);
|
|
||||||
*dtb = NULL;
|
|
||||||
} else {
|
|
||||||
perrorf (g, "link: %s %s", filename, *kernel);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(void) utimes (filename, NULL);
|
|
||||||
|
|
||||||
snprintf (filename, len, "%s/initrd", cachedir);
|
|
||||||
(void) unlink (*initrd);
|
|
||||||
if (link (filename, *initrd) == -1) {
|
|
||||||
perrorf (g, "link: %s %s", filename, *initrd);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
(void) utime (filename, NULL);
|
|
||||||
|
|
||||||
snprintf (filename, len, "%s/root", cachedir);
|
|
||||||
(void) unlink (*appliance);
|
|
||||||
if (link (filename, *appliance) == -1) {
|
|
||||||
perrorf (g, "link: %s %s", filename, *appliance);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
/* Checking backend != "uml" is a big hack. UML encodes the mtime
|
/* Checking backend != "uml" is a big hack. UML encodes the mtime
|
||||||
* of the original backing file (in this case, the appliance) in the
|
* of the original backing file (in this case, the appliance) in the
|
||||||
* COW file, and checks it when adding it to the VM. If there are
|
* COW file, and checks it when adding it to the VM. If there are
|
||||||
@@ -675,211 +310,69 @@ hard_link_to_cached_appliance (guestfs_h *g,
|
|||||||
* XXX
|
* XXX
|
||||||
*/
|
*/
|
||||||
if (STRNEQ (g->backend, "uml"))
|
if (STRNEQ (g->backend, "uml"))
|
||||||
(void) utime (filename, NULL);
|
(void) utimes (*appliance, NULL);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
error:
|
|
||||||
free (*kernel);
|
|
||||||
free (*dtb);
|
|
||||||
free (*initrd);
|
|
||||||
free (*appliance);
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Run supermin-helper and tell it to generate the
|
/* Run supermin --build and tell it to generate the
|
||||||
* appliance.
|
* appliance.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifdef SUPERMIN_HELPER_NEW_STYLE_SYNTAX
|
|
||||||
|
|
||||||
/* supermin_path is a path which is known to contain a supermin
|
|
||||||
* appliance. Using supermin-helper -f checksum calculate
|
|
||||||
* the checksum so we can see if it is cached.
|
|
||||||
*/
|
|
||||||
static char *
|
|
||||||
calculate_supermin_checksum (guestfs_h *g, const char *supermin_path)
|
|
||||||
{
|
|
||||||
size_t len;
|
|
||||||
CLEANUP_CMD_CLOSE struct command *cmd = guestfs___new_command (g);
|
|
||||||
int pass_u_g_args = getuid () != geteuid () || getgid () != getegid ();
|
|
||||||
char checksum[MAX_CHECKSUM_LEN + 1] = { 0 };
|
|
||||||
|
|
||||||
guestfs___cmd_add_arg (cmd, SUPERMIN_HELPER);
|
|
||||||
if (g->verbose)
|
|
||||||
guestfs___cmd_add_arg (cmd, "--verbose");
|
|
||||||
if (pass_u_g_args) {
|
|
||||||
guestfs___cmd_add_arg (cmd, "-u");
|
|
||||||
guestfs___cmd_add_arg_format (cmd, "%d", geteuid ());
|
|
||||||
guestfs___cmd_add_arg (cmd, "-g");
|
|
||||||
guestfs___cmd_add_arg_format (cmd, "%d", getegid ());
|
|
||||||
}
|
|
||||||
guestfs___cmd_add_arg (cmd, "-f");
|
|
||||||
guestfs___cmd_add_arg (cmd, "checksum");
|
|
||||||
guestfs___cmd_add_arg (cmd, "--host-cpu");
|
|
||||||
guestfs___cmd_add_arg (cmd, host_cpu);
|
|
||||||
guestfs___cmd_add_arg_format (cmd, "%s/supermin.d", supermin_path);
|
|
||||||
guestfs___cmd_set_stdout_callback (cmd, read_checksum, checksum, 0);
|
|
||||||
|
|
||||||
/* Errors here are non-fatal, so we don't need to call error(). */
|
|
||||||
if (guestfs___cmd_run (cmd) == -1)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
debug (g, "checksum of existing appliance: %s", checksum);
|
|
||||||
|
|
||||||
len = strlen (checksum);
|
|
||||||
if (len < 16) { /* sanity check */
|
|
||||||
warning (g, "supermin-helper -f checksum returned a short string");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return safe_strndup (g, checksum, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
run_supermin_helper (guestfs_h *g, const char *supermin_path,
|
run_supermin_build (guestfs_h *g,
|
||||||
const char *cachedir)
|
const char *lockfile,
|
||||||
|
const char *appliancedir,
|
||||||
|
const char *supermin_path)
|
||||||
{
|
{
|
||||||
CLEANUP_CMD_CLOSE struct command *cmd = guestfs___new_command (g);
|
CLEANUP_CMD_CLOSE struct command *cmd = guestfs___new_command (g);
|
||||||
int r;
|
int r;
|
||||||
|
#if 0 /* not supported in supermin 5 yet XXX */
|
||||||
uid_t uid = getuid ();
|
uid_t uid = getuid ();
|
||||||
uid_t euid = geteuid ();
|
uid_t euid = geteuid ();
|
||||||
gid_t gid = getgid ();
|
gid_t gid = getgid ();
|
||||||
gid_t egid = getegid ();
|
gid_t egid = getegid ();
|
||||||
int pass_u_g_args = uid != euid || gid != egid;
|
int pass_u_g_args = uid != euid || gid != egid;
|
||||||
|
#endif
|
||||||
|
|
||||||
guestfs___cmd_add_arg (cmd, SUPERMIN_HELPER);
|
guestfs___cmd_add_arg (cmd, SUPERMIN);
|
||||||
|
guestfs___cmd_add_arg (cmd, "--build");
|
||||||
if (g->verbose)
|
if (g->verbose)
|
||||||
guestfs___cmd_add_arg (cmd, "--verbose");
|
guestfs___cmd_add_arg (cmd, "--verbose");
|
||||||
|
#if 0
|
||||||
if (pass_u_g_args) {
|
if (pass_u_g_args) {
|
||||||
guestfs___cmd_add_arg (cmd, "-u");
|
guestfs___cmd_add_arg (cmd, "-u");
|
||||||
guestfs___cmd_add_arg_format (cmd, "%d", euid);
|
guestfs___cmd_add_arg_format (cmd, "%d", euid);
|
||||||
guestfs___cmd_add_arg (cmd, "-g");
|
guestfs___cmd_add_arg (cmd, "-g");
|
||||||
guestfs___cmd_add_arg_format (cmd, "%d", egid);
|
guestfs___cmd_add_arg_format (cmd, "%d", egid);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
guestfs___cmd_add_arg (cmd, "--copy-kernel");
|
guestfs___cmd_add_arg (cmd, "--copy-kernel");
|
||||||
guestfs___cmd_add_arg (cmd, "-f");
|
guestfs___cmd_add_arg (cmd, "-f");
|
||||||
guestfs___cmd_add_arg (cmd, "ext2");
|
guestfs___cmd_add_arg (cmd, "ext2");
|
||||||
guestfs___cmd_add_arg (cmd, "--host-cpu");
|
guestfs___cmd_add_arg (cmd, "--host-cpu");
|
||||||
guestfs___cmd_add_arg (cmd, host_cpu);
|
guestfs___cmd_add_arg (cmd, host_cpu);
|
||||||
|
guestfs___cmd_add_arg (cmd, "--if-newer");
|
||||||
|
guestfs___cmd_add_arg (cmd, "--lock");
|
||||||
|
guestfs___cmd_add_arg (cmd, lockfile);
|
||||||
#ifdef DTB_WILDCARD
|
#ifdef DTB_WILDCARD
|
||||||
guestfs___cmd_add_arg (cmd, "--dtb");
|
guestfs___cmd_add_arg (cmd, "--dtb");
|
||||||
guestfs___cmd_add_arg (cmd, DTB_WILDCARD);
|
guestfs___cmd_add_arg (cmd, DTB_WILDCARD);
|
||||||
#endif
|
#endif
|
||||||
guestfs___cmd_add_arg_format (cmd, "%s/supermin.d", supermin_path);
|
guestfs___cmd_add_arg_format (cmd, "%s/supermin.d", supermin_path);
|
||||||
guestfs___cmd_add_arg (cmd, "--output-kernel");
|
guestfs___cmd_add_arg (cmd, "-o");
|
||||||
guestfs___cmd_add_arg_format (cmd, "%s/kernel", cachedir);
|
guestfs___cmd_add_arg (cmd, appliancedir);
|
||||||
#ifdef DTB_WILDCARD
|
|
||||||
guestfs___cmd_add_arg (cmd, "--output-dtb");
|
|
||||||
guestfs___cmd_add_arg_format (cmd, "%s/dtb", cachedir);
|
|
||||||
#endif
|
|
||||||
guestfs___cmd_add_arg (cmd, "--output-initrd");
|
|
||||||
guestfs___cmd_add_arg_format (cmd, "%s/initrd", cachedir);
|
|
||||||
guestfs___cmd_add_arg (cmd, "--output-appliance");
|
|
||||||
guestfs___cmd_add_arg_format (cmd, "%s/root", cachedir);
|
|
||||||
|
|
||||||
r = guestfs___cmd_run (cmd);
|
r = guestfs___cmd_run (cmd);
|
||||||
if (r == -1)
|
if (r == -1)
|
||||||
return -1;
|
return -1;
|
||||||
if (!WIFEXITED (r) || WEXITSTATUS (r) != 0) {
|
if (!WIFEXITED (r) || WEXITSTATUS (r) != 0) {
|
||||||
guestfs___external_command_failed (g, r, SUPERMIN_HELPER, NULL);
|
guestfs___external_command_failed (g, r, SUPERMIN, NULL);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#else /* ! SUPERMIN_HELPER_NEW_STYLE_SYNTAX */
|
|
||||||
|
|
||||||
#ifdef DTB_WILDCARD
|
|
||||||
#error "This architecture has device trees, so requires supermin-helper >= 4.1.5"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* supermin_path is a path which is known to contain a supermin
|
|
||||||
* appliance. Using supermin-helper -f checksum calculate
|
|
||||||
* the checksum so we can see if it is cached.
|
|
||||||
*/
|
|
||||||
static char *
|
|
||||||
calculate_supermin_checksum (guestfs_h *g, const char *supermin_path)
|
|
||||||
{
|
|
||||||
size_t len;
|
|
||||||
CLEANUP_CMD_CLOSE struct command *cmd = guestfs___new_command (g);
|
|
||||||
int pass_u_g_args = getuid () != geteuid () || getgid () != getegid ();
|
|
||||||
char checksum[MAX_CHECKSUM_LEN + 1] = { 0 };
|
|
||||||
|
|
||||||
guestfs___cmd_add_arg (cmd, SUPERMIN_HELPER);
|
|
||||||
if (g->verbose)
|
|
||||||
guestfs___cmd_add_arg (cmd, "--verbose");
|
|
||||||
if (pass_u_g_args) {
|
|
||||||
guestfs___cmd_add_arg (cmd, "-u");
|
|
||||||
guestfs___cmd_add_arg_format (cmd, "%d", geteuid ());
|
|
||||||
guestfs___cmd_add_arg (cmd, "-g");
|
|
||||||
guestfs___cmd_add_arg_format (cmd, "%d", getegid ());
|
|
||||||
}
|
|
||||||
guestfs___cmd_add_arg (cmd, "-f");
|
|
||||||
guestfs___cmd_add_arg (cmd, "checksum");
|
|
||||||
guestfs___cmd_add_arg_format (cmd, "%s/supermin.d", supermin_path);
|
|
||||||
guestfs___cmd_add_arg (cmd, host_cpu);
|
|
||||||
guestfs___cmd_set_stdout_callback (cmd, read_checksum, checksum, 0);
|
|
||||||
|
|
||||||
/* Errors here are non-fatal, so we don't need to call error(). */
|
|
||||||
if (guestfs___cmd_run (cmd) == -1)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
debug (g, "checksum of existing appliance: %s", checksum);
|
|
||||||
|
|
||||||
len = strlen (checksum);
|
|
||||||
if (len < 16) { /* sanity check */
|
|
||||||
warning (g, "supermin-helper -f checksum returned a short string");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return safe_strndup (g, checksum, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
run_supermin_helper (guestfs_h *g, const char *supermin_path,
|
|
||||||
const char *cachedir)
|
|
||||||
{
|
|
||||||
CLEANUP_CMD_CLOSE struct command *cmd = guestfs___new_command (g);
|
|
||||||
int r;
|
|
||||||
uid_t uid = getuid ();
|
|
||||||
uid_t euid = geteuid ();
|
|
||||||
gid_t gid = getgid ();
|
|
||||||
gid_t egid = getegid ();
|
|
||||||
int pass_u_g_args = uid != euid || gid != egid;
|
|
||||||
|
|
||||||
guestfs___cmd_add_arg (cmd, SUPERMIN_HELPER);
|
|
||||||
if (g->verbose)
|
|
||||||
guestfs___cmd_add_arg (cmd, "--verbose");
|
|
||||||
if (pass_u_g_args) {
|
|
||||||
guestfs___cmd_add_arg (cmd, "-u");
|
|
||||||
guestfs___cmd_add_arg_format (cmd, "%d", euid);
|
|
||||||
guestfs___cmd_add_arg (cmd, "-g");
|
|
||||||
guestfs___cmd_add_arg_format (cmd, "%d", egid);
|
|
||||||
}
|
|
||||||
guestfs___cmd_add_arg (cmd, "--copy-kernel");
|
|
||||||
guestfs___cmd_add_arg (cmd, "-f");
|
|
||||||
guestfs___cmd_add_arg (cmd, "ext2");
|
|
||||||
guestfs___cmd_add_arg_format (cmd, "%s/supermin.d", supermin_path);
|
|
||||||
guestfs___cmd_add_arg (cmd, host_cpu);
|
|
||||||
guestfs___cmd_add_arg_format (cmd, "%s/kernel", cachedir);
|
|
||||||
guestfs___cmd_add_arg_format (cmd, "%s/initrd", cachedir);
|
|
||||||
guestfs___cmd_add_arg_format (cmd, "%s/root", cachedir);
|
|
||||||
|
|
||||||
r = guestfs___cmd_run (cmd);
|
|
||||||
if (r == -1)
|
|
||||||
return -1;
|
|
||||||
if (!WIFEXITED (r) || WEXITSTATUS (r) != 0) {
|
|
||||||
guestfs___external_command_failed (g, r, SUPERMIN_HELPER, NULL);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* ! SUPERMIN_HELPER_NEW_STYLE_SYNTAX */
|
|
||||||
|
|
||||||
/* Search elements of g->path, returning the first path element which
|
/* Search elements of g->path, returning the first path element which
|
||||||
* matches the predicate function 'pred'.
|
* matches the predicate function 'pred'.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -3477,15 +3477,14 @@ debugging (set the environment variable C<LIBGUESTFS_DEBUG=1>).
|
|||||||
|
|
||||||
=item Create the appliance
|
=item Create the appliance
|
||||||
|
|
||||||
C<supermin-helper> is invoked to create the kernel, a
|
C<supermin --build> is invoked to create the kernel, a small initrd
|
||||||
small initrd and the appliance.
|
and the appliance.
|
||||||
|
|
||||||
The appliance is cached in C</var/tmp/.guestfs-E<lt>UIDE<gt>> (or in
|
The appliance is cached in C</var/tmp/.guestfs-E<lt>UIDE<gt>> (or in
|
||||||
another directory if C<LIBGUESTFS_CACHEDIR> or C<TMPDIR> are set).
|
another directory if C<LIBGUESTFS_CACHEDIR> or C<TMPDIR> are set).
|
||||||
|
|
||||||
For a complete description of how the appliance is created and cached,
|
For a complete description of how the appliance is created and cached,
|
||||||
read the L<supermin(1)> and L<supermin-helper(1)> man
|
read the L<supermin(1)> man page.
|
||||||
pages.
|
|
||||||
|
|
||||||
=item Start qemu and boot the kernel
|
=item Start qemu and boot the kernel
|
||||||
|
|
||||||
@@ -3493,12 +3492,12 @@ qemu is invoked to boot the kernel.
|
|||||||
|
|
||||||
=item Run the initrd
|
=item Run the initrd
|
||||||
|
|
||||||
C<supermin-helper> builds a small initrd. The initrd is
|
C<supermin --build> builds a small initrd. The initrd is not the
|
||||||
not the appliance. The purpose of the initrd is to load enough kernel
|
appliance. The purpose of the initrd is to load enough kernel modules
|
||||||
modules in order that the appliance itself can be mounted and started.
|
in order that the appliance itself can be mounted and started.
|
||||||
|
|
||||||
The initrd is a cpio archive called
|
The initrd is a cpio archive called
|
||||||
C</var/tmp/.guestfs-E<lt>UIDE<gt>/initrd>.
|
C</var/tmp/.guestfs-E<lt>UIDE<gt>/appliance.d/initrd>.
|
||||||
|
|
||||||
When the initrd has started you will see messages showing that kernel
|
When the initrd has started you will see messages showing that kernel
|
||||||
modules are being loaded, similar to this:
|
modules are being loaded, similar to this:
|
||||||
@@ -3512,7 +3511,8 @@ modules are being loaded, similar to this:
|
|||||||
|
|
||||||
The appliance is a sparse file containing an ext2 filesystem which
|
The appliance is a sparse file containing an ext2 filesystem which
|
||||||
contains a familiar (although reduced in size) Linux operating system.
|
contains a familiar (although reduced in size) Linux operating system.
|
||||||
It would normally be called C</var/tmp/.guestfs-E<lt>UIDE<gt>/root>.
|
It would normally be called
|
||||||
|
C</var/tmp/.guestfs-E<lt>UIDE<gt>/appliance.d/root>.
|
||||||
|
|
||||||
The regular disks being inspected by libguestfs are the first
|
The regular disks being inspected by libguestfs are the first
|
||||||
devices exposed by qemu (eg. as C</dev/vda>).
|
devices exposed by qemu (eg. as C</dev/vda>).
|
||||||
@@ -4706,7 +4706,7 @@ environment which tends to break everything.
|
|||||||
These two environment variables allow the kernel that libguestfs uses
|
These two environment variables allow the kernel that libguestfs uses
|
||||||
in the appliance to be selected. If C<$SUPERMIN_KERNEL> is not
|
in the appliance to be selected. If C<$SUPERMIN_KERNEL> is not
|
||||||
set, then the most recent host kernel is chosen. For more information
|
set, then the most recent host kernel is chosen. For more information
|
||||||
about kernel selection, see L<supermin-helper(1)>. This
|
about kernel selection, see L<supermin(1)>. This
|
||||||
feature is only available in supermin / febootstrap E<ge> 3.8.
|
feature is only available in supermin / febootstrap E<ge> 3.8.
|
||||||
|
|
||||||
=item TMPDIR
|
=item TMPDIR
|
||||||
@@ -4758,7 +4758,6 @@ L<guestfs-testing(1)>,
|
|||||||
L<libguestfs-test-tool(1)>,
|
L<libguestfs-test-tool(1)>,
|
||||||
L<libguestfs-make-fixed-appliance(1)>,
|
L<libguestfs-make-fixed-appliance(1)>,
|
||||||
L<supermin(1)>,
|
L<supermin(1)>,
|
||||||
L<supermin-helper(1)>,
|
|
||||||
L<qemu(1)>,
|
L<qemu(1)>,
|
||||||
L<hivex(3)>,
|
L<hivex(3)>,
|
||||||
L<stap(1)>,
|
L<stap(1)>,
|
||||||
|
|||||||
@@ -87,8 +87,7 @@ variables C<SUPERMIN_KERNEL> and/or C<SUPERMIN_MODULES>
|
|||||||
(C<FEBOOTSTRAP_KERNEL> and C<FEBOOTSTRAP_MODULES> if still using the
|
(C<FEBOOTSTRAP_KERNEL> and C<FEBOOTSTRAP_MODULES> if still using the
|
||||||
old febootstrap 3.21 program).
|
old febootstrap 3.21 program).
|
||||||
|
|
||||||
Refer to L<supermin-helper(1)/ENVIRONMENT VARIABLES>
|
Refer to L<supermin(1)/ENVIRONMENT VARIABLES> for further information.
|
||||||
for further information.
|
|
||||||
|
|
||||||
=head1 TRYING OUT A DIFFERENT VERSION OF LIBVIRT
|
=head1 TRYING OUT A DIFFERENT VERSION OF LIBVIRT
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user