@xen-orchestra/backups 0.72.0 → 0.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ImportVmBackup.mjs +1 -1
- package/RemoteAdapter.mjs +11 -8
- package/_otherConfig.mjs +2 -0
- package/_runners/_vmRunners/IncrementalXapi.mjs +115 -41
- package/_runners/_vmRunners/_AbstractXapi.mjs +69 -28
- package/_runners/_writers/IncrementalRemoteWriter.mjs +1 -2
- package/_runners/_writers/IncrementalXapiWriter.mjs +172 -77
- package/_runners/_writers/_MixinRemoteWriter.mjs +0 -2
- package/package.json +8 -6
- package/tests.fixtures.d.mts +47 -0
- package/_cleanVm.mjs +0 -628
- package/disks/MergeRemoteDisk.mjs +0 -325
- package/disks/RemoteDisk.mjs +0 -223
- package/disks/RemoteVhdDisk.mjs +0 -480
- package/disks/RemoteVhdDiskChain.mjs +0 -304
- package/disks/index.mjs +0 -49
- package/disks/openDiskChain.mjs +0 -40
|
@@ -19,11 +19,14 @@ import {
|
|
|
19
19
|
REPLICATED_TO_SR_UUID,
|
|
20
20
|
DATETIME,
|
|
21
21
|
VM_UUID,
|
|
22
|
+
CONTENT_KEY,
|
|
23
|
+
resetVmOtherConfig,
|
|
22
24
|
} from '../../_otherConfig.mjs'
|
|
23
25
|
import { formatFilenameDate } from '../../_filenameDate.mjs'
|
|
24
26
|
import { XapiDiskSource } from '@xen-orchestra/xapi'
|
|
25
27
|
import { asyncEach } from '@vates/async-each'
|
|
26
28
|
import { createLogger } from '@xen-orchestra/log'
|
|
29
|
+
import { VM_POWER_STATE } from '@vates/types'
|
|
27
30
|
|
|
28
31
|
const { debug } = createLogger('xo:backups:IncrementalXapiWriter')
|
|
29
32
|
|
|
@@ -32,7 +35,20 @@ export class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWr
|
|
|
32
35
|
// Built by checkBaseVdis, consumed by #decorateVmMetadata to set baseVdi.
|
|
33
36
|
#baseVdisBySourceUuid = new Map()
|
|
34
37
|
|
|
35
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Finds VDIs on the target SR that can serve as base for the next delta transfer.
|
|
40
|
+
*
|
|
41
|
+
* For each entry in `baseUuidToSrcVdi`, searches the target SR for a matching snapshot
|
|
42
|
+
* using CONTENT_KEY when available, then falls back to COPY_OF matching for older snapshots.
|
|
43
|
+
* Entries with no matching base found on the target SR are removed from `baseUuidToSrcVdi`.
|
|
44
|
+
*
|
|
45
|
+
* Side-effects: populates `#baseVdisBySourceUuid`; may set `_targetVmRef`.
|
|
46
|
+
*
|
|
47
|
+
* @param {Map<string, string>} baseUuidToSrcVdi - Source snapshot UUID → source active VDI UUID. Mutated in place.
|
|
48
|
+
* @param {Map<string, string>} contentKeys - Source snapshot UUID → CONTENT_KEY value (empty for pre-CONTENT_KEY snapshots).
|
|
49
|
+
* @returns {Promise<void>}
|
|
50
|
+
*/
|
|
51
|
+
async checkBaseVdis(baseUuidToSrcVdi, contentKeys) {
|
|
36
52
|
const sr = this._sr
|
|
37
53
|
this.#baseVdisBySourceUuid = new Map()
|
|
38
54
|
|
|
@@ -45,86 +61,58 @@ export class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWr
|
|
|
45
61
|
// look for the same snapshot
|
|
46
62
|
// ensure there are no data between the snapshot and the active disk
|
|
47
63
|
|
|
48
|
-
const snapshotCandidates =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
vdi.other_config[JOB_ID] === this._job.id &&
|
|
53
|
-
vdi.other_config[VM_UUID] === this._vmUuid &&
|
|
54
|
-
baseUuidToSrcVdi.has(vdi?.other_config[COPY_OF])
|
|
55
|
-
)
|
|
56
|
-
})
|
|
57
|
-
debug('checkBaseVdis, got snapshot candidates,', snapshotCandidates.length)
|
|
58
|
-
|
|
59
|
-
if (snapshotCandidates.length > 0) {
|
|
60
|
-
// New snapshot-based flow (6.3+): verify no data was written between
|
|
61
|
-
// the target snapshot and its active VDI.
|
|
62
|
-
let targetVmRef
|
|
63
|
-
let canChainToTargetVm = true
|
|
64
|
-
await asyncEach(
|
|
65
|
-
snapshotCandidates,
|
|
66
|
-
async snapshot => {
|
|
67
|
-
let diffDisk
|
|
68
|
-
let activeVdi
|
|
69
|
-
try {
|
|
70
|
-
activeVdi = sr.$xapi.getObject(snapshot.$snapshot_of)
|
|
71
|
-
const userVbds = activeVdi.$VBDs?.filter(vbd => vbd.$VM && !vbd.$VM.is_control_domain) ?? []
|
|
72
|
-
if (userVbds.length !== 1) {
|
|
73
|
-
debug('checkBaseVdis, share vbd ', { ref: snapshot.$ref, userVbds })
|
|
74
|
-
// shared vdi ignore
|
|
75
|
-
return
|
|
76
|
-
}
|
|
77
|
-
const vm = userVbds[0].$VM
|
|
78
|
-
if (!('start' in vm.blocked_operations)) {
|
|
79
|
-
debug('checkBaseVdis, vm not blocked', { vmRef: vm.$ref })
|
|
80
|
-
// vm start unlocked
|
|
81
|
-
// not really an issue since we have check the delta
|
|
82
|
-
// but it indicates the users played with the blocked operations
|
|
83
|
-
return
|
|
84
|
-
}
|
|
85
|
-
diffDisk = new XapiDiskSource({
|
|
86
|
-
xapi: sr.$xapi,
|
|
87
|
-
vdiRef: activeVdi.$ref,
|
|
88
|
-
baseRef: snapshot.$ref,
|
|
89
|
-
onlyListChangedBlocks: true,
|
|
90
|
-
})
|
|
91
|
-
await diffDisk.init()
|
|
92
|
-
if (diffDisk.getBlockIndexes().length === 0) {
|
|
93
|
-
const sourceUuid = snapshot.other_config?.[COPY_OF]
|
|
94
|
-
if (sourceUuid) {
|
|
95
|
-
this.#baseVdisBySourceUuid.set(sourceUuid, activeVdi)
|
|
96
|
-
}
|
|
97
|
-
// Track the target VM (the replicated VM to update on the next transfer).
|
|
98
|
-
targetVmRef = vm.$ref
|
|
99
|
-
} else {
|
|
100
|
-
// not empty, we will create a new VM
|
|
101
|
-
canChainToTargetVm = false
|
|
102
|
-
debug('checkBaseVdis, data between snapshot and active disk', {
|
|
103
|
-
vdiRef: snapshot.$ref,
|
|
104
|
-
nbBlocks: diffDisk.getBlockIndexes().length,
|
|
105
|
-
})
|
|
106
|
-
}
|
|
107
|
-
} catch (error) {
|
|
108
|
-
debug('checkBaseVdis, skipping snapshot', { ref: snapshot.$ref, error })
|
|
109
|
-
return
|
|
110
|
-
} finally {
|
|
111
|
-
await diffDisk?.close().catch(error => debug('checkBaseVdis, error closing', error))
|
|
112
|
-
await sr.$xapi.VDI_disconnectFromControlDomain(snapshot.$ref)
|
|
113
|
-
if (activeVdi !== undefined) {
|
|
114
|
-
await sr.$xapi.VDI_disconnectFromControlDomain(activeVdi.$ref)
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
concurrency: 4,
|
|
120
|
-
}
|
|
121
|
-
)
|
|
64
|
+
const snapshotCandidates = new Map()
|
|
65
|
+
|
|
66
|
+
for (const [baseUuid, srcVdiuid] of baseUuidToSrcVdi) {
|
|
67
|
+
let target
|
|
122
68
|
|
|
123
|
-
|
|
124
|
-
|
|
69
|
+
const contentKey = contentKeys.get(baseUuid)
|
|
70
|
+
|
|
71
|
+
if (contentKey !== undefined) {
|
|
72
|
+
debug('got one content key, look for a candidate')
|
|
73
|
+
target = sr.$VDIs.find(
|
|
74
|
+
vdi =>
|
|
75
|
+
vdi?.managed &&
|
|
76
|
+
vdi?.is_a_snapshot &&
|
|
77
|
+
vdi.other_config[CONTENT_KEY] === contentKey && // &&
|
|
78
|
+
// ensure we don't replicate on ourself or any of the vdi in the source chain
|
|
79
|
+
vdi.$snapshot_of.uuid !== srcVdiuid
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// fall back for older snapshots
|
|
84
|
+
if (target === undefined) {
|
|
85
|
+
debug('content key not here or not found , look by jobid ')
|
|
86
|
+
target = sr.$VDIs.find(
|
|
87
|
+
vdi =>
|
|
88
|
+
vdi?.managed &&
|
|
89
|
+
vdi?.is_a_snapshot &&
|
|
90
|
+
vdi.other_config[JOB_ID] === this._job.id &&
|
|
91
|
+
vdi.other_config[VM_UUID] === this._vmUuid &&
|
|
92
|
+
vdi?.other_config[COPY_OF] === baseUuid
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
if (target !== undefined) {
|
|
96
|
+
snapshotCandidates.set(baseUuid, target)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
debug('checkBaseVdis, got snapshot candidates,', snapshotCandidates.size)
|
|
101
|
+
|
|
102
|
+
if (snapshotCandidates.size > 0) {
|
|
103
|
+
// reset before searching for candidates
|
|
104
|
+
this.#baseVdisBySourceUuid = new Map()
|
|
105
|
+
this._targetVmRef = undefined
|
|
106
|
+
const { baseVdisBySourceUuid, targetVmRef } = await this.#validateSnapshotCandidates(snapshotCandidates)
|
|
107
|
+
for (const [sourceUuid, vdi] of baseVdisBySourceUuid) {
|
|
108
|
+
this.#baseVdisBySourceUuid.set(sourceUuid, vdi)
|
|
109
|
+
}
|
|
110
|
+
debug(' this.#baseVdisBySourceUuid ', this.#baseVdisBySourceUuid.size)
|
|
111
|
+
if (targetVmRef !== undefined) {
|
|
125
112
|
this._targetVmRef = targetVmRef
|
|
126
113
|
}
|
|
127
114
|
} else {
|
|
115
|
+
debug('legacy fallback ( no content key ) ')
|
|
128
116
|
// Legacy fallback (upgrade from pre-6.3): no target snapshots exist yet,
|
|
129
117
|
// look for active (non-snapshot) VDIs with matching COPY_OF, like the old code did.
|
|
130
118
|
debug('checkBaseVdis, no snapshot candidates, falling back to legacy active VDI lookup')
|
|
@@ -145,6 +133,108 @@ export class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWr
|
|
|
145
133
|
}
|
|
146
134
|
}
|
|
147
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* 6.3+ snapshot-based validation: for each snapshot candidate, check whether
|
|
138
|
+
* the active VDI has diverged from the snapshot. Returns a baseVdisBySourceUuid
|
|
139
|
+
* map and, when all disks are clean, the targetVmRef to reuse.
|
|
140
|
+
*
|
|
141
|
+
* @param {Map<XenApiVdi['id'], import('@vates/types').XenApiVdi>} snapshotCandidates - Snapshot VDIs on the target SR to validate.
|
|
142
|
+
* @returns {Promise<{ baseVdisBySourceUuid: Map<string, import('@vates/types').XenApiVdi>, targetVmRef: import('@vates/types').XenApiVm['$ref'] | undefined }>}
|
|
143
|
+
*/
|
|
144
|
+
async #validateSnapshotCandidates(snapshotCandidates) {
|
|
145
|
+
const sr = this._sr
|
|
146
|
+
const baseVdisBySourceUuid = new Map()
|
|
147
|
+
let targetVmRef
|
|
148
|
+
let canChainToTargetVm = true
|
|
149
|
+
|
|
150
|
+
await asyncEach(
|
|
151
|
+
snapshotCandidates.entries(),
|
|
152
|
+
async ([sourceUuid, snapshot]) => {
|
|
153
|
+
let diffDisk
|
|
154
|
+
let activeVdi
|
|
155
|
+
try {
|
|
156
|
+
activeVdi = sr.$xapi.getObject(snapshot.$snapshot_of)
|
|
157
|
+
const userVbds = activeVdi.$VBDs?.filter(vbd => vbd.$VM && !vbd.$VM.is_control_domain) ?? []
|
|
158
|
+
if (userVbds.length !== 1) {
|
|
159
|
+
canChainToTargetVm = false
|
|
160
|
+
debug('checkBaseVdis, shared vbd ', { ref: snapshot.$ref, userVbds })
|
|
161
|
+
// shared vdi ignore / don't chain
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
const vm = userVbds[0].$VM
|
|
165
|
+
|
|
166
|
+
// a running VM will fail to compute disk exports
|
|
167
|
+
// also a running VM can be assumed to have changed data
|
|
168
|
+
if (vm.power_state !== VM_POWER_STATE.HALTED && vm.power_state !== VM_POWER_STATE.SUSPENDED) {
|
|
169
|
+
canChainToTargetVm = false
|
|
170
|
+
debug('checkBaseVdis, target vm is not halted or suspended', {
|
|
171
|
+
ref: snapshot.$ref,
|
|
172
|
+
userVbds,
|
|
173
|
+
powerState: vm.power_state,
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
// from this disk of from another
|
|
177
|
+
// skip the costly part, only do the disk chaining
|
|
178
|
+
if (!canChainToTargetVm) {
|
|
179
|
+
debug("Can't chain VM anyway , fast return and chain with snapshot")
|
|
180
|
+
baseVdisBySourceUuid.set(sourceUuid, snapshot)
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
diffDisk = new XapiDiskSource({
|
|
184
|
+
xapi: sr.$xapi,
|
|
185
|
+
vdiRef: activeVdi.$ref,
|
|
186
|
+
baseRef: snapshot.$ref,
|
|
187
|
+
onlyListChangedBlocks: true,
|
|
188
|
+
})
|
|
189
|
+
await diffDisk.init()
|
|
190
|
+
if (diffDisk.getBlockIndexes().length === 0) {
|
|
191
|
+
debug(' NO CHANGE , source detected ? ', !!sourceUuid)
|
|
192
|
+
// no block modification since the common snapshot, we can chain VM and disk
|
|
193
|
+
// the disk is chained with the active to keep the chain linear
|
|
194
|
+
baseVdisBySourceUuid.set(sourceUuid, activeVdi)
|
|
195
|
+
// Track the target VM (the replicated VM to update on the next transfer).
|
|
196
|
+
targetVmRef = vm.$ref
|
|
197
|
+
} else {
|
|
198
|
+
debug(' GOT CHANGE, source detected ? ', !!sourceUuid)
|
|
199
|
+
baseVdisBySourceUuid.set(sourceUuid, snapshot)
|
|
200
|
+
// there are changed block since the snapshot
|
|
201
|
+
// we can reuse it to transfer a delta, but we will
|
|
202
|
+
// create a new VM
|
|
203
|
+
canChainToTargetVm = false
|
|
204
|
+
debug('checkBaseVdis, data between snapshot and active disk', {
|
|
205
|
+
vdiRef: snapshot.$ref,
|
|
206
|
+
nbBlocks: diffDisk.getBlockIndexes().length,
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
} catch (error) {
|
|
210
|
+
debug('checkBaseVdis, skipping snapshot', { ref: snapshot.$ref, error })
|
|
211
|
+
return
|
|
212
|
+
} finally {
|
|
213
|
+
await diffDisk?.close().catch(error => debug('checkBaseVdis, error closing', error))
|
|
214
|
+
await sr.$xapi.VDI_disconnectFromControlDomain(snapshot.$ref)
|
|
215
|
+
if (activeVdi !== undefined) {
|
|
216
|
+
await sr.$xapi.VDI_disconnectFromControlDomain(activeVdi.$ref)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
concurrency: 4,
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
if (!canChainToTargetVm) {
|
|
226
|
+
debug('checkBaseVdis,NOT a valid vm target')
|
|
227
|
+
// if at least one disk has new data, create a new VM
|
|
228
|
+
// instead of updating it
|
|
229
|
+
targetVmRef = undefined
|
|
230
|
+
} else if (targetVmRef !== undefined) {
|
|
231
|
+
debug('checkBaseVdis,got a valid vm target', targetVmRef)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
debug('checkBaseVdis,base vdis found : ', baseVdisBySourceUuid.size)
|
|
235
|
+
return { baseVdisBySourceUuid, targetVmRef }
|
|
236
|
+
}
|
|
237
|
+
|
|
148
238
|
updateUuidAndChain() {
|
|
149
239
|
// nothing to do, the chaining is not modified in this case
|
|
150
240
|
}
|
|
@@ -239,6 +329,10 @@ export class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWr
|
|
|
239
329
|
}
|
|
240
330
|
|
|
241
331
|
Object.values(backup.vdis).forEach(vdi => {
|
|
332
|
+
// setVmSnapshotContentKeys sets CONTENT_KEY = the snapshot VDI's own UUID, but
|
|
333
|
+
// that XenAPI write may not be visible in the xapi cache yet when exportIncrementalVm
|
|
334
|
+
// reads vdi.other_config. Derive the correct value directly instead of relying on cache.
|
|
335
|
+
vdi.other_config[CONTENT_KEY] = vdi.uuid
|
|
242
336
|
vdi.other_config[COPY_OF] = vdi.uuid
|
|
243
337
|
vdi.other_config[JOB_ID] = job.id
|
|
244
338
|
vdi.other_config[SCHEDULE_ID] = scheduleId
|
|
@@ -297,6 +391,7 @@ export class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWr
|
|
|
297
391
|
await xapi.VM_snapshot(targetVmRef, {
|
|
298
392
|
name_label: `${vm.name_label} - ${job.name} / ${schedule.name} ${formatFilenameDate(timestamp)}`,
|
|
299
393
|
})
|
|
394
|
+
await resetVmOtherConfig(xapi, targetVmRef)
|
|
300
395
|
})
|
|
301
396
|
|
|
302
397
|
return {
|
|
@@ -31,7 +31,6 @@ export const MixinRemoteWriter = (BaseClass = Object) =>
|
|
|
31
31
|
return await Task.run({ properties: { name: 'clean-vm' } }, () => {
|
|
32
32
|
return this._adapter.cleanVm(this._vmBackupDir, {
|
|
33
33
|
...options,
|
|
34
|
-
fixMetadata: true,
|
|
35
34
|
logInfo: info,
|
|
36
35
|
logWarn: (message, data) => {
|
|
37
36
|
warn(message, data)
|
|
@@ -39,7 +38,6 @@ export const MixinRemoteWriter = (BaseClass = Object) =>
|
|
|
39
38
|
},
|
|
40
39
|
lock: false,
|
|
41
40
|
mergeBlockConcurrency: this._config.mergeBlockConcurrency,
|
|
42
|
-
removeTmp: true,
|
|
43
41
|
})
|
|
44
42
|
})
|
|
45
43
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
|
10
10
|
},
|
|
11
|
-
"version": "0.
|
|
11
|
+
"version": "0.73.0",
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=14.18"
|
|
14
14
|
},
|
|
@@ -30,12 +30,14 @@
|
|
|
30
30
|
"@vates/nbd-client": "^3.4.0",
|
|
31
31
|
"@vates/parse-duration": "^0.1.1",
|
|
32
32
|
"@vates/task": "^0.7.0",
|
|
33
|
+
"@vates/types": "^1.25.0",
|
|
33
34
|
"@xen-orchestra/async-map": "^0.1.3",
|
|
34
|
-
"@xen-orchestra/disk-transform": "^1.
|
|
35
|
-
"@xen-orchestra/fs": "^4.
|
|
35
|
+
"@xen-orchestra/disk-transform": "^1.3.0",
|
|
36
|
+
"@xen-orchestra/fs": "^4.9.0",
|
|
36
37
|
"@xen-orchestra/log": "^0.7.2",
|
|
37
38
|
"@xen-orchestra/qcow2": "^1.3.0",
|
|
38
39
|
"@xen-orchestra/template": "^0.1.1",
|
|
40
|
+
"@xen-orchestra/backup-archive": "^2.0.0",
|
|
39
41
|
"app-conf": "^3.0.0",
|
|
40
42
|
"compare-versions": "^6.0.0",
|
|
41
43
|
"d3-time-format": "^4.1.0",
|
|
@@ -61,10 +63,11 @@
|
|
|
61
63
|
"fs-extra": "^11.1.0",
|
|
62
64
|
"rimraf": "^6.0.1",
|
|
63
65
|
"sinon": "^18.0.0",
|
|
64
|
-
"tmp": "^0.2.1"
|
|
66
|
+
"tmp": "^0.2.1",
|
|
67
|
+
"typescript": "^5.9.3"
|
|
65
68
|
},
|
|
66
69
|
"peerDependencies": {
|
|
67
|
-
"@xen-orchestra/xapi": "^8.
|
|
70
|
+
"@xen-orchestra/xapi": "^8.8.0"
|
|
68
71
|
},
|
|
69
72
|
"license": "AGPL-3.0-or-later",
|
|
70
73
|
"author": {
|
|
@@ -72,7 +75,6 @@
|
|
|
72
75
|
"url": "https://vates.fr"
|
|
73
76
|
},
|
|
74
77
|
"exports": {
|
|
75
|
-
"./disks": "./disks/index.mjs",
|
|
76
78
|
"./*": "./*"
|
|
77
79
|
}
|
|
78
80
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export namespace VHDFOOTER {
|
|
2
|
+
let cookie: string
|
|
3
|
+
let features: number
|
|
4
|
+
let fileFormatVersion: number
|
|
5
|
+
let dataOffset: number
|
|
6
|
+
let timestamp: number
|
|
7
|
+
let creatorApplication: string
|
|
8
|
+
let creatorVersion: number
|
|
9
|
+
let creatorHostOs: number
|
|
10
|
+
let originalSize: number
|
|
11
|
+
let currentSize: number
|
|
12
|
+
namespace diskGeometry {
|
|
13
|
+
let cylinders: number
|
|
14
|
+
let heads: number
|
|
15
|
+
let sectorsPerTrackCylinder: number
|
|
16
|
+
}
|
|
17
|
+
let diskType: number
|
|
18
|
+
let checksum: number
|
|
19
|
+
let uuid: Buffer<ArrayBuffer>
|
|
20
|
+
let saved: string
|
|
21
|
+
let hidden: string
|
|
22
|
+
let reserved: string
|
|
23
|
+
}
|
|
24
|
+
export namespace VHDHEADER {
|
|
25
|
+
let cookie_1: string
|
|
26
|
+
export { cookie_1 as cookie }
|
|
27
|
+
let dataOffset_1: any
|
|
28
|
+
export { dataOffset_1 as dataOffset }
|
|
29
|
+
export let tableOffset: number
|
|
30
|
+
export let headerVersion: number
|
|
31
|
+
export let maxTableEntries: number
|
|
32
|
+
export let blockSize: number
|
|
33
|
+
let checksum_1: number
|
|
34
|
+
export { checksum_1 as checksum }
|
|
35
|
+
export let parentUuid: any
|
|
36
|
+
export let parentTimestamp: number
|
|
37
|
+
export let reserved1: number
|
|
38
|
+
export let parentUnicodeName: string
|
|
39
|
+
export let parentLocatorEntry: {
|
|
40
|
+
platformCode: number
|
|
41
|
+
platformDataSpace: number
|
|
42
|
+
platformDataLength: number
|
|
43
|
+
reserved: number
|
|
44
|
+
platformDataOffset: number
|
|
45
|
+
}[]
|
|
46
|
+
export let reserved2: string
|
|
47
|
+
}
|