calamares/src/modules/partition/core/PartUtils.cpp
Arnaud Ferraris 74ead4c7ba [partition] improve filesystem search operation
Due to changes to the FileSsytem::typeForName() function, more
processing is needed to deal with locales and different cases.
This is done by refactoring the findFS() function, initially located in
the PartitionViewStep class, and making it available to the whole module.

Additionnally, more checks have been implemented regarding the use of
global storage in the PartitionLayout class, and the filesystem types
now use the correct FileSystem::Type, as requested.

Signed-off-by: Arnaud Ferraris <arnaud.ferraris@collabora.com>
2019-02-22 18:42:16 +01:00

490 lines
16 KiB
C++

/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Copyright 2015-2016, Teo Mrnjavac <teo@kde.org>
* Copyright 2018, Adriaan de Groot <groot@kde.org>
*
* Calamares 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.
*
* Calamares 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 Calamares. If not, see <http://www.gnu.org/licenses/>.
*/
#include "PartUtils.h"
#include "PartitionCoreModule.h"
#include "core/DeviceModel.h"
#include "core/KPMHelpers.h"
#include "core/PartitionInfo.h"
#include "core/PartitionIterator.h"
#include <kpmcore/backend/corebackend.h>
#include <kpmcore/backend/corebackendmanager.h>
#include <kpmcore/core/device.h>
#include <kpmcore/core/partition.h>
#include <utils/CalamaresUtilsSystem.h>
#include <utils/Logger.h>
#include <JobQueue.h>
#include <GlobalStorage.h>
#include <QProcess>
#include <QTemporaryDir>
namespace PartUtils
{
bool
canBeReplaced( Partition* candidate )
{
if ( !candidate )
return false;
if ( candidate->isMounted() )
return false;
bool ok = false;
double requiredStorageGB = Calamares::JobQueue::instance()
->globalStorage()
->value( "requiredStorageGB" )
.toDouble( &ok );
qint64 availableStorageB = candidate->capacity();
qint64 requiredStorageB = ( requiredStorageGB + 0.5 ) * 1024 * 1024 * 1024;
cDebug() << "Required storage B:" << requiredStorageB
<< QString( "(%1GB)" ).arg( requiredStorageB / 1024 / 1024 / 1024 );
cDebug() << "Storage capacity B:" << availableStorageB
<< QString( "(%1GB)" ).arg( availableStorageB / 1024 / 1024 / 1024 )
<< "for" << candidate->partitionPath() << " length:" << candidate->length();
if ( ok &&
availableStorageB > requiredStorageB )
{
cDebug() << "Partition" << candidate->partitionPath() << "authorized for replace install.";
return true;
}
return false;
}
bool
canBeResized( Partition* candidate )
{
if ( !candidate )
{
cDebug() << "Partition* is NULL";
return false;
}
cDebug() << "Checking if" << candidate->partitionPath() << "can be resized.";
if ( !candidate->fileSystem().supportGrow() ||
!candidate->fileSystem().supportShrink() )
{
cDebug() << " .. filesystem" << candidate->fileSystem().name()
<< "does not support resize.";
return false;
}
if ( KPMHelpers::isPartitionFreeSpace( candidate ) )
{
cDebug() << " .. partition is free space";
return false;
}
if ( candidate->isMounted() )
{
cDebug() << " .. partition is mounted";
return false;
}
if ( candidate->roles().has( PartitionRole::Primary ) )
{
PartitionTable* table = dynamic_cast< PartitionTable* >( candidate->parent() );
if ( !table )
{
cDebug() << " .. no partition table found";
return false;
}
if ( table->numPrimaries() >= table->maxPrimaries() )
{
cDebug() << " .. partition table already has"
<< table->maxPrimaries() << "primary partitions.";
return false;
}
}
bool ok = false;
double requiredStorageGB = Calamares::JobQueue::instance()
->globalStorage()
->value( "requiredStorageGB" )
.toDouble( &ok );
double advisedStorageGB = requiredStorageGB + 0.5 + 2.0;
qint64 availableStorageB = candidate->available();
// We require a little more for partitioning overhead and swap file
// TODO: maybe make this configurable?
qint64 advisedStorageB = advisedStorageGB * 1024 * 1024 * 1024;
cDebug() << "Required storage B:" << advisedStorageB
<< QString( "(%1GB)" ).arg( advisedStorageGB );
cDebug() << "Available storage B:" << availableStorageB
<< QString( "(%1GB)" ).arg( availableStorageB / 1024 / 1024 / 1024 )
<< "for" << candidate->partitionPath() << " length:" << candidate->length()
<< " sectorsUsed:" << candidate->sectorsUsed() << " fsType:" << candidate->fileSystem().name();
if ( ok &&
availableStorageB > advisedStorageB )
{
cDebug() << "Partition" << candidate->partitionPath() << "authorized for resize + autopartition install.";
return true;
}
return false;
}
bool
canBeResized( PartitionCoreModule* core, const QString& partitionPath )
{
//FIXME: check for max partitions count on DOS MBR
cDebug() << "Checking if" << partitionPath << "can be resized.";
QString partitionWithOs = partitionPath;
if ( partitionWithOs.startsWith( "/dev/" ) )
{
DeviceModel* dm = core->deviceModel();
for ( int i = 0; i < dm->rowCount(); ++i )
{
Device* dev = dm->deviceForIndex( dm->index( i ) );
Partition* candidate = KPMHelpers::findPartitionByPath( { dev }, partitionWithOs );
if ( candidate )
{
cDebug() << " .. found Partition* for" << partitionWithOs;
return canBeResized( candidate );
}
}
cDebug() << " .. no Partition* found for" << partitionWithOs;
}
cDebug() << "Partition" << partitionWithOs << "CANNOT BE RESIZED FOR AUTOINSTALL.";
return false;
}
static FstabEntryList
lookForFstabEntries( const QString& partitionPath )
{
QStringList mountOptions{ "ro" };
auto r = CalamaresUtils::System::runCommand(
CalamaresUtils::System::RunLocation::RunInHost,
{ "blkid", "-s", "TYPE", "-o", "value", partitionPath }
);
if ( r.getExitCode() )
cWarning() << "blkid on" << partitionPath << "failed.";
else
{
QString fstype = r.getOutput().trimmed();
if ( ( fstype == "ext3" ) || ( fstype == "ext4" ) )
mountOptions.append( "noload" );
}
cDebug() << "Checking device" << partitionPath
<< "for fstab (fs=" << r.getOutput() << ')';
FstabEntryList fstabEntries;
QTemporaryDir mountsDir;
mountsDir.setAutoRemove( false );
int exit = QProcess::execute( "mount", { "-o", mountOptions.join(','), partitionPath, mountsDir.path() } );
if ( !exit ) // if all is well
{
QFile fstabFile( mountsDir.path() + "/etc/fstab" );
cDebug() << " .. reading" << fstabFile.fileName();
if ( fstabFile.open( QIODevice::ReadOnly | QIODevice::Text ) )
{
const QStringList fstabLines = QString::fromLocal8Bit( fstabFile.readAll() )
.split( '\n' );
for ( const QString& rawLine : fstabLines )
fstabEntries.append( FstabEntry::fromEtcFstab( rawLine ) );
fstabFile.close();
cDebug() << " .. got" << fstabEntries.count() << "lines.";
std::remove_if( fstabEntries.begin(), fstabEntries.end(), [](const FstabEntry& x) { return !x.isValid(); } );
cDebug() << " .. got" << fstabEntries.count() << "fstab entries.";
}
else
cWarning() << "Could not read fstab from mounted fs";
if ( QProcess::execute( "umount", { "-R", mountsDir.path() } ) )
cWarning() << "Could not unmount" << mountsDir.path();
}
else
cWarning() << "Could not mount existing fs";
return fstabEntries;
}
static QString
findPartitionPathForMountPoint( const FstabEntryList& fstab,
const QString& mountPoint )
{
if ( fstab.isEmpty() )
return QString();
for ( const FstabEntry& entry : fstab )
{
if ( entry.mountPoint == mountPoint )
{
QProcess readlink;
QString partPath;
if ( entry.partitionNode.startsWith( "/dev" ) ) // plain dev node
{
partPath = entry.partitionNode;
}
else if ( entry.partitionNode.startsWith( "LABEL=" ) )
{
partPath = entry.partitionNode.mid( 6 );
partPath.remove( "\"" );
partPath.replace( "\\040", "\\ " );
partPath.prepend( "/dev/disk/by-label/" );
}
else if ( entry.partitionNode.startsWith( "UUID=" ) )
{
partPath = entry.partitionNode.mid( 5 );
partPath.remove( "\"" );
partPath = partPath.toLower();
partPath.prepend( "/dev/disk/by-uuid/" );
}
else if ( entry.partitionNode.startsWith( "PARTLABEL=" ) )
{
partPath = entry.partitionNode.mid( 10 );
partPath.remove( "\"" );
partPath.replace( "\\040", "\\ " );
partPath.prepend( "/dev/disk/by-partlabel/" );
}
else if ( entry.partitionNode.startsWith( "PARTUUID=" ) )
{
partPath = entry.partitionNode.mid( 9 );
partPath.remove( "\"" );
partPath = partPath.toLower();
partPath.prepend( "/dev/disk/by-partuuid/" );
}
// At this point we either have /dev/sda1, or /dev/disk/by-something/...
if ( partPath.startsWith( "/dev/disk/by-" ) ) // we got a fancy node
{
readlink.start( "readlink", { "-en", partPath });
if ( !readlink.waitForStarted( 1000 ) )
return QString();
if ( !readlink.waitForFinished( 1000 ) )
return QString();
if ( readlink.exitCode() != 0 || readlink.exitStatus() != QProcess::NormalExit )
return QString();
partPath = QString::fromLocal8Bit(
readlink.readAllStandardOutput() ).trimmed();
}
return partPath;
}
}
return QString();
}
OsproberEntryList
runOsprober( PartitionCoreModule* core )
{
QString osproberOutput;
QProcess osprober;
osprober.setProgram( "os-prober" );
osprober.setProcessChannelMode( QProcess::SeparateChannels );
osprober.start();
if ( !osprober.waitForStarted() )
{
cError() << "os-prober cannot start.";
}
else if ( !osprober.waitForFinished( 60000 ) )
{
cError() << "os-prober timed out.";
}
else
{
osproberOutput.append(
QString::fromLocal8Bit(
osprober.readAllStandardOutput() ).trimmed() );
}
QStringList osproberCleanLines;
OsproberEntryList osproberEntries;
const auto lines = osproberOutput.split( '\n' );
for ( const QString& line : lines )
{
if ( !line.simplified().isEmpty() )
{
QStringList lineColumns = line.split( ':' );
QString prettyName;
if ( !lineColumns.value( 1 ).simplified().isEmpty() )
prettyName = lineColumns.value( 1 ).simplified();
else if ( !lineColumns.value( 2 ).simplified().isEmpty() )
prettyName = lineColumns.value( 2 ).simplified();
QString path = lineColumns.value( 0 ).simplified();
if ( !path.startsWith( "/dev/" ) ) //basic sanity check
continue;
FstabEntryList fstabEntries = lookForFstabEntries( path );
QString homePath = findPartitionPathForMountPoint( fstabEntries, "/home" );
osproberEntries.append( { prettyName,
path,
QString(),
canBeResized( core, path ),
lineColumns,
fstabEntries,
homePath } );
osproberCleanLines.append( line );
}
}
if ( osproberCleanLines.count() > 0 )
cDebug() << "os-prober lines after cleanup:" << Logger::DebugList( osproberCleanLines );
else
cDebug() << "os-prober gave no output.";
Calamares::JobQueue::instance()->globalStorage()->insert( "osproberLines", osproberCleanLines );
return osproberEntries;
}
bool
isEfiSystem()
{
return QDir( "/sys/firmware/efi/efivars" ).exists();
}
bool
isEfiBootable( const Partition* candidate )
{
cDebug() << "Check EFI bootable" << candidate->partitionPath() << candidate->devicePath();
cDebug() << " .. flags" << candidate->activeFlags();
auto flags = PartitionInfo::flags( candidate );
/* If bit 17 is set, old-style Esp flag, it's OK */
if ( flags.testFlag( PartitionTable::FlagEsp ) )
return true;
/* Otherwise, if it's a GPT table, Boot (bit 0) is the same as Esp */
const PartitionNode* root = candidate;
while ( root && !root->isRoot() )
{
root = root->parent();
cDebug() << " .. moved towards root" << (void *)root;
}
// Strange case: no root found, no partition table node?
if ( !root )
return false;
const PartitionTable* table = dynamic_cast<const PartitionTable*>( root );
cDebug() << " .. partition table" << (void *)table << "type" << ( table ? table->type() : PartitionTable::TableType::unknownTableType );
return table && ( table->type() == PartitionTable::TableType::gpt ) &&
flags.testFlag( PartitionTable::FlagBoot );
}
QString
findFS( QString fsName, FileSystem::Type* fsType )
{
QStringList fsLanguage { QLatin1Literal( "C" ) }; // Required language list to turn off localization
if ( fsName.isEmpty() )
fsName = QStringLiteral( "ext4" );
FileSystem::Type tmpType = FileSystem::typeForName( fsName, fsLanguage );
if ( tmpType != FileSystem::Unknown )
{
cDebug() << "Found filesystem" << fsName;
if ( fsType )
*fsType = tmpType;
return fsName;
}
// Second pass: try case-insensitive
const auto fstypes = FileSystem::types();
for ( FileSystem::Type t : fstypes )
{
if ( 0 == QString::compare( fsName, FileSystem::nameForType( t, fsLanguage ), Qt::CaseInsensitive ) )
{
QString fsRealName = FileSystem::nameForType( t, fsLanguage );
cDebug() << "Filesystem name" << fsName << "translated to" << fsRealName;
if ( fsType )
*fsType = t;
return fsRealName;
}
}
cDebug() << "Filesystem" << fsName << "not found, using ext4";
fsName = QStringLiteral( "ext4" );
// fsType can be used to check whether fsName was a valid filesystem.
if (fsType)
*fsType = FileSystem::Unknown;
#ifdef DEBUG_FILESYSTEMS
// This bit is for distro's debugging their settings, and shows
// all the strings that KPMCore is matching against for FS type.
{
Logger::CDebug d;
using TR = Logger::DebugRow< int, QString >;
const auto fstypes = FileSystem::types();
d << "Available types (" << fstypes.count() << ')';
for ( FileSystem::Type t : fstypes )
d << TR( static_cast<int>( t ), FileSystem::nameForType( t, fsLanguage ) );
}
#endif
return fsName;
}
} // nmamespace PartUtils
/* Implementation of methods for FstabEntry, from OsproberEntry.h */
bool
FstabEntry::isValid() const
{
return !partitionNode.isEmpty() && !mountPoint.isEmpty() && !fsType.isEmpty();
}
FstabEntry
FstabEntry::fromEtcFstab( const QString& rawLine )
{
QString line = rawLine.simplified();
if ( line.startsWith( '#' ) )
return FstabEntry{ QString(), QString(), QString(), QString(), 0, 0 };
QStringList splitLine = line.split( ' ' );
if ( splitLine.length() != 6 )
return FstabEntry{ QString(), QString(), QString(), QString(), 0, 0 };
return FstabEntry{ splitLine.at( 0 ), // path, or UUID, or LABEL, etc.
splitLine.at( 1 ), // mount point
splitLine.at( 2 ), // fs type
splitLine.at( 3 ), // options
splitLine.at( 4 ).toInt(), //dump
splitLine.at( 5 ).toInt() //pass
};
}