summaryrefslogtreecommitdiffstats
path: root/src/main/java/org/openslx/virtualization/disk/DiskImageVmdk.java
blob: 77986ef0fc4b51c53485d2938481c8e48522424e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
package org.openslx.virtualization.disk;

import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;

import org.openslx.util.Util;
import org.openslx.virtualization.configuration.VirtualizationConfigurationVmwareFileFormat;
import org.openslx.virtualization.Version;
import org.openslx.virtualization.configuration.VirtualizationConfigurationException;

/**
 * VMDK (sparse extent) disk image for virtual machines.
 * 
 * @author Manuel Bentele
 * @version 1.0
 */
public class DiskImageVmdk extends DiskImage
{
	/**
	 * Big endian representation of the little endian magic bytes <code>KDMV</code>.
	 */
	private static final int VMDK_MAGIC = 0x4b444d56;

	/**
	 * Size of a VMDK disk image data cluster in bytes.
	 */
	private static final int VMDK_SECTOR_SIZE = 512;

	/**
	 * Default hardware version of a VMDK disk image.
	 */
	private static final int VMDK_DEFAULT_HW_VERSION = 10;

	/**
	 * Stores disk configuration if VMDK disk image contains an embedded descriptor file.
	 */
	private final VirtualizationConfigurationVmwareFileFormat vmdkConfig;

	/**
	 * Creates a new VMDK disk image from an existing VMDK image file.
	 * 
	 * @param diskImage file to a VMDK disk storing the image content.
	 * 
	 * @throws DiskImageException parsing of the VMDK's embedded descriptor file failed.
	 */
	DiskImageVmdk( RandomAccessFile diskImage ) throws DiskImageException
	{
		super( diskImage );

		this.vmdkConfig = this.parseVmdkConfig();
	}

	/**
	 * Probe specified file with unknown format to be a VMDK disk image file.
	 * 
	 * @param diskImage file with unknown format that should be probed.
	 * @return state whether file is a VMDK disk image or not.
	 * 
	 * @throws DiskImageException cannot probe specified file with unknown format.
	 */
	public static boolean probe( RandomAccessFile diskImage ) throws DiskImageException
	{
		final boolean isVmdkImageFormat;

		// goto the beginning of the disk image to read the magic bytes
		final int diskImageMagic = DiskImageUtils.readInt( diskImage, 0 );

		// check if disk image's magic bytes can be found
		if ( diskImageMagic == DiskImageVmdk.VMDK_MAGIC ) {
			isVmdkImageFormat = true;
		} else {
			isVmdkImageFormat = false;
		}

		return isVmdkImageFormat;
	}

	/**
	 * Returns the creation type from the VMDK's embedded descriptor file.
	 * 
	 * @return creation type from the VMDK's embedded descriptor file.
	 */
	private String getCreationType()
	{
		final VirtualizationConfigurationVmwareFileFormat vmdkConfig = this.getVmdkConfig();
		final String vmdkCreationType;

		if ( vmdkConfig == null ) {
			// VMDK disk image does not contain any descriptor file
			// assume that the file is not stand alone
			vmdkCreationType = null;
		} else {
			// VMDK disk image contains a descriptor file
			// get creation type from the content of the descriptor file
			vmdkCreationType = this.vmdkConfig.get( "createType" );
		}

		return vmdkCreationType;
	}

	/**
	 * Parse the configuration of the VMDK's embedded descriptor file.
	 * 
	 * @return parsed configuration of the VMDK's embedded descriptor file.
	 * 
	 * @throws DiskImageException parsing of the VMDK's embedded descriptor file failed.
	 */
	protected VirtualizationConfigurationVmwareFileFormat parseVmdkConfig() throws DiskImageException
	{
		final RandomAccessFile diskFile = this.getDiskImage();
		final VirtualizationConfigurationVmwareFileFormat vmdkConfig;

		// get offset and size of descriptor file embedded into the VMDK disk image
		final long vmdkDescriptorSectorOffset = Long.reverseBytes( DiskImageUtils.readLong( diskFile, 28 ) );
		final long vmdkDescriptorSectorSize = Long.reverseBytes( DiskImageUtils.readLong( diskFile, 36 ) );

		if ( vmdkDescriptorSectorOffset > 0 ) {
			// get content of descriptor file embedded into the VMDK disk image
			final long vmdkDescriptorOffset = vmdkDescriptorSectorOffset * DiskImageVmdk.VMDK_SECTOR_SIZE;
			final long vmdkDescriptorSizeMax = vmdkDescriptorSectorSize * DiskImageVmdk.VMDK_SECTOR_SIZE;
			final String descriptorStr = new String ( DiskImageUtils.readBytesAsArray( diskFile, vmdkDescriptorOffset,
					Long.valueOf( vmdkDescriptorSizeMax ).intValue() ), StandardCharsets.US_ASCII );

			// get final length of the content within the sectors to be able to trim all 'zero' characters
			final int vmdkDescriptorSize = descriptorStr.indexOf( 0 );

			// if final length of the content is invalid, throw an exception 
			if ( vmdkDescriptorSize > vmdkDescriptorSizeMax || vmdkDescriptorSize < 0 ) {
				final String errorMsg = "Embedded descriptor size in VMDK disk image is invalid!";
				throw new DiskImageException( errorMsg );
			}

			// trim all 'zero' characters at the end of the descriptor content to avoid errors during parsing
			final String configStr = descriptorStr.substring( 0, vmdkDescriptorSize );

			// create configuration instance from content of the descriptor file
			try {
				vmdkConfig = new VirtualizationConfigurationVmwareFileFormat( configStr.getBytes(), vmdkDescriptorSize );
			} catch ( VirtualizationConfigurationException e ) {
				throw new DiskImageException( e.getLocalizedMessage() );
			}
		} else {
			// there is no descriptor file embedded into the VMDK disk image
			vmdkConfig = null;
		}

		return vmdkConfig;
	}

