* Copyright 2003-2007, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#include "DiskDeviceJobGenerator.h"
#include <new>
#include <stdlib.h>
#include <string.h>
#include <DiskDevice.h>
#include <MutablePartition.h>
#include <ddm_userland_interface_defs.h>
#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...)
using std::nothrow;
\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);
}
struct DiskDeviceJobGenerator::MoveInfo {
BPartition* partition;
off_t position;
off_t target_position;
off_t size;
};
struct DiskDeviceJobGenerator::PartitionRefInfo {
PartitionRefInfo()
: partition(NULL),
reference(NULL)
{
}
~PartitionRefInfo()
{
if (reference)
reference->ReleaseReference();
}
BPartition* partition;
PartitionReference* reference;
};
DiskDeviceJobGenerator::DiskDeviceJobGenerator(BDiskDevice* device,
DiskDeviceJobQueue* jobQueue)
: fDevice(device),
fJobQueue(jobQueue),
fMoveInfos(NULL),
fPartitionRefs(NULL),
fContentsToMove(NULL),
fContentsToMoveCount(0)
{
fPartitionCount = fDevice->CountDescendants()
+ fDevice->_CountDescendants();
fMoveInfos = new(nothrow) MoveInfo[fPartitionCount];
fPartitionRefs = new(nothrow) PartitionRefInfo[fPartitionCount];
fContentsToMove = new(nothrow) PartitionReference*[fPartitionCount];
}
DiskDeviceJobGenerator::~DiskDeviceJobGenerator()
{
delete[] fMoveInfos;
delete[] fPartitionRefs;
delete[] fContentsToMove;
}
status_t
DiskDeviceJobGenerator::GenerateJobs()
{
if (!fDevice || !fJobQueue)
return B_BAD_VALUE;
if (!fMoveInfos || !fPartitionRefs || !fContentsToMove)
return B_NO_MEMORY;
status_t error = _GenerateCleanupJobs(fDevice);
if (error != B_OK) {
TRACE("DiskDeviceJobGenerator::GenerateJobs(): _GenerateCleanupJobs() "
"failed\n");
return error;
}
error = _GeneratePlacementJobs(fDevice);
if (error != B_OK) {
TRACE("DiskDeviceJobGenerator::GenerateJobs(): "
"_GeneratePlacementJobs() failed\n");
return error;
}
error = _GenerateRemainingJobs(NULL, fDevice);
if (error != B_OK) {
TRACE("DiskDeviceJobGenerator::GenerateJobs(): "
"_GenerateRemainingJobs() failed\n");
return error;
}
TRACE("DiskDeviceJobGenerator::GenerateJobs(): succeeded\n");
return B_OK;
}
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;
}
status_t
DiskDeviceJobGenerator::_GenerateCleanupJobs(BPartition* partition)
{
if (BMutablePartition* shadow = _GetMutablePartition(partition)) {
if ((shadow->ChangeFlags() & B_PARTITION_CHANGED_INITIALIZATION)
&& partition->fPartitionData->content_type) {
status_t error = _GenerateUninitializeJob(partition);
if (error != B_OK)
return error;
} else {
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()) {
status_t error = _GenerateDeleteChildJob(parent, partition);
if (error != B_OK)
return error;
}
return B_OK;
}
status_t
DiskDeviceJobGenerator::_GeneratePlacementJobs(BPartition* partition)
{
if (BMutablePartition* shadow = _GetMutablePartition(partition)) {
if (shadow->Status() == B_PARTITION_UNRECOGNIZED
&& (shadow->Size() != partition->Size()
|| shadow->Offset() != partition->Offset())) {
return B_ERROR;
}
if (shadow->Size() > partition->Size()) {
status_t error = _GenerateResizeJob(partition);
if (error != B_OK)
return error;
}
status_t error = _GenerateChildPlacementJobs(partition);
if (error != B_OK)
return error;
if (shadow->Size() < partition->Size()) {
status_t error = _GenerateResizeJob(partition);
if (error != B_OK)
return error;
}
}
return B_OK;
}
status_t
DiskDeviceJobGenerator::_GenerateChildPlacementJobs(BPartition* partition)
{
BMutablePartition* shadow = _GetMutablePartition(partition);
if (!shadow->ContentType()
|| (shadow->ChangeFlags() & B_PARTITION_CHANGED_INITIALIZATION)) {
return B_OK;
}
int32 childCount = 0;
int32 moveForth = 0;
int32 moveBack = 0;
for (int32 i = 0; BPartition* child = partition->_ChildAt(i); i++) {
if (BMutablePartition* childShadow = _GetMutablePartition(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++;
if (childShadow->Size() < child->Size()) {
status_t error = _GeneratePlacementJobs(child);
if (error != B_OK)
return error;
info.size = childShadow->Size();
}
}
}
if (childCount > 0 && moveForth + moveBack > 0) {
qsort(fMoveInfos, childCount, sizeof(MoveInfo),
_CompareMoveInfoPosition);
}
while (moveForth + moveBack > 0) {
int32 moved = 0;
if (moveForth < moveBack) {
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) {
status_t error = _GenerateMoveJob(info.partition);
if (error != B_OK)
return error;
info.position = info.target_position;
moved++;
moveBack--;
}
}
}
} else {
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) {
status_t error = _GenerateMoveJob(info.partition);
if (error != B_OK)
return error;
info.position = info.target_position;
moved++;
moveForth--;
}
}
}
}
if (moved == 0)
return B_ERROR;
}
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;
}
status_t
DiskDeviceJobGenerator::_GenerateRemainingJobs(BPartition* parent,
BPartition* partition)
{
user_partition_data* partitionData = partition->fPartitionData;
uint32 changeFlags
= partition->fDelegate->MutablePartition()->ChangeFlags();
if (!partitionData) {
if (!parent)
return B_BAD_VALUE;
status_t error = _GenerateCreateChildJob(parent, partition);
if (error != B_OK)
return error;
} else {
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;
}
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;
}
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()) {
if (changeFlags & B_PARTITION_CHANGED_INITIALIZATION) {
status_t error = _GenerateInitializeJob(partition);
if (error != B_OK)
return error;
} else {
if ((changeFlags & B_PARTITION_CHANGED_NAME)
|| compare_string(partition->RawContentName(),
partitionData->content_name)) {
status_t error = _GenerateSetContentNameJob(partition);
if (error != B_OK)
return error;
}
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;
}
if (changeFlags & B_PARTITION_CHANGED_DEFRAGMENTATION) {
status_t error = _GenerateDefragmentJob(partition);
if (error != B_OK)
return error;
}
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;
}
}
}
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;
}
BMutablePartition*
DiskDeviceJobGenerator::_GetMutablePartition(BPartition* partition)
{
if (!partition)
return NULL;
return partition->fDelegate
? partition->fDelegate->MutablePartition() : NULL;
}
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);
}
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));
}
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);
}
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);
}
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));
}
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));
}
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);
}
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));
}
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()));
}
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;
fContentsToMoveCount = 0;
error = _CollectContentsToMove(partition);
if (error != B_OK)
return B_OK;
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);
}
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);
}
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);
}
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);
}
status_t
DiskDeviceJobGenerator::_CollectContentsToMove(BPartition* partition)
{
BMutablePartition* shadow = _GetMutablePartition(partition);
if (shadow->Status() == B_PARTITION_UNRECOGNIZED)
return B_ERROR;
if (shadow->ContentType()
&& !(shadow->ChangeFlags() & B_PARTITION_CHANGED_INITIALIZATION)) {
status_t error = _PushContentsToMove(partition);
if (error != B_OK)
return error;
}
for (int32 i = 0; BPartition* child = partition->ChildAt(i); i++) {
status_t error = _CollectContentsToMove(child);
if (error != B_OK)
return error;
}
return B_OK;
}
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;
}
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) {
info.reference = new(nothrow) PartitionReference();
if (!info.reference)
return B_NO_MEMORY;
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;
}
}
return B_ERROR;
}
int
DiskDeviceJobGenerator::_CompareMoveInfoPosition(const void* _a, const void* _b)
{
const MoveInfo* a = static_cast<const MoveInfo*>(_a);
const MoveInfo* b = static_cast<const MoveInfo*>(_b);
if (a->position < b->position)
return -1;
if (a->position > b->position)
return 1;
return 0;
}