gparted/src/GParted_Core.cc

4330 lines
152 KiB
C++

/* Copyright (C) 2004 Bart 'plors' Hakvoort
* Copyright (C) 2008, 2009, 2010, 2011, 2012 Curtis Gedak
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "GParted_Core.h"
#include "BCache_Info.h"
#include "BlockSpecial.h"
#include "CopyBlocks.h"
#include "Device.h"
#include "DMRaid.h"
#include "FileSystem.h"
#include "FS_Info.h"
#include "LVM2_PV_Info.h"
#include "LUKS_Info.h"
#include "Mount_Info.h"
#include "Operation.h"
#include "OperationCopy.h"
#include "Partition.h"
#include "PartitionLUKS.h"
#include "PartitionVector.h"
#include "Proc_Partitions_Info.h"
#include "SupportedFileSystems.h"
#include "SWRaid_Info.h"
#include "Utils.h"
#include "../config.h"
#include "btrfs.h"
#include <parted/parted.h>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <endian.h>
#include <glibmm/miscutils.h>
#include <glibmm/fileutils.h>
#include <glibmm/shell.h>
#include <gtkmm/messagedialog.h>
#include <gtkmm/main.h>
std::vector<Glib::ustring> libparted_messages ; //see ped_exception_handler()
namespace GParted
{
const std::time_t SETTLE_DEVICE_PROBE_MAX_WAIT_SECONDS = 1;
const std::time_t SETTLE_DEVICE_APPLY_MAX_WAIT_SECONDS = 10;
static bool udevadm_found = false;
static bool hdparm_found = false;
static const Glib::ustring GPARTED_BUG( _("GParted Bug") );
GParted_Core::GParted_Core()
{
thread_status_message = "" ;
ped_exception_set_handler( ped_exception_handler ) ;
//get valid flags ...
for ( PedPartitionFlag flag = ped_partition_flag_next( static_cast<PedPartitionFlag>( 0 ) ) ;
flag ;
flag = ped_partition_flag_next( flag ) )
flags .push_back( flag ) ;
find_supported_core();
supported_filesystems = new SupportedFileSystems();
//Determine file system support capabilities for the first time
supported_filesystems->find_supported_filesystems();
}
Glib::ustring GParted_Core::get_version_and_config_string()
{
Glib::ustring str = Glib::ustring("GParted ") + VERSION + "\n";
str += "configuration";
bool added_config_flag = false;
#ifdef USE_LIBPARTED_DMRAID
str += " --enable-libparted-dmraid";
added_config_flag = true;
#endif
#ifdef ENABLE_ONLINE_RESIZE
str += " --enable-online-resize";
added_config_flag = true;
#endif
if (! added_config_flag)
str += " (none)";
str += "\n";
str += Glib::ustring("libparted ") + ped_get_version();
return str;
}
void GParted_Core::find_supported_core()
{
udevadm_found = ! Glib::find_program_in_path( "udevadm" ).empty();
hdparm_found = ! Glib::find_program_in_path( "hdparm" ).empty();
}
void GParted_Core::find_supported_filesystems()
{
supported_filesystems->find_supported_filesystems();
}
void GParted_Core::set_user_devices( const std::vector<Glib::ustring> & user_devices )
{
this ->device_paths = user_devices ;
this ->probe_devices = ! user_devices .size() ;
}
void GParted_Core::set_devices( std::vector<Device> & devices )
{
Glib::Thread::create( sigc::bind(
sigc::mem_fun( *this, &GParted_Core::set_devices_thread ),
&devices),
false );
Gtk::Main::run();
}
static bool _mainquit( void *dummy )
{
Gtk::Main::quit();
return false;
}
void GParted_Core::set_devices_thread( std::vector<Device> * pdevices )
{
std::vector<Device> &devices = *pdevices;
devices .clear() ;
// Initialise and load caches needed for device discovery.
BlockSpecial::clear_cache(); // MUST BE FIRST. Cache of name to major, minor
// numbers incrementally loaded when BlockSpecial
// objects are created in the following caches.
Proc_Partitions_Info::load_cache(); // SHOULD BE SECOND. Caches /proc/partitions and
// pre-populates BlockSpecial cache.
DMRaid dmraid( true ) ; //Refresh cache of dmraid device information
//only probe if no devices were specified as arguments..
if ( probe_devices )
{
device_paths .clear() ;
//FIXME: When libparted bug 194 is fixed, remove code to read:
// /proc/partitions
// This was a problem with no floppy drive yet BIOS indicated one existed.
// http://parted.alioth.debian.org/cgi-bin/trac.cgi/ticket/194
//
//try to find all available devices if devices exist in /proc/partitions
std::vector<Glib::ustring> temp_devices = Proc_Partitions_Info::get_device_paths();
if ( ! temp_devices .empty() )
{
//Try to find all devices in /proc/partitions
for (unsigned int k=0; k < temp_devices .size(); k++)
{
/*TO TRANSLATORS: looks like Scanning /dev/sda */
set_thread_status_message( Glib::ustring::compose( _("Scanning %1"), temp_devices[ k ] ) ) ;
ped_device_get( temp_devices[ k ] .c_str() ) ;
}
//Try to find all dmraid devices
if (dmraid .is_dmraid_supported() ) {
std::vector<Glib::ustring> dmraid_devices ;
dmraid .get_devices( dmraid_devices ) ;
for ( unsigned int k=0; k < dmraid_devices .size(); k++ ) {
set_thread_status_message( Glib::ustring::compose( _("Scanning %1"), dmraid_devices[k] ) ) ;
#ifndef USE_LIBPARTED_DMRAID
dmraid .create_dev_map_entries( dmraid_devices[k] ) ;
settle_device( SETTLE_DEVICE_PROBE_MAX_WAIT_SECONDS );
#endif
ped_device_get( dmraid_devices[k] .c_str() ) ;
}
}
}
else
{
//No devices found in /proc/partitions so use libparted to probe devices
ped_device_probe_all();
}
PedDevice* lp_device = ped_device_get_next(nullptr);
while ( lp_device )
{
/* TO TRANSLATORS: looks like Confirming /dev/sda */
set_thread_status_message( Glib::ustring::compose( _("Confirming %1"), lp_device->path ) );
//only add this device if we can read the first sector (which means it's a real device)
if ( useable_device( lp_device ) )
device_paths.push_back( lp_device->path );
lp_device = ped_device_get_next( lp_device ) ;
}
std::sort( device_paths .begin(), device_paths .end() ) ;
}
else
{
//Device paths were passed in on the command line.
// Sort name device paths and remove duplicates. Avoids repeated scanning
// the same device and showing it multiple times in the UI.
// Reference:
// What's the most efficient way to erase duplicates and sort a vector?
// http://stackoverflow.com/questions/1041620/whats-the-most-efficient-way-to-erase-duplicates-and-sort-a-vector
std::sort( device_paths.begin(), device_paths.end() );
device_paths.erase( std::unique( device_paths.begin(), device_paths.end() ), device_paths.end() );
for ( unsigned int t = 0 ; t < device_paths .size() ; t++ )
{
set_thread_status_message( Glib::ustring::compose( _("Confirming %1"), device_paths[t] ) );
#ifndef USE_LIBPARTED_DMRAID
// Ensure that dmraid device entries are created
if ( dmraid .is_dmraid_supported() &&
dmraid .is_dmraid_device( device_paths[t] ) )
{
dmraid .create_dev_map_entries( dmraid .get_dmraid_name( device_paths [t] ) ) ;
settle_device( SETTLE_DEVICE_PROBE_MAX_WAIT_SECONDS );
}
#endif
PedDevice* lp_device = ped_device_get( device_paths[t].c_str() );
if (lp_device == nullptr || ! useable_device(lp_device))
{
// Remove this disk device which isn't useable
device_paths.erase( device_paths.begin() + t-- );
}
}
}
// Initialise and load caches needed for content discovery.
FS_Info::clear_cache();
const std::vector<Glib::ustring>& device_and_partition_paths =
Proc_Partitions_Info::get_device_and_partition_paths_for(device_paths);
FS_Info::load_cache_for_paths(device_and_partition_paths);
Mount_Info::load_cache();
LVM2_PV_Info::clear_cache();
btrfs::clear_cache();
SWRaid_Info::load_cache();
LUKS_Info::clear_cache();
for ( unsigned int t = 0 ; t < device_paths .size() ; t++ )
{
/*TO TRANSLATORS: looks like Searching /dev/sda partitions */
set_thread_status_message( Glib::ustring::compose( _("Searching %1 partitions"), device_paths[ t ] ) ) ;
Device temp_device;
set_device_from_disk( temp_device, device_paths[t] );
devices.push_back( temp_device );
}
set_thread_status_message("") ;
g_idle_add((GSourceFunc)_mainquit, nullptr);
}
void GParted_Core::set_thread_status_message( Glib::ustring msg )
{
//Remember to clear status message when finished with thread.
thread_status_message = msg ;
}
Glib::ustring GParted_Core::get_thread_status_message( )
{
return thread_status_message ;
}
bool GParted_Core::valid_partition(const Device& device, Partition& partition, Glib::ustring& error)
{
//Ensure that partition start and end are not beyond the ends of the disk device
if (partition.sector_start < 0)
{
error = GPARTED_BUG + ": " +
Glib::ustring::compose(
/* TO TRANSLATORS: looks like A partition cannot start (-2048)
* before the start of the device */
_("A partition cannot start (%1) before the start of the device"),
partition.sector_start);
return false;
}
if (partition.sector_end >= device.length)
{
error = GPARTED_BUG + ": " +
Glib::ustring::compose(
/* TO TRANSLATORS: looks like A partition cannot end (2099199)
* after the end of the device (2097151) */
_("A partition cannot end (%1) after the end of the device (%2)"),
partition.sector_start);
return false;
}
//do some basic checks on the partition
if ( partition .get_sector_length() <= 0 )
{
error = GPARTED_BUG + ": " +
Glib::ustring::compose(
/* TO TRANSLATORS: looks like A partition cannot have a length of -1 sectors */
_("A partition cannot have a length of %1 sectors"),
partition .get_sector_length() ) ;
return false ;
}
// Check this partition's file system usage is valid.
if ( partition .get_sector_length() < partition .sectors_used )
{
error = GPARTED_BUG + ": " +
Glib::ustring::compose(
/* TO TRANSLATORS: looks like A partition with used sectors (2048) greater than its length (1536) is not valid */
_("A partition with used sectors (%1) greater than its length (%2) is not valid"),
partition .sectors_used,
partition .get_sector_length() ) ;
return false ;
}
//FIXME: it would be perfect if we could check for overlapping with adjacent partitions as well,
//however, finding the adjacent partitions is not as easy as it seems and at this moment all the dialogs
//already perform these checks. A perfect 'fixme-later' ;)
return true;
}
bool GParted_Core::apply_operation_to_disk( Operation * operation )
{
bool success = false;
libparted_messages .clear() ;
operation->operation_detail.signal_capture_errors.connect(
sigc::mem_fun( *this, &GParted_Core::capture_libparted_messages ) );
switch ( operation->type )
{
// Call calibrate_partition() first for each operation to ensure the
// correct partition path name and boundary is known before performing the
// actual modifications. (See comments in calibrate_partition() for
// reasons why this is necessary). Calibrate the most relevant partition
// object(s), either partition_original, partition_new or partition_copy,
// as required. Calibrate also displays details of the partition being
// modified in the operation results to inform the user.
//
// Win_GParted::set_valid_operations() determines which operations are
// allowed on file systems depending on whether each is busy mounted or
// not. For encrypted file systems allowed operations also depends on
// whether the encryption mapping is open or not. Therefore each
// operation must leave the status of the file system and any encryption
// mapping in the same state which it found it, ready for the next
// operation.
case OPERATION_DELETE:
success = calibrate_partition( operation->get_partition_original(),
operation->operation_detail )
&& remove_filesystem( operation->get_partition_original(),
operation->operation_detail )
&& delete_partition( operation->get_partition_original(),
operation->operation_detail );
break;
case OPERATION_CHECK:
success = calibrate_partition( operation->get_partition_original(),
operation->operation_detail )
&& check_repair_filesystem( operation->get_partition_original().get_filesystem_partition(),
operation->operation_detail )
&& check_repair_maximize( operation->get_partition_original(),
operation->operation_detail );
break;
case OPERATION_CREATE:
// The partition doesn't exist yet so there's nothing to calibrate.
success = create( operation->get_partition_new(), operation->operation_detail );
break;
case OPERATION_RESIZE_MOVE:
success = calibrate_partition( operation->get_partition_original(),
operation->operation_detail );
if ( ! success )
break;
// Replace the new partition object's path from the calibration in
// case the path is "Copy of ..." from the partition having been
// newly created by a paste into unallocated space earlier in the
// sequence of operations now being applied.
operation->get_partition_new().set_path( operation->get_partition_original().get_path() );
success = resize_move( operation->get_partition_original(),
operation->get_partition_new(),
operation->operation_detail );
break;
case OPERATION_FORMAT:
success = calibrate_partition( operation->get_partition_new(), operation->operation_detail );
if ( ! success )
break;
// Replace the original partition object's path from the
// calibration in case the path is "Copy of ..." from the
// partition having been newly created by a paste into unallocated
// space earlier in the sequence of operations now being applied.
operation->get_partition_original().set_path( operation->get_partition_new().get_path() );
success = remove_filesystem( operation->get_partition_original().get_filesystem_partition(),
operation->operation_detail )
&& format( operation->get_partition_new().get_filesystem_partition(),
operation->operation_detail );
break;
case OPERATION_COPY:
{
//FIXME: in case of a new partition we should make sure the new partition is >= the source partition...
//i think it's best to do this in the dialog_paste
OperationCopy * copy_op = static_cast<OperationCopy*>( operation );
success = calibrate_partition( copy_op->get_partition_copied(),
copy_op->operation_detail )
// Only calibrate the destination when pasting into an existing
// partition, rather than when creating a new partition.
&& ( copy_op->get_partition_original().type == TYPE_UNALLOCATED ||
calibrate_partition( copy_op->get_partition_new(), copy_op->operation_detail ) );
if ( ! success )
break;
success = remove_filesystem( copy_op->get_partition_original().get_filesystem_partition(),
copy_op->operation_detail )
&& copy( copy_op->get_partition_copied(),
copy_op->get_partition_new(),
copy_op->operation_detail );
break;
}
case OPERATION_LABEL_FILESYSTEM:
success = calibrate_partition( operation->get_partition_new(), operation->operation_detail )
&& label_filesystem( operation->get_partition_new().get_filesystem_partition(),
operation->operation_detail );
break;
case OPERATION_NAME_PARTITION:
success = calibrate_partition( operation->get_partition_new(), operation->operation_detail )
&& name_partition( operation->get_partition_new(), operation->operation_detail );
break;
case OPERATION_CHANGE_UUID:
success = calibrate_partition( operation->get_partition_new(), operation->operation_detail )
&& change_filesystem_uuid( operation->get_partition_new().get_filesystem_partition(),
operation->operation_detail );
break;
}
return success;
}
bool GParted_Core::set_disklabel( const Device & device, const Glib::ustring & disklabel )
{
const Glib::ustring& device_path = device.get_path();
// FIXME: Should call file system specific removal actions
// (to remove LVM2 PVs before deleting the partitions).
#ifdef ENABLE_LOOP_DELETE_OLD_PTNS_WORKAROUND
// When creating a "loop" table with libparted 2.0 to 3.0 inclusive, it doesn't
// inform the kernel to delete old partitions so as a consequence blkid's cache
// becomes stale and it won't report a file system subsequently created on the
// whole disk device. Create a GPT first to use that code in libparted to delete
// any old partitions. Fixed in parted 3.1 by commit:
// f5c909c0cd50ed52a48dae6d35907dc08b137e88
// libparted: remove has_partitions check to allow loopback partitions
if ( disklabel == "loop" )
new_disklabel( device_path, "gpt", false );
#endif
// Ensure that any previous whole disk device file system can't be recognised by
// libparted in preference to the "loop" partition table signature, or by blkid in
// preference to any partition table about to be written.
OperationDetail dummy_od;
Partition temp_partition;
temp_partition.set_unpartitioned( device_path,
"",
FS_UNALLOCATED,
device.length,
device.sector_size,
false );
erase_filesystem_signatures( temp_partition, dummy_od );
return new_disklabel( device_path, disklabel );
}
bool GParted_Core::new_disklabel( const Glib::ustring & device_path, const Glib::ustring & disklabel,
bool recreate_dmraid_devs )
{
bool return_value = false ;
PedDevice* lp_device = nullptr;
PedDisk* lp_disk = nullptr;
if ( get_device( device_path, lp_device ) )
{
PedDiskType* type = nullptr;
type = ped_disk_type_get( disklabel .c_str() ) ;
if ( type )
{
lp_disk = ped_disk_new_fresh( lp_device, type );
return_value = commit( lp_disk ) ;
}
destroy_device_and_disk( lp_device, lp_disk ) ;
}
#ifndef USE_LIBPARTED_DMRAID
//delete and recreate disk entries if dmraid
DMRaid dmraid ;
if ( recreate_dmraid_devs && return_value && dmraid.is_dmraid_device( device_path ) )
{
dmraid .purge_dev_map_entries( device_path ) ;
dmraid .create_dev_map_entries( device_path ) ;
}
#endif
return return_value ;
}
bool GParted_Core::toggle_flag( const Partition & partition, const Glib::ustring & flag, bool state )
{
bool succes = false ;
PedDevice* lp_device = nullptr;
PedDisk* lp_disk = nullptr;
if ( get_device_and_disk( partition .device_path, lp_device, lp_disk ) )
{
PedPartition* lp_partition = get_lp_partition( lp_disk, partition );
if ( lp_partition )
{
PedPartitionFlag lp_flag = ped_partition_flag_get_by_name( flag .c_str() ) ;
if ( lp_flag > 0 && ped_partition_set_flag( lp_partition, lp_flag, state ) )
succes = commit( lp_disk ) ;
}
destroy_device_and_disk( lp_device, lp_disk ) ;
}
return succes ;
}
const std::vector<FS> & GParted_Core::get_filesystems() const
{
return supported_filesystems->get_all_fs_support();
}
// Return supported capabilities of the file system type or, if not found, not supported
// capabilities set.
const FS& GParted_Core::get_fs(FSType fstype) const
{
return supported_filesystems->get_fs_support(fstype);
}
//Return all libparted's partition table types in it's preferred ordering,
// alphabetic except with "loop" last.
// Ref: parted >= 1.8 ./libparted/libparted.c init_disk_types()
std::vector<Glib::ustring> GParted_Core::get_disklabeltypes()
{
std::vector<Glib::ustring> disklabeltypes ;
PedDiskType *disk_type ;
for (disk_type = ped_disk_type_get_next(nullptr); disk_type; disk_type = ped_disk_type_get_next(disk_type))
disklabeltypes .push_back( disk_type->name ) ;
return disklabeltypes ;
}
std::map<Glib::ustring, bool> GParted_Core::get_available_flags( const Partition & partition )
{
std::map<Glib::ustring, bool> flag_info ;
PedDevice* lp_device = nullptr;
PedDisk* lp_disk = nullptr;
if ( get_device_and_disk( partition .device_path, lp_device, lp_disk ) )
{
PedPartition* lp_partition = get_lp_partition( lp_disk, partition );
if ( lp_partition )
{
for ( unsigned int t = 0 ; t < flags .size() ; t++ )
if ( ped_partition_is_flag_available( lp_partition, flags[ t ] ) )
flag_info[ ped_partition_flag_get_name( flags[ t ] ) ] =
ped_partition_get_flag( lp_partition, flags[ t ] ) ;
}
destroy_device_and_disk( lp_device, lp_disk ) ;
}
return flag_info ;
}
//private functions...
Glib::ustring GParted_Core::get_partition_path(const PedPartition *lp_partition)
{
g_assert(lp_partition != nullptr); // Bug: Not initialised by suitable ped_disk_*partition*() call
char * lp_path; //we have to free the result of ped_partition_get_path()
Glib::ustring partition_path = "Partition path not found";
lp_path = ped_partition_get_path(lp_partition);
if (lp_path != nullptr)
{
partition_path = lp_path;
free(lp_path);
}
#ifndef USE_LIBPARTED_DMRAID
//Ensure partition path name is compatible with dmraid
DMRaid dmraid; //Use cache of dmraid device information
if ( dmraid .is_dmraid_supported()
&& dmraid .is_dmraid_device( partition_path )
)
{
partition_path = dmraid .make_path_dmraid_compatible(partition_path);
}
#endif
return partition_path ;
}
void GParted_Core::set_device_from_disk( Device & device, const Glib::ustring & device_path )
{
PedDevice* lp_device = nullptr;
PedDisk* lp_disk = nullptr;
if (! get_device(device_path, lp_device, true))
return;
device.Reset();
// Device info ...
device.set_path(device_path);
device.model = lp_device->model;
device.length = lp_device->length;
device.sector_size = lp_device->sector_size;
device.heads = lp_device->bios_geom.heads;
device.sectors = lp_device->bios_geom.sectors;
device.cylinders = lp_device->bios_geom.cylinders;
device.cylsize = device.heads * device.sectors;
set_device_serial_number(device);
// Make sure cylsize is at least 1 MiB
if (device.cylsize < (MEBIBYTE / device.sector_size))
device.cylsize = MEBIBYTE / device.sector_size;
std::vector<Glib::ustring> messages;
FSType fstype = detect_filesystem(lp_device, nullptr, messages);
if (fstype != FS_UNKNOWN)
{
// Case 1/4: Recognised whole disk device file system
//
// FS_Info (blkid) or GParted internal detection recognised file system
// signature on whole disk device. Need to detect before libparted
// reported partitioning to avoid bug in libparted 1.9.0 to 2.3 inclusive
// which recognised FAT file systems as MSDOS partition tables. Fixed in
// parted 2.4 by commit:
// 616a2a1659d89ff90f9834016a451da8722df509
// libparted: avoid regression when processing a whole-disk FAT partition
// Clear the possible "unrecognised disk label" message
libparted_messages.clear();
device.disktype = "none";
device.max_prims = 1;
set_device_one_partition(device, lp_device, fstype, messages);
}
else if (! get_disk(lp_device, lp_disk))
{
// Case 2/4: Empty drive
//
// Empty drive without a disk label (partition table).
device.disktype =
/* TO TRANSLATORS: unrecognized
* means that the partition table for this disk device is unknown
* or not recognized.
*/
_("unrecognized");
device.max_prims = 1;
Partition* partition_temp = new Partition();
partition_temp->set_unpartitioned(device.get_path(),
"", // Overridden with "unallocated"
FS_UNALLOCATED,
device.length,
device.sector_size,
false);
// Place libparted messages in this unallocated partition
partition_temp->append_messages(libparted_messages);
libparted_messages.clear();
device.partitions.push_back_adopt(partition_temp);
}
else if (lp_disk && lp_disk->type && lp_disk->type->name &&
strcmp(lp_disk->type->name, "loop") == 0 )
{
// Case 3/4: Libparted "loop" signature only
//
// Drive just containing libparted "loop" signature and nothing else.
// (Actually any drive reported by libparted as "loop" but not recognised
// in case 1/4 as whole disk device file system).
device.disktype = lp_disk->type->name;
device.max_prims = 1;
// Create virtual partition covering the whole disk device with unknown
// contents.
Partition* partition_temp = new Partition();
partition_temp->set_unpartitioned(device.get_path(),
lp_device->path,
FS_UNKNOWN,
device.length,
device.sector_size,
false);
// Place unknown file system message in this partition.
partition_temp->append_messages(messages);
device.partitions.push_back_adopt(partition_temp);
}
else
{
// Case 4/4: Partitioned drive
//
// (excluding libparted "loop" recognised in case 3/4).
device.disktype = lp_disk->type->name;
device.max_prims = ped_disk_get_max_primary_partition_count(lp_disk);
// Determine if partition naming is supported.
if (ped_disk_type_check_feature(lp_disk->type, PED_DISK_TYPE_PARTITION_NAME))
device.enable_partition_naming(Utils::get_max_partition_name_length(device.disktype));
set_device_partitions(device, lp_device, lp_disk);
if (device.highest_busy)
{
device.readonly = ! commit_to_os(lp_disk, SETTLE_DEVICE_PROBE_MAX_WAIT_SECONDS);
// Clear libparted messages. Typically these are:
// The kernel was unable to re-read the partition table...
libparted_messages.clear();
}
}
destroy_device_and_disk(lp_device, lp_disk);
return;
}
void GParted_Core::set_device_serial_number( Device & device )
{
if ( ! hdparm_found )
// Serial number left blank when the hdparm command is not installed.
return;
Glib::ustring output;
Glib::ustring error;
Utils::execute_command( "hdparm -I " + Glib::shell_quote( device.get_path() ), output, error, true );
if ( ! error.empty() )
{
// hdparm reported an error message to stderr. Assume it's a device
// without a hard drive serial number.
//
// Using hdparm -I to query Linux software RAID arrays and BIOS fake RAID
// arrays, both devices without their own hard drive serial numbers,
// produce this error:
// HDIO_DRIVE_CMD(identify) failed: Inappropriate ioctl for device
//
// And querying USB flash drives, also a device type without their own
// hard drive serial numbers, generates this error:
// SG_IO: bad/missing sense data, sb[]: 70 00 05 00 00 00 00 0a ...
device.serial_number = "none";
}
else
{
Glib::ustring serial_number = Utils::trim( Utils::regexp_label( output,
"^[[:blank:]]*Serial Number:[[:blank:]]*(.*)[[:blank:]]*$" ) );
if ( ! serial_number.empty() )
device.serial_number = serial_number;
}
// Otherwise serial number left blank when not found in the hdparm output.
}
/**
* Fills the device.partitions member of device by scanning
* all partitions
*/
void GParted_Core::set_device_partitions( Device & device, PedDevice* lp_device, PedDisk* lp_disk )
{
int EXT_INDEX = -1 ;
#ifndef USE_LIBPARTED_DMRAID
DMRaid dmraid ; //Use cache of dmraid device information
#endif
//clear partitions
device .partitions .clear() ;
PedPartition* lp_partition = ped_disk_next_partition(lp_disk, nullptr);
while ( lp_partition )
{
libparted_messages .clear() ;
Partition* partition_temp = nullptr;
bool partition_is_busy = false ;
FSType fstype;
std::vector<Glib::ustring> detect_messages;
Glib::ustring partition_path;
// NOTE: lp_partition->type bit field
// lp_partition->type is a bit field using enumerated names for each bit.
// GParted is only interested in partitions with these single bits set:
// PED_PARTITION_NORMAL, PED_PARTITION_LOGICAL and PED_PARTITION_EXTENDED.
// Partitions, ranges of blocks, with other bits set representing free
// space and disk label meta-data, PED_PARTITION_FREESPACE and
// PED_PARTITION_METADATA bits respectively, are ignored and GParted
// creates its own unallocated partitions and accounts for partition
// tables.
// References:
// * struct PedPartition and type PedPartitionType
// https://www.gnu.org/software/parted/api/struct__PedPartition.html
// * enum _PedPartitionType
// http://git.savannah.gnu.org/cgit/parted.git/tree/include/parted/disk.in.h?id=v3.2#n45
switch ( lp_partition ->type )
{
case PED_PARTITION_NORMAL:
case PED_PARTITION_LOGICAL:
fstype = detect_filesystem(lp_device, lp_partition, detect_messages);
partition_path = get_partition_path(lp_partition);
#ifndef USE_LIBPARTED_DMRAID
//Handle dmraid devices differently because the minor number might not
// match the last number of the partition filename as shown by "ls -l /dev/mapper"
if ( dmraid .is_dmraid_device( device .get_path() ) )
{
//Try device_name + partition_number
Glib::ustring dmraid_path = device .get_path() + Utils::num_to_str( lp_partition ->num ) ;
partition_is_busy = is_busy(device.get_path(), fstype, dmraid_path);
//Try device_name + p + partition_number
dmraid_path = device .get_path() + "p" + Utils::num_to_str( lp_partition ->num ) ;
partition_is_busy |= is_busy(device.get_path(), fstype, dmraid_path);
}
else
#endif
{
partition_is_busy = is_busy(device.get_path(), fstype, partition_path);
}
if (fstype == FS_LUKS)
partition_temp = new PartitionLUKS();
else
partition_temp = new Partition();
partition_temp->Set( device .get_path(),
partition_path,
lp_partition->num,
( lp_partition->type == PED_PARTITION_NORMAL ) ? TYPE_PRIMARY
: TYPE_LOGICAL,
fstype,
lp_partition->geom.start,
lp_partition->geom.end,
device.sector_size,
( lp_partition->type == PED_PARTITION_LOGICAL ),
partition_is_busy );
partition_temp->append_messages( detect_messages );
set_flags( *partition_temp, lp_partition );
if (fstype == FS_LUKS)
set_luks_partition( *dynamic_cast<PartitionLUKS *>( partition_temp ) );
if ( partition_temp->busy && partition_temp->partition_number > device.highest_busy )
device.highest_busy = partition_temp->partition_number;
break ;
case PED_PARTITION_EXTENDED:
partition_path = get_partition_path(lp_partition);
partition_temp = new Partition();
partition_temp->Set( device.get_path(),
partition_path,
lp_partition->num,
TYPE_EXTENDED,
FS_EXTENDED,
lp_partition->geom.start,
lp_partition->geom.end,
device.sector_size,
false,
false );
set_flags( *partition_temp, lp_partition );
EXT_INDEX = device .partitions .size() ;
break ;
default:
// Ignore libparted reported partitions with other type
// bits set.
break;
}
// Only for libparted reported partition types that we care about: NORMAL,
// LOGICAL, EXTENDED
if (partition_temp != nullptr)
{
set_partition_label_and_uuid( *partition_temp );
set_mountpoints( *partition_temp );
set_used_sectors( *partition_temp, lp_disk );
// Retrieve partition name
if ( device.partition_naming_supported() )
partition_temp->name = Glib::ustring( ped_partition_get_name( lp_partition ) );
partition_temp->append_messages( libparted_messages );
if ( ! partition_temp->inside_extended )
device.partitions.push_back_adopt( partition_temp );
else
device.partitions[EXT_INDEX].logicals.push_back_adopt( partition_temp );
}
//next partition (if any)
lp_partition = ped_disk_next_partition( lp_disk, lp_partition ) ;
}
if ( EXT_INDEX > -1 )
{
insert_unallocated( device .get_path(),
device .partitions[ EXT_INDEX ] .logicals,
device .partitions[ EXT_INDEX ] .sector_start,
device .partitions[ EXT_INDEX ] .sector_end,
device .sector_size,
true ) ;
//Set busy status of extended partition if and only if
// there is at least one busy logical partition.
for ( unsigned int t = 0 ; t < device .partitions[ EXT_INDEX ] .logicals .size() ; t ++ )
{
if ( device .partitions[ EXT_INDEX ] .logicals[ t ] .busy )
{
device .partitions[ EXT_INDEX ] .busy = true ;
break ;
}
}
}
insert_unallocated( device .get_path(), device .partitions, 0, device .length -1, device .sector_size, false ) ;
}
// Create one Partition object spanning the Device after identifying the file system
// on the whole disk device. Much simplified equivalent of set_device_partitions().
void GParted_Core::set_device_one_partition( Device & device, PedDevice * lp_device, FSType fstype,
std::vector<Glib::ustring> & messages )
{
device.partitions.clear();
Glib::ustring path = lp_device->path;
bool partition_is_busy = is_busy(device.get_path(), fstype, path);
Partition* partition_temp = nullptr;
if ( fstype == FS_LUKS )
partition_temp = new PartitionLUKS();
else
partition_temp = new Partition();
partition_temp->set_unpartitioned( device.get_path(),
path,
fstype,
device.length,
device.sector_size,
partition_is_busy );
partition_temp->append_messages( messages );
if ( fstype == FS_LUKS )
set_luks_partition( *dynamic_cast<PartitionLUKS *>( partition_temp ) );
if ( partition_temp->busy )
device.highest_busy = 1;
set_partition_label_and_uuid( *partition_temp );
set_mountpoints( *partition_temp );
set_used_sectors(*partition_temp, nullptr);
device.partitions.push_back_adopt( partition_temp );
}
void GParted_Core::set_luks_partition( PartitionLUKS & partition )
{
LUKS_Mapping mapping = LUKS_Info::get_cache_entry( partition.get_path() );
if ( mapping.name.empty() )
// No LUKS mapping found so no device file with which to query the
// encrypted file system. Assume no open dm-crypt mapping exists.
// Details of encrypted file system left blank.
return;
Glib::ustring mapping_path = DEV_MAPPER_PATH + mapping.name;
std::vector<Glib::ustring> detect_messages;
FSType fstype = detect_filesystem_in_encryption_mapping(mapping_path, detect_messages);
bool fs_busy = is_busy(mapping_path, fstype, mapping_path);
partition.set_luks( mapping_path,
fstype,
mapping.offset / partition.sector_size,
mapping.length / partition.sector_size,
partition.sector_size,
fs_busy );
Partition & encrypted = partition.get_encrypted();
encrypted.append_messages( detect_messages );
set_partition_label_and_uuid( encrypted );
set_mountpoints( encrypted );
set_used_sectors(encrypted, nullptr);
}
void GParted_Core::set_partition_label_and_uuid( Partition & partition )
{
const Glib::ustring& partition_path = partition.get_path();
// For SWRaid members only get the label and UUID from SWRaid_Info. Never use
// values from FS_Info to avoid showing incorrect information in cases where blkid
// reports the wrong values.
if (partition.fstype == FS_LINUX_SWRAID ||
partition.fstype == FS_ATARAID )
{
const Glib::ustring& label = SWRaid_Info::get_label(partition_path);
if ( ! label.empty() )
partition.set_filesystem_label( label );
partition.uuid = SWRaid_Info::get_uuid( partition_path );
return;
}
// Retrieve file system label. Use file system specific method first because it
// has been that way since (#662537) to display Unicode labels correctly.
// (#786502) adds support for reading Unicode labels through the FS_Info cache.
// Either method should produce the same labels however the FS specific command is
// run in the users locale where as blkid is run in the C locale. Shouldn't
// matter but who knows for sure!
read_label( partition );
if ( ! partition.filesystem_label_known() )
{
bool label_found = false;
Glib::ustring label = FS_Info::get_label( partition_path, label_found );
if ( label_found )
partition.set_filesystem_label( label );
}
// Retrieve file system UUID. Use cached method first in an effort to speed up
// device scanning.
partition.uuid = FS_Info::get_uuid( partition_path );
if ( partition.uuid.empty() )
{
read_uuid( partition );
}
}
FSType GParted_Core::detect_filesystem_in_encryption_mapping(const Glib::ustring& path,
std::vector<Glib::ustring>& messages)
{
// Run blkid identification on this one encryption mapping.
std::vector<Glib::ustring> one_path;
one_path.push_back(path);
FS_Info::load_cache_for_paths(one_path);
FSType fstype = FS_UNKNOWN;
PedDevice* lp_device = nullptr;
if (get_device(path, lp_device))
{
// Run libparted partition table and file system identification. Only use
// (get the first partition) if it's a "loop" table; because GParted only
// supports one block device to one encryption mapping to one file system.
PedDisk* lp_disk = nullptr;
PedPartition* lp_partition = nullptr;
if (get_disk(lp_device, lp_disk) && lp_disk && lp_disk->type &&
lp_disk->type->name && strcmp(lp_disk->type->name, "loop") == 0 )
{
lp_partition = ped_disk_next_partition(lp_disk, nullptr);
}
// Clear the "unrecognised disk label" message reported when libparted
// fails to detect anything.
libparted_messages.clear();
fstype = detect_filesystem(lp_device, lp_partition, messages);
destroy_device_and_disk(lp_device, lp_disk);
}
return fstype;
}
// GParted simple internal file system signature detection. Use sparingly. Only when
// (old versions of) blkid and libparted don't recognise a signature.
FSType GParted_Core::detect_filesystem_internal(const Glib::ustring& path, Byte_Value sector_size)
{
char magic1[16]; // Big enough for largest signatures[].sig1 or sig2
char magic2[16];
FSType fstype = FS_UNKNOWN;
char* buf = static_cast<char *>(malloc(sector_size));
if ( ! buf )
return FS_UNKNOWN;
int fd = open(path.c_str(), O_RDONLY|O_NONBLOCK);
if (fd == -1)
{
free( buf );
return FS_UNKNOWN;
}
struct {
Byte_Value offset1;
const char * sig1;
Byte_Value offset2;
const char * sig2;
FSType fstype;
} signatures[] = {
//offset1, sig1 , offset2, sig2 , fstype
{ 0LL, "LUKS\xBA\xBE" , 0LL, nullptr, FS_LUKS },
{ 3LL, "-FVE-FS-" , 0LL, nullptr, FS_BITLOCKER },
{ 0LL, "\x52\x56\xBE\x1B", 0LL, nullptr, FS_GRUB2_CORE_IMG },
{ 0LL, "\x52\x56\xBE\x6F", 0LL, nullptr, FS_GRUB2_CORE_IMG },
{ 0LL, "\x52\xE8\x28\x01", 0LL, nullptr, FS_GRUB2_CORE_IMG },
{ 0LL, "\x52\xBF\xF4\x81", 0LL, nullptr, FS_GRUB2_CORE_IMG },
{ 0LL, "\x52\x56\xBE\x63", 0LL, nullptr, FS_GRUB2_CORE_IMG },
{ 0LL, "\x52\x56\xBE\x56", 0LL, nullptr, FS_GRUB2_CORE_IMG },
{ 24LL, "\x01\x00" , 32LL, "NXSB" , FS_APFS },
{ 512LL, "LABELONE" , 536LL, "LVM2" , FS_LVM2_PV },
{ 1030LL, "\x34\x34" , 0LL, nullptr, FS_NILFS2 },
{ 65536LL, "ReIsEr4" , 0LL, nullptr, FS_REISER4 },
{ 65600LL, "_BHRfS_M" , 0LL, nullptr, FS_BTRFS }
};
// For simple BitLocker recognition consider validation of BIOS Parameter block
// fields unnecessary.
// * Detecting BitLocker
// https://docs.microsoft.com/en-us/archive/blogs/si_team/detecting-bitlocker
// https://web.archive.org/web/20200720030153/https://docs.microsoft.com/en-us/archive/blogs/si_team/detecting-bitlocker
//
// Recognise GRUB2 core.img just by any of the possible first 4 bytes of x86 CPU
// instructions it starts with.
// * bootinfoscript v0.77 line 1990 [GRUB2 core.img possible staring 4 bytes]
// https://github.com/arvidjaar/bootinfoscript/blob/009f509d59e2f0d39b8d44692e2a81720f5af7b6/bootinfoscript#L1990
//
// Simple APFS recognition based on matching the following fields in the
// superblock:
// 1) Object type is OBJECT_TYPE_NX_SUPERBLOCK, lower 16-bits of the object type
// field is 0x0001 stored as little endian bytes 0x01, 0x00.
// WARNING: The magic signatures are defined as NUL terminated strings so the
// below code only does a 1-byte match for 0x01, rather than a 2-byte match
// for 0x01, 0x00.
// 2) 4 byte magic "NXSB".
// * Apple File System Reference
// https://developer.apple.com/support/apple-file-system/Apple-File-System-Reference.pdf
Byte_Value prev_read_offset = -1;
memset(buf, 0, sector_size);
for ( unsigned int i = 0 ; i < sizeof( signatures ) / sizeof( signatures[0] ) ; i ++ )
{
const size_t len1 = std::min((signatures[i].sig1 == nullptr) ? 0U : strlen(signatures[i].sig1),
sizeof(magic1));
const size_t len2 = std::min((signatures[i].sig2 == nullptr) ? 0U : strlen(signatures[i].sig2),
sizeof(magic2));
// NOTE: From this point onwards signatures[].sig1 and .sig2 are treated
// as character buffers of known lengths len1 and len2, not NUL terminated
// strings.
if (len1 == 0UL || (signatures[i].sig2 != nullptr && len2 == 0UL))
continue; // Don't allow 0 length signatures to match
Byte_Value read_offset = signatures[i].offset1 / sector_size * sector_size;
// Optimisation: only read new sector when it is different to the
// previously read sector.
if (read_offset != prev_read_offset)
{
if (lseek(fd, read_offset, SEEK_SET) == read_offset &&
read(fd, buf, sector_size) == sector_size )
{
prev_read_offset = read_offset;
}
else
{
// Outside block device boundaries or other error.
continue;
}
}
memcpy(magic1, buf + signatures[i].offset1 % sector_size, len1);
// WARNING: This assumes offset2 is in the same sector as offset1
if (signatures[i].sig2 != nullptr)
memcpy(magic2, buf + signatures[i].offset2 % sector_size, len2);
if (memcmp(magic1, signatures[i].sig1, len1) == 0 &&
(signatures[i].sig2 == nullptr || memcmp(magic2, signatures[i].sig2, len2) == 0) )
{
fstype = signatures[i].fstype;
break;
}
}
close(fd);
free( buf );
return fstype;
}
FSType GParted_Core::detect_filesystem(const PedDevice *lp_device, const PedPartition *lp_partition,
std::vector<Glib::ustring> &messages)
{
g_assert(lp_device != nullptr); // Bug: Not initialised by call to ped_device_get() or ped_device_get_next()
Glib::ustring fsname = "";
Glib::ustring path;
DMRaid dmraid;
if ( lp_partition )
// Will query partition using methods: (Q1) RAID, (Q2) blkid,
// (Q3) libparted, (Q4) internal
path = get_partition_path( lp_partition );
else
// Will query whole disk device using methods: (Q1) RAID, (Q2) blkid,
// (Q4) internal
path = lp_device->path;
// (Q1) SWRaid_Info (mdadm) and DMRaid member detection.
if ( SWRaid_Info::is_member( path ) )
return SWRaid_Info::get_fstype(path);
if (dmraid.is_member(path))
return FS_ATARAID;
// (Q2) FS_Info (blkid) file system detection
// Blkid detects more signatures and generally has less limitations so use before
// libparted detection, but it doesn't report anything for extended partitions.
fsname = FS_Info::get_fs_type( path );
// (Q3) Libparted file system detection
// Only used when blkid didn't report anything and only on partitions, not whole
// disk devices. (Doesn't detect anything on non-512 byte sector devices before
// libparted 3.2).
if ( fsname.empty() && lp_partition && lp_partition->fs_type )
fsname = lp_partition->fs_type->name;
if ( ! fsname.empty() )
{
if ( fsname == "extended" )
return FS_EXTENDED;
else if ( fsname == "btrfs" )
return FS_BTRFS;
else if ( fsname == "exfat" )
return FS_EXFAT;
else if ( fsname == "ext2" )
return FS_EXT2;
else if ( fsname == "ext3" )
return FS_EXT3;
else if ( fsname == "ext4" ||
fsname == "ext4dev" )
return FS_EXT4;
else if (fsname == "linux-swap(v0)" ||
fsname == "linux-swap(v1)" ||
fsname == "swap" )
return FS_LINUX_SWAP;
else if ( fsname == "crypto_LUKS" )
return FS_LUKS;
else if ( fsname == "LVM2_member" )
return FS_LVM2_PV;
else if ( fsname == "f2fs" )
return FS_F2FS;
else if ( fsname == "fat16" )
return FS_FAT16;
else if ( fsname == "fat32" )
return FS_FAT32;
else if ( fsname == "minix" )
return FS_MINIX;
else if ( fsname == "nilfs2" )
return FS_NILFS2;
else if ( fsname == "ntfs" )
return FS_NTFS;
else if ( fsname == "reiserfs" )
return FS_REISERFS;
else if ( fsname == "xfs" )
return FS_XFS;
else if ( fsname == "jfs" )
return FS_JFS;
else if ( fsname == "hfs" )
return FS_HFS;
else if ( fsname == "hfs+" ||
fsname == "hfsx" ||
fsname == "hfsplus" )
return FS_HFSPLUS;
else if ( fsname == "udf" )
return FS_UDF;
else if ( fsname == "ufs" )
return FS_UFS;
else if ( fsname == "apfs" )
return FS_APFS;
else if (fsname == "bcache")
return FS_BCACHE;
else if ( fsname == "BitLocker" )
return FS_BITLOCKER;
else if ( fsname == "iso9660" )
return FS_ISO9660;
else if ( fsname == "jbd" )
return FS_JBD;
else if ( fsname == "linux_raid_member" )
return FS_LINUX_SWRAID ;
else if ( fsname == "swsusp" ||
fsname == "swsuspend" )
return FS_LINUX_SWSUSPEND ;
else if ( fsname == "ReFS" )
return FS_REFS;
else if ( fsname == "zfs_member" )
return FS_ZFS;
else if (fsname == "adaptec_raid_member" ||
fsname == "ddf_raid_member" ||
fsname == "hpt45x_raid_member" ||
fsname == "hpt37x_raid_member" ||
fsname == "isw_raid_member" ||
fsname == "jmicron_raid_member" ||
fsname == "lsi_mega_raid_member" ||
fsname == "nvidia_raid_member" ||
fsname == "promise_fasttrack_raid_member" ||
fsname == "silicon_medley_raid_member" ||
fsname == "via_raid_member" )
return FS_ATARAID;
}
// (Q4) Fallback to GParted simple internal file system detection
FSType fstype = detect_filesystem_internal(path, lp_device->sector_size);
if ( fstype != FS_UNKNOWN )
return fstype;
//no file system found....
Glib::ustring temp = _( "Unable to detect file system! Possible reasons are:" ) ;
temp += "\n- ";
temp += _( "The file system is damaged" ) ;
temp += "\n- " ;
temp += _( "The file system is unknown to GParted" ) ;
temp += "\n- ";
temp += _( "There is no file system available (unformatted)" ) ;
temp += "\n- ";
/* TO TRANSLATORS: looks like The device entry /dev/sda5 is missing */
temp += Glib::ustring::compose( _("The device entry %1 is missing"), path );
messages .push_back( temp ) ;
return FS_UNKNOWN;
}
void GParted_Core::read_label( Partition & partition )
{
FileSystem* p_filesystem = nullptr;
switch (get_fs(partition.fstype).read_label)
{
case FS::EXTERNAL:
p_filesystem = get_filesystem_object(partition.fstype);
if ( p_filesystem )
p_filesystem->read_label( partition );
break;
default:
break;
}
}
void GParted_Core::read_uuid( Partition & partition )
{
FileSystem* p_filesystem = nullptr;
switch (get_fs(partition.fstype).read_uuid)
{
case FS::EXTERNAL:
p_filesystem = get_filesystem_object(partition.fstype);
if ( p_filesystem )
p_filesystem->read_uuid( partition );
break;
default:
break;
}
}
void GParted_Core::insert_unallocated( const Glib::ustring & device_path,
PartitionVector & partitions,
Sector start,
Sector end,
Byte_Value sector_size,
bool inside_extended )
{
//if there are no partitions at all..
if ( partitions .empty() )
{
Partition * partition_temp = new Partition();
partition_temp->Set_Unallocated( device_path, start, end, sector_size, inside_extended );
partitions.push_back_adopt( partition_temp );
return ;
}
//start <---> first partition start
if ( (partitions .front() .sector_start - start) > (MEBIBYTE / sector_size) )
{
Sector temp_end = partitions.front().sector_start - 1;
Partition * partition_temp = new Partition();
partition_temp->Set_Unallocated( device_path, start, temp_end, sector_size, inside_extended );
partitions.insert_adopt( partitions.begin(), partition_temp );
}
//look for gaps in between
for ( unsigned int t =0 ; t < partitions .size() -1 ; t++ )
{
if ( ( ( partitions[ t + 1 ] .sector_start - partitions[ t ] .sector_end - 1 ) > (MEBIBYTE / sector_size) )
|| ( ( partitions[ t + 1 ] .type != TYPE_LOGICAL ) // Only show exactly 1 MiB if following partition is not logical.
&& ( ( partitions[ t + 1 ] .sector_start - partitions[ t ] .sector_end - 1 ) == (MEBIBYTE / sector_size) )
)
)
{
Sector temp_start = partitions[t].sector_end + 1;
Sector temp_end = partitions[t+1].sector_start - 1;
Partition * partition_temp = new Partition();
partition_temp->Set_Unallocated( device_path, temp_start, temp_end,
sector_size, inside_extended );
partitions.insert_adopt( partitions.begin() + ++t, partition_temp );
}
}
//last partition end <---> end
if ( (end - partitions .back() .sector_end) >= (MEBIBYTE / sector_size) )
{
Sector temp_start = partitions.back().sector_end + 1;
Partition * partition_temp = new Partition();
partition_temp->Set_Unallocated( device_path, temp_start, end, sector_size, inside_extended );
partitions.push_back_adopt( partition_temp );
}
}
void GParted_Core::set_mountpoints( Partition & partition )
{
DMRaid dmraid ; //Use cache of dmraid device information
if (partition.fstype == FS_LVM2_PV)
{
const Glib::ustring& vgname = LVM2_PV_Info::get_vg_name(partition.get_path());
if ( ! vgname.empty() )
partition.add_mountpoint( vgname );
}
else if (partition.fstype == FS_LINUX_SWRAID)
{
const Glib::ustring& array_path = SWRaid_Info::get_array(partition.get_path());
if ( ! array_path.empty() )
partition.add_mountpoint( array_path );
}
else if (partition.fstype == FS_ATARAID)
{
const Glib::ustring& array_path = SWRaid_Info::get_array(partition.get_path());
if (! array_path.empty())
{
partition.add_mountpoint(array_path);
}
else
{
const Glib::ustring& array_path_2 = dmraid.get_array(partition.get_path());
if (! array_path_2.empty())
partition.add_mountpoint(array_path_2);
}
}
else if (partition.fstype == FS_BCACHE)
{
const Glib::ustring bcache_path = BCache_Info::get_bcache_device(partition.device_path,
partition.get_path());
if (! bcache_path.empty())
partition.add_mountpoint(bcache_path);
}
else if (partition.fstype == FS_LUKS)
{
LUKS_Mapping mapping = LUKS_Info::get_cache_entry( partition.get_path() );
if ( ! mapping.name.empty() )
partition.add_mountpoint( DEV_MAPPER_PATH + mapping.name );
}
// Swap spaces and jbds don't have mount points so don't bother trying to add them.
else if (partition.fstype != FS_LINUX_SWAP &&
partition.fstype != FS_JBD )
{
if ( partition.busy )
{
#ifndef USE_LIBPARTED_DMRAID
// Handle dmraid devices differently because there may be more
// than one partition name.
// E.g., there might be names with and/or without a 'p' between
// the device name and partition number.
if ( dmraid.is_dmraid_device( partition.device_path ) )
{
// Try device_name + partition_number
Glib::ustring dmraid_path = partition.device_path +
Utils::num_to_str( partition.partition_number );
if ( set_mountpoints_helper( partition, dmraid_path ) )
return;
// Try device_name + p + partition_number
dmraid_path = partition.device_path + "p" +
Utils::num_to_str( partition.partition_number );
if ( set_mountpoints_helper( partition, dmraid_path ) )
return;
}
else
#endif
{
// Normal device, not DMRaid device
if ( set_mountpoints_helper( partition, partition.get_path() ) )
return;
}
if ( partition.get_mountpoints().empty() )
partition.push_back_message( _("Unable to find mount point") );
}
else // Not busy file system
{
partition.add_mountpoints( Mount_Info::get_fstab_mountpoints( partition.get_path() ) );
}
}
}
bool GParted_Core::set_mountpoints_helper( Partition & partition, const Glib::ustring & path )
{
Glib::ustring search_path ;
if (partition.fstype == FS_BTRFS)
search_path = btrfs::get_mount_device( path ) ;
else
search_path = path ;
const std::vector<Glib::ustring> & mountpoints = Mount_Info::get_mounted_mountpoints( search_path );
if ( mountpoints.size() )
{
partition.add_mountpoints( mountpoints );
partition.fs_readonly = Mount_Info::is_dev_mounted_readonly( search_path );
return true ;
}
return false;
}
// Report whether the partition is busy (mounted/active)
bool GParted_Core::is_busy(const Glib::ustring& device_path, FSType fstype, const Glib::ustring& partition_path)
{
FileSystem* p_filesystem = nullptr;
bool busy = false ;
if ( supported_filesystem( fstype ) )
{
switch ( get_fs( fstype ) .busy )
{
case FS::GPARTED:
//Search GParted internal mounted partitions map
busy = Mount_Info::is_dev_mounted(partition_path);
break ;
case FS::EXTERNAL:
//Call file system specific method
p_filesystem = get_filesystem_object( fstype ) ;
if ( p_filesystem )
busy = p_filesystem -> is_busy(partition_path) ;
break;
default:
break ;
}
}
else
{
DMRaid dmraid;
//Still search GParted internal mounted partitions map in case an
// unknown file system is mounted
busy = Mount_Info::is_dev_mounted(partition_path);
// Custom checks for recognised but other not-supported file system types.
busy |= (fstype == FS_LINUX_SWRAID && SWRaid_Info::is_member_active(partition_path));
busy |= (fstype == FS_ATARAID && (SWRaid_Info::is_member_active(partition_path) ||
dmraid.is_member_active(partition_path) ));
busy |= (fstype == FS_BCACHE && BCache_Info::is_active(device_path, partition_path));
busy |= (fstype == FS_JBD && Utils::is_dev_busy(partition_path));
}
return busy ;
}
void GParted_Core::set_used_sectors( Partition & partition, PedDisk* lp_disk )
{
if (supported_filesystem(partition.fstype))
{
FileSystem* p_filesystem = nullptr;
if ( partition.busy )
{
switch(get_fs(partition.fstype).online_read)
{
case FS::EXTERNAL:
p_filesystem = get_filesystem_object(partition.fstype);
if ( p_filesystem )
p_filesystem->set_used_sectors( partition );
break;
case FS::GPARTED:
mounted_fs_set_used_sectors( partition );
break;
default:
break;
}
}
else // Not busy file system
{
switch(get_fs(partition.fstype).read)
{
case FS::EXTERNAL:
p_filesystem = get_filesystem_object(partition.fstype);
if ( p_filesystem )
p_filesystem->set_used_sectors( partition );
break;
#ifdef HAVE_LIBPARTED_FS_RESIZE
case FS::LIBPARTED:
if ( lp_disk )
LP_set_used_sectors( partition, lp_disk );
break;
#endif
default:
break;
}
}
Sector unallocated;
// Only confirm that the above code succeeded in setting the sector usage
// values for this base Partition object, hence the explicit call to the
// base Partition class sector_usage_known() method. For LUKS this avoids
// calling derived PartitionLUKS class sector_usage_known() which also
// checks for known sector usage in the encrypted file system. But that
// wasn't set by the above code so in the case of luks/unknown would
// produce a false positive.
if ( ! partition.Partition::sector_usage_known() )
{
Glib::ustring temp = _("Unable to read the contents of this file system!");
temp += "\n";
temp += _("Because of this some operations may be unavailable.");
if (! Utils::get_filesystem_software(partition.fstype).empty())
{
temp += "\n";
temp += _("The cause might be a missing software package.");
temp += "\n";
/*TO TRANSLATORS: looks like The following list of software packages is required for NTFS file system support: ntfsprogs. */
temp += Glib::ustring::compose( _("The following list of software packages is required for %1 file system support: %2."),
Utils::get_filesystem_string(partition.fstype),
Utils::get_filesystem_software(partition.fstype)
);
}
partition.push_back_message( temp );
}
else if ( ( unallocated = partition.get_sectors_unallocated() ) > 0 )
{
/* TO TRANSLATORS: looks like 1.28GiB of unallocated space within the partition. */
Glib::ustring temp = Glib::ustring::compose( _("%1 of unallocated space within the partition."),
Utils::format_size( unallocated, partition.sector_size ) );
FS fs = get_fs(partition.fstype);
if ( fs.check != FS::NONE && fs.grow != FS::NONE )
{
temp += "\n";
/* TO TRANSLATORS: To grow the file system to fill the partition, select the partition and choose the menu item:
* means that the user can perform a check of the partition which will
* also grow the file system to fill the partition.
*/
temp += _("To grow the file system to fill the partition, select the partition and choose the menu item:");
temp += "\n";
temp += _("Partition --> Check.");
}
partition.push_back_message( temp );
}
if ( filesystem_resize_disallowed( partition ) )
{
Glib::ustring temp = get_filesystem_object(partition.fstype)
->get_custom_text( CTEXT_RESIZE_DISALLOWED_WARNING );
if ( ! temp.empty() )
partition.push_back_message( temp );
}
}
else
{
// Set usage of mounted but unsupported file systems.
if ( partition.busy )
mounted_fs_set_used_sectors(partition);
}
}
void GParted_Core::mounted_fs_set_used_sectors( Partition & partition )
{
if (partition.get_mountpoints().size() > 0 && Mount_Info::is_dev_mounted(partition.get_path()))
{
Byte_Value fs_size ;
Byte_Value fs_free ;
Glib::ustring error_message ;
if ( Utils::get_mounted_filesystem_usage( partition .get_mountpoint(),
fs_size, fs_free, error_message ) == 0 )
partition .set_sector_usage( fs_size / partition .sector_size,
fs_free / partition .sector_size ) ;
else
partition.push_back_message( error_message );
}
}
#ifdef HAVE_LIBPARTED_FS_RESIZE
void GParted_Core::LP_set_used_sectors( Partition & partition, PedDisk* lp_disk )
{
PedFileSystem* fs = nullptr;
PedConstraint* constraint = nullptr;
if ( lp_disk )
{
PedPartition* lp_partition = ped_disk_get_partition_by_sector( lp_disk, partition .get_sector() ) ;
if ( lp_partition )
{
fs = ped_file_system_open( & lp_partition ->geom );
if ( fs )
{
constraint = ped_file_system_get_resize_constraint( fs ) ;
if ( constraint )
{
partition .set_sector_usage( fs ->geom ->length,
fs ->geom ->length - constraint ->min_size ) ;
ped_constraint_destroy( constraint );
}
ped_file_system_close( fs ) ;
}
}
}
}
#endif
void GParted_Core::set_flags( Partition & partition, PedPartition* lp_partition )
{
for ( unsigned int t = 0 ; t < flags .size() ; t++ )
if ( ped_partition_is_flag_available( lp_partition, flags[ t ] ) &&
ped_partition_get_flag( lp_partition, flags[ t ] ) )
partition .flags .push_back( ped_partition_flag_get_name( flags[ t ] ) ) ;
}
bool GParted_Core::create( Partition & new_partition, OperationDetail & operationdetail )
{
bool success;
if ( new_partition.type == TYPE_EXTENDED )
{
success = create_partition( new_partition, operationdetail );
}
else
{
FS_Limits fs_limits = get_filesystem_limits(new_partition.fstype, new_partition);
success = create_partition( new_partition, operationdetail,
fs_limits.min_size / new_partition.sector_size );
}
if ( ! success )
return false;
if ( ! new_partition.name.empty() )
{
if ( ! name_partition( new_partition, operationdetail ) )
return false;
}
if (new_partition.type == TYPE_EXTENDED ||
new_partition.fstype == FS_UNFORMATTED )
return true;
else if (new_partition.fstype == FS_CLEARED)
return erase_filesystem_signatures( new_partition, operationdetail );
else
return erase_filesystem_signatures( new_partition, operationdetail )
&& set_partition_type( new_partition, operationdetail )
&& create_filesystem( new_partition, operationdetail );
return false;
}
bool GParted_Core::create_partition( Partition & new_partition, OperationDetail & operationdetail, Sector min_size )
{
operationdetail .add_child( OperationDetail( _("create empty partition") ) ) ;
new_partition .partition_number = 0 ;
PedDevice* lp_device = nullptr;
PedDisk* lp_disk = nullptr;
if ( get_device_and_disk( new_partition .device_path, lp_device, lp_disk ) )
{
PedPartitionType type;
PedConstraint* constraint = nullptr;
PedFileSystemType* fs_type = nullptr;
//create new partition
switch ( new_partition .type )
{
case TYPE_PRIMARY:
type = PED_PARTITION_NORMAL ;
break ;
case TYPE_LOGICAL:
type = PED_PARTITION_LOGICAL ;
break ;
case TYPE_EXTENDED:
type = PED_PARTITION_EXTENDED ;
break ;
default :
type = PED_PARTITION_FREESPACE;
}
if (new_partition.type != TYPE_EXTENDED)
fs_type = ped_file_system_type_get( "ext2" ) ;
PedPartition* lp_partition = ped_partition_new( lp_disk,
type,
fs_type,
new_partition .sector_start,
new_partition .sector_end ) ;
if ( lp_partition )
{
if ( new_partition .alignment == ALIGN_STRICT
|| new_partition .alignment == ALIGN_MEBIBYTE
)
{
PedGeometry *geom = ped_geometry_new( lp_device,
new_partition .sector_start,
new_partition .get_sector_length() ) ;
if ( geom )
{
constraint = ped_constraint_exact( geom ) ;
ped_geometry_destroy( geom );
}
}
else
constraint = ped_constraint_any( lp_device );
if ( constraint )
{
if ( min_size > 0
&& new_partition.fstype != FS_XFS // Permit copying to smaller xfs partition
)
constraint ->min_size = min_size ;
if ( ped_disk_add_partition( lp_disk, lp_partition, constraint ) && commit( lp_disk ) )
{
new_partition.set_path( get_partition_path( lp_partition ) );
new_partition .partition_number = lp_partition ->num ;
new_partition .sector_start = lp_partition ->geom .start ;
new_partition .sector_end = lp_partition ->geom .end ;
operationdetail .get_last_child() .add_child( OperationDetail(
/* TO TRANSLATORS: looks like path: /dev/sda1 (partition)
* This is showing the name and the fact
* that it is a partition within a device.
*/
Glib::ustring::compose( _("path: %1 (%2)"),
new_partition.get_path(), _("partition") ) + "\n" +
Glib::ustring::compose( _("start: %1"), new_partition .sector_start ) + "\n" +
Glib::ustring::compose( _("end: %1"), new_partition .sector_end ) + "\n" +
Glib::ustring::compose( _("size: %1 (%2)"),
new_partition .get_sector_length(),
Utils::format_size( new_partition .get_sector_length(), new_partition .sector_size ) ),
STATUS_NONE,
FONT_ITALIC ) ) ;
}
ped_constraint_destroy( constraint );
}
}
destroy_device_and_disk( lp_device, lp_disk ) ;
}
bool succes = new_partition .partition_number > 0 ;
#ifndef USE_LIBPARTED_DMRAID
//create dev map entries if dmraid
DMRaid dmraid ;
if ( succes && dmraid .is_dmraid_device( new_partition .device_path ) )
succes = dmraid .create_dev_map_entries( new_partition, operationdetail .get_last_child() ) ;
#endif
operationdetail.get_last_child().set_success_and_capture_errors( succes );
return succes ;
}
bool GParted_Core::create_filesystem( const Partition & partition, OperationDetail & operationdetail )
{
if (partition.fstype == FS_LUKS && partition.busy)
{
operationdetail.add_child( OperationDetail(
GPARTED_BUG + ": " + _("partition contains open LUKS encryption for a create file system only step"),
STATUS_ERROR, FONT_ITALIC ) );
return false;
}
operationdetail .add_child( OperationDetail( Glib::ustring::compose(
/*TO TRANSLATORS: looks like create new ext3 file system */
_("create new %1 file system"),
Utils::get_filesystem_string(partition.fstype))));
bool succes = false ;
FileSystem* p_filesystem = nullptr;
switch (get_fs(partition.fstype).create)
{
case FS::NONE:
break ;
case FS::GPARTED:
break ;
case FS::LIBPARTED:
break ;
case FS::EXTERNAL:
succes = (p_filesystem = get_filesystem_object(partition.fstype)) &&
p_filesystem ->create( partition, operationdetail .get_last_child() ) ;
break ;
default:
break ;
}
operationdetail.get_last_child().set_success_and_capture_errors( succes );
return succes ;
}
bool GParted_Core::format( const Partition & partition, OperationDetail & operationdetail )
{
if (partition.fstype == FS_LUKS && partition.busy)
{
operationdetail.add_child( OperationDetail(
GPARTED_BUG + ": " + _("partition contains open LUKS encryption for a format file system only step"),
STATUS_ERROR, FONT_ITALIC ) );
return false;
}
if (partition.fstype == FS_CLEARED)
return erase_filesystem_signatures(partition, operationdetail)
&& set_partition_type(partition, operationdetail);
else
return erase_filesystem_signatures( partition, operationdetail )
&& set_partition_type( partition, operationdetail )
&& create_filesystem( partition, operationdetail ) ;
}
bool GParted_Core::delete_partition( const Partition & partition, OperationDetail & operationdetail )
{
operationdetail .add_child( OperationDetail( _("delete partition") ) ) ;
bool succes = false ;
PedDevice* lp_device = nullptr;
PedDisk* lp_disk = nullptr;
if ( get_device_and_disk( partition .device_path, lp_device, lp_disk ) )
{
PedPartition* lp_partition = get_lp_partition( lp_disk, partition );
succes = ped_disk_delete_partition( lp_disk, lp_partition ) && commit( lp_disk ) ;
destroy_device_and_disk( lp_device, lp_disk ) ;
}
#ifndef USE_LIBPARTED_DMRAID
//delete partition dev mapper entry, and delete and recreate all other affected dev mapper entries if dmraid
DMRaid dmraid ;
if ( succes && dmraid .is_dmraid_device( partition .device_path ) )
{
PedDevice* lp_device = nullptr;
PedDisk* lp_disk = nullptr;
//Open disk handle before and close after to prevent application crash.
if ( get_device_and_disk( partition .device_path, lp_device, lp_disk ) )
{
if ( ! dmraid .delete_affected_dev_map_entries( partition, operationdetail .get_last_child() ) )
succes = false ; //comand failed
if ( ! dmraid .create_dev_map_entries( partition, operationdetail .get_last_child() ) )
succes = false ; //command failed
destroy_device_and_disk( lp_device, lp_disk ) ;
}
}
#endif
operationdetail.get_last_child().set_success_and_capture_errors( succes );
return succes ;
}
bool GParted_Core::remove_filesystem( const Partition & partition, OperationDetail & operationdetail )
{
if (partition.fstype == FS_LUKS && partition.busy)
{
operationdetail.add_child( OperationDetail(
GPARTED_BUG + ": " + _("partition contains open LUKS encryption for a delete file system only step"),
STATUS_ERROR, FONT_ITALIC ) );
return false;
}
bool success = true ;
FileSystem* p_filesystem = nullptr;
switch (get_fs(partition.fstype ).remove)
{
case FS::EXTERNAL:
//Run file system specific remove method to delete the file system. Most
// file systems should NOT implement a remove() method as it will prevent
// recovery from accidental partition deletion.
operationdetail .add_child( OperationDetail( Glib::ustring::compose(
_("delete %1 file system"),
Utils::get_filesystem_string(partition.fstype))));
success = (p_filesystem = get_filesystem_object(partition.fstype)) &&
p_filesystem ->remove( partition, operationdetail .get_last_child() ) ;
operationdetail.get_last_child().set_success_and_capture_errors( success );
break ;
default:
break ;
}
return success ;
}
bool GParted_Core::label_filesystem( const Partition & partition, OperationDetail & operationdetail )
{
if (partition.fstype == FS_LUKS && partition.busy)
{
operationdetail.add_child( OperationDetail(
GPARTED_BUG + ": " + _("partition contains open LUKS encryption for a label file system only step"),
STATUS_ERROR, FONT_ITALIC ) );
return false;
}
if( partition.get_filesystem_label().empty() ) {
operationdetail.add_child( OperationDetail(
Glib::ustring::compose( _("Clear file system label on %1"), partition.get_path() ) ) );
} else {
operationdetail.add_child( OperationDetail(
Glib::ustring::compose( _("Set file system label to \"%1\" on %2"),
partition.get_filesystem_label(), partition.get_path() ) ) );
}
bool succes = false ;
FileSystem* p_filesystem = nullptr;
const FS& fs_cap = get_fs(partition.fstype);
FS::Support support = (partition.busy) ? fs_cap.online_write_label : fs_cap.write_label;
switch (support)
{
case FS::EXTERNAL:
succes = (p_filesystem = get_filesystem_object(partition.fstype))
&& p_filesystem->write_label( partition, operationdetail.get_last_child() );
break;
default:
break;
}
operationdetail.get_last_child().set_success_and_capture_errors( succes );
return succes ;
}
bool GParted_Core::name_partition( const Partition & partition, OperationDetail & operationdetail )
{
if ( partition.name.empty() )
operationdetail.add_child( OperationDetail(
Glib::ustring::compose( _("Clear partition name on %1"), partition.get_path() ) ) );
else
operationdetail.add_child( OperationDetail(
Glib::ustring::compose( _("Set partition name to \"%1\" on %2"),
partition.name, partition.get_path() ) ) );
bool success = false;
PedDevice* lp_device = nullptr;
PedDisk* lp_disk = nullptr;
if ( get_device_and_disk( partition.device_path, lp_device, lp_disk ) )
{
PedPartition *lp_partition = ped_disk_get_partition_by_sector( lp_disk, partition.get_sector() );
if ( lp_partition )
{
success = ped_partition_set_name( lp_partition, partition.name.c_str() )
&& commit( lp_disk );
}
}
operationdetail.get_last_child().set_success_and_capture_errors( success );
return success;
}
bool GParted_Core::change_filesystem_uuid( const Partition & partition, OperationDetail & operationdetail )
{
if (partition.fstype == FS_LUKS && partition.busy)
{
operationdetail.add_child( OperationDetail(
GPARTED_BUG + ": " + _("partition contains open LUKS encryption for a change file system UUID only step"),
STATUS_ERROR, FONT_ITALIC ) );
return false;
}
if ( partition .uuid == UUID_RANDOM_NTFS_HALF ) {
operationdetail .add_child( OperationDetail( Glib::ustring::compose(
_("Set half of the UUID on %1 to a new, random value"),
partition .get_path()
) ) ) ;
} else {
operationdetail .add_child( OperationDetail( Glib::ustring::compose(
_("Set UUID on %1 to a new, random value"),
partition .get_path()
) ) ) ;
}
bool succes = false ;
FileSystem* p_filesystem = nullptr;
switch (get_fs(partition.fstype).write_uuid)
{
case FS::EXTERNAL:
succes = (p_filesystem = get_filesystem_object(partition.fstype))
&& p_filesystem->write_uuid( partition, operationdetail.get_last_child() );
break;
default:
break;
}
operationdetail.get_last_child().set_success_and_capture_errors( succes );
return succes ;
}
bool GParted_Core::resize_move( const Partition & partition_old,
Partition & partition_new,
OperationDetail & operationdetail )
{
if ( (partition_new .alignment == ALIGN_STRICT)
|| (partition_new .alignment == ALIGN_MEBIBYTE)
|| partition_new .strict_start
|| calculate_exact_geom( partition_old, partition_new, operationdetail )
)
{
if ( partition_old .type == TYPE_EXTENDED )
return resize_move_partition( partition_old, partition_new, operationdetail, false );
if ( partition_new .sector_start == partition_old .sector_start )
return resize( partition_old, partition_new, operationdetail ) ;
if ( partition_new .get_sector_length() == partition_old .get_sector_length() )
{
return move( partition_old, partition_new, operationdetail );
}
Partition* temp = nullptr;
if ( partition_new .get_sector_length() > partition_old .get_sector_length() )
{
//first move, then grow. Since old.length < new.length and new.start is valid, temp is valid.
temp = partition_new.clone();
temp->sector_end = temp->sector_start + partition_old.get_sector_length() - 1;
}
else // ( partition_new.get_sector_length() < partition_old.get_sector_length() )
{
//first shrink, then move. Since new.length < old.length and old.start is valid, temp is valid.
temp = partition_old.clone();
temp->sector_end = partition_old.sector_start + partition_new.get_sector_length() - 1;
}
PartitionAlignment previous_alignment = temp->alignment;
temp->alignment = ALIGN_STRICT;
bool success = resize_move( partition_old, *temp, operationdetail );
temp->alignment = previous_alignment;
if ( success )
success = resize_move( *temp, partition_new, operationdetail );
delete temp;
temp = nullptr;
return success;
}
return false ;
}
bool GParted_Core::move( const Partition & partition_old,
const Partition & partition_new,
OperationDetail & operationdetail )
{
if ( partition_old .get_sector_length() != partition_new .get_sector_length() )
{
operationdetail .add_child( OperationDetail(
/* TO TRANSLATORS:
* means that GParted has encountered a programming bug and tried
* to change the size of a partition when performing a move only
* step which is not permitted to change the partition size.
*/
GPARTED_BUG + ": " + _("size of the partition is changing for a move only step"),
STATUS_ERROR, FONT_ITALIC ) );
return false ;
}
if ( ! check_repair_filesystem( partition_old, operationdetail ) )
return false;
// NOTE:
// Logical partitions are preceded by meta data. To prevent this meta data from
// being overwritten we first expand the partition to encompass all of the space
// involved in the move. In this way we prevent overwriting the meta data for
// this partition when we move this partition to the left. We also prevent
// overwriting the meta data of a following partition when we move this partition
// to the right.
Partition * partition_all_space = partition_old.clone();
partition_all_space->alignment = ALIGN_STRICT;
if ( partition_new.sector_start < partition_all_space->sector_start )
partition_all_space->sector_start = partition_new.sector_start;
if ( partition_new.sector_end > partition_all_space->sector_end )
partition_all_space->sector_end = partition_new.sector_end;
// Make old partition all encompassing and if move file system fails then return
// partition table to original state
bool success = false;
if ( resize_move_partition( partition_old, *partition_all_space, operationdetail, true ) )
{
// Note move of file system is from old values to new values, not from the
// all encompassing values.
if ( ! move_filesystem( partition_old, partition_new, operationdetail ) )
{
operationdetail.add_child( OperationDetail( _("rollback last change to the partition") ) );
Partition * partition_restore = partition_old.clone();
partition_restore->alignment = ALIGN_STRICT; // Ensure that old partition boundaries are not modified
if ( resize_move_partition( *partition_all_space, *partition_restore,
operationdetail.get_last_child(), false ) )
{
operationdetail.get_last_child().set_success_and_capture_errors( true );
check_repair_filesystem( partition_old, operationdetail );
}
else
{
operationdetail.get_last_child().set_success_and_capture_errors( false );
}
delete partition_restore;
partition_restore = nullptr;
}
else
{
success = true;
}
}
// Make new partition from all encompassing partition
if ( success )
{
success = resize_move_partition( *partition_all_space, partition_new, operationdetail, false )
&& update_bootsector( partition_new, operationdetail );
}
delete partition_all_space;
partition_all_space = nullptr;
if ( ! success )
return false;
if (partition_new.fstype == FS_LINUX_SWAP)
// linux-swap is recreated, not moved
return recreate_linux_swap_filesystem( partition_new, operationdetail );
return true;
}
bool GParted_Core::move_filesystem( const Partition & partition_old,
const Partition & partition_new,
OperationDetail & operationdetail )
{
if ( partition_new .sector_start < partition_old .sector_start )
operationdetail .add_child( OperationDetail( _("move file system to the left") ) ) ;
else if ( partition_new .sector_start > partition_old .sector_start )
operationdetail .add_child( OperationDetail( _("move file system to the right") ) ) ;
else
{
operationdetail .add_child( OperationDetail( _("move file system") ) ) ;
operationdetail .get_last_child() .add_child(
OperationDetail( _("new and old file system have the same position. Hence skipping this operation"),
STATUS_NONE,
FONT_ITALIC ) ) ;
operationdetail.get_last_child().set_success_and_capture_errors( true );
return true ;
}
bool succes = false ;
FileSystem* p_filesystem = nullptr;
Sector total_done = 0;
switch (get_fs(partition_old.fstype).move)
{
case FS::NONE:
break ;
case FS::GPARTED:
succes = false ;
if ( partition_new .test_overlap( partition_old ) )
{
succes = copy_filesystem_internal( partition_old,
partition_new,
operationdetail.get_last_child(),
total_done,
true );
operationdetail.get_last_child().get_last_child()
.set_success_and_capture_errors( succes );
if ( ! succes )
{
rollback_move_filesystem( partition_old,
partition_new,
operationdetail.get_last_child(),
total_done );
}
}
else
succes = copy_filesystem_internal( partition_old,
partition_new,
operationdetail.get_last_child(),
total_done,
true );
break ;
case FS::LIBPARTED:
break ;
case FS::EXTERNAL:
succes = (p_filesystem = get_filesystem_object(partition_new.fstype)) &&
p_filesystem ->move( partition_old
, partition_new
, operationdetail .get_last_child()
) ;
break ;
default:
break ;
}
operationdetail.get_last_child().set_success_and_capture_errors( succes );
return succes ;
}
#ifdef HAVE_LIBPARTED_FS_RESIZE
bool GParted_Core::resize_move_filesystem_using_libparted( const Partition & partition_old,
const Partition & partition_new,
OperationDetail & operationdetail )
{
operationdetail .add_child( OperationDetail( _("using libparted"), STATUS_NONE ) ) ;
bool return_value = false ;
PedDevice* lp_device = nullptr;
PedDisk* lp_disk = nullptr;
if ( get_device_and_disk( partition_old .device_path, lp_device, lp_disk ) )
{
PedFileSystem* fs = nullptr;
PedGeometry* lp_geom = nullptr;
lp_geom = ped_geometry_new( lp_device,
partition_old .sector_start,
partition_old .get_sector_length() ) ;
if ( lp_geom )
{
fs = ped_file_system_open( lp_geom );
ped_geometry_destroy( lp_geom );
lp_geom = nullptr;
if ( fs )
{
lp_geom = ped_geometry_new( lp_device,
partition_new .sector_start,
partition_new .get_sector_length() ) ;
if ( lp_geom )
{
// Use thread for libparted FS resize call to avoid blocking GUI
Glib::Thread::create( sigc::bind<PedFileSystem *, PedGeometry *, bool *>(
sigc::mem_fun( *this, &GParted_Core::thread_lp_ped_file_system_resize ),
fs,
lp_geom,
&return_value ),
false );
Gtk::Main::run();
if ( return_value )
commit( lp_disk ) ;
ped_geometry_destroy( lp_geom );
}
ped_file_system_close( fs );
}
}
destroy_device_and_disk( lp_device, lp_disk ) ;
}
return return_value ;
}
void GParted_Core::thread_lp_ped_file_system_resize( PedFileSystem * fs,
PedGeometry * lp_geom,
bool * return_value )
{
*return_value = ped_file_system_resize(fs, lp_geom, nullptr);
g_idle_add((GSourceFunc)_mainquit, nullptr);
}
#endif
bool GParted_Core::resize( const Partition & partition_old,
const Partition & partition_new,
OperationDetail & operationdetail )
{
if ( partition_old .sector_start != partition_new .sector_start )
{
operationdetail .add_child( OperationDetail(
/* TO TRANSLATORS:
* means that GParted has encountered a programming bug and tried
* to move the start of the partition when performing a resize
* only step which is not permitted to change the start of the
* partition.
*/
GPARTED_BUG + ": " + _("start of the partition is changing for a resize only step"),
STATUS_ERROR, FONT_ITALIC ) );
return false ;
}
if (partition_new.fstype == FS_LUKS)
return resize_encryption( partition_old, partition_new, operationdetail );
else
return resize_plain( partition_old, partition_new, operationdetail );
}
bool GParted_Core::resize_encryption( const Partition & partition_old,
const Partition & partition_new,
OperationDetail & operationdetail )
{
if (partition_old.fstype != FS_LUKS)
{
operationdetail.add_child( OperationDetail(
GPARTED_BUG + ": " + _("partition does not contain LUKS encryption for a resize encryption only step"),
STATUS_ERROR, FONT_ITALIC ) );
return false;
}
Sector delta = partition_new.get_sector_length() - partition_old.get_sector_length();
if ( ! partition_old.busy && delta < 0LL )
{
operationdetail.add_child( OperationDetail(
GPARTED_BUG + ": " + _("impossible to shrink a closed LUKS encryption volume"),
STATUS_ERROR, FONT_ITALIC ) );
return false;
}
if ( ! partition_old.busy )
{
// grow closed LUKS.
// maximize_encryption() is only called to display no action needed
// operation message generated in luks::resize() for this case.
return resize_move_partition( partition_old, partition_new, operationdetail, true )
&& maximize_encryption( partition_new, operationdetail );
}
const Partition & filesystem_ptn_new = partition_new.get_filesystem_partition();
if (filesystem_ptn_new.fstype == FS_LINUX_SWAP)
{
// LUKS is resized, but linux-swap is recreated, not resized
if ( delta < 0LL ) // shrink
{
return shrink_encryption( partition_old, partition_new, operationdetail )
&& resize_move_partition( partition_old, partition_new, operationdetail, false )
&& recreate_linux_swap_filesystem( filesystem_ptn_new, operationdetail );
}
else if ( delta > 0LL ) // grow
{
return resize_move_partition( partition_old, partition_new, operationdetail, true )
&& maximize_encryption( partition_new, operationdetail )
&& recreate_linux_swap_filesystem( filesystem_ptn_new, operationdetail );
}
}
const Partition & filesystem_ptn_old = partition_old.get_filesystem_partition();
if ( delta < 0LL ) // shrink
{
return check_repair_filesystem( filesystem_ptn_old, operationdetail )
&& shrink_filesystem( filesystem_ptn_old, filesystem_ptn_new, operationdetail )
&& shrink_encryption( partition_old, partition_new, operationdetail )
&& resize_move_partition( partition_old, partition_new, operationdetail, false );
}
else if ( delta > 0LL ) // grow
{
return check_repair_filesystem( filesystem_ptn_old, operationdetail )
&& resize_move_partition( partition_old, partition_new, operationdetail, true )
&& maximize_encryption( partition_new, operationdetail )
&& maximize_filesystem( filesystem_ptn_new, operationdetail );
}
return true;
}
bool GParted_Core::resize_plain( const Partition & partition_old,
const Partition & partition_new,
OperationDetail & operationdetail )
{
if (partition_old.fstype == FS_LUKS && partition_old.busy)
{
operationdetail.add_child( OperationDetail(
GPARTED_BUG + ": " + _("partition contains open LUKS encryption for a resize file system only step"),
STATUS_ERROR, FONT_ITALIC ) );
return false;
}
if (partition_new.fstype == FS_LINUX_SWAP)
{
// linux-swap is recreated, not resized
return resize_move_partition( partition_old, partition_new, operationdetail, true )
&& recreate_linux_swap_filesystem( partition_new, operationdetail );
}
Sector delta = partition_new.get_sector_length() - partition_old.get_sector_length();
if ( delta < 0LL ) // shrink
{
return check_repair_filesystem( partition_new, operationdetail )
&& shrink_filesystem( partition_old, partition_new, operationdetail )
&& resize_move_partition( partition_old, partition_new, operationdetail, false );
}
else if ( delta > 0LL ) // grow
{
return check_repair_filesystem( partition_new, operationdetail )
&& resize_move_partition( partition_old, partition_new, operationdetail, true )
&& maximize_filesystem( partition_new, operationdetail );
}
return true;
}
bool GParted_Core::resize_move_partition( const Partition & partition_old,
const Partition & partition_new,
OperationDetail & operationdetail,
bool rollback_on_fail )
{
if ( partition_new.type == TYPE_UNPARTITIONED )
// Trying to resize/move a non-partitioned whole disk device is a
// successful non-operation.
return true;
//i'm not too happy with this, but i think it is the correct way from a i18n POV
enum Action
{
NONE = 0,
MOVE_RIGHT = 1,
MOVE_LEFT = 2,
GROW = 3,
SHRINK = 4,
MOVE_RIGHT_GROW = 5,
MOVE_RIGHT_SHRINK = 6,
MOVE_LEFT_GROW = 7,
MOVE_LEFT_SHRINK = 8
} ;
Action action = NONE ;
if ( partition_new .get_sector_length() > partition_old .get_sector_length() )
action = GROW ;
else if ( partition_new .get_sector_length() < partition_old .get_sector_length() )
action = SHRINK ;
if ( partition_new .sector_start > partition_old .sector_start &&
partition_new .sector_end > partition_old .sector_end )
action = action == GROW ? MOVE_RIGHT_GROW : action == SHRINK ? MOVE_RIGHT_SHRINK : MOVE_RIGHT ;
else if ( partition_new .sector_start < partition_old .sector_start &&
partition_new .sector_end < partition_old .sector_end )
action = action == GROW ? MOVE_LEFT_GROW : action == SHRINK ? MOVE_LEFT_SHRINK : MOVE_LEFT ;
Glib::ustring description ;
switch ( action )
{
case NONE :
description = _("resize/move partition") ;
break ;
case MOVE_RIGHT :
description = _("move partition to the right") ;
break ;
case MOVE_LEFT :
description = _("move partition to the left") ;
break ;
case GROW :
description = _("grow partition from %1 to %2") ;
break ;
case SHRINK :
description = _("shrink partition from %1 to %2") ;
break ;
case MOVE_RIGHT_GROW :
description = _("move partition to the right and grow it from %1 to %2") ;
break ;
case MOVE_RIGHT_SHRINK :
description = _("move partition to the right and shrink it from %1 to %2") ;
break ;
case MOVE_LEFT_GROW :
description = _("move partition to the left and grow it from %1 to %2") ;
break ;
case MOVE_LEFT_SHRINK :
description = _("move partition to the left and shrink it from %1 to %2") ;
break ;
}
if ( ! description .empty() && action != NONE && action != MOVE_LEFT && action != MOVE_RIGHT )
description = Glib::ustring::compose( description,
Utils::format_size( partition_old .get_sector_length(), partition_old .sector_size ),
Utils::format_size( partition_new .get_sector_length(), partition_new .sector_size ) ) ;
operationdetail .add_child( OperationDetail( description ) ) ;
if ( action == NONE )
{
operationdetail .get_last_child() .add_child(
OperationDetail( _("new and old partition have the same size and position. Hence skipping this operation"),
STATUS_NONE,
FONT_ITALIC ) ) ;
operationdetail.get_last_child().set_success_and_capture_errors( true );
return true ;
}
operationdetail .get_last_child() .add_child(
OperationDetail(
Glib::ustring::compose( _("old start: %1"), partition_old .sector_start ) + "\n" +
Glib::ustring::compose( _("old end: %1"), partition_old .sector_end ) + "\n" +
Glib::ustring::compose( _("old size: %1 (%2)"),
partition_old .get_sector_length(),
Utils::format_size( partition_old .get_sector_length(), partition_old .sector_size ) ),
STATUS_NONE,
FONT_ITALIC ) ) ;
//finally the actual resize/move
Sector new_start = -1;
Sector new_end = -1;
bool success = resize_move_partition_implement( partition_old, partition_new, new_start, new_end );
if ( success )
{
//Change to partition succeeded
operationdetail .get_last_child() .add_child(
OperationDetail(
Glib::ustring::compose( _("new start: %1"), new_start ) + "\n" +
Glib::ustring::compose( _("new end: %1"), new_end ) + "\n" +
Glib::ustring::compose( _("new size: %1 (%2)"),
new_end - new_start + 1,
Utils::format_size( new_end - new_start + 1, partition_new .sector_size ) ),
STATUS_NONE,
FONT_ITALIC ) ) ;
//update dev mapper entry if partition is dmraid.
success = success && update_dmraid_entry( partition_new, operationdetail );
}
else
{
//Change to partition failed
operationdetail .get_last_child() .add_child(
OperationDetail(
Glib::ustring::compose( _("requested start: %1"), partition_new .sector_start ) + "\n" +
Glib::ustring::compose( _("requested end: %1"), partition_new . sector_end ) + "\n" +
Glib::ustring::compose( _("requested size: %1 (%2)"),
partition_new .get_sector_length(),
Utils::format_size( partition_new .get_sector_length(), partition_new .sector_size ) ),
STATUS_NONE,
FONT_ITALIC )
) ;
}
operationdetail.get_last_child().set_success_and_capture_errors( success );
if ( ! success && rollback_on_fail )
{
operationdetail.add_child(
OperationDetail( _("attempt to rollback failed change to the partition") ) );
// Create a temporary partition which is the intersection of the old and
// new partitions so that the middle sector can be used to identify the
// partition needing rollback, whether or not the above failed partition
// change made it to the drive or not.
Partition *partition_intersection = partition_new.clone();
partition_intersection->sector_start = std::max( partition_old.sector_start,
partition_new.sector_start );
partition_intersection->sector_end = std::min( partition_old.sector_end,
partition_new.sector_end );
Partition *partition_restore = partition_old.clone();
// Ensure that old partition boundaries are not modified
partition_restore->alignment = ALIGN_STRICT;
bool rollback_success = resize_move_partition_implement( *partition_intersection, *partition_restore,
new_start, new_end );
operationdetail.get_last_child().add_child(
OperationDetail(
Glib::ustring::compose( _("original start: %1"), partition_restore->sector_start ) + "\n" +
Glib::ustring::compose( _("original end: %1"), partition_restore->sector_end ) + "\n" +
Glib::ustring::compose( _("original size: %1 (%2)"),
partition_restore->get_sector_length(),
Utils::format_size( partition_restore->get_sector_length(), partition_restore->sector_size ) ),
STATUS_NONE, FONT_ITALIC ) );
// Update dev mapper entry if partition is dmraid.
rollback_success = rollback_success && update_dmraid_entry( *partition_restore, operationdetail );
delete partition_restore;
partition_restore = nullptr;
delete partition_intersection;
partition_intersection = nullptr;
operationdetail.get_last_child().set_success_and_capture_errors( rollback_success );
}
return success;
}
bool GParted_Core::resize_move_partition_implement( const Partition & partition_old,
const Partition & partition_new,
Sector & new_start,
Sector & new_end )
{
bool success = false;
PedDevice* lp_device = nullptr;
PedDisk* lp_disk = nullptr;
if ( get_device_and_disk( partition_old.device_path, lp_device, lp_disk ) )
{
PedPartition *lp_partition = get_lp_partition( lp_disk, partition_old );
if ( lp_partition )
{
PedConstraint* constraint = nullptr;
if ( partition_new.alignment == ALIGN_STRICT ||
partition_new.alignment == ALIGN_MEBIBYTE ||
partition_new.strict_start )
{
PedGeometry *geom = ped_geometry_new( lp_device,
partition_new.sector_start,
partition_new.get_sector_length() );
if ( geom )
{
constraint = ped_constraint_exact( geom );
ped_geometry_destroy( geom );
}
}
else
{
constraint = ped_constraint_any( lp_device );
}
if ( constraint )
{
if ( ped_disk_set_partition_geom( lp_disk,
lp_partition,
constraint,
partition_new.sector_start,
partition_new.sector_end ) )
{
new_start = lp_partition->geom.start;
new_end = lp_partition->geom.end;
success = commit( lp_disk );
}
ped_constraint_destroy( constraint );
}
}
destroy_device_and_disk( lp_device, lp_disk );
}
return success;
}
bool GParted_Core::shrink_encryption( const Partition & partition_old,
const Partition & partition_new,
OperationDetail & operationdetail )
{
if (! (partition_old.fstype == FS_LUKS && partition_old.busy))
{
operationdetail.add_child( OperationDetail(
GPARTED_BUG + ": " + _("partition does not contain open LUKS encryption for a shrink encryption only step"),
STATUS_ERROR, FONT_ITALIC ) );
return false;
}
operationdetail.add_child( OperationDetail( _("shrink encryption volume") ) );
bool success = resize_filesystem_implement( partition_old, partition_new, operationdetail );
operationdetail.get_last_child().set_success_and_capture_errors( success );
return success;
}
bool GParted_Core::maximize_encryption( const Partition & partition, OperationDetail & operationdetail )
{
if (partition.fstype != FS_LUKS)
{
operationdetail.add_child( OperationDetail(
GPARTED_BUG + ": " + _("partition does not contain LUKS encryption for a maximize encryption only step"),
STATUS_ERROR, FONT_ITALIC ) );
return false;
}
operationdetail.add_child( OperationDetail( _("grow encryption volume to fill the partition") ) );
// Checking if growing is allowed is only relevant for the check repair operation
// to inform the user why the grow step is being skipped. For a resize/move
// operation these growing checks are merely retesting those performed to allow
// the operation to be queued in the first place. See
// Win_GParted::set_valid_operations().
if (get_fs(partition.fstype).grow == FS::NONE)
{
operationdetail.get_last_child().add_child( OperationDetail(
_("growing is not available for this encryption volume"),
STATUS_NONE, FONT_ITALIC ) );
operationdetail.get_last_child().set_status( STATUS_WARNING );
return true;
}
bool success = resize_filesystem_implement( partition, partition, operationdetail );
operationdetail.get_last_child().set_success_and_capture_errors( success );
return success;
}
bool GParted_Core::shrink_filesystem( const Partition & partition_old,
const Partition & partition_new,
OperationDetail & operationdetail )
{
if (partition_old.fstype == FS_LUKS && partition_old.busy)
{
operationdetail.add_child( OperationDetail(
GPARTED_BUG + ": " + _("partition contains open LUKS encryption for a shrink file system only step"),
STATUS_ERROR, FONT_ITALIC ) );
return false;
}
if ( partition_new.get_sector_length() >= partition_old.get_sector_length() )
{
operationdetail.add_child( OperationDetail(
/* TO TRANSLATORS:
* means that GParted has encountered a programming bug and tried
* to grow the partition size or keep it the same when performing
* a shrink partition only step.
*/
GPARTED_BUG + ": " + _("the new partition size is larger or the same for a shrink only step"),
STATUS_ERROR, FONT_ITALIC ) );
return false;
}
operationdetail.add_child( OperationDetail( _("shrink file system") ) );
bool success = resize_filesystem_implement( partition_old, partition_new, operationdetail );
operationdetail.get_last_child().set_success_and_capture_errors( success );
return success;
}
bool GParted_Core::maximize_filesystem( const Partition & partition, OperationDetail & operationdetail )
{
if (partition.fstype == FS_LUKS && partition.busy)
{
operationdetail.add_child( OperationDetail(
GPARTED_BUG + ": " + _("partition contains open LUKS encryption for a maximize file system only step"),
STATUS_ERROR, FONT_ITALIC ) );
return false;
}
operationdetail .add_child( OperationDetail( _("grow file system to fill the partition") ) ) ;
// Checking if growing is available or allowed is only relevant for the check
// repair operation to inform the user why the grow step is being skipped. For a
// resize/move operation these growing checks are merely retesting those performed
// to allow the operation to be queued in the first place. See
// Win_GParted::set_valid_operations() and
// Dialog_Partition_Resize_Move::Resize_Move_Normal().
if (get_fs(partition.fstype).grow == FS::NONE)
{
operationdetail .get_last_child() .add_child(
OperationDetail( _("growing is not available for this file system"),
STATUS_NONE,
FONT_ITALIC ) ) ;
operationdetail.get_last_child().set_status( STATUS_WARNING );
return true ;
}
else if ( filesystem_resize_disallowed( partition ) )
{
Glib::ustring msg = _("growing the file system is currently disallowed") ;
Glib::ustring custom_msg = get_filesystem_object(partition.fstype)
->get_custom_text( CTEXT_RESIZE_DISALLOWED_WARNING ) ;
if ( ! custom_msg .empty() )
{
msg += "\n" ;
msg += custom_msg ;
}
operationdetail .get_last_child() .add_child( OperationDetail( msg, STATUS_NONE, FONT_ITALIC ) ) ;
operationdetail.get_last_child().set_status( STATUS_WARNING );
return true ;
}
bool success = resize_filesystem_implement( partition, partition, operationdetail );
operationdetail.get_last_child().set_success_and_capture_errors( success );
return success;
}
bool GParted_Core::recreate_linux_swap_filesystem( const Partition & partition, OperationDetail & operationdetail )
{
if (partition.fstype != FS_LINUX_SWAP)
{
operationdetail.add_child( OperationDetail(
/* TO TRANSLATORS: looks like not a linux-swap file system for a recreate linux-swap only step */
GPARTED_BUG + ": " + Glib::ustring::compose( _("not a %1 file system for a recreate %1 only step"),
Utils::get_filesystem_string( FS_LINUX_SWAP ),
Utils::get_filesystem_string( FS_LINUX_SWAP ) ),
STATUS_ERROR, FONT_ITALIC ) );
return false;
}
if ( ! erase_filesystem_signatures( partition, operationdetail ) )
return false;
operationdetail.add_child( OperationDetail(
/* TO TRANSLATORS: looks like recreate linux-swap file system */
Glib::ustring::compose( _("recreate %1 file system"),
Utils::get_filesystem_string( FS_LINUX_SWAP ) ) ) );
// Linux-swap is recreated by using the linux_swap::resize() method
bool success = resize_filesystem_implement( partition, partition, operationdetail );
operationdetail.get_last_child().set_success_and_capture_errors( success );
return success;
}
bool GParted_Core::resize_filesystem_implement( const Partition & partition_old,
const Partition & partition_new,
OperationDetail & operationdetail )
{
bool fill_partition = false;
const FS& fs_cap = get_fs(partition_new.fstype);
FS::Support action = FS::NONE;
if ( partition_new.get_sector_length() >= partition_old.get_sector_length() )
{
// grow (always maximises the file system to fill the partition)
fill_partition = true;
action = ( partition_old.busy ) ? fs_cap.online_grow : fs_cap.grow;
}
else
{
// shrink
fill_partition = false;
action = ( partition_old.busy ) ? fs_cap.online_shrink : fs_cap.shrink;
}
bool success = false;
FileSystem* p_filesystem = nullptr;
switch ( action )
{
case FS::NONE:
break;
case FS::GPARTED:
break;
#ifdef HAVE_LIBPARTED_FS_RESIZE
case FS::LIBPARTED:
success = resize_move_filesystem_using_libparted( partition_old,
partition_new,
operationdetail.get_last_child() );
break;
#endif
case FS::EXTERNAL:
success = (p_filesystem = get_filesystem_object(partition_new.fstype)) &&
p_filesystem->resize( partition_new,
operationdetail.get_last_child(),
fill_partition );
break;
default:
break;
}
return success;
}
bool GParted_Core::copy( const Partition & partition_src,
Partition & partition_dst,
OperationDetail & operationdetail )
{
const Partition & filesystem_ptn_src = partition_src.get_filesystem_partition();
Partition & filesystem_ptn_dst = partition_dst.get_filesystem_partition();
if ( filesystem_ptn_dst.get_byte_length() < filesystem_ptn_src.get_byte_length()
&& filesystem_ptn_src.fstype != FS_XFS // Permit copying to smaller xfs partition
)
{
operationdetail .add_child( OperationDetail(
_("the destination is smaller than the source partition"), STATUS_ERROR, FONT_ITALIC ) ) ;
return false ;
}
if ( partition_dst.status == STAT_COPY )
{
// Handle situation where src sector size is smaller than dst sector size
// and an additional partial dst sector is required.
Sector min_size = ( partition_src.get_byte_length() + partition_dst.sector_size - 1 ) /
partition_dst.sector_size;
if ( ! create_partition( partition_dst, operationdetail, min_size ) )
return false;
}
// NOTE:
// Deliberately call set_partition_type() on the Partition object directly
// containing the file system, rather than the Partition object containing the
// partition. When copying into an existing open LUKS encryption mapping this
// will avoid changing partition type, where as when copying into a plain
// partition the type will be set.
bool success = set_partition_type( filesystem_ptn_dst, operationdetail )
&& copy_filesystem( filesystem_ptn_src, filesystem_ptn_dst, operationdetail )
&& update_bootsector( partition_dst, operationdetail );
if ( ! success )
return false;
if (filesystem_ptn_dst.fstype == FS_LINUX_SWAP)
{
// linux-swap is recreated, not copied
return recreate_linux_swap_filesystem( filesystem_ptn_dst, operationdetail );
}
if (! check_repair_filesystem(filesystem_ptn_dst, operationdetail))
return false;
if (filesystem_ptn_dst.get_byte_length() > filesystem_ptn_src.get_byte_length())
{
// Copied into a bigger partition so maximise file system
return maximize_filesystem(filesystem_ptn_dst, operationdetail);
}
return true;
}
bool GParted_Core::copy_filesystem( const Partition & partition_src,
Partition & partition_dst,
OperationDetail & operationdetail )
{
if (partition_src.fstype == FS_LUKS && partition_src.busy)
{
operationdetail.add_child( OperationDetail(
GPARTED_BUG + ": " + _("source partition contains open LUKS encryption for a file system copy only step"),
STATUS_ERROR, FONT_ITALIC ) );
return false;
}
if (partition_dst.fstype == FS_LUKS && partition_dst.busy)
{
operationdetail.add_child( OperationDetail(
GPARTED_BUG + ": " + _("destination partition contains open LUKS encryption for a file system copy only step"),
STATUS_ERROR, FONT_ITALIC ) );
return false;
}
operationdetail.add_child( OperationDetail(
Glib::ustring::compose( _("copy file system from %1 to %2"),
partition_src.get_path(),
partition_dst.get_path() ) ) );
bool success = false;
FileSystem* p_filesystem = nullptr;
switch (get_fs(partition_dst.fstype).copy)
{
case FS::GPARTED:
success = copy_filesystem_internal( partition_src,
partition_dst,
operationdetail.get_last_child(),
true );
break;
case FS::LIBPARTED:
// FIXME: see if copying through libparted has any advantages
break;
case FS::EXTERNAL:
success = (p_filesystem = get_filesystem_object(partition_dst.fstype)) &&
p_filesystem->copy( partition_src,
partition_dst,
operationdetail.get_last_child() );
break;
default:
break;
}
operationdetail.get_last_child().set_success_and_capture_errors( success );
return success;
}
bool GParted_Core::copy_filesystem_internal( const Partition & partition_src,
const Partition & partition_dst,
OperationDetail & operationdetail,
bool cancel_safe )
{
Sector dummy ;
return copy_blocks( partition_src.device_path,
partition_dst.device_path,
partition_src.sector_start,
partition_dst.sector_start,
partition_src.sector_size,
partition_dst.sector_size,
partition_src.get_byte_length(),
operationdetail,
dummy,
cancel_safe );
}
bool GParted_Core::copy_filesystem_internal( const Partition & partition_src,
const Partition & partition_dst,
OperationDetail & operationdetail,
Byte_Value & total_done,
bool cancel_safe )
{
return copy_blocks( partition_src.device_path,
partition_dst.device_path,
partition_src.sector_start,
partition_dst.sector_start,
partition_src.sector_size,
partition_dst.sector_size,
partition_src.get_byte_length(),
operationdetail,
total_done,
cancel_safe );
}
bool GParted_Core::copy_blocks( const Glib::ustring & src_device,
const Glib::ustring & dst_device,
Sector src_start,
Sector dst_start,
Byte_Value src_sector_size,
Byte_Value dst_sector_size,
Byte_Value src_length,
OperationDetail & operationdetail,
Byte_Value & total_done,
bool cancel_safe )
{
operationdetail .add_child( OperationDetail( _("using internal algorithm"), STATUS_NONE ) ) ;
operationdetail .add_child( OperationDetail(
Glib::ustring::compose( /*TO TRANSLATORS: looks like copy 1.00 MiB */
_("copy %1"), Utils::format_size( src_length, 1 ) ),
STATUS_NONE ) ) ;
operationdetail .add_child( OperationDetail( _("finding optimal block size"), STATUS_NONE ) ) ;
Byte_Value benchmark_blocksize = (1 * MEBIBYTE) ;
Byte_Value benchmark_copysize = 16 * MEBIBYTE;
Byte_Value optimal_blocksize = benchmark_blocksize ;
Sector offset_read = src_start ;
Sector offset_write = dst_start ;
//Handle situation where we need to perform the copy beginning
// with the end of the partition and finishing with the start.
if ( dst_start > src_start )
{
offset_read += (src_length/src_sector_size) - (benchmark_copysize/src_sector_size);
/* Handle situation where src sector size is smaller than dst sector size and an additional partial dst sector is required. */
offset_write += ((src_length + (dst_sector_size - 1))/dst_sector_size) - (benchmark_copysize/dst_sector_size);
}
total_done = 0 ;
Byte_Value done = 0 ;
Glib::Timer timer ;
double smallest_time = 1000000 ;
bool succes = true ;
OperationDetail & benchmark_od = operationdetail.get_last_child();
//Benchmark copy times using different block sizes to determine optimal size
while (succes &&
llabs(done) + benchmark_copysize <= src_length &&
benchmark_blocksize <= benchmark_copysize )
{
benchmark_od.add_child( OperationDetail(
/*TO TRANSLATORS: looks like copy 16.00 MiB using a block size of 1.00 MiB */
Glib::ustring::compose(_("copy %1 using a block size of %2"),
Utils::format_size(benchmark_copysize, 1),
Utils::format_size(benchmark_blocksize, 1))));
timer .reset() ;
succes = CopyBlocks( src_device,
dst_device,
offset_read + (done / src_sector_size),
offset_write + (done / dst_sector_size),
benchmark_copysize,
benchmark_blocksize,
benchmark_od,
total_done,
src_length,
cancel_safe ).copy();
timer.stop() ;
benchmark_od.get_last_child().add_child( OperationDetail(
Glib::ustring::compose( _("%1 seconds"), timer .elapsed() ), STATUS_NONE, FONT_ITALIC ) ) ;
benchmark_od.get_last_child().set_success_and_capture_errors( succes );
if ( timer .elapsed() <= smallest_time )
{
smallest_time = timer .elapsed() ;
optimal_blocksize = benchmark_blocksize ;
}
benchmark_blocksize *= 2 ;
if ( ( dst_start > src_start ) )
done -= benchmark_copysize;
else
done += benchmark_copysize;
}
if ( succes )
operationdetail .get_last_child() .add_child( OperationDetail( Glib::ustring::compose(
/*TO TRANSLATORS: looks like optimal block size is 1.00 MiB */
_("optimal block size is %1"),
Utils::format_size( optimal_blocksize, 1 ) ),
STATUS_NONE ) ) ;
if ( succes && llabs( done ) < src_length )
{
Byte_Value remaining_length = src_length - llabs( done );
operationdetail.add_child( OperationDetail(
/*TO TRANSLATORS: looks like copy 16.00 MiB using a block size of 1.00 MiB */
Glib::ustring::compose( _("copy %1 using a block size of %2"),
Utils::format_size( remaining_length, 1 ),
Utils::format_size( optimal_blocksize, 1 ) ) ) );
succes = CopyBlocks( src_device,
dst_device,
src_start + ((done > 0 ? done : 0) / src_sector_size),
dst_start + ((done > 0 ? done : 0) / dst_sector_size),
remaining_length,
optimal_blocksize,
operationdetail,
total_done,
src_length,
cancel_safe ).copy();
operationdetail.get_last_child().set_success_and_capture_errors( succes );
}
operationdetail .add_child( OperationDetail(
Glib::ustring::compose( /*TO TRANSLATORS: looks like 1.00 MiB (1048576 B) copied */
_("%1 (%2 B) copied"), Utils::format_size( total_done, 1 ), total_done ),
STATUS_NONE ) ) ;
return succes ;
}
void GParted_Core::rollback_move_filesystem( const Partition & partition_src,
const Partition & partition_dst,
OperationDetail & operationdetail,
Byte_Value total_done )
{
if ( total_done > 0 )
{
//find out exactly which part of the file system was copied (and to where it was copied)..
Partition * temp_src = partition_src.clone();
Partition * temp_dst = partition_dst.clone();
bool rollback_needed = true;
if ( partition_dst .sector_start > partition_src .sector_start )
{
Sector distance = partition_dst.sector_start - partition_src.sector_start;
temp_src->sector_start = temp_src->sector_end - ( (total_done / temp_src->sector_size) - 1 ) + distance;
temp_dst->sector_start = temp_dst->sector_end - ( (total_done / temp_dst->sector_size) - 1 ) + distance;
if ( temp_src->sector_start > temp_src->sector_end )
// Nothing has been overwritten yet, so nothing to roll back
rollback_needed = false;
}
else
{
Sector distance = partition_src.sector_start - partition_dst.sector_start;
temp_src->sector_end = temp_src->sector_start + ( (total_done / temp_src->sector_size) - 1 ) - distance;
temp_dst->sector_end = temp_dst->sector_start + ( (total_done / temp_dst->sector_size) - 1 ) - distance;
if ( temp_src->sector_start > temp_src->sector_end )
// Nothing has been overwritten yet, so nothing to roll back
rollback_needed = false;
}
if ( rollback_needed )
{
operationdetail.add_child( OperationDetail( _("rollback failed file system move") ) );
//and copy it back (NOTE the reversed dst and src)
bool success = copy_filesystem_internal( *temp_dst,
*temp_src,
operationdetail.get_last_child(),
false );
operationdetail.get_last_child().set_success_and_capture_errors( success );
}
delete temp_src;
delete temp_dst;
temp_src = nullptr;
temp_dst = nullptr;
}
}
bool GParted_Core::check_repair_filesystem( const Partition & partition, OperationDetail & operationdetail )
{
if (partition.fstype == FS_LUKS && partition.busy)
{
operationdetail.add_child( OperationDetail(
GPARTED_BUG + ": " + _("partition contains open LUKS encryption for a check file system only step"),
STATUS_ERROR, FONT_ITALIC ) );
return false;
}
if ( partition.busy )
// Trying to check an online file system is a successful non-operation.
return true;
operationdetail .add_child( OperationDetail(
Glib::ustring::compose(
/* TO TRANSLATORS: looks like check file system on /dev/sda5 for errors and (if possible) fix them */
_("check file system on %1 for errors and (if possible) fix them"),
partition .get_path() ) ) ) ;
bool succes = false ;
FileSystem* p_filesystem = nullptr;
switch (get_fs(partition.fstype).check)
{
case FS::NONE:
operationdetail .get_last_child() .add_child(
OperationDetail( _("checking is not available for this file system"),
STATUS_NONE,
FONT_ITALIC ) ) ;
operationdetail.get_last_child().set_status( STATUS_WARNING );
return true ;
break ;
case FS::GPARTED:
break ;
case FS::LIBPARTED:
break ;
case FS::EXTERNAL:
succes = (p_filesystem = get_filesystem_object(partition.fstype)) &&
p_filesystem ->check_repair( partition, operationdetail .get_last_child() ) ;
break ;
default:
break ;
}
operationdetail.get_last_child().set_success_and_capture_errors( succes );
return succes ;
}
bool GParted_Core::check_repair_maximize( const Partition & partition,
OperationDetail & operationdetail )
{
if (partition.fstype == FS_LUKS)
{
// Pretend that the LUKS partition is closed so that
// resize_filesystem_implement() checks the offline .grow capability
// rather than .online_grow so that maximising LUKS in the context of a
// check encrypted file system operation, which doesn't want to change the
// partition boundaries, can be performed even if libparted and/or the
// kernel aren't new enough to support online partition resizing.
// luks::resize() determines the open status of the LUKS mapping by
// querying the LUKS_Info module cache so doesn't care if the partition
// busy status is wrong.
Partition * temp_offline = partition.clone();
temp_offline->busy = false;
bool success = maximize_encryption( *temp_offline, operationdetail );
delete temp_offline;
temp_offline = nullptr;
if ( ! success )
return false;
if ( partition.busy )
success = maximize_filesystem( partition.get_filesystem_partition(), operationdetail );
return success;
}
else
{
return maximize_filesystem( partition, operationdetail );
}
}
bool GParted_Core::set_partition_type( const Partition & partition, OperationDetail & operationdetail )
{
if ( partition.type == TYPE_UNPARTITIONED )
// Trying to set the type of a partition on a non-partitioned whole disk
// device is a successful non-operation.
return true;
operationdetail .add_child( OperationDetail(
Glib::ustring::compose( _("set partition type on %1"), partition .get_path() ) ) ) ;
//Set partition type appropriately for the type of file system stored in the partition.
// Libparted treats every type as a file system, except LVM which it treats as a flag.
bool return_value = false ;
PedDevice* lp_device = nullptr;
PedDisk* lp_disk = nullptr;
if ( get_device_and_disk( partition .device_path, lp_device, lp_disk ) )
{
PedPartition* lp_partition = ped_disk_get_partition_by_sector( lp_disk, partition.get_sector() );
if ( lp_partition )
{
Glib::ustring fs_type = Utils::get_filesystem_string(partition.fstype);
// Lookup libparted file system type using GParted's name, as most
// match. Exclude cleared as the name won't be recognised by
// libparted and get_filesystem_string() has also translated it.
PedFileSystemType* lp_fs_type = nullptr;
if (partition.fstype != FS_CLEARED)
lp_fs_type = ped_file_system_type_get(fs_type.c_str());
// Ensure that UDF and exFAT get the required partition type even
// when libparted doesn't know, or is to old to know, about them.
// Required partition types:
// * [on MBR] 07 IFS (Installable File System)
// * [on GPT] BDP (Basic Data Partition)
// Use NTFS to achieve this.
// References:
// * What is the partition id / filesystem type for UDF?
// https://serverfault.com/a/829172
// * exFAT file system specification
// https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification
// 10.2 Partition Tables
if (! lp_fs_type && (partition.fstype == FS_UDF || partition.fstype == FS_EXFAT))
lp_fs_type = ped_file_system_type_get( "ntfs" );
// default is Linux (83)
if ( ! lp_fs_type )
lp_fs_type = ped_file_system_type_get( "ext2" );
bool supports_lvm_flag = ped_partition_is_flag_available( lp_partition, PED_PARTITION_LVM );
if (lp_fs_type && partition.fstype != FS_LVM2_PV)
{
// Also clear any libparted LVM flag so that it doesn't
// override the file system type
if ( ( ! supports_lvm_flag ||
ped_partition_set_flag( lp_partition, PED_PARTITION_LVM, 0 ) ) &&
ped_partition_set_system( lp_partition, lp_fs_type ) &&
commit( lp_disk ) )
{
operationdetail.get_last_child().add_child(
/* TO TRANSLATORS: looks like new partition type: ext4 */
OperationDetail( Glib::ustring::compose( _("new partition type: %1"),
lp_partition->fs_type->name ),
STATUS_NONE,
FONT_ITALIC ) );
return_value = true;
}
}
else if (partition.fstype == FS_LVM2_PV)
{
if ( supports_lvm_flag &&
ped_partition_set_flag( lp_partition, PED_PARTITION_LVM, 1 ) &&
commit( lp_disk ) )
{
operationdetail.get_last_child().add_child(
/* TO TRANSLATORS: looks like new partition flag: lvm */
OperationDetail( Glib::ustring::compose( _("new partition flag: %1"),
ped_partition_flag_get_name( PED_PARTITION_LVM ) ),
STATUS_NONE,
FONT_ITALIC ) );
return_value = true;
}
else if ( ! supports_lvm_flag )
{
// Skip setting the lvm flag because the partition
// table type doesn't support it. Applies to dvh
// and pc98 disk labels.
return_value = true;
}
}
}
destroy_device_and_disk( lp_device, lp_disk ) ;
}
operationdetail.get_last_child().set_success_and_capture_errors( return_value );
return return_value ;
}
bool GParted_Core::calibrate_partition( Partition & partition, OperationDetail & operationdetail )
{
if ( partition.type == TYPE_PRIMARY || partition.type == TYPE_LOGICAL ||
partition.type == TYPE_EXTENDED || partition.type == TYPE_UNPARTITIONED )
{
const Glib::ustring& curr_path = partition.get_path();
operationdetail.add_child( OperationDetail( Glib::ustring::compose( _("calibrate %1"), curr_path ) ) );
bool success = false;
PedDevice* lp_device = nullptr;
PedDisk* lp_disk = nullptr;
if ( get_device( partition.device_path, lp_device ) )
{
if ( partition.type == TYPE_UNPARTITIONED )
{
// Virtual partition spanning whole disk device
success = true;
}
else if ( get_disk( lp_device, lp_disk ) )
{
// Partitioned device
PedPartition *lp_partition = get_lp_partition( lp_disk, partition );
if ( lp_partition ) // FIXME: add check to see if lp_partition->type matches partition.type..
{
if ( ! file_test( curr_path, Glib::FILE_TEST_EXISTS ) )
{
// Re-set the real partition path from libparted.
//
// When creating a copy operation by pasting into
// unallocated space the path for the partition
// object was set to "Copy of /dev/SRC" because
// the partition didn't yet exist before the
// operations were applied. Additional operations
// on that new partition also got the path set to
// "Copy of /dev/SRC". This re-sets the real path
// to "/dev/NEW". This provides the real path for
// file system specific tools used during those
// additional operations such mkfs for the format
// operation or fsck and others for the
// resize/move operation.
partition.set_path( get_partition_path( lp_partition ) );
}
// Reload the partition boundaries from libparted
// to ensure that GParted knows what the actual
// partition boundaries are before applying the
// next operation. Necessary when the previous
// operation in the sequence was a resize/move
// operation where GParted may have only estimated
// where libparted would move the boundaries to.
partition.sector_start = lp_partition->geom.start;
partition.sector_end = lp_partition->geom.end;
success = true;
}
}
// else error from get_disk() reading partition table
if ( success )
{
operationdetail .get_last_child() .add_child(
OperationDetail(
/* TO TRANSLATORS: looks like path: /dev/sda (device)
* or looks like path: /dev/sda1 (partition)
* This is showing the name and whether it
* is a whole disk device or a partition
* within a device.
*/
Glib::ustring::compose( _("path: %1 (%2)"),
partition.get_path(),
( partition.type == TYPE_UNPARTITIONED )
? _("device")
: _("partition") ) + "\n" +
Glib::ustring::compose( _("start: %1"), partition .sector_start ) + "\n" +
Glib::ustring::compose( _("end: %1"), partition .sector_end ) + "\n" +
Glib::ustring::compose( _("size: %1 (%2)"),
partition .get_sector_length(),
Utils::format_size( partition .get_sector_length(), partition .sector_size ) ),
STATUS_NONE,
FONT_ITALIC ) ) ;
if (partition.fstype == FS_LUKS && partition.busy)
{
const Partition & encrypted = dynamic_cast<const PartitionLUKS *>( &partition )->get_encrypted();
operationdetail.get_last_child().add_child( OperationDetail(
Glib::ustring::compose( _("encryption path: %1"), encrypted.get_path() ),
STATUS_NONE, FONT_ITALIC ) );
}
}
destroy_device_and_disk( lp_device, lp_disk ) ;
}
operationdetail.get_last_child().set_success_and_capture_errors( success );
return success;
}
else //nothing to calibrate...
return true ;
}
bool GParted_Core::calculate_exact_geom( const Partition & partition_old,
Partition & partition_new,
OperationDetail & operationdetail )
{
operationdetail .add_child( OperationDetail(
Glib::ustring::compose( _("calculate new size and position of %1"), partition_new .get_path() ) ) ) ;
operationdetail .get_last_child() .add_child(
OperationDetail(
Glib::ustring::compose( _("requested start: %1"), partition_new .sector_start ) + "\n" +
Glib::ustring::compose( _("requested end: %1"), partition_new .sector_end ) + "\n" +
Glib::ustring::compose( _("requested size: %1 (%2)"),
partition_new .get_sector_length(),
Utils::format_size( partition_new .get_sector_length(), partition_new .sector_size ) ),
STATUS_NONE,
FONT_ITALIC ) ) ;
bool succes = false ;
PedDevice* lp_device = nullptr;
PedDisk* lp_disk = nullptr;
if ( get_device_and_disk( partition_old .device_path, lp_device, lp_disk ) )
{
PedPartition* lp_partition = get_lp_partition( lp_disk, partition_old );
if ( lp_partition )
{
PedConstraint* constraint = nullptr;
constraint = ped_constraint_any( lp_device ) ;
if ( constraint )
{
//FIXME: if we insert a weird partitionnew geom here (e.g. start > end)
//ped_disk_set_partition_geom() will still return true (although an lp exception is written
//to stdout.. see if this also affect create_partition and resize_move_partition
//sended a patch to fix this to libparted list. will probably be in 1.7.2
if ( ped_disk_set_partition_geom( lp_disk,
lp_partition,
constraint,
partition_new .sector_start,
partition_new .sector_end ) )
{
partition_new .sector_start = lp_partition ->geom .start ;
partition_new .sector_end = lp_partition ->geom .end ;
succes = true ;
}
ped_constraint_destroy( constraint );
}
}
destroy_device_and_disk( lp_device, lp_disk ) ;
}
if ( succes )
{
operationdetail .get_last_child() .add_child(
OperationDetail(
Glib::ustring::compose( _("new start: %1"), partition_new .sector_start ) + "\n" +
Glib::ustring::compose( _("new end: %1"), partition_new .sector_end ) + "\n" +
Glib::ustring::compose( _("new size: %1 (%2)"),
partition_new .get_sector_length(),
Utils::format_size( partition_new .get_sector_length(), partition_new .sector_size ) ),
STATUS_NONE,
FONT_ITALIC ) ) ;
//Update dev mapper entry if partition is dmraid.
succes = succes && update_dmraid_entry( partition_new, operationdetail );
}
operationdetail.get_last_child().set_success_and_capture_errors( succes );
return succes ;
}
bool GParted_Core::update_dmraid_entry( const Partition & partition, OperationDetail & operationdetail )
{
bool success = true;
#ifndef USE_LIBPARTED_DMRAID
DMRaid dmraid;
if ( dmraid.is_dmraid_device( partition.device_path ) )
{
PedDevice* lp_device = nullptr;
PedDisk *lp_disk;
// Open disk handle before and close after to prevent application crash.
if ( get_device_and_disk( partition.device_path, lp_device, lp_disk ) )
{
success = dmraid.update_dev_map_entry( partition, operationdetail.get_last_child() );
destroy_device_and_disk( lp_device, lp_disk );
}
}
#endif
return success;
}
FileSystem * GParted_Core::get_filesystem_object( FSType fstype )
{
return supported_filesystems->get_fs_object(fstype);
}
// Return true for file systems with an implementation class, false otherwise
bool GParted_Core::supported_filesystem( FSType fstype )
{
return supported_filesystems->get_fs_object(fstype) != nullptr;
}
FS_Limits GParted_Core::get_filesystem_limits( FSType fstype, const Partition & partition )
{
FileSystem* p_filesystem = supported_filesystems->get_fs_object(fstype);
FS_Limits fs_limits;
if (p_filesystem != nullptr)
fs_limits = p_filesystem->get_filesystem_limits( partition );
return fs_limits;
}
bool GParted_Core::filesystem_resize_disallowed( const Partition & partition )
{
if (partition.fstype == FS_LVM2_PV)
{
//The LVM2 PV can't be resized when it's a member of an export VG
const Glib::ustring& vgname = LVM2_PV_Info::get_vg_name(partition.get_path());
if ( vgname .empty() )
return false ;
return LVM2_PV_Info::is_vg_exported( vgname );
}
return false ;
}
bool GParted_Core::erase_filesystem_signatures( const Partition & partition, OperationDetail & operationdetail )
{
if (partition.fstype == FS_LUKS && partition.busy)
{
operationdetail.add_child( OperationDetail(
GPARTED_BUG + ": " + _("partition contains open LUKS encryption for an erase file system signatures only step"),
STATUS_ERROR, FONT_ITALIC ) );
return false;
}
bool overall_success = false ;
operationdetail .add_child( OperationDetail(
Glib::ustring::compose( _("clear old file system signatures in %1"),
partition .get_path() ) ) ) ;
//Get device, disk & partition and open the device. Allocate buffer and fill with
// zeros. Buffer size is the greater of 4 KiB and the sector size.
PedDevice* lp_device = nullptr;
PedDisk* lp_disk = nullptr;
PedPartition* lp_partition = nullptr;
PedGeometry* lp_geom = nullptr;
bool device_is_open = false ;
Byte_Value bufsize = 4LL * KIBIBYTE ;
char* buf = nullptr;
if ( get_device( partition.device_path, lp_device ) )
{
if ( partition.type == TYPE_UNPARTITIONED )
{
// Whole disk device; create a matching geometry
lp_geom = ped_geometry_new(lp_device,
partition.sector_start,
partition.get_sector_length());
}
else if ( get_disk( lp_device, lp_disk ) )
{
// Partitioned device; copy partition geometry
lp_partition = ped_disk_get_partition_by_sector( lp_disk, partition.get_sector() );
if (lp_partition)
lp_geom = ped_geometry_duplicate(&lp_partition->geom);
}
if (lp_geom != nullptr && ped_device_open(lp_device))
{
device_is_open = true ;
bufsize = std::max( bufsize, lp_device ->sector_size ) ;
buf = static_cast<char *>( malloc( bufsize ) ) ;
if ( buf )
memset( buf, 0, bufsize ) ;
}
overall_success = device_is_open;
}
// Erase all file system super blocks, including their signatures. The specified
// byte ranges are converted to whole sectors (as disks fundamentally only read or
// write whole sectors) and written using ped_geometry_write(). Therefore don't
// try to surgically overwrite just the few bytes of each signature as this code
// overwrites whole sectors and it embeds more knowledge than is necessary.
//
// First byte range from offset 0, length 512 KiB covers ZFS Labels L0 and L1 and
// all other super blocks at the start of the device.
// ZFS On-Disk Specification
// http://www.giis.co.in/Zfs_ondiskformat.pdf
//
// Last byte range from offset -512 KiB (from the end), length 768 KiB covers ZFS
// Labels L2 and L3 and other super blocks at the end of the device. Includes:
// * Linux Software RAID metadata 0.90 and 1.0
// * secondary Nilfs2 super block
// * Almost all the various ATARAID types
//
// Not covered by the above are:
// * Btrfs super block mirror copies
// * One possible location of Promise FastTrack RAID super block
//
// Btrfs super blocks are located at: 64 KiB, 64 MiB, 256 GiB and 1 PiB. The
// super block at 64 KiB will be erased by the zeroing from offset 0. The super
// block mirror copies need to be explicitly cleared.
// Btrfs: On-disk Format, Superblock
// https://btrfs.wiki.kernel.org/index.php/On-disk_Format#Superblock
//
// Promise FastTrack RAID super block may be located at any of these 512-byte
// sectors before the end of the device: -16, -63, -255, -256, -399, -591, -675,
// -753, -911, -951, -974, -991, -3087. All from -991 and smaller offsets from
// the end of the device will be erased by the zeroing from -512 KiB. Possible
// location -3087 must be explicitly cleared.
// util-linux v2.38.1: libblkid/src/subperblocks/promise_raid.c:probe_pdcraid()
// https://git.kernel.org/pub/scm/utils/util-linux/util-linux.git/tree/libblkid/src/superblocks/promise_raid.c?h=v2.38.1#n27
struct {
Byte_Value offset; // Negative offsets work backwards from the end of the partition
Byte_Value rounding; // Minimum desired rounding for offset
Byte_Value length;
} ranges[] = {
//offset , rounding , length
{ 0LL , 1LL , 512LL * KIBIBYTE }, // Super blocks at start
{ 64LL * MEBIBYTE, 1LL , 4LL * KIBIBYTE }, // Btrfs super block mirror copy
{ 256LL * GIBIBYTE, 1LL , 4LL * KIBIBYTE }, // Btrfs super block mirror copy
{ 1LL * PEBIBYTE, 1LL , 4LL * KIBIBYTE }, // Btrfs super block mirror copy
{ -3087LL * 512LL , 1LL , 512LL }, // Promise FastTrack RAID super block
{ -512LL * KIBIBYTE, 256LL * KIBIBYTE, 768LL * KIBIBYTE } // Super blocks at end
} ;
for ( unsigned int i = 0 ; overall_success && i < sizeof( ranges ) / sizeof( ranges[0] ) ; i ++ )
{
//Rounding is performed in multiples of the sector size because writes are in whole sectors.
Byte_Value rounding_size = Utils::ceil_size( ranges[i].rounding, lp_device ->sector_size ) ;
Byte_Value byte_offset ;
Byte_Value byte_len ;
// Compute range to be erased. Starting position takes into account
// negative offsets from the end of the partition and minimum desired
// rounding requirements. Length is only rounded to device sector size.
// Ranges may become larger, but not smaller than requested.
if ( ranges[i] .offset >= 0LL )
{
byte_offset = Utils::floor_size( ranges[i] .offset, rounding_size ) ;
byte_len = Utils::ceil_size( ranges[i].offset + ranges[i].length, lp_device->sector_size )
- byte_offset ;
}
else //Negative offsets
{
Byte_Value notional_offset = Utils::floor_size( partition .get_byte_length() + ranges[i] .offset, ranges[i]. rounding ) ;
byte_offset = Utils::floor_size( notional_offset, rounding_size ) ;
byte_len = Utils::ceil_size( notional_offset + ranges[i].length, lp_device->sector_size )
- byte_offset ;
}
//Limit range to partition size.
if ( byte_offset + byte_len <= 0LL )
{
//Byte range entirely before partition start. Programmer error!
continue;
}
else if ( byte_offset < 0 )
{
//Byte range spans partition start. Trim to fit.
byte_len += byte_offset ;
byte_offset = 0LL ;
}
if ( byte_offset >= partition .get_byte_length() )
{
//Byte range entirely after partition end. Ignore.
continue ;
}
else if ( byte_offset + byte_len > partition .get_byte_length() )
{
//Byte range spans partition end. Trim to fit.
byte_len = partition .get_byte_length() - byte_offset ;
}
OperationDetail & od = operationdetail .get_last_child() ;
Byte_Value written = 0LL ;
bool zero_success = false ;
if ( device_is_open && buf )
{
od .add_child( OperationDetail(
/*TO TRANSLATORS: looks like write 68.00 KiB of zeros at byte offset 0 */
Glib::ustring::compose( "write %1 of zeros at byte offset %2",
Utils::format_size( byte_len, 1 ),
byte_offset ) ) ) ;
while ( written < byte_len )
{
//Write in bufsize amounts. Last write may be smaller but
// will still be a whole number of sectors.
Byte_Value amount = std::min( bufsize, byte_len - written ) ;
zero_success = ped_geometry_write(lp_geom,
buf,
(byte_offset + written) / lp_device->sector_size,
amount / lp_device->sector_size);
if ( ! zero_success )
break ;
written += amount ;
}
od.get_last_child().set_success_and_capture_errors( zero_success );
}
overall_success &= zero_success ;
}
if ( buf )
free( buf ) ;
if (lp_geom != nullptr)
ped_geometry_destroy(lp_geom);
//Linux kernel doesn't maintain buffer cache coherency between the whole disk
// device and partition devices. So even though the file system signatures
// have been overwritten on the disk via a partition device, libparted reading
// via the whole disk device will read the old data and report the file system
// as still existing.
//
// Calling ped_device_sync() to flush the cache is required when the partition is
// just being cleared. However the sync can be skipped when the wipe is being
// performed as part of writing a new file system as the partition type is also
// changed, which modified the partition table so GParted calls
// ped_disk_commit_to_os(). This instructs the kernel to remove all non-busy
// partitions on the disk and re-adds them, thus effectively flushing the cache
// of the disk.
//
// Opening the device and calling ped_device_sync() took 0.15 seconds or less on
// a 5400 RPM laptop hard drive. For now just always call ped_device_sync() as
// it doesn't add much time to the overall operation.
if ( overall_success )
{
OperationDetail & od = operationdetail .get_last_child() ;
od .add_child( OperationDetail( Glib::ustring::compose( _("flush operating system cache of %1"),
lp_device ->path ) ) ) ;
bool flush_success = false ;
if ( device_is_open )
{
flush_success = ped_device_sync( lp_device ) ;
ped_device_close( lp_device ) ;
// (#83) Wait for udev rules to complete after this
// ped_device_close() to avoid busy /dev/DISK entry when running
// following file system specific manipulation commands on the
// whole disk device in format(), after this
// erase_filesystem_signatures().
settle_device(SETTLE_DEVICE_APPLY_MAX_WAIT_SECONDS);
}
od.get_last_child().set_success_and_capture_errors( flush_success );
overall_success &= flush_success ;
}
destroy_device_and_disk( lp_device, lp_disk ) ;
operationdetail.get_last_child().set_success_and_capture_errors( overall_success );
return overall_success ;
}
bool GParted_Core::update_bootsector(const Partition& partition, OperationDetail& operationdetail)
{
// Only for NTFS.
// FIXME: This should probably be done in the FS classes.
if (partition.fstype != FS_NTFS)
return true;
// The NTFS file system stores a value in the boot record called the Number of
// Hidden Sectors. This value must match the partition start sector number in
// order for Windows to boot from the file system.
// References to the NTFS Partition Boot Sector (PBS):
// https://en.wikipedia.org/wiki/NTFS#Partition_Boot_Sector_(PBS)
// https://thestarman.pcministry.com/asm/mbr/NTFSBR.htm
operationdetail.add_child(OperationDetail(
/* TO TRANSLATORS: looks like update boot sector of ntfs file system on /dev/sdd1 */
Glib::ustring::compose(_("update boot sector of %1 file system on %2"),
Utils::get_filesystem_string(partition.fstype),
partition.get_path())));
uint32_t hidden_sectors = 0;
bool beyond_32bit = false;
if ((partition.sector_start >> 32) == 0)
{
hidden_sectors = htole32(static_cast<uint32_t>(partition.sector_start & 0xFFFFFFFF));
}
else
{
operationdetail.get_last_child().add_child(OperationDetail(
Glib::ustring::compose(_("Partition start (%1) is beyond sector 4294967295 (2^32-1).\n"
"Windows will not be able to boot from this file system."),
partition.sector_start),
STATUS_NONE, FONT_ITALIC));
beyond_32bit = true;
}
std::ofstream dev_file;
dev_file.open(partition.get_path().c_str(), std::ios::out|std::ios::binary);
if (! dev_file)
{
/* TO TRANSLATORS: looks like Error trying to open /dev/sdd1 */
operationdetail.get_last_child().add_child(OperationDetail(
Glib::ustring::compose(_("Error trying to open %1"), partition.get_path()),
STATUS_NONE, FONT_ITALIC));
operationdetail.get_last_child().set_success_and_capture_errors(false);
return false;
}
dev_file.seekp(0x1C);
if (! dev_file)
{
/* TO TRANSLATORS: looks like Error trying to seek to position 0x1C in /dev/sdd1 */
operationdetail.get_last_child().add_child(OperationDetail(
Glib::ustring::compose(_("Error trying to seek to position 0x1c in %1"),
partition.get_path()),
STATUS_NONE, FONT_ITALIC));
operationdetail.get_last_child().set_success_and_capture_errors(false);
dev_file.close();
return false;
}
dev_file.write(reinterpret_cast<const char *>(&hidden_sectors), sizeof(hidden_sectors));
dev_file.flush();
dev_file.close();
if (! dev_file)
{
/* TO TRANSLATORS: looks like Error trying to write to boot sector in /dev/sdd1 */
operationdetail.get_last_child().add_child(OperationDetail(
Glib::ustring::compose(_("Error trying to write to boot sector in %1"),
partition.get_path()),
STATUS_NONE, FONT_ITALIC));
operationdetail.get_last_child().set_success_and_capture_errors(false);
return false;
}
operationdetail.get_last_child().set_success_and_capture_errors(true);
if (beyond_32bit)
operationdetail.get_last_child().set_status(STATUS_WARNING);
return true;
}
void GParted_Core::capture_libparted_messages( OperationDetail & operationdetail, bool success )
{
if ( libparted_messages.size() > 0 )
{
operationdetail.add_child( OperationDetail( _("libparted messages"),
success ? STATUS_INFO : STATUS_ERROR ) );
for ( unsigned int i = 0 ; i < libparted_messages.size() ; i++ )
operationdetail.get_last_child().add_child(
OperationDetail( libparted_messages[i], STATUS_NONE, FONT_ITALIC ) );
libparted_messages.clear();
}
}
bool GParted_Core::useable_device(const PedDevice* lp_device)
{
g_assert(lp_device != nullptr); // Bug: Not initialised by call to ped_device_get() or ped_device_get_next()
char * buf = static_cast<char *>( malloc( lp_device->sector_size ) );
if ( ! buf )
return false;
// Must be able to read from the first sector before the disk device is considered
// useable in GParted.
bool success = false;
int fd = open(lp_device->path, O_RDONLY|O_NONBLOCK);
if (fd >= 0)
{
ssize_t bytes_read = read(fd, buf, lp_device->sector_size);
success = (bytes_read == lp_device->sector_size);
close(fd);
}
free( buf );
return success;
}
//Flush the Linux kernel caches, and therefore ensure coherency between the caches of the
// whole disk device and the partition devices.
//
// Libparted >= 2.0 works around this by calling ioctl(fd, BLKFLSBUF) to flush the cache
// when opening the whole disk device, but only for kernels before 2.6.0.
// Ref: parted v3.1-52-g1c659d5 ./libparted/arch/linux.c linux_open()
// 1657 /* With kernels < 2.6 flush cache for cache coherence issues */
// 1658 if (!_have_kern26())
// 1659 _flush_cache (dev);
//
// Libparted >= v3.1-61-gfb99ba5 works around this for all kernel versions.
// Ref: parted v3.1-61-gfb99ba5 ./libparted/arch/linux.c linux_open()
// 1640 _flush_cache (dev);
bool GParted_Core::flush_device( PedDevice * lp_device )
{
bool success = false ;
if ( ped_device_open( lp_device ) )
{
success = ped_device_sync( lp_device ) ;
ped_device_close( lp_device ) ;
// (!46) Wait for udev rules to complete after this ped_device_open() and
// ped_device_close() pair to avoid busy /dev/DISK entry when running file
// system specific querying commands on the whole disk device in the call
// sequence after get_device() in set_device_from_disk().
settle_device(SETTLE_DEVICE_PROBE_MAX_WAIT_SECONDS);
}
return success ;
}
bool GParted_Core::get_device( const Glib::ustring & device_path, PedDevice *& lp_device, bool flush )
{
lp_device = ped_device_get( device_path.c_str() );
if ( lp_device )
{
if ( flush )
// Force cache coherency before going on to read the partition
// table so that libparted reading the whole disk device and the
// file system tools reading the partition devices read the same
// data.
flush_device( lp_device );
return true;
}
return false;
}
bool GParted_Core::get_disk(PedDevice *lp_device, PedDisk*& lp_disk)
{
g_assert(lp_device != nullptr); // Bug: Not initialised by call to ped_device_get() or ped_device_get_next()
lp_disk = ped_disk_new(lp_device);
// (#762941)(!46) After ped_disk_new() wait for triggered udev rules to complete
// which remove and re-add all the partition specific /dev entries to avoid FS
// specific commands failing because they happen to be running when the needed
// /dev/PTN entries don't exist.
settle_device(SETTLE_DEVICE_PROBE_MAX_WAIT_SECONDS);
return (lp_disk != nullptr);
}
bool GParted_Core::get_device_and_disk(const Glib::ustring& device_path,
PedDevice*& lp_device, PedDisk*& lp_disk, bool flush)
{
if ( get_device( device_path, lp_device, flush ) )
{
if (get_disk(lp_device, lp_disk))
return true;
destroy_device_and_disk(lp_device, lp_disk);
}
return false;
}
void GParted_Core::destroy_device_and_disk( PedDevice*& lp_device, PedDisk*& lp_disk )
{
if ( lp_disk )
ped_disk_destroy( lp_disk ) ;
lp_disk = nullptr;
if ( lp_device )
ped_device_destroy( lp_device ) ;
lp_device = nullptr;
}
bool GParted_Core::commit( PedDisk* lp_disk )
{
// (#790418) Hold a file handle open across the ped_disk_commit_to_dev() and
// commit_to_os()->ped_disk_commit_to_os() calls to avoid libparted having to open
// and close the device twice itself. This avoids the kernel and udev events
// removing and re-adding partitions exactly when ped_disk_commit_to_os() is
// trying to doing the same thing.
bool opened = ped_device_open( lp_disk->dev );
bool succes = ped_disk_commit_to_dev( lp_disk ) ;
succes = commit_to_os( lp_disk, SETTLE_DEVICE_APPLY_MAX_WAIT_SECONDS ) && succes;
if ( opened )
{
ped_device_close( lp_disk->dev );
// Wait for udev rules to complete and partition device nodes to settle
// from this ped_device_close().
settle_device( SETTLE_DEVICE_APPLY_MAX_WAIT_SECONDS );
}
return succes ;
}
bool GParted_Core::commit_to_os( PedDisk* lp_disk, std::time_t timeout )
{
bool succes ;
#ifndef USE_LIBPARTED_DMRAID
DMRaid dmraid ;
if ( dmraid .is_dmraid_device( lp_disk ->dev ->path ) )
succes = true ;
else
{
#endif
succes = ped_disk_commit_to_os( lp_disk ) ;
#ifndef USE_LIBPARTED_DMRAID
}
#endif
// Wait for udev rules to complete and partition device nodes to settle from above
// ped_disk_commit_to_os() initiated kernel update of the partitions.
settle_device( timeout ) ;
return succes ;
}
void GParted_Core::settle_device( std::time_t timeout )
{
if (udevadm_found)
Utils::execute_command( "udevadm settle --timeout=" + Utils::num_to_str( timeout ) ) ;
else
sleep( timeout ) ;
}
PedPartition* GParted_Core::get_lp_partition( const PedDisk* lp_disk, const Partition & partition )
{
if ( partition.type == TYPE_EXTENDED )
return ped_disk_extended_partition( lp_disk );
return ped_disk_get_partition_by_sector( lp_disk, partition.get_sector() );
}
class PedExceptionMsg : public Gtk::MessageDialog
{
public:
PedExceptionMsg( PedException &e ) : MessageDialog( Glib::ustring(e.message), false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_NONE, true )
{
switch( e.type )
{
case PED_EXCEPTION_INFORMATION:
set_title( _("Libparted Information") );
property_message_type() = Gtk::MESSAGE_INFO;
break;
case PED_EXCEPTION_WARNING:
set_title( _("Libparted Warning") );
property_message_type() = Gtk::MESSAGE_WARNING;
break;
case PED_EXCEPTION_ERROR:
set_title( _("Libparted Error") );
break;
case PED_EXCEPTION_FATAL:
set_title( _("Libparted Fatal") );
break;
case PED_EXCEPTION_BUG:
set_title( _("Libparted Bug") );
break;
case PED_EXCEPTION_NO_FEATURE:
set_title( _("Libparted Unsupported Feature") );
break;
default:
set_title( _("Libparted unknown exception") );
break;
}
if (e.options & PED_EXCEPTION_FIX)
add_button( _("Fix"), PED_EXCEPTION_FIX );
if (e.options & PED_EXCEPTION_YES)
add_button( _("Yes"), PED_EXCEPTION_YES );
if (e.options & PED_EXCEPTION_OK)
add_button( _("Ok"), PED_EXCEPTION_OK );
if (e.options & PED_EXCEPTION_RETRY)
add_button( _("Retry"), PED_EXCEPTION_RETRY );
if (e.options & PED_EXCEPTION_NO)
add_button( _("No"), PED_EXCEPTION_NO );
if (e.options & PED_EXCEPTION_CANCEL)
add_button( _("Cancel"), PED_EXCEPTION_CANCEL );
if (e.options & PED_EXCEPTION_IGNORE)
add_button( _("Ignore"), PED_EXCEPTION_IGNORE );
}
};
struct ped_exception_ctx {
PedExceptionOption ret;
PedException *e;
Glib::Mutex mutex;
Glib::Cond cond;
};
static bool _ped_exception_handler( struct ped_exception_ctx *ctx )
{
std::cerr << ctx->e->message << std::endl;
libparted_messages.push_back( ctx->e->message );
char optcount = 0;
int opt = 0;
for( char c = 0; c < 10; c++ )
if( ctx->e->options & (1 << c) ) {
opt = (1 << c);
optcount++;
}
// if only one option was given, choose it without popup
if( optcount == 1 && ctx->e->type != PED_EXCEPTION_BUG && ctx->e->type != PED_EXCEPTION_FATAL )
{
ctx->ret = (PedExceptionOption)opt;
ctx->mutex.lock();
ctx->cond.signal();
ctx->mutex.unlock();
return false;
}
PedExceptionMsg msg( *ctx->e );
msg.show_all();
ctx->ret = (PedExceptionOption)msg.run();
if (ctx->ret < 0)
ctx->ret = PED_EXCEPTION_UNHANDLED;
ctx->mutex.lock();
ctx->cond.signal();
ctx->mutex.unlock();
return false;
}
PedExceptionOption GParted_Core::ped_exception_handler( PedException * e )
{
struct ped_exception_ctx ctx;
ctx.ret = PED_EXCEPTION_UNHANDLED;
ctx.e = e;
if (Glib::Thread::self() != GParted_Core::mainthread) {
ctx.mutex.lock();
g_idle_add( (GSourceFunc)_ped_exception_handler, &ctx );
ctx.cond.wait( ctx.mutex );
ctx.mutex.unlock();
} else _ped_exception_handler( &ctx );
return ctx.ret;
}
GParted_Core::~GParted_Core()
{
delete supported_filesystems;
supported_filesystems = nullptr;
}
Glib::Thread *GParted_Core::mainthread;
SupportedFileSystems* GParted_Core::supported_filesystems;
} //GParted