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