[zfs] Ensure overlapping datasets don't get created and code cleanup

This commit is contained in:
dalto 2021-11-13 13:43:26 -06:00
parent cca38695ed
commit c3524c07ad
2 changed files with 99 additions and 26 deletions

View File

@ -32,6 +32,43 @@ ZfsJob::prettyName() const
return tr( "Create ZFS pools and datasets" ); return tr( "Create ZFS pools and datasets" );
} }
QString
ZfsJob::AlphaNumeric( QString input ) const
{
return input.remove( QRegExp( "[^a-zA-Z\\d\\s]" ) );
}
void
ZfsJob::CollectMountpoints( const QVariantList& partitions )
{
m_mountpoints.empty();
for ( const QVariant& partition : partitions )
{
if ( partition.canConvert( QVariant::Map ) )
{
QString mountpoint = partition.toMap().value( "mountPoint" ).toString();
if ( !mountpoint.isEmpty() )
{
m_mountpoints.append( mountpoint );
}
}
}
}
bool
ZfsJob::IsMountpointOverlapping( const QString& targetMountpoint ) const
{
for ( const QString& mountpoint : m_mountpoints )
{
if ( mountpoint != '/' && targetMountpoint.startsWith( mountpoint ) )
{
return true;
}
}
return false;
}
ZfsResult ZfsResult
ZfsJob::CreateZpool( QString deviceName, QString poolName, QString poolOptions, bool encrypt, QString passphrase ) const ZfsJob::CreateZpool( QString deviceName, QString poolName, QString poolOptions, bool encrypt, QString passphrase ) const
{ {
@ -85,7 +122,7 @@ ZfsJob::CreateZpool( QString deviceName, QString poolName, QString poolOptions,
Calamares::JobResult Calamares::JobResult
ZfsJob::exec() ZfsJob::exec()
{ {
QList< QVariant > partitions; QVariantList partitions;
Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage();
if ( gs && gs->contains( "partitions" ) && gs->value( "partitions" ).canConvert( QVariant::List ) ) if ( gs && gs->contains( "partitions" ) && gs->value( "partitions" ).canConvert( QVariant::List ) )
{ {
@ -132,27 +169,28 @@ ZfsJob::exec()
continue; continue;
} }
// If the partition doesn't have a mountpoint, skip it
QString mountpoint = pMap[ "mountPoint" ].toString(); QString mountpoint = pMap[ "mountPoint" ].toString();
if ( mountpoint.isEmpty() ) if ( mountpoint.isEmpty() )
{ {
continue; continue;
} }
// Build a poolname off the mountpoint, this is not ideal but should work until there is UI built for zfs // Build a poolname off config pool name and the mountpoint, this is not ideal but should work until there is UI built for zfs
QString poolName = m_poolName; QString poolName = m_poolName;
if ( mountpoint != '/' ) if ( mountpoint != '/' )
{ {
QString suffix = mountpoint; poolName += AlphaNumeric( mountpoint );
poolName += suffix.remove( QRegExp( "[^a-zA-Z\\d\\s]" ) );
} }
// Check to ensure the list of zfs info from the partition module is available and convert it to a list
if ( !gs->contains( "zfsInfo" ) && gs->value( "zfsInfo" ).canConvert( QVariant::List ) ) if ( !gs->contains( "zfsInfo" ) && gs->value( "zfsInfo" ).canConvert( QVariant::List ) )
{ {
return Calamares::JobResult::error( tr( "Internal data missing" ), tr( "Failed to create zpool" ) ); return Calamares::JobResult::error( tr( "Internal data missing" ), tr( "Failed to create zpool" ) );
} }
QVariantList zfsInfoList = gs->value( "zfsInfo" ).toList(); QVariantList zfsInfoList = gs->value( "zfsInfo" ).toList();
// Look in the zfs info list to see if this partition should be encrypted
bool encrypt = false; bool encrypt = false;
QString passphrase; QString passphrase;
for ( const QVariant& zfsInfo : qAsConst( zfsInfoList ) ) for ( const QVariant& zfsInfo : qAsConst( zfsInfoList ) )
@ -165,11 +203,11 @@ ZfsJob::exec()
} }
} }
// Create the zpool
ZfsResult zfsResult; ZfsResult zfsResult;
if ( encrypt ) if ( encrypt )
{ {
zfsResult zfsResult = CreateZpool( deviceName, poolName, m_poolOptions, true, passphrase );
= CreateZpool( deviceName, poolName, m_poolOptions, true, passphrase );
} }
else else
{ {
@ -181,16 +219,17 @@ ZfsJob::exec()
return Calamares::JobResult::error( tr( "Failed to create zpool" ), zfsResult.failureMessage ); return Calamares::JobResult::error( tr( "Failed to create zpool" ), zfsResult.failureMessage );
} }
// Add the poolname, dataset name and mountpoint to the list // Save the poolname, dataset name and mountpoint. It will later be added to a list and placed in global storage.
// This will be used by later modules including mount and umount
QVariantMap poolNameEntry; QVariantMap poolNameEntry;
poolNameEntry["poolName"] = poolName; poolNameEntry[ "poolName" ] = poolName;
poolNameEntry["mountpoint"] = mountpoint; poolNameEntry[ "mountpoint" ] = mountpoint;
poolNameEntry["dsName"] = "none"; poolNameEntry[ "dsName" ] = "none";
// If the mountpoint is /, create datasets per the config file. If not, create a single dataset mounted at the partitions mountpoint
if ( mountpoint == '/' ) if ( mountpoint == '/' )
{ {
// Create the datasets CollectMountpoints( partitions );
QVariantList datasetList; QVariantList datasetList;
for ( const auto& dataset : qAsConst( m_datasets ) ) for ( const auto& dataset : qAsConst( m_datasets ) )
{ {
@ -204,6 +243,12 @@ ZfsJob::exec()
continue; continue;
} }
// We should skip this dataset if it conflicts with a permanent mountpoint
if ( IsMountpointOverlapping( datasetMap[ "mountpoint" ].toString() ) )
{
continue;
}
// Create the dataset. We set canmount=no regardless of the setting for now. // Create the dataset. We set canmount=no regardless of the setting for now.
// It is modified to the correct value in the mount module to ensure mount order is maintained // It is modified to the correct value in the mount module to ensure mount order is maintained
auto r = system->runCommand( { "sh", auto r = system->runCommand( { "sh",
@ -218,7 +263,8 @@ ZfsJob::exec()
continue; continue;
} }
// Add the dataset to the list for global storage // Add the dataset to the list for global storage this information is used later to properly set
// the mount options on each dataset
datasetMap[ "zpool" ] = m_poolName; datasetMap[ "zpool" ] = m_poolName;
datasetList.append( datasetMap ); datasetList.append( datasetMap );
} }
@ -231,24 +277,23 @@ ZfsJob::exec()
} }
else else
{ {
// Create the dataset. We set canmount=no regardless of the setting for now. // This is a zpool with a single dataset We again set canmount=no regardless of the desired setting.
// It is modified to the correct value in the mount module to ensure mount order is maintained // It is modified to the correct value in the mount module to ensure mount order is maintained
QString dsName = mountpoint; QString dsName = mountpoint;
dsName.remove( QRegExp( "[^a-zA-Z\\d\\s]" ) ); dsName = AlphaNumeric( mountpoint );
auto r = system->runCommand( { "sh", auto r = system->runCommand( { "sh",
"-c", "-c",
"zfs create " + m_datasetOptions + " -o canmount=off -o mountpoint=" "zfs create " + m_datasetOptions + " -o canmount=off -o mountpoint="
+ mountpoint + " " + poolName + "/" + mountpoint + " " + poolName + "/" + dsName },
+ dsName },
std::chrono::seconds( 10 ) ); std::chrono::seconds( 10 ) );
if ( r.getExitCode() != 0 ) if ( r.getExitCode() != 0 )
{ {
cWarning() << "Failed to create dataset" << dsName; cWarning() << "Failed to create dataset" << dsName;
} }
poolNameEntry["dsName"] = dsName; poolNameEntry[ "dsName" ] = dsName;
} }
poolNames.append(poolNameEntry); poolNames.append( poolNameEntry );
// Export the zpool so it can be reimported at the correct location later // Export the zpool so it can be reimported at the correct location later
auto r = system->runCommand( { "zpool", "export", poolName }, std::chrono::seconds( 10 ) ); auto r = system->runCommand( { "zpool", "export", poolName }, std::chrono::seconds( 10 ) );
@ -258,9 +303,10 @@ ZfsJob::exec()
} }
} }
if (!poolNames.isEmpty()) // Put the list of zpools into global storage
if ( !poolNames.isEmpty() )
{ {
gs->insert("zfsPoolInfo", poolNames); gs->insert( "zfsPoolInfo", poolNames );
} }
return Calamares::JobResult::ok(); return Calamares::JobResult::ok();

