gparted/src/ext2.cc

445 lines
16 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>
#include <glibmm/miscutils.h>
#include <glibmm/shell.h>
namespace GParted
{
FS ext2::get_filesystem_support()
{
FS fs( specific_type );
fs .busy = FS::GPARTED ;
mkfs_cmd = "mkfs." + Utils::get_filesystem_string( specific_type );
bool have_64bit_feature = false;
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.
force_auto_64bit = false;
if ( specific_type == FS_EXT4 )
{
Utils::execute_command( mkfs_cmd + " -V", output, error, true );
int mke2fs_major_ver = 0;
int mke2fs_minor_ver = 0;
int mke2fs_patch_ver = 0;
if (sscanf(error.c_str(), "mke2fs %d.%d.%d",
&mke2fs_major_ver, &mke2fs_minor_ver, &mke2fs_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 = (mke2fs_major_ver > 1)
|| (mke2fs_major_ver == 1 && mke2fs_minor_ver > 42)
|| (mke2fs_major_ver == 1 && mke2fs_minor_ver == 42 && mke2fs_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 = (mke2fs_major_ver > 1)
|| (mke2fs_major_ver == 1 && mke2fs_minor_ver >= 42);
}
}
}
if ( ! Glib::find_program_in_path( "dumpe2fs").empty() )
{
// Resize2fs is preferred, but not required, to determine the minimum FS
// size. Can fall back to using dumpe2fs instead. Dumpe2fs is required
// for reading FS size and FS block size. See ext2::set_used_sectors()
// implementation for details.
fs.read = FS::EXTERNAL;
fs.online_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;
fs.online_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;
}
}
#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. When mounted read the free space from
// the kernel via the statvfs() system call. When unmounted read the
// free space using resize2fs itself falling back to using dumpe2fs.
exit_status = Utils::execute_command("dumpe2fs -h " + Glib::shell_quote(partition.get_path()),
output, error, true);
if (exit_status != 0)
{
if (! output.empty())
partition.push_back_message(output);
if (! error.empty())
partition.push_back_message(error);
return;
}
long long block_count = -1;
Glib::ustring::size_type index = output.find("\nBlock count:");
if (index < output.length())
sscanf(output.substr(index).c_str(), "\nBlock count: %lld", &block_count);
long long block_size = -1;
index = output.find("\nBlock size:");
if (index < output.length())
sscanf(output.substr(index).c_str(), "\nBlock size: %lld", &block_size);
long long free_blocks = -1;
if (partition.busy)
{
Byte_Value ignored;
Byte_Value fs_free_bytes;
if (Utils::get_mounted_filesystem_usage(partition.get_mountpoint(),
ignored, fs_free_bytes, error) == 0)
{
free_blocks = fs_free_bytes / block_size;
}
else
{
partition.push_back_message(error);
}
}
else
{
// Resize2fs won't shrink a file system smaller than it's own estimated
// minimum size, so use that to derive the free space.
Glib::ustring output2;
Glib::ustring error2;
if (! Utils::execute_command("resize2fs -P " + Glib::shell_quote(partition.get_path()),
output2, error2, true) )
{
long long minimum_blocks = -1;
sscanf(output2.c_str(), "Estimated minimum size of the filesystem: %lld", &minimum_blocks);
if (minimum_blocks > -1)
free_blocks = block_count - minimum_blocks;
}
// Resize2fs can fail reporting please run fsck first. Fall back to
// reading dumpe2fs output for free space.
if (free_blocks == -1)
{
index = output.find("\nFree blocks:");
if (index < output.length())
sscanf(output.substr(index).c_str(), "\nFree blocks: %lld", &free_blocks);
}
if (free_blocks == -1 && error2.empty())
partition.push_back_message(error2);
}
if (block_count > -1 && block_size > -1 && free_blocks > -1)
{
Sector fs_size = block_count * block_size / partition.sector_size;
Sector fs_free = free_blocks * block_size / partition.sector_size;
partition.set_sector_usage(fs_size, fs_free);
partition.fs_block_size = block_size;
}
}
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 )
{
// Called when file system is unmounted *and* when mounted.
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 size;
if ( ! fill_partition )
size = " " + Utils::num_to_str(partition_new.get_byte_length() / KIBIBYTE) + "K";
return ! execute_command("resize2fs -p " + Glib::shell_quote(partition_new.get_path()) + size,
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