blob: a007e1c2a4b3fc1413f6d82230b0dc89aca903e2 (
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
|
package org.openslx.virtualization.disk;
import java.io.RandomAccessFile;
import org.openslx.virtualization.Version;
/**
* QCOW2 disk image for virtual machines.
*
* A QCOW2 disk image consists of a header, one L1 table and several L2 tables used for lookup data
* clusters in the file via a two-level lookup. The QCOW2 header contains the following fields:
*
* <pre>
* QCOW2 (version 2 and 3) header format:
*
* magic (4 byte)
* version (4 byte)
* backing_file_offset (8 byte)
* backing_file_size (4 byte)
* cluster_bits (4 byte)
* size (8 byte)
* crypt_method (4 byte)
* l1_size (4 byte)
* l1_table_offset (8 byte)
* refcount_table_offset (8 byte)
* refcount_table_clusters (4 byte)
* nb_snapshots (4 byte)
* snapshots_offset (8 byte)
* incompatible_features (8 byte) [*]
* compatible_features (8 byte) [*]
* autoclear_features (8 byte) [*]
* refcount_order (8 byte) [*]
* header_length (4 byte) [*]
*
* [*] these fields are only available in the QCOW2 version 3 header format
* </pre>
*
* @author Manuel Bentele
* @version 1.0
*/
public class DiskImageQcow2 extends DiskImage
{
/**
* Big endian representation of the big endian QCOW2 magic bytes <code>QFI\xFB</code>.
*/
private static final int QCOW2_MAGIC = 0x514649fb;
/**
* Creates a new QCOW2 disk image from an existing QCOW2 image file.
*
* @param diskImage file to a QCOW2 disk storing the image content.
*/
DiskImageQcow2( RandomAccessFile diskImage )
{
super( diskImage );
}
/**
* Probe specified file with unknown format to be a QCOW2 disk image file.
*
* @param diskImage file with unknown format that should be probed.
* @return state whether file is a QCOW2 disk image or not.
*
* @throws DiskImageException cannot probe specified file with unknown format.
*/
public static boolean probe( RandomAccessFile diskImage ) throws DiskImageException
{
final boolean isQcow2ImageFormat;
// goto the beginning of the disk image to read the disk image
final int diskImageMagic = DiskImageUtils.readInt( diskImage, 0 );
// check if disk image's magic bytes can be found
if ( diskImageMagic == DiskImageQcow2.QCOW2_MAGIC ) {
isQcow2ImageFormat = true;
} else {
isQcow2ImageFormat = false;
}
return isQcow2ImageFormat;
}
@Override
public boolean isStandalone() throws DiskImageException
{
final RandomAccessFile diskFile = this.getDiskImage();
final long qcowBackingFileOffset = DiskImageUtils.readLong( diskFile, 8 );
final boolean qcowStandalone;
// check if QCOW2 image does not refer to any backing file
if ( qcowBackingFileOffset == 0 ) {
qcowStandalone = true;
} else {
qcowStandalone = false;
}
return qcowStandalone;
}
@Override
public boolean isCompressed() throws DiskImageException
{
final RandomAccessFile diskFile = this.getDiskImage();
final boolean qcowUseExtendedL2;
boolean qcowCompressed = false;
// check if QCOW2 image uses extended L2 tables
// extended L2 tables are only possible in QCOW2 version 3 header format
if ( this.getVersion().getMajor() >= Short.valueOf( "3" ) ) {
// read incompatible feature bits
final long qcowIncompatibleFeatures = DiskImageUtils.readLong( diskFile, 72 );
// support for extended L2 tables is enabled if bit 4 is set
qcowUseExtendedL2 = ( ( ( qcowIncompatibleFeatures & 0x000000000010 ) >>> 4 ) == 1 );
} else {
qcowUseExtendedL2 = false;
}
// get number of entries in L1 table
final int qcowL1TableSize = DiskImageUtils.readInt( diskFile, 36 );
// check if a valid L1 table is present
if ( qcowL1TableSize > 0 ) {
// QCOW2 image contains active L1 table with more than 0 entries: l1_size > 0
// search for first L2 table and its first entry to get compression bit
// get cluster bits to calculate the cluster size
final int qcowClusterBits = DiskImageUtils.readInt( diskFile, 20 );
final int qcowClusterSize = ( 1 << qcowClusterBits );
// entries of a L1 table have always the size of 8 byte (64 bit)
final int qcowL1TableEntrySize = 8;
// entries of a L2 table have either the size of 8 or 16 byte (64 or 128 bit)
final int qcowL2TableEntrySize = ( qcowUseExtendedL2 ) ? 16 : 8;
// calculate number of L2 table entries
final int qcowL2TableSize = qcowClusterSize / qcowL2TableEntrySize;
// get offset of L1 table
final long qcowL1TableOffset = DiskImageUtils.readLong( diskFile, 40 );
// check for each L2 table referenced from an L1 table its entries
// until a compressed cluster descriptor is found
for ( long i = 0; i < qcowL1TableSize; i++ ) {
// get offset of current L2 table from the current L1 table entry
final long qcowL1TableEntryOffset = qcowL1TableOffset + ( i * qcowL1TableEntrySize );
final long qcowL1TableEntry = DiskImageUtils.readLong( diskFile, qcowL1TableEntryOffset );
// extract offset (bits 9 - 55) from L1 table entry
final long qcowL2TableOffset = ( qcowL1TableEntry & 0x00fffffffffffe00L );
if ( qcowL2TableOffset == 0 ) {
// L2 table and all clusters described by this L2 table are unallocated
continue;
}
// get each L2 table entry and check if it is a compressed cluster descriptor
for ( long j = 0; j < qcowL2TableSize; j++ ) {
// get current L2 table entry
final long qcowL2TableEntryOffset = qcowL2TableOffset + ( j * qcowL2TableEntrySize );
final long qcowL2TableEntry = DiskImageUtils.readLong( diskFile, qcowL2TableEntryOffset );
// extract cluster type (standard or compressed) (bit 62)
boolean qcowClusterCompressed = ( ( ( qcowL2TableEntry & 0x4000000000000000L ) >>> 62 ) == 1 );
// check if QCOW2 disk image contains at least one compressed cluster descriptor
if ( qcowClusterCompressed ) {
qcowCompressed = true;
break;
}
}
// terminate if one compressed cluster descriptor is already found
if ( qcowCompressed ) {
break;
}
}
} else {
// QCOW2 image does not contain an active L1 table with any entry: l1_size = 0
qcowCompressed = false;
}
return qcowCompressed;
}
@Override
public boolean isSnapshot() throws DiskImageException
{
final RandomAccessFile diskFile = this.getDiskImage();
final int qcowNumSnapshots = DiskImageUtils.readInt( diskFile, 56 );
final boolean qcowSnapshot;
// check if QCOW2 image contains at least one snapshot
if ( qcowNumSnapshots == 0 ) {
qcowSnapshot = true;
} else {
qcowSnapshot = false;
}
return qcowSnapshot;
}
@Override
public Version getVersion() throws DiskImageException
{
final RandomAccessFile diskFile = this.getDiskImage();
final int qcowVersion = DiskImageUtils.readInt( diskFile, 4 );
// check QCOW2 file format version
if ( qcowVersion < 2 || qcowVersion > 3 ) {
// QCOW2 disk image does not contain a valid QCOW2 version
final String errorMsg = new String( "Invalid QCOW2 version in header found!" );
throw new DiskImageException( errorMsg );
}
return new Version( Integer.valueOf( qcowVersion ).shortValue() );
}
@Override
public String getDescription() throws DiskImageException
{
// QCOW2 disk image format does not support any disk description
return null;
}
@Override
public ImageFormat getFormat()
{
return ImageFormat.QCOW2;
}
}
|