package org.openslx.util.vm; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.ByteBuffer; import java.nio.charset.Charset; 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; 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; // Init static members static { String[] list = { "^guestos", "^uuid\\.bios", "^config\\.version", "^ehci\\.", "mks\\.", "virtualhw\\.", "^sound\\.", "\\.pcislotnumber$", "^pcibridge", "\\.virtualdev$" }; whitelist = new Pattern[ list.length ]; for ( int i = 0; i < list.length; ++i ) { whitelist[i] = Pattern.compile( list[i] ); } } private final Map disks = new HashMap<>(); private final StringBuilder cleanedConfig = new StringBuilder( 200 ); public VmwareMetaData( List osList, File file ) throws IOException { super( osList ); int todo = (int)Math.min( 100000, file.length() ); int offset = 0; byte[] data = new byte[ todo ]; FileInputStream fr = null; try { fr = new FileInputStream( file ); while ( todo > 0 ) { int ret = fr.read( data, offset, todo ); if ( ret <= 0 ) break; todo -= ret; offset += ret; } } finally { Util.safeClose( fr ); } init( data, offset ); } public VmwareMetaData( List osList, byte[] vmxContent, int length ) { super( osList ); init( vmxContent, length ); } private void init( byte[] vmxContent, int length ) { String csName = detectCharset( new ByteArrayInputStream( vmxContent, 0, length ) ); Charset cs = null; try { cs = Charset.forName( csName ); } catch ( Exception e ) { LOGGER.warn( "Could not instantiate charset " + csName, e ); } if ( cs == null ) cs = StandardCharsets.ISO_8859_1; try { loadVmx( new ByteArrayInputStream( vmxContent, 0, length ), cs ); } catch ( IOException e ) { LOGGER.warn( "Exception when loading vmx from byte array (how!?)", e ); } } private void loadVmx( InputStream is, Charset cs ) throws IOException { LOGGER.info( "Loading VMX with charset " + cs.name() ); // Prepare filtered vmx addFiltered( ".encoding", "UTF-8" ); // Read line-by-line BufferedReader reader = null; try { reader = new BufferedReader( new InputStreamReader( is, cs ) ); String line; while ( ( line = reader.readLine() ) != null ) { KeyValuePair entry = parse( line ); if ( entry != null ) handleLoadEntry( entry ); } } finally { Util.safeClose( reader ); } // 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.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 ) { cleanedConfig.append( key ).append( " = \"" ).append( value ).append( "\"\n" ); } private void handleLoadEntry( KeyValuePair entry ) { String lowerKey = entry.key.toLowerCase(); // Cleaned vmx construction for ( Pattern exp : whitelist ) { if ( exp.matcher( lowerKey ).find() ) { addFiltered( entry.key, entry.value ); break; } } // // Dig Usable meta data if ( lowerKey.equals( "guestos" ) ) { setOs( "vmware", entry.value ); return; } if ( lowerKey.equals( "displayname" ) ) { displayName = entry.value; return; } Matcher hdd = hddKey.matcher( entry.key ); if ( hdd.find() ) { handleHddEntry( hdd.group( 1 ).toLowerCase(), hdd.group( 2 ), hdd.group( 3 ), entry.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 ); } } private String detectCharset( InputStream is ) { BufferedReader csDetectReader = null; try { csDetectReader = new BufferedReader( new InputStreamReader( is, StandardCharsets.ISO_8859_1 ) ); String line; while ( ( line = csDetectReader.readLine() ) != null ) { KeyValuePair entry = parse( line ); if ( entry == null ) continue; if ( entry.key.equals( ".encoding" ) ) { return entry.value; } } } catch ( Exception e ) { LOGGER.warn( "Could not detect charset, fallback to latin1", e ); } finally { Util.safeClose( csDetectReader ); } // Dumb fallback return "ISO-8859-1"; } private static final Pattern settingMatcher = Pattern.compile( "^\\s*([a-z0-9\\.\\:]+)\\s*=\\s*\"(.*)\"\\s*$", Pattern.CASE_INSENSITIVE ); private KeyValuePair parse( String line ) { Matcher matcher = settingMatcher.matcher( line ); if ( !matcher.matches() ) return null; return new KeyValuePair( matcher.group( 1 ), matcher.group( 2 ) ); } @Override public ByteBuffer getFilteredDefinition() { return ByteBuffer.wrap( cleanedConfig.toString().getBytes( StandardCharsets.UTF_8 ) ); } @Override public Virtualizer getVirtualizer() { return virtualizer; } // ############################### private static class KeyValuePair { public final String key; public final String value; public KeyValuePair( String key, String value ) { this.key = key; this.value = value; } } private static class ConfigEntry { private String value; private boolean forFiltered; private boolean forGenerated; public ConfigEntry( String value ) { this.value = value; } public ConfigEntry filtered( boolean set ) { this.forFiltered = set; return this; } public ConfigEntry generated( boolean set ) { this.forGenerated = set; return this; } } private static class ConfigEntries { private Map entries = new HashMap<>(); public ConfigEntry set( String key, String value ) { return entries.put( key, new ConfigEntry( value ) ); } public ConfigEntry set( KeyValuePair entry ) { return set( entry.key, entry.value ); } public String get( boolean filteredRequired, boolean generatedRequired ) { StringBuilder sb = new StringBuilder( 300 ); for ( Entry entry : entries.entrySet() ) { ConfigEntry value = entry.getValue(); if ( ( !filteredRequired || value.forFiltered ) && ( !generatedRequired || value.forGenerated ) ) { sb.append( entry.getKey() ); sb.append( " = \"" ); sb.append( entry.getValue() ); sb.append( "\"\n" ); } } return sb.toString(); } } 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(); } } }