package org.openslx.util.vm;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import org.openslx.bwlp.thrift.iface.OperatingSystem;
import org.openslx.bwlp.thrift.iface.Virtualizer;
import org.openslx.util.Util;
import org.openslx.util.vm.VmwareConfig.ConfigEntry;
public class VmwareMetaData extends VmMetaData {
private static final Logger LOGGER = Logger.getLogger(VmwareMetaData.class);
private static final Virtualizer virtualizer = new Virtualizer("vmware", "VMware");
private static final Pattern hddKey = Pattern.compile("^(ide\\d|scsi\\d|sata\\d):?(\\d)?\\.(.*)",
Pattern.CASE_INSENSITIVE);
// Lowercase list of allowed settings for upload (as regex)
private static final Pattern[] whitelist;
private final VmwareConfig config;
// Init static members
static {
String[] list = { "^guestos", "^uuid\\.bios", "^config\\.version", "^ehci\\.", "^mks\\.enable3d",
"^virtualhw\\.", "^sound\\.", "\\.pcislotnumber$", "^pcibridge", "\\.virtualdev$", "^tools\\.syncTime$",
"^time\\.synchronize", "^bios\\.bootDelay", "^rtc\\.", "^xhci\\." };
whitelist = new Pattern[list.length];
for (int i = 0; i < list.length; ++i) {
whitelist[i] = Pattern.compile(list[i].toLowerCase());
}
}
private final Map<String, Controller> disks = new HashMap<>();
public VmwareMetaData(List<OperatingSystem> osList, File file)
throws IOException, UnsupportedVirtualizerFormatException {
super(osList);
this.config = new VmwareConfig(file);
init();
}
public VmwareMetaData(List<OperatingSystem> osList, byte[] vmxContent, int length)
throws UnsupportedVirtualizerFormatException {
super(osList);
this.config = new VmwareConfig(vmxContent, length); // still unfiltered
init(); // now filtered
}
private void init() {
for (Entry<String, ConfigEntry> entry : config.entrySet()) {
handleLoadEntry(entry);
}
// if we find this tag, we already went through the hdd's - so we're done.
if (config.get("#SLX_HDD_BUS") != null) {
return;
}
// Now find the HDDs and add to list
for (Entry<String, Controller> cEntry : disks.entrySet()) {
Controller controller = cEntry.getValue();
String controllerType = cEntry.getKey();
if (!controller.present)
continue;
for (Entry<String, Device> dEntry : controller.devices.entrySet()) {
Device device = dEntry.getValue();
if (!device.present)
continue; // Not present
if (device.deviceType != null && !device.deviceType.toLowerCase().endsWith("disk"))
continue; // Not a HDD
DriveBusType bus = null;
if (controllerType.startsWith("ide")) {
bus = DriveBusType.IDE;
} else if (controllerType.startsWith("scsi")) {
bus = DriveBusType.SCSI;
} else if (controllerType.startsWith("sata")) {
bus = DriveBusType.SATA;
}
hdds.add(new HardDisk(controller.virtualDev, bus, device.filename));
}
}
// Add HDD to cleaned vmx
if (!hdds.isEmpty()) {
HardDisk hdd = hdds.get(0);
addFiltered("#SLX_HDD_BUS", hdd.bus.toString());
if (hdd.chipsetDriver != null) {
addFiltered("#SLX_HDD_CHIP", hdd.chipsetDriver);
}
}
}
private void addFiltered(String key, String value) {
config.set(key, value).filtered(true);
}
private boolean isSetAndTrue(String key) {
String value = config.get(key);
return value != null && value.equalsIgnoreCase("true");
}
private void handleLoadEntry(Entry<String, ConfigEntry> entry) {
String lowerKey = entry.getKey().toLowerCase();
// Cleaned vmx construction
for (Pattern exp : whitelist) {
if (exp.matcher(lowerKey).find()) {
entry.getValue().filtered(true);
break;
}
}
//
// Dig Usable meta data
String value = entry.getValue().getValue();
if (lowerKey.equals("guestos")) {
setOs("vmware", value);
return;
}
if (lowerKey.equals("displayname")) {
displayName = value;
return;
}
Matcher hdd = hddKey.matcher(entry.getKey());
if (hdd.find()) {
handleHddEntry(hdd.group(1).toLowerCase(), hdd.group(2), hdd.group(3), value);
}
}
private void handleHddEntry(String controllerStr, String deviceStr, String property, String value) {
Controller controller = disks.get(controllerStr);
if (controller == null) {
controller = new Controller();
disks.put(controllerStr, controller);
}
if (deviceStr == null || deviceStr.isEmpty()) {
// Controller property
if (property.equalsIgnoreCase("present")) {
controller.present = Boolean.parseBoolean(value);
} else if (property.equalsIgnoreCase("virtualDev")) {
controller.virtualDev = value;
}
return;
}
// Device property
Device device = controller.devices.get(deviceStr);
if (device == null) {
device = new Device();
controller.devices.put(deviceStr, device);
}
if (property.equalsIgnoreCase("deviceType")) {
device.deviceType = value;
} else if (property.equalsIgnoreCase("filename")) {
device.filename = value;
} else if (property.equalsIgnoreCase("present")) {
device.present = Boolean.parseBoolean(value);
}
}
@Override
public boolean addHddTemplate(File diskImage, String hddMode, String redoDir) {
String diskImagePath = diskImage.getName();
DriveBusType bus;
try {
bus = DriveBusType.valueOf(config.get("#SLX_HDD_BUS"));
} catch (Exception e) {
LOGGER.warn("Unknown bus type: " + config.get("#SLX_HDD_BUS") + ". Cannot add hdd config.");
return false;
}
String chipset = config.get("#SLX_HDD_CHIP");
String prefix;
switch (bus) {
case IDE:
prefix = "ide0:0";
addFiltered("ide0.present", "TRUE");
break;
case SATA:
// Cannot happen?... use lsisas1068
case SCSI:
prefix = "scsi0:0";
addFiltered("scsi0.present", "TRUE");
if (chipset != null) {
addFiltered("scsi0.virtualDev", chipset);
}
break;
default:
LOGGER.warn("Unknown HDD bus type: " + bus.toString());
return false;
}
// Gen
addFiltered(prefix + ".present", "TRUE");
addFiltered(prefix + ".deviceType", "disk");
addFiltered(prefix + ".fileName", diskImagePath);
if (hddMode != null) {
addFiltered(prefix + ".mode", hddMode);
addFiltered(prefix + ".redo", "");
addFiltered(prefix + ".redoLogDir", redoDir);
}
config.remove("#SLX_HDD_BUS");
config.remove("#SLX_HDD_CHIP");
return true;
}
@Override
public boolean addHddTemplate(String diskImagePath, String hddMode, String redoDir) {
DriveBusType bus;
try {
bus = DriveBusType.valueOf(config.get("#SLX_HDD_BUS"));
} catch (Exception e) {
LOGGER.warn("Unknown bus type: " + config.get("#SLX_HDD_BUS") + ". Cannot add hdd config.");
return false;
}
String chipset = config.get("#SLX_HDD_CHIP");
String prefix;
switch (bus) {
case IDE:
prefix = "ide0:0";
addFiltered("ide0.present", "TRUE");
break;
case SATA:
// Cannot happen?... use lsisas1068
case SCSI:
prefix = "scsi0:0";
addFiltered("scsi0.present", "TRUE");
if (chipset != null) {
addFiltered("scsi0.virtualDev", chipset);
}
break;
default:
LOGGER.warn("Unknown HDD bus type: " + bus.toString());
return false;
}
// Gen
addFiltered(prefix + ".present", "TRUE");
addFiltered(prefix + ".deviceType", "disk");
addFiltered(prefix + ".fileName", diskImagePath);
if (hddMode != null) {
addFiltered(prefix + ".mode", hddMode);
addFiltered(prefix + ".redo", "");
addFiltered(prefix + ".redoLogDir", redoDir);
}
config.remove("#SLX_HDD_BUS");
config.remove("#SLX_HDD_CHIP");
return true;
}
public boolean addDefaultNat() {
addFiltered("ethernet0.present", "TRUE");
addFiltered("ethernet0.connectionType", "nat");
return true;
}
public boolean addEthernet(EthernetType type) {
int index = 0;
for (;; ++index) {
if (config.get("ethernet" + index + ".present") == null)
break;
}
return addEthernet(index, type);
}
public boolean addEthernet(int index, EthernetType type) {
String ether = "ethernet" + index;
addFiltered(ether + ".present", "TRUE");
addFiltered(ether + ".connectionType", "custom");
addFiltered(ether + ".vnet", type.vmnet);
if (config.get(ether + ".virtualDev") == null) {
String dev = config.get("ethernet0.virtualDev");
if (dev != null) {
addFiltered(ether + ".virtualDev", dev);
}
}
return true;
}
public void addFloppy(int index, String image, boolean readOnly) {
String pre = "floppy" + index;
addFiltered(pre + ".present", "TRUE");
if (image == null) {
addFiltered(pre + ".startConnected", "FALSE");
addFiltered(pre + ".fileType", "device");
config.remove(pre + ".fileName");
config.remove(pre + ".readonly");
addFiltered(pre + ".autodetect", "TRUE");
} else {
addFiltered(pre + ".startConnected", "TRUE");
addFiltered(pre + ".fileType", "file");
addFiltered(pre + ".fileName", image);
addFiltered(pre + ".readonly", vmBoolean(readOnly));
config.remove(pre + ".autodetect");
}
}
public boolean addCdrom(String image) {
for (String port : new String[] { "ide0:0", "ide0:1", "ide1:0", "ide1:1", "scsi0:1" }) {
if (!isSetAndTrue(port + ".present")) {
addFiltered(port + ".present", "TRUE");
if (image == null) {
addFiltered(port + ".autodetect", "TRUE");
addFiltered(port + ".deviceType", "cdrom-raw");
config.remove(port + ".fileName");
} else {
config.remove(port + ".autodetect");
addFiltered(port + ".deviceType", "cdrom-image");
addFiltered(port + ".fileName", image);
}
return true;
}
}
return false;
}
private static String vmBoolean(boolean var) {
return Boolean.toString(var).toUpperCase();
}
private static String vmInteger(int val) {
return Integer.toString(val);
}
public boolean disableSuspend() {
addFiltered("suspend.disabled", "TRUE");
return true;
}
public boolean addDisplayName(String name) {
addFiltered("displayName", name);
return true;
}
public boolean addRam(int mem) {
addFiltered("memsize", Integer.toString(mem));
return true;
}
public void setOs(String vendorOsId) {
addFiltered("guestOS", vendorOsId);
setOs("vmware", vendorOsId);
}
@Override
public byte[] getFilteredDefinitionArray() {
return config.toString(true, false).getBytes(StandardCharsets.UTF_8);
}
public byte[] getDefinitionArray() {
return config.toString(false, false).getBytes(StandardCharsets.UTF_8);
}
@Override
public Virtualizer getVirtualizer() {
return virtualizer;
}
private static class Device {
public boolean present = false;
public String deviceType = null;
public String filename = null;
@Override
public String toString() {
return filename + " is " + deviceType + " (present: " + present + ")";
}
}
private static class Controller {
public boolean present = true; // Seems to be implicit, seen at least for IDE...
public String virtualDev = null;
Map<String, Device> devices = new HashMap<>();
@Override
public String toString() {
return virtualDev + " is (present: " + present + "): " + devices.toString();
}
}
public static enum EthernetType {
NAT("vmnet1"), BRIDGED("vmnet0"), HOST_ONLY("vmnet2");
public final String vmnet;
private EthernetType(String vnet) {
this.vmnet = vnet;
}
}
@Override
public void enableUsb(boolean enabled) {
addFiltered("usb.present", vmBoolean(enabled));
addFiltered("ehci.present", vmBoolean(enabled));
}
@Override
public void applySettingsForLocalEdit() {
addFiltered("gui.applyHostDisplayScalingToGuest", "FALSE");
}
public String getValue(String key) {
return config.get(key);
}
// SOUND
public static enum SoundCardType {
NONE(false, null, "None"), DEFAULT(true, null, "(default)"), SOUND_BLASTER(true, "sb16",
"Sound Blaster 16"), ES(true, "es1371",
"ES 1371"), HD_AUDIO(true, "hdaudio", "Intel Integrated HD Audio");
public final boolean isPresent;
public final String value;
public final String displayName;
private SoundCardType(boolean present, String value, String dName) {
this.isPresent = present;
this.value = value;
this.displayName = dName;
}
}
public void setSoundCard(SoundCardType type) {
addFiltered("sound.present", vmBoolean(type.isPresent));
if (type.value != null) {
addFiltered("sound.virtualDev", type.value);
} else {
config.remove("sound.virtualDev");
}
}
public SoundCardType getSoundCard() {
if (!isSetAndTrue("sound.present") || !isSetAndTrue("sound.autodetect")) {
return SoundCardType.NONE;
}
String current = config.get("sound.virtualDev");
if (current != null) {
for (SoundCardType type : SoundCardType.values()) {
if (current.equals(type.value)) {
return type;
}
}
}
return SoundCardType.DEFAULT;
}
// 3DAcceleration
public static enum DDAcceleration {
OFF(false, "Off"), ON(true, "On");
public final boolean isPresent;
public final String displayName;
private DDAcceleration(boolean present, String dName) {
this.isPresent = present;
this.displayName = dName;
}
}
public void setDDAcceleration(DDAcceleration type) {
addFiltered("mks.enable3d", vmBoolean(type.isPresent));
}
public DDAcceleration getDDAcceleration() {
if (isSetAndTrue("mks.enable3d")) {
return DDAcceleration.ON;
} else {
return DDAcceleration.OFF;
}
}
// Virtual hardware version
public static enum HWVersion {
NONE(0, "(invalid)"), THREE(3, " 3 (Workstation 4/5, Player 1)"), FOUR(4,
" 4 (Workstation 4/5, Player 1/2, Fusion 1)"), SIX(6, " 6 (Workstation 6)"), SEVEN(7,
" 7 (Workstation 6.5/7, Player 3, Fusion 2/3)"), EIGHT(8,
" 8 (Workstation 8, Player/Fusion 4)"), NINE(9,
" 9 (Workstation 9, Player/Fusion 5)"), TEN(10,
"10 (Workstation 10, Player/Fusion 6)"), ELEVEN(11,
"11 (Workstation 11, Player/Fusion 7)"), TWELVE(12,
"12 (Workstation/Player 12, Fusion 8)");
public final int version;
public final String displayName;
private HWVersion(int vers, String dName) {
this.version = vers;
this.displayName = dName;
}
}
public void setHWVersion(HWVersion type) {
addFiltered("virtualHW.version", vmInteger(type.version));
}
public HWVersion getHWVersion() {
int currentValue = Util.parseInt(config.get("virtualHW.version"), -1);
for (HWVersion ver : HWVersion.values()) {
if (currentValue == ver.version) {
return ver;
}
}
return HWVersion.NONE;
}
// Virtual network adapter
public static enum EthernetDevType {
AUTO(null, "(default)"), PCNET32("vlance", "AMD PCnet32"), E1000("e1000", "Intel E1000 (PCI)"), E1000E("e1000e",
"Intel E1000e (PCI-Express)"), VMXNET("vmxnet", "VMXnet"), VMXNET3("vmxnet3", "VMXnet 3");
public final String value;
public final String displayName;
private EthernetDevType(String value, String dName) {
this.value = value;
this.displayName = dName;
}
}
public void setEthernetDevType(int cardIndex, EthernetDevType type) {
if (type.value != null) {
addFiltered("ethernet" + cardIndex + ".virtualDev", type.value);
} else {
config.remove("ethernet" + cardIndex + ".virtualDev");
}
}
public EthernetDevType getEthernetDevType(int cardIndex) {
String temp = config.get("ethernet" + cardIndex + ".virtualDev");
if (temp != null) {
for (EthernetDevType type : EthernetDevType.values()) {
if (temp.equals(type.value)) {
return type;
}
}
}
return EthernetDevType.AUTO;
}
@Override
public void reWrite() {
// TODO Auto-generated method stub
}
@Override
public boolean addCpuCoreCount(int nrOfCores) {
// TODO Auto-generated method stub
return false;
}
}