package fs::dmraid;

use diagnostics;
use strict;

#-######################################################################################
#- misc imports
#-######################################################################################
use common;
use modules;
use devices;
use fs::type;
use fs::wild_device;
use run_program;


=head1 SYNOPSYS

Manage fake RAIDs using dmraid

=head1 Functions

=over

=item init()

Load kernel modules, init device mapper then scan for fake RAIDs.

=cut

sub init() {
    whereis_binary('dmraid') or die "dmraid not installed";

    eval { modules::load('dm-mirror', 'dm-zero') };
    devices::init_device_mapper();
    if ($::isInstall) {
	call_dmraid('-ay');
    }
    1;
}

=item call_dmraid($option, @args)

Runs dmraid with $option & @args.
It is overloaded when debugging, see the end of this file.

=cut

sub call_dmraid {
    my ($option, @args) = @_;
    run_program::get_stdout('dmraid', $option, @args);
}

=item check($in)

Ensures dmraid is installed. If yes, calls init().

=cut

sub check {
    my ($in) = @_;

    $in->do_pkgs->ensure_binary_is_installed('dmraid', 'dmraid') or return;
    init();
    1;
}

=item _raid_devices_raw()

Get the real VG names, needed for ddf1, and safer than begins_with for raid10

=cut

sub _raid_devices_raw() {
    log::l("_raid_devices_raw");
    my %vgs;
    my %pv2vg = map {
	chomp();
	log::l("got: $_");
	my %l; @l{qw(name size stride level status subsets devs spares)} = split(':');
	$vgs{$l{name}} = 1 if defined $l{spares};
	if (/freeing device "(.*)", path "(.*)"/ && defined $vgs{$1}) {
	    log::l("$2 => $1");
	    $2 => $1;
        }
    } call_dmraid(qw(-d -s -c -c));

    map {
	chomp;
	log::l("got: $_");
	my %l; @l{qw(pv format vg level status size)} = split(':');
	if (defined $l{size} && defined $l{vg} && defined $pv2vg{$l{pv}} && !defined $vgs{$l{vg}}) {
	    log::l("using $pv2vg{$l{pv}} instead of $l{vg}");
	    $l{vg} = $pv2vg{$l{pv}};
	}
	if_(defined $l{size}, \%l);
    } call_dmraid(qw(-r -c -c));
}

sub _raid_devices() {
    my @l = _raid_devices_raw();
    my %vg2pv; push @{$vg2pv{$_->{vg}}}, delete $_->{pv} foreach @l;
    my %vg2status; push @{$vg2status{$_->{vg}}}, delete $_->{status} foreach @l;
    map {
	delete $_->{size}; #- now irrelevant
	$_->{disks} = $vg2pv{$_->{vg}};
	$_->{status} = (every { $_ eq 'ok' } @{$vg2status{$_->{vg}}}) ? 'ok' : join(' ', @{$vg2status{$_->{vg}}});
	$_;
    } uniq_ { $_->{vg} } @l;
}

sub _sets_raw() {
    map {
	chomp;
	log::l("got: $_");
	my %l; @l{qw(name size stride level status subsets devs spares)} = split(':');
	if_(defined $l{spares}, \%l);
    } call_dmraid('-s', '-c', '-c');
}

sub _sets() {
    my @sets = _sets_raw();
    my @raid_devices = _raid_devices();
    foreach (@sets) {
	my $name = $_->{name};
	my @l = grep { begins_with($name, $_->{vg}) } @raid_devices;
	log::l("ERROR: multiple match for set $name: " . join(' ', map { $_->{vg} } @l)) if @l > 1;

	@l = grep { begins_with($_->{vg}, $name) } @raid_devices if !@l;
	
	if (@l) {
	    foreach my $raid (@l) {
		push @{$_->{disks}}, @{$raid->{disks}};
		add2hash($_, $raid);
		$_->{status} = $raid->{status} if $_->{status} eq 'ok' && $::isInstall;
	    }
	} else {
	    log::l("ERROR: no matching raid devices for set $name");
	}
    }
    @sets;
}

=item vgs()

Returns the list of VGs corresponding to dmraid

=cut

sub vgs() {
    map {
	my $dev = "mapper/$_->{name}";
	my $vg = fs::wild_device::to_subpart("/dev/$dev");
	add2hash($vg, { media_type => 'hd', bus => "dmraid_$_->{format}", disks => $_->{disks} });

	#- device should exist, created by dmraid(8) using libdevmapper
	#- if it doesn't, we suppose it's not in use
	if (-e "/dev/$dev") {
	    $vg; 
	} else {
	    log::l("ignoring $dev as /dev/$dev doesn't exist");
	    ();
	}

    } grep { 
	if ($_->{status} eq 'ok') {
	    1;
	} else {
	    call_dmraid('-an', $_->{vg}) if $::isInstall; #- for things like bad_sil below, deactivating half activated dmraid
	    0;
	}
    } _sets();
}

=item migrate_device_names ($vg)

