btrbk: refactor mountinfo

- Create tree from /proc/self/mountinfo, and use it to find mount
  points.

- Populate realpath cache from mount points, possibly reducing calls
  to `realpath`.

- Replace btrfs_mountpoint with vinfo_mountpoint(fs_type => 'btrfs)

- Tidy action "ls".

- Move code
pull/427/head
Axel Burri 2021-08-17 16:50:22 +02:00
parent 063c25ad24
commit eb69bc883e
1 changed files with 140 additions and 160 deletions

300
btrbk
View File

@ -2053,92 +2053,6 @@ sub system_mkdir($)
}
sub btrfs_mountpoint
{
my $vol = shift // die;
my $autofs_retry = shift;
DEBUG "Resolving btrfs mount point for: $vol->{PRINT}";
# get real path
my $realpath = $realpath_cache{$vol->{URL}};
unless(defined($realpath)) {
$realpath = system_realpath($vol);
# set to empty string on errors (try only once)
$realpath_cache{$vol->{URL}} = $realpath // "";
}
return undef unless($realpath);
# get all mountpoints
my $mountinfo = $mountinfo_cache{$vol->{MACHINE_ID}};
TRACE "mountinfo_cache " . ($mountinfo ? "HIT" : "MISS") . ": $vol->{MACHINE_ID}" if($do_trace);
unless($mountinfo) {
$mountinfo = system_list_mountinfo($vol);
return undef unless($mountinfo);
$mountinfo_cache{$vol->{MACHINE_ID}} = $mountinfo;
}
# find mount point (last mountinfo entry matching realpath)
$realpath .= '/' unless($realpath =~ /\/$/); # correctly handle root path="/"
my $mountpoint;
foreach(reverse @$mountinfo) {
my $mnt_path = $_->{mount_point};
$mnt_path .= '/' unless($mnt_path =~ /\/$/); # correctly handle root path="/"
if($realpath =~ /^\Q$mnt_path\E/) {
$mountpoint = $_;
last;
}
}
unless($mountpoint) {
# should never happen, as "/" should always be present in mounts
ERROR "No mount point found for: $vol->{PRINT} (realpath=\"$realpath\")";
return undef;
}
TRACE "resolved mount point (mount_source=$mountpoint->{mount_source}, subvolid=" . ($mountpoint->{MNTOPS}->{subvolid} // '<undef>') . "): $mountpoint->{mount_point}" if($do_trace);
# handle autofs
if($mountpoint->{fs_type} eq 'autofs') {
if($autofs_retry) {
DEBUG "Non-btrfs autofs mount point for: $vol->{PRINT}";
return undef;
}
DEBUG "Found autofs mount point, triggering automount on $mountpoint->{mount_point} for: $vol->{PRINT}";
btrfs_subvolume_show(vinfo($vol->{URL_PREFIX} . $mountpoint->{mount_point}, $vol->{CONFIG}));
$mountinfo_cache{$vol->{MACHINE_ID}} = undef;
return btrfs_mountpoint($vol, 1);
}
elsif($mountpoint->{fs_type} ne 'btrfs') {
DEBUG "No btrfs mount point found for: $vol->{PRINT}";
return undef;
}
# list all mountpoints of same device
my @same_source_mounts;
my $mount_source_match = $mountpoint->{mount_source};
foreach my $mnt (@$mountinfo) {
if($mnt->{mount_source} eq $mount_source_match) {
unless($mnt->{fs_type} eq 'btrfs') {
# should never happen, same device should always have fs_type=btrfs
DEBUG "Ignoring non-btrfs mount point: $mnt->{mount_source} $mnt->{mount_point} $mnt->{fs_type}";
next;
}
unless($mnt->{MNTOPS}->{subvolid}) {
# kernel <= 4.2 does not have subvolid=NN in /proc/self/mounts, read it with btrfs-progs
DEBUG "No subvolid provided in mounts for: $mnt->{mount_point}";
my $detail = btrfs_subvolume_show(vinfo($vol->{URL_PREFIX} . $mnt->{mount_point}, $vol->{CONFIG}));
return undef unless($detail);
$mnt->{MNTOPS}->{subvolid} = $detail->{id} || die; # also affects %mountinfo_cache
}
TRACE "using btrfs mount point (mount_source=$mnt->{mount_source}, subvolid=$mnt->{MNTOPS}->{subvolid}): $mnt->{mount_point}" if($do_trace);
push(@same_source_mounts, $mnt);
}
}
DEBUG "Btrfs mount point for \"$vol->{PRINT}\": $mountpoint->{mount_point} (mount_source=$mountpoint->{mount_source}, subvolid=$mountpoint->{MNTOPS}->{subvolid})";
return ($mountpoint->{mount_point}, $realpath, $mountpoint->{MNTOPS}->{subvolid}, $mountpoint->{mount_source}, \@same_source_mounts);
}
sub system_read_raw_info_dir($)
{
my $droot = shift // die;
@ -3044,22 +2958,122 @@ sub add_btrbk_filename_info($;$)
}
sub _find_mountpoint($$)
{
my $root = shift;
my $path = shift;
$path .= '/' unless($path =~ /\/$/); # append trailing slash
while (my $tree = $root->{SUBTREE}) {
my $m = undef;
foreach (@$tree) {
$m = $_, last if($path =~ /^\Q$_->{mount_point}\E\//);
}
last unless defined $m;
$root = $m;
}
TRACE "resolved mount point for \"$path\": $root->{mount_point} (mount_source=$root->{mount_source}, subvolid=" . ($root->{MNTOPS}->{subvolid} // '<undef>') . ")" if($do_trace);
return $root;
}
sub mountinfo_tree($)
{
my $vol = shift;
my $mountinfo = $mountinfo_cache{$vol->{MACHINE_ID}};
TRACE "mountinfo_cache " . ($mountinfo ? "HIT" : "MISS") . ": $vol->{MACHINE_ID}" if($do_trace);
unless($mountinfo) {
$mountinfo = system_list_mountinfo($vol);
return undef unless($mountinfo);
$mountinfo_cache{$vol->{MACHINE_ID}} = $mountinfo;
}
return $mountinfo->[0]->{TREE_ROOT} if($mountinfo->[0]->{TREE_ROOT});
my %id = map +( $_->{mount_id} => $_ ), @$mountinfo;
my $tree_root;
foreach my $node (@$mountinfo) {
if(my $parent = $id{$node->{parent_id}}) {
$node->{PARENT} = $parent;
push @{$parent->{SUBTREE}}, $node;
} else {
die "multiple root mount points" if($tree_root);
$tree_root = $node;
}
# populate cache (mount points are always real paths)
$realpath_cache{$vol->{URL_PREFIX} . $node->{mount_point}} = $node->{mount_point};
}
die "no root mount point" unless($tree_root);
$_->{TREE_ROOT} = $tree_root foreach (@$mountinfo);
$tree_root->{MOUNTINFO_LIST} = $mountinfo;
return $tree_root;
}
sub vinfo_mountpoint
{
my $vol = shift // die;
my %args = @_;
DEBUG "Resolving mount point for: $vol->{PRINT}";
my $mountinfo_root = mountinfo_tree($vol);
return undef unless($mountinfo_root);
my $realpath = $realpath_cache{$vol->{URL}};
unless(defined($realpath)) {
$realpath = system_realpath($vol);
# set to empty string on errors (try only once)
$realpath_cache{$vol->{URL}} = $realpath // "";
}
return undef unless($realpath);
my $mountpoint = _find_mountpoint($mountinfo_root, $realpath);
# handle autofs
if($mountpoint->{fs_type} eq 'autofs') {
if($args{autofs_retry}) {
DEBUG "Non-btrfs autofs mount point for: $vol->{PRINT}";
return undef;
}
DEBUG "Found autofs mount point, triggering automount on $mountpoint->{mount_point} for: $vol->{PRINT}";
btrfs_subvolume_show(vinfo($vol->{URL_PREFIX} . $mountpoint->{mount_point}, $vol->{CONFIG}));
$mountinfo_cache{$vol->{MACHINE_ID}} = undef;
return vinfo_mountpoint($vol, %args, autofs_retry => 1);
}
if($args{fs_type} && ($mountpoint->{fs_type} ne $args{fs_type})) {
ERROR "Not a btrfs filesystem (mountpoint=\"$mountpoint->{mount_point}\", fs_type=\"$mountpoint->{fs_type}\"): $vol->{PRINT}";
return undef;
}
DEBUG "Mount point for \"$vol->{PRINT}\": $mountpoint->{mount_point} (mount_source=$mountpoint->{mount_source}, fs_type=$mountpoint->{fs_type})";
return ($realpath, $mountpoint);
}
sub vinfo_init_root($)
{
my $vol = shift || die;
# resolve btrfs tree from mount point
@stderr = (); # clear @stderr (propagated for logging)
my ($mnt_path, $real_path, $subvolid, $mount_source, $mountpoints) = btrfs_mountpoint($vol);
return undef unless($mnt_path && $real_path && $subvolid);
my ($real_path, $mountpoint) = vinfo_mountpoint($vol, fs_type => 'btrfs');
return undef unless($mountpoint);
my @same_source_mounts = grep { $_->{mount_source} eq $mountpoint->{mount_source} } @{$mountpoint->{TREE_ROOT}{MOUNTINFO_LIST}};
foreach my $mnt (grep { !defined($_->{MNTOPS}{subvolid}) } @same_source_mounts) {
# kernel <= 4.2 does not have subvolid=NN in /proc/self/mounts, read it with btrfs-progs
DEBUG "No subvolid provided in mounts for: $mnt->{mount_point}";
my $detail = btrfs_subvolume_show(vinfo($vol->{URL_PREFIX} . $mnt->{mount_point}, $vol->{CONFIG}));
return undef unless($detail);
$mnt->{MNTOPS}{subvolid} = $detail->{id} || die; # also affects %mountinfo_cache
}
# read btrfs tree for the mount point
@stderr = (); # clear @stderr (propagated for logging)
my $mnt_path = $mountpoint->{mount_point};
my $mnt_vol = vinfo($vol->{URL_PREFIX} . $mnt_path, $vol->{CONFIG});
my $mnt_tree_root = btr_tree($mnt_vol, $subvolid, $mount_source, $mountpoints);
my $mnt_tree_root = btr_tree($mnt_vol, $mountpoint->{MNTOPS}{subvolid}, $mountpoint->{mount_source}, \@same_source_mounts);
return undef unless($mnt_tree_root);
# find longest match in btrfs tree
$real_path .= '/' unless($real_path =~ /\/$/); # correctly handle root path="/"
my $ret = _get_longest_match($mnt_tree_root, $mnt_path, $real_path) // die;
my $tree_root = $ret->{node};
return undef unless($tree_root);
@ -5928,90 +5942,54 @@ MAIN:
my $exit_status = 0;
my %data_uniq;
foreach my $root_vol (@subvol_args) {
# map url to real path (we need to match against mount points below)
my $root_path = system_realpath($root_vol);
unless($root_path) {
ERROR "Cannot find real path for: $root_vol->{PATH}", @stderr;
my ($root_path, $mountpoint) = vinfo_mountpoint($root_vol);
unless($mountpoint) {
$exit_status = 1;
next;
}
$root_vol = vinfo($root_vol->{URL_PREFIX} . $root_path, $config);
$root_path .= '/' unless($root_path =~ /\/$/); # append trailing slash
INFO "Listing subvolumes for directory: $root_vol->{PRINT}";
my $mountinfo = $mountinfo_cache{$root_vol->{MACHINE_ID}};
unless($mountinfo) {
$mountinfo = system_list_mountinfo($root_vol);
unless($mountinfo) {
my @search = ( $mountpoint );
while(my $mnt = shift @search) {
unshift @search, @{$mnt->{SUBTREE}} if($mnt->{SUBTREE});
next if($mnt->{fs_type} ne "btrfs");
my $vol = vinfo($root_vol->{URL_PREFIX} . $mnt->{mount_point}, $config);
unless(vinfo_init_root($vol)) {
ERROR "Failed to fetch subvolume detail for: $vol->{PRINT}", @stderr;
$exit_status = 1;
next;
}
$mountinfo_cache{$root_vol->{MACHINE_ID}} = $mountinfo;
}
my @mnt_path_hidden;
foreach my $mnt (reverse @$mountinfo) {
my $mnt_path = $mnt->{mount_point};
$mnt_path .= '/' unless($mnt_path =~ /\/$/); # append trailing slash
my $subvol_list = vinfo_subvol_list($vol);
my $count_added = 0;
foreach my $svol ($vol, @$subvol_list) {
my $svol_path = $svol->{PATH};
$svol_path =~ s/^\/\//\//; # sanitize "//" (see vinfo_child)
if(($mnt->{fs_type} eq "btrfs") &&
(($root_path =~ /^\Q$mnt_path\E/) || ($mnt_path =~ /^\Q$root_path\E/)))
{
unless(defined(check_file($mnt->{mount_point}, { absolute => 1}))) {
ERROR "Ignoring btrfs mount point (unsupported file name): $mnt->{mount_point}";
$exit_status = 1;
if(_find_mountpoint($mnt, $svol_path) ne $mnt) {
DEBUG "Subvolume is hidden by another mount point: $svol->{PRINT}";
next;
}
# we know those are real paths, prevents calling readlink in btrfs_mountpoint
$realpath_cache{$root_vol->{URL_PREFIX} . $mnt->{mount_point}} = $mnt->{mount_point};
my $vol = vinfo($root_vol->{URL_PREFIX} . $mnt->{mount_point}, $config);
DEBUG "Processing btrfs mount point: $mnt_path";
unless(vinfo_init_root($vol)) {
ERROR "Failed to fetch subvolume detail for: $vol->{PRINT}", @stderr;
$exit_status = 1;
next;
}
my $subvol_list = vinfo_subvol_list($vol);
my $count_added = 0;
foreach my $svol ($vol, @$subvol_list) {
my $svol_path = $svol->{PATH};
$svol_path =~ s/^\/\//\//; # sanitize "//" (see vinfo_child)
my $svol_path_ts = $svol_path . ($svol_path =~ /\/$/ ? "" : "/"); # append trailing slash
next unless($svol_path_ts =~ /^\Q$root_path\E/);
if(grep { $svol_path_ts =~ /^\Q$_\E/ } @mnt_path_hidden) {
DEBUG "subvolume is hidden by another mount point: $svol->{PRINT}";
next;
}
$data_uniq{$svol->{PRINT}} = {
%{$svol->{node}}, # copy node
top => $svol->{node}{top_level}, # alias (narrow column)
mount_point => $svol->{VINFO_MOUNTPOINT}{PATH},
mount_source => $svol->{node}{TREE_ROOT}{mount_source},
mount_subvolid => $mnt->{MNTOPS}{subvolid},
mount_subvol => $mnt->{MNTOPS}{subvol},
subvolume_path => $svol->{node}{path},
subvolume_rel_path => $svol->{node}{REL_PATH},
url => $svol->{URL},
host => $svol->{HOST},
path => $svol_path,
flags => ($svol->{node}{readonly} ? "readonly" : undef),
};
$count_added++;
}
DEBUG "Listing $count_added/" . (scalar(@$subvol_list) + 1) . " subvolumes for btrfs mount: $vol->{PRINT}";
$data_uniq{$svol->{PRINT}} = {
%{$svol->{node}}, # copy node
top => $svol->{node}{top_level}, # alias (narrow column)
mount_point => $svol->{VINFO_MOUNTPOINT}{PATH},
mount_source => $svol->{node}{TREE_ROOT}{mount_source},
mount_subvolid => $mnt->{MNTOPS}{subvolid},
mount_subvol => $mnt->{MNTOPS}{subvol},
subvolume_path => $svol->{node}{path},
subvolume_rel_path => $svol->{node}{REL_PATH},
url => $svol->{URL},
host => $svol->{HOST},
path => $svol_path,
flags => ($svol->{node}{readonly} ? "readonly" : undef),
};
$count_added++;
}
else {
TRACE "Skipping mount point: $mnt_path (fs_type=$mnt->{fs_type})" if($do_trace);
}
last if($root_path =~ /^\Q$mnt_path\E/);
push @mnt_path_hidden, $mnt_path;
DEBUG "Listing $count_added/" . (scalar(@$subvol_list) + 1) . " subvolumes for btrfs mount: $vol->{PRINT}";
}
}
@ -6518,7 +6496,9 @@ MAIN:
my $push_data = sub {
my ($vol, $type) = @_;
return if $processed{$vol->{URL}};
my (undef, undef, undef, $mount_source, undef) = btrfs_mountpoint($vol);
my $mountpoint = vinfo_mountpoint($vol, fs_type => 'btrfs');
return unless($mountpoint);
my $mount_source = $mountpoint->{mount_source};
my $mid = $vol->{MACHINE_ID} . $mount_source;
$usage_cache{$mid} //= btrfs_filesystem_usage($vol);
push @data, { %{$usage_cache{$mid}},