summaryrefslogblamecommitdiffstats
path: root/src/main/java/org/openslx/util/vm/DiskImage.java
blob: 30fea993cde8c39e9667b471cc100375219e4339 (plain) (tree)
1
2
3
4
5
6
7
8
9





                                     
                        
 
                               
                                                 
                                       
                             


                      
                                                                                 
           
                                                          

                                                         
                                                        
                                                         


                               
                                                               







                                                       
                                                                                   


                                            






                                                                                
                                                                  
                                            
                                                                      
                                           
                                                                
                                             





                                          
                                        
                                        
                                      
                                   
                                            
 





                                                                                                            
         
                                                                                  
                                                                                   
                               







                                                                                                 







                                                                                                
                                                                               
                                                                                  













                                                                                                                                         
                                         

                                 








































                                                                                                                             
                                                                 









                                                                                                             
                         
 























                                                           
                 
                                                        










                                                           
                                                                            


                                     

                                                                               
                                                                                                                 





                                                                                   
 
package org.openslx.util.vm;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Arrays;

import org.apache.log4j.Logger;
import org.openslx.bwlp.thrift.iface.Virtualizer;
import org.openslx.thrifthelper.TConst;
import org.openslx.util.Util;

public class DiskImage
{
	private static final Logger LOGGER = Logger.getLogger( DiskImage.class );
	/**
	 * Big endian representation of the 4 bytes 'KDMV'
	 */
	private static final int VMDK_MAGIC = 0x4b444d56;
	private static final int VDI_MAGIC = 0x7f10dabe;
	private static final int QEMU_MAGIC = 0x514649fb;

	public enum ImageFormat
	{
		VMDK( "vmdk" ), QCOW2( "qcow2" ), VDI( "vdi" );

		public final String extension;

		private ImageFormat( String extension )
		{
			this.extension = extension;
		}

		public static ImageFormat defaultForVirtualizer( Virtualizer virt )
		{
			if ( virt == null )
				return null;
			return defaultForVirtualizer( virt.virtId );
		}

		public static ImageFormat defaultForVirtualizer( String virtId )
		{
			if ( virtId == null )
				return null;
			if ( virtId.equals( TConst.VIRT_VMWARE ) )
				return VMDK;
			if ( virtId.equals( TConst.VIRT_VIRTUALBOX ) )
				return VDI;
			if ( virtId.equals( TConst.VIRT_QEMU ) )
				return QCOW2;
			return null;
		}
	}

	public final boolean isStandalone;
	public final boolean isCompressed;
	public final boolean isSnapshot;
	public final ImageFormat format;
	public final String subFormat;
	public final int hwVersion;
	public final String diskDescription;

	public ImageFormat getImageFormat()
	{
		return format;
	}

