diff --git a/src/libcalamares/Job.cpp b/src/libcalamares/Job.cpp index 9e32ff47f..5a63c100e 100644 --- a/src/libcalamares/Job.cpp +++ b/src/libcalamares/Job.cpp @@ -35,6 +35,13 @@ JobResult::message() const } +void +JobResult::setMessage( const QString& message ) +{ + m_message = message; +} + + QString JobResult::details() const { @@ -42,6 +49,13 @@ JobResult::details() const } +void +JobResult::setDetails( const QString& details ) +{ + m_details = details; +} + + JobResult JobResult::ok() { diff --git a/src/libcalamares/Job.h b/src/libcalamares/Job.h index 85eb93cf4..f6a2f10dc 100644 --- a/src/libcalamares/Job.h +++ b/src/libcalamares/Job.h @@ -32,8 +32,10 @@ public: operator bool() const; QString message() const; + void setMessage( const QString& message ); QString details() const; + void setDetails( const QString& details ); static JobResult ok(); diff --git a/src/modules/partition/CMakeLists.txt b/src/modules/partition/CMakeLists.txt index 4a5eb5f3d..b9ddd618d 100644 --- a/src/modules/partition/CMakeLists.txt +++ b/src/modules/partition/CMakeLists.txt @@ -22,6 +22,8 @@ calamares_add_plugin( partition EXPORT_MACRO PLUGINDLLEXPORT_PRO SOURCES BootLoaderModel.cpp + CheckFileSystemJob.cpp + ColorUtils.cpp CreatePartitionDialog.cpp CreatePartitionJob.cpp CreatePartitionTableJob.cpp @@ -30,6 +32,7 @@ calamares_add_plugin( partition EditExistingPartitionDialog.cpp FillGlobalStorageJob.cpp FormatPartitionJob.cpp + MoveFileSystemJob.cpp PartitionCoreModule.cpp PartitionInfo.cpp PartitionIterator.cpp @@ -37,9 +40,10 @@ calamares_add_plugin( partition PartitionModel.cpp PartitionPage.cpp PartitionPreview.cpp - PartitionSizeWidget.cpp + PartitionSizeController.cpp PartitionViewStep.cpp PMUtils.cpp + ResizePartitionJob.cpp UI CreatePartitionDialog.ui CreatePartitionTableDialog.ui diff --git a/src/modules/partition/CheckFileSystemJob.cpp b/src/modules/partition/CheckFileSystemJob.cpp new file mode 100644 index 000000000..3d353bd7a --- /dev/null +++ b/src/modules/partition/CheckFileSystemJob.cpp @@ -0,0 +1,56 @@ +/* === This file is part of Calamares - === + * + * Copyright 2014, Aurélien Gâteau + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include + +// CalaPM +#include +#include +#include + +CheckFileSystemJob::CheckFileSystemJob( Partition* partition ) + : PartitionJob( partition ) +{} + +QString +CheckFileSystemJob::prettyName() const +{ + QString path = partition()->partitionPath(); + return tr( "Checking file system on partition %1." ).arg( path ); +} + +Calamares::JobResult +CheckFileSystemJob::exec() +{ + FileSystem& fs = partition()->fileSystem(); + + // if we cannot check, assume everything is fine + if ( fs.supportCheck() != FileSystem::cmdSupportFileSystem ) + return Calamares::JobResult::ok(); + + Report report( nullptr ); + bool ok = fs.check( report, partition()->partitionPath() ); + if ( !ok ) + return Calamares::JobResult::error( + tr( "The file system check on partition %1 failed." ) + .arg( partition()->partitionPath() ), + report.toText() + ); + + return Calamares::JobResult::ok(); +} diff --git a/src/modules/partition/CheckFileSystemJob.h b/src/modules/partition/CheckFileSystemJob.h new file mode 100644 index 000000000..2eb32d2be --- /dev/null +++ b/src/modules/partition/CheckFileSystemJob.h @@ -0,0 +1,33 @@ +/* === This file is part of Calamares - === + * + * Copyright 2014, Aurélien Gâteau + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef CHECKFILESYSTEMJOB_H +#define CHECKFILESYSTEMJOB_H + +#include + +class CheckFileSystemJob : public PartitionJob +{ +public: + CheckFileSystemJob( Partition* partition ); + + QString prettyName() const override; + Calamares::JobResult exec() override; +}; + +#endif /* CHECKFILESYSTEMJOB_H */ diff --git a/src/modules/partition/ColorUtils.cpp b/src/modules/partition/ColorUtils.cpp new file mode 100644 index 000000000..f9ef94793 --- /dev/null +++ b/src/modules/partition/ColorUtils.cpp @@ -0,0 +1,85 @@ +/* === This file is part of Calamares - === + * + * Copyright 2014, Aurélien Gâteau + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include + +#include + +// CalaPM +#include + +// Qt +#include + +static QColor COLORS[ 4 ] = +{ + "#448eca", + "#a5cc42", + "#d87e30", + "#ffbdbd", +}; +static QColor FREE_SPACE_COLOR = "#777777"; +static QColor EXTENDED_COLOR = "#aaaaaa"; + + +namespace ColorUtils +{ + +QColor freeSpaceColor() +{ + return FREE_SPACE_COLOR; +} + +QColor colorForPartition( Partition* partition ) +{ + if ( PMUtils::isPartitionFreeSpace( partition ) ) + return FREE_SPACE_COLOR; + if ( partition->roles().has( PartitionRole::Extended ) ) + return EXTENDED_COLOR; + // No partition-specific color needed, pick one from our list, but skip + // free space: we don't want a partition to change colors if space before + // it is inserted or removed + PartitionNode* parent = partition->parent(); + Q_ASSERT( parent ); + int colorIdx = 0; + for ( auto child : parent->children() ) + { + if ( child == partition ) + break; + if ( !PMUtils::isPartitionFreeSpace( child ) ) + ++colorIdx; + } + return COLORS[ colorIdx % 4 ]; +} + +QColor colorForPartitionInFreeSpace( Partition* partition ) +{ + PartitionNode* parent = partition->parent(); + Q_ASSERT( parent ); + int colorIdx = 0; + for ( auto child : parent->children() ) + { + if ( child == partition ) + break; + if ( !PMUtils::isPartitionFreeSpace( child ) ) + ++colorIdx; + } + return COLORS[ colorIdx % 4 ]; +} + +} // namespace diff --git a/src/modules/partition/PartitionSizeWidget.h b/src/modules/partition/ColorUtils.h similarity index 55% rename from src/modules/partition/PartitionSizeWidget.h rename to src/modules/partition/ColorUtils.h index 028de980d..b1e79a2dc 100644 --- a/src/modules/partition/PartitionSizeWidget.h +++ b/src/modules/partition/ColorUtils.h @@ -15,36 +15,22 @@ * You should have received a copy of the GNU General Public License * along with Calamares. If not, see . */ +#ifndef COLORUTILS_H +#define COLORUTILS_H -#ifndef PARTITIONSIZEWIDGET_H -#define PARTITIONSIZEWIDGET_H +class QColor; -#include - -class Device; class Partition; -class PartitionSizeWidget : public QSpinBox +namespace ColorUtils { -public: - typedef QPair< qint64, qint64 > SectorRange; - explicit PartitionSizeWidget( QWidget* parent = nullptr ); - void init( Device* device, Partition* partition ); +QColor freeSpaceColor(); - SectorRange sectorRange() const; +QColor colorForPartition( Partition* partition ); - bool isDirty() const; +QColor colorForPartitionInFreeSpace( Partition* partition ); -private: - Device* m_device = nullptr; - Partition* m_partition = nullptr; - int m_initialValue; +} - qint64 mbSizeForSectorRange( qint64 first, qint64 last ) const; - - qint64 computeMinSector() const; - qint64 computeMaxSector() const; -}; - -#endif /* PARTITIONSIZEWIDGET_H */ +#endif /* COLORUTILS_H */ diff --git a/src/modules/partition/CreatePartitionDialog.cpp b/src/modules/partition/CreatePartitionDialog.cpp index a717848f5..06d12827e 100644 --- a/src/modules/partition/CreatePartitionDialog.cpp +++ b/src/modules/partition/CreatePartitionDialog.cpp @@ -18,7 +18,9 @@ #include +#include #include +#include #include #include #include @@ -47,8 +49,6 @@ CreatePartitionDialog::CreatePartitionDialog( Device* device, PartitionNode* par { m_ui->setupUi( this ); - FileSystemFactory::init(); - if ( device->partitionTable()->type() == PartitionTable::msdos ) initMbrPartitionTypeUi(); else @@ -118,7 +118,8 @@ CreatePartitionDialog::createPartition() ); } - PartitionSizeWidget::SectorRange range = m_ui->sizeSpinBox->sectorRange(); + qint64 first = m_partResizerWidgetPartition->firstSector(); + qint64 last = m_partResizerWidgetPartition->lastSector(); FileSystem::Type fsType = m_role.has( PartitionRole::Extended ) ? FileSystem::Extended @@ -127,7 +128,7 @@ CreatePartitionDialog::createPartition() m_parent, *m_device, m_role, - fsType, range.first, range.second ); + fsType, first, last ); PartitionInfo::setMountPoint( partition, m_ui->mountPointComboBox->currentText() ); PartitionInfo::setFormat( partition, true ); @@ -147,10 +148,28 @@ CreatePartitionDialog::updateMountPointUi() m_ui->mountPointComboBox->setEnabled( enabled ); } +void +CreatePartitionDialog::initPartResizerWidget( Partition* partition ) +{ + PartitionSizeController* controller = new PartitionSizeController( this ); + m_partResizerWidgetPartition.reset( PMUtils::clonePartition( m_device, partition ) ); + + qint64 minFirstSector = partition->firstSector() - m_device->partitionTable()->freeSectorsBefore( *partition ); + qint64 maxLastSector = partition->lastSector() + m_device->partitionTable()->freeSectorsAfter( *partition ); + m_ui->partResizerWidget->init( *m_device, *m_partResizerWidgetPartition, minFirstSector, maxLastSector ); + + QColor color = PMUtils::isPartitionFreeSpace( partition ) + ? ColorUtils::colorForPartitionInFreeSpace( partition ) + : ColorUtils::colorForPartition( partition ); + controller->init( m_device, m_partResizerWidgetPartition.data(), color ); + controller->setPartResizerWidget( m_ui->partResizerWidget ); + controller->setSpinBox( m_ui->sizeSpinBox ); +} + void CreatePartitionDialog::initFromFreeSpace( Partition* freeSpacePartition ) { - m_ui->sizeSpinBox->init( m_device, freeSpacePartition ); + initPartResizerWidget( freeSpacePartition ); } void @@ -166,7 +185,7 @@ CreatePartitionDialog::initFromPartitionToCreate( Partition* partition ) return; } - m_ui->sizeSpinBox->init( m_device, partition ); + initPartResizerWidget( partition ); // File System FileSystem::Type fsType = partition->fileSystem().type(); diff --git a/src/modules/partition/CreatePartitionDialog.h b/src/modules/partition/CreatePartitionDialog.h index 289d25c5a..5680637aa 100644 --- a/src/modules/partition/CreatePartitionDialog.h +++ b/src/modules/partition/CreatePartitionDialog.h @@ -49,10 +49,11 @@ private: Device* m_device; PartitionNode* m_parent; PartitionRole m_role = PartitionRole( PartitionRole::None ); + QScopedPointer< Partition > m_partResizerWidgetPartition; void initGptPartitionTypeUi(); void initMbrPartitionTypeUi(); - void initSectorRange( Partition* ); + void initPartResizerWidget( Partition* ); }; #endif /* CREATEPARTITIONDIALOG_H */ diff --git a/src/modules/partition/CreatePartitionDialog.ui b/src/modules/partition/CreatePartitionDialog.ui index c14074c8c..2671ca9a6 100644 --- a/src/modules/partition/CreatePartitionDialog.ui +++ b/src/modules/partition/CreatePartitionDialog.ui @@ -14,9 +14,25 @@ Create a Partition + + + + + 0 + 0 + + + + + 0 + 59 + + + + - + Partition &Type: @@ -26,7 +42,7 @@ - + @@ -67,7 +83,7 @@ - + F&ile System: @@ -77,27 +93,10 @@ - + - - - - Si&ze: - - - sizeSpinBox - - - - - - - MB - - - - + Qt::Vertical @@ -113,7 +112,7 @@ - + &Mount Point: @@ -123,7 +122,7 @@ - + true @@ -163,7 +162,7 @@ - + Qt::Vertical @@ -176,7 +175,7 @@ - + Qt::Vertical @@ -189,6 +188,23 @@ + + + + Si&ze: + + + sizeSpinBox + + + + + + + MB + + + @@ -205,15 +221,15 @@ - PartitionSizeWidget - QSpinBox -
PartitionSizeWidget.h
+ PartResizerWidget + QWidget +
gui/partresizerwidget.h
+ 1
primaryRadioButton fsComboBox - sizeSpinBox diff --git a/src/modules/partition/EditExistingPartitionDialog.cpp b/src/modules/partition/EditExistingPartitionDialog.cpp index 18a25800c..3d20e9793 100644 --- a/src/modules/partition/EditExistingPartitionDialog.cpp +++ b/src/modules/partition/EditExistingPartitionDialog.cpp @@ -18,8 +18,10 @@ #include +#include #include #include +#include #include #include #include @@ -27,6 +29,7 @@ // CalaPM #include #include +#include // Qt #include @@ -36,10 +39,26 @@ EditExistingPartitionDialog::EditExistingPartitionDialog( Device* device, Partit , m_ui( new Ui_EditExistingPartitionDialog ) , m_device( device ) , m_partition( partition ) + , m_partitionSizeController( new PartitionSizeController( this ) ) { m_ui->setupUi( this ); - m_ui->sizeSpinBox->init( device, partition ); + + // Create a partition for partResizerWidget because it alters the first and + // last sectors when used + m_partResizerWidgetPartition.reset( PMUtils::clonePartition( m_device, m_partition ) ); + + QColor color = ColorUtils::colorForPartition( m_partition ); + m_partitionSizeController->init( m_device, m_partResizerWidgetPartition.data(), color ); + m_partitionSizeController->setSpinBox( m_ui->sizeSpinBox ); + m_ui->mountPointComboBox->setCurrentText( PartitionInfo::mountPoint( partition ) ); + + replacePartResizerWidget(); + + connect( m_ui->formatRadioButton, &QAbstractButton::toggled, [ this ]( bool ) + { + replacePartResizerWidget(); + } ); } EditExistingPartitionDialog::~EditExistingPartitionDialog() @@ -50,9 +69,12 @@ EditExistingPartitionDialog::applyChanges( PartitionCoreModule* core ) { PartitionInfo::setMountPoint( m_partition, m_ui->mountPointComboBox->currentText() ); - if ( m_ui->sizeSpinBox->isDirty() ) + qint64 newFirstSector = m_partResizerWidgetPartition->firstSector(); + qint64 newLastSector = m_partResizerWidgetPartition->lastSector(); + bool partitionChanged = newFirstSector != m_partition->firstSector() || newLastSector != m_partition->lastSector(); + + if ( partitionChanged ) { - PartitionSizeWidget::SectorRange range = m_ui->sizeSpinBox->sectorRange(); if ( m_ui->formatRadioButton->isChecked() ) { Partition* newPartition = PMUtils::createNewPartition( @@ -60,8 +82,8 @@ EditExistingPartitionDialog::applyChanges( PartitionCoreModule* core ) *m_device, m_partition->roles(), m_partition->fileSystem().type(), - range.first, - range.second ); + newFirstSector, + newLastSector ); PartitionInfo::setMountPoint( newPartition, PartitionInfo::mountPoint( m_partition ) ); PartitionInfo::setFormat( newPartition, true ); @@ -69,9 +91,7 @@ EditExistingPartitionDialog::applyChanges( PartitionCoreModule* core ) core->createPartition( m_device, newPartition ); } else - { - //core->resizePartition( m_device, m_partition ); - } + core->resizePartition( m_device, m_partition, newFirstSector, newLastSector ); } else { @@ -82,3 +102,39 @@ EditExistingPartitionDialog::applyChanges( PartitionCoreModule* core ) core->refreshPartition( m_device, m_partition ); } } + +void +EditExistingPartitionDialog::replacePartResizerWidget() +{ + /* + * There is no way to reliably update the partition used by + * PartResizerWidget, which is necessary when we switch between "format" and + * "keep". This is a hack which replaces the existing PartResizerWidget + * with a new one. + */ + + bool format = m_ui->formatRadioButton->isChecked(); + + qint64 used = format ? 0 : m_partition->fileSystem().sectorsUsed(); + m_partResizerWidgetPartition->fileSystem().setSectorsUsed( used ); + + qint64 minFirstSector = m_partition->firstSector() - m_device->partitionTable()->freeSectorsBefore( *m_partition ); + qint64 maxLastSector = m_partition->lastSector() + m_device->partitionTable()->freeSectorsAfter( *m_partition ); + + PartResizerWidget* widget = new PartResizerWidget( this ); + widget->init( *m_device, *m_partResizerWidgetPartition, minFirstSector, maxLastSector ); + + if ( !format ) + { + // If we are not formatting, make sure the space between the first and + // last sectors is big enough to fit the existing content. + widget->updateLastSector( m_partResizerWidgetPartition->lastSector() ); + widget->updateFirstSector( m_partResizerWidgetPartition->firstSector() ); + } + + layout()->replaceWidget( m_ui->partResizerWidget, widget ); + delete m_ui->partResizerWidget; + m_ui->partResizerWidget = widget; + + m_partitionSizeController->setPartResizerWidget( widget ); +} diff --git a/src/modules/partition/EditExistingPartitionDialog.h b/src/modules/partition/EditExistingPartitionDialog.h index 270bbb0e5..779e318d0 100644 --- a/src/modules/partition/EditExistingPartitionDialog.h +++ b/src/modules/partition/EditExistingPartitionDialog.h @@ -25,6 +25,7 @@ class PartitionCoreModule; class Device; class Partition; +class PartitionSizeController; class Ui_EditExistingPartitionDialog; class EditExistingPartitionDialog : public QDialog @@ -40,6 +41,10 @@ private: QScopedPointer< Ui_EditExistingPartitionDialog > m_ui; Device* m_device; Partition* m_partition; + QScopedPointer< Partition > m_partResizerWidgetPartition; + PartitionSizeController* m_partitionSizeController; + + void replacePartResizerWidget(); }; #endif /* EDITEXISTINGPARTITIONDIALOG_H */ diff --git a/src/modules/partition/EditExistingPartitionDialog.ui b/src/modules/partition/EditExistingPartitionDialog.ui index 3cbfeebd7..3618302b3 100644 --- a/src/modules/partition/EditExistingPartitionDialog.ui +++ b/src/modules/partition/EditExistingPartitionDialog.ui @@ -7,11 +7,11 @@ 0 0 350 - 203 + 236 - + 0 0 @@ -20,29 +20,31 @@ Edit Existing Partition + + QLayout::SetMinimumSize + + + + + + 0 + 0 + + + + + 0 + 59 + + + + QFormLayout::ExpandingFieldsGrow - - - - Si&ze: - - - sizeSpinBox - - - - - - - MB - - - - + Content: @@ -52,7 +54,7 @@ - + Keep @@ -62,15 +64,21 @@ - + Format - + + + + 0 + 0 + + Warning: Formatting the partition will erase all existing data. @@ -129,6 +137,19 @@ + + + + Size: + + + sizeSpinBox + + + + + + @@ -145,9 +166,10 @@ - PartitionSizeWidget - QSpinBox -
PartitionSizeWidget.h
+ PartResizerWidget + QWidget +
gui/partresizerwidget.h
+ 1
diff --git a/src/modules/partition/MoveFileSystemJob.cpp b/src/modules/partition/MoveFileSystemJob.cpp new file mode 100644 index 000000000..eb1391f57 --- /dev/null +++ b/src/modules/partition/MoveFileSystemJob.cpp @@ -0,0 +1,238 @@ +/* === This file is part of Calamares - === + * + * Copyright 2014, Aurélien Gâteau + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +// This class is heavily based on the MoveFileSystemJob class from KDE Partition +// Manager. +// The copyBlock functions come from Partition Manager Job class. +// Original copyright follow: + +/*************************************************************************** + * Copyright (C) 2008 by Volker Lanz * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include + +#include + +// CalaPM +#include +#include +#include +#include +#include +#include + +MoveFileSystemJob::MoveFileSystemJob( Device* device, Partition* partition, qint64 oldFirstSector, qint64 newFirstSector, qint64 length ) + : PartitionJob( partition ) + , m_device( device ) + , m_oldFirstSector( oldFirstSector ) + , m_newFirstSector( newFirstSector ) + , m_length( length ) +{} + +QString +MoveFileSystemJob::prettyName() const +{ + return tr( "Move file system of partition %1." ).arg( partition()->partitionPath() ); +} + +Calamares::JobResult +MoveFileSystemJob::exec() +{ + Report report( nullptr ); + QString partitionPath = partition()->partitionPath(); + CopySourceDevice moveSource( *m_device, m_oldFirstSector, m_oldFirstSector + m_length - 1 ); + CopyTargetDevice moveTarget( *m_device, m_newFirstSector, m_newFirstSector + m_length - 1 ); + + if ( !moveSource.open() ) + return Calamares::JobResult::error( + QString(), + tr( "Could not open file system on partition %1 for moving." ).arg( partitionPath ) + ); + + if ( !moveTarget.open() ) + return Calamares::JobResult::error( + QString(), + tr( "Could not create target for moving file system on partition %1." ).arg( partitionPath ) + ); + + bool ok = copyBlocks( report, moveTarget, moveSource ); + if ( !ok ) + { + if ( rollbackCopyBlocks( report, moveTarget, moveSource ) ) + return Calamares::JobResult::error( + QString(), + tr( "Moving of partition %1 failed, changes have been rolled back." ).arg( partitionPath ) + + '\n' + report.toText() + ); + else + return Calamares::JobResult::error( + QString(), + tr( "Moving of partition %1 failed. Roll back of the changes have failed." ).arg( partitionPath ) + + '\n' + report.toText() + ); + } + + FileSystem& fs = partition()->fileSystem(); + fs.setFirstSector( m_newFirstSector ); + fs.setLastSector( m_newFirstSector + m_length - 1 ); + + if ( !fs.updateBootSector( report, partitionPath ) ) + return Calamares::JobResult::error( + QString(), + tr( "Updating boot sector after the moving of partition %1 failed." ).arg( partitionPath ) + + '\n' + report.toText() + ); + + return Calamares::JobResult::ok(); +} + +bool +MoveFileSystemJob::copyBlocks( Report& report, CopyTargetDevice& target, CopySourceDevice& source ) +{ + /** @todo copyBlocks() assumes that source.sectorSize() == target.sectorSize(). */ + + if ( source.sectorSize() != target.sectorSize() ) + { + report.line() << tr( "The logical sector sizes in the source and target for copying are not the same. This is currently unsupported." ); + return false; + } + + bool rval = true; + const qint64 blockSize = 16065 * 8; // number of sectors per block to copy + const qint64 blocksToCopy = source.length() / blockSize; + + qint64 readOffset = source.firstSector(); + qint64 writeOffset = target.firstSector(); + qint32 copyDir = 1; + + if ( target.firstSector() > source.firstSector() ) + { + readOffset = source.firstSector() + source.length() - blockSize; + writeOffset = target.firstSector() + source.length() - blockSize; + copyDir = -1; + } + + qint64 blocksCopied = 0; + + void* buffer = malloc( blockSize * source.sectorSize() ); + int percent = 0; + + while ( blocksCopied < blocksToCopy ) + { + rval = source.readSectors( buffer, readOffset + blockSize * blocksCopied * copyDir, blockSize ); + if ( !rval ) + break; + + rval = target.writeSectors( buffer, writeOffset + blockSize * blocksCopied * copyDir, blockSize ); + if ( !rval ) + break; + + if ( ++blocksCopied * 100 / blocksToCopy != percent ) + { + percent = blocksCopied * 100 / blocksToCopy; + progress( qreal( percent ) / 100. ); + } + } + + const qint64 lastBlock = source.length() % blockSize; + + // copy the remainder + if ( rval && lastBlock > 0 ) + { + Q_ASSERT( lastBlock < blockSize ); + + if ( lastBlock >= blockSize ) + cLog() << "warning: lastBlock: " << lastBlock << ", blockSize: " << blockSize; + + const qint64 lastBlockReadOffset = copyDir > 0 ? readOffset + blockSize * blocksCopied : source.firstSector(); + const qint64 lastBlockWriteOffset = copyDir > 0 ? writeOffset + blockSize * blocksCopied : target.firstSector(); + + rval = source.readSectors( buffer, lastBlockReadOffset, lastBlock ); + + if ( rval ) + rval = target.writeSectors( buffer, lastBlockWriteOffset, lastBlock ); + + if ( rval ) + emit progress( 1.0 ); + } + + free( buffer ); + + return rval; +} + +bool +MoveFileSystemJob::rollbackCopyBlocks( Report& report, CopyTargetDevice& origTarget, CopySourceDevice& origSource ) +{ + if ( !origSource.overlaps( origTarget ) ) + { + report.line() << tr( "Source and target for copying do not overlap: Rollback is not required." ); + return true; + } + + // default: use values as if we were copying from front to back. + qint64 undoSourceFirstSector = origTarget.firstSector(); + qint64 undoSourceLastSector = origTarget.firstSector() + origTarget.sectorsWritten() - 1; + + qint64 undoTargetFirstSector = origSource.firstSector(); + qint64 undoTargetLastSector = origSource.firstSector() + origTarget.sectorsWritten() - 1; + + if ( origTarget.firstSector() > origSource.firstSector() ) + { + // we were copying from back to front + undoSourceFirstSector = origTarget.firstSector() + origSource.length() - origTarget.sectorsWritten(); + undoSourceLastSector = origTarget.firstSector() + origSource.length() - 1; + + undoTargetFirstSector = origSource.lastSector() - origTarget.sectorsWritten() + 1; + undoTargetLastSector = origSource.lastSector(); + } + + CopySourceDevice undoSource( origTarget.device(), undoSourceFirstSector, undoSourceLastSector ); + if ( !undoSource.open() ) + { + report.line() << tr( "Could not open device %1 to rollback copying." ) + .arg( origTarget.device().deviceNode() ); + return false; + } + + CopyTargetDevice undoTarget( origSource.device(), undoTargetFirstSector, undoTargetLastSector ); + if ( !undoTarget.open() ) + { + report.line() << tr( "Could not open device %1 to rollback copying." ) + .arg( origSource.device().deviceNode() ); + return false; + } + + return copyBlocks( report, undoTarget, undoSource ); +} diff --git a/src/modules/partition/MoveFileSystemJob.h b/src/modules/partition/MoveFileSystemJob.h new file mode 100644 index 000000000..1ae3534c6 --- /dev/null +++ b/src/modules/partition/MoveFileSystemJob.h @@ -0,0 +1,69 @@ +/* === This file is part of Calamares - === + * + * Copyright 2014, Aurélien Gâteau + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +// This class is heavily based on the MoveFileSystemJob class from KDE Partition +// Manager. Original copyright follow: + +/*************************************************************************** + * Copyright (C) 2008 by Volker Lanz * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ +#ifndef MOVEFILESYSTEMJOB_H +#define MOVEFILESYSTEMJOB_H + +#include + +class CopySourceDevice; +class CopyTargetDevice; +class Device; +class Partition; +class Report; + +class MoveFileSystemJob : public PartitionJob +{ +public: + MoveFileSystemJob( Device* device, Partition* partition, qint64 oldFirstSector, qint64 newFirstSector, qint64 length ); + + QString prettyName() const override; + + Calamares::JobResult exec() override; + +private: + Device* m_device; + qint64 m_oldFirstSector; + qint64 m_newFirstSector; + qint64 m_length; + bool copyBlocks( Report& report, CopyTargetDevice& target, CopySourceDevice& source ); + bool rollbackCopyBlocks( Report& report, CopyTargetDevice& origTarget, CopySourceDevice& origSource ); +}; + +#endif /* MOVEFILESYSTEMJOB_H */ diff --git a/src/modules/partition/PMUtils.cpp b/src/modules/partition/PMUtils.cpp index 7de100d27..10cfb8911 100644 --- a/src/modules/partition/PMUtils.cpp +++ b/src/modules/partition/PMUtils.cpp @@ -66,4 +66,21 @@ createNewPartition( PartitionNode* parent, const Device& device, const Partition ); } +Partition* +clonePartition( Device* device, Partition* partition ) +{ + FileSystem* fs = FileSystemFactory::create( + partition->fileSystem().type(), + partition->firstSector(), + partition->lastSector() + ); + return new Partition( + partition->parent(), + *device, + partition->roles(), + fs, fs->firstSector(), fs->lastSector(), + partition->partitionPath() + ); +} + } // namespace diff --git a/src/modules/partition/PMUtils.h b/src/modules/partition/PMUtils.h index 23eed5708..b04cef8b0 100644 --- a/src/modules/partition/PMUtils.h +++ b/src/modules/partition/PMUtils.h @@ -40,6 +40,7 @@ Partition* findPartitionByMountPoint( const QList< Device* >& devices, const QSt Partition* createNewPartition( PartitionNode* parent, const Device& device, const PartitionRole& role, FileSystem::Type fsType, qint64 firstSector, qint64 lastSector ); +Partition* clonePartition( Device* device, Partition* partition ); } #endif /* PMUTILS_H */ diff --git a/src/modules/partition/PartitionCoreModule.cpp b/src/modules/partition/PartitionCoreModule.cpp index 9f6bb829a..017a8e6b6 100644 --- a/src/modules/partition/PartitionCoreModule.cpp +++ b/src/modules/partition/PartitionCoreModule.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -38,6 +39,7 @@ #include #include #include +#include // Qt #include @@ -89,10 +91,9 @@ PartitionCoreModule::PartitionCoreModule( QObject* parent ) , m_deviceModel( new DeviceModel( this ) ) , m_bootLoaderModel( new BootLoaderModel( this ) ) { - // FIXME: Should be done at startup if ( !CalaPM::init() ) qFatal( "Failed to init CalaPM" ); - + FileSystemFactory::init(); init(); } @@ -257,6 +258,20 @@ PartitionCoreModule::formatPartition( Device* device, Partition* partition ) refresh(); } +void +PartitionCoreModule::resizePartition( Device* device, Partition* partition, qint64 first, qint64 last ) +{ + auto deviceInfo = infoForDevice( device ); + Q_ASSERT( deviceInfo ); + PartitionModel::ResetHelper helper( partitionModelForDevice( device ) ); + + ResizePartitionJob* job = new ResizePartitionJob( device, partition, first, last ); + job->updatePreview(); + deviceInfo->jobs << Calamares::job_ptr( job ); + + refresh(); +} + QList< Calamares::job_ptr > PartitionCoreModule::jobs() const { diff --git a/src/modules/partition/PartitionCoreModule.h b/src/modules/partition/PartitionCoreModule.h index 4a29f0994..7d3fb5f3b 100644 --- a/src/modules/partition/PartitionCoreModule.h +++ b/src/modules/partition/PartitionCoreModule.h @@ -69,6 +69,8 @@ public: void formatPartition( Device* device, Partition* partition ); + void resizePartition( Device* device, Partition* partition, qint64 first, qint64 last ); + void setBootLoaderInstallPath( const QString& path ); QList< Calamares::job_ptr > jobs() const; diff --git a/src/modules/partition/PartitionModel.cpp b/src/modules/partition/PartitionModel.cpp index 6364838ba..3ce3f4df2 100644 --- a/src/modules/partition/PartitionModel.cpp +++ b/src/modules/partition/PartitionModel.cpp @@ -17,6 +17,7 @@ */ #include +#include #include #include #include @@ -33,26 +34,6 @@ // Qt #include -static QColor COLORS[ 4 ] = -{ - "#448eca", - "#a5cc42", - "#d87e30", - "#ffbdbd", -}; -static QColor FREE_SPACE_COLOR = "#777777"; -static QColor EXTENDED_COLOR = "#aaaaaa"; - -static QColor colorForPartition( Partition* partition, int row ) -{ - if ( PMUtils::isPartitionFreeSpace( partition ) ) - return FREE_SPACE_COLOR; - if ( partition->roles().has( PartitionRole::Extended ) ) - return EXTENDED_COLOR; - // No partition-specific color needed, pick one from our list - return COLORS[ row % 4 ]; -} - //- ResetHelper -------------------------------------------- PartitionModel::ResetHelper::ResetHelper( PartitionModel* model ) : m_model( model ) @@ -170,7 +151,7 @@ PartitionModel::data( const QModelIndex& index, int role ) const } case Qt::DecorationRole: if ( index.column() == NameColumn ) - return colorForPartition( partition, index.row() ); + return ColorUtils::colorForPartition( partition ); else return QVariant(); case SizeRole: diff --git a/src/modules/partition/PartitionSizeController.cpp b/src/modules/partition/PartitionSizeController.cpp new file mode 100644 index 000000000..4fa4dc3e0 --- /dev/null +++ b/src/modules/partition/PartitionSizeController.cpp @@ -0,0 +1,123 @@ +/* === This file is part of Calamares - === + * + * Copyright 2014, Aurélien Gâteau + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include + +#include + +// Qt +#include + +// CalaPM +#include +#include +#include + +// stdc++ +#include + +PartitionSizeController::PartitionSizeController( QObject* parent ) + : QObject( parent ) +{} + +void +PartitionSizeController::setPartResizerWidget( PartResizerWidget* widget ) +{ + if ( m_partResizerWidget ) + disconnect( m_partResizerWidget, 0, this, 0 ); + m_partResizerWidget = widget; + // FIXME: Should be set by PartResizerWidget itself + m_partResizerWidget->setFixedHeight( PartResizerWidget::handleHeight() ); + + QPalette pal = widget->palette(); + pal.setColor( QPalette::Base, ColorUtils::freeSpaceColor() ); + pal.setColor( QPalette::Button, m_partitionColor ); + m_partResizerWidget->setPalette( pal ); + updateConnections(); +} + +void +PartitionSizeController::setSpinBox( QSpinBox* spinBox ) +{ + if ( m_spinBox ) + disconnect( m_spinBox, 0, this, 0 ); + m_spinBox = spinBox; + m_spinBox->setMaximum( std::numeric_limits< int >::max() ); + updateConnections(); +} + +void +PartitionSizeController::init( Device* device, Partition* partition, const QColor& color ) +{ + m_device = device; + m_partition = partition; + m_partitionColor = color; +} + +void +PartitionSizeController::updateConnections() +{ + if ( !m_spinBox || !m_partResizerWidget ) + return; + + connect( m_spinBox, SIGNAL( editingFinished() ), SLOT( updatePartResizerWidget() ) ); + connect( m_partResizerWidget, SIGNAL( firstSectorChanged( qint64 ) ), SLOT( updateSpinBox() ) ); + connect( m_partResizerWidget, SIGNAL( lastSectorChanged( qint64 ) ), SLOT( updateSpinBox() ) ); + updateSpinBox(); +} + +void +PartitionSizeController::updatePartResizerWidget() +{ + if ( m_updating ) + return; + m_updating = true; + qint64 sectorSize = qint64( m_spinBox->value() ) * 1024 * 1024 / m_device->logicalSectorSize(); + + qint64 firstSector = m_partition->firstSector(); + qint64 lastSector = firstSector + sectorSize - 1; + if ( lastSector > m_partResizerWidget->maximumLastSector() ) + { + qint64 delta = lastSector - m_partResizerWidget->maximumLastSector(); + firstSector -= delta; + lastSector -= delta; + } + m_partResizerWidget->updateLastSector( lastSector ); + m_partResizerWidget->updateFirstSector( firstSector ); + + // Update spinbox value in case it was an impossible value + doUpdateSpinBox(); + m_updating = false; +} + +void +PartitionSizeController::updateSpinBox() +{ + if ( m_updating ) + return; + m_updating = true; + doUpdateSpinBox(); + m_updating = false; +} + +void +PartitionSizeController::doUpdateSpinBox() +{ + qint64 mbSize = ( m_partition->lastSector() - m_partition->firstSector() + 1 ) * m_device->logicalSectorSize() / 1024 / 1024; + m_spinBox->setValue( mbSize ); +} diff --git a/src/modules/partition/PartitionSizeController.h b/src/modules/partition/PartitionSizeController.h new file mode 100644 index 000000000..0ed4c4a2d --- /dev/null +++ b/src/modules/partition/PartitionSizeController.h @@ -0,0 +1,60 @@ +/* === This file is part of Calamares - === + * + * Copyright 2014, Aurélien Gâteau + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef PARTITIONSIZECONTROLLER_H +#define PARTITIONSIZECONTROLLER_H + +#include +#include +#include + +class QSpinBox; + +class Device; +class Partition; +class PartResizerWidget; + +/** + * Synchronize a PartResizerWidget and a QSpinBox + */ +class PartitionSizeController : public QObject +{ + Q_OBJECT +public: + explicit PartitionSizeController( QObject* parent = nullptr ); + void setPartResizerWidget( PartResizerWidget* widget ); + void setSpinBox( QSpinBox* spinBox ); + void init( Device* device, Partition* partition, const QColor& color ); + +private: + QPointer< PartResizerWidget > m_partResizerWidget; + QPointer< QSpinBox > m_spinBox; + Device* m_device = nullptr; + Partition* m_partition = nullptr; + QColor m_partitionColor; + bool m_updating = false; + + void updateConnections(); + void doUpdateSpinBox(); + +private Q_SLOTS: + void updatePartResizerWidget(); + void updateSpinBox(); +}; + +#endif /* PARTITIONSIZECONTROLLER_H */ diff --git a/src/modules/partition/PartitionSizeWidget.cpp b/src/modules/partition/PartitionSizeWidget.cpp deleted file mode 100644 index 485770138..000000000 --- a/src/modules/partition/PartitionSizeWidget.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/* === This file is part of Calamares - === - * - * Copyright 2014, Aurélien Gâteau - * - * Calamares is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Calamares is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Calamares. If not, see . - */ - -#include - -#include - -// CalaPM -#include -#include -#include - -PartitionSizeWidget::PartitionSizeWidget( QWidget* parent ) - : QSpinBox( parent ) -{ -} - -void -PartitionSizeWidget::init( Device* device, Partition* partition ) -{ - m_device = device; - m_partition = partition; - Q_ASSERT( m_device->partitionTable() ); - - qint64 minSector = computeMinSector(); - qint64 maxSector = computeMaxSector(); - cLog() << minSector << maxSector; - setMaximum( mbSizeForSectorRange( minSector, maxSector ) ); - - m_initialValue = mbSizeForSectorRange( partition->firstSector(), partition->lastSector() ); - setValue( m_initialValue ); -} - -PartitionSizeWidget::SectorRange -PartitionSizeWidget::sectorRange() const -{ - qint64 minSector = computeMinSector(); - qint64 maxSector = computeMaxSector(); - - int mbSize = value(); - if ( mbSize == maximum() ) - { - // If we are at the maximum value, select the last sector to avoid - // potential rounding errors which could leave a few sectors at the end - // unused - return SectorRange( minSector, maxSector ); - } - - qint64 lastSector = minSector + qint64( mbSize ) * 1024 * 1024 / m_device->logicalSectorSize(); - Q_ASSERT( lastSector <= maxSector ); - if ( lastSector > maxSector ) - { - cLog() << "lastSector (" << lastSector << ") > maxSector (" << maxSector << "). This should not happen!"; - lastSector = maxSector; - } - return SectorRange( minSector, lastSector ); -} - -bool -PartitionSizeWidget::isDirty() const -{ - return m_initialValue != value(); -} - -qint64 -PartitionSizeWidget::mbSizeForSectorRange( qint64 first, qint64 last ) const -{ - return ( last - first + 1 ) * m_device->logicalSectorSize() / 1024 / 1024; -} - -qint64 -PartitionSizeWidget::computeMaxSector() const -{ - Q_ASSERT( m_device ); - Q_ASSERT( m_partition ); - return m_partition->lastSector() + m_device->partitionTable()->freeSectorsAfter( *m_partition ); -} - -qint64 -PartitionSizeWidget::computeMinSector() const -{ - Q_ASSERT( m_device ); - Q_ASSERT( m_partition ); - return m_partition->firstSector() - m_device->partitionTable()->freeSectorsBefore( *m_partition ); -} diff --git a/src/modules/partition/ResizePartitionJob.cpp b/src/modules/partition/ResizePartitionJob.cpp new file mode 100644 index 000000000..f9dcdb692 --- /dev/null +++ b/src/modules/partition/ResizePartitionJob.cpp @@ -0,0 +1,302 @@ +/* === This file is part of Calamares - === + * + * Copyright 2014, Aurélien Gâteau + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +// This class is heavily based on the ResizeOperation class from KDE Partition +// Manager. Original copyright follow: + +/*************************************************************************** + * Copyright (C) 2008,2012 by Volker Lanz * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include + +#include +#include +#include + +// CalaPM +#include +#include +#include +#include +#include +#include +#include +#include + +// Qt +#include + +//- Context -------------------------------------------------------------------- +struct Context +{ + Context( ResizePartitionJob* job_ ) + : job( job_ ) + {} + + ResizePartitionJob* job; + qint64 oldFirstSector; + qint64 oldLastSector; + + QScopedPointer< CoreBackendPartitionTable > backendPartitionTable; +}; + +//- ResizeFileSystemJob -------------------------------------------------------- +class ResizeFileSystemJob : public Calamares::Job +{ +public: + ResizeFileSystemJob( Context* context, qint64 length ) + : m_context( context ) + , m_length( length ) + {} + + QString prettyName() const override + { + QString path = m_context->job->partition()->partitionPath(); + return tr( "Resize file system on partition %1." ).arg( path ); + } + + Calamares::JobResult exec() override + { + Report report( nullptr ); + Device* device = m_context->job->device(); + Partition* partition = m_context->job->partition(); + FileSystem& fs = partition->fileSystem(); + FileSystem::CommandSupportType support = m_length < fs.length() ? fs.supportShrink() : fs.supportGrow(); + + switch ( support ) + { + case FileSystem::cmdSupportBackend: + if ( !backendResize( &report ) ) + return Calamares::JobResult::error( + QString(), + tr( "Parted failed to resize filesystem." ) + '\n' + report.toText() + ); + break; + case FileSystem::cmdSupportFileSystem: + { + qint64 byteLength = device->logicalSectorSize() * m_length; + bool ok = fs.resize( report, partition->partitionPath(), byteLength ); + if ( !ok ) + return Calamares::JobResult::error( + QString(), + tr( "Failed to resize filesystem." ) + '\n' + report.toText() + ); + break; + } + default: + break; + } + + fs.setLastSector( fs.firstSector() + m_length - 1 ); + return Calamares::JobResult::ok(); + } + +private: + Context* m_context; + qint64 m_length; + + bool backendResize( Report* report ) + { + Partition* partition = m_context->job->partition(); + bool ok = m_context->backendPartitionTable->resizeFileSystem( *report, *partition, m_length ); + if ( !ok ) + return false; + m_context->backendPartitionTable->commit(); + return true; + } +}; + +//- SetPartGeometryJob --------------------------------------------------------- +class SetPartGeometryJob : public Calamares::Job +{ +public: + SetPartGeometryJob( Context* context, qint64 firstSector, qint64 length ) + : m_context( context ) + , m_firstSector( firstSector ) + , m_length( length ) + {} + + QString prettyName() const override + { + QString path = m_context->job->partition()->partitionPath(); + return tr( "Update geometry of partition %1." ).arg( path ); + } + + Calamares::JobResult exec() override + { + Report report( nullptr ); + Partition* partition = m_context->job->partition(); + qint64 lastSector = m_firstSector + m_length - 1; + bool ok = m_context->backendPartitionTable->updateGeometry( report, *partition, m_firstSector, lastSector ); + if ( !ok ) + { + return Calamares::JobResult::error( + QString(), + tr( "Failed to change the geometry of the partition." ) + '\n' + report.toText() ); + } + partition->setFirstSector( m_firstSector ); + partition->setLastSector( lastSector ); + m_context->backendPartitionTable->commit(); + return Calamares::JobResult::ok(); + } + +private: + Context* m_context; + qint64 m_firstSector; + qint64 m_length; +}; + +//- ResizePartitionJob --------------------------------------------------------- +ResizePartitionJob::ResizePartitionJob( Device* device, Partition* partition, qint64 firstSector, qint64 lastSector ) + : PartitionJob( partition ) + , m_device( device ) + , m_oldFirstSector( partition->firstSector() ) // Keep a copy of old sectors because they will be overwritten in updatePreview() + , m_oldLastSector( partition->lastSector() ) + , m_newFirstSector( firstSector ) + , m_newLastSector( lastSector ) +{ +} + +QString +ResizePartitionJob::prettyName() const +{ + // FIXME: Copy PM ResizeOperation code which generates a description of the + // operation + return tr( "Resize partition %1." ).arg( partition()->partitionPath() ); +} + +Calamares::JobResult +ResizePartitionJob::exec() +{ + qint64 oldLength = m_oldLastSector - m_oldFirstSector + 1; + qint64 newLength = m_newLastSector - m_newFirstSector + 1; + + // Assuming updatePreview() has been called, `partition` uses its new + // position and size. Reset it to the old values: part of the libparted + // backend relies on this (for example: + // LibPartedPartitionTable::updateGeometry()) + // The jobs are responsible for updating the partition back when they are + // done. + m_partition->setFirstSector( m_oldFirstSector ); + m_partition->setLastSector( m_oldLastSector ); + + // Setup context + QString partitionPath = m_partition->partitionPath(); + Context context( this ); + context.oldFirstSector = m_oldFirstSector; + context.oldLastSector = m_oldLastSector; + + CoreBackend* backend = CoreBackendManager::self()->backend(); + QScopedPointer backendDevice( backend->openDevice( m_device->deviceNode() ) ); + if ( !backendDevice.data() ) + { + QString errorMessage = tr( "The installer failed to resize partition %1 on disk '%2'." ) + .arg( m_partition->partitionPath() ) + .arg( m_device->name() ); + return Calamares::JobResult::error( + errorMessage, + tr( "Could not open device '%1'." ).arg( m_device->deviceNode() ) + ); + } + context.backendPartitionTable.reset( backendDevice->openPartitionTable() ); + + // Create jobs + QList< Calamares::job_ptr > jobs; + jobs << Calamares::job_ptr( new CheckFileSystemJob( partition() ) ); + if ( m_partition->roles().has( PartitionRole::Extended ) ) + jobs << Calamares::job_ptr( new SetPartGeometryJob( &context, m_newFirstSector, newLength ) ); + else + { + bool shrink = newLength < oldLength; + bool grow = newLength > oldLength; + bool moveRight = m_newFirstSector > m_oldFirstSector; + bool moveLeft = m_newFirstSector < m_oldFirstSector; + if ( shrink ) + { + jobs << Calamares::job_ptr( new ResizeFileSystemJob( &context, newLength ) ); + jobs << Calamares::job_ptr( new SetPartGeometryJob( &context, m_oldFirstSector, newLength ) ); + } + if ( moveRight || moveLeft ) + { + // At this point, we need to set the partition's length to either the resized length, if it has already been + // shrunk, or to the original length (it may or may not then later be grown, we don't care here) + const qint64 length = shrink ? newLength : oldLength; + jobs << Calamares::job_ptr( new SetPartGeometryJob( &context, m_newFirstSector, length ) ); + jobs << Calamares::job_ptr( new MoveFileSystemJob( m_device, m_partition, m_oldFirstSector, m_newFirstSector, length ) ); + } + if ( grow ) + { + jobs << Calamares::job_ptr( new SetPartGeometryJob( &context, m_newFirstSector, newLength ) ); + jobs << Calamares::job_ptr( new ResizeFileSystemJob( &context, newLength ) ); + } + } + jobs << Calamares::job_ptr( new CheckFileSystemJob( partition() ) ); + return execJobList( jobs ); +} + +void +ResizePartitionJob::updatePreview() +{ + m_device->partitionTable()->removeUnallocated(); + m_partition->parent()->remove( m_partition ); + m_partition->setFirstSector( m_newFirstSector ); + m_partition->setLastSector( m_newLastSector ); + m_partition->parent()->insert( m_partition ); + m_device->partitionTable()->updateUnallocated( *m_device ); +} + +Calamares::JobResult +ResizePartitionJob::execJobList( const QList< Calamares::job_ptr >& jobs ) +{ + QString errorMessage = tr( "The installer failed to resize partition %1 on disk '%2'." ) + .arg( m_partition->partitionPath() ) + .arg( m_device->name() ); + + int nbJobs = jobs.size(); + int count = 0; + for ( Calamares::job_ptr job : jobs ) + { + cLog() << "- " + job->prettyName(); + Calamares::JobResult result = job->exec(); + if ( !result ) + { + if ( result.message().isEmpty() ) + result.setMessage( errorMessage ); + return result; + } + ++count; + progress( qreal( count ) / nbJobs ); + } + return Calamares::JobResult::ok(); +} diff --git a/src/modules/partition/ResizePartitionJob.h b/src/modules/partition/ResizePartitionJob.h new file mode 100644 index 000000000..7deb3baa5 --- /dev/null +++ b/src/modules/partition/ResizePartitionJob.h @@ -0,0 +1,57 @@ +/* === This file is part of Calamares - === + * + * Copyright 2014, Aurélien Gâteau + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef RESIZEPARTITIONJOB_H +#define RESIZEPARTITIONJOB_H + +#include + +class Device; +class Partition; +class FileSystem; + +struct Context; + +class ResizePartitionJob : public PartitionJob +{ + Q_OBJECT +public: + ResizePartitionJob( Device* device, Partition* partition, qint64 firstSector, qint64 lastSector ); + QString prettyName() const override; + Calamares::JobResult exec() override; + + void updatePreview(); + + Device* device() const + { + return m_device; + } + +private: + Device* m_device; + qint64 m_oldFirstSector; + qint64 m_oldLastSector; + qint64 m_newFirstSector; + qint64 m_newLastSector; + + Calamares::JobResult execJobList( const QList< Calamares::job_ptr >& jobs ); + + friend struct Context; +}; + +#endif /* RESIZEPARTITIONJOB_H */ diff --git a/src/modules/partition/partitionmanager b/src/modules/partition/partitionmanager index f958c7be7..8726fc2b3 160000 --- a/src/modules/partition/partitionmanager +++ b/src/modules/partition/partitionmanager @@ -1 +1 @@ -Subproject commit f958c7be734135f22454e464518c1f7110298a95 +Subproject commit 8726fc2b3f2a703b9a72cf76038be1bab22d8a3f diff --git a/src/modules/partition/tests/CMakeLists.txt b/src/modules/partition/tests/CMakeLists.txt index 9b0c54767..8eb8c3fc0 100644 --- a/src/modules/partition/tests/CMakeLists.txt +++ b/src/modules/partition/tests/CMakeLists.txt @@ -2,13 +2,16 @@ find_package( Qt5 COMPONENTS Test REQUIRED ) include( ECMAddTests ) set( jobtests_SRCS + ${PartitionModule_SOURCE_DIR}/CheckFileSystemJob.cpp ${PartitionModule_SOURCE_DIR}/CreatePartitionJob.cpp ${PartitionModule_SOURCE_DIR}/CreatePartitionTableJob.cpp ${PartitionModule_SOURCE_DIR}/DeletePartitionJob.cpp + ${PartitionModule_SOURCE_DIR}/MoveFileSystemJob.cpp ${PartitionModule_SOURCE_DIR}/PartitionInfo.cpp ${PartitionModule_SOURCE_DIR}/PartitionIterator.cpp ${PartitionModule_SOURCE_DIR}/PartitionJob.cpp ${PartitionModule_SOURCE_DIR}/PMUtils.cpp + ${PartitionModule_SOURCE_DIR}/ResizePartitionJob.cpp JobTests.cpp ) diff --git a/src/modules/partition/tests/JobTests.cpp b/src/modules/partition/tests/JobTests.cpp index 169683c5d..40edce79b 100644 --- a/src/modules/partition/tests/JobTests.cpp +++ b/src/modules/partition/tests/JobTests.cpp @@ -2,6 +2,7 @@ #include #include +#include #include // CalaPM @@ -12,6 +13,7 @@ // Qt #include +#include #include QTEST_GUILESS_MAIN( JobTests ) @@ -20,8 +22,77 @@ static const qint64 MB = 1024 * 1024; using namespace Calamares; -static -Partition* firstFreePartition( PartitionNode* parent ) +class PartitionMounter +{ +public: + PartitionMounter( const QString& devicePath ) + : m_mountPointDir( "calamares-partitiontests-mountpoint" ) + { + QStringList args = QStringList() << devicePath << m_mountPointDir.path(); + int ret = QProcess::execute( "mount", args ); + m_mounted = ret == 0; + QCOMPARE( ret, 0 ); + } + + ~PartitionMounter() + { + if ( !m_mounted ) + return; + int ret = QProcess::execute( "umount", QStringList() << m_mountPointDir.path() ); + QCOMPARE( ret, 0 ); + } + + QString mountPoint() const + { + return m_mounted ? m_mountPointDir.path() : QString(); + } + +private: + QString m_devicePath; + QTemporaryDir m_mountPointDir; + bool m_mounted; +}; + +static QByteArray +generateTestData( qint64 size ) +{ + QByteArray ba; + ba.resize( size ); + // Fill the array explicitly to keep Valgrind happy + for ( auto it = ba.data() ; it < ba.data() + size ; ++it ) + { + *it = rand() % 256; + } + return ba; +} + +static void +writeFile( const QString& path, const QByteArray data ) +{ + QFile file( path ); + QVERIFY( file.open( QIODevice::WriteOnly ) ); + + const char* ptr = data.constData(); + const char* end = data.constData() + data.size(); + const qint64 chunkSize = 16384; + + while ( ptr < end ) + { + qint64 count = file.write( ptr, chunkSize ); + if ( count < 0 ) + { + QString msg = QString( "Writing file failed. Only %1 bytes written out of %2. Error: '%3'." ) + .arg( ptr - data.constData() ) + .arg( data.size() ) + .arg( file.errorString() ); + QFAIL( qPrintable( msg ) ); + } + ptr += count; + } +} + +static Partition* +firstFreePartition( PartitionNode* parent ) { for( auto child : parent->children() ) if ( PMUtils::isPartitionFreeSpace( child ) ) @@ -29,6 +100,7 @@ Partition* firstFreePartition( PartitionNode* parent ) return nullptr; } +//- QueueRunner --------------------------------------------------------------- QueueRunner::QueueRunner( JobQueue* queue ) : m_queue( queue ) { @@ -62,7 +134,7 @@ QueueRunner::onFailed( const QString& message, const QString& details ) QFAIL( qPrintable( msg ) ); } -//---------------------------------------------------------- +//- JobTests ------------------------------------------------------------------ JobTests::JobTests() : m_runner( &m_queue ) {} @@ -77,12 +149,18 @@ JobTests::initTestCase() } QVERIFY( CalaPM::init() ); + FileSystemFactory::init(); + refreshDevice(); +} + +void +JobTests::refreshDevice() +{ + QString devicePath = qgetenv( "CALAMARES_TEST_DISK" ); CoreBackend* backend = CoreBackendManager::self()->backend(); m_device.reset( backend->scanDevice( devicePath ) ); QVERIFY( !m_device.isNull() ); - - FileSystemFactory::init(); } void @@ -216,3 +294,101 @@ JobTests::testCreatePartitionExtended() QCOMPARE( extendedPartition->partitionPath(), devicePath + "2" ); QCOMPARE( partition2->partitionPath(), devicePath + "5" ); } + +void +JobTests::testResizePartition_data() +{ + QTest::addColumn< int >( "oldStartMB" ); + QTest::addColumn< int >( "oldSizeMB" ); + QTest::addColumn< int >( "newStartMB" ); + QTest::addColumn< int >( "newSizeMB" ); + + QTest::newRow("grow") << 10 << 50 << 10 << 70; + QTest::newRow("shrink") << 10 << 70 << 10 << 50; + QTest::newRow("moveLeft") << 10 << 50 << 8 << 50; + QTest::newRow("moveRight") << 10 << 50 << 12 << 50; +} + +void +JobTests::testResizePartition() +{ + QFETCH( int, oldStartMB ); + QFETCH( int, oldSizeMB ); + QFETCH( int, newStartMB ); + QFETCH( int, newSizeMB ); + + const qint64 sectorForMB = MB / m_device->logicalSectorSize(); + + qint64 oldFirst = sectorForMB * oldStartMB; + qint64 oldLast = oldFirst + sectorForMB * oldSizeMB - 1; + qint64 newFirst = sectorForMB * newStartMB; + qint64 newLast = newFirst + sectorForMB * newSizeMB - 1; + + // Make the test data file smaller than the full size of the partition to + // accomodate for the file system overhead + const QByteArray testData = generateTestData( ( qMin( oldSizeMB, newSizeMB ) ) * MB * 3 / 4 ); + const QString testName = "test.data"; + + // Setup: create the test partition + { + queuePartitionTableCreation( PartitionTable::msdos ); + + Partition* freePartition = firstFreePartition( m_device->partitionTable() ); + QVERIFY( freePartition ); + Partition* partition = PMUtils::createNewPartition( freePartition->parent(), *m_device, PartitionRole( PartitionRole::Primary ), FileSystem::Ext4, oldFirst, oldLast ); + CreatePartitionJob* job = new CreatePartitionJob( m_device.data(), partition ); + job->updatePreview(); + m_queue.enqueue( job_ptr( job ) ); + + QVERIFY( m_runner.run() ); + } + + { + // Write a test file in the partition + refreshDevice(); + QVERIFY( m_device->partitionTable() ); + Partition* partition = m_device->partitionTable()->findPartitionBySector( oldFirst, PartitionRole( PartitionRole::Primary ) ); + QVERIFY( partition ); + QCOMPARE( partition->firstSector(), oldFirst ); + QCOMPARE( partition->lastSector(), oldLast ); + { + PartitionMounter mounter( partition->partitionPath() ); + QString mountPoint = mounter.mountPoint(); + QVERIFY( !mountPoint.isEmpty() ); + writeFile( mountPoint + '/' + testName, testData ); + } + + // Resize + ResizePartitionJob* job = new ResizePartitionJob( m_device.data(), partition, newFirst, newLast ); + job->updatePreview(); + m_queue.enqueue( job_ptr( job ) ); + QVERIFY( m_runner.run() ); + + QCOMPARE( partition->firstSector(), newFirst ); + QCOMPARE( partition->lastSector(), newLast ); + } + + // Test + { + refreshDevice(); + QVERIFY( m_device->partitionTable() ); + Partition* partition = m_device->partitionTable()->findPartitionBySector( newFirst, PartitionRole( PartitionRole::Primary ) ); + QVERIFY( partition ); + QCOMPARE( partition->firstSector(), newFirst ); + QCOMPARE( partition->lastSector(), newLast ); + QCOMPARE( partition->fileSystem().firstSector(), newFirst ); + QCOMPARE( partition->fileSystem().lastSector(), newLast ); + + + PartitionMounter mounter( partition->partitionPath() ); + QString mountPoint = mounter.mountPoint(); + QVERIFY( !mountPoint.isEmpty() ); + { + QFile file( mountPoint + '/' + testName ); + QVERIFY( file.open( QIODevice::ReadOnly ) ); + QByteArray outData = file.readAll(); + QCOMPARE( outData.size(), testData.size() ); + QCOMPARE( outData, testData ); + } + } +} diff --git a/src/modules/partition/tests/JobTests.h b/src/modules/partition/tests/JobTests.h index e4a35cb3f..281450cc5 100644 --- a/src/modules/partition/tests/JobTests.h +++ b/src/modules/partition/tests/JobTests.h @@ -43,6 +43,8 @@ private Q_SLOTS: void testPartitionTable(); void testCreatePartition(); void testCreatePartitionExtended(); + void testResizePartition_data(); + void testResizePartition(); private: QScopedPointer< Device > m_device; @@ -51,6 +53,7 @@ private: void queuePartitionTableCreation( PartitionTable::TableType type ); CreatePartitionJob* newCreatePartitionJob( Partition* freeSpacePartition, PartitionRole, FileSystem::Type type, qint64 size ); + void refreshDevice(); }; #endif /* JOBTESTS_H */