File indexing completed on 2024-04-28 05:46:34

0001 /*
0002     SPDX-FileCopyrightText: 2008-2012 Volker Lanz <vl@fidra.de>
0003     SPDX-FileCopyrightText: 2014-2020 Andrius Štikonas <andrius@stikonas.eu>
0004     SPDX-FileCopyrightText: 2015-2016 Teo Mrnjavac <teo@kde.org>
0005     SPDX-FileCopyrightText: 2016 Chantara Tith <tith.chantara@gmail.com>
0006     SPDX-FileCopyrightText: 2018 Abhijeet Sharma <sharma.abhijeet2096@gmail.com>
0007 
0008     SPDX-License-Identifier: GPL-3.0-or-later
0009 */
0010 
0011 #include "gui/partitionmanagerwidget.h"
0012 #include "gui/partpropsdialog.h"
0013 #include "gui/resizedialog.h"
0014 #include "gui/newdialog.h"
0015 #include "gui/applyprogressdialog.h"
0016 #include "gui/insertdialog.h"
0017 #include "gui/editmountpointdialog.h"
0018 #include "util/guihelpers.h"
0019 
0020 #include <core/partition.h>
0021 #include <core/device.h>
0022 #include <core/operationstack.h>
0023 #include <core/partitiontable.h>
0024 
0025 #include <fs/filesystemfactory.h>
0026 #include <fs/luks.h>
0027 
0028 #include <gui/partwidget.h>
0029 
0030 #include <ops/deleteoperation.h>
0031 #include <ops/resizeoperation.h>
0032 #include <ops/newoperation.h>
0033 #include <ops/copyoperation.h>
0034 #include <ops/checkoperation.h>
0035 #include <ops/backupoperation.h>
0036 #include <ops/restoreoperation.h>
0037 #include <ops/setfilesystemlabeloperation.h>
0038 #include <ops/setpartflagsoperation.h>
0039 #include <ops/createfilesystemoperation.h>
0040 
0041 #include <util/globallog.h>
0042 #include <util/capacity.h>
0043 #include <util/report.h>
0044 #include <util/helpers.h>
0045 
0046 #include <QFileDialog>
0047 #include <QLocale>
0048 #include <QPointer>
0049 #include <QReadLocker>
0050 
0051 #include <KLocalizedString>
0052 #include <KMessageBox>
0053 
0054 #include <config.h>
0055 
0056 #include <typeinfo>
0057 
0058 class PartitionTreeWidgetItem : public QTreeWidgetItem
0059 {
0060     Q_DISABLE_COPY(PartitionTreeWidgetItem)
0061 
0062 public:
0063     PartitionTreeWidgetItem(const Partition* p) : QTreeWidgetItem(), m_Partition(p) {}
0064     const Partition* partition() const {
0065         return m_Partition;
0066     }
0067 
0068 private:
0069     const Partition* m_Partition;
0070 };
0071 
0072 /** Creates a new PartitionManagerWidget instance.
0073     @param parent the parent widget
0074 */
0075 PartitionManagerWidget::PartitionManagerWidget(QWidget* parent) :
0076     QWidget(parent),
0077     Ui::PartitionManagerWidgetBase(),
0078     m_OperationStack(nullptr),
0079     m_SelectedDevice(nullptr),
0080     m_ClipboardPartition(nullptr)
0081 {
0082     setupUi(this);
0083 
0084     treePartitions().header()->setStretchLastSection(false);
0085     treePartitions().header()->setContextMenuPolicy(Qt::CustomContextMenu);
0086     treePartitions().setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::TopEdge}));
0087 }
0088 
0089 PartitionManagerWidget::~PartitionManagerWidget()
0090 {
0091     saveConfig();
0092 }
0093 
0094 void PartitionManagerWidget::init(OperationStack* ostack)
0095 {
0096     m_OperationStack = ostack;
0097 
0098     loadConfig();
0099     setupConnections();
0100 }
0101 
0102 void PartitionManagerWidget::loadConfig()
0103 {
0104     QList<int> colWidths = Config::treePartitionColumnWidths();
0105     QList<int> colPositions = Config::treePartitionColumnPositions();
0106     QList<int> colVisible = Config::treePartitionColumnVisible();
0107     QHeaderView* header = treePartitions().header();
0108 
0109     for (int i = 0; i < treePartitions().columnCount(); i++) {
0110         if (colPositions[0] != -1 && colPositions.size() > i)
0111             header->moveSection(header->visualIndex(i), colPositions[i]);
0112 
0113         if (colVisible[0] != -1 && colVisible.size() > i)
0114             treePartitions().setColumnHidden(i, colVisible[i] == 0);
0115 
0116         if (colWidths[0] != -1 && colWidths.size() > i)
0117             treePartitions().setColumnWidth(i, colWidths[i]);
0118     }
0119 }
0120 
0121 void PartitionManagerWidget::saveConfig() const
0122 {
0123     QList<int> colWidths;
0124     QList<int> colPositions;
0125     QList<int> colVisible;
0126 
0127     for (int i = 0; i < treePartitions().columnCount(); i++) {
0128         colPositions.append(treePartitions().header()->visualIndex(i));
0129         colVisible.append(treePartitions().isColumnHidden(i) ? 0 : 1);
0130         colWidths.append(treePartitions().columnWidth(i));
0131     }
0132 
0133     Config::setTreePartitionColumnPositions(colPositions);
0134     Config::setTreePartitionColumnVisible(colVisible);
0135     Config::setTreePartitionColumnWidths(colWidths);
0136 
0137     Config::self()->save();
0138 }
0139 
0140 void PartitionManagerWidget::setupConnections()
0141 {
0142     connect(treePartitions().header(), &QHeaderView::customContextMenuRequested, this, &PartitionManagerWidget::onHeaderContextMenu);
0143 }
0144 
0145 void PartitionManagerWidget::clear()
0146 {
0147     setSelectedDevice(nullptr);
0148     setClipboardPartition(nullptr);
0149     treePartitions().clear();
0150     partTableWidget().clear();
0151 }
0152 
0153 void PartitionManagerWidget::setSelectedPartition(const Partition* p)
0154 {
0155     if (p == nullptr) {
0156         treePartitions().setCurrentItem(nullptr);
0157         Q_EMIT selectedPartitionChanged(nullptr);
0158         updatePartitions();
0159     } else
0160         partTableWidget().setActivePartition(p);
0161 }
0162 
0163 Partition* PartitionManagerWidget::selectedPartition()
0164 {
0165     if (selectedDevice() == nullptr || selectedDevice()->partitionTable() == nullptr || partTableWidget().activeWidget() == nullptr)
0166         return nullptr;
0167 
0168     return partTableWidget().activeWidget()->partition();
0169 }
0170 
0171 void PartitionManagerWidget::setSelectedDevice(const QString& deviceNode)
0172 {
0173     QReadLocker lockDevices(&operationStack().lock());
0174 
0175     const auto previewDevices = operationStack().previewDevices();
0176     for (const auto &d : previewDevices) {
0177         if (d->deviceNode() == deviceNode) {
0178             setSelectedDevice(d);
0179             return;
0180         }
0181     }
0182 
0183     setSelectedDevice(nullptr);
0184 }
0185 
0186 void PartitionManagerWidget::setSelectedDevice(Device* d)
0187 {
0188     m_SelectedDevice = d;
0189     setSelectedPartition(nullptr);
0190 }
0191 
0192 static QTreeWidgetItem* createTreeWidgetItem(const Partition& p)
0193 {
0194     QTreeWidgetItem* item = new PartitionTreeWidgetItem(&p);
0195 
0196     int i = 0;
0197     item->setText(i++, p.deviceNode());
0198 
0199     if (p.roles().has(PartitionRole::Luks) && (p.fileSystem().name() != p.fileSystem().nameForType(FileSystem::Type::Luks) && p.fileSystem().name() != p.fileSystem().nameForType(FileSystem::Type::Luks2)))
0200         item->setText(i, xi18nc("@item:intable Encrypted file systems, e.g. btrfs[Encrypted]", "%1 [Encrypted]", p.fileSystem().name()));
0201     else
0202         item->setText(i, p.fileSystem().name());
0203     item->setIcon(i, createFileSystemColor(p.fileSystem().type(), 14));
0204     i++;
0205 
0206     item->setText(i, p.mountPoint());
0207     if (p.isMounted())
0208         item->setIcon(i, QIcon::fromTheme(QStringLiteral("object-locked")));
0209     i++;
0210 
0211     item->setText(i++, p.fileSystem().label());
0212     item->setText(i++, p.fileSystem().uuid());
0213     item->setText(i++, p.label());
0214     item->setText(i++, p.uuid());
0215 
0216     item->setText(i++, Capacity::formatByteSize(p.capacity()));
0217     item->setText(i++, Capacity::formatByteSize(p.used()));
0218     item->setText(i++, Capacity::formatByteSize(p.available()));
0219 
0220     item->setText(i++, QLocale().toString(p.firstSector()));
0221     item->setText(i++, QLocale().toString(p.lastSector()));
0222     item->setText(i++, QLocale().toString(p.length()));
0223 
0224     item->setText(i++, PartitionTable::flagNames(p.activeFlags()).join(QStringLiteral(", ")));
0225 
0226     item->setSizeHint(0, QSize(0, 32));
0227 
0228     return item;
0229 }
0230 
0231 void PartitionManagerWidget::updatePartitions()
0232 {
0233     if (selectedDevice() == nullptr)
0234         return;
0235 
0236     treePartitions().clear();
0237     partTableWidget().clear();
0238 
0239     partTableWidget().setPartitionTable(selectedDevice()->partitionTable());
0240 
0241     QTreeWidgetItem* deviceItem = new QTreeWidgetItem();
0242 
0243     QFont font;
0244     font.setBold(true);
0245     font.setWeight(QFont::Bold);
0246     deviceItem->setFont(0, font);
0247 
0248     deviceItem->setText(0, selectedDevice()->prettyName());
0249     deviceItem->setIcon(0, QIcon::fromTheme(selectedDevice()->iconName()));
0250 
0251     deviceItem->setSizeHint(0, QSize(0, 32));
0252 
0253     treePartitions().addTopLevelItem(deviceItem);
0254 
0255     if (selectedDevice()->partitionTable() != nullptr) {
0256         const auto children = selectedDevice()->partitionTable()->children();
0257         for (const auto * p : children) {
0258             QTreeWidgetItem* item = createTreeWidgetItem(*p);
0259 
0260             for (const auto &child : p->children()) {
0261                 QTreeWidgetItem* childItem = createTreeWidgetItem(*child);
0262                 item->addChild(childItem);
0263             }
0264 
0265             deviceItem->addChild(item);
0266             item->setExpanded(true);
0267         }
0268     }
0269 
0270     deviceItem->setFirstColumnSpanned(true);
0271     deviceItem->setExpanded(true);
0272     deviceItem->setFlags(Qt::ItemIsEnabled);
0273 
0274     partTableWidget().update();
0275 }
0276 
0277 void PartitionManagerWidget::on_m_TreePartitions_currentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem*)
0278 {
0279     if (current) {
0280         const PartitionTreeWidgetItem* ptwItem = dynamic_cast<PartitionTreeWidgetItem*>(current);
0281         partTableWidget().setActivePartition(ptwItem ? ptwItem->partition() : nullptr);
0282     } else
0283         partTableWidget().setActiveWidget(nullptr);
0284 }
0285 
0286 bool PartitionManagerWidget::setCurrentPartitionByName(const QString& name)
0287 {
0288     auto rootNode = treePartitions().invisibleRootItem();
0289     for (int i = 0; i < rootNode->childCount(); i++) {
0290         auto driveNode = rootNode->child(i);
0291         for (int e = 0; e < driveNode->childCount(); e++) {
0292             auto partitionNode = driveNode->child(e);
0293             const QString text = partitionNode->data(0, Qt::DisplayRole).toString();
0294             if (text.endsWith(name)) {
0295                 partitionNode->setSelected(true);
0296                 treePartitions().setCurrentItem(partitionNode);
0297                 return true;
0298             }
0299         }
0300     }
0301 
0302     return false;
0303 }
0304 
0305 void PartitionManagerWidget::on_m_TreePartitions_itemDoubleClicked(QTreeWidgetItem* item, int)
0306 {
0307     if (item == treePartitions().topLevelItem(0)) {
0308         if (selectedDevice() != nullptr)
0309             Q_EMIT deviceDoubleClicked(selectedDevice());
0310     } else {
0311         if (selectedPartition() != nullptr)
0312             Q_EMIT partitionDoubleClicked(selectedPartition());
0313     }
0314 }
0315 
0316 void PartitionManagerWidget::onHeaderContextMenu(const QPoint& p)
0317 {
0318     showColumnsContextMenu(p, treePartitions());
0319 }
0320 
0321 void PartitionManagerWidget::on_m_PartTableWidget_itemSelectionChanged(PartWidget* item)
0322 {
0323     if (item == nullptr) {
0324         treePartitions().setCurrentItem(nullptr);
0325         Q_EMIT selectedPartitionChanged(nullptr);
0326         return;
0327     }
0328 
0329     const Partition* p = item->partition();
0330 
0331     Q_ASSERT(p);
0332 
0333     if (p) {
0334         QList<QTreeWidgetItem*> findResult = treePartitions().findItems(p->deviceNode(), Qt::MatchFixedString | Qt::MatchRecursive, 0);
0335 
0336         for (const auto &treeWidgetItem : findResult) {
0337             const PartitionTreeWidgetItem* ptwItem = dynamic_cast<PartitionTreeWidgetItem*>(treeWidgetItem);
0338 
0339             if (ptwItem && ptwItem->partition() == p) {
0340                 treePartitions().setCurrentItem(treeWidgetItem);
0341                 break;
0342             }
0343         }
0344     }
0345 
0346     Q_EMIT selectedPartitionChanged(p);
0347 }
0348 
0349 void PartitionManagerWidget::on_m_PartTableWidget_customContextMenuRequested(const QPoint& pos)
0350 {
0351     Q_EMIT contextMenuRequested(partTableWidget().mapToGlobal(pos));
0352 }
0353 
0354 void PartitionManagerWidget::on_m_PartTableWidget_itemDoubleClicked()
0355 {
0356     if (selectedPartition())
0357         Q_EMIT partitionDoubleClicked(selectedPartition());
0358 }
0359 
0360 void PartitionManagerWidget::on_m_TreePartitions_customContextMenuRequested(const QPoint& pos)
0361 {
0362     Q_EMIT contextMenuRequested(treePartitions().viewport()->mapToGlobal(pos));
0363 }
0364 
0365 void PartitionManagerWidget::onPropertiesPartition()
0366 {
0367     if (selectedPartition()) {
0368         Partition& p = *selectedPartition();
0369 
0370         Q_ASSERT(selectedDevice());
0371 
0372         QPointer<PartPropsDialog> dlg = new PartPropsDialog(this, *selectedDevice(), p);
0373 
0374         if (dlg->exec() == QDialog::Accepted) {
0375             if (dlg->newFileSystemType() != p.fileSystem().type() || dlg->forceRecreate())
0376                 operationStack().push(new CreateFileSystemOperation(*selectedDevice(), p, dlg->newFileSystemType()));
0377 
0378             if (dlg->newLabel() != p.fileSystem().label())
0379                 operationStack().push(new SetFileSystemLabelOperation(p, dlg->newLabel()));
0380 
0381             if (dlg->newFlags() != p.activeFlags())
0382                 operationStack().push(new SetPartFlagsOperation(*selectedDevice(), p, dlg->newFlags()));
0383         }
0384 
0385         delete dlg;
0386     }
0387 }
0388 
0389 void PartitionManagerWidget::onMountPartition()
0390 {
0391     Partition* p = selectedPartition();
0392 
0393     Q_ASSERT(p);
0394 
0395     if (p == nullptr) {
0396         qWarning() << "no partition selected";
0397         return;
0398     }
0399 
0400     Report report(nullptr);
0401 
0402     if (p->canMount()) {
0403         if (!p->mount(report))
0404             KMessageBox::detailedError(this, xi18nc("@info", "The file system on partition <filename>%1</filename> could not be mounted.", p->deviceNode()), QStringLiteral("<pre>%1</pre>").arg(report.toText()), xi18nc("@title:window", "Could Not Mount File System."));
0405     } else if (p->canUnmount()) {
0406         if (!p->unmount(report))
0407             KMessageBox::detailedError(this, xi18nc("@info", "The file system on partition <filename>%1</filename> could not be unmounted.", p->deviceNode()), QStringLiteral("<pre>%1</pre>").arg(report.toText()), xi18nc("@title:window", "Could Not Unmount File System."));
0408     }
0409 
0410     if (p->roles().has(PartitionRole::Logical)) {
0411         Partition* parent = dynamic_cast<Partition*>(p->parent());
0412 
0413         Q_ASSERT(parent);
0414 
0415         if (parent != nullptr)
0416             parent->checkChildrenMounted();
0417         else
0418             qWarning() << "parent is null";
0419     }
0420 
0421     updatePartitions();
0422 }
0423 
0424 void PartitionManagerWidget::onDecryptPartition()
0425 {
0426     Partition* p = selectedPartition();
0427 
0428     Q_ASSERT(p);
0429 
0430     if (p == nullptr) {
0431         qWarning() << "no partition selected";
0432         return;
0433     }
0434 
0435     if (!p->roles().has(PartitionRole::Luks))
0436         return;
0437 
0438     const FileSystem& fsRef = p->fileSystem();
0439     FS::luks* luksFs = const_cast<FS::luks*>(dynamic_cast<const FS::luks*>(&fsRef));
0440     if (!luksFs)
0441         return;
0442 
0443     if (luksFs->canCryptOpen(p->partitionPath())) {
0444         if (!luksFs->cryptOpen(this, p->partitionPath()))
0445             KMessageBox::detailedError(this,
0446                                        xi18nc("@info",
0447                                               "The encrypted file system on partition "
0448                                               "<filename>%1</filename> could not be "
0449                                               "unlocked.",
0450                                               p->deviceNode()),
0451                                        QString(),
0452                                        xi18nc("@title:window",
0453                                              "Could Not Unlock Encrypted File System."));
0454     } else if (luksFs->canCryptClose(p->partitionPath())) {
0455         if (!luksFs->cryptClose(p->partitionPath()))
0456             KMessageBox::detailedError(this,
0457                                        xi18nc("@info",
0458                                               "The encrypted file system on partition "
0459                                               "<filename>%1</filename> could not be "
0460                                               "locked.",
0461                                               p->deviceNode()),
0462                                        QString(),
0463                                        xi18nc("@title:window",
0464                                              "Could Not Lock Encrypted File System."));
0465     }
0466 
0467     updatePartitions();
0468 }
0469 
0470 void PartitionManagerWidget::onEditMountPoint()
0471 {
0472     Partition* p = selectedPartition();
0473 
0474     Q_ASSERT(p);
0475 
0476     if (p == nullptr)
0477         return;
0478 
0479     QPointer<EditMountPointDialog> dlg = new EditMountPointDialog(this, *p);
0480 
0481     if (dlg->exec() == QDialog::Accepted)
0482         updatePartitions();
0483 
0484     delete dlg;
0485 }
0486 
0487 static bool checkTooManyPartitions(QWidget* parent, const Device& d, const Partition& p)
0488 {
0489     Q_ASSERT(d.partitionTable());
0490 
0491     if (p.roles().has(PartitionRole::Unallocated) && d.partitionTable()->numPrimaries() >= d.partitionTable()->maxPrimaries() && !p.roles().has(PartitionRole::Logical)) {
0492         KMessageBox::error(parent, xi18ncp("@info",
0493                                            "<para>There is already one primary partition on this device. This is the maximum number its partition table type can handle.</para>"
0494                                            "<para>You cannot create, paste or restore a primary partition on it before you delete an existing one.</para>",
0495                                            "<para>There are already %1 primary partitions on this device. This is the maximum number its partition table type can handle.</para>"
0496                                            "<para>You cannot create, paste or restore a primary partition on it before you delete an existing one.</para>",
0497                                            d.partitionTable()->numPrimaries()), xi18nc("@title:window", "Too Many Primary Partitions."));
0498         return true;
0499     }
0500 
0501     return false;
0502 }
0503 
0504 void PartitionManagerWidget::onNewPartition()
0505 {
0506     Q_ASSERT(selectedDevice());
0507     Q_ASSERT(selectedPartition());
0508 
0509     if (selectedDevice() == nullptr || selectedPartition() == nullptr) {
0510         qWarning() << "selected device: " << selectedDevice() << ", selected partition: " << selectedPartition();
0511         return;
0512     }
0513 
0514     Q_ASSERT(selectedDevice()->partitionTable());
0515 
0516     if (selectedDevice()->partitionTable() == nullptr) {
0517         qWarning() << "partition table on selected device is null";
0518         return;
0519     }
0520 
0521     if (checkTooManyPartitions(this, *selectedDevice(), *selectedPartition()))
0522         return;
0523 
0524     Partition* newPartition = NewOperation::createNew(*selectedPartition(), static_cast<FileSystem::Type>(Config::defaultFileSystem()));
0525 
0526     QPointer<NewDialog> dlg = new NewDialog(this, *selectedDevice(), *newPartition, selectedDevice()->partitionTable()->childRoles(*selectedPartition()));
0527     if (dlg->exec() == QDialog::Accepted) {
0528         if (dlg->useUnsecuredPartition()) {
0529             newPartition->fileSystem().setPosixPermissions(QStringLiteral("777"));
0530         }
0531         operationStack().push(new NewOperation(*selectedDevice(), newPartition));
0532     } else {
0533         delete newPartition;
0534     }
0535 
0536     delete dlg;
0537 }
0538 
0539 void PartitionManagerWidget::onDeletePartition(bool shred)
0540 {
0541     Q_ASSERT(selectedDevice());
0542     Q_ASSERT(selectedPartition());
0543 
0544     if (selectedDevice() == nullptr || selectedPartition() == nullptr) {
0545         qWarning() << "selected device: " << selectedDevice() << ", selected partition: " << selectedPartition();
0546         return;
0547     }
0548 
0549     if (selectedPartition()->roles().has(PartitionRole::Logical)) {
0550         Q_ASSERT(selectedPartition()->parent());
0551 
0552         if (selectedPartition()->parent() == nullptr) {
0553             qWarning() << "parent of selected partition is null.";
0554             return;
0555         }
0556 
0557         if (selectedPartition()->number() > 0 && selectedPartition()->parent()->highestMountedChild() > selectedPartition()->number()) {
0558             KMessageBox::error(this,
0559                                xi18nc("@info",
0560                                       "<para>The partition <filename>%1</filename> cannot currently be deleted because one or more partitions with higher logical numbers are still mounted.</para>"
0561                                       "<para>Please unmount all partitions with higher logical numbers than %2 first.</para>",
0562                                       selectedPartition()->deviceNode(), selectedPartition()->number()),
0563                                xi18nc("@title:window", "Cannot Delete Partition."));
0564 
0565             return;
0566         }
0567     }
0568 
0569     if (clipboardPartition() == selectedPartition()) {
0570         if (KMessageBox::warningContinueCancel(this,
0571                                                xi18nc("@info",
0572                                                        "Do you really want to delete the partition that is currently in the clipboard? "
0573                                                        "It will no longer be available for pasting after it has been deleted."),
0574                                                xi18nc("@title:window", "Really Delete Partition in the Clipboard?"),
0575                                                KGuiItem(xi18nc("@action:button", "Delete It"), QStringLiteral("arrow-right")),
0576                                                KStandardGuiItem::cancel(), QStringLiteral("reallyDeleteClipboardPartition")) == KMessageBox::Cancel)
0577             return;
0578 
0579         setClipboardPartition(nullptr);
0580     }
0581 
0582     if (shred && Config::shredSource() == Config::EnumShredSource::random)
0583         operationStack().push(new DeleteOperation(*selectedDevice(), selectedPartition(), DeleteOperation::ShredAction::RandomShred));
0584     else if (shred && Config::shredSource() == Config::EnumShredSource::zeros)
0585         operationStack().push(new DeleteOperation(*selectedDevice(), selectedPartition(), DeleteOperation::ShredAction::ZeroShred));
0586     else
0587         operationStack().push(new DeleteOperation(*selectedDevice(), selectedPartition(), DeleteOperation::ShredAction::NoShred));
0588 }
0589 
0590 void PartitionManagerWidget::onShredPartition()
0591 {
0592     onDeletePartition(true);
0593 }
0594 
0595 void PartitionManagerWidget::onResizePartition()
0596 {
0597     Q_ASSERT(selectedDevice());
0598     Q_ASSERT(selectedPartition());
0599 
0600     if (selectedDevice() == nullptr || selectedPartition() == nullptr) {
0601         qWarning() << "selected device: " << selectedDevice() << ", selected partition: " << selectedPartition();
0602         return;
0603     }
0604 
0605     Q_ASSERT(selectedDevice()->partitionTable());
0606 
0607     if (selectedDevice()->partitionTable() == nullptr) {
0608         qWarning() << "partition table on selected device is null";
0609         return;
0610     }
0611 
0612     // we cannot work with selectedPartition() here because opening and closing the dialog will
0613     // clear the selection, so we'll lose the partition after the dialog's been exec'd
0614     Partition& p = *selectedPartition();
0615 
0616     qint64 freeBefore = selectedDevice()->partitionTable()->freeSectorsBefore(p);
0617     qint64 freeAfter = selectedDevice()->partitionTable()->freeSectorsAfter(p);
0618 
0619     if (selectedDevice()->type() == Device::Type::LVM_Device) {
0620         freeBefore = 0;
0621         freeAfter  = selectedDevice()->partitionTable()->freeSectors();
0622     }
0623 
0624     QPointer<ResizeDialog> dlg = new ResizeDialog(this, *selectedDevice(), p, p.firstSector() - freeBefore, p.lastSector() + freeAfter);
0625 
0626     if (dlg->exec() == QDialog::Accepted) {
0627         if (dlg->resizedFirstSector() == p.firstSector() && dlg->resizedLastSector() == p.lastSector())
0628             Log(Log::Level::information) << xi18nc("@info:status", "Partition <filename>%1</filename> has the same position and size after resize/move. Ignoring operation.", p.deviceNode());
0629         else
0630             operationStack().push(new ResizeOperation(*selectedDevice(), p, dlg->resizedFirstSector(), dlg->resizedLastSector()));
0631     }
0632 
0633     if (p.roles().has(PartitionRole::Extended)) {
0634         // Even if the user dismissed the resize dialog we must update the partitions
0635         // if it's an extended partition:
0636         // The dialog has to remove and create unallocated children if the user resizes
0637         // an extended partition. We can't know if that has happened, so to avoid
0638         // any problems (like, the user resized an extended and then canceled, which would
0639         // lead to the unallocated children having the wrong size) do this now.
0640         updatePartitions();
0641     }
0642 
0643     delete dlg;
0644 }
0645 
0646 void PartitionManagerWidget::onCopyPartition()
0647 {
0648     Q_ASSERT(selectedPartition());
0649 
0650     if (selectedPartition() == nullptr) {
0651         qWarning() << "selected partition: " << selectedPartition();
0652         return;
0653     }
0654 
0655     setClipboardPartition(selectedPartition());
0656     Log() << xi18nc("@info:status", "Partition <filename>%1</filename> has been copied to the clipboard.", selectedPartition()->deviceNode());
0657 }
0658 
0659 void PartitionManagerWidget::onPastePartition()
0660 {
0661     Q_ASSERT(selectedDevice());
0662     Q_ASSERT(selectedPartition());
0663 
0664     if (selectedDevice() == nullptr || selectedPartition() == nullptr) {
0665         qWarning() << "selected device: " << selectedDevice() << ", selected partition: " << selectedPartition();
0666         return;
0667     }
0668 
0669     if (clipboardPartition() == nullptr) {
0670         qWarning() << "no partition in the clipboard.";
0671         return;
0672     }
0673 
0674     if (checkTooManyPartitions(this, *selectedDevice(), *selectedPartition()))
0675         return;
0676 
0677     Device* dSource = operationStack().findDeviceForPartition(clipboardPartition());
0678 
0679     Q_ASSERT(dSource);
0680 
0681     if (dSource == nullptr) {
0682         qWarning() << "source partition is null.";
0683         return;
0684     }
0685 
0686     Partition* copiedPartition = CopyOperation::createCopy(*selectedPartition(), *clipboardPartition());
0687 
0688     if (showInsertDialog(*copiedPartition, clipboardPartition()->length()))
0689         operationStack().push(new CopyOperation(*selectedDevice(), copiedPartition, *dSource, clipboardPartition()));
0690     else
0691         delete copiedPartition;
0692 }
0693 
0694 bool PartitionManagerWidget::showInsertDialog(Partition& insertedPartition, qint64 sourceLength)
0695 {
0696     Q_ASSERT(selectedDevice());
0697     Q_ASSERT(selectedPartition());
0698 
0699     if (selectedDevice() == nullptr || selectedPartition() == nullptr) {
0700         qWarning() << "selected device: " << selectedDevice() << ", selected partition: " << selectedPartition();
0701         return false;
0702     }
0703 
0704     const bool overwrite = !selectedPartition()->roles().has(PartitionRole::Unallocated);
0705 
0706     // Make sure the inserted partition has the right parent and logical or primary set. Only then
0707     // can PartitionTable::alignPartition() work correctly.
0708     selectedPartition()->parent()->reparent(insertedPartition);
0709 
0710     if (!overwrite) {
0711         QPointer<InsertDialog> dlg = new InsertDialog(this, *selectedDevice(), insertedPartition, *selectedPartition());
0712 
0713         int result = dlg->exec();
0714         delete dlg;
0715 
0716         if (result != QDialog::Accepted)
0717             return false;
0718     } else if (KMessageBox::warningContinueCancel(this,
0719                xi18nc("@info", "<para><warning>You are about to lose all data on partition "
0720                       "<filename>%1</filename>.</warning></para>"
0721                       "<para>Overwriting one partition with another (or with an image file) will "
0722                       "destroy all data on this target partition.</para>"
0723                       "<para>If you continue now and apply the resulting operation in the main "
0724                       "window, all data currently stored on <filename>%1</filename> will "
0725                       "unrecoverably be overwritten.</para>",
0726                       selectedPartition()->deviceNode()),
0727                xi18nc("@title:window", "Really Overwrite Existing Partition?"),
0728                KGuiItem(xi18nc("@action:button", "Overwrite Partition"), QStringLiteral("arrow-right")),
0729                KStandardGuiItem::cancel(),
0730                QStringLiteral("reallyOverwriteExistingPartition")) == KMessageBox::Cancel)
0731         return false;
0732 
0733     if (insertedPartition.length() < sourceLength) {
0734         if (overwrite)
0735             KMessageBox::error(this, xi18nc("@info",
0736                                             "<para>The selected partition is not large enough to hold the source partition or the backup file.</para>"
0737                                             "<para>Pick another target or resize this partition so it is as large as the source.</para>"), xi18nc("@title:window", "Target Not Large Enough"));
0738         else
0739             KMessageBox::error(this, xi18nc("@info",
0740                                             "<para>It is not possible to create the target partition large enough to hold the source.</para>"
0741                                             "<para>This may happen if not all partitions on a device are correctly aligned "
0742                                             "or when copying a primary partition into an extended partition.</para>"),
0743                                xi18nc("@title:window", "Cannot Create Target Partition."));
0744         return false;
0745     }
0746 
0747     return true;
0748 }
0749 
0750 void PartitionManagerWidget::onCheckPartition()
0751 {
0752     Q_ASSERT(selectedDevice());
0753     Q_ASSERT(selectedPartition());
0754 
0755     if (selectedDevice() == nullptr || selectedPartition() == nullptr) {
0756         qWarning() << "selected device: " << selectedDevice() << ", selected partition: " << selectedPartition();
0757         return;
0758     }
0759 
0760     operationStack().push(new CheckOperation(*selectedDevice(), *selectedPartition()));
0761 }
0762 
0763 void PartitionManagerWidget::onBackupPartition()
0764 {
0765     Q_ASSERT(selectedDevice());
0766     Q_ASSERT(selectedPartition());
0767 
0768     if (selectedDevice() == nullptr || selectedPartition() == nullptr) {
0769         qWarning() << "selected device: " << selectedDevice() << ", selected partition: " << selectedPartition();
0770         return;
0771     }
0772 
0773     QString fileName = QFileDialog::getSaveFileName(this);
0774 
0775     if (fileName.isEmpty())
0776         return;
0777 
0778     operationStack().push(new BackupOperation(*selectedDevice(), *selectedPartition(), fileName));
0779 }
0780 
0781 void PartitionManagerWidget::onRestorePartition()
0782 {
0783     Q_ASSERT(selectedDevice());
0784     Q_ASSERT(selectedPartition());
0785 
0786     if (selectedDevice() == nullptr || selectedPartition() == nullptr) {
0787         qWarning() << "selected device: " << selectedDevice() << ", selected partition: " << selectedPartition();
0788         return;
0789     }
0790 
0791     if (checkTooManyPartitions(this, *selectedDevice(), *selectedPartition()))
0792         return;
0793 
0794     QString fileName = QFileDialog::getOpenFileName(this);
0795 //  QString fileName = "/tmp/backuptest.img";
0796 
0797     if (!fileName.isEmpty() && QFile::exists(fileName)) {
0798         Partition* restorePartition = RestoreOperation::createRestorePartition(*selectedDevice(), *selectedPartition()->parent(), selectedPartition()->firstSector(), fileName);
0799 
0800         if (restorePartition->length() > selectedPartition()->length()) {
0801             KMessageBox::error(this, xi18nc("@info", "The file system in the image file <filename>%1</filename> is too large to be restored to the selected partition.", fileName), xi18nc("@title:window", "Not Enough Space to Restore File System."));
0802             delete restorePartition;
0803             return;
0804         }
0805 
0806         if (showInsertDialog(*restorePartition, restorePartition->length()))
0807             operationStack().push(new RestoreOperation(*selectedDevice(), restorePartition, fileName));
0808         else
0809             delete restorePartition;
0810     }
0811 }
0812 
0813 #include "moc_partitionmanagerwidget.cpp"