[gnome-boxes] Merge virt-installer as gnome-boxes-installer
- From: Zeeshan Ali Khattak <zeeshanak src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-boxes] Merge virt-installer as gnome-boxes-installer
- Date: Fri, 28 Oct 2011 13:37:00 +0000 (UTC)
commit 07c183e22680577cefd8a4fa9575c98ead9a5511
Author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
Date: Thu Oct 27 19:28:44 2011 +0300
Merge virt-installer as gnome-boxes-installer
For now we just merge it as a separate binary only.
data/Makefile.am | 13 +++
data/floppy.img | Bin 0 -> 2097152 bytes
data/ks.cfg | 28 ++++++
data/win2k.sif | 42 ++++++++
data/win2k.xml | 94 ++++++++++++++++++
data/win2k3.sif | 48 +++++++++
data/win7.xml | 96 +++++++++++++++++++
data/winxp.sif | 48 +++++++++
src/Makefile.am | 17 +++-
src/fedora-installer.vala | 105 ++++++++++++++++++++
src/installer-main.vala | 74 ++++++++++++++
src/installer-media.vala | 105 ++++++++++++++++++++
src/installer-util.vala | 1 +
src/os-database.vala | 120 +++++++++++++++++++++++
src/unattended-installer.vala | 140 +++++++++++++++++++++++++++
src/util.vala | 16 +++
src/vm-creator.vala | 212 +++++++++++++++++++++++++++++++++++++++++
src/win7-installer.vala | 9 ++
src/winxp-installer.vala | 9 ++
19 files changed, 1176 insertions(+), 1 deletions(-)
---
diff --git a/data/Makefile.am b/data/Makefile.am
index 131a472..3583447 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -18,6 +18,19 @@ source_DATA = QEMU_Session
iconsdir = $(pkgdatadir)/icons
+unattendeddir = $(datadir)/gnome-boxes/unattended
+unattended_DATA = ks.cfg \
+ win7.xml \
+ win2k.xml \
+ winxp.sif \
+ win2k.sif \
+ win2k3.sif \
+ floppy.img \
+ $(NULL)
+
+floppy.img:
+ rm -f floppy.img && mkfs.msdos -C floppy.img 2048
+
EXTRA_DIST = \
gnome-boxes.desktop.in \
org.gnome.boxes.gschema.xml.in \
diff --git a/data/floppy.img b/data/floppy.img
new file mode 100644
index 0000000..df58d6d
Binary files /dev/null and b/data/floppy.img differ
diff --git a/data/ks.cfg b/data/ks.cfg
new file mode 100644
index 0000000..5c348ef
--- /dev/null
+++ b/data/ks.cfg
@@ -0,0 +1,28 @@
+install
+text
+keyboard us
+lang en_US.UTF-8
+skipx
+network --device eth0 --bootproto dhcp
+rootpw whatever
+firewall --disabled
+authconfig --enableshadow --enablemd5
+selinux --enforcing
+timezone --utc Europe/Helsinki
+bootloader --location=mbr
+zerombr
+clearpart --all --drives=sda
+
+part /boot --fstype ext4 --recommended --ondisk=sda
+part pv.2 --size=1 --grow --ondisk=sda
+volgroup VolGroup00 --pesize=32768 pv.2
+logvol swap --fstype swap --name=LogVol01 --vgname=VolGroup00 --size=768 --grow --maxsize=1536
+logvol / --fstype ext4 --name=LogVol00 --vgname=VolGroup00 --size=1024 --grow
+reboot
+
+%packages
+ base
+ core
+ hardware-support
+
+%end
diff --git a/data/win2k.sif b/data/win2k.sif
new file mode 100644
index 0000000..d0a0a19
--- /dev/null
+++ b/data/win2k.sif
@@ -0,0 +1,42 @@
+;SetupMgrTag
+[Data]
+ AutoPartition=1
+ MsDosInitiated=No
+ UnattendedInstall=Yes
+
+[Unattended]
+ UnattendMode=FullUnattended
+ OemSkipEula=Yes
+ OemPreinstall=No
+ TargetPath=\WINNT
+ Repartition=Yes
+ WaitForReboot="No"
+ UnattendSwitch="Yes"
+
+[GuiUnattended]
+ AdminPassword=%ROOTPW%
+ AutoLogon=Yes
+ AutoLogonCount=1
+ OEMSkipRegional=1
+ TimeZone=35
+ OemSkipWelcome=1
+
+[UserData]
+ ProductID=
+ FullName="OZ"
+ ComputerName=Whatever
+
+[Display]
+ BitsPerPel=16
+ Xresolution=640
+ YResolution=480
+ Vrefresh=60
+
+[RegionalSettings]
+ LanguageGroup=1
+
+[Identification]
+ JoinWorkgroup=WORKGROUP
+
+[Networking]
+ InstallDefaultComponents=Yes
diff --git a/data/win2k.xml b/data/win2k.xml
new file mode 100644
index 0000000..d51c433
--- /dev/null
+++ b/data/win2k.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="utf-8"?>
+<unattend xmlns="urn:schemas-microsoft-com:unattend">
+ <settings pass="windowsPE">
+ <component name="Microsoft-Windows-Setup" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <DiskConfiguration>
+ <WillShowUI>OnError</WillShowUI>
+ <Disk>
+ <CreatePartitions>
+ <CreatePartition>
+ <Order>1</Order>
+ <Size>1</Size>
+ <Type>Primary</Type>
+ </CreatePartition>
+ </CreatePartitions>
+ <DiskID>0</DiskID>
+ <WillWipeDisk>true</WillWipeDisk>
+ <ModifyPartitions>
+ <ModifyPartition>
+ <Active>true</Active>
+ <Extend>true</Extend>
+ <Format>NTFS</Format>
+ <Label>C drive</Label>
+ <Letter>C</Letter>
+ <Order>1</Order>
+ <PartitionID>1</PartitionID>
+ </ModifyPartition>
+ </ModifyPartitions>
+ </Disk>
+ </DiskConfiguration>
+ <ImageInstall>
+ <OSImage>
+ <InstallFrom>
+ <MetaData wcm:action="add">
+ <Key>/IMAGE/INDEX</Key>
+ <Value>1</Value>
+ </MetaData>
+ </InstallFrom>
+ <InstallTo>
+ <DiskID>0</DiskID>
+ <PartitionID>1</PartitionID>
+ </InstallTo>
+ <WillShowUI>OnError</WillShowUI>
+ </OSImage>
+ </ImageInstall>
+ <UserData>
+ <AcceptEula>true</AcceptEula>
+ </UserData>
+ </component>
+ <component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <SetupUILanguage>
+ <UILanguage>en-US</UILanguage>
+ </SetupUILanguage>
+ <SystemLocale>en-US</SystemLocale>
+ <UILanguage>en-US</UILanguage>
+ <UserLocale>en-US</UserLocale>
+ </component>
+ </settings>
+ <settings pass="oobeSystem">
+ <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <UserAccounts>
+ <AdministratorPassword>
+ <Value></Value>
+ <PlainText>true</PlainText>
+ </AdministratorPassword>
+ </UserAccounts>
+ <AutoLogon>
+ <Password>
+ <Value></Value>
+ <PlainText>true</PlainText>
+ </Password>
+ <Enabled>true</Enabled>
+ <LogonCount>5</LogonCount>
+ <Username>Administrator</Username>
+ </AutoLogon>
+ <RegisteredOwner/>
+ <OOBE>
+ <HideEULAPage>true</HideEULAPage>
+ <ProtectYourPC>3</ProtectYourPC>
+ </OOBE>
+ <FirstLogonCommands>
+ <SynchronousCommand wcm:action="add">
+ <Order>1</Order>
+ <Description>Shutting down Windows</Description>
+ <CommandLine>cmd /C shutdown /s /t 0</CommandLine>
+ </SynchronousCommand>
+ </FirstLogonCommands>
+ </component>
+ </settings>
+ <settings pass="specialize">
+ <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <ProductKey></ProductKey>
+ </component>
+ </settings>
+</unattend>
diff --git a/data/win2k3.sif b/data/win2k3.sif
new file mode 100644
index 0000000..e888fd1
--- /dev/null
+++ b/data/win2k3.sif
@@ -0,0 +1,48 @@
+;SetupMgrTag
+[Data]
+ AutoPartition=1
+ MsDosInitiated="0"
+ UnattendedInstall="Yes"
+
+[Unattended]
+ UnattendMode=FullUnattended
+ OemSkipEula=Yes
+ OemPreinstall=No
+ TargetPath=\WINDOWS
+ Repartition=Yes
+ WaitForReboot="No"
+ UnattendSwitch="Yes"
+
+[GuiUnattended]
+ AdminPassword="%ROOTPW%"
+ EncryptedAdminPassword=NO
+ AutoLogon=Yes
+ AutoLogonCount=1
+ OEMSkipRegional=1
+ TimeZone=35
+ OemSkipWelcome=1
+
+[UserData]
+ ProductKey=
+ FullName="OZ"
+ OrgName=""
+ ComputerName=Whatever
+
+[LicenseFilePrintData]
+ AutoMode=PerServer
+ AutoUsers=5
+
+[Identification]
+ JoinWorkgroup=WORKGROUP
+
+[Networking]
+ InstallDefaultComponents=Yes
+
+[SetupParams]
+ UserExecute = "sc config TlntSvr start= auto"
+
+[WindowsFirewall]
+ Profiles = WindowsFirewall.TurnOffFirewall
+
+[WindowsFirewall.TurnOffFirewall]
+ Mode = 0
diff --git a/data/win7.xml b/data/win7.xml
new file mode 100644
index 0000000..c691257
--- /dev/null
+++ b/data/win7.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<unattend xmlns="urn:schemas-microsoft-com:unattend">
+ <settings pass="windowsPE">
+ <component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <DiskConfiguration>
+ <WillShowUI>OnError</WillShowUI>
+ <Disk>
+ <CreatePartitions>
+ <CreatePartition>
+ <Order>1</Order>
+ <Size>1</Size>
+ <Type>Primary</Type>
+ </CreatePartition>
+ </CreatePartitions>
+ <DiskID>0</DiskID>
+ <WillWipeDisk>true</WillWipeDisk>
+ <ModifyPartitions>
+ <ModifyPartition>
+ <Active>true</Active>
+ <Extend>true</Extend>
+ <Format>NTFS</Format>
+ <Label>C drive</Label>
+ <Letter>C</Letter>
+ <Order>1</Order>
+ <PartitionID>1</PartitionID>
+ </ModifyPartition>
+ </ModifyPartitions>
+ </Disk>
+ </DiskConfiguration>
+ <ImageInstall>
+ <OSImage>
+ <InstallFrom>
+ <MetaData wcm:action="add">
+ <Key>/IMAGE/INDEX</Key>
+ <Value>1</Value>
+ </MetaData>
+ </InstallFrom>
+ <InstallTo>
+ <DiskID>0</DiskID>
+ <PartitionID>1</PartitionID>
+ </InstallTo>
+ <WillShowUI>OnError</WillShowUI>
+ </OSImage>
+ </ImageInstall>
+ <UserData>
+ <AcceptEula>true</AcceptEula>
+ </UserData>
+ </component>
+ <component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <SetupUILanguage>
+ <UILanguage>en-US</UILanguage>
+ </SetupUILanguage>
+ <SystemLocale>en-US</SystemLocale>
+ <UILanguage>en-US</UILanguage>
+ <UserLocale>en-US</UserLocale>
+ </component>
+ </settings>
+ <settings pass="oobeSystem">
+ <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <UserAccounts>
+ <AdministratorPassword>
+ <Value>whatever</Value>
+ <PlainText>true</PlainText>
+ </AdministratorPassword>
+ </UserAccounts>
+ <AutoLogon>
+ <Password>
+ <Value>whatever</Value>
+ <PlainText>true</PlainText>
+ </Password>
+ <Enabled>true</Enabled>
+ <LogonCount>5</LogonCount>
+ <Username>Administrator</Username>
+ </AutoLogon>
+ <OOBE>
+ <NetworkLocation>Work</NetworkLocation>
+ <HideEULAPage>true</HideEULAPage>
+ <ProtectYourPC>3</ProtectYourPC>
+ <SkipMachineOOBE>true</SkipMachineOOBE>
+ <SkipUserOOBE>true</SkipUserOOBE>
+ </OOBE>
+ <FirstLogonCommands>
+ <SynchronousCommand wcm:action="add">
+ <Order>1</Order>
+ <Description>Turn Off Network Selection pop-up</Description>
+ <CommandLine>cmd /c reg add "HKLM\SYSTEM\CurrentControlSet\Control\Network\NewNetworkWindowOff"</CommandLine>
+ </SynchronousCommand>
+ <SynchronousCommand wcm:action="add">
+ <Order>2</Order>
+ <Description>Shutting down Windows</Description>
+ <CommandLine>cmd /C shutdown /s /t 0</CommandLine>
+ </SynchronousCommand>
+ </FirstLogonCommands>
+ </component>
+ </settings>
+</unattend>
diff --git a/data/winxp.sif b/data/winxp.sif
new file mode 100644
index 0000000..c860bff
--- /dev/null
+++ b/data/winxp.sif
@@ -0,0 +1,48 @@
+;SetupMgrTag
+[Data]
+ AutoPartition=1
+ MsDosInitiated="0"
+ UnattendedInstall="Yes"
+
+[Unattended]
+ UnattendMode=FullUnattended
+ OemSkipEula=Yes
+ OemPreinstall=No
+ TargetPath=\WINDOWS
+ Repartition=Yes
+ WaitForReboot="No"
+ UnattendSwitch="Yes"
+
+[GuiUnattended]
+ AdminPassword="%ROOTPW%"
+ EncryptedAdminPassword=NO
+ AutoLogon=Yes
+ AutoLogonCount=1
+ OEMSkipRegional=1
+ TimeZone=35
+ OemSkipWelcome=1
+
+[UserData]
+ ProductKey=
+ FullName="OZ"
+ OrgName=""
+ ComputerName=Nothing
+
+[LicenseFilePrintData]
+ AutoMode=PerServer
+ AutoUsers=5
+
+[Identification]
+ JoinWorkgroup=WORKGROUP
+
+[Networking]
+ InstallDefaultComponents=Yes
+
+[SetupParams]
+ UserExecute = "sc config TlntSvr start= auto"
+
+[WindowsFirewall]
+ Profiles = WindowsFirewall.TurnOffFirewall
+
+[WindowsFirewall.TurnOffFirewall]
+ Mode = 0
diff --git a/src/Makefile.am b/src/Makefile.am
index 79bd5bc..5ecf99f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -26,7 +26,7 @@ AM_VALAFLAGS = \
--pkg libosinfo-1.0 \
$(NULL)
-bin_PROGRAMS = gnome-boxes
+bin_PROGRAMS = gnome-boxes gnome-boxes-installer
gnome_boxes_SOURCES = \
app.vala \
@@ -49,9 +49,24 @@ gnome_boxes_SOURCES = \
wizard.vala \
$(NULL)
+gnome_boxes_installer_SOURCES = \
+ installer-media.vala \
+ unattended-installer.vala \
+ fedora-installer.vala \
+ win7-installer.vala \
+ winxp-installer.vala \
+ os-database.vala \
+ vm-creator.vala \
+ installer-util.vala \
+ installer-main.vala \
+ $(NULL)
+
gnome_boxes_LDADD = $(BOXES_LIBS)
gnome_boxes_CFLAGS = $(BOXES_CFLAGS)
+gnome_boxes_installer_LDADD = $(BOXES_LIBS)
+gnome_boxes_installer_CFLAGS = $(BOXES_CFLAGS)
+
MAINTAINERCLEANFILES = \
$(gnome_boxes_SOURCES:.vala=.c) \
*.stamp \
diff --git a/src/fedora-installer.vala b/src/fedora-installer.vala
new file mode 100644
index 0000000..3d65bc4
--- /dev/null
+++ b/src/fedora-installer.vala
@@ -0,0 +1,105 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+
+private class Boxes.FedoraInstaller: UnattendedInstaller {
+ private bool mounted;
+
+ private File source_dir;
+ private File kernel_file;
+ private File initrd_file;
+
+ public FedoraInstaller.copy (InstallerMedia media) throws GLib.Error {
+ var source_path = get_unattended_dir ("ks.cfg");
+
+ base.copy (media, source_path, "ks.cfg");
+ }
+
+ protected override async void prepare_direct_boot (Cancellable? cancellable) throws GLib.Error {
+ var kernel_path = os_media.get_kernel_path ();
+ var initrd_path = os_media.get_initrd_path ();
+
+ if (kernel_path == null || initrd_path == null)
+ return;
+
+ yield mount_media (cancellable);
+
+ yield extract_boot_files (kernel_path, initrd_path, cancellable);
+
+ yield normal_clean_up (cancellable);
+ }
+
+ protected override void clean_up () throws GLib.Error {
+ base.clean_up ();
+
+ if (kernel_file != null) {
+ debug ("Removing '%s'..", kernel_path);
+ kernel_file.delete ();
+ debug ("Removed '%s'.", kernel_path);
+ }
+
+ if (initrd_file != null) {
+ debug ("Removing '%s'..", initrd_path);
+ initrd_file.delete ();
+ debug ("Removed '%s'.", initrd_path);
+ }
+ }
+
+ private async void normal_clean_up (Cancellable? cancellable) throws GLib.Error {
+ if (!mounted)
+ return;
+
+ debug ("Unmounting '%s'..", mount_point);
+ string[] argv = { "fusermount", "-u", mount_point };
+ yield exec (argv, cancellable);
+ debug ("Unmounted '%s'.", mount_point);
+
+ source_dir.delete ();
+ debug ("Removed '%s'.", mount_point);
+ }
+
+ private async void mount_media (Cancellable? cancellable) throws GLib.Error {
+ if (mount_point != null) {
+ source_dir = File.new_for_path (mount_point);
+
+ return;
+ }
+
+ mount_point = get_user_unattended_dir (os.short_id);
+ var dir = File.new_for_path (mount_point);
+ try {
+ dir.make_directory (null);
+ } catch (IOError.EXISTS error) {}
+ source_dir = dir;
+
+ debug ("Mounting '%s' on '%s'..", device_file, mount_point);
+ string[] argv = { "fuseiso", device_file, mount_point };
+ yield exec (argv, cancellable);
+ debug ("'%s' now mounted on '%s'.", device_file, mount_point);
+
+ mounted = true;
+ }
+
+ private async void extract_boot_files (string kernel, string initrd, Cancellable? cancellable) throws GLib.Error {
+ kernel_path = Path.build_filename (mount_point, kernel);
+ kernel_file = File.new_for_path (kernel_path);
+ initrd_path = Path.build_filename (mount_point, initrd);
+ initrd_file = File.new_for_path (initrd_path);
+
+ if (!mounted)
+ return;
+
+ kernel_path = get_user_unattended_dir (os.short_id + "-kernel");
+ kernel_file = yield copy_file (kernel_file, kernel_path, cancellable);
+ initrd_path = get_user_unattended_dir (os.short_id + "-initrd");
+ initrd_file = yield copy_file (initrd_file, initrd_path, cancellable);
+ }
+
+ private async File copy_file (File file, string dest_path, Cancellable? cancellable) throws GLib.Error {
+ var dest_file = File.new_for_path (dest_path);
+
+ try {
+ yield file.copy_async (dest_file, 0, Priority.DEFAULT, cancellable);
+ } catch (IOError.EXISTS error) {}
+
+ return dest_file;
+ }
+}
diff --git a/src/installer-main.vala b/src/installer-main.vala
new file mode 100644
index 0000000..bf63491
--- /dev/null
+++ b/src/installer-main.vala
@@ -0,0 +1,74 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+
+private class Boxes.Main {
+ private OSDatabase os_db;
+ private VMCreator vm_creator;
+ private GUdev.Client client;
+ private MainLoop main_loop;
+ private string[] paths;
+
+ public Main (string[] paths) throws GLib.Error {
+ os_db = new OSDatabase ();
+ client = new GUdev.Client ({"block"});
+ vm_creator = new VMCreator ("qemu:///session");
+ main_loop = new MainLoop ();
+ this.paths = paths;
+ }
+
+ public async void launch_new_vm_for_path (string path, Cancellable? cancellable) throws GLib.Error {
+ var install_media = yield InstallerMedia.instantiate (path, os_db, client, cancellable);
+ var resources = os_db.get_resources_for_os (install_media.os);
+ var domain = yield vm_creator.create_domain_for_installer (install_media, resources, cancellable);
+
+ domain.start (0);
+
+ // Launch view to newly created VM
+ var commandline = "virt-viewer --connect qemu:///session -w " + domain.get_uuid ();
+ debug ("Launching: %s", commandline);
+ var app = AppInfo.create_from_commandline (commandline, install_media.label, AppInfoCreateFlags.NONE);
+
+ app.launch (null, null);
+ debug ("Launched: %s", commandline);
+ }
+
+ public int run () {
+ int ret = 0;
+ uint8 vms_created = 0;
+
+ foreach (var path in paths) {
+ // FIXME: Work-around for bug#628336
+ var _path = path;
+
+ launch_new_vm_for_path.begin (path, null, (obj, res) => {
+ try {
+ launch_new_vm_for_path.end (res);
+ } catch (GLib.Error error) {
+ printerr ("Failed to create VM for '%s': %s", _path, error.message);
+
+ ret = -error.code;
+ }
+
+ vms_created++;
+
+ if (vms_created == paths.length)
+ main_loop.quit ();
+ });
+ }
+
+ main_loop.run ();
+
+ return ret;
+ }
+
+ public static int main (string[] args) {
+ try {
+ var main = new Main (args[1:args.length]);
+
+ return main.run ();
+ } catch (GLib.Error error) {
+ printerr ("Failed to initialize: %s", error.message);
+
+ return -error.code;
+ }
+ }
+}
diff --git a/src/installer-media.vala b/src/installer-media.vala
new file mode 100644
index 0000000..3bc55c3
--- /dev/null
+++ b/src/installer-media.vala
@@ -0,0 +1,105 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+
+using Osinfo;
+using GUdev;
+
+private class Boxes.InstallerMedia : Object {
+ public Os os;
+ public Media os_media;
+ public string label;
+ public string device_file;
+ public string mount_point;
+ public bool from_image;
+
+ public static async InstallerMedia instantiate (string path,
+ OSDatabase os_db,
+ Client client,
+ Cancellable? cancellable) throws GLib.Error {
+ var media = new InstallerMedia ();
+ yield media.setup_for_path (path, os_db, client, cancellable);
+
+ if (media.os == null)
+ return media;
+
+ switch (media.os.short_id) {
+ case "fedora14":
+ case "fedora15":
+ case "fedora16":
+ media = new FedoraInstaller.copy (media);
+
+ break;
+
+ case "win7":
+ case "win2k8":
+ media = new Win7Installer.copy (media);
+
+ break;
+
+ case "winxp":
+ case "win2k":
+ case "win2k3":
+ media = new WinXPInstaller.copy (media);
+
+ break;
+
+ default:
+ return media;
+ }
+
+ return media;
+ }
+
+ private async void setup_for_path (string path,
+ OSDatabase os_db,
+ Client client,
+ Cancellable? cancellable) throws GLib.Error {
+ var device = yield get_device_from_path (path, client, cancellable);
+
+ if (device != null)
+ get_os_and_label_from_device (device, os_db);
+ else {
+ from_image = true;
+ os = yield os_db.guess_os_from_install_media (device_file, cancellable, out os_media);
+ }
+
+ if (os != null)
+ label = os.get_name ();
+
+ if (label == null)
+ label = Path.get_basename (device_file);
+ }
+
+ private async GUdev.Device? get_device_from_path (string path, Client client, Cancellable? cancellable) {
+ try {
+ var mount_dir = File.new_for_commandline_arg (path);
+ var mount = yield mount_dir.find_enclosing_mount_async (Priority.DEFAULT, cancellable);
+ var root_dir = mount.get_root ();
+ if (root_dir.get_path () == mount_dir.get_path ()) {
+ var volume = mount.get_volume ();
+ device_file = volume.get_identifier (VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
+ mount_point = path;
+ } else
+ // Assume direct path to device node/image
+ device_file = path;
+ } catch (GLib.Error error) {
+ // Assume direct path to device node/image
+ device_file = path;
+ }
+
+ return client.query_by_device_file (device_file);
+ }
+
+ private void get_os_and_label_from_device (GUdev.Device device, OSDatabase os_db) throws OSDatabaseError {
+ if (!device.get_property_as_boolean ("OSINFO_BOOTABLE"))
+ throw new OSDatabaseError.NON_BOOTABLE ("Media %s is not bootable.", device_file);
+
+ label = device.get_property ("ID_FS_LABEL");
+
+ var os_id = device.get_property ("OSINFO_INSTALLER");
+ if (os_id != null) {
+ os = os_db.get_os_by_id (os_id);
+ if (os == null)
+ throw new OSDatabaseError.UNKNOWN_OS_ID ("Unknown OS ID '%s'", os_id);
+ }
+ }
+}
diff --git a/src/installer-util.vala b/src/installer-util.vala
new file mode 120000
index 0000000..ec2be93
--- /dev/null
+++ b/src/installer-util.vala
@@ -0,0 +1 @@
+util.vala
\ No newline at end of file
diff --git a/src/os-database.vala b/src/os-database.vala
new file mode 100644
index 0000000..5ddcde6
--- /dev/null
+++ b/src/os-database.vala
@@ -0,0 +1,120 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+
+using Osinfo;
+
+public errordomain Boxes.OSDatabaseError {
+ NON_BOOTABLE,
+ UNKNOWN_OS_ID
+}
+
+private class Boxes.OSDatabase {
+ private const int DEFAULT_VCPUS = 1;
+ private const int64 DEFAULT_RAM = 500 * (int64) MEBIBYTES;
+ private const int64 DEFAULT_STORAGE = 2 * (int64) GIBIBYTES;
+
+ private Db db;
+
+ private static Resources get_default_resources () {
+ var resources = new Resources ("whatever", "x86_64");
+
+ resources.n_cpus = DEFAULT_VCPUS;
+ resources.ram = DEFAULT_RAM;
+ resources.storage = DEFAULT_STORAGE;
+
+ return resources;
+ }
+
+ public OSDatabase () throws GLib.Error {
+ var loader = new Loader ();
+ loader.process_default_path ();
+ db = loader.get_db ();
+ }
+
+ public async Os? guess_os_from_install_media (string media_path,
+ Cancellable? cancellable,
+ out Media os_media) throws GLib.Error {
+ var media = yield Media.create_from_location_async (media_path, Priority.DEFAULT, cancellable);
+
+ var os = db.guess_os_from_media (media);
+ if (os != null) {
+ var medias = os.get_media_list ();
+ os_media = get_prefered_media (medias);
+ } else
+ os_media = null;
+
+ return os;
+ }
+
+ public Os? get_os_by_id (string id) {
+ return db.get_os (id);
+ }
+
+ public Resources get_resources_for_os (Os? os) {
+ if (os == null)
+ return get_default_resources ();
+
+ // First try recommended resources
+ var list = os.get_recommended_resources ();
+ var recommended = get_prefered_resources (list);
+
+ list = os.get_minimum_resources ();
+ var minimum = get_prefered_resources (list);
+
+ return get_resources_from_os_resources (minimum, recommended);
+ }
+
+ private Resources get_resources_from_os_resources (Resources? minimum, Resources? recommended) {
+ var resources = get_default_resources ();
+
+ // Number of CPUs
+ if (recommended != null && recommended.n_cpus > 0)
+ resources.n_cpus = recommended.n_cpus;
+ else if (minimum != null && minimum.n_cpus > 0)
+ resources.n_cpus = int.max (minimum.n_cpus, resources.n_cpus);
+
+ // RAM
+ if (recommended != null && recommended.ram > 0)
+ resources.ram = recommended.ram;
+ else if (minimum != null && minimum.ram > 0)
+ resources.ram = int64.max (minimum.ram, resources.ram);
+
+ // Storage
+ if (recommended != null && recommended.storage > 0)
+ resources.storage = recommended.storage;
+ else if (minimum != null && minimum.storage > 0)
+ resources.storage = int64.max (minimum.storage, resources.storage);
+
+ return resources;
+ }
+
+ private Resources? get_prefered_resources (ResourcesList list) {
+ // Prefer x86_64 resources
+ string[] prefs = {"x86_64", "i386", ARCHITECTURE_ALL};
+
+ return get_prefered_entity (list.new_filtered, RESOURCES_PROP_ARCHITECTURE, prefs) as Resources;
+ }
+
+ private Media? get_prefered_media (MediaList list) {
+ // Prefer x86_64 resources
+ string[] prefs = {"x86_64", "i386", ARCHITECTURE_ALL};
+
+ return get_prefered_entity (list.new_filtered, MEDIA_PROP_ARCHITECTURE, prefs) as Media;
+ }
+
+ private Entity? get_prefered_entity (ListFilterFunc filter_func, string property, string[] prefs) {
+ if (prefs.length <= 0)
+ return null;
+
+ var filter = new Filter ();
+ filter.add_constraint (property, prefs[0]);
+ var filtered = filter_func (filter);
+ if (filtered.get_length () <= 0)
+ return get_prefered_entity (filter_func, property, prefs[1:prefs.length]);
+ else
+ // Assumption: There is only one resources instance of each type
+ // (minimum/recommended) of each architecture for each OS.
+ return filtered.get_nth (0);
+ }
+
+ private delegate Osinfo.List ListFilterFunc (Filter filter);
+}
diff --git a/src/unattended-installer.vala b/src/unattended-installer.vala
new file mode 100644
index 0000000..3f0e9ab
--- /dev/null
+++ b/src/unattended-installer.vala
@@ -0,0 +1,140 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+
+public errordomain UnattendedInstallerError {
+ COMMAND_FAILED
+}
+
+private abstract class Boxes.UnattendedInstaller: InstallerMedia {
+ public string kernel_path;
+ public string initrd_path;
+
+ public string floppy_path;
+ protected File floppy_file;
+
+ protected string unattended_src_path;
+ protected string unattended_dest_name;
+
+ private bool created_floppy;
+
+ public UnattendedInstaller.copy (InstallerMedia media,
+ string unattended_src_path,
+ string unattended_dest_name) throws GLib.Error {
+ os = media.os;
+ os_media = media.os_media;
+ label = media.label;
+ device_file = media.device_file;
+ from_image = media.from_image;
+
+ floppy_path = get_pkgcache (os.short_id + "-unattended.img");
+ this.unattended_src_path = unattended_src_path;
+ this.unattended_dest_name = unattended_dest_name;
+ }
+
+ public async void setup (Cancellable? cancellable) throws GLib.Error {
+ try {
+ if (yield unattended_floppy_exists (cancellable))
+ debug ("Found previously created unattended floppy image for '%s', re-using..", os.short_id);
+ else {
+ yield create_floppy_image (cancellable);
+ yield copy_unattended_file (cancellable);
+ }
+
+ yield prepare_direct_boot (cancellable);
+ } catch (GLib.Error error) {
+ clean_up ();
+
+ throw error;
+ }
+ }
+
+ protected virtual void clean_up () throws GLib.Error {
+ if (!created_floppy)
+ return;
+
+ var floppy_file = File.new_for_path (floppy_path);
+
+ floppy_file.delete ();
+
+ debug ("Removed '%s'.", floppy_path);
+ }
+
+ protected virtual async void prepare_direct_boot (Cancellable? cancellable) throws GLib.Error {}
+
+ protected async void exec (string[] argv, Cancellable? cancellable) throws GLib.Error {
+ SourceFunc continuation = exec.callback;
+ GLib.Error error = null;
+ var context = MainContext.get_thread_default ();
+
+ g_io_scheduler_push_job ((job) => {
+ try {
+ exec_sync (argv);
+ } catch (GLib.Error err) {
+ error = err;
+ }
+
+ var source = new IdleSource ();
+ source.set_callback (() => {
+ continuation ();
+
+ return false;
+ });
+ source.attach (context);
+
+ return false;
+ });
+
+ yield;
+
+ if (error != null)
+ throw error;
+ }
+
+ private async void create_floppy_image (Cancellable? cancellable) throws GLib.Error {
+ var floppy_file = File.new_for_path (floppy_path);
+ var template_path = get_unattended_dir ("floppy.img");
+ var template_file = File.new_for_path (template_path);
+
+ debug ("Creating floppy image for unattended installation at '%s'..", floppy_path);
+ yield template_file.copy_async (floppy_file, 0, Priority.DEFAULT, cancellable);
+ debug ("Floppy image for unattended installation created at '%s'", floppy_path);
+
+ created_floppy = true;
+ }
+
+ private async void copy_unattended_file (Cancellable? cancellable) throws GLib.Error {
+ debug ("Putting unattended file: %s", unattended_dest_name);
+ // FIXME: Perhaps we should use libarchive for this?
+ string[] argv = { "mcopy", "-i", floppy_path,
+ unattended_src_path,
+ "::" + unattended_dest_name };
+ yield exec (argv, cancellable);
+ debug ("Put unattended file: %s", unattended_dest_name);
+ }
+
+ private async bool unattended_floppy_exists (Cancellable? cancellable) {
+ var file = File.new_for_path (floppy_path);
+
+ try {
+ yield file.read_async (Priority.DEFAULT, cancellable);
+ } catch (IOError.NOT_FOUND not_found_error) {
+ return false;
+ } catch (GLib.Error error) {}
+
+ return true;
+ }
+
+ private void exec_sync (string[] argv) throws GLib.Error {
+ int exit_status = -1;
+
+ Process.spawn_sync (null,
+ argv,
+ null,
+ SpawnFlags.SEARCH_PATH,
+ null,
+ null,
+ null,
+ out exit_status);
+ if (exit_status != 0)
+ throw new UnattendedInstallerError.COMMAND_FAILED ("Failed to execute: %s", string.joinv (" ", argv));
+ }
+}
diff --git a/src/util.vala b/src/util.vala
index 7fa78de..40b0e8c 100644
--- a/src/util.vala
+++ b/src/util.vala
@@ -25,6 +25,22 @@ namespace Boxes {
return Path.build_filename (get_pkgdata (), "pixmaps", file_name);
}
+ private string get_unattended_dir (string? file_name = null) {
+ var dir = Path.build_filename (get_pkgdata (), "unattended");
+
+ ensure_directory (dir);
+
+ return Path.build_filename (dir, file_name);
+ }
+
+ private string get_user_unattended_dir (string? file_name = null) {
+ var dir = Path.build_filename (get_pkgconfig (), "unattended", file_name);
+
+ ensure_directory (dir);
+
+ return Path.build_filename (dir, file_name);
+ }
+
private string get_pkgdata_source (string? file_name = null) {
return Path.build_filename (get_pkgdata (), "sources", file_name);
}
diff --git a/src/vm-creator.vala b/src/vm-creator.vala
new file mode 100644
index 0000000..439fbbb
--- /dev/null
+++ b/src/vm-creator.vala
@@ -0,0 +1,212 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+
+using Osinfo;
+using GVir;
+
+private class Boxes.VMCreator {
+ private Connection connection;
+
+ public VMCreator (string uri) throws GLib.Error {
+ connection = new Connection (uri);
+ }
+
+ public async GVir.Domain create_domain_for_installer (InstallerMedia install_media,
+ Resources resources,
+ Cancellable? cancellable) throws GLib.Error {
+ if (!connection.is_open ())
+ yield connect (cancellable);
+
+ if (install_media is UnattendedInstaller)
+ yield (install_media as UnattendedInstaller).setup (cancellable);
+
+ string name;
+ if (install_media.os != null)
+ name = install_media.os.short_id;
+ else
+ name = install_media.label.replace (" ", "-");
+
+ var target_path = yield create_target_volume (name, resources.storage);
+
+ var xml = get_virt_xml (install_media, name, target_path, resources);
+ var config = new GVirConfig.Domain (xml);
+
+ return connection.create_domain (config);
+ }
+
+ private async void connect (Cancellable? cancellable) throws GLib.Error {
+ yield connection.open_async (cancellable);
+ yield connection.fetch_domains_async (cancellable);
+ yield connection.fetch_storage_pools_async (cancellable);
+ }
+
+ private string get_virt_xml (InstallerMedia install_media, string name, string target_path, Resources resources) {
+ // FIXME: This information should come from libosinfo
+ var clock_offset = "utc";
+ if (install_media.os != null && install_media.os.short_id.contains ("win"))
+ clock_offset = "localtime";
+
+ var spice_port = 61000;
+
+ var domain_name = name;
+ for (var i = 1; connection.find_domain_by_name (domain_name) != null; i++)
+ domain_name = name + "-" + i.to_string ();
+
+ var ram = (resources.ram / KIBIBYTES).to_string ();
+ return "<domain type='kvm'>\n" +
+ " <name>" + domain_name + "</name>\n" +
+ " <memory>" + ram + "</memory>\n" +
+ " <vcpu>" + resources.n_cpus.to_string () + "</vcpu>\n" +
+ " <os>\n" +
+ " <type arch='x86_64'>hvm</type>\n" +
+ " <boot dev='cdrom'/>\n" +
+ " <boot dev='hd'/>\n" +
+ get_direct_boot_xml (install_media) +
+ " </os>\n" +
+ " <features>\n" +
+ " <acpi/><apic/><pae/>\n" +
+ " </features>\n" +
+ " <clock offset='" + clock_offset + "'/>\n" +
+ " <on_poweroff>destroy</on_poweroff>\n" +
+ " <on_reboot>destroy</on_reboot>\n" +
+ " <on_crash>destroy</on_crash>\n" +
+ " <devices>\n" +
+ " <disk type='file' device='disk'>\n" +
+ " <driver name='qemu' type='qcow2'/>\n" +
+ " <source file='" + target_path + "'/>\n" +
+ " <target dev='hda' bus='ide'/>\n" +
+ " </disk>\n" +
+ get_unattended_dir_floppy_xml (install_media) +
+ get_source_media_xml (install_media) +
+ " <interface type='user'>\n" +
+ " <mac address='00:11:22:33:44:55'/>\n" +
+ " </interface>\n" +
+ " <input type='tablet' bus='usb'/>\n" +
+ " <graphics type='spice' port='" + spice_port.to_string () + "'/>\n" +
+ " <console type='pty'/>\n" +
+ " <video>\n" +
+ // FIXME: Should be 'qxl', work-around for a spice bug
+ " <model type='vga'/>\n" +
+ " </video>\n" +
+ " </devices>\n" +
+ "</domain>";
+ }
+
+ private async string create_target_volume (string name, int64 storage) throws GLib.Error {
+ var pool = yield get_storage_pool ();
+
+ var volume_name = name + ".qcow2";
+ for (var i = 1; pool.get_volume (volume_name) != null; i++)
+ volume_name = name + "-" + i.to_string () + ".qcow2";
+
+ var storage_str = (storage / GIBIBYTES * 2).to_string ();
+ var xml = "<volume>\n" +
+ " <name>" + volume_name + "</name>\n" +
+ " <capacity unit='G'>" + storage_str + "</capacity>\n" +
+ " <target>\n" +
+ " <format type='qcow2'/>\n" +
+ " <permissions>\n" +
+ " <owner>" + get_uid () + "</owner>\n" +
+ " <group>" + get_gid () + "</group>\n" +
+ " <mode>0744</mode>\n" +
+ " <label>virt-image-" + name + "</label>\n" +
+ " </permissions>\n" +
+ " </target>\n" +
+ "</volume>";
+ var config = new GVirConfig.StorageVol (xml);
+ var volume = pool.create_volume (config);
+
+ return volume.get_path ();
+ }
+
+ private string get_source_media_xml (InstallerMedia install_media) {
+ string type, source_attr;
+
+ if (install_media.from_image) {
+ type = "file";
+ source_attr = "file" ;
+ } else {
+ type = "block";
+ source_attr = "dev";
+ }
+
+ return " <disk type='" + type + "'\n" +
+ " device='cdrom'>\n" +
+ " <driver name='qemu' type='raw'/>\n" +
+ " <source " + source_attr + "='" +
+ install_media.device_file + "'/>\n" +
+ " <target dev='hdc' bus='ide'/>\n" +
+ " <readonly/>\n" +
+ " </disk>\n";
+ }
+
+ private string get_unattended_dir_floppy_xml (InstallerMedia install_media) {
+ if (!(install_media is UnattendedInstaller))
+ return "";
+
+ var floppy_path = (install_media as UnattendedInstaller).floppy_path;
+ if (floppy_path == null)
+ return "";
+
+ return " <disk type='file' device='floppy'>\n" +
+ " <driver name='qemu' type='raw'/>\n" +
+ " <source file='" + floppy_path + "'/>\n" +
+ " <target dev='fd'/>\n" +
+ " </disk>\n";
+ }
+
+ private string get_direct_boot_xml (InstallerMedia install_media) {
+ if (!(install_media is UnattendedInstaller))
+ return "";
+
+ var unattended = install_media as UnattendedInstaller;
+
+ var kernel_path = unattended.kernel_path;
+ var initrd_path = unattended.initrd_path;
+
+ if (kernel_path == null || initrd_path == null)
+ return "";
+
+ return " <kernel>" + kernel_path + "</kernel>\n" +
+ " <initrd>" + initrd_path + "</initrd>\n" +
+ " <cmdline>ks=floppy</cmdline>\n";
+ }
+
+ private async StoragePool get_storage_pool () throws GLib.Error {
+ var pool = connection.find_storage_pool_by_name (Config.PACKAGE_TARNAME);
+ if (pool == null) {
+ var pool_path = get_pkgconfig ("images");
+ var xml = "<pool type='dir'>\n" +
+ "<name>" + Config.PACKAGE_TARNAME + "</name>\n" +
+ " <source>\n" +
+ " <directory path='" + pool_path + "'/>\n" +
+ " </source>\n" +
+ " <target>\n" +
+ " <path>" + pool_path + "</path>\n" +
+ " <permissions>\n" +
+ " <owner>" + get_uid () + "</owner>\n" +
+ " <group>" + get_gid () + "</group>\n" +
+ " <mode>0744</mode>\n" +
+ " <label>" + Config.PACKAGE_TARNAME + "</label>\n" +
+ " </permissions>\n" +
+ " </target>\n" +
+ "</pool>";
+ var config = new GVirConfig.StoragePool (xml);
+ pool = connection.create_storage_pool (config, 0);
+ yield pool.build_async (0, null);
+ yield pool.start_async (0, null);
+ }
+
+ // This should be async
+ pool.refresh (null);
+
+ return pool;
+ }
+
+ private string get_uid () {
+ return ((uint32) Posix.getuid ()).to_string ();
+ }
+
+ private string get_gid () {
+ return ((uint32) Posix.getgid ()).to_string ();
+ }
+}
diff --git a/src/win7-installer.vala b/src/win7-installer.vala
new file mode 100644
index 0000000..9db0c0b
--- /dev/null
+++ b/src/win7-installer.vala
@@ -0,0 +1,9 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+
+// Automated installer media for Windows 7 and 2008
+private class Boxes.Win7Installer: UnattendedInstaller {
+ public Win7Installer.copy (InstallerMedia media) throws GLib.Error {
+ var unattended_source = get_unattended_dir (media.os.short_id + ".xml");
+ base.copy (media, unattended_source, "Autounattend.xml");
+ }
+}
diff --git a/src/winxp-installer.vala b/src/winxp-installer.vala
new file mode 100644
index 0000000..37220e2
--- /dev/null
+++ b/src/winxp-installer.vala
@@ -0,0 +1,9 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+
+// Automated installer media for Windows XP, 2000 and 2003
+private class Boxes.WinXPInstaller: UnattendedInstaller {
+ public WinXPInstaller.copy (InstallerMedia media) throws GLib.Error {
+ var unattended_source = get_unattended_dir (media.os.short_id + ".sif");
+ base.copy (media, unattended_source, "Winnt.sif");
+ }
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]