Handles migration from /dev/mapper/xxx1 to /dev/mapper/xxxp1, as used by initrd/nash.
dmraid has been patched to follow xxxp1 device names.
So until the box has rebooted on new initrd/dmraid, we must cope with /dev/mapper/xxx1 device names
(cf mdk#44182)

=cut

sub migrate_device_names {
    my ($vg) = @_;

    my $dev_name = basename($vg->{device});
    foreach (all('/dev/mapper')) {
	my ($nb) = /^\Q$dev_name\E(\d+)$/ or next;
	my $new = $dev_name . 'p' . $nb;
	if (! -e "/dev/mapper/$new") {
	    log::l("migrating to $new, creating a compat symlink $_");
	    rename "/dev/mapper/$_", "/dev/mapper/$new";
	    symlink $new, "/dev/mapper/$_";
	}
    }
}

=back

=head1 Debugging

If $ENV{DRAKX_DEBUG_DMRAID} is set, debugging dmraid is done.
The C<call_dmraid()> function is overloaded and will spit out warnings.
=cut

if ($ENV{DRAKX_DEBUG_DMRAID}) {
    eval(<<'EOF');
    my %debug_data = (
    
     isw => {
    
      # dmraid -s ####################
      # *** Group superset isw_ffafgbdhi
      # --> Active Subset
      # name   : isw_ffafgbdhi_toto
      # size   : 234441216
      # stride : 256
      # type   : mirror
      # status : ok
      # subsets: 0
      # devs   : 2
      # spares : 0
    
      '-s' => "isw_ffafgbdhi_toto:234441216:256:mirror:ok:0:2:0\n",
    
      # dmraid -r ####################
      #/dev/sda: isw, "isw_ffafgbdhi", GROUP, ok, 488397166 sectors, data@ 0
      #/dev/sdb: isw, "isw_ffafgbdhi", GROUP, ok, 234441646 sectors, data@ 0
    
      '-r' => "/dev/sda:isw:isw_ffafgbdhi:GROUP:ok:488397166:0\n" .
              "/dev/sdb:isw:isw_ffafgbdhi:GROUP:ok:234441646:0\n",
     },
    
     pdc => {
      # dmraid -s ####################
      # *** Active Set
      # name   : pdc_bcefbiigfg
      # size   : 80043200
      # stride : 128
      # type   : mirror
      # status : ok
      # subsets: 0
      # devs   : 2
      # spares : 0
    
      '-s' => "pdc_bcefbiigfg:80043200:128:mirror:ok:0:2:0\n",
    
      # dmraid -r ####################
      # /dev/sda: pdc, "pdc_bcefbiigfg", mirror, ok, 80043200 sectors, data@ 0
      # /dev/sdb: pdc, "pdc_bcefbiigfg", mirror, ok, 80043200 sectors, data@ 0
    
      '-r' => "/dev/sda:pdc:pdc_bcefbiigfg:mirror:ok:80043200:0\n" .
                "/dev/sdb:pdc:pdc_bcefbiigfg:mirror:ok:80043200:0\n",
     },
    
     bad_sil => {
      '-s' => "sil_aeacdidecbcb:234439600:0:mirror:ok:0:1:0\n",
                # ERROR: sil: only 3/4 metadata areas found on /dev/sdb, electing...

      '-r' => "/dev/sdb:sil:sil_aeacdidecbcb:mirror:broken:234439600:0\n",
                # ERROR: sil: only 3/4 metadata areas found on /dev/sdb, electing...
     },

     weird_nvidia =>  {
      '-s' => <<'EO',
/dev/sda: "sil" and "nvidia" formats discovered (using nvidia)!
/dev/sdb: "sil" and "nvidia" formats discovered (using nvidia)!
nvidia_bcjdbjfa:586114702:128:mirror:ok:0:2:0
EO
       '-r' => <<'EO',
/dev/sda: "sil" and "nvidia" formats discovered (using nvidia)!
/dev/sdb: "sil" and "nvidia" formats discovered (using nvidia)!
/dev/sda:nvidia:nvidia_bcjdbjfa:mirror:ok:586114702:0
/dev/sdb:nvidia:nvidia_bcjdbjfa:mirror:ok:586114702:0
EO
	 # ERROR: multiple match for set nvidia_bcjdbjfa:  nvidia_bcjdbjfa
     },

     nvidia_with_subsets => {
      '-s' => <<'EO',
nvidia_bfcciffh:625163520:128:raid10:ok:2:4:0
EO
       '-r' => <<'EO',
/dev/sda:nvidia:nvidia_bfcciffh-0:stripe:ok:312581806:0
/dev/sdb:nvidia:nvidia_bfcciffh-0:stripe:ok:312581806:0
/dev/sdc:nvidia:nvidia_bfcciffh-1:stripe:ok:312581806:0
/dev/sdd:nvidia:nvidia_bfcciffh-1:stripe:ok:312581806:0
EO
	 # ERROR: multiple match for set nvidia_bcjdbjfa:  nvidia_bcjdbjfa
     },
    );
    
    *call_dmraid = sub {
        my ($option, @args) = @_;
        if (my $s = $debug_data{$ENV{DRAKX_DEBUG_DMRAID}}{$option}) {
            split("\n", $s);
	} else {
            warn "dmraid $option @args\n";
        }
    };
EOF
    $@ and die;
}

1;