Here are the errors reported in the deliberately broken
CreateAndReadLabel test from the previous commit message:
[ RUN ] ext2Test.CreateAndReadLabel
test_ext2.cc:311: Failure
Value of: m_partition.get_messages().empty()
Actual: false
Expected: true
Partition messages:
e2label: No such file or directory while trying to open /does_not_exist/test_ext2.img
Couldn't find valid filesystem superblock.
[ FAILED ] ext2Test.CreateAndReadLabel (77 ms)
Even though the test was deliberately broken by setting the wrong path
for the file system image and the e2label command failed, apparently
testing for the expected label still passed. What happened was that the
desired "TEST_LABEL" has to be in the Partition object and then the file
system was created. Then reading the file system label failed, however
"TEST_LABEL" was already set in the Partition object so it matched.
Reading the label is unique among the read actions of usage, label and
UUID as the others don't need to be set before the file system is
created. GParted doesn't encounter this issue because when refreshing
devices it creates new blank Partition objects and then performs the
read actions to populate them.
Fix by resetting the Partition object back to only containing basic
information before all the reading file system information tests, even
though it is only needed in the read label case. This also better
reflects how GParted works.
Now with the same deliberate brokenness the test also reports the label
does not match it's expected value:
$ ./test_ext2 --gtest_filter='ext2Test.CreateAndReadLabel'
Running main() from test_ext2.cc
Note: Google Test filter = ext2Test.CreateAndReadLabel
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from ext2Test
[ RUN ] ext2Test.CreateAndReadLabel
test_ext2.cc:322: Failure
Expected equality of these values:
fs_label
Which is: "TEST_LABEL"
m_partition.get_filesystem_label().c_str()
Which is: ""
test_ext2.cc:272: Failure
Value of: m_partition.get_messages().empty()
Actual: false
Expected: true
Partition messages:
e2label: No such file or directory while trying to open /does_not_exist/test_ext2.img
Couldn't find valid filesystem superblock.
[ FAILED ] ext2Test.CreateAndReadLabel (70 ms)
[----------] 1 test from ext2Test (70 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (75 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] ext2Test.CreateAndReadLabel
1 FAILED TEST
Closes!49 - Add file system interface tests
The file system reading methods report errors into Partition messages
because they are used as part of the GParted refresh, rather than
reporting errors into OperationDetails used when applying operations.
Therefore test for no messages for success and print the messages on
failure.
For example, temporarily breaking the read label test code by setting
the wrong file system image name produces this:
$ ./test_ext2 --gtest_filter='ext2Test.CreateAndReadLabel'
Running main() from test_ext2.cc
Note: Google Test filter = ext2Test.CreateAndReadLabel
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from ext2Test
[ RUN ] ext2Test.CreateAndReadLabel
test_ext2.cc:311: Failure
Value of: m_partition.get_messages().empty()
Actual: false
Expected: true
Partition messages:
e2label: No such file or directory while trying to open /does_not_exist/test_ext2.img
Couldn't find valid filesystem superblock.
[ FAILED ] ext2Test.CreateAndReadLabel (77 ms)
[----------] 1 test from ext2Test (77 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (85 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] ext2Test.CreateAndReadLabel
1 FAILED TEST
$ echo $?
1
Closes!49 - Add file system interface tests
As seen in the first commit message, operation detail text is XML
encoded. This makes it harder to read, especially commands which often
have single quotes encoded as '. For example:
<b><i>mkfs.ext2 -F -L '' '/home/centos/programming/c/gparted/tests/test_ext2.img'</i></b>
Strip this encoding when printing the operation details. Now the same
example looks like:
mkfs.ext2 -F -L '' '/home/centos/programming/c/gparted/tests/test_ext2.img'
Closes!49 - Add file system interface tests
Running test_ext2 in GitLab Continuous Integration environment fails like
this:
(test_ext2:6338): Gtk-WARNING **: 09:06:17.576: cannot open display:
Running main() from test_ext2.cc
Obviously the GitLab CI environment doesn't have an X11 display, but
unfortunately this test case code requires one.
Utils::execute_command() calls Gtk::Main::run() so requires a Gtk::Main
object constructing and therefore an X11 display, even though this
program never displays anything graphical. The call chain is:
main() test_ext2.cc
Gtk::Main::Main() gtkmm/gtk/src/main.ccg
Gtk::Main::init() [1]
gtk_init() gtk/gtk/gtkmain.c [2]
which exits with a non-zero exit status when the DISPLAY environment
variable is unset.
Looked at deriving from Gtk::Main class and writing a replacement init()
method which calls gtk_init_check() instead of gtk_init() but
Gtk::Main::instance_ is a private member so not accessible in a derived
class.
Tried using Glib::MainLoop instead of Gtk::Main, but that doesn't
initialise everything that Gtk::Main(), so the program crashes.
Therefore use xvfb-run [3][4] to run this test program against a virtual
X11 display when a real display isn't available. Coded execution of
xvfb-run into this test program so that it can simply be executed on the
command line like the other test programs, without having to remember to
run "xvfb-run ./test_ext2 ...".
[1] Gtk::Main::init()
https://gitlab.gnome.org/GNOME/gtkmm/blob/3.10.1/gtk/src/main.ccg#L287
[2] gtk_init()
https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkmain.c#L1000
[3] how to run gtk app on linux without an x server
https://superuser.com/questions/624918/how-to-run-gtk-app-on-linux-without-an-x-server
[4] Using GTK without DISPLAY
https://stackoverflow.com/questions/11694278/using-gtk-without-displayCloses!49 - Add file system interface tests
This is the first step of adding testing of the derived FileSystem
interface classes which call the file system specific executables.
Rather than mocking command execution and returned output the tests run
the real commands, effectively making this integration testing.
Test case setup determines the file system supported actions using
get_filesystem_support() and individual tests are skipped if a feature
is not supported, just as GParted does for it's actions.
Each test creates it's own sparse image file and a fresh file system,
performs a test on one FileSystem interface call and deletes the image
file. This makes each test independent and allows them to run as a
non-root user, provided the file system command itself doesn't require
root. Errors reported for a failed interface call will include the
GParted OperationDetails, which in turn includes the file system
specific command used and stdout and stderr from it's execution.
For example, temporarily breaking the test code to create a 10 KiB image
file instead of 256 MiB one produces this:
$ ./test_ext2
Running main() from test_ext2.cc
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from ext2Test
[ RUN ] ext2Test.Create
test_ext2.cc:199: Failure
Value of: s_ext2_obj->create(m_partition, m_operation_detail)
Actual: false
Expected: true
Operation details:
<b><i>mkfs.ext2 -F -L '' '/home/centos/programming/c/gparted/tests/test_ext2.img'</i></b> 00:00:00 (ERROR)
<i></i>
<i>mke2fs 1.42.9 (28-Dec-2013)
/home/centos/programming/c/gparted/tests/test_ext2.img: Not enough space to build proposed filesystem while setting up superblock
</i>
[ FAILED ] ext2Test.Create (25 ms)
[----------] 1 test from ext2Test (25 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (30 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] ext2Test.Create
1 FAILED TEST
$ echo $?
1
Also as Utils:: and FileSystem::execute_command() are needed, this
requires linking with GParted_Core for GParted_Core::mainthread and
therefore with most of the non-UI classes in gpartedbin.
Closes!49 - Add file system interface tests
To match the renaming performed as part of Bug 741424 "Add support for
GPT partition names". In particular this is most closely similar to:
d480800600
Rename enum to OPERATION_LABEL_FILESYSTEM (#741424)
When GParted GitLab Continuous Integration was setup, CentOS 7 image was
used for a slow moving distribution and Ubuntu as a different, faster
moving distribution.
CentOS 8 has recently been released [1]. To avoid automatically
switching to it when an official CentOS 8 docker image is made available
[2], explicitly specify the CentOS 7 image.
[1] Release for CentOS Linux 8 and CentOS Streams (2019-09-24)
https://lists.centos.org/pipermail/centos-announce/2019-September/023449.html
[2] Docker Official Images, The official build of CentOS
https://hub.docker.com/_/centos/Closes!48 - Remain with CentOS 7 for GitLab CI
Was attempting to erase the wrong key "key-long" for the test. It
should be erasing "key-too-long". Fix this. Given that that line in
the test was to erase a non-existent key and confirm failure; the test
passed before with the wrong non-existent key, and still passes with the
right non-existent key. Clearly a cut and paste error as a result of
copying the previous LongPassword test when initially added in:
c6657aab9e
Add unit tests for PasswordRAMStore module (#795617)
Separate udevsettle command was merged into udevadm back in udev 117
(released 2007-11-13) by:
225cb03bd8
udevadm: merge all udev tools into a single binary
The oldest supported distributions have these much newer versions of
udev:
Distro EOL udevadm -V
Debian 8 2020-Apr 215 (combined systemd and udev)
RHEL / CentOS 7 2024-Jun 219 (combined systemd and udev)
Ubuntu 16.04 LTS 2021-Apr 229 (combined systemd and udev)
Along with the previous patch "Avoid reading the same sector multiple
times in a row (!46)(#16)" internal detection of file system signatures
now reads the minimum number of sectors in increasing order.
As the code still considers the sector size, it now also reads less
sectors from devices with larger sectors to get all the signatures. So
on a 512 byte sector device it maximally seeks to and reads from these
byte offsets:
0, 512, 1024, 65536
and on a 4096 byte sector device, only from these byte offsets:
0, 65536
Closes!46 - Whole device FAT32 file system reports device busy warning
from mlabel
Closes#16 - "invalid argument for seek()" error on very small (<=40KiB)
drives
GParted internal file system detection is reading the same sector
multiple times in a row. Add an optimisation to only read a sector if
it is different to the previously read sector.
Closes!46 - Whole device FAT32 file system reports device busy warning
from mlabel
Closes#16 - "invalid argument for seek()" error on very small (<=40KiB)
drives
After the previous commit the lp_device structure pointer parameter is
only used to provide sector_size. Just pass that instead.
Closes!46 - Whole device FAT32 file system reports device busy warning
from mlabel
Closes#16 - "invalid argument for seek()" error on very small (<=40KiB)
drives
GParted_Core::useable_device() doesn't change the pointed to PedDevice
structure. Pass it as const so the compiler enforces this.
Closes!46 - Whole device FAT32 file system reports device busy warning
from mlabel
For every device that GParted scans, it determines if it can be used by
attempting to read the first sector. This is the sequence of events,
as reported in the previous two commit messages:
gparted |set_devices_thread(pdevices) pdevices->["/dev/sdb"]
...
gparted | useable_device(lp_device) lp_device->path="/dev/sdb"
gparted | ped_device_open()
libparted | open("/dev/sdb", O_RDWR) = 8
gparted | ped_device_read()
gparted | ped_device_close()
libparted | close(8)
udev(async)| KERNEL change /devices/.../sdb (block)
...
udev(async)| UDEV change /devices/.../sdb (block)
Note that these udev block device change events occur before the ones
waited for in the first commit "Wait for udev change on /dev/DISK when
querying whole device FS (!46)" so these were already waited for.
So because libparted only opens devices read-write this triggers a set
of udev change events for the whole block device, and if the device is
partitioned, also remove and re-add events for all the partitions.
Switch to using POSIX open(), read() and close() calls so read-only
access can be specified to avoid the unnecessary udev change events.
Closes!46 - Whole device FAT32 file system reports device busy warning
from mlabel
After the previous fix, on my CentOS 7 VM, GParted is now often
reporting a warning that the /dev/PTN entry is missing when reading the
label of the file system in the first partition. This happens
regardless of the file system type.
Example error for EXT4:
e2label: No such file or directory while trying to open /dev/sdb1
Couldn't find valid file system superblock.
Example error for XFS:
/dev/sdb1: No such file or directory
fatal error -- couldn't initialize XFS library
As with the case in the previous commit, GParted gets the label via
blkid instead.
Again with debugging and sleeping in GParted, stracing the GParted
thread GParted_Core::set_devices_thread() and using 'udevadm monitor' to
watch udev events the following sequence of events is observed:
gparted |set_devices_thread(pdevices) pdevices->["/dev/sdb"]
gparted | ped_device_get("/dev/sdb")
libparted | open("/dev/sdb", O_RDONLY) = 8
libparted | close(8)
gparted | useable_device(lp_device) lp_device->path="/dev/sdb"
gparted | ped_device_open()
libparted | open("/dev/sdb", O_RDWR) = 8
gparted | ped_device_read()
gparted | ped_device_close()
libparted | close(8)
udev(async)| KERNEL remove /devices/.../sdb/sdb1 (block)
udev(async)| KERNEL remove /devices/.../sdb/sdb2 (block)
udev(async)| KERNEL change /devices/.../sdb (block)
udev(async)| KERNEL add /devices/.../sdb/sdb1 (block)
udev(async)| KERNEL add /devices/.../sdb/sdb2 (block)
udev(async)| UDEV remove /devices/.../sdb/sdb1 (block)
udev(async)| UDEV remove /devices/.../sdb/sdb2 (block)
udev(async)| UDEV change /devices/.../sdb (block)
udev(async)| UDEV add /devices/.../sdb/sdb1 (block)
udev(async)| UDEV add /devices/.../sdb/sdb2 (block)
gparted | set_device_from_disk(device, device_path="/dev/sdb")
gparted | get_device(device_path="/dev/sdb", ..., flush=true)
gparted | ped_device_get()
gparted | flush_device(lp_device) lp_device->path="/dev/sdb"
gparted | ped_device_open()
libparted | open("/dev/sdb", O_RDWR) = 8
gparted | ped_device_sync()
gparted | ped_device_close()
libparted | close(8)
udev(async)| KERNEL remove /devices/.../sdb/sdb1 (block)
udev(async)| KERNEL remove /devices/.../sdb/sdb2 (block)
udev(async)| KERNEL change /devices/.../sdb (block)
udev(async)| KERNEL add /devices/.../sdb/sdb1 (block)
udev(async)| KERNEL add /devices/.../sdb/sdb2 (block)
udev(async)| UDEV remove /devices/.../sdb/sdb1 (block)
udev(async)| UDEV remove /devices/.../sdb/sdb2 (block)
udev(async)| UDEV change /devices/.../sdb (block)
udev(async)| UDEV add /devices/.../sdb/sdb1 (block)
udev(async)| UDEV add /devices/.../sdb/sdb2 (block)
gparted | settle_device()
gparted | Utils::execute_command("udevadm settle --timeout=1")
gparted | detect_filesystem(lp_device, NULL, ...) lp_device->path="/dev/sdb"
gparted | detect_filesystem_internal(lp_device, NULL) lp_device->path="/dev/sdb"
gparted | ped_device_open()
libparted | open("/dev/sdb", O_RDWR) = 8
gparted | ped_device_read()
gparted | ped_device_read()
gparted | ped_device_read()
gparted | ped_device_read()
gparted | ped_device_read()
gparted | ped_device_read()
gparted | ped_device_read()
gparted | ped_device_read()
gparted | ped_device_read()
gparted | ped_device_read()
gparted | ped_device_read()
gparted | ped_device_read()
gparted | ped_device_read()
gparted | ped_device_close()
libparted | close(8)
udev(async)| KERNEL remove /devices/.../sdb/sdb1 (block)
udev(async)| KERNEL remove /devices/.../sdb/sdb2 (block)
udev(async)| KERNEL change /devices/.../sdb (block)
udev(async)| KERNEL add /devices/.../sdb/sdb1 (block)
udev(async)| KERNEL add /devices/.../sdb/sdb2 (block)
udev(async)| UDEV remove /devices/.../sdb/sdb1 (block)
udev(async)| UDEV remove /devices/.../sdb/sdb2 (block)
udev(async)| UDEV change /devices/.../sdb (block)
udev(async)| UDEV add /devices/.../sdb/sdb1 (block)
udev(async)| UDEV add /devices/.../sdb/sdb2 (block)
gparted | get_disk(lp_device, lp_disk, strict=false)
gparted | ped_disk_new(lp_device) lp_device->path="/dev/sdb"
libparted | open("/dev/sdb", O_RDWR) = 8
libparted | close(8)
udev(async)| KERNEL remove /devices/.../sdb/sdb1 (block)
udev(async)| KERNEL remove /devices/.../sdb/sdb2 (block)
udev(async)| KERNEL change /devices/.../sdb (block)
udev(async)| KERNEL add /devices/.../sdb/sdb1 (block)
udev(async)| KERNEL add /devices/.../sdb/sdb2 (block)
udev(async)| UDEV remove /devices/.../sdb/sdb1 (block)
udev(async)| UDEV remove /devices/.../sdb/sdb2 (block)
udev(async)| UDEV change /devices/.../sdb (block)
udev(async)| UDEV add /devices/.../sdb/sdb1 (block)
udev(async)| UDEV add /devices/.../sdb/sdb2 (block)
gparted | set_device_partitions()
gparted | detect_filesystem()
gparted | set_partition_label_and_uuid(partition) partition.get_path()="/dev/sdb1"
gparted | read_label()
gparted | ext2::read_label()
gparted | Utils::execute_command("e2label /dev/sdb1")
So in this case for a partitioned drive, libparted ped_disk_new() is
opening and closing the device read-write and triggering the udev remove
and add partition block special entries exactly when the label is trying
to be read from the first partition, causing the device missing error.
The call chain is:
set_devices_thread()
set_device_from_disk()
get_disk()
ped_disk_new()
Looking at the source for ped_disk_new() [1] it is calling
ped_device_open() and ped_device_close(), hence why it is opening the
device read-write and triggering the udev events.
Looking back at commit "Wait for udev to recreate /dev/PTN entries when
calibrating (#762941)" [2] and re-testing it, the udev events were also
caused by ped_disk_new() with this call chain:
apply_operation_to_disk()
calibrate_partition()
get_disk()
ped_disk_new()
Fix this probe case and keep the fix for the previous calibrate case by
moving the waiting for udev events from calibrate_partition() into
get_disk(), right after ped_disk_new(). The maximum wait time is
shortened to the shorter 1 second probing timeout to avoid the longer
10 second apply timeout in this probing case. The calibrate case commit
message said "The longest I have seen udev take to do this is 0.80
seconds in a VM".
[1] parted/libparted/disk.c:ped_disk_new()
http://git.savannah.gnu.org/cgit/parted.git/tree/libparted/disk.c?id=v3.2#n169
[2] fd9013d5f6
Wait for udev to recreate /dev/PTN entries when calibrating
(#762941)
Closes!46 - Whole device FAT32 file system reports device busy warning
from mlabel
GParted nearly always shows this warning against a whole disk device
FAT32 file system on my development VMs:
plain floppy: device "/dev/sdb" busy (Resource temporarily
unavailable): Cannot initialize '::'
mlabel: Cannot initialize drive
However GParted does get the label via blkid instead. Occasionally
everything works correctly and there is no warning.
Never found a similar error for any other file system on a whole disk
device. The timing never seems to clash.
Remember that when a writable file descriptor of a disk device is
closed, udev events are triggered which update the kernel and /dev
entries for any partition changes. (This is in addition to coding which
has always existed in the partitioning tools, including fdisk and
parted, to inform the kernel, but not create /dev entries, of partition
changes).
With debugging and sleeping in GParted, stracing the GParted thread
GParted_Core::set_devices_thread() and using 'udevadm monitor' to watch
udev events the following sequence of events is observed:
gparted |set_devices_thread(pdevices) pdevices->["/dev/sdb"]
gparted | ped_device_get("/dev/sdb")
libparted | open("/dev/sdb", O_RDONLY) = 8
libparted | close(8)
gparted | useable_device(lp_device) lp_device->path="/dev/sdb"
gparted | ped_device_open()
libparted | open("/dev/sdb", O_RDWR) = 8
gparted | ped_device_read()
gparted | ped_device_close()
libparted | close(8)
udev(async)| KERNEL change /devices/.../sdb (block)
udev(async)| KERNEL change /devices/.../sdb (block)
udev(async)| UDEV change /devices/.../sdb (block)
udev(async)| UDEV change /devices/.../sdb (block)
gparted | set_device_from_disk(device, device_path="/dev/sdb")
gparted | get_device(device_path="/dev/sdb", ..., flush=true)
gparted | ped_device_get()
gparted | flush_device(lp_device) lp_device->path="/dev/sdb"
gparted | ped_device_open()
libparted | open("/dev/sdb", O_RDWR) = 8
gparted | ped_device_sync()
gparted | ped_device_close()
libparted | close(8)
udev(async)| KERNEL change /devices/.../sdb (block)
udev(async)| KERNEL change /devices/.../sdb (block)
udev(async)| UDEV change /devices/.../sdb (block)
udev(async)| UDEV change /devices/.../sdb (block)
gparted | set_device_one_partition()
gparted | set_partition_label_and_uuid()
gparted | read_label()
gparted | fat16::read_label()
gparted | Utils::execute_command("mlabel -s :: -i /dev/sdb")
This sequence of events only shows which close()s by libparted trigger
udev events. It does not show that processing those events are
asynchronous and overlap with GParted executing mlabel, causing the
device busy error.
As libparted's ped_device_open() doesn't offer a way to specify that a
device will only be used for reading, it always opens it read-write
[1][2]. Hence this sequence in GParted_Core::flush_device():
ped_device_open()
ped_device_sync()
ped_device_close()
always triggers udev change events on the whole disk device. Fix by
waiting for those udev events to complete.
[1] libparted documentation, PedDevice, ped_device_open()
https://www.gnu.org/software/parted/api/group__PedDevice.html#ga7c87bd06e76553c8ff3262b5e6ca256
[2] parted/libparted/arch/linux.c:linux_open()
http://git.savannah.gnu.org/cgit/parted.git/tree/libparted/arch/linux.c?id=v3.2#n1628Closes!46 - Whole device FAT32 file system reports device busy warning
from mlabel
The last distribution to include the separate 'udevinfo' command was
RHEL / CentOS 5 with udev 095. All supported distributions have much
newer versions of udev and instead have the 'udevadm' command.
A user reported that GParted was slow to refresh on FAT32 file systems:
"can take very long, up to several minutes; can be reproduced by running
dosfsck manually". This is because the file system was large and almost
full, and GParted performs a file system check just to to report the
file system usage.
Created a 4 GiB FAT32 file system and almost filled it with 4 KiB files,
just over 970,000 files.
# df -k /mnt/2
Filesystem 1K-blocks Used Available Used% Mounted on
/dev/sdb2 4186108 39155384 270724 94% /mnt/2
# df -i /mnt/2
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sdb2 0 0 0 - /mnt/2
# find /mnt/2 -type f -print | wc -l
971059
# find /mnt/2 -type d -print | wc -l
1949
Testing performance of the current fsck.fat:
# time fsck.fat -n -v /dev/sdb2 | \
> egrep 'bytes per logical sector|bytes per cluster|sectors total|clusters$'
512 bytes per logical sector
4096 bytes per cluster
8388608 sectors total
/dev/sdb2: 973008 files, 978846/1046527 clusters
real 0m11.552s
user 0m2.183s
sys 0m7.547s
Free sectors in the file system according to fsck.fat:
(1046527 - 978846) * 4096 / 512 = 541448 sectors
Repeating this test while also using 'blktrace /dev/sdb2' and Ctrl-C
around the test in a separate terminal, reports these numbers of I/Os
being performed:
Read requests Read bytes
15,563 165 MiB
Prior to this commit [1] from 0.0.9, GParted used libparted's
ped_file_system_get_resize_constraint() to report the minimum size to
which a FAT* file system can be resized. Use this test program [2] to
performance test this method:
# time ./fscons /dev/sdb2
dev=/dev/sdb2
sector_size=512
min_size=7909522
max_size=8388608
real 0m2.673s
user 0m0.070s
sys 0m1.834s
Free sectors in the file system according to libparted
ped_file_system_get_resize_constraint():
8388608 - 7909522 = 479086 sectors
blktrace reports these numbers of I/Os being performed:
Read requests Read bytes
7,821 71 MiB
So using libparted resize constraint is a bit faster but is still
reading too much data and is really too slow. Also when testing GParted
using this libparted method against a corrupted FAT32 file system, on
every refresh, one popup dialog is displayed for each error libparted
detects with the file system, each of which needs acknowledgement.
Example popup:
Libparted Error
\DIRNAME\FILENAME.EXT is 107724k, but is has 1920
clusters (122880k).
[ Cancel ][ Ignore ]
There could be a huge number of such errors in a corrupted file system.
Not really suitable for use by GParted.
Test the performance of mtools' minfo command to report the file system
figures:
# time minfo -i /dev/sdb2 :: | \
> egrep 'sector size:|cluster size:|small size:|big size:|free clusters='
sector size: 512 bytes
cluster size: 8 sectors
small size: 0 sectors
big size: 8388608 sectors
free clusters=67681
real 0m0.013s
user 0m0.004s
sys 0m0.019s
Free sectors in the file system according to minfo:
67681 * 8 = 541448 sectors
blktrace reports these numbers of I/Os being performed by minfo:
Read requests Read bytes
1 16 KiB
This matches with minfo just reading information from the BPB (BIOS
Parameter Block) [3] from sector 0 and the FS Information Sector [4]
usually in sector 1. Note that the free cluster figure reported by
minfo comes from the FS Information Sector because it only reports it
for FAT32 file systems, not for FAT16 file systems. Scanning the File
Allocation Table (FAT) [5] to count free clusters is exactly what mdir,
without the '-f' (fast) flag, does. Test the performance of mdir:
# export MTOOLS_SKIP_CHECK=1
# time mdir -i /dev/sdb2 ::/ | fgrep 'bytes free'
277 221 376 bytes free
real 0m0.023s
user 0m0.011s
sys 0m0.023s
Free sectors in the file system according to mdir:
277221376 / 512 = 541448 sectors
blktrace reports these number of I/Os being performed by mdir:
Read requests Read bytes
5 448 KiB
So minfo and mdir together provide the needed information and are 2 to 3
orders of magnitude faster because they only read the needed BPB and FAT
data from the drive. Use these together to read the file system usage.
[1] 61cd0ce778
lots of stuff and cleanups, including fixing getting used/unused
space of hfs/hfs+/fat16/fat32
[2] fscons.c
/* FILE: fscons.c
* SYNOPSIS: Report libparted's FS resize limits.
* BUILD: gcc -o fscons fscons.c -lparted -lparted-fs-resize
*/
int main(int argc, const char *argv[])
{
PedDevice* dev = NULL;
PedDisk* tab = NULL;
PedPartition* ptn = NULL;
PedFileSystem* fs = NULL;
PedConstraint* cons = NULL;
if (argc != 2)
{
fprintf(stderr, "Usage: fscons BLOCKDEV\n");
exit(1);
}
dev = ped_device_get(argv[1]);
if (dev == NULL)
{
fprintf(stderr, "ped_device_get(\"%s\") failed\n", argv[1]);
exit(1);
}
printf("dev=%s\n", dev->path);
printf("sector_size=%ld\n", dev->sector_size);
tab = ped_disk_new(dev);
if (tab == NULL)
{
fprintf(stderr, "ped_disk_new(dev) failed\n");
exit(1);
}
ptn = ped_disk_get_partition_by_sector(tab, 0);
if (ptn == NULL)
{
fprintf(stderr, "ped_disk_get_partition(tab, 0) failed\n");
exit(1);
}
fs = ped_file_system_open(&ptn->geom);
if (fs == NULL)
{
fprintf(stderr, "ped_file_system_open(&ptn->geom) failed\n");
exit(1);
}
cons = ped_file_system_get_resize_constraint(fs);
if (cons == NULL)
{
fprintf(stderr, "ped_file_system_get_resize_constraint(fs) failed\n");
exit(1);
}
printf("min_size=%ld\n", cons->min_size);
printf("max_size=%ld\n", cons->max_size);
ped_constraint_destroy(cons);
ped_file_system_close(fs);
ped_disk_destroy(tab);
ped_device_destroy(dev);
return 0;
}
[3] Design of the FAT file system, BIOS Parameter Block
https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system#BIOS_Parameter_Block
[4] Design of the FAT file system, FS Information Sector
https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system#FS_Information_Sector
[5] Design of the FAT file system, File Allocation Table
https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system#File_Allocation_Table
Bug 569921 - dosfsck -n delays device scan
When GParted is configured with `--disable-doc` the help documentation
is neither built, nor installed.
With this configuration the Help -> Contents menu displays a message
dialog indicating the "Documentation is not available".
On GNOME 3 no title is shown; however, on some other graphic toolkits
/ window managers an unspecified title is shown.
For example on GParted Live with Fluxbox the following is displayed:
Unnamed
-------
Documentation is not available
This build of gparted is configured without documentation.
Documentation is available at the project web site.
https://gparted.org
OK
To address the unspecified title on non-GNOME 3 graphic toolkits, add
a title that works with and without GNOME 3.
Closes!45 - Add missing window title to Help Contents dialog
All the dialogs which compose new or modified partition boundaries
create those partitions within the boundaries of the device. Therefore
trimming the partition boundaries to device boundaries never happens.
So replace this trimming with bug error reports.
Also add bug prefixes to the other error messages in
GParted_Core::valid_partition() too.
Closes#48 - Error when moving locked LUKS-encrypted partition
The previous commit moved the code from GParted_Core to
Dialog_Base_Partition class without making a single formatting change to
ensure the code was guaranteed to work the same within that larger
commit. Now reformat the code to current layout standards. Making it a
separate commit simplifies the effort for both changes and improves
bisectability.
Additionally to be sure there were no code changes,
Dialog_Base_Partition.cc was compiled to assembler code with and without
this change applied and the resultant assembler code compared. There
were no differences in the generated assembler code.
Start with the make generated g++ command for compiling
Dialog_Base_Partition.o; (1) remove the '-g' flag as inserted debugging
directives do differ; and (2) replace '-c -o Dialog_Base_Partition.o'
with '-S -o Dialog_Base_Partition.s' to produce assembler output instead
of object code.
Closes#48 - Error when moving locked LUKS-encrypted partition
Move snap_to_*() method calls from the point when all operations are
added to the list, to earlier when Resize/Move, Paste (into new) and
Create New dialogs are composing the new partition objects. In
particular for the Resize/Move operation, to just before updating the
file system usage.
This change finally resolves this bug.
Because of the dialog call chains into Dialog_Base_Partition,
snap_to_alignment() must be added into:
* Dialog_Base_Partition::prepare_new_partition() for the Resize/Move and
Paste (into new) dialogs; and
* Dialog_Partition_New::Get_New_Partition() for the Create New dialog.
Closes#48 - Error when moving locked LUKS-encrypted partition
The current device has to be passed to the dialog constructors and then
on to the Dialog_Base_Constructor. Note that the Dialog_Partition_New
constructor is already passed the current device, however it still needs
to pass it on to Dialog_Base_Constructor.
This is ready so that snap_to_*() methods can access the current device
when they are called from within these dialogs.
* Pass Parameter to Base Class Constructor while creating Derived class
Object
https://stackoverflow.com/questions/16585856/pass-parameter-to-base-class-constructor-while-creating-derived-class-objectCloses#48 - Error when moving locked LUKS-encrypted partition
Move setting of the new_partition object file system usage to after
everything else, specifically after free_space_before and strict_start.
This is because snap_to_*() use free_space_before and strict_start and
snap_to_alignment() is going to be called before the file system usage
is updated to avoid the error in this bug report.
Closes#48 - Error when moving locked LUKS-encrypted partition
None of the snap_to_*() methods report any errors so remove the unused
error parameter and return value.
Closes#48 - Error when moving locked LUKS-encrypted partition
PATCHSET OVERVIEW
A user had 2 adjacent partitions which were aligned to multiples of
33553920 bytes (32 MiB - 512 bytes), not MiB or cylinders. As far as
GParted is concerned this is not aligned. The second partition
contained closed LUKS encrypted data. Recreate this setup with:
# truncate -s 200G /tmp/disk.img
# losetup -f --show /tmp/disk.img
/dev/loop0
# sfdisk -u S /dev/loop0 << EOF
65535 2162655 83
2228190 78904140 83
EOF
# partprobe /dev/loop0
# echo -n badpassword | cryptsetup luksFormat /dev/loop0p2 -
When trying to move the second LUKS encrypted partition to the right by
any amount, with the default MiB alignment, GParted displays this error
dialog and fails to even queue the operation:
Could not add this operation to the list
A partition with used sectors (78907392) greater than its
length (78905344) is not valid
[ OK ]
Overview of the steps involved:
1. The Resize/Move dialog composed a new partition to start a whole
multiple of MiB after the end of the previous non-aligned partition.
The new partition also had it's size increased to a whole multiple of
MiB, to 78907392 sectors (38529 MiB) which was 1.59 MiB larger than
before. Neither the start or end of the new partition are aligned at
this point.
2. Win_GParted::activate_resize() applied the change back to the closed
LUKS partition object, additionally making the used sectors equal to
the partition size.
(To match the fact that when opened the LUKS mapping it will
automatically fill the new larger partition size).
3. GParted_Core::snap_to_mebibyte() then aligned the partition start and
end to whole MiB boundaries, reducing the partition size in the
process to 78905344 (38528 MiB).
4. GParted_Core::snap_to_alignment() reported the error saying that it
couldn't add the operation to the list because it was invalid to have
the file system used sectors larger than the partition size.
Fix this by having the snap to alignment adjustments applied before the
dialogs update any associated file system usage. Specifically the
Resize/Move, Paste (into new) and Create New dialogs as these are the
only ones which either create or modify partition boundaries.
Validation done by snap_to_alignment() will continue to occur at the
current point when the operation is added to the list.
THIS COMMIT
snap_to_alignment() is doing two different jobs, it is (1) optionally
adjusting the new partition boundaries for MiB or Cylinder alignment;
and (2) checking that the partition boundaries and file system usage are
valid.
Split those into two different functions (1) snap_to_alignment() and
(2) valid_partition(). For now valid_partition() still calls
snap_to_alignment() so there is no functional change with this commit.
Closes#48 - Error when moving locked LUKS-encrypted partition
We have to copy the dentry->d_name before calling closedir(). If not,
the string points to nothing and the test fails (It does not fail all
the time, but only by chance).
Confirmed using valgrind. Selected output from running the unit test
under valgrind:
$ valgrind --track-origins=yes ./test_blockSpecial
==25110== Memcheck, a memory error detector
...
==25110== Command: ./test_BlockSpecial
==25110==
Running main() from src/gtest_main.cc
[==========] Running 26 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 26 tests from BlockSpecialTest
...
[ RUN ] BlockSpecialTest.NamedBlockSpecialObjectBySymlinkMatches
==25110== Invalid read of size 1
==25110== at 0x4C2C9B2: strlen (vg_replace_strmem.c:458)
==25110== by 0x40E7C4: length (char_traits.h:259)
==25110== by 0x40E7C4: append (basic_string.h:1009)
==25110== by 0x40E7C4: operator+<char, std::char_traits<char>, std::allocator<char> > (basic_string.h:2468)
==25110== by 0x40E7C4: get_link_name (test_BlockSpecial.cc:156)
==25110== by 0x40E7C4: GParted::BlockSpecialTest_NamedBlockSpecialObjectBySymlinkMatches_Test::TestBody() (test_BlockSpecial.cc:247)
...
=25110== Address 0x1231ea93 is 115 bytes inside a block of size 32,816 free'd
==25110== at 0x4C2ACBD: free (vg_replace_malloc.c:530)
==25110== by 0x9F773AC: closedir (in /usr/lib64/libc-2.17.so)
==25110== by 0x40E7AC: get_link_name (test_BlockSpecial.cc:153)
==25110== by 0x40E7AC: GParted::BlockSpecialTest_NamedBlockSpecialObjectBySymlinkMatches_Test::TestBody() (test_BlockSpecial.cc:247)
...
==25110== Block was alloc'd at
==25110== at 0x4C29BC3: malloc (vg_replace_malloc.c:299)
==25110== by 0x9F77280: __alloc_dir (in /usr/lib64/libc-2.17.so)
==25110== by 0x40E746: get_link_name (test_BlockSpecial.cc:134)
==25110== by 0x40E746: GParted::BlockSpecialTest_NamedBlockSpecialObjectBySymlinkMatches_Test::TestBody() (test_BlockSpecial.cc:247)
...
==25110== Invalid read of size 1
==25110== at 0x4C2E220: memcpy@@GLIBC_2.14 (vg_replace_strmem.c:1022)
==25110== by 0x953A997: std::string::append(char const*, unsigned long) (in /usr/lib64/libstdc++.so.6.0.19)
==25110== by 0x40E7D2: append (basic_string.h:1009)
==25110== by 0x40E7D2: operator+<char, std::char_traits<char>, std::allocator<char> > (basic_string.h:2468)
==25110== by 0x40E7D2: get_link_name (test_BlockSpecial.cc:156)
==25110== by 0x40E7D2: GParted::BlockSpecialTest_NamedBlockSpecialObjectBySymlinkMatches_Test::TestBody() (test_BlockSpecial.cc:247)
...
==25110== Address 0x1231ea93 is 115 bytes inside a block of size 32,816 free'd
==25110== at 0x4C2ACBD: free (vg_replace_malloc.c:530)
==25110== by 0x9F773AC: closedir (in /usr/lib64/libc-2.17.so)
==25110== by 0x40E7AC: get_link_name (test_BlockSpecial.cc:153)
==25110== by 0x40E7AC: GParted::BlockSpecialTest_NamedBlockSpecialObjectBySymlinkMatches_Test::TestBody() (test_BlockSpecial.cc:247)
...
==25110== Block was alloc'd at
==25110== at 0x4C29BC3: malloc (vg_replace_malloc.c:299)
==25110== by 0x9F77280: __alloc_dir (in /usr/lib64/libc-2.17.so)
==25110== by 0x40E746: get_link_name (test_BlockSpecial.cc:134)
==25110== by 0x40E746: GParted::BlockSpecialTest_NamedBlockSpecialObjectBySymlinkMatches_Test::TestBody() (test_BlockSpecial.cc:247)
...
[ OK ] BlockSpecialTest.NamedBlockSpecialObjectBySymlinkMatches (50 ms)
Selected lines from test_BlockSpecial.cc:
132 static std::string get_link_name()
133 {
134 DIR * dir = opendir( "/dev/disk/by-id" );
...
141 bool found = false;
142 struct dirent * dentry;
143 // Silence GCC [-Wparentheses] warning with double parentheses
144 while ( ( dentry = readdir( dir ) ) )
145 {
146 if ( strcmp( dentry->d_name, "." ) != 0 &&
147 strcmp( dentry->d_name, ". " ) != 0 )
148 {
149 found = true;
150 break;
151 }
152 }
153 closedir( dir );
154
155 if ( found )
156 return std::string( "/dev/disk/by-id/" ) + dentry->d_name;
So the memory referred to by dentry was allocated on line 134, freed on
153 and accessed after freed on 156.
Closes!41 - Fix test (dentry->d_name is invalidated by closedir...)