View File

@ -20,7 +20,8 @@
#include "DllMacro.h" #include "DllMacro.h"
struct ZfsResult { struct ZfsResult
{
bool success; bool success;
QString failureMessage; QString failureMessage;
}; };
@ -46,12 +47,12 @@ private:
QString m_poolName; QString m_poolName;
QString m_poolOptions; QString m_poolOptions;
QString m_datasetOptions; QString m_datasetOptions;
QStringList m_mountpoints;
QList<QVariant> m_datasets; QList< QVariant > m_datasets;
/** @brief Creates a zpool based on the provided arguments /** @brief Creates a zpool based on the provided arguments
* *
* Creates a zpool
* @p deviceName is a full path to the device the zpool should be created on * @p deviceName is a full path to the device the zpool should be created on
* @p poolName is a string containing the name of the pool to create * @p poolName is a string containing the name of the pool to create
* @p poolOptions are the options to pass to zpool create * @p poolOptions are the options to pass to zpool create
@ -59,9 +60,35 @@ private:
* @p passphrase is a string continaing the passphrase * @p passphrase is a string continaing the passphrase
* *
*/ */
ZfsResult CreateZpool(QString deviceName, QString poolName, QString poolOptions, bool encrypt, QString passphrase = QString() ) const; ZfsResult CreateZpool( QString deviceName,
QString poolName,
QString poolOptions,
bool encrypt,
QString passphrase = QString() ) const;
/** @brief Returns the alphanumeric portion of a string
*
* @p input is the input string
*
*/
QString AlphaNumeric( QString input ) const;
/** @brief Collects all the mountpoints from the partitions
*
* Iterates over @p partitions to gather each mountpoint present
* in the list of maps and populates m_mountpoints
*
*/
void CollectMountpoints( const QVariantList& partitions );
/** @brief Check to see if a given mountpoint overlaps with one of the defined moutnpoints
*
* Iterates over m_partitions and checks if @p targetMountpoint overlaps with them by comparing
* the beginning of targetMountpoint with all the values in m_mountpoints. Of course, / is excluded
* since all the mountpoints would begin with /
*
*/
bool IsMountpointOverlapping( const QString& targetMountpoint ) const;
}; };
CALAMARES_PLUGIN_FACTORY_DECLARATION( ZfsJobFactory ) CALAMARES_PLUGIN_FACTORY_DECLARATION( ZfsJobFactory )