445 lines
16 KiB
C++
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
|