@xen-orchestra/backups 0.68.1 → 0.69.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/RemoteAdapter.mjs +3 -3
- package/_cleanVm.mjs +22 -4
- package/_runners/_vmRunners/FullRemote.mjs +3 -2
- package/_runners/_vmRunners/FullXapi.mjs +3 -1
- package/_runners/_vmRunners/IncrementalRemote.mjs +3 -2
- package/_runners/_vmRunners/IncrementalXapi.mjs +8 -1
- package/_runners/_vmRunners/_AbstractRemote.mjs +27 -12
- package/_runners/_vmRunners/_AbstractXapi.mjs +58 -22
- package/_runners/_writers/AggregatedFullRemoteWriter.mjs +38 -0
- package/_runners/_writers/AggregatedFullXapiWriter.mjs +31 -0
- package/_runners/_writers/AggregatedIncrementalRemoteWriter.mjs +78 -0
- package/_runners/_writers/AggregatedIncrementalXapiWriter.mjs +85 -0
- package/_runners/_writers/FullRemoteWriter.mjs +3 -3
- package/_runners/_writers/FullXapiWriter.mjs +3 -3
- package/_runners/_writers/IncrementalRemoteWriter.mjs +2 -2
- package/_runners/_writers/IncrementalXapiWriter.mjs +6 -4
- package/_runners/_writers/_AbstractAggregatedRemoteWriter.mjs +157 -0
- package/_runners/_writers/_AbstractAggregatedXapiWriter.mjs +114 -0
- package/_runners/_writers/_MixinRemoteWriter.mjs +1 -1
- package/_runners/_writers/_MixinXapiWriter.mjs +1 -1
- package/_runners/_writers/_listReplicatedVms.mjs +10 -2
- package/disks/MergeRemoteDisk.mjs +317 -0
- package/disks/RemoteDisk.mjs +204 -0
- package/disks/RemoteVhdDisk.mjs +458 -0
- package/disks/RemoteVhdDiskChain.mjs +271 -0
- package/disks/openDiskChain.mjs +8 -3
- package/package.json +3 -3
- package/disks/RemoteVhd.mjs +0 -171
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('@xen-orchestra/disk-transform').DiskBlock} DiskBlock
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { RandomAccessDisk } from '@xen-orchestra/disk-transform'
|
|
8
|
+
export class RemoteDisk extends RandomAccessDisk {
|
|
9
|
+
/**
|
|
10
|
+
* Abstract
|
|
11
|
+
* @param {Object} options
|
|
12
|
+
* @param {boolean} options.force
|
|
13
|
+
* @returns {Promise<void>}
|
|
14
|
+
*/
|
|
15
|
+
async init(options = { force: false }) {
|
|
16
|
+
throw new Error(`init must be implemented`)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Abstract
|
|
21
|
+
* @returns {Promise<void>}
|
|
22
|
+
*/
|
|
23
|
+
async close() {
|
|
24
|
+
throw new Error(`close must be implemented`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Abstract
|
|
29
|
+
* @returns {number}
|
|
30
|
+
*/
|
|
31
|
+
getVirtualSize() {
|
|
32
|
+
throw new Error(`getVirtualSize must be implemented`)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Abstract
|
|
37
|
+
* @returns {number} size
|
|
38
|
+
*/
|
|
39
|
+
getSizeOnDisk() {
|
|
40
|
+
throw new Error(`getSizeOnDisk must be implemented`)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @returns {number}
|
|
45
|
+
*/
|
|
46
|
+
getBlockSize() {
|
|
47
|
+
throw new Error(`getBlockSize must be implemented`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Abstract
|
|
52
|
+
* @returns {string}
|
|
53
|
+
*/
|
|
54
|
+
getPath() {
|
|
55
|
+
throw new Error(`getPath must be implemented`)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Abstract
|
|
60
|
+
* @returns {string}
|
|
61
|
+
*/
|
|
62
|
+
getUuid() {
|
|
63
|
+
throw new Error(`getUuid must be implemented`)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Abstract
|
|
68
|
+
* @returns {Promise<boolean>} canMergeConcurently
|
|
69
|
+
*/
|
|
70
|
+
async canMergeConcurently() {
|
|
71
|
+
throw new Error(`canMergeConcurently must be implemented`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Abstract
|
|
76
|
+
* @returns {number} getMaxBlockCount
|
|
77
|
+
*/
|
|
78
|
+
getMaxBlockCount() {
|
|
79
|
+
throw new Error(`getMaxBlockCount must be implemented`)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Checks if the VHD contains a specific block.
|
|
84
|
+
* @param {number} index
|
|
85
|
+
* @returns {boolean}
|
|
86
|
+
*/
|
|
87
|
+
hasBlock(index) {
|
|
88
|
+
throw new Error(`hasBlock must be implemented`)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Abstract
|
|
93
|
+
* Gets the indexes of all blocks in the VHD.
|
|
94
|
+
* @returns {Array<number>}
|
|
95
|
+
*/
|
|
96
|
+
getBlockIndexes() {
|
|
97
|
+
throw new Error(`getBlockIndexes must be implemented`)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Abstract
|
|
102
|
+
* Returns the parent non inizialized instance
|
|
103
|
+
* @returns {RemoteDisk}
|
|
104
|
+
*/
|
|
105
|
+
instantiateParent() {
|
|
106
|
+
throw new Error(`instantiateParent must be implemented`)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Abstract
|
|
111
|
+
* Writes a full block.
|
|
112
|
+
* @param {DiskBlock} diskBlock
|
|
113
|
+
* @return {Promise<number>} blockSize
|
|
114
|
+
*/
|
|
115
|
+
async writeBlock(diskBlock) {
|
|
116
|
+
throw new Error(`writeBlock must be implemented`)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Abstract
|
|
121
|
+
* Reads a specific block from the VHD.
|
|
122
|
+
* @param {number} index
|
|
123
|
+
* @returns {Promise<DiskBlock>} diskBlock
|
|
124
|
+
*/
|
|
125
|
+
async readBlock(index) {
|
|
126
|
+
throw new Error(`readBlock must be implemented`)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Abstract
|
|
131
|
+
* Reads a specific block from the child disk to copy/move it to this disk.
|
|
132
|
+
* @param {RemoteDisk} childDisk
|
|
133
|
+
* @param {number} index
|
|
134
|
+
* @param {boolean} isResumingMerge
|
|
135
|
+
* @returns {Promise<number>} blockSize
|
|
136
|
+
*/
|
|
137
|
+
async mergeBlock(childDisk, index, isResumingMerge) {
|
|
138
|
+
throw new Error(`mergeBlock must be implemented`)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Abstract
|
|
143
|
+
* Manually set the disk allocated blocks.
|
|
144
|
+
* @param {Array<number>} blockIds
|
|
145
|
+
* @returns {Promise<void>}
|
|
146
|
+
*/
|
|
147
|
+
async setAllocatedBlocks(blockIds) {
|
|
148
|
+
throw new Error(`setAllocatedBlocks must be implemented`)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Abstract
|
|
153
|
+
* @param {RemoteDisk} childDisk
|
|
154
|
+
* @returns {Promise<void>}
|
|
155
|
+
*/
|
|
156
|
+
async flushMetadata(childDisk) {
|
|
157
|
+
throw new Error(`flushMetadata must be implemented`)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Abstract
|
|
162
|
+
* @param {RemoteDisk} childDisk
|
|
163
|
+
*/
|
|
164
|
+
mergeMetadata(childDisk) {
|
|
165
|
+
throw new Error(`mergeMetadata must be implemented`)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Abstract
|
|
170
|
+
* Checks if the VHD is a differencing disk.
|
|
171
|
+
* @returns {boolean}
|
|
172
|
+
*/
|
|
173
|
+
isDifferencing() {
|
|
174
|
+
throw new Error(`isDifferencing must be implemented`)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Abstract
|
|
179
|
+
* Rename alias/disk
|
|
180
|
+
* @param {string} newPath
|
|
181
|
+
*/
|
|
182
|
+
async rename(newPath) {
|
|
183
|
+
throw new Error(`rename must be implemented`)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Abstract
|
|
188
|
+
* Deletes alias/disk/disks
|
|
189
|
+
*/
|
|
190
|
+
async unlink() {
|
|
191
|
+
throw new Error(`unlink must be implemented`)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* @returns {Promise<RemoteDisk>}
|
|
196
|
+
*/
|
|
197
|
+
async openParent() {
|
|
198
|
+
const parent = await super.openParent()
|
|
199
|
+
if (!(parent instanceof RemoteDisk)) {
|
|
200
|
+
throw new Error('parent of a RemoteDisk must be also a RemoteDisk')
|
|
201
|
+
}
|
|
202
|
+
return parent
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('vhd-lib/Vhd/VhdDirectory.js').VhdDirectory} VhdDirectory
|
|
5
|
+
* @typedef {import('vhd-lib/Vhd/VhdFile.js').VhdFile} VhdFile
|
|
6
|
+
* @typedef {import('vhd-lib/_createFooterHeader').VhdFooter} VhdFooter
|
|
7
|
+
* @typedef {import('@xen-orchestra/disk-transform').DiskBlock} DiskBlock
|
|
8
|
+
* @typedef {import('@xen-orchestra/disk-transform').FileAccessor} FileAccessor
|
|
9
|
+
*
|
|
10
|
+
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { openVhd, VhdAbstract, VhdDirectory } from 'vhd-lib'
|
|
14
|
+
import { RemoteDisk } from './RemoteDisk.mjs'
|
|
15
|
+
import { DISK_TYPES } from 'vhd-lib/_constants.js'
|
|
16
|
+
import { isVhdAlias, resolveVhdAlias } from 'vhd-lib/aliases.js'
|
|
17
|
+
import { stringify } from 'uuid'
|
|
18
|
+
import { dirname, join } from 'node:path'
|
|
19
|
+
|
|
20
|
+
export class RemoteVhdDisk extends RemoteDisk {
|
|
21
|
+
/**
|
|
22
|
+
* @type {string}
|
|
23
|
+
*/
|
|
24
|
+
#path
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @type {FileAccessor}
|
|
28
|
+
*/
|
|
29
|
+
#handler
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @type {VhdFile | VhdDirectory | undefined}
|
|
33
|
+
*/
|
|
34
|
+
#vhd
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @type {boolean | undefined}
|
|
38
|
+
*/
|
|
39
|
+
#isDifferencing
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @type {number}
|
|
43
|
+
*/
|
|
44
|
+
#blockSize = 2 * 1024 * 1024
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @type {number}
|
|
48
|
+
*/
|
|
49
|
+
#bitmapSize = 512
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @type {() => any}
|
|
53
|
+
*/
|
|
54
|
+
#dispose = () => {}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {Object} params
|
|
58
|
+
* @param {FileAccessor} params.handler
|
|
59
|
+
* @param {string} params.path
|
|
60
|
+
*/
|
|
61
|
+
constructor({ handler, path }) {
|
|
62
|
+
super()
|
|
63
|
+
// @todo : ensure this is the full path from the root of the remote
|
|
64
|
+
this.#path = path
|
|
65
|
+
this.#handler = handler
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param {Object} [options]
|
|
70
|
+
* @param {boolean} [options.force=false]
|
|
71
|
+
* @returns {Promise<void>}
|
|
72
|
+
*/
|
|
73
|
+
async init(options = {}) {
|
|
74
|
+
if (this.#vhd === undefined) {
|
|
75
|
+
try {
|
|
76
|
+
const { value, dispose } = await openVhd(this.#handler, await resolveVhdAlias(this.#handler, this.#path), {
|
|
77
|
+
checkSecondFooter: !options.force,
|
|
78
|
+
})
|
|
79
|
+
this.#vhd = value
|
|
80
|
+
|
|
81
|
+
if ((await this.isDirectory()) && !isVhdAlias(this.#path)) {
|
|
82
|
+
this.#vhd = undefined
|
|
83
|
+
throw Object.assign(new Error("Can't init vhd directory without using alias"), { code: 'NOT_SUPPORTED' })
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.#dispose = dispose
|
|
87
|
+
await this.#vhd.readBlockAllocationTable()
|
|
88
|
+
this.#isDifferencing = value.footer.diskType === DISK_TYPES.DIFFERENCING
|
|
89
|
+
} catch (error) {
|
|
90
|
+
await this.close()
|
|
91
|
+
throw error
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Closes the VHD.
|
|
98
|
+
* @returns {Promise<void>}
|
|
99
|
+
*/
|
|
100
|
+
async close() {
|
|
101
|
+
await this.#dispose()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @returns {number}
|
|
106
|
+
*/
|
|
107
|
+
getVirtualSize() {
|
|
108
|
+
if (this.#vhd === undefined) {
|
|
109
|
+
throw new Error(`can't call getVirtualSize of a RemoteVhdDisk before init`)
|
|
110
|
+
}
|
|
111
|
+
return this.#vhd.footer.currentSize
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @returns {number} size
|
|
116
|
+
*/
|
|
117
|
+
getSizeOnDisk() {
|
|
118
|
+
if (this.#vhd === undefined) {
|
|
119
|
+
throw new Error(`can't call getVirtualSize of a RemoteVhdDisk before init`)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return this.#vhd.streamSize()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @returns {number}
|
|
127
|
+
*/
|
|
128
|
+
getBlockSize() {
|
|
129
|
+
return this.#blockSize
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @returns {string}
|
|
134
|
+
*/
|
|
135
|
+
getPath() {
|
|
136
|
+
return this.#path
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @returns {string}
|
|
141
|
+
*/
|
|
142
|
+
getUuid() {
|
|
143
|
+
if (this.#vhd === undefined) {
|
|
144
|
+
throw new Error(`can't call getUid of a RemoteVhdDisk before init`)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return stringify(this.#vhd.footer.uuid)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* @returns {string}
|
|
152
|
+
*/
|
|
153
|
+
getParentUuid() {
|
|
154
|
+
if (this.#vhd === undefined) {
|
|
155
|
+
throw new Error(`can't call getParentUid of a RemoteVhdDisk before init`)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return stringify(this.#vhd.header.parentUuid)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* @returns {Promise<boolean>} canMergeConcurently
|
|
163
|
+
*/
|
|
164
|
+
async canMergeConcurently() {
|
|
165
|
+
return await this.isDirectory()
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @returns {number} getMaxBlockCount
|
|
170
|
+
*/
|
|
171
|
+
getMaxBlockCount() {
|
|
172
|
+
if (this.#vhd === undefined) {
|
|
173
|
+
throw new Error(`can't call getMaxBlockCount of a RemoteVhdDisk before init`)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return this.#vhd.header.maxTableEntries
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Checks if the VHD contains a specific block.
|
|
181
|
+
* @param {number} index
|
|
182
|
+
* @returns {boolean}
|
|
183
|
+
*/
|
|
184
|
+
hasBlock(index) {
|
|
185
|
+
if (this.#vhd === undefined) {
|
|
186
|
+
throw new Error(`can't call hasblock of a RemoteVhdDisk before init`)
|
|
187
|
+
}
|
|
188
|
+
return this.#vhd.containsBlock(index)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Gets the indexes of all blocks in the VHD.
|
|
193
|
+
* @returns {Array<number>}
|
|
194
|
+
*/
|
|
195
|
+
getBlockIndexes() {
|
|
196
|
+
if (this.#vhd === undefined) {
|
|
197
|
+
throw new Error(`can't call getBlockIndexes of a RemoteVhdDisk before init`)
|
|
198
|
+
}
|
|
199
|
+
const indexes = []
|
|
200
|
+
for (let blockIndex = 0; blockIndex < this.#vhd.header.maxTableEntries; blockIndex++) {
|
|
201
|
+
if (this.hasBlock(blockIndex)) {
|
|
202
|
+
indexes.push(blockIndex)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return indexes
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Returns the parent non inizialized instance
|
|
210
|
+
* @returns {RemoteDisk}
|
|
211
|
+
*/
|
|
212
|
+
instantiateParent() {
|
|
213
|
+
if (this.#vhd === undefined) {
|
|
214
|
+
throw new Error(`can't call instantiateParent of a RemoteVhdDisk before init`)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const parentPath = this.#vhd.header.parentUnicodeName
|
|
218
|
+
const fullParentPath = join(dirname(this.#path), parentPath)
|
|
219
|
+
|
|
220
|
+
if (!parentPath) {
|
|
221
|
+
throw new Error(`disk ${this.#path} doesn't have parents`)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const parent = new RemoteVhdDisk({ handler: this.#handler, path: fullParentPath })
|
|
225
|
+
return parent
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Writes a full block into this VHD.
|
|
230
|
+
* @param {DiskBlock} diskBlock
|
|
231
|
+
* @return {Promise<number>} blockSize
|
|
232
|
+
*/
|
|
233
|
+
async writeBlock(diskBlock) {
|
|
234
|
+
if (this.#vhd === undefined) {
|
|
235
|
+
throw new Error(`can't call readBlock of a RemoteVhdDisk before init`)
|
|
236
|
+
}
|
|
237
|
+
await this.#vhd.writeEntireBlock({
|
|
238
|
+
id: diskBlock.index,
|
|
239
|
+
buffer: Buffer.concat([Buffer.alloc(this.#bitmapSize, 255), diskBlock.data]),
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
return this.getBlockSize()
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Reads a specific block from the VHD.
|
|
247
|
+
* @param {number} index
|
|
248
|
+
* @returns {Promise<DiskBlock>} diskBlock
|
|
249
|
+
*/
|
|
250
|
+
async readBlock(index) {
|
|
251
|
+
if (this.#vhd === undefined) {
|
|
252
|
+
throw new Error(`can't call readBlock of a RemoteVhdDisk before init`)
|
|
253
|
+
}
|
|
254
|
+
const { data } = await this.#vhd.readBlock(index)
|
|
255
|
+
return {
|
|
256
|
+
index,
|
|
257
|
+
data,
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Reads a specific block from the child disk to copy/move it to this disk.
|
|
263
|
+
* @param {RemoteDisk} childDisk
|
|
264
|
+
* @param {number} index
|
|
265
|
+
* @param {boolean} isResumingMerge
|
|
266
|
+
* @returns {Promise<number>} blockSize
|
|
267
|
+
*/
|
|
268
|
+
async mergeBlock(childDisk, index, isResumingMerge) {
|
|
269
|
+
if ((await this.isDirectory()) && childDisk instanceof RemoteVhdDisk && (await childDisk.isDirectory())) {
|
|
270
|
+
try {
|
|
271
|
+
await this.#handler.rename(childDisk.getBlockPath(index), this.getBlockPath(index))
|
|
272
|
+
|
|
273
|
+
this.setAllocatedBlocks([index])
|
|
274
|
+
} catch (error) {
|
|
275
|
+
// @ts-ignore
|
|
276
|
+
if (error.code === 'ENOENT' && isResumingMerge === true) {
|
|
277
|
+
// when resuming, the blocks moved since the last merge state write are
|
|
278
|
+
// not in the child anymore but it should be ok
|
|
279
|
+
|
|
280
|
+
// it will throw an error if block is missing in parent
|
|
281
|
+
// won't detect if the block was already in parent and is broken/missing in child
|
|
282
|
+
|
|
283
|
+
// since we can't know the initial size, this will create a discrepancy
|
|
284
|
+
// on the size
|
|
285
|
+
const { data } = await this.readBlock(index)
|
|
286
|
+
if (data.length !== this.getBlockSize()) {
|
|
287
|
+
throw error
|
|
288
|
+
} else {
|
|
289
|
+
this.setAllocatedBlocks([index])
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
throw error
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return this.getBlockSize()
|
|
297
|
+
} else {
|
|
298
|
+
return this.writeBlock(await childDisk.readBlock(index))
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Gets a specific block path from the VHD directory.
|
|
304
|
+
* @param {number} index
|
|
305
|
+
* @returns {string} blockPath
|
|
306
|
+
*/
|
|
307
|
+
getBlockPath(index) {
|
|
308
|
+
if (this.#vhd === undefined) {
|
|
309
|
+
throw new Error(`can't call readBlock of a RemoteVhdDisk before init`)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (this.#vhd instanceof VhdDirectory) {
|
|
313
|
+
return this.#vhd.getFullBlockPath(index)
|
|
314
|
+
} else {
|
|
315
|
+
throw new Error(`can't call getBlockPath of non directory VHD`)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Manually set the disk allocated blocks.
|
|
321
|
+
* @param {Array<number>} blockIds
|
|
322
|
+
* @returns {Promise<void>}
|
|
323
|
+
*/
|
|
324
|
+
async setAllocatedBlocks(blockIds) {
|
|
325
|
+
if (this.#vhd instanceof VhdDirectory) {
|
|
326
|
+
this.#vhd.setAllocatedBlocks(blockIds)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Writes Block Allocation Table
|
|
332
|
+
* @param {RemoteDisk} childDisk
|
|
333
|
+
* @returns {Promise<void>}
|
|
334
|
+
*/
|
|
335
|
+
async flushMetadata(childDisk) {
|
|
336
|
+
if (this.#vhd === undefined) {
|
|
337
|
+
throw new Error(`can't call flushMetadata of a RemoteVhdDisk before init`)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
await this.#vhd.writeBlockAllocationTable()
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* @returns {VhdFooter}
|
|
345
|
+
*/
|
|
346
|
+
getMetadata() {
|
|
347
|
+
if (this.#vhd === undefined) {
|
|
348
|
+
throw new Error(`can't call getMetadata of a RemoteVhdDisk before init`)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return this.#vhd.footer
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* @param {RemoteVhdDisk} childDisk
|
|
356
|
+
*/
|
|
357
|
+
async mergeMetadata(childDisk) {
|
|
358
|
+
const childDiskMetadata = childDisk.getMetadata()
|
|
359
|
+
|
|
360
|
+
if (this.#vhd === undefined) {
|
|
361
|
+
throw new Error(`can't call mergeMetadata of a RemoteVhdDisk before init`)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// @ts-ignore
|
|
365
|
+
this.#vhd.footer.currentSize = childDiskMetadata.currentSize
|
|
366
|
+
// @ts-ignore
|
|
367
|
+
this.#vhd.footer.diskGeometry = { ...childDiskMetadata.diskGeometry }
|
|
368
|
+
// @ts-ignore
|
|
369
|
+
this.#vhd.footer.originalSize = childDiskMetadata.originalSize
|
|
370
|
+
// @ts-ignore
|
|
371
|
+
this.#vhd.footer.timestamp = childDiskMetadata.timestamp
|
|
372
|
+
// @ts-ignore
|
|
373
|
+
this.#vhd.footer.uuid = childDiskMetadata.uuid
|
|
374
|
+
|
|
375
|
+
await this.#vhd.writeFooter()
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Checks if the VHD is a differencing disk.
|
|
380
|
+
* @returns {boolean}
|
|
381
|
+
*/
|
|
382
|
+
isDifferencing() {
|
|
383
|
+
if (this.#isDifferencing === undefined) {
|
|
384
|
+
throw new Error(`can't call isDifferencing of a RemoteVhdDisk before init`)
|
|
385
|
+
}
|
|
386
|
+
return this.#isDifferencing
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Rename alias/disk
|
|
391
|
+
* @param {string} newPath
|
|
392
|
+
*/
|
|
393
|
+
async rename(newPath) {
|
|
394
|
+
if (isVhdAlias(newPath)) {
|
|
395
|
+
const dataPath = await resolveVhdAlias(this.#handler, this.#path)
|
|
396
|
+
|
|
397
|
+
await this.#handler.unlink(this.#path)
|
|
398
|
+
await this.#handler.unlink(newPath)
|
|
399
|
+
|
|
400
|
+
await VhdAbstract.createAlias(this.#handler, newPath, dataPath)
|
|
401
|
+
|
|
402
|
+
this.#path = newPath
|
|
403
|
+
} else {
|
|
404
|
+
try {
|
|
405
|
+
await this.#handler.unlink(newPath)
|
|
406
|
+
} catch (err) {
|
|
407
|
+
if (err && typeof err === 'object' && 'code' in err && err.code === 'EISDIR') {
|
|
408
|
+
await this.#handler.rmtree(newPath).catch(() => {})
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
await this.#handler.rename(this.#path, newPath)
|
|
413
|
+
|
|
414
|
+
this.#path = newPath
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Deletes disk
|
|
420
|
+
*/
|
|
421
|
+
async unlink() {
|
|
422
|
+
if (this.#vhd === undefined) {
|
|
423
|
+
throw new Error(`can't call unlink of a RemoteVhdDisk before init`)
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
await this.close()
|
|
427
|
+
|
|
428
|
+
if (isVhdAlias(this.#path)) {
|
|
429
|
+
try {
|
|
430
|
+
await this.#handler.unlink(await resolveVhdAlias(this.#handler, this.#path))
|
|
431
|
+
} catch (err) {
|
|
432
|
+
if (err && typeof err === 'object' && 'code' in err && err.code === 'EISDIR') {
|
|
433
|
+
await this.#handler.rmtree(await resolveVhdAlias(this.#handler, this.#path)).catch(() => {})
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
try {
|
|
439
|
+
await this.#handler.unlink(this.#path)
|
|
440
|
+
} catch (err) {
|
|
441
|
+
if (err && typeof err === 'object' && 'code' in err && err.code === 'EISDIR') {
|
|
442
|
+
await this.#handler.rmtree(this.#path).catch(() => {})
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Check if the disk is a VHD directory.
|
|
449
|
+
* @returns {Promise<boolean>}
|
|
450
|
+
*/
|
|
451
|
+
async isDirectory() {
|
|
452
|
+
if (this.#vhd === undefined) {
|
|
453
|
+
throw new Error(`can't call isDirectory of a RemoteVhdDisk before init`)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return this.#vhd instanceof VhdDirectory
|
|
457
|
+
}
|
|
458
|
+
}
|