	/**
	 * Returns parsed configuration of the VMDK's embedded descriptor file.
	 * 
	 * @return parsed configuration of the VMDK's embedded descriptor file.
	 */
	protected VirtualizationConfigurationVmwareFileFormat getVmdkConfig()
	{
		return this.vmdkConfig;
	}

	/**
	 * Returns the hardware version from the VMDK's embedded descriptor file.
	 * 
	 * If the VMDK's embedded descriptor file does not contain any hardware version configuration
	 * entry, the default hardware version (see {@link #VMDK_DEFAULT_HW_VERSION}) is returned.
	 * 
	 * @return hardware version from the VMDK's embedded descriptor file.
	 * 
	 * @throws DiskImageException unable to obtain the VMDK's hardware version of the disk image
	 *            format.
	 */
	public Version getHwVersion() throws DiskImageException
	{
		final VirtualizationConfigurationVmwareFileFormat vmdkConfig = this.getVmdkConfig();
		final Version hwVersion;

		if ( vmdkConfig != null ) {
			// VMDK image contains a hardware version, so return parsed hardware version
			// if hardware version cannot be parsed, return default hardware version 
			final String hwVersionStr = vmdkConfig.get( "ddb.virtualHWVersion" );

			final int hwVersionMajor = Util.parseInt( hwVersionStr, DiskImageVmdk.VMDK_DEFAULT_HW_VERSION );
			hwVersion = new Version( Integer.valueOf( hwVersionMajor ).shortValue() );
		} else {
			// VMDK image does not contain any hardware version, so return default hardware version
			final int hwVersionMajor = DiskImageVmdk.VMDK_DEFAULT_HW_VERSION;
			hwVersion = new Version( Integer.valueOf( hwVersionMajor ).shortValue() );
		}

		return hwVersion;
	}

	@Override
	public boolean isStandalone() throws DiskImageException
	{
		final String vmdkCreationType = this.getCreationType();
		final boolean vmdkStandalone;

		if ( vmdkCreationType != null ) {
			// creation type is defined, so check if VMDK disk image is a snapshot
			if ( this.isSnapshot() ) {
				// VMDK disk image is a snapshot and not stand alone
				vmdkStandalone = false;
			} else {
				// VMDK disk image is not a snapshot
				// determine stand alone disk image property
				vmdkStandalone = vmdkCreationType.equalsIgnoreCase( "streamOptimized" ) ||
						vmdkCreationType.equalsIgnoreCase( "monolithicSparse" );
			}
		} else {
			// creation type is not defined
			// assume that the file is not stand alone
			vmdkStandalone = false;
		}

		return vmdkStandalone;
	}

	@Override
	public boolean isCompressed() throws DiskImageException
	{
		final String vmdkCreationType = this.getCreationType();
		final boolean vmdkCompressed;

		if ( vmdkCreationType != null && vmdkCreationType.equalsIgnoreCase( "streamOptimized" ) ) {
			// creation type is defined, and VMDK disk image is compressed
			vmdkCompressed = true;
		} else {
			// creation type for compression is not defined
			// assume that the file is not compressed
			vmdkCompressed = false;
		}

		return vmdkCompressed;
	}

	@Override
	public boolean isSnapshot() throws DiskImageException
	{
		final VirtualizationConfigurationVmwareFileFormat vmdkConfig = this.getVmdkConfig();
		final boolean vmdkSnapshot;

		if ( vmdkConfig == null ) {
			// VMDK disk image does not contain any descriptor file
			// assume that the file is not a snapshot
			vmdkSnapshot = false;
		} else {
			// get parent CID to determine snapshot disk image property
			final String parentCid = vmdkConfig.get( "parentCID" );

			if ( parentCid != null && !parentCid.equalsIgnoreCase( "ffffffff" ) ) {
				// link to parent content identifier is defined, so VMDK disk image is a snapshot
				vmdkSnapshot = true;
			} else {
				// link to parent content identifier is not defined, so VMDK disk image is not a snapshot
				vmdkSnapshot = false;
			}
		}

		return vmdkSnapshot;
	}

	@Override
	public Version getVersion() throws DiskImageException
	{
		final RandomAccessFile diskFile = this.getDiskImage();
		final int vmdkVersion = Integer.reverseBytes( DiskImageUtils.readInt( diskFile, 4 ) );

		return new Version( Integer.valueOf( vmdkVersion ).shortValue() );
	}

	@Override
	public String getDescription() throws DiskImageException
	{
		return null;
	}

	@Override
	public ImageFormat getFormat()
	{
		return ImageFormat.VMDK;
	}
}