Always use directory mount point when resizing btrfs (#193)

A user received the following error when attempting to resize a mounted
btrfs file system on their NixOS distribution:

    Shrink /dev/nvme0n1p3 from 933.38 GiB to 894.32 GiB        (ERROR)
    + calibrate /dev/nvme0n1p3  00:00:00                       (SUCCESS)
    + btrfs filesystem resize 1:937759744K '/etc/machine-id'   (ERROR)
        ERROR: not a directory: /etc/machine-id
        ERROR: resize works on mounted filesystems and accepts only
        directories as argument. Passing file containing a btrfs image
        would resize the underlying filesystem instead of the image.

In the partition table section of the gparted_details /dev/nvme0n1p3 was
reported with these mount points:
    /etc/machine-id, /etc/NetworkManager/system-connections,
    /etc/ssh/ssh_host_ed25519_key, /etc/ssh/ssh_host_ed25519_key.pub,
    /etc/ssh/ssh_host_rsa_key, /etc/ssh/ssh_host_rsa_key.pub, /home,
    /nix, /nix/store, /state, /var

The user had a common configuration of NixOS which boots with an empty
tmpfs as root with a few bind mounted files and directories to provide
the needed persistent data [1][2].

Re-create an equivalent situation:
1. Create a btrfs file system and mount it:
    # mkfs.btrfs /dev/sdb1
    # mkdir /mnt/store
    # mount /dev/sdb1 /mnt/store

2. Bind mount a file from this file system else where in the hierarchy.
   The only criteria is that this mount point sorts before /mnt/store.
    # echo 'Test contents' > /mnt/store/test
    # touch /boot/test
    # mount --bind /mnt/store/test /boot/test

  The kernel reports these mount mounts:
    # grep sdb1 /proc/mounts
    /dev/sdb1 /mnt/store btrfs rw,seclabel,relatime,space_cache=v2,subvolid=5,subvol=/ 0 0
    /dev/sdb1 /boot/test btrfs rw,seclabel,relatime,space_cache=v2,subvolid=5,subvol=/ 0 0

3. Use GParted to resize this mounted btrfs file system.  It fails with
   the above error.

GParted read the mount points from /proc/mounts and sorted them.  (See
the end of Mount_Info::load_cache() for the sorting).  When resizing the
btrfs file system GParted just used the first sorted mount point.  This
was the file /etc/machine-id for the user and file /boot/test in the
re-creation, hence the error.

Fix by selecting the first directory mount point to pass to the btrfs
resize command.

[1] NixOS tmpfs as root
    https://elis.nu/blog/2020/05/nixos-tmpfs-as-root/
[2] Erase your darlings
    https://grahamc.com/blog/erase-your-darlings

Closes #193 - path used to resize btrfs needs to be a directory
This commit is contained in:
Mike Fleetwood 2022-05-22 09:30:24 +01:00 committed by Curtis Gedak
parent 6b62b9c8da
commit 59b3fd068f
3 changed files with 25 additions and 1 deletions

View File

@ -184,6 +184,7 @@ public:
Byte_Value & fs_size, Byte_Value & fs_free,
Glib::ustring & error_message ) ;
static bool is_dev_busy(const Glib::ustring& path);
static const Glib::ustring& first_directory(const std::vector<Glib::ustring>& paths);
static Byte_Value floor_size( Byte_Value value, Byte_Value rounding_size ) ;
static Byte_Value ceil_size( Byte_Value value, Byte_Value rounding_size ) ;

View File

@ -30,6 +30,7 @@
#include <glibmm/ustring.h>
#include <glibmm/stringutils.h>
#include <glibmm/shell.h>
#include <glibmm/fileutils.h>
#include <gtkmm/main.h>
#include <gtkmm/enums.h>
#include <gtkmm/stock.h>
@ -943,6 +944,18 @@ bool Utils::is_dev_busy(const Glib::ustring& path)
}
// Return the first path that is a directory, or the empty string.
const Glib::ustring& Utils::first_directory(const std::vector<Glib::ustring>& paths)
{
for (unsigned int i = 0; i < paths.size(); i++)
if (file_test(paths[i], Glib::FILE_TEST_IS_DIR))
return paths[i];
static const Glib::ustring not_found;
return not_found;
}
//Round down to multiple of rounding_size
Byte_Value Utils::floor_size( Byte_Value value, Byte_Value rounding_size )
{

View File

@ -306,7 +306,17 @@ bool btrfs::resize( const Partition & partition_new, OperationDetail & operation
operationdetail, EXEC_CHECK_STATUS );
}
else
mount_point = partition_new .get_mountpoint() ;
{
mount_point = Utils::first_directory(partition_new.get_mountpoints());
if (mount_point.empty())
{
Glib::ustring mount_list = Glib::build_path(", ", partition_new.get_mountpoints());
operationdetail.add_child(OperationDetail(
Glib::ustring::compose(_("No directory mount point found in %1"), mount_list),
STATUS_ERROR));
return false;
}
}
if ( success )
{