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.thrifthelper.TConst; import org.openslx.util.Util; import org.openslx.util.vm.VmwareConfig.ConfigEntry; class VmWareSoundCardMeta { public final boolean isPresent; public final String value; public VmWareSoundCardMeta(boolean present, String val) { isPresent = present; value = val; } } class VmWareDDAccelMeta { public final boolean isPresent; public VmWareDDAccelMeta(boolean present) { isPresent = present; } } class VmWareHWVersionMeta { public final int version; public VmWareHWVersionMeta(int vers) { version = vers; } } class VmWareEthernetDevTypeMeta { public final String value; public VmWareEthernetDevTypeMeta(String val) { value = val; } } public class VmwareMetaData extends VmMetaData { private static final Logger LOGGER = Logger.getLogger(VmwareMetaData.class); private static final Virtualizer virtualizer = new Virtualizer(TConst.VIRT_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()); } } public static enum EthernetType { NAT("vmnet1"), BRIDGED("vmnet0"), HOST_ONLY("vmnet2"); public final String vmnet; private EthernetType(String vnet) { this.vmnet = vnet; } } private final Map disks = new HashMap<>(); public VmwareMetaData(List osList, File file) throws IOException, UnsupportedVirtualizerFormatException { super(osList); this.config = new VmwareConfig(file); init(); } public VmwareMetaData(List osList, byte[] vmxContent, int length) throws UnsupportedVirtualizerFormatException { super(osList); this.config = new VmwareConfig(vmxContent, length); // still unfiltered init(); // now filtered } private void init() { registerVirtualHW(); for (Entry 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 cEntry : disks.entrySet()) { Controller controller = cEntry.getValue(); String controllerType = cEntry.getKey(); if (!controller.present) { continue; } for (Entry 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)); } } // TODO check if this machine is in a paused/suspended state this.isMachineSnapshot = false; // 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 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(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) { return addHddTemplate(diskImage.getName(), hddMode, redoDir); } @Override public boolean addHddTemplate(String diskImagePath, String hddMode, String redoDir) { if (diskImagePath.isEmpty()) { LOGGER.error("Empty disk image path given!"); return false; } 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(VmMetaData.EtherType type) { boolean ret = false; int index = 0; for (;; ++index) { if (config.get("ethernet" + index + ".present") == null) { break; } } switch (type) { case NAT: ret = addEthernet(index, EthernetType.NAT); break; case BRIDGED: ret = addEthernet(index, EthernetType.BRIDGED); break; case HOST_ONLY: ret = addEthernet(index, EthernetType.HOST_ONLY); break; default: // Should not come to this... break; } return ret; } 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); } @Override public boolean disableSuspend() { addFiltered("suspend.disabled", "TRUE"); return true; } @Override public boolean addDisplayName(String name) { addFiltered("displayName", name); return true; } @Override public boolean addRam(int mem) { addFiltered("memsize", Integer.toString(mem)); return true; } public void setOs(String vendorOsId) { addFiltered("guestOS", vendorOsId); setOs(TConst.VIRT_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 devices = new HashMap<>(); @Override public String toString() { return virtualDev + " is (present: " + present + "): " + devices.toString(); } } @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); } public void setSoundCard(VmMetaData.SoundCardType type) { VmWareSoundCardMeta soundCardMeta = soundCards.get(type); addFiltered("sound.present", vmBoolean(soundCardMeta.isPresent)); if (soundCardMeta.value != null) { addFiltered("sound.virtualDev", soundCardMeta.value); } else { config.remove("sound.virtualDev"); } } public VmMetaData.SoundCardType getSoundCard() { if (!isSetAndTrue("sound.present") || !isSetAndTrue("sound.autodetect")) { return VmMetaData.SoundCardType.NONE; } String current = config.get("sound.virtualDev"); if (current != null) { VmWareSoundCardMeta soundCardMeta = null; for (VmMetaData.SoundCardType type : VmMetaData.SoundCardType.values()) { soundCardMeta = soundCards.get(type); if (soundCardMeta != null) { if (current.equals(soundCardMeta.value)) { return type; } } } } return VmMetaData.SoundCardType.DEFAULT; } public void setDDAcceleration(VmMetaData.DDAcceleration type) { VmWareDDAccelMeta ddaMeta = ddacc.get(type); addFiltered("mks.enable3d", vmBoolean(ddaMeta.isPresent)); } public VmMetaData.DDAcceleration getDDAcceleration() { if (isSetAndTrue("mks.enable3d")) { return VmMetaData.DDAcceleration.ON; } else { return VmMetaData.DDAcceleration.OFF; } } public void setHWVersion(VmMetaData.HWVersion type) { VmWareHWVersionMeta hwVersionMeta = hwversion.get(type); addFiltered("virtualHW.version", vmInteger(hwVersionMeta.version)); } public VmMetaData.HWVersion getHWVersion() { int currentValue = Util.parseInt(config.get("virtualHW.version"), -1); VmWareHWVersionMeta hwVersionMeta = null; for (VmMetaData.HWVersion ver : VmMetaData.HWVersion.values()) { hwVersionMeta = hwversion.get(ver); if (hwVersionMeta == null) { continue; } if (currentValue == hwVersionMeta.version) { return ver; } } return HWVersion.NONE; } public void setEthernetDevType(int cardIndex, VmMetaData.EthernetDevType type) { VmWareEthernetDevTypeMeta ethernetDevTypeMeta = networkCards.get(type); if (ethernetDevTypeMeta.value != null) { addFiltered("ethernet" + cardIndex + ".virtualDev", ethernetDevTypeMeta.value); } else { config.remove("ethernet" + cardIndex + ".virtualDev"); } } public EthernetDevType getEthernetDevType(int cardIndex) { String temp = config.get("ethernet" + cardIndex + ".virtualDev"); if (temp != null) { VmWareEthernetDevTypeMeta ethernetDevTypeMeta = null; for (EthernetDevType type : VmMetaData.EthernetDevType.values()) { ethernetDevTypeMeta = networkCards.get(type); if (ethernetDevTypeMeta == null) { continue; } if (temp.equals(ethernetDevTypeMeta.value)) { return type; } } } return EthernetDevType.AUTO; } @Override public boolean addCpuCoreCount(int numCores) { // TODO actually add the cpu core count to the machine description return false; } public void registerVirtualHW() { soundCards.put(VmMetaData.SoundCardType.NONE, new VmWareSoundCardMeta(false, null)); soundCards.put(VmMetaData.SoundCardType.DEFAULT, new VmWareSoundCardMeta(true, null)); soundCards.put(VmMetaData.SoundCardType.SOUND_BLASTER, new VmWareSoundCardMeta(true, "sb16")); soundCards.put(VmMetaData.SoundCardType.ES, new VmWareSoundCardMeta(true, "es1371")); soundCards.put(VmMetaData.SoundCardType.HD_AUDIO, new VmWareSoundCardMeta(true, "hdaudio")); ddacc.put(VmMetaData.DDAcceleration.OFF, new VmWareDDAccelMeta(false)); ddacc.put(VmMetaData.DDAcceleration.ON, new VmWareDDAccelMeta(true)); hwversion.put(VmMetaData.HWVersion.NONE, new VmWareHWVersionMeta(0)); hwversion.put(VmMetaData.HWVersion.THREE, new VmWareHWVersionMeta(3)); hwversion.put(VmMetaData.HWVersion.FOUR, new VmWareHWVersionMeta(4)); hwversion.put(VmMetaData.HWVersion.SIX, new VmWareHWVersionMeta(6)); hwversion.put(VmMetaData.HWVersion.SEVEN, new VmWareHWVersionMeta(7)); hwversion.put(VmMetaData.HWVersion.EIGHT, new VmWareHWVersionMeta(8)); hwversion.put(VmMetaData.HWVersion.NINE, new VmWareHWVersionMeta(9)); hwversion.put(VmMetaData.HWVersion.TEN, new VmWareHWVersionMeta(10)); hwversion.put(VmMetaData.HWVersion.ELEVEN, new VmWareHWVersionMeta(11)); hwversion.put(VmMetaData.HWVersion.TWELVE, new VmWareHWVersionMeta(12)); networkCards.put(VmMetaData.EthernetDevType.AUTO, new VmWareEthernetDevTypeMeta(null)); networkCards.put(VmMetaData.EthernetDevType.PCNET32, new VmWareEthernetDevTypeMeta("vlance")); networkCards.put(VmMetaData.EthernetDevType.E1000, new VmWareEthernetDevTypeMeta("e1000")); networkCards.put(VmMetaData.EthernetDevType.E1000E, new VmWareEthernetDevTypeMeta("e1000e")); networkCards.put(VmMetaData.EthernetDevType.VMXNET, new VmWareEthernetDevTypeMeta("vmxnet")); networkCards.put(VmMetaData.EthernetDevType.VMXNET3, new VmWareEthernetDevTypeMeta("vmxnet3")); } }