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

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

sync with github

File size: 12.1 KB
Line 
1/* === This file is part of Calamares - <https://github.com/calamares> ===
2 *
3 *   Copyright 2015-2016, Teo Mrnjavac <teo@kde.org>
4 *   Copyright 2018, Adriaan de Groot <groot@kde.org>
5 *
6 *   Calamares is free software: you can redistribute it and/or modify
7 *   it under the terms of the GNU General Public License as published by
8 *   the Free Software Foundation, either version 3 of the License, or
9 *   (at your option) any later version.
10 *
11 *   Calamares is distributed in the hope that it will be useful,
12 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 *   GNU General Public License for more details.
15 *
16 *   You should have received a copy of the GNU General Public License
17 *   along with Calamares. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include "PartUtils.h"
21
22#include "PartitionCoreModule.h"
23
24#include "core/DeviceModel.h"
25#include "core/KPMHelpers.h"
26#include "core/PartitionInfo.h"
27#include "core/PartitionIterator.h"
28
29#include <kpmcore/backend/corebackend.h>
30#include <kpmcore/backend/corebackendmanager.h>
31#include <kpmcore/core/device.h>
32#include <kpmcore/core/partition.h>
33
34#include <utils/Logger.h>
35#include <JobQueue.h>
36#include <GlobalStorage.h>
37
38#include <QProcess>
39#include <QTemporaryDir>
40
41namespace PartUtils
42{
43
44bool
45canBeReplaced( Partition* candidate )
46{
47    if ( !candidate )
48        return false;
49
50    if ( candidate->isMounted() )
51        return false;
52
53    bool ok = false;
54    double requiredStorageGB = Calamares::JobQueue::instance()
55                                    ->globalStorage()
56                                    ->value( "requiredStorageGB" )
57                                    .toDouble( &ok );
58
59    qint64 availableStorageB = candidate->capacity();
60    qint64 requiredStorageB = ( requiredStorageGB + 0.5 ) * 1024 * 1024 * 1024;
61    cDebug() << "Required  storage B:" << requiredStorageB
62             << QString( "(%1GB)" ).arg( requiredStorageB / 1024 / 1024 / 1024 );
63    cDebug() << "Storage capacity  B:" << availableStorageB
64             << QString( "(%1GB)" ).arg( availableStorageB / 1024 / 1024 / 1024 )
65             << "for" << candidate->partitionPath() << "   length:" << candidate->length();
66
67    if ( ok &&
68         availableStorageB > requiredStorageB )
69    {
70        cDebug() << "Partition" << candidate->partitionPath() << "authorized for replace install.";
71
72        return true;
73    }
74    return false;
75}
76
77
78bool
79canBeResized( Partition* candidate )
80{
81    if ( !candidate )
82        return false;
83
84    if ( !candidate->fileSystem().supportGrow() ||
85         !candidate->fileSystem().supportShrink() )
86        return false;
87
88    if ( KPMHelpers::isPartitionFreeSpace( candidate ) )
89        return false;
90
91    if ( candidate->isMounted() )
92        return false;
93
94    if ( candidate->roles().has( PartitionRole::Primary ) )
95    {
96        PartitionTable* table = dynamic_cast< PartitionTable* >( candidate->parent() );
97        if ( !table )
98            return false;
99
100        if ( table->numPrimaries() >= table->maxPrimaries() )
101            return false;
102    }
103
104    bool ok = false;
105    double requiredStorageGB = Calamares::JobQueue::instance()
106                                    ->globalStorage()
107                                    ->value( "requiredStorageGB" )
108                                    .toDouble( &ok );
109    double advisedStorageGB = requiredStorageGB + 0.5 + 2.0;
110
111    qint64 availableStorageB = candidate->available();
112
113    // We require a little more for partitioning overhead and swap file
114    // TODO: maybe make this configurable?
115    qint64 advisedStorageB = advisedStorageGB * 1024 * 1024 * 1024;
116    cDebug() << "Required  storage B:" << advisedStorageB
117             << QString( "(%1GB)" ).arg( advisedStorageGB );
118    cDebug() << "Available storage B:" << availableStorageB
119             << QString( "(%1GB)" ).arg( availableStorageB / 1024 / 1024 / 1024 )
120             << "for" << candidate->partitionPath() << "   length:" << candidate->length()
121             << "   sectorsUsed:" << candidate->sectorsUsed() << "   fsType:" << candidate->fileSystem().name();
122
123    if ( ok &&
124         availableStorageB > advisedStorageB )
125    {
126        cDebug() << "Partition" << candidate->partitionPath() << "authorized for resize + autopartition install.";
127
128        return true;
129    }
130    return false;
131}
132
133
134bool
135canBeResized( PartitionCoreModule* core, const QString& partitionPath )
136{
137    //FIXME: check for max partitions count on DOS MBR
138    cDebug() << "checking if" << partitionPath << "can be resized.";
139    QString partitionWithOs = partitionPath;
140    if ( partitionWithOs.startsWith( "/dev/" ) )
141    {
142        cDebug() << partitionWithOs << "seems like a good path";
143        DeviceModel* dm = core->deviceModel();
144        for ( int i = 0; i < dm->rowCount(); ++i )
145        {
146            Device* dev = dm->deviceForIndex( dm->index( i ) );
147            Partition* candidate = KPMHelpers::findPartitionByPath( { dev }, partitionWithOs );
148            if ( candidate )
149            {
150                cDebug() << "found Partition* for" << partitionWithOs;
151                return canBeResized( candidate );
152            }
153        }
154    }
155
156    cDebug() << "Partition" << partitionWithOs << "CANNOT BE RESIZED FOR AUTOINSTALL.";
157    return false;
158}
159
160
161static FstabEntryList
162lookForFstabEntries( const QString& partitionPath )
163{
164    FstabEntryList fstabEntries;
165    QTemporaryDir mountsDir;
166
167    int exit = QProcess::execute( "mount", { partitionPath, mountsDir.path() } );
168    if ( !exit ) // if all is well
169    {
170        QFile fstabFile( mountsDir.path() + "/etc/fstab" );
171        if ( fstabFile.open( QIODevice::ReadOnly | QIODevice::Text ) )
172        {
173            const QStringList fstabLines = QString::fromLocal8Bit( fstabFile.readAll() )
174                                     .split( '\n' );
175
176            for ( const QString& rawLine : fstabLines )
177            {
178                QString line = rawLine.simplified();
179                if ( line.startsWith( '#' ) )
180                    continue;
181
182                QStringList splitLine = line.split( ' ' );
183                if ( splitLine.length() != 6 )
184                    continue;
185
186                fstabEntries.append( { splitLine.at( 0 ), // path, or UUID, or LABEL, etc.
187                                       splitLine.at( 1 ), // mount point
188                                       splitLine.at( 2 ), // fs type
189                                       splitLine.at( 3 ), // options
190                                       splitLine.at( 4 ).toInt(), //dump
191                                       splitLine.at( 5 ).toInt()  //pass
192                                     } );
193            }
194
195            fstabFile.close();
196        }
197
198        QProcess::execute( "umount", { "-R", mountsDir.path() } );
199    }
200
201    return fstabEntries;
202}
203
204
205static QString
206findPartitionPathForMountPoint( const FstabEntryList& fstab,
207                                const QString& mountPoint )
208{
209    if ( fstab.isEmpty() )
210        return QString();
211
212    for ( const FstabEntry& entry : fstab )
213    {
214        if ( entry.mountPoint == mountPoint )
215        {
216            QProcess readlink;
217            QString partPath;
218
219            if ( entry.partitionNode.startsWith( "/dev" ) ) // plain dev node
220            {
221                partPath = entry.partitionNode;
222            }
223            else if ( entry.partitionNode.startsWith( "LABEL=" ) )
224            {
225                partPath = entry.partitionNode.mid( 6 );
226                partPath.remove( "\"" );
227                partPath.replace( "\\040", "\\ " );
228                partPath.prepend( "/dev/disk/by-label/" );
229            }
230            else if ( entry.partitionNode.startsWith( "UUID=" ) )
231            {
232                partPath = entry.partitionNode.mid( 5 );
233                partPath.remove( "\"" );
234                partPath = partPath.toLower();
235                partPath.prepend( "/dev/disk/by-uuid/" );
236            }
237            else if ( entry.partitionNode.startsWith( "PARTLABEL=" ) )
238            {
239                partPath = entry.partitionNode.mid( 10 );
240                partPath.remove( "\"" );
241                partPath.replace( "\\040", "\\ " );
242                partPath.prepend( "/dev/disk/by-partlabel/" );
243            }
244            else if ( entry.partitionNode.startsWith( "PARTUUID=" ) )
245            {
246                partPath = entry.partitionNode.mid( 9 );
247                partPath.remove( "\"" );
248                partPath = partPath.toLower();
249                partPath.prepend( "/dev/disk/by-partuuid/" );
250            }
251
252            // At this point we either have /dev/sda1, or /dev/disk/by-something/...
253
254            if ( partPath.startsWith( "/dev/disk/by-" ) ) // we got a fancy node
255            {
256                readlink.start( "readlink", { "-en", partPath });
257                if ( !readlink.waitForStarted( 1000 ) )
258                    return QString();
259                if ( !readlink.waitForFinished( 1000 ) )
260                    return QString();
261                if ( readlink.exitCode() != 0 || readlink.exitStatus() != QProcess::NormalExit )
262                    return QString();
263                partPath = QString::fromLocal8Bit(
264                               readlink.readAllStandardOutput() ).trimmed();
265            }
266
267            return partPath;
268        }
269    }
270
271    return QString();
272}
273
274
275OsproberEntryList
276runOsprober( PartitionCoreModule* core )
277{
278    QString osproberOutput;
279    QProcess osprober;
280    osprober.setProgram( "os-prober" );
281    osprober.setProcessChannelMode( QProcess::SeparateChannels );
282    osprober.start();
283    if ( !osprober.waitForStarted() )
284    {
285        cError() << "os-prober cannot start.";
286    }
287    else if ( !osprober.waitForFinished( 60000 ) )
288    {
289        cError() << "os-prober timed out.";
290    }
291    else
292    {
293        osproberOutput.append(
294            QString::fromLocal8Bit(
295                osprober.readAllStandardOutput() ).trimmed() );
296    }
297
298    QString osProberReport( "Osprober lines, clean:\n" );
299    QStringList osproberCleanLines;
300    OsproberEntryList osproberEntries;
301    const auto lines = osproberOutput.split( '\n' );
302    for ( const QString& line : lines )
303    {
304        if ( !line.simplified().isEmpty() )
305        {
306            QStringList lineColumns = line.split( ':' );
307            QString prettyName;
308            if ( !lineColumns.value( 1 ).simplified().isEmpty() )
309                prettyName = lineColumns.value( 1 ).simplified();
310            else if ( !lineColumns.value( 2 ).simplified().isEmpty() )
311                prettyName = lineColumns.value( 2 ).simplified();
312
313            QString path = lineColumns.value( 0 ).simplified();
314            if ( !path.startsWith( "/dev/" ) ) //basic sanity check
315                continue;
316
317            FstabEntryList fstabEntries = lookForFstabEntries( path );
318            QString homePath = findPartitionPathForMountPoint( fstabEntries, "/home" );
319
320            osproberEntries.append( { prettyName,
321                                      path,
322                                      QString(),
323                                      canBeResized( core, path ),
324                                      lineColumns,
325                                      fstabEntries,
326                                      homePath } );
327            osproberCleanLines.append( line );
328        }
329    }
330    osProberReport.append( osproberCleanLines.join( '\n' ) );
331    cDebug() << osProberReport;
332
333    Calamares::JobQueue::instance()->globalStorage()->insert( "osproberLines", osproberCleanLines );
334
335    return osproberEntries;
336}
337
338bool
339isEfiSystem()
340{
341    return QDir( "/sys/firmware/efi/efivars" ).exists();
342}
343
344bool
345isEfiBootable( const Partition* candidate )
346{
347    auto flags = PartitionInfo::flags( candidate );
348
349    /* If bit 17 is set, old-style Esp flag, it's OK */
350    if ( flags.testFlag( PartitionTable::FlagEsp ) )
351        return true;
352
353
354    /* Otherwise, if it's a GPT table, Boot (bit 0) is the same as Esp */
355    const PartitionNode* root = candidate;
356    while ( root && !root->isRoot() )
357        root = root->parent();
358
359    // Strange case: no root found, no partition table node?
360    if ( !root )
361        return false;
362
363    const PartitionTable* table = dynamic_cast<const PartitionTable*>( root );
364    return table && ( table->type() == PartitionTable::TableType::gpt ) &&
365        flags.testFlag( PartitionTable::FlagBoot );
366}
367
368}  // nmamespace PartUtils
Note: See TracBrowser for help on using the repository browser.