Logo Search packages:      
Sourcecode: partitionmanager version File versions  Download package

resizeoperation.cpp

/***************************************************************************
 *   Copyright (C) 2008 by Volker Lanz <vl@fidra.de>                       *
 *                                                                         *
 *   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 "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 "util/capacity.h"
#include "util/report.h"

#include <QString>

#include <kdebug.h>
#include <klocale.h>

/** Creates a new ResizeOperation.
      @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
*/
00047 ResizeOperation::ResizeOperation(Device& d, Partition& p, qint64 newfirst, qint64 newlast) :
      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())),
      m_MoveExtendedJob(NULL),
      m_ShrinkResizeJob(NULL),
      m_ShrinkSetGeomJob(NULL),
      m_MoveSetGeomJob(NULL),
      m_MoveFileSystemJob(NULL),
      m_GrowResizeJob(NULL),
      m_GrowSetGeomJob(NULL),
      m_CheckResizedJob(NULL)
{
      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());
      }
}

00111 void ResizeOperation::preview()
{
      // 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());
      }
      
      removePreviewPartition(targetDevice(), partition());
      
      partition().setFirstSector(newFirstSector());
      partition().setLastSector(newLastSector());
      
      insertPreviewPartition(targetDevice(), partition());
}

00130 void ResizeOperation::undo()
{
      removePreviewPartition(targetDevice(), partition());
      partition().setFirstSector(origFirstSector());
      partition().setLastSector(origLastSector());
      insertPreviewPartition(targetDevice(), partition());
}

00138 bool ResizeOperation::execute(Report& parent)
{
      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 libparted would not let us do that.
            if (moveExtendedJob())
            {
                  if (!(rval = moveExtendedJob()->run(*report)))
                        report->line() << i18nc("@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() << i18nc("@info/plain", "Checking partition <filename>%1</filename> after resize/move failed.", partition().deviceNode());
                  }
                  else
                        report->line() << i18nc("@info/plain", "Resizing/moving partition <filename>%1</filename> failed.", partition().deviceNode());
            }
      }
      else
            report->line() << i18nc("@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;
}

00179 QString ResizeOperation::description() const
{
      // 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...

      const QString moveDelta = Capacity(qAbs(newFirstSector() - origFirstSector()) * targetDevice().sectorSize()).toString();

      const QString origCapacity = Capacity(origLength() * targetDevice().sectorSize()).toString();
      const QString newCapacity = Capacity(newLength() * targetDevice().sectorSize()).toString();

      switch(resizeAction())
      {
            case MoveLeft:
                  return i18nc("@info/plain describe resize/move action", "Move partition <filename>%1</filename> to the left by %2", partition().deviceNode(), moveDelta);

            case MoveRight:
                  return i18nc("@info/plain describe resize/move action", "Move partition <filename>%1</filename> to the right by %2", partition().deviceNode(), moveDelta);

            case Grow:
                  return i18nc("@info/plain describe resize/move action", "Grow partition <filename>%1</filename> from %2 to %3", partition().deviceNode(), origCapacity, newCapacity);

            case Shrink:
                  return i18nc("@info/plain describe resize/move action", "Shrink partition <filename>%1</filename> from %2 to %3", partition().deviceNode(), origCapacity, newCapacity);

            case MoveLeftGrow:
                  return i18nc("@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);

            case MoveRightGrow:
                  return i18nc("@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);

            case MoveLeftShrink:
                  return i18nc("@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);

            case MoveRightShrink:
                  return i18nc("@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);

            default:
                  kWarning() << "Could not determine what to do with partition " << partition().deviceNode() << ".";
                  break;
      }

      return i18nc("@info/plain describe resize/move action", "Unknown resize/move action.");
}

ResizeOperation::ResizeAction ResizeOperation::resizeAction() const
{
      ResizeAction action = None;

      // Grow?
      if (newLength() > origLength())
            action = Grow;

      // Shrink?
      if (newLength() < origLength())
            action = Shrink;

      // Move to the right?
      if (newFirstSector() > origFirstSector())
            action = static_cast<ResizeAction>(action | MoveRight);

      // Move to the left?
      if (newFirstSector() < origFirstSector())
            action = static_cast<ResizeAction>(action | MoveLeft);

      return action;
}

00255 bool ResizeOperation::shrink(Report& report)
{
      if (shrinkResizeJob() && !shrinkResizeJob()->run(report))
      {
            report.line() << i18nc("@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() << i18nc("@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;
}

bool ResizeOperation::move(Report& report)
{
      // 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 (libparted will 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() << i18nc("@info/plain", "Moving partition <filename>%1</filename> failed.", partition().deviceNode());
            return false;
      }

      if (moveFileSystemJob() && !moveFileSystemJob()->run(report))
      {
            report.line() << i18nc("@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() << i18nc("@info/plain", "Moving back partition <filename>%1</filename> to its original position failed.", partition().deviceNode());

            return false;
      }

      return true;
}

bool ResizeOperation::grow(Report& report)
{
      const qint64 oldLength = partition().length();
      
      if (growSetGeomJob() && !growSetGeomJob()->run(report))
      {
            report.line() << i18nc("@info/plain", "Resize/move failed: Could not grow partition <filename>%1</filename>.", partition().deviceNode());
            return false;
      }

      if (growResizeJob() && !growResizeJob()->run(report))
      {
            report.line() << i18nc("@info/plain", "Resize/move failed: Could not resize the file system on partition <filename>%1</filename>", partition().deviceNode());
                  
            if (!SetPartGeometryJob(targetDevice(), partition(), partition().firstSector(), oldLength).run(report))
                  report.line() << i18nc("@info/plain", "Could not restore old partition size for partition <filename>%1</filename>.", partition().deviceNode());
            
            return false;
      }

      return true;
}

/** Can a Partition be grown, i.e. increased in size?
      @param p the Partition in question, may be NULL.
      @return true if @p p can be grown.
 */
00330 bool ResizeOperation::canGrow(const Partition* p)
{
      if (p == NULL)
            return false;
      
      // we can always grow, shrink or move a partition not yet written to disk
      if (p->state() == Partition::StateNew)
            return true;

      if (p->isMounted())
            return false;

      return p->fileSystem().supportGrow() != FileSystem::SupportNone;
}

/** Can a Partition be shrunk, i.e. decreased in size?
      @param p the Partition in question, may be NULL.
      @return true if @p p can be shrunk.
 */
00349 bool ResizeOperation::canShrink(const Partition* p)
{
      if (p == NULL)
            return false;
      
      // we can always grow, shrink or move a partition not yet written to disk
      if (p->state() == Partition::StateNew)
            return true;

      if (p->state() == Partition::StateCopy)
            return false;

      if (p->isMounted())
            return false;

      return p->fileSystem().supportShrink() != FileSystem::SupportNone;
}

/** Can a Partition be moved?
      @param p the Partition in question, may be NULL.
      @return true if @p p can be moved.
 */
00371 bool ResizeOperation::canMove(const Partition* p)
{
      if (p == NULL)
            return false;
      
      // we can always grow, shrink or move a partition not yet written to disk
      if (p->state() == Partition::StateNew)
            return true;

      if (p->isMounted())
            return false;

      // no moving of extended partitions if they have logicals
      if (p->roles().has(PartitionRole::Extended) && p->hasChildren())
            return false;

      return p->fileSystem().supportMove() != FileSystem::SupportNone;
}

Generated by  Doxygen 1.6.0   Back to index