source: calamares/trunk/fuentes/src/modules/partition/core/PartitionCoreModule.cpp @ 7538

Last change on this file since 7538 was 7538, checked in by kbut, 16 months ago

sync with github

File size: 19.2 KB
Line 
1/* === This file is part of Calamares - <https://github.com/calamares> ===
2 *
3 *   Copyright 2014, Aurélien Gâteau <agateau@kde.org>
4 *   Copyright 2014-2015, Teo Mrnjavac <teo@kde.org>
5 *   Copyright 2017, Adriaan de Groot <groot@kde.org>
6 *
7 *   Calamares is free software: you can redistribute it and/or modify
8 *   it under the terms of the GNU General Public License as published by
9 *   the Free Software Foundation, either version 3 of the License, or
10 *   (at your option) any later version.
11 *
12 *   Calamares is distributed in the hope that it will be useful,
13 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
14 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 *   GNU General Public License for more details.
16 *
17 *   You should have received a copy of the GNU General Public License
18 *   along with Calamares. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#include "core/PartitionCoreModule.h"
22
23#include "core/BootLoaderModel.h"
24#include "core/ColorUtils.h"
25#include "core/DeviceList.h"
26#include "core/DeviceModel.h"
27#include "core/PartitionInfo.h"
28#include "core/PartitionIterator.h"
29#include "core/PartitionModel.h"
30#include "core/KPMHelpers.h"
31#include "core/PartUtils.h"
32#include "jobs/ClearMountsJob.h"
33#include "jobs/ClearTempMountsJob.h"
34#include "jobs/CreatePartitionJob.h"
35#include "jobs/CreatePartitionTableJob.h"
36#include "jobs/DeletePartitionJob.h"
37#include "jobs/FillGlobalStorageJob.h"
38#include "jobs/FormatPartitionJob.h"
39#include "jobs/ResizePartitionJob.h"
40#include "jobs/SetPartitionFlagsJob.h"
41
42#include "Typedefs.h"
43#include "utils/Logger.h"
44
45// KPMcore
46#include <kpmcore/core/device.h>
47#include <kpmcore/core/partition.h>
48#include <kpmcore/backend/corebackend.h>
49#include <kpmcore/backend/corebackendmanager.h>
50#include <kpmcore/fs/filesystemfactory.h>
51
52// Qt
53#include <QStandardItemModel>
54#include <QDir>
55#include <QProcess>
56#include <QFutureWatcher>
57#include <QtConcurrent/QtConcurrent>
58
59//- DeviceInfo ---------------------------------------------
60PartitionCoreModule::DeviceInfo::DeviceInfo( Device* _device )
61    : device( _device )
62    , partitionModel( new PartitionModel )
63    , immutableDevice( new Device( *_device ) )
64{}
65
66PartitionCoreModule::DeviceInfo::~DeviceInfo()
67{
68}
69
70
71void
72PartitionCoreModule::DeviceInfo::forgetChanges()
73{
74    jobs.clear();
75    for ( auto it = PartitionIterator::begin( device.data() ); it != PartitionIterator::end( device.data() ); ++it )
76        PartitionInfo::reset( *it );
77    partitionModel->revert();
78}
79
80
81bool
82PartitionCoreModule::DeviceInfo::isDirty() const
83{
84    if ( !jobs.isEmpty() )
85        return true;
86
87    for ( auto it = PartitionIterator::begin( device.data() ); it != PartitionIterator::end( device.data() ); ++it )
88        if ( PartitionInfo::isDirty( *it ) )
89            return true;
90
91    return false;
92}
93
94//- PartitionCoreModule ------------------------------------
95PartitionCoreModule::PartitionCoreModule( QObject* parent )
96    : QObject( parent )
97    , m_deviceModel( new DeviceModel( this ) )
98    , m_bootLoaderModel( new BootLoaderModel( this ) )
99{
100    if ( !KPMHelpers::initKPMcore() )
101        qFatal( "Failed to initialize KPMcore backend" );
102}
103
104
105void
106PartitionCoreModule::init()
107{
108    QMutexLocker locker( &m_revertMutex );
109    doInit();
110}
111
112
113void
114PartitionCoreModule::doInit()
115{
116    FileSystemFactory::init();
117
118    using DeviceList = QList< Device* >;
119    DeviceList devices = PartUtils::getDevices( PartUtils::DeviceType::WritableOnly );
120
121    cDebug() << "LIST OF DETECTED DEVICES:";
122    cDebug() << "node\tcapacity\tname\tprettyName";
123    for ( auto device : devices )
124    {
125        // Gives ownership of the Device* to the DeviceInfo object
126        auto deviceInfo = new DeviceInfo( device );
127        m_deviceInfos << deviceInfo;
128        cDebug() << device->deviceNode() << device->capacity() << device->name() << device->prettyName();
129    }
130    cDebug() << ".." << devices.count() << "devices detected.";
131    m_deviceModel->init( devices );
132
133    // The following PartUtils::runOsprober call in turn calls PartUtils::canBeResized,
134    // which relies on a working DeviceModel.
135    m_osproberLines = PartUtils::runOsprober( this );
136
137    // We perform a best effort of filling out filesystem UUIDs in m_osproberLines
138    // because we will need them later on in PartitionModel if partition paths
139    // change.
140    // It is a known fact that /dev/sda1-style device paths aren't persistent
141    // across reboots (and this doesn't affect us), but partition numbers can also
142    // change at runtime against our will just for shits and giggles.
143    // But why would that ever happen? What system could possibly be so poorly
144    // designed that it requires a partition path rearrangement at runtime?
145    // Logical partitions on an MSDOS disklabel of course.
146    // See DeletePartitionJob::updatePreview.
147    for ( auto deviceInfo : m_deviceInfos )
148    {
149        for ( auto it = PartitionIterator::begin( deviceInfo->device.data() );
150                it != PartitionIterator::end( deviceInfo->device.data() ); ++it )
151        {
152            Partition* partition = *it;
153            for ( auto jt = m_osproberLines.begin();
154                    jt != m_osproberLines.end(); ++jt )
155            {
156                if ( jt->path == partition->partitionPath() &&
157                        partition->fileSystem().supportGetUUID() != FileSystem::cmdSupportNone &&
158                        !partition->fileSystem().uuid().isEmpty() )
159                    jt->uuid = partition->fileSystem().uuid();
160            }
161        }
162    }
163
164    for ( auto deviceInfo : m_deviceInfos )
165        deviceInfo->partitionModel->init( deviceInfo->device.data(), m_osproberLines );
166
167    DeviceList bootLoaderDevices;
168
169    for ( DeviceList::Iterator it = devices.begin(); it != devices.end(); ++it)
170        if ( (*it)->type() != Device::Type::Disk_Device )
171        {
172            cDebug() << "Ignoring device that is not Disk_Device to bootLoaderDevices list.";
173            continue;
174        }
175        else
176            bootLoaderDevices.append(*it);
177
178    m_bootLoaderModel->init( bootLoaderDevices );
179
180    //FIXME: this should be removed in favor of
181    //       proper KPM support for EFI
182    if ( PartUtils::isEfiSystem() )
183        scanForEfiSystemPartitions();
184}
185
186PartitionCoreModule::~PartitionCoreModule()
187{
188    qDeleteAll( m_deviceInfos );
189}
190
191DeviceModel*
192PartitionCoreModule::deviceModel() const
193{
194    return m_deviceModel;
195}
196
197QAbstractItemModel*
198PartitionCoreModule::bootLoaderModel() const
199{
200    return m_bootLoaderModel;
201}
202
203PartitionModel*
204PartitionCoreModule::partitionModelForDevice( const Device* device ) const
205{
206    DeviceInfo* info = infoForDevice( device );
207    Q_ASSERT( info );
208    return info->partitionModel.data();
209}
210
211
212Device*
213PartitionCoreModule::immutableDeviceCopy( const Device* device )
214{
215    Q_ASSERT( device );
216    DeviceInfo* info = infoForDevice( device );
217    if ( !info )
218        return nullptr;
219
220    return info->immutableDevice.data();
221}
222
223
224void
225PartitionCoreModule::createPartitionTable( Device* device, PartitionTable::TableType type )
226{
227    DeviceInfo* info = infoForDevice( device );
228    if ( info )
229    {
230        // Creating a partition table wipes all the disk, so there is no need to
231        // keep previous changes
232        info->forgetChanges();
233
234        PartitionModel::ResetHelper helper( partitionModelForDevice( device ) );
235        CreatePartitionTableJob* job = new CreatePartitionTableJob( device, type );
236        job->updatePreview();
237        info->jobs << Calamares::job_ptr( job );
238    }
239
240    refresh();
241}
242
243void
244PartitionCoreModule::createPartition( Device* device,
245                                      Partition* partition,
246                                      PartitionTable::Flags flags )
247{
248    auto deviceInfo = infoForDevice( device );
249    Q_ASSERT( deviceInfo );
250
251    PartitionModel::ResetHelper helper( partitionModelForDevice( device ) );
252    CreatePartitionJob* job = new CreatePartitionJob( device, partition );
253    job->updatePreview();
254
255    deviceInfo->jobs << Calamares::job_ptr( job );
256
257    if ( flags != PartitionTable::FlagNone )
258    {
259        SetPartFlagsJob* fJob = new SetPartFlagsJob( device, partition, flags );
260        deviceInfo->jobs << Calamares::job_ptr( fJob );
261        PartitionInfo::setFlags( partition, flags );
262    }
263
264    refresh();
265}
266
267void
268PartitionCoreModule::deletePartition( Device* device, Partition* partition )
269{
270    auto deviceInfo = infoForDevice( device );
271    Q_ASSERT( deviceInfo );
272
273    PartitionModel::ResetHelper helper( partitionModelForDevice( device ) );
274
275    if ( partition->roles().has( PartitionRole::Extended ) )
276    {
277        // Delete all logical partitions first
278        // I am not sure if we can iterate on Partition::children() while
279        // deleting them, so let's play it safe and keep our own list.
280        QList< Partition* > lst;
281        for ( auto childPartition : partition->children() )
282            if ( !KPMHelpers::isPartitionFreeSpace( childPartition ) )
283                lst << childPartition;
284
285        for ( auto childPartition : lst )
286            deletePartition( device, childPartition );
287    }
288
289    QList< Calamares::job_ptr >& jobs = deviceInfo->jobs;
290    if ( partition->state() == Partition::StateNew )
291    {
292        // First remove matching SetPartFlagsJobs
293        for ( auto it = jobs.begin(); it != jobs.end(); )
294        {
295            SetPartFlagsJob* job = qobject_cast< SetPartFlagsJob* >( it->data() );
296            if ( job && job->partition() == partition )
297                it = jobs.erase( it );
298            else
299                ++it;
300        }
301
302        // Find matching CreatePartitionJob
303        auto it = std::find_if( jobs.begin(), jobs.end(), [ partition ]( Calamares::job_ptr job )
304        {
305            CreatePartitionJob* createJob = qobject_cast< CreatePartitionJob* >( job.data() );
306            return createJob && createJob->partition() == partition;
307        } );
308        if ( it == jobs.end() )
309        {
310            cDebug() << "Failed to find a CreatePartitionJob matching the partition to remove";
311            return;
312        }
313        // Remove it
314        if ( ! partition->parent()->remove( partition ) )
315        {
316            cDebug() << "Failed to remove partition from preview";
317            return;
318        }
319
320        device->partitionTable()->updateUnallocated( *device );
321        jobs.erase( it );
322        // The partition is no longer referenced by either a job or the device
323        // partition list, so we have to delete it
324        delete partition;
325    }
326    else
327    {
328        // Remove any PartitionJob on this partition
329        for ( auto it = jobs.begin(); it != jobs.end(); )
330        {
331            PartitionJob* job = qobject_cast< PartitionJob* >( it->data() );
332            if ( job && job->partition() == partition )
333                it = jobs.erase( it );
334            else
335                ++it;
336        }
337        DeletePartitionJob* job = new DeletePartitionJob( device, partition );
338        job->updatePreview();
339        jobs << Calamares::job_ptr( job );
340    }
341
342    refresh();
343}
344
345void
346PartitionCoreModule::formatPartition( Device* device, Partition* partition )
347{
348    auto deviceInfo = infoForDevice( device );
349    Q_ASSERT( deviceInfo );
350    PartitionModel::ResetHelper helper( partitionModelForDevice( device ) );
351
352    FormatPartitionJob* job = new FormatPartitionJob( device, partition );
353    deviceInfo->jobs << Calamares::job_ptr( job );
354
355    refresh();
356}
357
358void
359PartitionCoreModule::resizePartition( Device* device,
360                                      Partition* partition,
361                                      qint64 first,
362                                      qint64 last )
363{
364    auto deviceInfo = infoForDevice( device );
365    Q_ASSERT( deviceInfo );
366    PartitionModel::ResetHelper helper( partitionModelForDevice( device ) );
367
368    ResizePartitionJob* job = new ResizePartitionJob( device, partition, first, last );
369    job->updatePreview();
370    deviceInfo->jobs << Calamares::job_ptr( job );
371
372    refresh();
373}
374
375void
376PartitionCoreModule::setPartitionFlags( Device* device,
377                                        Partition* partition,
378                                        PartitionTable::Flags flags )
379{
380    auto deviceInfo = infoForDevice( device );
381    Q_ASSERT( deviceInfo );
382    PartitionModel::ResetHelper( partitionModelForDevice( device ) );
383
384    SetPartFlagsJob* job = new SetPartFlagsJob( device, partition, flags );
385    deviceInfo->jobs << Calamares::job_ptr( job );
386    PartitionInfo::setFlags( partition, flags );
387
388    refresh();
389}
390
391QList< Calamares::job_ptr >
392PartitionCoreModule::jobs() const
393{
394    QList< Calamares::job_ptr > lst;
395    QList< Device* > devices;
396
397    lst << Calamares::job_ptr( new ClearTempMountsJob() );
398
399    for ( auto info : m_deviceInfos )
400    {
401        if ( info->isDirty() )
402            lst << Calamares::job_ptr( new ClearMountsJob( info->device.data() ) );
403    }
404
405    for ( auto info : m_deviceInfos )
406    {
407        lst << info->jobs;
408        devices << info->device.data();
409    }
410    cDebug() << "Creating FillGlobalStorageJob with bootLoader path" << m_bootLoaderInstallPath;
411    lst << Calamares::job_ptr( new FillGlobalStorageJob( devices, m_bootLoaderInstallPath ) );
412
413
414    QStringList jobsDebug;
415    foreach ( auto job, lst )
416        jobsDebug.append( job->prettyName() );
417
418    cDebug() << "PartitionCodeModule has been asked for jobs. About to return:"
419             << jobsDebug.join( "\n" );
420
421    return lst;
422}
423
424bool
425PartitionCoreModule::hasRootMountPoint() const
426{
427    return m_hasRootMountPoint;
428}
429
430QList< Partition* >
431PartitionCoreModule::efiSystemPartitions() const
432{
433    return m_efiSystemPartitions;
434}
435
436void
437PartitionCoreModule::dumpQueue() const
438{
439    cDebug() << "# Queue:";
440    for ( auto info : m_deviceInfos )
441    {
442        cDebug() << "## Device:" << info->device->name();
443        for ( auto job : info->jobs )
444            cDebug() << "-" << job->prettyName();
445    }
446}
447
448
449const OsproberEntryList
450PartitionCoreModule::osproberEntries() const
451{
452    return m_osproberLines;
453}
454
455void
456PartitionCoreModule::refreshPartition( Device* device, Partition* )
457{
458    // Keep it simple for now: reset the model. This can be improved to cause
459    // the model to emit dataChanged() for the affected row instead, avoiding
460    // the loss of the current selection.
461    auto model = partitionModelForDevice( device );
462    Q_ASSERT( model );
463    PartitionModel::ResetHelper helper( model );
464
465    refresh();
466}
467
468void
469PartitionCoreModule::refresh()
470{
471    updateHasRootMountPoint();
472    updateIsDirty();
473    m_bootLoaderModel->update();
474
475    //FIXME: this should be removed in favor of
476    //       proper KPM support for EFI
477    if ( PartUtils::isEfiSystem() )
478        scanForEfiSystemPartitions();
479}
480
481void PartitionCoreModule::updateHasRootMountPoint()
482{
483    bool oldValue = m_hasRootMountPoint;
484    m_hasRootMountPoint = findPartitionByMountPoint( "/" );
485
486    if ( oldValue != m_hasRootMountPoint )
487        hasRootMountPointChanged( m_hasRootMountPoint );
488}
489
490void
491PartitionCoreModule::updateIsDirty()
492{
493    bool oldValue = m_isDirty;
494    m_isDirty = false;
495    for ( auto info : m_deviceInfos )
496        if ( info->isDirty() )
497        {
498            m_isDirty = true;
499            break;
500        }
501    if ( oldValue != m_isDirty )
502        isDirtyChanged( m_isDirty );
503}
504
505void
506PartitionCoreModule::scanForEfiSystemPartitions()
507{
508    m_efiSystemPartitions.clear();
509
510    QList< Device* > devices;
511    for ( int row = 0; row < deviceModel()->rowCount(); ++row )
512    {
513        Device* device = deviceModel()->deviceForIndex(
514                             deviceModel()->index( row ) );
515        devices.append( device );
516    }
517
518    QList< Partition* > efiSystemPartitions =
519        KPMHelpers::findPartitions( devices, PartUtils::isEfiBootable );
520
521    if ( efiSystemPartitions.isEmpty() )
522        cWarning() << "system is EFI but no EFI system partitions found.";
523
524    m_efiSystemPartitions = efiSystemPartitions;
525}
526
527PartitionCoreModule::DeviceInfo*
528PartitionCoreModule::infoForDevice( const Device* device ) const
529{
530    for ( auto it = m_deviceInfos.constBegin();
531            it != m_deviceInfos.constEnd(); ++it )
532    {
533        if ( ( *it )->device.data() == device )
534            return *it;
535        if ( ( *it )->immutableDevice.data() == device )
536            return *it;
537    }
538    return nullptr;
539}
540
541Partition*
542PartitionCoreModule::findPartitionByMountPoint( const QString& mountPoint ) const
543{
544    for ( auto deviceInfo : m_deviceInfos )
545    {
546        Device* device = deviceInfo->device.data();
547        for ( auto it = PartitionIterator::begin( device ); it != PartitionIterator::end( device ); ++it )
548            if ( PartitionInfo::mountPoint( *it ) == mountPoint )
549                return *it;
550    }
551    return nullptr;
552}
553
554void
555PartitionCoreModule::setBootLoaderInstallPath( const QString& path )
556{
557    cDebug() << "PCM::setBootLoaderInstallPath" << path;
558    m_bootLoaderInstallPath = path;
559}
560
561void
562PartitionCoreModule::revert()
563{
564    QMutexLocker locker( &m_revertMutex );
565    qDeleteAll( m_deviceInfos );
566    m_deviceInfos.clear();
567    doInit();
568    updateIsDirty();
569    emit reverted();
570}
571
572
573void
574PartitionCoreModule::revertAllDevices()
575{
576    foreach ( DeviceInfo* devInfo, m_deviceInfos )
577        revertDevice( devInfo->device.data() );
578    refresh();
579}
580
581
582void
583PartitionCoreModule::revertDevice( Device* dev )
584{
585    QMutexLocker locker( &m_revertMutex );
586    DeviceInfo* devInfo = infoForDevice( dev );
587    if ( !devInfo )
588        return;
589    devInfo->forgetChanges();
590    CoreBackend* backend = CoreBackendManager::self()->backend();
591    Device* newDev = backend->scanDevice( devInfo->device->deviceNode() );
592    devInfo->device.reset( newDev );
593    devInfo->partitionModel->init( newDev, m_osproberLines );
594
595    m_deviceModel->swapDevice( dev, newDev );
596
597    QList< Device* > devices;
598    foreach ( auto info, m_deviceInfos )
599        devices.append( info->device.data() );
600
601    m_bootLoaderModel->init( devices );
602
603    refresh();
604    emit deviceReverted( newDev );
605}
606
607
608void
609PartitionCoreModule::asyncRevertDevice( Device* dev, std::function< void() > callback )
610{
611    QFutureWatcher< void >* watcher = new QFutureWatcher< void >();
612    connect( watcher, &QFutureWatcher< void >::finished,
613             this, [ watcher, callback ]
614    {
615        callback();
616        watcher->deleteLater();
617    } );
618
619    QFuture< void > future = QtConcurrent::run( this, &PartitionCoreModule::revertDevice, dev );
620    watcher->setFuture( future );
621}
622
623
624void
625PartitionCoreModule::clearJobs()
626{
627    foreach ( DeviceInfo* deviceInfo, m_deviceInfos )
628        deviceInfo->forgetChanges();
629    updateIsDirty();
630}
631
632
633bool
634PartitionCoreModule::isDirty()
635{
636    return m_isDirty;
637}
638
639QList< PartitionCoreModule::SummaryInfo >
640PartitionCoreModule::createSummaryInfo() const
641{
642    QList< SummaryInfo > lst;
643    for ( auto deviceInfo : m_deviceInfos )
644    {
645        if ( !deviceInfo->isDirty() )
646            continue;
647        SummaryInfo summaryInfo;
648        summaryInfo.deviceName = deviceInfo->device->name();
649        summaryInfo.deviceNode = deviceInfo->device->deviceNode();
650
651        Device* deviceBefore = deviceInfo->immutableDevice.data();
652        summaryInfo.partitionModelBefore = new PartitionModel;
653        summaryInfo.partitionModelBefore->init( deviceBefore, m_osproberLines );
654        // Make deviceBefore a child of partitionModelBefore so that it is not
655        // leaked (as long as partitionModelBefore is deleted)
656        deviceBefore->setParent( summaryInfo.partitionModelBefore );
657
658        summaryInfo.partitionModelAfter = new PartitionModel;
659        summaryInfo.partitionModelAfter->init( deviceInfo->device.data(), m_osproberLines );
660
661        lst << summaryInfo;
662    }
663    return lst;
664}
Note: See TracBrowser for help on using the repository browser.