	public DiskImage( File disk ) throws FileNotFoundException, IOException, UnknownImageFormatException
	{
		LOGGER.debug( "Validating disk file: " + disk.getAbsolutePath() );
		try ( RandomAccessFile file = new RandomAccessFile( disk, "r" ) ) {
			// vmdk
			boolean isVmdkMagic = ( file.readInt() == VMDK_MAGIC );
			if ( isVmdkMagic || file.length() < 4096 ) {
				if ( isVmdkMagic ) {
					file.seek( 512 );
				} else {
					file.seek( 0 );
				}
				byte[] buffer = new byte[ (int)Math.min( 2048, file.length() ) ];
				file.readFully( buffer );
				VmwareConfig config;
				try {
					config = new VmwareConfig( buffer, findNull( buffer ) );
				} catch ( UnsupportedVirtualizerFormatException e ) {
					config = null;
				}
				if ( config != null ) {
					String sf = config.get( "createType" );
					String parent = config.get( "parentCID" );
					if ( sf != null || parent != null ) {
						subFormat = sf;
						this.isStandalone = isStandaloneCreateType( subFormat, parent );
						this.isCompressed = subFormat != null && subFormat.equalsIgnoreCase( "streamOptimized" );
						this.isSnapshot = parent != null && !parent.equalsIgnoreCase( "ffffffff" );
						this.format = ImageFormat.VMDK;
						String hwv = config.get( "ddb.virtualHWVersion" );
						if ( hwv == null ) {
							this.hwVersion = 10;
						} else {
							this.hwVersion = Util.parseInt( hwv, 10 );
						}
						this.diskDescription = null;
						return;
					}
				}
			}
			// Format spec from: https://forums.virtualbox.org/viewtopic.php?t=8046
			// First 64 bytes are the opening tag: <<< .... >>>
			// which we don't care about, then comes the VDI signature
			file.seek( 64 );
			if ( file.readInt() == VDI_MAGIC ) {
				// skip the next 4 ints as they don't interest us:
				// - VDI version
				// - size of header, strangely irrelevant?
				// - image type, 1 for dynamic allocated storage, 2 for fixed size
				// - image flags, seem to be always 00 00 00 00
				file.skipBytes( 4 * 4 );

				// next 256 bytes are image description
				byte[] imageDesc = new byte[ 256 ];
				file.readFully( imageDesc );
				// next sections are irrelevant (int if not specified):
				// - offset blocks
				// - offset data
				// - cylinders
				// - heads
				// - sectors
				// - sector size
				// - <unused>
				// - disk size (long = 8 bytes)
				// - block size
				// - block extra data
				// - blocks in hdd
				// - blocks allocated
				file.skipBytes( 4 * 13 );

				// now it gets interesting, UUID of VDI
				byte[] diskUuid = new byte[ 16 ];
				file.readFully( diskUuid );
				// last snapshot uuid, mostly uninteresting since we don't support snapshots -> skip 16 bytes
				// TODO: meaning of "uuid link"? for now, skip 16
				file.skipBytes( 32 );

				// parent uuid, indicator if this VDI is a snapshot or not
				byte[] parentUuid = new byte[ 16 ];
				file.readFully( parentUuid );
				byte[] zeroUuid = new byte[ 16 ];
				Arrays.fill( zeroUuid, (byte)0 );
				this.isSnapshot = !Arrays.equals( parentUuid, zeroUuid );
				// VDI does not seem to support split VDI files so always standalone
				this.isStandalone = true;
				// compression is done by sparsifying the disk files, there is no flag for it
				this.isCompressed = false;
				this.format = ImageFormat.VDI;
				this.subFormat = null;
				this.diskDescription = new String( imageDesc );
				this.hwVersion = 0;
				return;
			}

      file.seek(0);
      //TODO: Standalone & Snapshot
      if (file.readInt() == QEMU_MAGIC) {
          //skip the next 14 ints as they don't interest us
          // - QFI version           (4 bytes)
          // - backing file offset   (8 bytes)
          // - backing               (8 bytes)
          // - crypt method          (4 bytes)
          // - l1 size               (4 bytes)
          // - l1 table offset       (8 bytes)
          // - refcount table offset (8 bytes)
          // - refcount cluster      (4 bytes)
          file.skipBytes( 14 * 4 );
          this.isSnapshot = file.readInt() > 0;
          //skip the next 14 ints as they don't interest us
          file.seek(374);
          this.isCompressed = file.read() == 1;
          this.diskDescription = null;
          this.format = ImageFormat.QCOW2;
          this.isStandalone = true;
          this.subFormat = null;
          this.hwVersion = 0;
          return;
      }
		}
		throw new UnknownImageFormatException();
	}

	private int findNull( byte[] buffer )
	{
		for ( int i = 0; i < buffer.length; ++i ) {
			if ( buffer[i] == 0 )
				return i;
		}
		return buffer.length;
	}

	private boolean isStandaloneCreateType( String type, String parent )
	{
		if ( type == null )
			return false;
		if ( parent != null && !parent.equalsIgnoreCase( "ffffffff" ) )
			return false;
		return type.equalsIgnoreCase( "streamOptimized" ) || type.equalsIgnoreCase( "monolithicSparse" );
	}

	public static class UnknownImageFormatException extends Exception
	{
		private static final long serialVersionUID = -6647935235475007171L;
	}
}