-# Copyright (c) 2005-2008 Zmanda, Inc. All Rights Reserved.
+# Copyright (c) 2008,2009,2010 Zmanda, Inc. All Rights Reserved.
#
-# This library is free software; you can redistribute it and/or modify it
-# under the terms of the GNU Lesser General Public License version 2.1 as
-# published by the Free Software Foundation.
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 as published
+# by the Free Software Foundation.
#
-# This library is distributed in the hope that it will be useful, but
+# 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 Lesser General Public
-# License for more details.
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this library; if not, write to the Free Software Foundation,
-# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
-# Contact information: Zmanda Inc., 465 S Mathlida Ave, Suite 300
-# Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
+# Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
+# Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
package Amanda::Changer::disk;
use File::Glob qw( :glob );
use File::Path;
-use Amanda::Config qw( :getconf );
+use File::Basename;
+use Amanda::Config qw( :getconf string_to_boolean );
use Amanda::Debug;
use Amanda::Changer;
use Amanda::MainLoop;
+use Amanda::Device qw( :constants );
=head1 NAME
| | data -> '../slot4'
|- drive1/ -|
| | data -> '../slot1'
- |- current -> slot5
+ |- data -> slot5
|- slot1/
|- slot2/
|- ...
|- slot$n/
-The user should create the desired number C<slot$n> subdirectories, and
-the changer will take care of dynamically creating the drives as needed,
-and track the "current" slot using the eponymous symlink.
+The user should create the desired number of C<slot$n> subdirectories. The
+changer will take care of dynamically creating the drives as needed, and track
+the current slot using a "data" symlink. This allows use of "file:$dir" as a
+device operating on the current slot, although note that it is unlocked.
Drives are dynamically allocated as Amanda applications request access to
particular slots. Each drive is represented as a subdirectory containing a
'data' symlink pointing to the "loaded" slot.
-=head1 TODO
-
- - better locking (at least to work on a shared filesystem, if not NFS)
- - manpage
+See the amanda-changers(7) manpage for usage information.
=cut
+# STATE
+#
+# The device state is shared between all changers accessing the same changer.
+# It is a hash with keys:
+# drives - see below
+#
+# The 'drives' key is a hash, with drive as keys and hashes
+# as values. Each drive's hash has keys:
+# pid - the pid that reserved that drive.
+#
+
+
sub new {
my $class = shift;
- my ($cc, $tpchanger) = @_;
+ my ($config, $tpchanger) = @_;
my ($dir) = ($tpchanger =~ /chg-disk:(.*)/);
+ my $properties = $config->{'properties'};
# note that we don't track outstanding Reservation objects -- we know
# they're gone when they delete their drive directory
my $self = {
dir => $dir,
+ config => $config,
+ state_filename => "$dir/state",
+
+ # list of all reservations
+ reservation => {},
+
+ # this is set to 0 by various test scripts,
+ # notably Amanda_Taper_Scan_traditional
+ support_fast_search => 1,
};
bless ($self, $class);
+
+ $self->{'num-slot'} = $config->get_property('num-slot');
+ $self->{'auto-create-slot'} = $config->get_boolean_property(
+ 'auto-create-slot', 0);
+ $self->{'removable'} = $config->get_boolean_property('removable', 0);
+ $self->{'mount'} = $config->get_boolean_property('mount', 0);
+ $self->{'umount'} = $config->get_boolean_property('umount', 0);
+ $self->{'umount_lockfile'} = $config->get_property('umount-lockfile');
+ $self->{'umount_idle'} = $config->get_property('umount-idle');
+ if (defined $self->{'umount_lockfile'}) {
+ $self->{'fl'} = Amanda::Util::file_lock->new($self->{'umount_lockfile'})
+ }
+
+ $self->_validate();
+ return $self->{'fatal_error'} if defined $self->{'fatal_error'};
+
return $self;
}
+sub DESTROY {
+ my $self = shift;
+
+ $self->SUPER::DESTROY();
+}
+
+sub quit {
+ my $self = shift;
+
+ $self->force_unlock();
+ delete $self->{'fl'};
+ $self->SUPER::quit();
+}
+
sub load {
my $self = shift;
my %params = @_;
+ my $old_res_cb = $params{'res_cb'};
+ my $state;
- die "no res_cb supplied" unless (exists $params{'res_cb'});
+ $self->validate_params('load', \%params);
- if (exists $params{'slot'}) {
- $self->_load_by_slot(%params);
- } elsif (exists $params{'label'}) {
- $self->_load_by_label(%params);
- } else {
- die "Invalid parameters to 'load'";
- }
+ return if $self->check_error($params{'res_cb'});
+
+ $self->with_disk_locked_state($params{'res_cb'}, sub {
+ my ($state, $res_cb) = @_;
+ $params{'state'} = $state;
+
+ # overwrite the callback for _load_by_xxx
+ $params{'res_cb'} = $res_cb;
+
+ if (exists $params{'slot'} or exists $params{'relative_slot'}) {
+ $self->_load_by_slot(%params);
+ } elsif (exists $params{'label'}) {
+ $self->_load_by_label(%params);
+ }
+ });
}
-sub info {
+sub info_key {
my $self = shift;
- my %params = @_;
+ my ($key, %params) = @_;
my %results;
+ my $info_cb = $params{'info_cb'};
- die "no info_cb supplied" unless (exists $params{'info_cb'});
- die "no info supplied" unless (exists $params{'info'});
+ return if $self->check_error($info_cb);
- for my $inf (@{$params{'info'}}) {
- if ($inf eq 'num_slots') {
- my @slots = $self->_all_slots();
- $results{$inf} = scalar @slots;
- } else {
- warn "Ignoring request for info key '$inf'";
- }
- }
+ my $steps = define_steps
+ cb_ref => \$info_cb;
- Amanda::MainLoop::call_later($params{'info_cb'}, undef, %results);
+ step init => sub {
+ $self->try_lock($steps->{'locked'});
+ };
+
+ step locked => sub {
+ return if $self->check_error($info_cb);
+
+ # no need for synchronization -- all of these values are static
+
+ if ($key eq 'num_slots') {
+ my @slots = $self->_all_slots();
+ $results{$key} = scalar @slots;
+ } elsif ($key eq 'vendor_string') {
+ $results{$key} = 'chg-disk'; # mostly just for testing
+ } elsif ($key eq 'fast_search') {
+ $results{$key} = $self->{'support_fast_search'};
+ }
+
+ $self->try_unlock();
+ $info_cb->(undef, %results) if $info_cb;
+ }
}
sub reset {
my $slot;
my @slots = $self->_all_slots();
- $slot = (scalar @slots)? $slots[0] : 0;
- $self->_set_current($slot);
+ return if $self->check_error($params{'finished_cb'});
- if (exists $params{'finished_cb'}) {
- Amanda::MainLoop::call_later($params{'finished_cb'});
- }
+ $self->with_disk_locked_state($params{'finished_cb'}, sub {
+ my ($state, $finished_cb) = @_;
+
+ $slot = (scalar @slots)? $slots[0] : 0;
+ $self->_set_current($slot);
+
+ $finished_cb->();
+ });
+}
+
+sub inventory {
+ my $self = shift;
+ my %params = @_;
+
+ return if $self->check_error($params{'inventory_cb'});
+
+ $self->with_disk_locked_state($params{'inventory_cb'}, sub {
+ my ($state, $finished_cb) = @_;
+ my @inventory;
+
+ my @slots = $self->_all_slots();
+ my $current = $self->_get_current();
+ for my $slot (@slots) {
+ my $s = { slot => $slot, state => Amanda::Changer::SLOT_FULL };
+ $s->{'reserved'} = $self->_is_slot_in_use($state, $slot);
+ my $label = $self->_get_slot_label($slot);
+ if ($label) {
+ $s->{'label'} = $self->_get_slot_label($slot);
+ $s->{'f_type'} = "".$Amanda::Header::F_TAPESTART;
+ $s->{'device_status'} = "".$DEVICE_STATUS_SUCCESS;
+ } else {
+ $s->{'label'} = undef;
+ $s->{'f_type'} = "".$Amanda::Header::F_EMPTY;
+ $s->{'device_status'} = "".$DEVICE_STATUS_VOLUME_UNLABELED;
+ }
+ $s->{'current'} = 1 if $slot eq $current;
+ push @inventory, $s;
+ }
+ $finished_cb->(undef, \@inventory);
+ });
+}
+
+sub set_meta_label {
+ my $self = shift;
+ my %params = @_;
+
+ return if $self->check_error($params{'finished_cb'});
+
+ $self->with_disk_locked_state($params{'finished_cb'}, sub {
+ my ($state, $finished_cb) = @_;
+
+ $state->{'meta'} = $params{'meta'};
+ $finished_cb->(undef);
+ });
+}
+
+sub with_disk_locked_state {
+ my $self = shift;
+ my ($cb, $sub) = @_;
+
+ my $steps = define_steps
+ cb_ref => \$cb;
+
+ step init => sub {
+ $self->try_lock($steps->{'locked'});
+ };
+
+ step locked => sub {
+ my $err = shift;
+ return $cb->($err) if $err;
+ $self->with_locked_state($self->{'state_filename'},
+ sub { my @args = @_;
+ $self->try_unlock();
+ $cb->(@args);
+ },
+ $sub);
+ };
+}
+
+sub get_meta_label {
+ my $self = shift;
+ my %params = @_;
+
+ return if $self->check_error($params{'finished_cb'});
+
+ $self->with_disk_locked_state($params{'finished_cb'}, sub {
+ my ($state, $finished_cb) = @_;
+
+ $finished_cb->(undef, $state->{'meta'});
+ });
}
sub _load_by_slot {
my $self = shift;
my %params = @_;
- my $slot = $params{'slot'};
my $drive;
+ my $slot;
- if ($slot eq "current") {
- $slot = $self->_get_current();
- } elsif ($slot eq "next") {
- $slot = $self->_get_current();
- $slot = $self->_get_next($slot);
+ if (exists $params{'relative_slot'}) {
+ if ($params{'relative_slot'} eq "current") {
+ $slot = $self->_get_current();
+ } elsif ($params{'relative_slot'} eq "next") {
+ if (exists $params{'slot'}) {
+ $slot = $params{'slot'};
+ } else {
+ $slot = $self->_get_current();
+ }
+ $slot = $self->_get_next($slot);
+ $self->_set_current($slot) if ($params{'set_current'});
+ } else {
+ return $self->make_error("failed", $params{'res_cb'},
+ reason => "invalid",
+ message => "Invalid relative slot '$params{relative_slot}'");
+ }
+ } else {
+ $slot = $params{'slot'};
+ }
+
+ if (exists $params{'except_slots'} and exists $params{'except_slots'}->{$slot}) {
+ return $self->make_error("failed", $params{'res_cb'},
+ reason => "notfound",
+ message => "all slots have been loaded");
}
if (!$self->_slot_exists($slot)) {
- Amanda::MainLoop::call_later($params{'res_cb'},
- "Slot $slot not found", undef);
- return;
+ return $self->make_error("failed", $params{'res_cb'},
+ reason => "invalid",
+ message => "Slot $slot not found");
}
- if ($drive = $self->_is_slot_in_use($slot)) {
- Amanda::MainLoop::call_later($params{'res_cb'},
- "Slot $slot is already in use by drive '$drive'", undef);
- return;
+ if ($drive = $self->_is_slot_in_use($params{'state'}, $slot)) {
+ return $self->make_error("failed", $params{'res_cb'},
+ reason => "volinuse",
+ slot => $slot,
+ message => "Slot $slot is already in use by drive '$drive' and process '$params{state}->{drives}->{$drive}->{pid}'");
}
$drive = $self->_alloc_drive();
$self->_load_drive($drive, $slot);
$self->_set_current($slot) if ($params{'set_current'});
- my $next_slot = $self->_get_next($slot);
-
- Amanda::MainLoop::call_later($params{'res_cb'},
- undef, Amanda::Changer::disk::Reservation->new($self, $drive, $slot, $next_slot));
+ $self->_make_res($params{'state'}, $params{'res_cb'}, $drive, $slot);
}
sub _load_by_label {
$slot = $self->_find_label($label);
if (!defined $slot) {
- Amanda::MainLoop::call_later($params{'res_cb'},
- "Label '$label' not found", undef);
- return;
+ return $self->make_error("failed", $params{'res_cb'},
+ reason => "notfound",
+ message => "Label '$label' not found");
}
- if ($drive = $self->_is_slot_in_use($slot)) {
- Amanda::MainLoop::call_later($params{'res_cb'},
- "Slot $slot, containing '$label', is already in use by drive '$drive'", undef);
+ if ($drive = $self->_is_slot_in_use($params{'state'}, $slot)) {
+ return $self->make_error("failed", $params{'res_cb'},
+ reason => "volinuse",
+ message => "Slot $slot, containing '$label', is already " .
+ "in use by drive '$drive'");
}
$drive = $self->_alloc_drive();
$self->_load_drive($drive, $slot);
$self->_set_current($slot) if ($params{'set_current'});
- my $next_slot = $self->_get_next($slot);
+ $self->_make_res($params{'state'}, $params{'res_cb'}, $drive, $slot);
+}
+
+sub _make_res {
+ my $self = shift;
+ my ($state, $res_cb, $drive, $slot) = @_;
+ my $res;
+
+ my $device = Amanda::Device->new("file:$drive");
+ if ($device->status != $DEVICE_STATUS_SUCCESS) {
+ return $self->make_error("failed", $res_cb,
+ reason => "device",
+ message => "opening 'file:$drive': " . $device->error_or_status());
+ }
+
+ if (my $err = $self->{'config'}->configure_device($device)) {
+ return $self->make_error("failed", $res_cb,
+ reason => "device",
+ message => $err);
+ }
+
+ $res = Amanda::Changer::disk::Reservation->new($self, $device, $drive, $slot);
+ $state->{drives}->{$drive}->{pid} = $$;
+ $device->read_label();
- Amanda::MainLoop::call_later($params{'res_cb'},
- undef, Amanda::Changer::disk::Reservation->new($self, $drive, $slot, $next_slot));
+ $res_cb->(undef, $res);
}
# Internal function to find an unused (nonexistent) driveN subdirectory and
}
# Internal function to enumerate all available slots. Slots are described by
-# integers.
+# strings.
sub _all_slots {
my ($self) = @_;
my $dir = _quote_glob($self->{'dir'});
push @slots, $slot + 0;
}
- return sort @slots;
+ return map { "$_"} sort { $a <=> $b } @slots;
}
# Internal function to determine whether a slot exists.
# Internal function to determine if a slot (specified by number) is in use by a
# drive, and return the path for that drive if so.
sub _is_slot_in_use {
- my ($self, $slot) = @_;
+ my ($self, $state, $slot) = @_;
my $dir = _quote_glob($self->{'dir'});
for my $symlink (bsd_glob("$dir/drive*/data")) {
}
if ($tslot+0 == $slot) {
- $symlink =~ s{/data$}{}; # strip the trailing '/data'
- return $symlink;
+ my $drive = $symlink;
+ $drive =~ s{/data$}{}; # strip the trailing '/data'
+
+ #check if process is alive
+ my $pid = $state->{drives}->{$drive}->{pid};
+ if (!defined $pid or !Amanda::Util::is_pid_alive($pid)) {
+ unlink("$drive/data")
+ or warn("Could not unlink '$drive/data': $!");
+ rmdir("$drive")
+ or warn("Could not rmdir '$drive': $!");
+ delete $state->{drives}->{$drive}->{pid};
+ next;
+ }
+ return $drive;
}
}
return 0;
}
+sub _get_slot_label {
+ my ($self, $slot) = @_;
+ my $dir = _quote_glob($self->{'dir'});
+
+ for my $symlink (bsd_glob("$dir/slot$slot/00000.*")) {
+ my ($label) = ($symlink =~ qr{\/00000\.([^/]*)$});
+ return $label;
+ }
+
+ return ''; # known, but blank
+}
+
# Internal function to point a drive to a slot
sub _load_drive {
my ($self, $drive, $slot) = @_;
return $all_slots[0];
}
-# Get the 'current' slot, represented as a symlink named 'current'
+# Get the 'current' slot, represented as a symlink named 'data'
sub _get_current {
my ($self) = @_;
- my $curlink = $self->{'dir'} . "/current";
+ my $curlink = $self->{'dir'} . "/data";
+
+ # for 2.6.1-compatibility, also parse a "current" symlink
+ my $oldlink = $self->{'dir'} . "/current";
+ if (-l $oldlink and ! -e $curlink) {
+ rename($oldlink, $curlink);
+ }
if (-l $curlink) {
my $target = readlink($curlink);
# Set the 'current' slot
sub _set_current {
my ($self, $slot) = @_;
- my $curlink = $self->{'dir'} . "/current";
+ my $curlink = $self->{'dir'} . "/data";
- if (-e $curlink) {
+ if (-l $curlink or -e $curlink) {
unlink($curlink)
- or die("Could not unlink '$curlink'");
+ or warn("Could not unlink '$curlink'");
}
# TODO: locking
return $filename;
}
+sub _validate() {
+ my $self = shift;
+ my $dir = $self->{'dir'};
+
+ unless (-d $dir) {
+ return $self->make_error("fatal", undef,
+ message => "directory '$dir' does not exist");
+ }
+
+ if ($self->{'removable'}) {
+ my ($dev, $ino) = stat $dir;
+ my $parentdir = dirname $dir;
+ my ($pdev, $pino) = stat $parentdir;
+ if ($dev == $pdev) {
+ if ($self->{'mount'}) {
+ system $Amanda::Constants::MOUNT, $dir;
+ ($dev, $ino) = stat $dir;
+ }
+ }
+ if ($dev == $pdev) {
+ return $self->make_error("failed", undef,
+ reason => "notfound",
+ message => "No removable disk mounted on '$dir'");
+ }
+ }
+
+ if ($self->{'num-slot'}) {
+ for my $i (1..$self->{'num-slot'}) {
+ my $slot_dir = "$dir/slot$i";
+ if (!-e $slot_dir) {
+ if ($self->{'auto-create-slot'}) {
+ if (!mkdir ($slot_dir)) {
+ return $self->make_error("fatal", undef,
+ message => "Can't create '$slot_dir': $!");
+ }
+ } else {
+ return $self->make_error("fatal", undef,
+ message => "slot $i doesn't exists '$slot_dir'");
+ }
+ }
+ }
+ } else {
+ if ($self->{'auto-create-slot'}) {
+ return $self->make_error("fatal", undef,
+ message => "property 'auto-create-slot' set but property 'num-slot' is not set");
+ }
+ }
+ return undef;
+}
+
+sub try_lock {
+ my $self = shift;
+ my $cb = shift;
+ my $poll = 0; # first delay will be 0.1s; see below
+
+ my $steps = define_steps
+ cb_ref => \$cb;
+
+ step init => sub {
+ if ($self->{'mount'} && defined $self->{'fl'} &&
+ !$self->{'fl'}->locked()) {
+ return $steps->{'lock'}->();
+ }
+ $steps->{'lock_done'}->();
+ };
+
+ step lock => sub {
+ my $rv = $self->{'fl'}->lock_rd();
+ if ($rv == 1) {
+ # loop until we get the lock, increasing $poll to 10s
+ $poll += 100 unless $poll >= 10000;
+ return Amanda::MainLoop::call_after($poll, $steps->{'lock'});
+ } elsif ($rv == -1) {
+ return $self->make_error("fatal", $cb,
+ message => "Error locking '$self->{'umount_lockfile'}'");
+ } elsif ($rv == 0) {
+ if (defined $self->{'umount_src'}) {
+ $self->{'umount_src'}->remove();
+ $self->{'umount_src'} = undef;
+ }
+ return $steps->{'lock_done'}->();
+ }
+ };
+
+ step lock_done => sub {
+ my $err = $self->_validate();
+ $cb->($err);
+ };
+}
+
+sub try_umount {
+ my $self = shift;
+
+ my $dir = $self->{'dir'};
+ if ($self->{'removable'} && $self->{'umount'}) {
+ my ($dev, $ino) = stat $dir;
+ my $parentdir = dirname $dir;
+ my ($pdev, $pino) = stat $parentdir;
+ if ($dev != $pdev) {
+ system $Amanda::Constants::UMOUNT, $dir;
+ }
+ }
+}
+
+sub force_unlock {
+ my $self = shift;
+
+ if (keys( %{$self->{'reservation'}}) == 0 ) {
+ if ($self->{'fl'}) {
+ if ($self->{'fl'}->locked()) {
+ $self->{'fl'}->unlock();
+ }
+ if ($self->{'umount'}) {
+ if (defined $self->{'umount_src'}) {
+ $self->{'umount_src'}->remove();
+ $self->{'umount_src'} = undef;
+ }
+ if ($self->{'fl'}->lock_wr() == 0) {
+ $self->try_umount();
+ $self->{'fl'}->unlock();
+ }
+ }
+ }
+ }
+}
+
+sub try_unlock {
+ my $self = shift;
+
+ my $do_umount = sub {
+ local $?;
+
+ $self->{'umount_src'} = undef;
+ if ($self->{'fl'}->lock_wr() == 0) {
+ $self->try_umount();
+ $self->{'fl'}->unlock();
+ }
+ };
+
+ if (defined $self->{'umount_idle'}) {
+ if ($self->{'umount_idle'} == 0) {
+ return $self->force_unlock();
+ }
+ if (defined $self->{'fl'}) {
+ if (keys( %{$self->{'reservation'}}) == 0 ) {
+ if ($self->{'fl'}->locked()) {
+ $self->{'fl'}->unlock();
+ }
+ if ($self->{'umount'}) {
+ if (defined $self->{'umount_src'}) {
+ $self->{'umount_src'}->remove();
+ $self->{'umount_src'} = undef;
+ }
+ $self->{'umount_src'} = Amanda::MainLoop::call_after(
+ 0+$self->{'umount_idle'},
+ $do_umount);
+ }
+ }
+ }
+ }
+}
+
package Amanda::Changer::disk::Reservation;
use vars qw( @ISA );
@ISA = qw( Amanda::Changer::Reservation );
sub new {
my $class = shift;
- my ($chg, $drive, $slot, $next_slot) = @_;
+ my ($chg, $device, $drive, $slot) = @_;
my $self = Amanda::Changer::Reservation::new($class);
$self->{'chg'} = $chg;
$self->{'drive'} = $drive;
- $self->{'device_name'} = "file:$drive";
+ $self->{'device'} = $device;
$self->{'this_slot'} = $slot;
- $self->{'next_slot'} = $next_slot;
+ $self->{'chg'}->{'reservation'}->{$slot} += 1;
return $self;
}
rmdir("$drive")
or warn("Could not rmdir '$drive': $!");
- if (exists $params{'finished_cb'}) {
- Amanda::MainLoop::call_later($params{'finished_cb'}, undef);
+ # unref the device, for good measure
+ $self->{'device'} = undef;
+ my $slot = $self->{'this_slot'};
+
+ my $finish = sub {
+ $self->{'chg'}->{'reservation'}->{$slot} -= 1;
+ delete $self->{'chg'}->{'reservation'}->{$slot} if
+ $self->{'chg'}->{'reservation'}->{$slot} == 0;
+ $self->{'chg'}->try_unlock();
+ delete $self->{'chg'};
+ $self = undef;
+ return $params{'finished_cb'}->();
+ };
+
+ if (exists $params{'unlocked'}) {
+ my $state = $params{state};
+ delete $state->{drives}->{$drive}->{pid};
+ return $finish->();
}
+
+ $self->{chg}->with_locked_state($self->{chg}->{'state_filename'},
+ $finish, sub {
+ my ($state, $finished_cb) = @_;
+
+ delete $state->{drives}->{$drive}->{pid};
+
+ $finished_cb->();
+ });
+}
+
+sub get_meta_label {
+ my $self = shift;
+ my %params = @_;
+
+ $params{'slot'} = $self->{'this_slot'};
+ $self->{'chg'}->get_meta_label(%params);
+}
+
+sub set_meta_label {
+ my $self = shift;
+ my %params = @_;
+
+ $params{'slot'} = $self->{'this_slot'};
+ $self->{'chg'}->set_meta_label(%params);
}