418 lines
15 KiB
C++
418 lines
15 KiB
C++
/* Copyright (C) 2004 Bart
|
|
* Copyright (C) 2008, 2009, 2010, 2011 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 "ext2.h"
|
|
#include "FileSystem.h"
|
|
#include "OperationDetail.h"
|
|
#include "Partition.h"
|
|
#include "Utils.h"
|
|
|
|
#include <glibmm/ustring.h>
|
|
|
|
namespace GParted
|
|
{
|
|
|
|
FS ext2::get_filesystem_support()
|
|
{
|
|
FS fs( specific_type );
|
|
|
|
fs .busy = FS::GPARTED ;
|
|
|
|
// Only enable functionality if the relevant mkfs.extX command is found to ensure
|
|
// that the version of e2fsprogs is new enough to support ext4. Applying to
|
|
// ext2/3 is OK as relevant mkfs.ext2/3 commands exist.
|
|
mkfs_cmd = "mkfs." + Utils::get_filesystem_string( specific_type );
|
|
if ( ! Glib::find_program_in_path( mkfs_cmd ).empty() )
|
|
{
|
|
fs .create = FS::EXTERNAL ;
|
|
fs .create_with_label = FS::EXTERNAL ;
|
|
|
|
// Determine mkfs.ext4 version specific capabilities.
|
|
bool have_64bit_feature = false;
|
|
force_auto_64bit = false;
|
|
if ( specific_type == FS_EXT4 )
|
|
{
|
|
Utils::execute_command( mkfs_cmd + " -V", output, error, true );
|
|
int mke4fs_major_ver = 0;
|
|
int mke4fs_minor_ver = 0;
|
|
int mke4fs_patch_ver = 0;
|
|
if ( sscanf( error.c_str(), "mke%*[24]fs %d.%d.%d",
|
|
&mke4fs_major_ver, &mke4fs_minor_ver, &mke4fs_patch_ver ) >= 2 )
|
|
{
|
|
// Ext4 64bit feature was added in e2fsprogs 1.42, but
|
|
// only enable large volumes from 1.42.9 when a large
|
|
// number of 64bit bugs were fixed.
|
|
// * Release notes, E2fsprogs 1.42 (November 29, 2011)
|
|
// http://e2fsprogs.sourceforge.net/e2fsprogs-release.html#1.42
|
|
// * Release notes, E2fsprogs 1.42.9 (December 28, 2013)
|
|
// http://e2fsprogs.sourceforge.net/e2fsprogs-release.html#1.42.9
|
|
have_64bit_feature = ( mke4fs_major_ver > 1 )
|
|
|| ( mke4fs_major_ver == 1 && mke4fs_minor_ver > 42 )
|
|
|| ( mke4fs_major_ver == 1 && mke4fs_minor_ver == 42 && mke4fs_patch_ver >= 9 );
|
|
|
|
// (#766910) E2fsprogs 1.43 creates 64bit ext4 file
|
|
// systems by default. RHEL/CentOS 7 configured e2fsprogs
|
|
// 1.42.9 to create 64bit ext4 file systems by default.
|
|
// Theoretically this can be done when 64bit feature was
|
|
// added in e2fsprogs 1.42. GParted will re-implement the
|
|
// removed mke2fs.conf(5) auto_64-bit_support option to
|
|
// avoid the issues with multiple boot loaders not working
|
|
// with 64bit ext4 file systems.
|
|
force_auto_64bit = ( mke4fs_major_ver > 1 )
|
|
|| ( mke4fs_major_ver == 1 && mke4fs_minor_ver >= 42 );
|
|
}
|
|
}
|
|
|
|
if ( ! Glib::find_program_in_path( "dumpe2fs").empty() )
|
|
fs .read = FS::EXTERNAL ;
|
|
|
|
if ( ! Glib::find_program_in_path( "tune2fs" ).empty() )
|
|
{
|
|
fs .read_uuid = FS::EXTERNAL ;
|
|
fs .write_uuid = FS::EXTERNAL ;
|
|
}
|
|
|
|
if ( ! Glib::find_program_in_path( "e2label" ).empty() )
|
|
{
|
|
fs .read_label = FS::EXTERNAL ;
|
|
fs .write_label = FS::EXTERNAL ;
|
|
}
|
|
|
|
if ( ! Glib::find_program_in_path( "e2fsck" ).empty() )
|
|
fs .check = FS::EXTERNAL ;
|
|
|
|
if ( ! Glib::find_program_in_path( "resize2fs" ).empty() )
|
|
{
|
|
fs .grow = FS::EXTERNAL ;
|
|
|
|
if ( fs .read ) //needed to determine a min file system size..
|
|
fs .shrink = FS::EXTERNAL ;
|
|
}
|
|
|
|
if ( fs .check )
|
|
{
|
|
fs.copy = fs.move = FS::GPARTED ;
|
|
|
|
//If supported, use e2image to copy/move the file system as it
|
|
// only copies used blocks, skipping unused blocks. This is more
|
|
// efficient than copying all blocks used by GParted's internal
|
|
// method.
|
|
if ( ! Glib::find_program_in_path( "e2image" ).empty() )
|
|
{
|
|
Utils::execute_command( "e2image", output, error, true ) ;
|
|
if ( Utils::regexp_label( error, "(-o src_offset)" ) == "-o src_offset" )
|
|
fs.copy = fs.move = FS::EXTERNAL ;
|
|
}
|
|
}
|
|
|
|
fs .online_read = FS::EXTERNAL ;
|
|
#ifdef ENABLE_ONLINE_RESIZE
|
|
if ( specific_type != FS_EXT2 && Utils::kernel_version_at_least( 3, 6, 0 ) )
|
|
fs .online_grow = fs .grow ;
|
|
#endif
|
|
|
|
// Maximum size of an ext2/3/4 volume is 2^32 - 1 blocks, except for ext4
|
|
// with 64bit feature. That is just under 16 TiB with a 4K block size.
|
|
// * Ext4 Disk Layout, Blocks
|
|
// https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout#Blocks
|
|
// FIXME: Rounding down to whole MiB here should not be necessary. The
|
|
// Copy, New and Resize/Move dialogs should limit FS correctly without
|
|
// this. See bug #766910 comment #12 onwards for further discussion.
|
|
// https://bugzilla.gnome.org/show_bug.cgi?id=766910#c12
|
|
if ( specific_type == FS_EXT2 ||
|
|
specific_type == FS_EXT3 ||
|
|
( specific_type == FS_EXT4 && ! have_64bit_feature ) )
|
|
fs_limits.max_size = Utils::floor_size( 16 * TEBIBYTE - 4 * KIBIBYTE, MEBIBYTE );
|
|
}
|
|
|
|
return fs ;
|
|
}
|
|
|
|
void ext2::set_used_sectors( Partition & partition )
|
|
{
|
|
//Called when file system is unmounted *and* when mounted. Always read
|
|
// the file system size from the on disk superblock using dumpe2fs to
|
|
// avoid overhead subtraction. Read the free space from the kernel via
|
|
// the statvfs() system call when mounted and from the superblock when
|
|
// unmounted.
|
|
if ( ! Utils::execute_command( "dumpe2fs -h " + Glib::shell_quote( partition.get_path() ),
|
|
output, error, true ) )
|
|
{
|
|
Glib::ustring::size_type index = output.find( "Block count:" );
|
|
if ( index >= output .length() ||
|
|
sscanf( output.substr( index ).c_str(), "Block count: %lld", &T ) != 1 )
|
|
T = -1 ;
|
|
|
|
index = output .find( "Block size:" ) ;
|
|
if ( index >= output.length() ||
|
|
sscanf( output.substr( index ).c_str(), "Block size: %lld", &S ) != 1 )
|
|
S = -1 ;
|
|
|
|
if ( T > -1 && S > -1 )
|
|
T = Utils::round( T * ( S / double(partition .sector_size) ) ) ;
|
|
|
|
if ( partition .busy )
|
|
{
|
|
Byte_Value ignored ;
|
|
Byte_Value fs_free ;
|
|
if ( Utils::get_mounted_filesystem_usage( partition .get_mountpoint(),
|
|
ignored, fs_free, error ) == 0 )
|
|
N = Utils::round( fs_free / double(partition .sector_size) ) ;
|
|
else
|
|
{
|
|
N = -1 ;
|
|
partition.push_back_message( error );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
index = output .find( "Free blocks:" ) ;
|
|
if ( index >= output .length() ||
|
|
sscanf( output.substr( index ).c_str(), "Free blocks: %lld", &N ) != 1 )
|
|
N = -1 ;
|
|
|
|
if ( N > -1 && S > -1 )
|
|
N = Utils::round( N * ( S / double(partition .sector_size) ) ) ;
|
|
}
|
|
|
|
if ( T > -1 && N > -1 && S > -1 )
|
|
{
|
|
partition .set_sector_usage( T, N ) ;
|
|
partition.fs_block_size = S;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( ! output .empty() )
|
|
partition.push_back_message( output );
|
|
|
|
if ( ! error .empty() )
|
|
partition.push_back_message( error );
|
|
}
|
|
}
|
|
|
|
void ext2::read_label( Partition & partition )
|
|
{
|
|
if ( ! Utils::execute_command( "e2label " + Glib::shell_quote( partition.get_path() ),
|
|
output, error, true ) )
|
|
{
|
|
partition.set_filesystem_label( Utils::trim( output ) );
|
|
}
|
|
else
|
|
{
|
|
if ( ! output .empty() )
|
|
partition.push_back_message( output );
|
|
|
|
if ( ! error .empty() )
|
|
partition.push_back_message( error );
|
|
}
|
|
}
|
|
|
|
bool ext2::write_label( const Partition & partition, OperationDetail & operationdetail )
|
|
{
|
|
return ! execute_command( "e2label " + Glib::shell_quote( partition.get_path() ) +
|
|
" " + Glib::shell_quote( partition.get_filesystem_label() ),
|
|
operationdetail, EXEC_CHECK_STATUS );
|
|
}
|
|
|
|
void ext2::read_uuid( Partition & partition )
|
|
{
|
|
if ( ! Utils::execute_command( "tune2fs -l " + Glib::shell_quote( partition.get_path() ),
|
|
output, error, true ) )
|
|
{
|
|
partition .uuid = Utils::regexp_label( output, "^Filesystem UUID:[[:blank:]]*(" RFC4122_NONE_NIL_UUID_REGEXP ")" ) ;
|
|
}
|
|
else
|
|
{
|
|
if ( ! output .empty() )
|
|
partition.push_back_message( output );
|
|
|
|
if ( ! error .empty() )
|
|
partition.push_back_message( error );
|
|
}
|
|
}
|
|
|
|
bool ext2::write_uuid( const Partition & partition, OperationDetail & operationdetail )
|
|
{
|
|
return ! execute_command( "tune2fs -U random " + Glib::shell_quote( partition.get_path() ),
|
|
operationdetail, EXEC_CHECK_STATUS );
|
|
}
|
|
|
|
bool ext2::create( const Partition & new_partition, OperationDetail & operationdetail )
|
|
{
|
|
Glib::ustring features;
|
|
if ( force_auto_64bit )
|
|
{
|
|
// (#766910) Manually implement mke2fs.conf(5) auto_64-bit_support option
|
|
// by setting or clearing the 64bit feature on the command line depending
|
|
// of the partition size.
|
|
if ( new_partition.get_byte_length() >= 16 * TEBIBYTE )
|
|
features = " -O 64bit";
|
|
else
|
|
features = " -O ^64bit";
|
|
}
|
|
return ! execute_command( mkfs_cmd + " -F" + features +
|
|
" -L " + Glib::shell_quote( new_partition.get_filesystem_label() ) +
|
|
" " + Glib::shell_quote( new_partition.get_path() ),
|
|
operationdetail, EXEC_CHECK_STATUS|EXEC_CANCEL_SAFE|EXEC_PROGRESS_STDOUT,
|
|
static_cast<StreamSlot>( sigc::mem_fun( *this, &ext2::create_progress ) ) );
|
|
}
|
|
|
|
bool ext2::resize( const Partition & partition_new, OperationDetail & operationdetail, bool fill_partition )
|
|
{
|
|
Glib::ustring str_temp = "resize2fs -p " + Glib::shell_quote( partition_new.get_path() );
|
|
|
|
if ( ! fill_partition )
|
|
str_temp += " " + Utils::num_to_str( floor( Utils::sector_to_unit(
|
|
partition_new .get_sector_length(), partition_new .sector_size, UNIT_KIB ) ) ) + "K";
|
|
|
|
return ! execute_command( str_temp, operationdetail, EXEC_CHECK_STATUS|EXEC_PROGRESS_STDOUT,
|
|
static_cast<StreamSlot>( sigc::mem_fun( *this, &ext2::resize_progress ) ) );
|
|
}
|
|
|
|
bool ext2::check_repair( const Partition & partition, OperationDetail & operationdetail )
|
|
{
|
|
exit_status = execute_command( "e2fsck -f -y -v -C 0 " + Glib::shell_quote( partition.get_path() ),
|
|
operationdetail, EXEC_CANCEL_SAFE|EXEC_PROGRESS_STDOUT,
|
|
static_cast<StreamSlot>( sigc::mem_fun( *this, &ext2::check_repair_progress ) ) );
|
|
bool success = ( exit_status == 0 || exit_status == 1 || exit_status == 2 );
|
|
set_status( operationdetail, success );
|
|
return success;
|
|
}
|
|
|
|
bool ext2::move( const Partition & partition_new,
|
|
const Partition & partition_old,
|
|
OperationDetail & operationdetail )
|
|
{
|
|
Sector distance = partition_old.sector_start - partition_new.sector_start;
|
|
Glib::ustring offset = Utils::num_to_str( llabs(distance) * partition_new.sector_size );
|
|
Glib::ustring cmd;
|
|
if ( distance < 0 )
|
|
cmd = "e2image -ra -p -o " + offset + " " + Glib::shell_quote( partition_new.get_path() );
|
|
else
|
|
cmd = "e2image -ra -p -O " + offset + " " + Glib::shell_quote( partition_new.get_path() );
|
|
|
|
fs_block_size = partition_old.fs_block_size;
|
|
return ! execute_command( cmd, operationdetail, EXEC_CHECK_STATUS|EXEC_CANCEL_SAFE|EXEC_PROGRESS_STDERR,
|
|
static_cast<StreamSlot>( sigc::mem_fun( *this, &ext2::copy_progress ) ) );
|
|
}
|
|
|
|
bool ext2::copy( const Partition & src_part,
|
|
Partition & dest_part,
|
|
OperationDetail & operationdetail )
|
|
{
|
|
fs_block_size = src_part.fs_block_size;
|
|
return ! execute_command( "e2image -ra -p " + Glib::shell_quote( src_part.get_path() ) +
|
|
" " + Glib::shell_quote( dest_part.get_path() ),
|
|
operationdetail, EXEC_CHECK_STATUS|EXEC_CANCEL_SAFE|EXEC_PROGRESS_STDERR,
|
|
static_cast<StreamSlot>( sigc::mem_fun( *this, &ext2::copy_progress ) ) );
|
|
}
|
|
|
|
//Private methods
|
|
|
|
void ext2::resize_progress( OperationDetail *operationdetail )
|
|
{
|
|
Glib::ustring line = Utils::last_line( output );
|
|
size_t llen = line.length();
|
|
// There may be multiple text progress bars on subsequent last lines which look
|
|
// like: "Scanning inode table XXXXXXXXXXXXXXXXXXXXXXXXXXXX------------"
|
|
if ( llen > 0 && ( line[llen-1] == 'X' || line[llen-1] == '-' ) )
|
|
{
|
|
// p = Start of progress bar
|
|
size_t p = line.find_last_not_of( "X-" );
|
|
if ( p == line.npos )
|
|
p = 0;
|
|
else
|
|
p++;
|
|
size_t barlen = llen - p;
|
|
|
|
// q = First dash in progress bar or end of string
|
|
size_t q = line.find( '-', p );
|
|
if ( q == line.npos )
|
|
q = llen;
|
|
size_t xlen = q - p;
|
|
|
|
operationdetail->run_progressbar( (double)xlen, (double)barlen );
|
|
}
|
|
// Ending summary line looks like:
|
|
// "The filesystem on /dev/sdb3 is now 256000 block long."
|
|
else if ( output.find( " is now " ) != output.npos )
|
|
{
|
|
operationdetail->stop_progressbar();
|
|
}
|
|
}
|
|
|
|
void ext2::create_progress( OperationDetail *operationdetail )
|
|
{
|
|
Glib::ustring line = Utils::last_line( output );
|
|
// Text progress on the LAST LINE looks like "Writing inode tables: 105/1600"
|
|
long long progress, target;
|
|
if ( sscanf( line.c_str(), "Writing inode tables: %lld/%lld", &progress, &target ) == 2 )
|
|
{
|
|
operationdetail->run_progressbar( (double)progress, (double)target );
|
|
}
|
|
// Or when finished, on any line, ...
|
|
else if ( output.find( "Writing inode tables: done" ) != output.npos )
|
|
{
|
|
operationdetail->stop_progressbar();
|
|
}
|
|
}
|
|
|
|
void ext2::check_repair_progress( OperationDetail *operationdetail )
|
|
{
|
|
Glib::ustring line = Utils::last_line( output );
|
|
// Text progress on the LAST LINE looks like
|
|
// "/dev/sdd3: |===================================================== \ 95.1% "
|
|
size_t p = line.rfind( "%" );
|
|
Glib::ustring pct;
|
|
if ( p != line.npos && p >= 5 )
|
|
pct = line.substr( p-5, 6 );
|
|
float progress;
|
|
if ( line.find( ": |" ) != line.npos && sscanf( pct.c_str(), "%f", &progress ) == 1 )
|
|
{
|
|
operationdetail->run_progressbar( progress, 100.0 );
|
|
}
|
|
// Only allow stopping the progress bar after seeing "non-contiguous" in the
|
|
// summary at the end to prevent the GUI progress bar flashing back to pulsing
|
|
// mode when the text progress bar is temporarily missing/incomplete before fsck
|
|
// output is fully updated when switching from one pass to the next.
|
|
else if ( output.find( "non-contiguous" ) != output.npos )
|
|
{
|
|
operationdetail->stop_progressbar();
|
|
}
|
|
}
|
|
|
|
void ext2::copy_progress( OperationDetail *operationdetail )
|
|
{
|
|
Glib::ustring line = Utils::last_line( error );
|
|
// Text progress on the LAST LINE of STDERR looks like "Copying 146483 / 258033 blocks ..."
|
|
long long progress, target;
|
|
if ( sscanf( line.c_str(), "Copying %lld / %lld blocks", &progress, &target ) == 2 )
|
|
{
|
|
operationdetail->run_progressbar( (double)(progress * fs_block_size),
|
|
(double)(target * fs_block_size),
|
|
PROGRESSBAR_TEXT_COPY_BYTES );
|
|
}
|
|
// Or when finished, on any line of STDERR, looks like "Copied 258033 / 258033 blocks ..."
|
|
else if ( error.find( "\nCopied " ) != error.npos )
|
|
{
|
|
operationdetail->stop_progressbar();
|
|
}
|
|
}
|
|
|
|
} //GParted
|