VirtualBox

source: vbox/trunk/src/VBox/Storage/testcase/tstVDSnap.cpp@ 106579

Last change on this file since 106579 was 106061, checked in by vboxsync, 2 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 14.5 KB
Line 
1/* $Id: tstVDSnap.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */
2/** @file
3 * Snapshot VBox HDD container test utility.
4 */
5
6/*
7 * Copyright (C) 2010-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28#include <VBox/vd.h>
29#include <iprt/errcore.h>
30#include <VBox/log.h>
31#include <iprt/asm.h>
32#include <iprt/dir.h>
33#include <iprt/string.h>
34#include <iprt/stream.h>
35#include <iprt/file.h>
36#include <iprt/mem.h>
37#include <iprt/initterm.h>
38#include <iprt/rand.h>
39
40/**
41 * A VD snapshot test.
42 */
43typedef struct VDSNAPTEST
44{
45 /** Backend to use */
46 const char *pcszBackend;
47 /** Base image name */
48 const char *pcszBaseImage;
49 /** Diff image ending */
50 const char *pcszDiffSuff;
51 /** Number of iterations before the test exits */
52 uint32_t cIterations;
53 /** Test pattern size */
54 size_t cbTestPattern;
55 /** Minimum number of disk segments */
56 uint32_t cDiskSegsMin;
57 /** Miaximum number of disk segments */
58 uint32_t cDiskSegsMax;
59 /** Minimum number of diffs needed before a merge
60 * operation can occur */
61 unsigned cDiffsMinBeforeMerge;
62 /** Chance to get create instead of a merge operation */
63 uint32_t uCreateDiffChance;
64 /** Chance to change a segment after a diff was created */
65 uint32_t uChangeSegChance;
66 /** Numer of allocated blocks in the base image in percent */
67 uint32_t uAllocatedBlocks;
68 /** Merge direction */
69 bool fForward;
70} VDSNAPTEST, *PVDSNAPTEST;
71
72/**
73 * Structure defining a disk segment.
74 */
75typedef struct VDDISKSEG
76{
77 /** Start offset in the disk. */
78 uint64_t off;
79 /** Size of the segment. */
80 uint64_t cbSeg;
81 /** Pointer to the start of the data in the test pattern used for the segment. */
82 uint8_t *pbData;
83 /** Pointer to the data for a diff write */
84 uint8_t *pbDataDiff;
85} VDDISKSEG, *PVDDISKSEG;
86
87
88/*********************************************************************************************************************************
89* Global Variables *
90*********************************************************************************************************************************/
91/** The error count. */
92unsigned g_cErrors = 0;
93/** Global RNG state. */
94RTRAND g_hRand;
95
96static DECLCALLBACK(void) tstVDError(void *pvUser, int rc, RT_SRC_POS_DECL, const char *pszFormat, va_list va)
97{
98 RT_NOREF1(pvUser);
99 g_cErrors++;
100 RTPrintf("tstVDSnap: Error %Rrc at %s:%u (%s): ", rc, RT_SRC_POS_ARGS);
101 RTPrintfV(pszFormat, va);
102 RTPrintf("\n");
103}
104
105static DECLCALLBACK(int) tstVDMessage(void *pvUser, const char *pszFormat, va_list va)
106{
107 RT_NOREF1(pvUser);
108 RTPrintf("tstVDSnap: ");
109 RTPrintfV(pszFormat, va);
110 return VINF_SUCCESS;
111}
112
113/**
114 * Returns true with the given chance in percent.
115 *
116 * @returns true or false
117 * @param iPercentage The percentage of the chance to return true.
118 */
119static bool tstVDSnapIsTrue(int iPercentage)
120{
121 int uRnd = RTRandAdvU32Ex(g_hRand, 0, 100);
122
123 return (uRnd <= iPercentage); /* This should be enough for our purpose */
124}
125
126static void tstVDSnapSegmentsDice(PVDSNAPTEST pTest, PVDDISKSEG paDiskSeg, uint32_t cDiskSegments,
127 uint8_t *pbTestPattern, size_t cbTestPattern)
128{
129 for (uint32_t i = 0; i < cDiskSegments; i++)
130 {
131 /* Do we want to change the current segment? */
132 if (tstVDSnapIsTrue(pTest->uChangeSegChance))
133 paDiskSeg[i].pbDataDiff = pbTestPattern + RT_ALIGN_64(RTRandAdvU64Ex(g_hRand, 0, cbTestPattern - paDiskSeg[i].cbSeg - 512), 512);
134 }
135}
136
137static int tstVDSnapWrite(PVDISK pVD, PVDDISKSEG paDiskSegments, uint32_t cDiskSegments, uint64_t cbDisk, bool fInit)
138{
139 RT_NOREF1(cbDisk);
140 int rc = VINF_SUCCESS;
141
142 for (uint32_t i = 0; i < cDiskSegments; i++)
143 {
144 if (fInit || paDiskSegments[i].pbDataDiff)
145 {
146 size_t cbWrite = paDiskSegments[i].cbSeg;
147 uint64_t off = paDiskSegments[i].off;
148 uint8_t *pbData = fInit
149 ? paDiskSegments[i].pbData
150 : paDiskSegments[i].pbDataDiff;
151
152 if (pbData)
153 {
154 rc = VDWrite(pVD, off, pbData, cbWrite);
155 if (RT_FAILURE(rc))
156 return rc;
157 }
158 }
159 }
160
161 return rc;
162}
163
164static int tstVDSnapReadVerify(PVDISK pVD, PVDDISKSEG paDiskSegments, uint32_t cDiskSegments, uint64_t cbDisk)
165{
166 RT_NOREF1(cbDisk);
167 int rc = VINF_SUCCESS;
168 uint8_t *pbBuf = (uint8_t *)RTMemAlloc(_1M);
169
170 for (uint32_t i = 0; i < cDiskSegments; i++)
171 {
172 size_t cbRead = paDiskSegments[i].cbSeg;
173 uint64_t off = paDiskSegments[i].off;
174 uint8_t *pbCmp = paDiskSegments[i].pbData;
175
176 Assert(!paDiskSegments[i].pbDataDiff);
177
178 while (cbRead)
179 {
180 size_t cbToRead = RT_MIN(cbRead, _1M);
181
182 rc = VDRead(pVD, off, pbBuf, cbToRead);
183 if (RT_FAILURE(rc))
184 return rc;
185
186 if (pbCmp)
187 {
188 if (memcmp(pbCmp, pbBuf, cbToRead))
189 {
190 for (unsigned iCmp = 0; iCmp < cbToRead; iCmp++)
191 {
192 if (pbCmp[iCmp] != pbBuf[iCmp])
193 {
194 RTPrintf("Unexpected data at %llu expected %#x got %#x\n", off+iCmp, pbCmp[iCmp], pbBuf[iCmp]);
195 break;
196 }
197 }
198 return VERR_INTERNAL_ERROR;
199 }
200 }
201 else
202 {
203 /* Verify that the block is 0 */
204 for (unsigned iCmp = 0; iCmp < cbToRead; iCmp++)
205 {
206 if (pbBuf[iCmp] != 0)
207 {
208 RTPrintf("Zero block contains data at %llu\n", off+iCmp);
209 return VERR_INTERNAL_ERROR;
210 }
211 }
212 }
213
214 cbRead -= cbToRead;
215 off += cbToRead;
216
217 if (pbCmp)
218 pbCmp += cbToRead;
219 }
220 }
221
222 RTMemFree(pbBuf);
223
224 return rc;
225}
226
227static int tstVDOpenCreateWriteMerge(PVDSNAPTEST pTest)
228{
229 int rc;
230 PVDISK pVD = NULL;
231 VDGEOMETRY PCHS = { 0, 0, 0 };
232 VDGEOMETRY LCHS = { 0, 0, 0 };
233 PVDINTERFACE pVDIfs = NULL;
234 VDINTERFACEERROR VDIfError;
235
236 /** Buffer storing the random test pattern. */
237 uint8_t *pbTestPattern = NULL;
238 /** Number of disk segments */
239 uint32_t cDiskSegments;
240 /** Array of disk segments */
241 PVDDISKSEG paDiskSeg = NULL;
242 unsigned cDiffs = 0;
243 unsigned idDiff = 0; /* Diff ID counter for the filename */
244
245 /* Delete all images from a previous run. */
246 RTFileDelete(pTest->pcszBaseImage);
247 for (unsigned i = 0; i < pTest->cIterations; i++)
248 {
249 char *pszDiffFilename = NULL;
250
251 rc = RTStrAPrintf(&pszDiffFilename, "tstVDSnapDiff%u.%s", i, pTest->pcszDiffSuff);
252 if (RT_SUCCESS(rc))
253 {
254 if (RTFileExists(pszDiffFilename))
255 RTFileDelete(pszDiffFilename);
256 RTStrFree(pszDiffFilename);
257 }
258 }
259
260 /* Create the virtual disk test data */
261 pbTestPattern = (uint8_t *)RTMemAlloc(pTest->cbTestPattern);
262 if (!pbTestPattern)
263 {
264 RTPrintf("Failed to allocate memory for test pattern\n");
265 g_cErrors++;
266 return VERR_NO_MEMORY;
267 }
268
269 RTRandAdvBytes(g_hRand, pbTestPattern, pTest->cbTestPattern);
270 cDiskSegments = RTRandAdvU32Ex(g_hRand, pTest->cDiskSegsMin, pTest->cDiskSegsMax);
271
272 uint64_t cbDisk = 0;
273
274 paDiskSeg = (PVDDISKSEG)RTMemAllocZ(cDiskSegments * sizeof(VDDISKSEG));
275 if (!paDiskSeg)
276 {
277 RTPrintf("Failed to allocate memory for random disk segments\n");
278 g_cErrors++;
279 return VERR_NO_MEMORY;
280 }
281
282 for (unsigned i = 0; i < cDiskSegments; i++)
283 {
284 paDiskSeg[i].off = cbDisk;
285 paDiskSeg[i].cbSeg = RT_ALIGN_64(RTRandAdvU64Ex(g_hRand, 512, pTest->cbTestPattern), 512);
286 if (tstVDSnapIsTrue(pTest->uAllocatedBlocks))
287 paDiskSeg[i].pbData = pbTestPattern + RT_ALIGN_64(RTRandAdvU64Ex(g_hRand, 0, pTest->cbTestPattern - paDiskSeg[i].cbSeg - 512), 512);
288 else
289 paDiskSeg[i].pbData = NULL; /* Not allocated initially */
290 cbDisk += paDiskSeg[i].cbSeg;
291 }
292
293 RTPrintf("Disk size is %llu bytes\n", cbDisk);
294
295#define CHECK(str) \
296 do \
297 { \
298 RTPrintf("%s rc=%Rrc\n", str, rc); \
299 if (RT_FAILURE(rc)) \
300 { \
301 if (pbTestPattern) \
302 RTMemFree(pbTestPattern); \
303 if (paDiskSeg) \
304 RTMemFree(paDiskSeg); \
305 VDDestroy(pVD); \
306 g_cErrors++; \
307 return rc; \
308 } \
309 } while (0)
310
311#define CHECK_BREAK(str) \
312 do \
313 { \
314 RTPrintf("%s rc=%Rrc\n", str, rc); \
315 if (RT_FAILURE(rc)) \
316 { \
317 g_cErrors++; \
318 break; \
319 } \
320 } while (0)
321
322 /* Create error interface. */
323 /* Create error interface. */
324 VDIfError.pfnError = tstVDError;
325 VDIfError.pfnMessage = tstVDMessage;
326
327 rc = VDInterfaceAdd(&VDIfError.Core, "tstVD_Error", VDINTERFACETYPE_ERROR,
328 NULL, sizeof(VDINTERFACEERROR), &pVDIfs);
329 AssertRC(rc);
330
331
332 rc = VDCreate(pVDIfs, VDTYPE_HDD, &pVD);
333 CHECK("VDCreate()");
334
335 rc = VDCreateBase(pVD, pTest->pcszBackend, pTest->pcszBaseImage, cbDisk,
336 VD_IMAGE_FLAGS_NONE, "Test image",
337 &PCHS, &LCHS, NULL, VD_OPEN_FLAGS_NORMAL,
338 NULL, NULL);
339 CHECK("VDCreateBase()");
340
341 bool fInit = true;
342 uint32_t cIteration = 0;
343
344 /* Do the real work now */
345 while ( RT_SUCCESS(rc)
346 && cIteration < pTest->cIterations)
347 {
348 /* Write */
349 rc = tstVDSnapWrite(pVD, paDiskSeg, cDiskSegments, cbDisk, fInit);
350 CHECK_BREAK("tstVDSnapWrite()");
351
352 fInit = false;
353
354 /* Write returned, do we want to create a new diff or merge them? */
355 bool fCreate = cDiffs < pTest->cDiffsMinBeforeMerge
356 ? true
357 : tstVDSnapIsTrue(pTest->uCreateDiffChance);
358
359 if (fCreate)
360 {
361 char *pszDiffFilename = NULL;
362
363 RTStrAPrintf(&pszDiffFilename, "tstVDSnapDiff%u.%s", idDiff, pTest->pcszDiffSuff);
364 CHECK("RTStrAPrintf()");
365 idDiff++;
366 cDiffs++;
367
368 rc = VDCreateDiff(pVD, pTest->pcszBackend, pszDiffFilename,
369 VD_IMAGE_FLAGS_NONE, "Test diff image", NULL, NULL,
370 VD_OPEN_FLAGS_NORMAL, NULL, NULL);
371 CHECK_BREAK("VDCreateDiff()");
372
373 RTStrFree(pszDiffFilename);
374 VDDumpImages(pVD);
375
376 /* Change data */
377 tstVDSnapSegmentsDice(pTest, paDiskSeg, cDiskSegments, pbTestPattern, pTest->cbTestPattern);
378 }
379 else
380 {
381 uint32_t uStartMerge = RTRandAdvU32Ex(g_hRand, 1, cDiffs - 1);
382 uint32_t uEndMerge = RTRandAdvU32Ex(g_hRand, uStartMerge + 1, cDiffs);
383 RTPrintf("Merging %u diffs from %u to %u...\n",
384 uEndMerge - uStartMerge,
385 uStartMerge,
386 uEndMerge);
387 if (pTest->fForward)
388 rc = VDMerge(pVD, uStartMerge, uEndMerge, NULL);
389 else
390 rc = VDMerge(pVD, uEndMerge, uStartMerge, NULL);
391 CHECK_BREAK("VDMerge()");
392
393 cDiffs -= uEndMerge - uStartMerge;
394
395 VDDumpImages(pVD);
396
397 /* Go through the disk segments and reset pointers. */
398 for (uint32_t i = 0; i < cDiskSegments; i++)
399 {
400 if (paDiskSeg[i].pbDataDiff)
401 {
402 paDiskSeg[i].pbData = paDiskSeg[i].pbDataDiff;
403 paDiskSeg[i].pbDataDiff = NULL;
404 }
405 }
406
407 /* Now compare the result with our test pattern */
408 rc = tstVDSnapReadVerify(pVD, paDiskSeg, cDiskSegments, cbDisk);
409 CHECK_BREAK("tstVDSnapReadVerify()");
410 }
411 cIteration++;
412 }
413
414 VDDumpImages(pVD);
415
416 VDDestroy(pVD);
417 RTMemFree(paDiskSeg);
418 RTMemFree(pbTestPattern);
419
420 RTFileDelete(pTest->pcszBaseImage);
421 for (unsigned i = 0; i < idDiff; i++)
422 {
423 char *pszDiffFilename = NULL;
424
425 RTStrAPrintf(&pszDiffFilename, "tstVDSnapDiff%u.%s", i, pTest->pcszDiffSuff);
426 RTFileDelete(pszDiffFilename);
427 RTStrFree(pszDiffFilename);
428 }
429#undef CHECK
430 return rc;
431}
432
433int main(int argc, char *argv[])
434{
435 RTR3InitExe(argc, &argv, 0);
436 int rc;
437 VDSNAPTEST Test;
438
439 RTPrintf("tstVDSnap: TESTING...\n");
440
441 rc = RTRandAdvCreateParkMiller(&g_hRand);
442 if (RT_FAILURE(rc))
443 {
444 RTPrintf("tstVDSnap: Creating RNG failed rc=%Rrc\n", rc);
445 return 1;
446 }
447
448 RTRandAdvSeed(g_hRand, 0x12345678);
449
450 Test.pcszBackend = "vmdk";
451 Test.pcszBaseImage = "tstVDSnapBase.vmdk";
452 Test.pcszDiffSuff = "vmdk";
453 Test.cIterations = 30;
454 Test.cbTestPattern = 10 * _1M;
455 Test.cDiskSegsMin = 10;
456 Test.cDiskSegsMax = 50;
457 Test.cDiffsMinBeforeMerge = 5;
458 Test.uCreateDiffChance = 50; /* % */
459 Test.uChangeSegChance = 50; /* % */
460 Test.uAllocatedBlocks = 50; /* 50% allocated */
461 Test.fForward = true;
462 tstVDOpenCreateWriteMerge(&Test);
463
464 /* Same test with backwards merge */
465 Test.fForward = false;
466 tstVDOpenCreateWriteMerge(&Test);
467
468 rc = VDShutdown();
469 if (RT_FAILURE(rc))
470 {
471 RTPrintf("tstVDSnap: unloading backends failed! rc=%Rrc\n", rc);
472 g_cErrors++;
473 }
474 /*
475 * Summary
476 */
477 if (!g_cErrors)
478 RTPrintf("tstVDSnap: SUCCESS\n");
479 else
480 RTPrintf("tstVDSnap: FAILURE - %d errors\n", g_cErrors);
481
482 RTRandAdvDestroy(g_hRand);
483
484 return !!g_cErrors;
485}
486
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette