- Timestamp:
- Sep 14, 2016 9:01:12 AM (8 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/Storage/VMDK.cpp
r63825 r63826 475 475 } VMDKGRAINALLOCASYNC, *PVMDKGRAINALLOCASYNC; 476 476 477 /** 478 * State information for vmdkRename() and helpers. 479 */ 480 typedef struct VMDKRENAMESTATE 481 { 482 /** Array of old filenames. */ 483 char **apszOldName; 484 /** Array of new filenames. */ 485 char **apszNewName; 486 /** Array of new lines in the extent descriptor. */ 487 char **apszNewLines; 488 /** Name of the old descriptor file if not a sparse image. */ 489 char *pszOldDescName; 490 /** Flag whether we called vmdkFreeImage(). */ 491 bool fImageFreed; 492 /** Flag whther the descriptor is embedded in the image (sparse) or 493 * in a separate file. */ 494 bool fEmbeddedDesc; 495 /** Number of extents in the image. */ 496 unsigned cExtents; 497 /** New base filename. */ 498 char *pszNewBaseName; 499 /** The old base filename. */ 500 char *pszOldBaseName; 501 /** New full filename. */ 502 char *pszNewFullName; 503 /** Old full filename. */ 504 char *pszOldFullName; 505 /** The old image name. */ 506 const char *pszOldImageName; 507 /** Copy of the original VMDK descriptor. */ 508 VMDKDESCRIPTOR DescriptorCopy; 509 /** Copy of the extent state for sparse images. */ 510 VMDKEXTENT ExtentCopy; 511 } VMDKRENAMESTATE; 512 /** Pointer to a VMDK rename state. */ 513 typedef VMDKRENAMESTATE *PVMDKRENAMESTATE; 477 514 478 515 /********************************************************************************************************************************* … … 5256 5293 } 5257 5294 5295 /** 5296 * Prepares the state for renaming a VMDK image, setting up the state and allocating 5297 * memory. 5298 * 5299 * @returns VBox status code. 5300 * @param pImage VMDK image instance. 5301 * @param pRenameState The state to initialize. 5302 * @param pszFilename The new filename. 5303 */ 5304 static int vmdkRenameStatePrepare(PVMDKIMAGE pImage, PVMDKRENAMESTATE pRenameState, const char *pszFilename) 5305 { 5306 int rc = VINF_SUCCESS; 5307 5308 memset(&pRenameState->DescriptorCopy, 0, sizeof(pRenameState->DescriptorCopy)); 5309 5310 /* 5311 * Allocate an array to store both old and new names of renamed files 5312 * in case we have to roll back the changes. Arrays are initialized 5313 * with zeros. We actually save stuff when and if we change it. 5314 */ 5315 pRenameState->cExtents = pImage->cExtents; 5316 pRenameState->apszOldName = (char **)RTMemTmpAllocZ((pRenameState->cExtents + 1) * sizeof(char*)); 5317 pRenameState->apszNewName = (char **)RTMemTmpAllocZ((pRenameState->cExtents + 1) * sizeof(char*)); 5318 pRenameState->apszNewLines = (char **)RTMemTmpAllocZ(pRenameState->cExtents * sizeof(char*)); 5319 if ( pRenameState->apszOldName 5320 && pRenameState->apszNewName 5321 && pRenameState->apszNewLines) 5322 { 5323 /* Save the descriptor size and position. */ 5324 if (pImage->pDescData) 5325 { 5326 /* Separate descriptor file. */ 5327 pRenameState->fEmbeddedDesc = false; 5328 } 5329 else 5330 { 5331 /* Embedded descriptor file. */ 5332 pRenameState->ExtentCopy = pImage->pExtents[0]; 5333 pRenameState->fEmbeddedDesc = true; 5334 } 5335 5336 /* Save the descriptor content. */ 5337 pRenameState->DescriptorCopy.cLines = pImage->Descriptor.cLines; 5338 for (unsigned i = 0; i < pRenameState->DescriptorCopy.cLines; i++) 5339 { 5340 pRenameState->DescriptorCopy.aLines[i] = RTStrDup(pImage->Descriptor.aLines[i]); 5341 if (!pRenameState->DescriptorCopy.aLines[i]) 5342 { 5343 rc = VERR_NO_MEMORY; 5344 break; 5345 } 5346 } 5347 5348 if (RT_SUCCESS(rc)) 5349 { 5350 /* Prepare both old and new base names used for string replacement. */ 5351 pRenameState->pszNewBaseName = RTStrDup(RTPathFilename(pszFilename)); 5352 RTPathStripSuffix(pRenameState->pszNewBaseName); 5353 pRenameState->pszOldBaseName = RTStrDup(RTPathFilename(pImage->pszFilename)); 5354 RTPathStripSuffix(pRenameState->pszOldBaseName); 5355 /* Prepare both old and new full names used for string replacement. */ 5356 pRenameState->pszNewFullName = RTStrDup(pszFilename); 5357 RTPathStripSuffix(pRenameState->pszNewFullName); 5358 pRenameState->pszOldFullName = RTStrDup(pImage->pszFilename); 5359 RTPathStripSuffix(pRenameState->pszOldFullName); 5360 5361 /* Save the old name for easy access to the old descriptor file. */ 5362 pRenameState->pszOldDescName = RTStrDup(pImage->pszFilename); 5363 /* Save old image name. */ 5364 pRenameState->pszOldImageName = pImage->pszFilename; 5365 } 5366 } 5367 else 5368 rc = VERR_NO_MEMORY; 5369 5370 return rc; 5371 } 5372 5373 /** 5374 * Destroys the given rename state, freeing all allocated memory. 5375 * 5376 * @returns nothing. 5377 * @param pRenameState The rename state to destroy. 5378 */ 5379 static void vmdkRenameStateDestroy(PVMDKRENAMESTATE pRenameState) 5380 { 5381 for (unsigned i = 0; i < pRenameState->DescriptorCopy.cLines; i++) 5382 if (pRenameState->DescriptorCopy.aLines[i]) 5383 RTStrFree(pRenameState->DescriptorCopy.aLines[i]); 5384 if (pRenameState->apszOldName) 5385 { 5386 for (unsigned i = 0; i <= pRenameState->cExtents; i++) 5387 if (pRenameState->apszOldName[i]) 5388 RTStrFree(pRenameState->apszOldName[i]); 5389 RTMemTmpFree(pRenameState->apszOldName); 5390 } 5391 if (pRenameState->apszNewName) 5392 { 5393 for (unsigned i = 0; i <= pRenameState->cExtents; i++) 5394 if (pRenameState->apszNewName[i]) 5395 RTStrFree(pRenameState->apszNewName[i]); 5396 RTMemTmpFree(pRenameState->apszNewName); 5397 } 5398 if (pRenameState->apszNewLines) 5399 { 5400 for (unsigned i = 0; i < pRenameState->cExtents; i++) 5401 if (pRenameState->apszNewLines[i]) 5402 RTStrFree(pRenameState->apszNewLines[i]); 5403 RTMemTmpFree(pRenameState->apszNewLines); 5404 } 5405 if (pRenameState->pszOldDescName) 5406 RTStrFree(pRenameState->pszOldDescName); 5407 if (pRenameState->pszOldBaseName) 5408 RTStrFree(pRenameState->pszOldBaseName); 5409 if (pRenameState->pszNewBaseName) 5410 RTStrFree(pRenameState->pszNewBaseName); 5411 if (pRenameState->pszOldFullName) 5412 RTStrFree(pRenameState->pszOldFullName); 5413 if (pRenameState->pszNewFullName) 5414 RTStrFree(pRenameState->pszNewFullName); 5415 } 5416 5417 /** 5418 * Rolls back the rename operation to the original state. 5419 * 5420 * @returns VBox status code. 5421 * @param pImage VMDK image instance. 5422 * @param pRenameState The rename state. 5423 */ 5424 static int vmdkRenameRollback(PVMDKIMAGE pImage, PVMDKRENAMESTATE pRenameState) 5425 { 5426 int rc = VINF_SUCCESS; 5427 5428 if (!pRenameState->fImageFreed) 5429 { 5430 /* 5431 * Some extents may have been closed, close the rest. We will 5432 * re-open the whole thing later. 5433 */ 5434 vmdkFreeImage(pImage, false); 5435 } 5436 5437 /* Rename files back. */ 5438 for (unsigned i = 0; i <= pRenameState->cExtents; i++) 5439 { 5440 if (pRenameState->apszOldName[i]) 5441 { 5442 rc = vdIfIoIntFileMove(pImage->pIfIo, pRenameState->apszNewName[i], pRenameState->apszOldName[i], 0); 5443 AssertRC(rc); 5444 } 5445 } 5446 /* Restore the old descriptor. */ 5447 PVMDKFILE pFile; 5448 rc = vmdkFileOpen(pImage, &pFile, pRenameState->pszOldDescName, 5449 VDOpenFlagsToFileOpenFlags(VD_OPEN_FLAGS_NORMAL, 5450 false /* fCreate */)); 5451 AssertRC(rc); 5452 if (pRenameState->fEmbeddedDesc) 5453 { 5454 pRenameState->ExtentCopy.pFile = pFile; 5455 pImage->pExtents = &pRenameState->ExtentCopy; 5456 } 5457 else 5458 { 5459 /* Shouldn't be null for separate descriptor. 5460 * There will be no access to the actual content. 5461 */ 5462 pImage->pDescData = pRenameState->pszOldDescName; 5463 pImage->pFile = pFile; 5464 } 5465 pImage->Descriptor = pRenameState->DescriptorCopy; 5466 vmdkWriteDescriptor(pImage, NULL); 5467 vmdkFileClose(pImage, &pFile, false); 5468 /* Get rid of the stuff we implanted. */ 5469 pImage->pExtents = NULL; 5470 pImage->pFile = NULL; 5471 pImage->pDescData = NULL; 5472 /* Re-open the image back. */ 5473 pImage->pszFilename = pRenameState->pszOldImageName; 5474 rc = vmdkOpenImage(pImage, pImage->uOpenFlags); 5475 5476 return rc; 5477 } 5478 5479 /** 5480 * Rename worker doing the real work. 5481 * 5482 * @returns VBox status code. 5483 * @param pImage VMDK image instance. 5484 * @param pRenameState The rename state. 5485 * @param pszFilename The new filename. 5486 */ 5487 static int vmdkRenameWorker(PVMDKIMAGE pImage, PVMDKRENAMESTATE pRenameState, const char *pszFilename) 5488 { 5489 int rc = VINF_SUCCESS; 5490 unsigned i, line; 5491 5492 /* Update the descriptor with modified extent names. */ 5493 for (i = 0, line = pImage->Descriptor.uFirstExtent; 5494 i < pRenameState->cExtents; 5495 i++, line = pImage->Descriptor.aNextLines[line]) 5496 { 5497 /* Update the descriptor. */ 5498 pRenameState->apszNewLines[i] = vmdkStrReplace(pImage->Descriptor.aLines[line], 5499 pRenameState->pszOldBaseName, 5500 pRenameState->pszNewBaseName); 5501 if (!pRenameState->apszNewLines[i]) 5502 { 5503 rc = VERR_NO_MEMORY; 5504 break; 5505 } 5506 pImage->Descriptor.aLines[line] = pRenameState->apszNewLines[i]; 5507 } 5508 5509 if (RT_SUCCESS(rc)) 5510 { 5511 /* Make sure the descriptor gets written back. */ 5512 pImage->Descriptor.fDirty = true; 5513 /* Flush the descriptor now, in case it is embedded. */ 5514 vmdkFlushImage(pImage, NULL); 5515 5516 /* Close and rename/move extents. */ 5517 for (i = 0; i < pRenameState->cExtents; i++) 5518 { 5519 PVMDKEXTENT pExtent = &pImage->pExtents[i]; 5520 /* Compose new name for the extent. */ 5521 pRenameState->apszNewName[i] = vmdkStrReplace(pExtent->pszFullname, 5522 pRenameState->pszOldFullName, 5523 pRenameState->pszNewFullName); 5524 if (!pRenameState->apszNewName[i]) 5525 { 5526 rc = VERR_NO_MEMORY; 5527 break; 5528 } 5529 /* Close the extent file. */ 5530 rc = vmdkFileClose(pImage, &pExtent->pFile, false); 5531 if (RT_FAILURE(rc)) 5532 break;; 5533 5534 /* Rename the extent file. */ 5535 rc = vdIfIoIntFileMove(pImage->pIfIo, pExtent->pszFullname, pRenameState->apszNewName[i], 0); 5536 if (RT_FAILURE(rc)) 5537 break; 5538 /* Remember the old name. */ 5539 pRenameState->apszOldName[i] = RTStrDup(pExtent->pszFullname); 5540 } 5541 5542 if (RT_SUCCESS(rc)) 5543 { 5544 /* Release all old stuff. */ 5545 rc = vmdkFreeImage(pImage, false); 5546 if (RT_SUCCESS(rc)) 5547 { 5548 pRenameState->fImageFreed = true; 5549 5550 /* Last elements of new/old name arrays are intended for 5551 * storing descriptor's names. 5552 */ 5553 pRenameState->apszNewName[pRenameState->cExtents] = RTStrDup(pszFilename); 5554 /* Rename the descriptor file if it's separate. */ 5555 if (!pRenameState->fEmbeddedDesc) 5556 { 5557 rc = vdIfIoIntFileMove(pImage->pIfIo, pImage->pszFilename, pRenameState->apszNewName[pRenameState->cExtents], 0); 5558 if (RT_SUCCESS(rc)) 5559 { 5560 /* Save old name only if we may need to change it back. */ 5561 pRenameState->apszOldName[pRenameState->cExtents] = RTStrDup(pszFilename); 5562 } 5563 } 5564 5565 /* Update pImage with the new information. */ 5566 pImage->pszFilename = pszFilename; 5567 5568 /* Open the new image. */ 5569 rc = vmdkOpenImage(pImage, pImage->uOpenFlags); 5570 } 5571 } 5572 } 5573 5574 return rc; 5575 } 5576 5258 5577 /** @copydoc VDIMAGEBACKEND::pfnRename */ 5259 5578 static DECLCALLBACK(int) vmdkRename(void *pBackendData, const char *pszFilename) … … 5262 5581 5263 5582 PVMDKIMAGE pImage = (PVMDKIMAGE)pBackendData; 5264 int rc = VINF_SUCCESS; 5265 char **apszOldName = NULL; 5266 char **apszNewName = NULL; 5267 char **apszNewLines = NULL; 5268 char *pszOldDescName = NULL; 5269 bool fImageFreed = false; 5270 bool fEmbeddedDesc = false; 5271 unsigned cExtents = pImage->cExtents;; 5272 char *pszNewBaseName = NULL; 5273 char *pszOldBaseName = NULL; 5274 char *pszNewFullName = NULL; 5275 char *pszOldFullName = NULL; 5276 const char *pszOldImageName; 5277 unsigned i, line; 5278 VMDKDESCRIPTOR DescriptorCopy; 5279 VMDKEXTENT ExtentCopy; 5280 5281 memset(&DescriptorCopy, 0, sizeof(DescriptorCopy)); 5583 VMDKRENAMESTATE RenameState; 5584 5585 memset(&RenameState, 0, sizeof(RenameState)); 5282 5586 5283 5587 /* Check arguments. */ … … 5287 5591 || !*pszFilename), VERR_INVALID_PARAMETER); 5288 5592 5289 /* 5290 * Allocate an array to store both old and new names of renamed files 5291 * in case we have to roll back the changes. Arrays are initialized 5292 * with zeros. We actually save stuff when and if we change it. 5293 */ 5294 apszOldName = (char **)RTMemTmpAllocZ((cExtents + 1) * sizeof(char*)); 5295 apszNewName = (char **)RTMemTmpAllocZ((cExtents + 1) * sizeof(char*)); 5296 apszNewLines = (char **)RTMemTmpAllocZ((cExtents) * sizeof(char*)); 5297 if (!apszOldName || !apszNewName || !apszNewLines) 5298 { 5299 rc = VERR_NO_MEMORY; 5300 goto out; 5301 } 5302 5303 /* Save the descriptor size and position. */ 5304 if (pImage->pDescData) 5305 { 5306 /* Separate descriptor file. */ 5307 fEmbeddedDesc = false; 5308 } 5309 else 5310 { 5311 /* Embedded descriptor file. */ 5312 ExtentCopy = pImage->pExtents[0]; 5313 fEmbeddedDesc = true; 5314 } 5315 /* Save the descriptor content. */ 5316 DescriptorCopy.cLines = pImage->Descriptor.cLines; 5317 for (i = 0; i < DescriptorCopy.cLines; i++) 5318 { 5319 DescriptorCopy.aLines[i] = RTStrDup(pImage->Descriptor.aLines[i]); 5320 if (!DescriptorCopy.aLines[i]) 5321 { 5322 rc = VERR_NO_MEMORY; 5323 goto out; 5324 } 5325 } 5326 5327 /* Prepare both old and new base names used for string replacement. */ 5328 pszNewBaseName = RTStrDup(RTPathFilename(pszFilename)); 5329 RTPathStripSuffix(pszNewBaseName); 5330 pszOldBaseName = RTStrDup(RTPathFilename(pImage->pszFilename)); 5331 RTPathStripSuffix(pszOldBaseName); 5332 /* Prepare both old and new full names used for string replacement. */ 5333 pszNewFullName = RTStrDup(pszFilename); 5334 RTPathStripSuffix(pszNewFullName); 5335 pszOldFullName = RTStrDup(pImage->pszFilename); 5336 RTPathStripSuffix(pszOldFullName); 5337 5338 /* --- Up to this point we have not done any damage yet. --- */ 5339 5340 /* Save the old name for easy access to the old descriptor file. */ 5341 pszOldDescName = RTStrDup(pImage->pszFilename); 5342 /* Save old image name. */ 5343 pszOldImageName = pImage->pszFilename; 5344 5345 /* Update the descriptor with modified extent names. */ 5346 for (i = 0, line = pImage->Descriptor.uFirstExtent; 5347 i < cExtents; 5348 i++, line = pImage->Descriptor.aNextLines[line]) 5349 { 5350 /* Assume that vmdkStrReplace will fail. */ 5351 rc = VERR_NO_MEMORY; 5352 /* Update the descriptor. */ 5353 apszNewLines[i] = vmdkStrReplace(pImage->Descriptor.aLines[line], 5354 pszOldBaseName, pszNewBaseName); 5355 if (!apszNewLines[i]) 5356 goto rollback; 5357 pImage->Descriptor.aLines[line] = apszNewLines[i]; 5358 } 5359 /* Make sure the descriptor gets written back. */ 5360 pImage->Descriptor.fDirty = true; 5361 /* Flush the descriptor now, in case it is embedded. */ 5362 vmdkFlushImage(pImage, NULL); 5363 5364 /* Close and rename/move extents. */ 5365 for (i = 0; i < cExtents; i++) 5366 { 5367 PVMDKEXTENT pExtent = &pImage->pExtents[i]; 5368 /* Compose new name for the extent. */ 5369 apszNewName[i] = vmdkStrReplace(pExtent->pszFullname, 5370 pszOldFullName, pszNewFullName); 5371 if (!apszNewName[i]) 5372 goto rollback; 5373 /* Close the extent file. */ 5374 rc = vmdkFileClose(pImage, &pExtent->pFile, false); 5593 int rc = vmdkRenameStatePrepare(pImage, &RenameState, pszFilename); 5594 if (RT_SUCCESS(rc)) 5595 { 5596 /* --- Up to this point we have not done any damage yet. --- */ 5597 5598 rc = vmdkRenameWorker(pImage, &RenameState, pszFilename); 5599 /* Roll back all changes in case of failure. */ 5375 5600 if (RT_FAILURE(rc)) 5376 goto rollback; 5377 5378 /* Rename the extent file. */ 5379 rc = vdIfIoIntFileMove(pImage->pIfIo, pExtent->pszFullname, apszNewName[i], 0); 5380 if (RT_FAILURE(rc)) 5381 goto rollback; 5382 /* Remember the old name. */ 5383 apszOldName[i] = RTStrDup(pExtent->pszFullname); 5384 } 5385 /* Release all old stuff. */ 5386 rc = vmdkFreeImage(pImage, false); 5387 if (RT_FAILURE(rc)) 5388 goto rollback; 5389 5390 fImageFreed = true; 5391 5392 /* Last elements of new/old name arrays are intended for 5393 * storing descriptor's names. 5394 */ 5395 apszNewName[cExtents] = RTStrDup(pszFilename); 5396 /* Rename the descriptor file if it's separate. */ 5397 if (!fEmbeddedDesc) 5398 { 5399 rc = vdIfIoIntFileMove(pImage->pIfIo, pImage->pszFilename, apszNewName[cExtents], 0); 5400 if (RT_FAILURE(rc)) 5401 goto rollback; 5402 /* Save old name only if we may need to change it back. */ 5403 apszOldName[cExtents] = RTStrDup(pszFilename); 5404 } 5405 5406 /* Update pImage with the new information. */ 5407 pImage->pszFilename = pszFilename; 5408 5409 /* Open the new image. */ 5410 rc = vmdkOpenImage(pImage, pImage->uOpenFlags); 5411 if (RT_SUCCESS(rc)) 5412 goto out; 5413 5414 rollback: 5415 /* Roll back all changes in case of failure. */ 5416 if (RT_FAILURE(rc)) 5417 { 5418 int rrc; 5419 if (!fImageFreed) 5420 { 5421 /* 5422 * Some extents may have been closed, close the rest. We will 5423 * re-open the whole thing later. 5424 */ 5425 vmdkFreeImage(pImage, false); 5426 } 5427 /* Rename files back. */ 5428 for (i = 0; i <= cExtents; i++) 5429 { 5430 if (apszOldName[i]) 5431 { 5432 rrc = vdIfIoIntFileMove(pImage->pIfIo, apszNewName[i], apszOldName[i], 0); 5433 AssertRC(rrc); 5434 } 5435 } 5436 /* Restore the old descriptor. */ 5437 PVMDKFILE pFile; 5438 rrc = vmdkFileOpen(pImage, &pFile, pszOldDescName, 5439 VDOpenFlagsToFileOpenFlags(VD_OPEN_FLAGS_NORMAL, 5440 false /* fCreate */)); 5441 AssertRC(rrc); 5442 if (fEmbeddedDesc) 5443 { 5444 ExtentCopy.pFile = pFile; 5445 pImage->pExtents = &ExtentCopy; 5446 } 5447 else 5448 { 5449 /* Shouldn't be null for separate descriptor. 5450 * There will be no access to the actual content. 5451 */ 5452 pImage->pDescData = pszOldDescName; 5453 pImage->pFile = pFile; 5454 } 5455 pImage->Descriptor = DescriptorCopy; 5456 vmdkWriteDescriptor(pImage, NULL); 5457 vmdkFileClose(pImage, &pFile, false); 5458 /* Get rid of the stuff we implanted. */ 5459 pImage->pExtents = NULL; 5460 pImage->pFile = NULL; 5461 pImage->pDescData = NULL; 5462 /* Re-open the image back. */ 5463 pImage->pszFilename = pszOldImageName; 5464 rrc = vmdkOpenImage(pImage, pImage->uOpenFlags); 5465 AssertRC(rrc); 5466 } 5467 5468 out: 5469 for (i = 0; i < DescriptorCopy.cLines; i++) 5470 if (DescriptorCopy.aLines[i]) 5471 RTStrFree(DescriptorCopy.aLines[i]); 5472 if (apszOldName) 5473 { 5474 for (i = 0; i <= cExtents; i++) 5475 if (apszOldName[i]) 5476 RTStrFree(apszOldName[i]); 5477 RTMemTmpFree(apszOldName); 5478 } 5479 if (apszNewName) 5480 { 5481 for (i = 0; i <= cExtents; i++) 5482 if (apszNewName[i]) 5483 RTStrFree(apszNewName[i]); 5484 RTMemTmpFree(apszNewName); 5485 } 5486 if (apszNewLines) 5487 { 5488 for (i = 0; i < cExtents; i++) 5489 if (apszNewLines[i]) 5490 RTStrFree(apszNewLines[i]); 5491 RTMemTmpFree(apszNewLines); 5492 } 5493 if (pszOldDescName) 5494 RTStrFree(pszOldDescName); 5495 if (pszOldBaseName) 5496 RTStrFree(pszOldBaseName); 5497 if (pszNewBaseName) 5498 RTStrFree(pszNewBaseName); 5499 if (pszOldFullName) 5500 RTStrFree(pszOldFullName); 5501 if (pszNewFullName) 5502 RTStrFree(pszNewFullName); 5601 { 5602 int rrc = vmdkRenameRollback(pImage, &RenameState); 5603 AssertRC(rrc); 5604 } 5605 } 5606 5607 vmdkRenameStateDestroy(&RenameState); 5503 5608 LogFlowFunc(("returns %Rrc\n", rc)); 5504 5609 return rc;
Note:
See TracChangeset
for help on using the changeset viewer.