/* * Copyright 2003-2007, Ingo Weinhold, ingo_weinhold@gmx.de. * Distributed under the terms of the MIT License. */ #include "DiskDeviceJobGenerator.h" #include #include #include #include #include #include #include "DiskDeviceJob.h" #include "DiskDeviceJobQueue.h" #include "PartitionDelegate.h" #include "PartitionReference.h" #include "CreateChildJob.h" #include "DeleteChildJob.h" #include "DefragmentJob.h" #include "InitializeJob.h" #include "MoveJob.h" #include "RepairJob.h" #include "ResizeJob.h" #include "SetStringJob.h" #include "UninitializeJob.h" #undef TRACE #define TRACE(x...) //#define TRACE(x...) printf(x) using std::nothrow; // compare_string /*! \brief \c NULL aware strcmp(). \c NULL is considered the least of all strings. \c NULL equals \c NULL. \param str1 First string. \param str2 Second string. \return A value less than 0, if \a str1 is less than \a str2, 0, if they are equal, or a value greater than 0, if \a str1 is greater \a str2. */ static inline int compare_string(const char* str1, const char* str2) { if (str1 == NULL) { if (str2 == NULL) return 0; return 1; } else if (str2 == NULL) return -1; return strcmp(str1, str2); } // MoveInfo struct DiskDeviceJobGenerator::MoveInfo { BPartition* partition; off_t position; off_t target_position; off_t size; }; // PartitionRefInfo struct DiskDeviceJobGenerator::PartitionRefInfo { PartitionRefInfo() : partition(NULL), reference(NULL) { } ~PartitionRefInfo() { if (reference) reference->ReleaseReference(); } BPartition* partition; PartitionReference* reference; }; // constructor DiskDeviceJobGenerator::DiskDeviceJobGenerator(BDiskDevice* device, DiskDeviceJobQueue* jobQueue) : fDevice(device), fJobQueue(jobQueue), fMoveInfos(NULL), fPartitionRefs(NULL), fContentsToMove(NULL), fContentsToMoveCount(0) { // Make sure the arrays are big enough (worst case: all old partitions have // been deleted and new ones been created). fPartitionCount = fDevice->CountDescendants() + fDevice->_CountDescendants(); fMoveInfos = new(nothrow) MoveInfo[fPartitionCount]; fPartitionRefs = new(nothrow) PartitionRefInfo[fPartitionCount]; fContentsToMove = new(nothrow) PartitionReference*[fPartitionCount]; } // destructor DiskDeviceJobGenerator::~DiskDeviceJobGenerator() { delete[] fMoveInfos; delete[] fPartitionRefs; delete[] fContentsToMove; } // GenerateJobs status_t DiskDeviceJobGenerator::GenerateJobs() { // check parameters if (!fDevice || !fJobQueue) return B_BAD_VALUE; if (!fMoveInfos || !fPartitionRefs || !fContentsToMove) return B_NO_MEMORY; // 1) Generate jobs for all physical partitions that don't have an // associated shadow partition, i.e. those that shall be deleted. // 2) Generate uninitialize jobs for all partition whose initialization // changes, also those that shall be initialized with a disk system. // This simplifies moving and resizing. status_t error = _GenerateCleanupJobs(fDevice); if (error != B_OK) { TRACE("DiskDeviceJobGenerator::GenerateJobs(): _GenerateCleanupJobs() " "failed\n"); return error; } // Generate jobs that move and resize the remaining physical partitions // to their final position/size. error = _GeneratePlacementJobs(fDevice); if (error != B_OK) { TRACE("DiskDeviceJobGenerator::GenerateJobs(): " "_GeneratePlacementJobs() failed\n"); return error; } // Generate the remaining jobs in one run: initialization, creation of // partitions, and changing of name, content name, type, parameters, and // content parameters. error = _GenerateRemainingJobs(NULL, fDevice); if (error != B_OK) { TRACE("DiskDeviceJobGenerator::GenerateJobs(): " "_GenerateRemainingJobs() failed\n"); return error; } TRACE("DiskDeviceJobGenerator::GenerateJobs(): succeeded\n"); return B_OK; } // _AddJob status_t DiskDeviceJobGenerator::_AddJob(DiskDeviceJob* job) { if (!job) return B_NO_MEMORY; status_t error = fJobQueue->AddJob(job); if (error != B_OK) delete job; return error; } // _GenerateCleanupJobs status_t DiskDeviceJobGenerator::_GenerateCleanupJobs(BPartition* partition) { // TODO: Depending on how this shall be handled, we might want to unmount // all descendants of a partition to be uninitialized or removed. if (BMutablePartition* shadow = _GetMutablePartition(partition)) { if ((shadow->ChangeFlags() & B_PARTITION_CHANGED_INITIALIZATION) && partition->fPartitionData->content_type) { // partition changes initialization status_t error = _GenerateUninitializeJob(partition); if (error != B_OK) return error; } else { // recurse for (int32 i = 0; BPartition* child = partition->_ChildAt(i); i++) { status_t error = _GenerateCleanupJobs(child); if (error != B_OK) return error; } } } else if (BPartition* parent = partition->Parent()) { // create job and add it to the queue status_t error = _GenerateDeleteChildJob(parent, partition); if (error != B_OK) return error; } return B_OK; } // _GeneratePlacementJobs status_t DiskDeviceJobGenerator::_GeneratePlacementJobs(BPartition* partition) { if (BMutablePartition* shadow = _GetMutablePartition(partition)) { // Don't resize/move partitions that have an unrecognized contents. // They must have been uninitialized before. if (shadow->Status() == B_PARTITION_UNRECOGNIZED && (shadow->Size() != partition->Size() || shadow->Offset() != partition->Offset())) { return B_ERROR; } if (shadow->Size() > partition->Size()) { // size grows: resize first status_t error = _GenerateResizeJob(partition); if (error != B_OK) return error; } // place the children status_t error = _GenerateChildPlacementJobs(partition); if (error != B_OK) return error; if (shadow->Size() < partition->Size()) { // size shrinks: resize now status_t error = _GenerateResizeJob(partition); if (error != B_OK) return error; } } return B_OK; } // _GenerateChildPlacementJobs status_t DiskDeviceJobGenerator::_GenerateChildPlacementJobs(BPartition* partition) { BMutablePartition* shadow = _GetMutablePartition(partition); // nothing to do, if the partition contains no partitioning system or // shall be re-initialized if (!shadow->ContentType() || (shadow->ChangeFlags() & B_PARTITION_CHANGED_INITIALIZATION)) { return B_OK; } // first resize all children that shall shrink and place their descendants int32 childCount = 0; int32 moveForth = 0; int32 moveBack = 0; for (int32 i = 0; BPartition* child = partition->_ChildAt(i); i++) { if (BMutablePartition* childShadow = _GetMutablePartition(child)) { // add a MoveInfo for the child MoveInfo& info = fMoveInfos[childCount]; childCount++; info.partition = child; info.position = child->Offset(); info.target_position = childShadow->Offset(); info.size = child->Size(); if (info.position < info.target_position) moveForth++; else if (info.position > info.target_position) moveBack++; // resize the child, if it shall shrink if (childShadow->Size() < child->Size()) { status_t error = _GeneratePlacementJobs(child); if (error != B_OK) return error; info.size = childShadow->Size(); } } } // sort the move infos if (childCount > 0 && moveForth + moveBack > 0) { qsort(fMoveInfos, childCount, sizeof(MoveInfo), _CompareMoveInfoPosition); } // move the children to their final positions while (moveForth + moveBack > 0) { int32 moved = 0; if (moveForth < moveBack) { // move children back for (int32 i = 0; i < childCount; i++) { MoveInfo& info = fMoveInfos[i]; if (info.position > info.target_position) { if (i == 0 || info.target_position >= fMoveInfos[i - 1].position + fMoveInfos[i - 1].size) { // check OK -- the partition wouldn't be moved before // the end of the preceding one status_t error = _GenerateMoveJob(info.partition); if (error != B_OK) return error; info.position = info.target_position; moved++; moveBack--; } } } } else { // move children forth for (int32 i = childCount - 1; i >= 0; i--) { MoveInfo &info = fMoveInfos[i]; if (info.position > info.target_position) { if (i == childCount - 1 || info.target_position + info.size <= fMoveInfos[i - 1].position) { // check OK -- the partition wouldn't be moved before // the end of the preceding one status_t error = _GenerateMoveJob(info.partition); if (error != B_OK) return error; info.position = info.target_position; moved++; moveForth--; } } } } // terminate, if no partition could be moved if (moved == 0) return B_ERROR; } // now resize all children that shall grow/keep their size and place // their descendants for (int32 i = 0; BPartition* child = partition->_ChildAt(i); i++) { if (BMutablePartition* childShadow = _GetMutablePartition(child)) { if (childShadow->Size() >= child->Size()) { status_t error = _GeneratePlacementJobs(child); if (error != B_OK) return error; } } } return B_OK; } // _GenerateRemainingJobs status_t DiskDeviceJobGenerator::_GenerateRemainingJobs(BPartition* parent, BPartition* partition) { user_partition_data* partitionData = partition->fPartitionData; uint32 changeFlags = partition->fDelegate->MutablePartition()->ChangeFlags(); // create the partition, if not existing yet if (!partitionData) { if (!parent) return B_BAD_VALUE; status_t error = _GenerateCreateChildJob(parent, partition); if (error != B_OK) return error; } else { // partition already exists: set non-content properties // name if ((changeFlags & B_PARTITION_CHANGED_NAME) || compare_string(partition->Name(), partitionData->name)) { if (!parent) return B_BAD_VALUE; status_t error = _GenerateSetNameJob(parent, partition); if (error != B_OK) return error; } // type if ((changeFlags & B_PARTITION_CHANGED_TYPE) || compare_string(partition->Type(), partitionData->type)) { if (!parent) return B_BAD_VALUE; status_t error = _GenerateSetTypeJob(parent, partition); if (error != B_OK) return error; } // parameters if ((partition->Parameters() != NULL) && ((changeFlags & B_PARTITION_CHANGED_PARAMETERS) != 0 || compare_string(partition->Parameters(), partitionData->parameters))) { if (!parent) return B_BAD_VALUE; status_t error = _GenerateSetParametersJob(parent, partition); if (error != B_OK) return error; } } if (partition->ContentType()) { // initialize the partition, if required if (changeFlags & B_PARTITION_CHANGED_INITIALIZATION) { status_t error = _GenerateInitializeJob(partition); if (error != B_OK) return error; } else { // partition not (re-)initialized, set content properties // content name if ((changeFlags & B_PARTITION_CHANGED_NAME) || compare_string(partition->RawContentName(), partitionData->content_name)) { status_t error = _GenerateSetContentNameJob(partition); if (error != B_OK) return error; } // content parameters if ((partition->ContentParameters() != NULL) && ((changeFlags & B_PARTITION_CHANGED_PARAMETERS) != 0 || compare_string(partition->ContentParameters(), partitionData->content_parameters))) { status_t error = _GenerateSetContentParametersJob(partition); if (error != B_OK) return error; } // defragment if (changeFlags & B_PARTITION_CHANGED_DEFRAGMENTATION) { status_t error = _GenerateDefragmentJob(partition); if (error != B_OK) return error; } // check / repair bool repair = (changeFlags & B_PARTITION_CHANGED_REPAIR); if ((changeFlags & B_PARTITION_CHANGED_CHECK) || repair) { status_t error = _GenerateRepairJob(partition, repair); if (error != B_OK) return error; } } } // recurse for (int32 i = 0; BPartition* child = partition->ChildAt(i); i++) { status_t error = _GenerateRemainingJobs(partition, child); if (error != B_OK) return error; } return B_OK; } // _GetMutablePartition BMutablePartition* DiskDeviceJobGenerator::_GetMutablePartition(BPartition* partition) { if (!partition) return NULL; return partition->fDelegate ? partition->fDelegate->MutablePartition() : NULL; } // _GenerateInitializeJob status_t DiskDeviceJobGenerator::_GenerateInitializeJob(BPartition* partition) { PartitionReference* reference; status_t error = _GetPartitionReference(partition, reference); if (error != B_OK) return error; InitializeJob* job = new(nothrow) InitializeJob(reference); if (!job) return B_NO_MEMORY; error = job->Init(partition->ContentType(), partition->RawContentName(), partition->ContentParameters()); if (error != B_OK) { delete job; return error; } return _AddJob(job); } // _GenerateUninitializeJob status_t DiskDeviceJobGenerator::_GenerateUninitializeJob(BPartition* partition) { PartitionReference* reference; status_t error = _GetPartitionReference(partition, reference); if (error != B_OK) return error; BPartition* parent = partition->Parent(); PartitionReference* parentReference = NULL; if (parent != NULL) { error = _GetPartitionReference(parent, parentReference); if (error != B_OK) return error; } return _AddJob(new(nothrow) UninitializeJob(reference, parentReference)); } // _GenerateSetContentNameJob status_t DiskDeviceJobGenerator::_GenerateSetContentNameJob(BPartition* partition) { PartitionReference* reference; status_t error = _GetPartitionReference(partition, reference); if (error != B_OK) return error; SetStringJob* job = new(nothrow) SetStringJob(reference); if (!job) return B_NO_MEMORY; error = job->Init(partition->RawContentName(), B_DISK_DEVICE_JOB_SET_CONTENT_NAME); if (error != B_OK) { delete job; return error; } return _AddJob(job); } // _GenerateSetContentParametersJob status_t DiskDeviceJobGenerator::_GenerateSetContentParametersJob(BPartition* partition) { PartitionReference* reference; status_t error = _GetPartitionReference(partition, reference); if (error != B_OK) return error; SetStringJob* job = new(nothrow) SetStringJob(reference); if (!job) return B_NO_MEMORY; error = job->Init(partition->ContentParameters(), B_DISK_DEVICE_JOB_SET_CONTENT_PARAMETERS); if (error != B_OK) { delete job; return error; } return _AddJob(job); } // _GenerateDefragmentJob status_t DiskDeviceJobGenerator::_GenerateDefragmentJob(BPartition* partition) { PartitionReference* reference; status_t error = _GetPartitionReference(partition, reference); if (error != B_OK) return error; return _AddJob(new(nothrow) DefragmentJob(reference)); } // _GenerateRepairJob status_t DiskDeviceJobGenerator::_GenerateRepairJob(BPartition* partition, bool repair) { PartitionReference* reference; status_t error = _GetPartitionReference(partition, reference); if (error != B_OK) return error; return _AddJob(new(nothrow) RepairJob(reference, repair)); } // _GenerateCreateChildJob status_t DiskDeviceJobGenerator::_GenerateCreateChildJob(BPartition* parent, BPartition* partition) { PartitionReference* parentReference; status_t error = _GetPartitionReference(parent, parentReference); if (error != B_OK) return error; PartitionReference* reference; error = _GetPartitionReference(partition, reference); if (error != B_OK) return error; CreateChildJob* job = new(nothrow) CreateChildJob(parentReference, reference); if (!job) return B_NO_MEMORY; error = job->Init(partition->Offset(), partition->Size(), partition->Type(), partition->Name(), partition->Parameters()); if (error != B_OK) { delete job; return error; } return _AddJob(job); } // _GenerateDeleteChildJob status_t DiskDeviceJobGenerator::_GenerateDeleteChildJob(BPartition* parent, BPartition* partition) { PartitionReference* parentReference; status_t error = _GetPartitionReference(parent, parentReference); if (error != B_OK) return error; PartitionReference* reference; error = _GetPartitionReference(partition, reference); if (error != B_OK) return error; return _AddJob(new(nothrow) DeleteChildJob(parentReference, reference)); } // _GenerateResizeJob status_t DiskDeviceJobGenerator::_GenerateResizeJob(BPartition* partition) { BPartition* parent = partition->Parent(); if (!parent) return B_BAD_VALUE; PartitionReference* parentReference; status_t error = _GetPartitionReference(parent, parentReference); if (error != B_OK) return error; PartitionReference* reference; error = _GetPartitionReference(partition, reference); if (error != B_OK) return error; return _AddJob(new(nothrow) ResizeJob(parentReference, reference, partition->Size(), partition->ContentSize())); } // _GenerateMoveJob status_t DiskDeviceJobGenerator::_GenerateMoveJob(BPartition* partition) { BPartition* parent = partition->Parent(); if (!parent) return B_BAD_VALUE; PartitionReference* parentReference; status_t error = _GetPartitionReference(parent, parentReference); if (error != B_OK) return error; PartitionReference* reference; error = _GetPartitionReference(partition, reference); if (error != B_OK) return error; // collect all descendants whose contents need to be moved fContentsToMoveCount = 0; error = _CollectContentsToMove(partition); if (error != B_OK) return B_OK; // create and init the job MoveJob* job = new(nothrow) MoveJob(parentReference, reference); if (!job) return B_NO_MEMORY; error = job->Init(partition->Offset(), fContentsToMove, fContentsToMoveCount); if (error != B_OK) { delete job; return error; } return _AddJob(job); } // _GenerateSetNameJob status_t DiskDeviceJobGenerator::_GenerateSetNameJob(BPartition* parent, BPartition* partition) { PartitionReference* parentReference; status_t error = _GetPartitionReference(parent, parentReference); if (error != B_OK) return error; PartitionReference* reference; error = _GetPartitionReference(partition, reference); if (error != B_OK) return error; SetStringJob* job = new(nothrow) SetStringJob(parentReference, reference); if (!job) return B_NO_MEMORY; error = job->Init(partition->Name(), B_DISK_DEVICE_JOB_SET_NAME); if (error != B_OK) { delete job; return error; } return _AddJob(job); } // _GenerateSetTypeJob status_t DiskDeviceJobGenerator::_GenerateSetTypeJob(BPartition* parent, BPartition* partition) { PartitionReference* parentReference; status_t error = _GetPartitionReference(parent, parentReference); if (error != B_OK) return error; PartitionReference* reference; error = _GetPartitionReference(partition, reference); if (error != B_OK) return error; SetStringJob* job = new(nothrow) SetStringJob(parentReference, reference); if (!job) return B_NO_MEMORY; error = job->Init(partition->Type(), B_DISK_DEVICE_JOB_SET_TYPE); if (error != B_OK) { delete job; return error; } return _AddJob(job); } // _GenerateSetParametersJob status_t DiskDeviceJobGenerator::_GenerateSetParametersJob(BPartition* parent, BPartition* partition) { PartitionReference* parentReference; status_t error = _GetPartitionReference(parent, parentReference); if (error != B_OK) return error; PartitionReference* reference; error = _GetPartitionReference(partition, reference); if (error != B_OK) return error; SetStringJob* job = new(nothrow) SetStringJob(parentReference, reference); if (!job) return B_NO_MEMORY; error = job->Init(partition->Parameters(), B_DISK_DEVICE_JOB_SET_PARAMETERS); if (error != B_OK) { delete job; return error; } return _AddJob(job); } // _CollectContentsToMove status_t DiskDeviceJobGenerator::_CollectContentsToMove(BPartition* partition) { BMutablePartition* shadow = _GetMutablePartition(partition); if (shadow->Status() == B_PARTITION_UNRECOGNIZED) return B_ERROR; // if the partition has contents, push its ID if (shadow->ContentType() && !(shadow->ChangeFlags() & B_PARTITION_CHANGED_INITIALIZATION)) { status_t error = _PushContentsToMove(partition); if (error != B_OK) return error; } // recurse for (int32 i = 0; BPartition* child = partition->ChildAt(i); i++) { status_t error = _CollectContentsToMove(child); if (error != B_OK) return error; } return B_OK; } // _PushContentsToMove status_t DiskDeviceJobGenerator::_PushContentsToMove(BPartition* partition) { if (fContentsToMoveCount >= fPartitionCount) return B_ERROR; PartitionReference* reference; status_t error = _GetPartitionReference(partition, reference); if (error != B_OK) return error; fContentsToMove[fContentsToMoveCount++] = reference; return B_OK; } // _GetPartitionReference status_t DiskDeviceJobGenerator::_GetPartitionReference(BPartition* partition, PartitionReference*& reference) { if (!partition) return B_BAD_VALUE; for (int32 i = 0; i < fPartitionCount; i++) { PartitionRefInfo& info = fPartitionRefs[i]; if (info.partition == partition) { reference = info.reference; return B_OK; } if (info.partition == NULL) { // create partition reference info.reference = new(nothrow) PartitionReference(); if (!info.reference) return B_NO_MEMORY; // set partition ID and change counter user_partition_data* partitionData = partition->fPartitionData; if (partitionData) { info.reference->SetPartitionID(partitionData->id); info.reference->SetChangeCounter(partitionData->change_counter); } info.partition = partition; reference = info.reference; return B_OK; } } // Out of slots -- that can't happen. return B_ERROR; } // _CompareMoveInfoOffset int DiskDeviceJobGenerator::_CompareMoveInfoPosition(const void* _a, const void* _b) { const MoveInfo* a = static_cast(_a); const MoveInfo* b = static_cast(_b); if (a->position < b->position) return -1; if (a->position > b->position) return 1; return 0; }