package org.openslx.util.vm;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.UUID;
import org.apache.log4j.Logger;
import org.openslx.bwlp.thrift.iface.OperatingSystem;
import org.openslx.bwlp.thrift.iface.Virtualizer;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
class VBoxSoundCardMeta
{
public final boolean isPresent;
public final String value;
public VBoxSoundCardMeta( boolean present, String val )
{
isPresent = present;
value = val;
}
}
class VBoxDDAccelMeta
{
public final boolean isPresent;
public VBoxDDAccelMeta( boolean present )
{
isPresent = present;
}
}
class VBoxHWVersionMeta
{
public final int version;
public VBoxHWVersionMeta( int vers )
{
version = vers;
}
}
class VBoxEthernetDevTypeMeta
{
public final String value;
public final boolean isPresent;
public VBoxEthernetDevTypeMeta( boolean present, String val )
{
value = val;
isPresent = present;
}
}
public class VboxMetaData extends VmMetaData<VBoxSoundCardMeta, VBoxDDAccelMeta, VBoxHWVersionMeta, VBoxEthernetDevTypeMeta>
{
private static final Logger LOGGER = Logger.getLogger( VboxMetaData.class );
private static final Virtualizer virtualizer = new Virtualizer( "virtualbox", "VirtualBox" );
private final VboxConfig config;
public static enum EthernetType
{
NAT( "vboxnet1" ),
BRIDGED( "vboxnet0" ),
HOST_ONLY( "vboxnet2" );
public final String vnet;
private EthernetType( String vnet )
{
this.vnet = vnet;
}
}
public VboxMetaData( List<OperatingSystem> osList, File file ) throws IOException, UnsupportedVirtualizerFormatException
{
super( osList );
this.config = new VboxConfig( file );
init();
}
public VboxMetaData( List<OperatingSystem> osList, byte[] vmContent, int length ) throws IOException, UnsupportedVirtualizerFormatException
{
super( osList );
this.config = new VboxConfig( vmContent, length );
init();
}
private void init()
{
populateTheMaps();
this.config.init();
displayName = config.getDisplayName();
setOs( "virtualbox", config.getOsName() );
for ( HardDisk hardDisk : config.getHdds() ) {
hdds.add( hardDisk );
}
}
@Override
public Virtualizer getVirtualizer()
{
return virtualizer;
}
@Override
public void enableUsb( boolean enabled )
{
if ( !enabled ) {
config.disableUsb();
} else {
config.enableUsb();
}
}
@Override
public void applySettingsForLocalEdit()
{
// TODO Auto-generated method stub
}
@Override
public byte[] getFilteredDefinitionArray()
{
return config.toString().getBytes( StandardCharsets.UTF_8 );
}
@Override
public boolean addHddTemplate( String diskImage, String hddMode, String redoDir )
{
config.changeAttribute( "HardDisk", "location", diskImage );
config.changeAttribute( "Machine", "snapshotFolder", redoDir );
return true;
}
@Override
public boolean addHddTemplate( File diskImage, String hddMode, String redoDir )
{
String diskImagePath = diskImage.getName();
config.changeAttribute( "HardDisk", "location", diskImagePath );
UUID newhdduuid = UUID.randomUUID();
// patching the new uuid in the vbox config file here
String vboxUUid = "{" + newhdduuid.toString() + "}";
config.changeAttribute( "HardDisk", "uuid", vboxUUid );
config.changeAttribute( "Image", "uuid", vboxUUid );
// write the new hdd uuid in the vdi file
byte[] bytesToWrite = new byte[ 16 ];
int[] bytesOffset = { 32, 40, 48, 56, 16, 24, 0, 8, 56, 48, 40, 32, 24, 16, 8, 0 };
int offset = 0;
for ( int i = 0; i < 2; i++ ) {
Long uuidlong = null;
if ( i == 0 ) {
uuidlong = newhdduuid.getMostSignificantBits();
} else {
uuidlong = newhdduuid.getLeastSignificantBits();
}
for ( int j = 0; j < 8; j++ ) {
int index = j + offset;
bytesToWrite[index] = (byte) ( uuidlong >>> bytesOffset[index] );
}
offset = 8;
}
try ( RandomAccessFile file = new RandomAccessFile( diskImage, "rw" ) ) {
file.seek( 392 );
file.write( bytesToWrite, 0, 16 );
} catch ( Exception e ) {
LOGGER.warn( "could not patch new uuid in the vdi", e );
}
// we need a new machine uuid
UUID newMachineUuid = UUID.randomUUID();
if ( newMachineUuid.equals( newhdduuid ) ) {
LOGGER.warn( "The new Machine UUID is the same as the new HDD UUID; tying again...this vm might not start" );
newMachineUuid = UUID.randomUUID();
}
String machineUUid = "{" + newMachineUuid.toString() + "}";
config.changeAttribute( "Machine", "uuid", machineUUid );
return true;
}
@Override
public boolean addDefaultNat()
{
config.addNewNode( "Adapter", "NAT", true );
config.changeAttribute( "Adapter", "MACAddress", "080027B86D12" );
return true;
}
@Override
public void setOs( String vendorOsId )
{
config.changeAttribute( "Machine", "OSType", vendorOsId );
setOs( "virtualbox", vendorOsId );
}
@Override
public boolean addDisplayName( String name )
{
config.changeAttribute( "Machine", "name", name );
return true;
}
@Override
public boolean addRam( int mem )
{
config.changeAttribute( "Memory", "RAMSize", Integer.toString( mem ) );
return true;
}
@Override
public void addFloppy( int index, String image, boolean readOnly )
{
Node somenode = config.findANode( "StorageController", "name", "Floppy" );
if ( somenode == null ) {
Element controller = (Element)config.addNewNode( "StorageControllers", "StorageController", false );
controller.setAttribute( "name", "Floppy" );
controller.setAttribute( "type", "I82078" );
controller.setAttribute( "PortCount", "1" );
controller.setAttribute( "useHostIOCache", "true" );
controller.setAttribute( "Bootable", "true" );
}
Element attachedDev = null;
if ( image == null ) {
attachedDev = (Element)config.addNewNode( "StorageController", "AttachedDevice", true, "name", "Floppy" );
LOGGER.warn( "Floppy controller has no image attached" );
} else {
attachedDev = (Element)config.addNewNode( "StorageController", "AttachedDevice", false, "name", "Floppy" );
Element imageTag = (Element)config.addNewNode( "AttachedDevice", "Image", true, "type", "Floppy" );
imageTag.setAttribute( "uuid", "#OpenSLX_FloppyUUID_place_holder" );
config.addNewNode( "MediaRegistry", "FloppyImages", false );
Element floppyImageTag = (Element)config.addNewNode( "FloppyImages", "Image", true );
floppyImageTag.setAttribute( "uuid", "#OpenSLX_FloppyUUID_place_holder" );
floppyImageTag.setAttribute( "location", "#OpenSLX_FloppyImageLocation_place_holder" );
}
attachedDev.setAttribute( "type", "Floppy" );
attachedDev.setAttribute( "hotpluggable", "false" );
attachedDev.setAttribute( "port", "0" );
attachedDev.setAttribute( "device", Integer.toString( index ) );
}
@Override
public boolean addCdrom( String image )
{
return false;
}
@Override
public boolean addCpuCoreCount( int nrOfCores )
{
config.changeAttribute( "CPU", "count", Integer.toString( nrOfCores ) );
return true;
}
@Override
public void setSoundCard( org.openslx.util.vm.VmMetaData.SoundCardType type )
{
VBoxSoundCardMeta sound = soundCards.get( type );
config.changeAttribute( "AudioAdapter", "enabled", vmBoolean( sound.isPresent ) );
config.changeAttribute( "AudioAdapter", "controller", sound.value );
}
@Override
public VmMetaData.SoundCardType getSoundCard()
{
VmMetaData.SoundCardType returnsct = null;
Element x = (Element)config.findANode( "AudioAdapter" ).item( 0 );
if ( !x.hasAttribute( "enabled" ) || ( x.hasAttribute( "enabled" ) && x.getAttribute( "enabled" ).equals( "false" ) ) ) {
returnsct = VmMetaData.SoundCardType.NONE;
} else {
// extra separate case for the non-existing argument}
if ( !x.hasAttribute( "type" ) ) {
returnsct = VmMetaData.SoundCardType.AC;
} else {
String controller = x.getAttribute( "controller" );
VBoxSoundCardMeta soundMeta = null;
for ( VmMetaData.SoundCardType type : VmMetaData.SoundCardType.values() ) {
soundMeta = soundCards.get( type );
if ( soundMeta != null ) {
if ( controller.equals( soundMeta.value ) ) {
returnsct = type;
}
}
}
}
}
return returnsct;
}
@Override
public void setDDAcceleration( VmMetaData.DDAcceleration type )
{
VBoxDDAccelMeta accel = ddacc.get( type );
config.changeAttribute( "Display", "accelerate3D", vmBoolean( accel.isPresent ) );
}
@Override
public VmMetaData.DDAcceleration getDDAcceleration()
{
VmMetaData.DDAcceleration returndda = null;
Element x = (Element)config.findANode( "Display" ).item( 0 );
if ( x.hasAttribute( "accelerate3D" ) ) {
if ( x.getAttribute( "accelerate3D" ).equals( "true" ) ) {
returndda = VmMetaData.DDAcceleration.ON;
} else {
returndda = VmMetaData.DDAcceleration.OFF;
}
} else {
returndda = VmMetaData.DDAcceleration.OFF;
}
return returndda;
}
@Override
/**
* Function does nothing for Virtual Box;
* Virtual Box accepts per default only one hardware version and is hidden from the user
*/
public void setHWVersion( HWVersion type )
{
}
@Override
public VmMetaData.HWVersion getHWVersion()
{
VmMetaData.HWVersion returnhwv = null;
// Virtual Box uses only one virtual hardware version and can't be changed
returnhwv = VmMetaData.HWVersion.DEFAULT;
return returnhwv;
}
@Override
public void setEthernetDevType( int cardIndex, EthernetDevType type )
{
String index = "0";
VBoxEthernetDevTypeMeta networkc = networkCards.get( type );
// cardIndex is not used yet...maybe later needed for different network cards
config.changeAttribute( "Adapter", "enabled", vmBoolean( networkc.isPresent ), "slot", index );
config.changeAttribute( "Adapter", "type", networkc.value, "slot", index );
}
@Override
public VmMetaData.EthernetDevType getEthernetDevType( int cardIndex )
{
VmMetaData.EthernetDevType returnedt = null;
Element x = (Element)config.findANode( "Adapter" ).item( 0 );
if ( !x.hasAttribute( "enabled" ) || ( x.hasAttribute( "enabled" ) && x.getAttribute( "enabled" ).equals( "false" ) ) ) {
returnedt = VmMetaData.EthernetDevType.NONE;
} else {
// extra separate case for the non-existing argument}
if ( !x.hasAttribute( "type" ) ) {
returnedt = VmMetaData.EthernetDevType.PCNETFAST3;
} else {
String temp = x.getAttribute( "type" );
VBoxEthernetDevTypeMeta etherMeta = null;
for ( VmMetaData.EthernetDevType type : VmMetaData.EthernetDevType.values() ) {
etherMeta = networkCards.get( type );
if ( etherMeta != null ) {
if ( temp.equals( etherMeta.value ) ) {
returnedt = type;
}
}
}
}
}
return returnedt;
}
@Override
public byte[] getDefinitionArray()
{
return config.toString().getBytes( StandardCharsets.UTF_8 );
}
public void populateTheMaps()
{
// add all from vmware supported sound cards here
// none type needs to have a valid value; it takes the value of AC97; if value is left null or empty vm will not start because value is not valid
soundCards.put( VmMetaData.SoundCardType.NONE, new VBoxSoundCardMeta( false, "AC97" ) );
soundCards.put( VmMetaData.SoundCardType.SOUND_BLASTER, new VBoxSoundCardMeta( true, "SB16" ) );
soundCards.put( VmMetaData.SoundCardType.HD_AUDIO, new VBoxSoundCardMeta( true, "HDA" ) );
soundCards.put( VmMetaData.SoundCardType.AC, new VBoxSoundCardMeta( true, "AC97" ) );
// end of supported sound cards
// add all from vmware supported settings for the 3D acceleration
ddacc.put( VmMetaData.DDAcceleration.OFF, new VBoxDDAccelMeta( false ) );
ddacc.put( VmMetaData.DDAcceleration.ON, new VBoxDDAccelMeta( true ) );
// end of all from vmware supported settings for the 3D acceleration
// add all from vmware supported Hardware versions here
hwversion.put( VmMetaData.HWVersion.DEFAULT, new VBoxHWVersionMeta( 0 ) );
// end of all from vmware supported Hardware versions here
// add all from vmware supported Ethernet devices versions here
// none type needs to have a valid value; it takes the value of pcnetcpi2; if value is left null or empty vm will not start because value is not valid
networkCards.put( VmMetaData.EthernetDevType.NONE, new VBoxEthernetDevTypeMeta( false, "Am79C970A" ) );
networkCards.put( VmMetaData.EthernetDevType.PCNETPCI2, new VBoxEthernetDevTypeMeta( true, "Am79C970A" ) );
networkCards.put( VmMetaData.EthernetDevType.PCNETFAST3, new VBoxEthernetDevTypeMeta( true, "Am79C973" ) );
networkCards.put( VmMetaData.EthernetDevType.PRO1000MTD, new VBoxEthernetDevTypeMeta( true, "82540EM" ) );
networkCards.put( VmMetaData.EthernetDevType.PRO1000TS, new VBoxEthernetDevTypeMeta( true, "82543GC" ) );
networkCards.put( VmMetaData.EthernetDevType.PRO1000MTS, new VBoxEthernetDevTypeMeta( true, "82545EM" ) );
networkCards.put( VmMetaData.EthernetDevType.PARAVIRT, new VBoxEthernetDevTypeMeta( true, "virtio" ) );
// end of all from vmware supported Ethernet devices versions here
}
/**
* given a boolean value returns a string in lowercase of given boolean
*
* @param var
* @return
*/
private static String vmBoolean( boolean var )
{
return Boolean.toString( var ).toLowerCase();
}
@Override
public boolean addEthernet( VmMetaData.EtherType type )
{
Node hostOnlyInterfaceNode = config.addNewNode( "Adapter", "HostOnlyInterface", true, "slot", "0" );
if (hostOnlyInterfaceNode == null) {
LOGGER.error("Failed to create node for HostOnlyInterface.");
return false;
}
return config.addAttributeToNode(hostOnlyInterfaceNode, "name", EthernetType.valueOf(type.name()).vnet);
}
@Override
public boolean disableSuspend()
{
// TODO how??
// short answer is: you can't
// https://forums.virtualbox.org/viewtopic.php?f=6&t=77169
// https://forums.virtualbox.org/viewtopic.php?f=8&t=80338
return true;
}
}