2015-06-04 01:29:22 +01:00
/*************************************************************************
* Copyright ( C ) 2008 , 2012 by Volker Lanz < vl @ fidra . de > *
2016-03-02 19:00:31 +00:00
* Copyright ( C ) 2016 by Andrius Š tikonas < andrius @ stikonas . eu > *
2015-06-04 01:29:22 +01:00
* *
* 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 3 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 , see < http : //www.gnu.org/licenses/>.*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# include "ops/resizeoperation.h"
# include "core/partition.h"
# include "core/device.h"
# include "core/partitiontable.h"
# include "core/copysourcedevice.h"
# include "core/copytargetdevice.h"
# include "jobs/checkfilesystemjob.h"
# include "jobs/setpartgeometryjob.h"
# include "jobs/resizefilesystemjob.h"
# include "jobs/movefilesystemjob.h"
# include "fs/filesystem.h"
# include "util/capacity.h"
# include "util/report.h"
# include <QDebug>
# include <QString>
# include <KLocalizedString>
/** Creates a new ResizeOperation.
2015-07-13 15:16:36 +01:00
@ param d the Device to resize a Partition on
@ param p the Partition to resize
@ param newfirst the new first sector of the Partition
@ param newlast the new last sector of the Partition
2015-06-04 01:29:22 +01:00
*/
ResizeOperation : : ResizeOperation ( Device & d , Partition & p , qint64 newfirst , qint64 newlast ) :
2015-07-13 15:16:36 +01:00
Operation ( ) ,
m_TargetDevice ( d ) ,
m_Partition ( p ) ,
m_OrigFirstSector ( partition ( ) . firstSector ( ) ) ,
m_OrigLastSector ( partition ( ) . lastSector ( ) ) ,
m_NewFirstSector ( newfirst ) ,
m_NewLastSector ( newlast ) ,
m_CheckOriginalJob ( new CheckFileSystemJob ( partition ( ) ) ) ,
2015-07-22 14:48:03 +01:00
m_MoveExtendedJob ( nullptr ) ,
m_ShrinkResizeJob ( nullptr ) ,
m_ShrinkSetGeomJob ( nullptr ) ,
m_MoveSetGeomJob ( nullptr ) ,
m_MoveFileSystemJob ( nullptr ) ,
m_GrowResizeJob ( nullptr ) ,
m_GrowSetGeomJob ( nullptr ) ,
m_CheckResizedJob ( nullptr )
2015-06-04 01:29:22 +01:00
{
2015-07-13 15:16:36 +01:00
addJob ( checkOriginalJob ( ) ) ;
if ( partition ( ) . roles ( ) . has ( PartitionRole : : Extended ) ) {
m_MoveExtendedJob = new SetPartGeometryJob ( targetDevice ( ) , partition ( ) , newFirstSector ( ) , newLength ( ) ) ;
addJob ( moveExtendedJob ( ) ) ;
} else {
if ( resizeAction ( ) & Shrink ) {
m_ShrinkResizeJob = new ResizeFileSystemJob ( targetDevice ( ) , partition ( ) , newLength ( ) ) ;
m_ShrinkSetGeomJob = new SetPartGeometryJob ( targetDevice ( ) , partition ( ) , partition ( ) . firstSector ( ) , newLength ( ) ) ;
addJob ( shrinkResizeJob ( ) ) ;
addJob ( shrinkSetGeomJob ( ) ) ;
}
if ( ( resizeAction ( ) & MoveLeft ) | | ( resizeAction ( ) & MoveRight ) ) {
// 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 currentLength = ( resizeAction ( ) & Shrink ) ? newLength ( ) : partition ( ) . length ( ) ;
m_MoveSetGeomJob = new SetPartGeometryJob ( targetDevice ( ) , partition ( ) , newFirstSector ( ) , currentLength ) ;
m_MoveFileSystemJob = new MoveFileSystemJob ( targetDevice ( ) , partition ( ) , newFirstSector ( ) ) ;
addJob ( moveSetGeomJob ( ) ) ;
addJob ( moveFileSystemJob ( ) ) ;
}
if ( resizeAction ( ) & Grow ) {
m_GrowSetGeomJob = new SetPartGeometryJob ( targetDevice ( ) , partition ( ) , newFirstSector ( ) , newLength ( ) ) ;
m_GrowResizeJob = new ResizeFileSystemJob ( targetDevice ( ) , partition ( ) , newLength ( ) ) ;
addJob ( growSetGeomJob ( ) ) ;
addJob ( growResizeJob ( ) ) ;
}
m_CheckResizedJob = new CheckFileSystemJob ( partition ( ) ) ;
addJob ( checkResizedJob ( ) ) ;
}
2015-06-04 01:29:22 +01:00
}
bool ResizeOperation : : targets ( const Device & d ) const
{
2015-07-13 15:16:36 +01:00
return d = = targetDevice ( ) ;
2015-06-04 01:29:22 +01:00
}
bool ResizeOperation : : targets ( const Partition & p ) const
{
2015-07-13 15:16:36 +01:00
return p = = partition ( ) ;
2015-06-04 01:29:22 +01:00
}
void ResizeOperation : : preview ( )
{
2015-07-13 15:16:36 +01:00
// If the operation has already been executed, the partition will of course have newFirstSector and
// newLastSector as first and last sector. But to remove it from its original position, we need to
// temporarily set these values back to where they were before the operation was executed.
if ( partition ( ) . firstSector ( ) = = newFirstSector ( ) & & partition ( ) . lastSector ( ) = = newLastSector ( ) ) {
partition ( ) . setFirstSector ( origFirstSector ( ) ) ;
partition ( ) . setLastSector ( origLastSector ( ) ) ;
}
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
removePreviewPartition ( targetDevice ( ) , partition ( ) ) ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
partition ( ) . setFirstSector ( newFirstSector ( ) ) ;
partition ( ) . setLastSector ( newLastSector ( ) ) ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
insertPreviewPartition ( targetDevice ( ) , partition ( ) ) ;
2015-06-04 01:29:22 +01:00
}
void ResizeOperation : : undo ( )
{
2015-07-13 15:16:36 +01:00
removePreviewPartition ( targetDevice ( ) , partition ( ) ) ;
partition ( ) . setFirstSector ( origFirstSector ( ) ) ;
partition ( ) . setLastSector ( origLastSector ( ) ) ;
insertPreviewPartition ( targetDevice ( ) , partition ( ) ) ;
2015-06-04 01:29:22 +01:00
}
bool ResizeOperation : : execute ( Report & parent )
{
2015-07-13 15:16:36 +01:00
bool rval = false ;
Report * report = parent . newChild ( description ( ) ) ;
if ( ( rval = checkOriginalJob ( ) - > run ( * report ) ) ) {
// Extended partitions are a special case: They don't have any file systems and so there's no
// need to move, shrink or grow their contents before setting the new geometry. In fact, trying
// to first shrink THEN move would not work for an extended partition that has children, because
// they might temporarily be outside the extended partition and the backend would not let us do that.
if ( moveExtendedJob ( ) ) {
if ( ! ( rval = moveExtendedJob ( ) - > run ( * report ) ) )
report - > line ( ) < < xi18nc ( " @info/plain " , " Moving extended partition <filename>%1</filename> failed. " , partition ( ) . deviceNode ( ) ) ;
} else {
// We run all three methods. Any of them returns true if it has nothing to do.
rval = shrink ( * report ) & & move ( * report ) & & grow ( * report ) ;
if ( rval ) {
if ( ! ( rval = checkResizedJob ( ) - > run ( * report ) ) )
report - > line ( ) < < xi18nc ( " @info/plain " , " Checking partition <filename>%1</filename> after resize/move failed. " , partition ( ) . deviceNode ( ) ) ;
} else
report - > line ( ) < < xi18nc ( " @info/plain " , " Resizing/moving partition <filename>%1</filename> failed. " , partition ( ) . deviceNode ( ) ) ;
}
} else
report - > line ( ) < < xi18nc ( " @info/plain " , " Checking partition <filename>%1</filename> before resize/move failed. " , partition ( ) . deviceNode ( ) ) ;
setStatus ( rval ? StatusFinishedSuccess : StatusError ) ;
report - > setStatus ( i18nc ( " @info/plain status (success, error, warning...) of operation " , " %1: %2 " , description ( ) , statusText ( ) ) ) ;
return rval ;
2015-06-04 01:29:22 +01:00
}
QString ResizeOperation : : description ( ) const
{
2015-07-13 15:16:36 +01:00
// There are eight possible things a resize operation might do:
// 1) Move a partition to the left (closer to the start of the disk)
// 2) Move a partition to the right (closer to the end of the disk)
// 3) Grow a partition
// 4) Shrink a partition
// 5) Move a partition to the left and grow it
// 6) Move a partition to the right and grow it
// 7) Move a partition to the left and shrink it
// 8) Move a partition to the right and shrink it
// Each of these needs a different description. And for reasons of i18n, we cannot
// just concatenate strings together...
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
const QString moveDelta = Capacity : : formatByteSize ( qAbs ( newFirstSector ( ) - origFirstSector ( ) ) * targetDevice ( ) . logicalSectorSize ( ) ) ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
const QString origCapacity = Capacity : : formatByteSize ( origLength ( ) * targetDevice ( ) . logicalSectorSize ( ) ) ;
const QString newCapacity = Capacity : : formatByteSize ( newLength ( ) * targetDevice ( ) . logicalSectorSize ( ) ) ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
switch ( resizeAction ( ) ) {
case MoveLeft :
return xi18nc ( " @info/plain describe resize/move action " , " Move partition <filename>%1</filename> to the left by %2 " , partition ( ) . deviceNode ( ) , moveDelta ) ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
case MoveRight :
return xi18nc ( " @info/plain describe resize/move action " , " Move partition <filename>%1</filename> to the right by %2 " , partition ( ) . deviceNode ( ) , moveDelta ) ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
case Grow :
return xi18nc ( " @info/plain describe resize/move action " , " Grow partition <filename>%1</filename> from %2 to %3 " , partition ( ) . deviceNode ( ) , origCapacity , newCapacity ) ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
case Shrink :
return xi18nc ( " @info/plain describe resize/move action " , " Shrink partition <filename>%1</filename> from %2 to %3 " , partition ( ) . deviceNode ( ) , origCapacity , newCapacity ) ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
case MoveLeftGrow :
return xi18nc ( " @info/plain describe resize/move action " , " Move partition <filename>%1</filename> to the left by %2 and grow it from %3 to %4 " , partition ( ) . deviceNode ( ) , moveDelta , origCapacity , newCapacity ) ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
case MoveRightGrow :
return xi18nc ( " @info/plain describe resize/move action " , " Move partition <filename>%1</filename> to the right by %2 and grow it from %3 to %4 " , partition ( ) . deviceNode ( ) , moveDelta , origCapacity , newCapacity ) ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
case MoveLeftShrink :
return xi18nc ( " @info/plain describe resize/move action " , " Move partition <filename>%1</filename> to the left by %2 and shrink it from %3 to %4 " , partition ( ) . deviceNode ( ) , moveDelta , origCapacity , newCapacity ) ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
case MoveRightShrink :
return xi18nc ( " @info/plain describe resize/move action " , " Move partition <filename>%1</filename> to the right by %2 and shrink it from %3 to %4 " , partition ( ) . deviceNode ( ) , moveDelta , origCapacity , newCapacity ) ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
default :
qWarning ( ) < < " Could not determine what to do with partition " < < partition ( ) . deviceNode ( ) < < " . " ;
break ;
}
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
return i18nc ( " @info/plain describe resize/move action " , " Unknown resize/move action. " ) ;
2015-06-04 01:29:22 +01:00
}
ResizeOperation : : ResizeAction ResizeOperation : : resizeAction ( ) const
{
2015-07-13 15:16:36 +01:00
ResizeAction action = None ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
// Grow?
if ( newLength ( ) > origLength ( ) )
action = Grow ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
// Shrink?
if ( newLength ( ) < origLength ( ) )
action = Shrink ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
// Move to the right?
if ( newFirstSector ( ) > origFirstSector ( ) )
action = static_cast < ResizeAction > ( action | MoveRight ) ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
// Move to the left?
if ( newFirstSector ( ) < origFirstSector ( ) )
action = static_cast < ResizeAction > ( action | MoveLeft ) ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
return action ;
2015-06-04 01:29:22 +01:00
}
bool ResizeOperation : : shrink ( Report & report )
{
2015-07-13 15:16:36 +01:00
if ( shrinkResizeJob ( ) & & ! shrinkResizeJob ( ) - > run ( report ) ) {
report . line ( ) < < xi18nc ( " @info/plain " , " Resize/move failed: Could not resize file system to shrink partition <filename>%1</filename>. " , partition ( ) . deviceNode ( ) ) ;
return false ;
}
if ( shrinkSetGeomJob ( ) & & ! shrinkSetGeomJob ( ) - > run ( report ) ) {
report . line ( ) < < xi18nc ( " @info/plain " , " Resize/move failed: Could not shrink partition <filename>%1</filename>. " , partition ( ) . deviceNode ( ) ) ;
return false ;
/** @todo if this fails, no one undoes the shrinking of the file system above, because we
rely upon there being a maximize job at the end , but that ' s no longer the case . */
}
return true ;
2015-06-04 01:29:22 +01:00
}
bool ResizeOperation : : move ( Report & report )
{
2015-07-13 15:16:36 +01:00
// We must make sure not to overwrite the partition's metadata if it's a logical partition
// and we're moving to the left. The easiest way to achieve this is to move the
// partition itself first (it's the backend's responsibility to then move the metadata) and
// only afterwards copy the filesystem. Disadvantage: We need to move the partition
// back to its original position if copyBlocks fails.
const qint64 oldStart = partition ( ) . firstSector ( ) ;
if ( moveSetGeomJob ( ) & & ! moveSetGeomJob ( ) - > run ( report ) ) {
report . line ( ) < < xi18nc ( " @info/plain " , " Moving partition <filename>%1</filename> failed. " , partition ( ) . deviceNode ( ) ) ;
return false ;
}
if ( moveFileSystemJob ( ) & & ! moveFileSystemJob ( ) - > run ( report ) ) {
report . line ( ) < < xi18nc ( " @info/plain " , " Moving the filesystem for partition <filename>%1</filename> failed. Rolling back. " , partition ( ) . deviceNode ( ) ) ;
// see above: We now have to move back the partition itself.
if ( ! SetPartGeometryJob ( targetDevice ( ) , partition ( ) , oldStart , partition ( ) . length ( ) ) . run ( report ) )
report . line ( ) < < xi18nc ( " @info/plain " , " Moving back partition <filename>%1</filename> to its original position failed. " , partition ( ) . deviceNode ( ) ) ;
return false ;
}
return true ;
2015-06-04 01:29:22 +01:00
}
bool ResizeOperation : : grow ( Report & report )
{
2015-07-13 15:16:36 +01:00
const qint64 oldLength = partition ( ) . length ( ) ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
if ( growSetGeomJob ( ) & & ! growSetGeomJob ( ) - > run ( report ) ) {
report . line ( ) < < xi18nc ( " @info/plain " , " Resize/move failed: Could not grow partition <filename>%1</filename>. " , partition ( ) . deviceNode ( ) ) ;
return false ;
}
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
if ( growResizeJob ( ) & & ! growResizeJob ( ) - > run ( report ) ) {
report . line ( ) < < xi18nc ( " @info/plain " , " Resize/move failed: Could not resize the file system on partition <filename>%1</filename> " , partition ( ) . deviceNode ( ) ) ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
if ( ! SetPartGeometryJob ( targetDevice ( ) , partition ( ) , partition ( ) . firstSector ( ) , oldLength ) . run ( report ) )
report . line ( ) < < xi18nc ( " @info/plain " , " Could not restore old partition size for partition <filename>%1</filename>. " , partition ( ) . deviceNode ( ) ) ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
return false ;
}
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
return true ;
2015-06-04 01:29:22 +01:00
}
/** Can a Partition be grown, i.e. increased in size?
2015-07-22 14:48:03 +01:00
@ param p the Partition in question , may be nullptr .
2015-07-13 15:16:36 +01:00
@ return true if @ p p can be grown .
2015-06-04 01:29:22 +01:00
*/
bool ResizeOperation : : canGrow ( const Partition * p )
{
2015-07-22 14:48:03 +01:00
if ( p = = nullptr )
2015-07-13 15:16:36 +01:00
return false ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
// we can always grow, shrink or move a partition not yet written to disk
if ( p - > state ( ) = = Partition : : StateNew )
return true ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
if ( p - > isMounted ( ) )
return false ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
return p - > fileSystem ( ) . supportGrow ( ) ! = FileSystem : : cmdSupportNone ;
2015-06-04 01:29:22 +01:00
}
/** Can a Partition be shrunk, i.e. decreased in size?
2015-07-22 14:48:03 +01:00
@ param p the Partition in question , may be nullptr .
2015-07-13 15:16:36 +01:00
@ return true if @ p p can be shrunk .
2015-06-04 01:29:22 +01:00
*/
bool ResizeOperation : : canShrink ( const Partition * p )
{
2015-07-22 14:48:03 +01:00
if ( p = = nullptr )
2015-07-13 15:16:36 +01:00
return false ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
// we can always grow, shrink or move a partition not yet written to disk
if ( p - > state ( ) = = Partition : : StateNew )
return true ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
if ( p - > state ( ) = = Partition : : StateCopy )
return false ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
if ( p - > isMounted ( ) )
return false ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
return p - > fileSystem ( ) . supportShrink ( ) ! = FileSystem : : cmdSupportNone ;
2015-06-04 01:29:22 +01:00
}
/** Can a Partition be moved?
2015-07-22 14:48:03 +01:00
@ param p the Partition in question , may be nullptr .
2015-07-13 15:16:36 +01:00
@ return true if @ p p can be moved .
2015-06-04 01:29:22 +01:00
*/
bool ResizeOperation : : canMove ( const Partition * p )
{
2015-07-22 14:48:03 +01:00
if ( p = = nullptr )
2015-07-13 15:16:36 +01:00
return false ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
// we can always grow, shrink or move a partition not yet written to disk
if ( p - > state ( ) = = Partition : : StateNew )
return true ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
if ( p - > isMounted ( ) )
return false ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
// no moving of extended partitions if they have logicals
if ( p - > roles ( ) . has ( PartitionRole : : Extended ) & & p - > hasChildren ( ) )
return false ;
2015-06-04 01:29:22 +01:00
2015-07-13 15:16:36 +01:00
return p - > fileSystem ( ) . supportMove ( ) ! = FileSystem : : cmdSupportNone ;
2015-06-04 01:29:22 +01:00
}