mirror of
https://github.com/webmin/webmin.git
synced 2025-07-20 16:48:46 +00:00
415 lines
10 KiB
Perl
415 lines
10 KiB
Perl
# Functions for editing the Open-iSCSI configuration file
|
|
|
|
BEGIN { push(@INC, ".."); };
|
|
use strict;
|
|
use warnings;
|
|
no warnings 'redefine';
|
|
no warnings 'uninitialized';
|
|
use WebminCore;
|
|
&init_config();
|
|
&foreign_require("fdisk");
|
|
&foreign_require("mount");
|
|
&foreign_require("lvm");
|
|
our (%text, %config, %gconfig, $module_config_file);
|
|
|
|
# check_config()
|
|
# Returns undef if the Open-iSCSI client is installed, or an error message if
|
|
# missing
|
|
sub check_config
|
|
{
|
|
return &text('check_econfig', "<tt>$config{'config_file'}</tt>")
|
|
if (!-r $config{'config_file'});
|
|
return &text('check_eisciadm', "<tt>$config{'iscsiadm'}</tt>")
|
|
if (!&has_command($config{'iscsiadm'}));
|
|
return undef;
|
|
}
|
|
|
|
# get_iscsi_config()
|
|
# Parses the iscsi client config file into an array ref of directives
|
|
sub get_iscsi_config
|
|
{
|
|
my @rv;
|
|
my $fh = "CONFIG";
|
|
my $lnum = 0;
|
|
&open_readfile($fh, $config{'config_file'}) || return [ ];
|
|
while(<$fh>) {
|
|
s/\r|\n//g;
|
|
s/#.*$//;
|
|
if (/^(\S+)\s*=\s*(.*)/) {
|
|
my $dir = { 'name' => $1,
|
|
'value' => $2,
|
|
'line' => $lnum };
|
|
push(@rv, $dir);
|
|
}
|
|
$lnum++;
|
|
}
|
|
close($fh);
|
|
return \@rv;
|
|
}
|
|
|
|
# find(&config, name)
|
|
# Returns all config objects with the given name
|
|
sub find
|
|
{
|
|
my ($conf, $name) = @_;
|
|
my @t = grep { $_->{'name'} eq $name } @$conf;
|
|
return wantarray ? @t : $t[0];
|
|
}
|
|
|
|
# find_value(&config, name)
|
|
# Returns all config values with the given name
|
|
sub find_value
|
|
{
|
|
my ($conf, $name) = @_;
|
|
my @rv = map { $_->{'value'} } &find($conf, $name);
|
|
return wantarray ? @rv : $rv[0];
|
|
}
|
|
|
|
# save_directive(&config, name, value)
|
|
# Creates, updates or deletes some directive
|
|
sub save_directive
|
|
{
|
|
my ($conf, $name, $value) = @_;
|
|
my $lref = &read_file_lines($config{'config_file'});
|
|
my $line = defined($value) ? $name." = ".$value : undef;
|
|
my $o = &find($conf, $name);
|
|
if ($o && defined($value)) {
|
|
# Update a line
|
|
$lref->[$o->{'line'}] = $line;
|
|
$o->{'value'} = $value;
|
|
}
|
|
elsif ($o && !defined($value)) {
|
|
# Commenting out a line
|
|
$lref->[$o->{'line'}] = "# ".$lref->[$o->{'line'}];
|
|
}
|
|
elsif (!$o && defined($value)) {
|
|
# Check if exists, but commented out
|
|
my $cline;
|
|
my $clnum = 0;
|
|
foreach my $l (@$lref) {
|
|
if ($l =~ /^#+\s*\Q$name\E\s*=/) {
|
|
$cline = $clnum;
|
|
last;
|
|
}
|
|
$clnum++;
|
|
}
|
|
my $dir = { 'name' => $name,
|
|
'value' => $value };
|
|
if (defined($cline)) {
|
|
# Comment back in
|
|
$dir->{'line'} = $cline;
|
|
$lref->[$cline] = $line;
|
|
}
|
|
else {
|
|
# Add at end
|
|
$dir->{'line'} = scalar(@$lref);
|
|
push(@$lref, $line);
|
|
}
|
|
push(@$conf, $dir);
|
|
}
|
|
}
|
|
|
|
# list_iscsi_connections()
|
|
# Returns a list of hash refs with details of active sessions
|
|
sub list_iscsi_connections
|
|
{
|
|
my @rv;
|
|
&clean_language();
|
|
my $out = &backquote_command(
|
|
"$config{'iscsiadm'} -m session -o show -P 3 -S 2>/dev/null");
|
|
&reset_environment();
|
|
my @lines = split(/\r?\n/, $out);
|
|
if ($?/256 == 21) {
|
|
# Code 21 means no sessions
|
|
return [ ];
|
|
}
|
|
if ($?) {
|
|
return $lines[0];
|
|
}
|
|
my $conn;
|
|
foreach my $l (@lines) {
|
|
if ($l =~ /^Target:\s+(\S+):(\S+)/) {
|
|
$conn = { 'name' => $1,
|
|
'target' => $2 };
|
|
push(@rv, $conn);
|
|
}
|
|
elsif ($l =~ /Current\s+Portal:\s+(\S+):(\d+)/) {
|
|
$conn->{'ip'} = $1;
|
|
$conn->{'port'} = $2;
|
|
}
|
|
elsif ($l =~ /SID:\s+(\d+)/) {
|
|
$conn->{'num'} = $1;
|
|
}
|
|
elsif ($l =~ /Iface\s+Name:\s+(\S+)/) {
|
|
$conn->{'iface'} = $1;
|
|
}
|
|
elsif ($l =~ /Iface\s+Transport:\s+(\S+)/) {
|
|
$conn->{'proto'} = $1;
|
|
}
|
|
elsif ($l =~ /Iface\s+Initiatorname:\s+(\S+)/) {
|
|
$conn->{'initiator'} = $1;
|
|
}
|
|
elsif ($l =~ /iSCSI\s+Connection\s+State:\s+(\S+)/) {
|
|
$conn->{'connection'} = $1;
|
|
}
|
|
elsif ($l =~ /iSCSI\s+Session\s+State:\s+(\S+)/) {
|
|
$conn->{'session'} = $1;
|
|
}
|
|
elsif ($l =~ /scsi(\d+)\s+Channel\s+(\d+)\s+Id\s+(\d+)\s+Lun:\s+(\d+)/) {
|
|
$conn->{'scsihost'} = $1;
|
|
$conn->{'scsichannel'} = $2;
|
|
$conn->{'scsiid'} = $3;
|
|
$conn->{'scsilun'} = $4;
|
|
}
|
|
elsif ($l =~ /Attached\s+scsi\s+disk\s+(\S+)/) {
|
|
$conn->{'device'} = "/dev/$1";
|
|
}
|
|
elsif ($l =~ /(username|password|username_in|password_in):\s+(\S+)/ &&
|
|
$2 ne "<empty>") {
|
|
$conn->{$1} = $2;
|
|
}
|
|
}
|
|
foreach my $c (@rv) {
|
|
my $dev = "/dev/disk/by-path/ip-$c->{'ip'}:$c->{'port'}-".
|
|
"iscsi-$c->{'name'}:$c->{'target'}-lun-$c->{'scsilun'}";
|
|
if (-e $dev) {
|
|
$c->{'longdevice'} = $dev;
|
|
}
|
|
}
|
|
return \@rv;
|
|
}
|
|
|
|
# list_iscsi_targets(host, [port], [iface])
|
|
# Returns an array ref listing available targets on some host, or an error
|
|
# message string
|
|
sub list_iscsi_targets
|
|
{
|
|
my ($host, $port, $iface) = @_;
|
|
my $cmd = "$config{'iscsiadm'} -m discovery -t sendtargets -p ".
|
|
quotemeta($host).($port ? ":".quotemeta($port) : "").
|
|
($iface ? " -I ".quotemeta($iface) : "");
|
|
&clean_language();
|
|
my $out = &backquote_command("$cmd 2>&1");
|
|
&reset_environment();
|
|
my @lines = split(/\r?\n/, $out);
|
|
if ($? || $out =~ /Could not perform SendTargets discovery/i) {
|
|
return $lines[0];
|
|
}
|
|
my @rv;
|
|
foreach my $l (@lines) {
|
|
if ($l =~ /^(\S+):(\d+),([0-9\-]+)\s+(\S+):(\S+)/) {
|
|
push(@rv, { 'ip' => $1,
|
|
'port' => $2,
|
|
'name' => $4,
|
|
'target' => $5 });
|
|
}
|
|
}
|
|
return \@rv;
|
|
}
|
|
|
|
# create_iscsi_connection(host, [port], [iface], &target,
|
|
# [method, username, password])
|
|
# Attempts to connect to an iscsi server for the given target (or all targets)
|
|
sub create_iscsi_connection
|
|
{
|
|
my ($host, $port, $iface, $target, $method, $user, $pass) = @_;
|
|
|
|
# Re-discover targets, so that this function works when called remotely
|
|
&list_iscsi_targets($host, $port, $iface);
|
|
|
|
my $cmd = "$config{'iscsiadm'} -m node".
|
|
($target ? " -T ".quotemeta($target->{'name'}).":".
|
|
quotemeta($target->{'target'}) : "").
|
|
" -p ".quotemeta($host).($port ? ":".quotemeta($port) : "").
|
|
($iface ? " -I ".quotemeta($iface) : "");
|
|
|
|
# Create the session
|
|
&clean_language();
|
|
my $out = &backquote_logged("$cmd 2>&1");
|
|
&reset_environment();
|
|
return $out if ($?);
|
|
|
|
# Set session username and password
|
|
if ($method) {
|
|
&clean_language();
|
|
my $out = &backquote_logged("$cmd --op=update --name=node.session.auth.authmethod --value=$method 2>&1");
|
|
&reset_environment();
|
|
return $out if ($?);
|
|
}
|
|
if ($user) {
|
|
&clean_language();
|
|
my $out = &backquote_logged("$cmd --op=update --name=node.session.auth.username --value=".quotemeta($user)." 2>&1");
|
|
&reset_environment();
|
|
return $out if ($?);
|
|
|
|
&clean_language();
|
|
$out = &backquote_logged("$cmd --op=update --name=node.session.auth.password --value=".quotemeta($pass)." 2>&1");
|
|
&reset_environment();
|
|
return $out if ($?);
|
|
}
|
|
|
|
# Connect the session with --login
|
|
&clean_language();
|
|
$out = &backquote_logged("$cmd --login 2>&1");
|
|
&reset_environment();
|
|
return $out if ($?);
|
|
|
|
return undef;
|
|
}
|
|
|
|
# delete_iscsi_connection(&connection)
|
|
# Remove an existing connection to some target
|
|
sub delete_iscsi_connection
|
|
{
|
|
my ($conn) = @_;
|
|
my $cmd = "$config{'iscsiadm'} -m node".
|
|
" -T ".quotemeta($conn->{'name'}).":".quotemeta($conn->{'target'}).
|
|
" -p ".quotemeta($conn->{'ip'}).":".quotemeta($conn->{'port'}).
|
|
" --logout";
|
|
&clean_language();
|
|
my $out = &backquote_logged("$cmd 2>&1");
|
|
&reset_environment();
|
|
return $? ? $out : undef;
|
|
}
|
|
|
|
# get_connection_users(&conn, [include-unused])
|
|
# Returns a list of partitions in the device for some connection, and their
|
|
# users (like raid, mount, lvm)
|
|
sub get_connection_users
|
|
{
|
|
my ($conn, $unused) = @_;
|
|
return ( ) if (!$conn->{'device'});
|
|
my @users;
|
|
my @disks = &fdisk::list_disks_partitions();
|
|
my ($disk) = grep { $_->{'device'} eq $conn->{'device'} } @disks;
|
|
next if (!$disk);
|
|
foreach my $part (@{$disk->{'parts'}}) {
|
|
my @st = &fdisk::device_status($part->{'device'});
|
|
if (@st || $unused) {
|
|
push(@users, [ $conn, $part, @st ]);
|
|
}
|
|
}
|
|
return @users;
|
|
}
|
|
|
|
# get_initiator_name()
|
|
# Returns the local iSCSI initiator name
|
|
sub get_initiator_name
|
|
{
|
|
my $data = &read_file_contents($config{'initiator_file'});
|
|
return $data =~ /InitiatorName=(\S+)/ ? $1 : undef;
|
|
}
|
|
|
|
# save_initiator_name(name)
|
|
# Writes out the initiator name file
|
|
sub save_initiator_name
|
|
{
|
|
my ($name) = @_;
|
|
my $fh = "INIT";
|
|
&open_tempfile($fh, ">$config{'initiator_file'}");
|
|
&print_tempfile($fh, "InitiatorName=$name\n");
|
|
&close_tempfile($fh);
|
|
}
|
|
|
|
# generate_initiator_name()
|
|
# Create a new initiator name with the iscsi-iname command
|
|
sub generate_initiator_name
|
|
{
|
|
my $out = &backquote_command("$config{'iscsiiname'} 2>/dev/null");
|
|
$out =~ s/\r?\n//;
|
|
return $out;
|
|
}
|
|
|
|
# list_iscsi_ifaces()
|
|
# Returns an array ref of details of all existing interfaces
|
|
sub list_iscsi_ifaces
|
|
{
|
|
&clean_language();
|
|
my $errtemp = &transname();
|
|
my $out = &backquote_command(
|
|
"$config{'iscsiadm'} -m iface -o show -P 1 2>$errtemp");
|
|
&reset_environment();
|
|
my @lines = split(/\r?\n/, $out);
|
|
my $err = &read_file_contents($errtemp);
|
|
if ($?) {
|
|
return "Interfaces are not supported by this OpenISCSI version"
|
|
if ($err =~ /Invalid\s+info\s+level/);
|
|
return $err || $lines[0];
|
|
}
|
|
my @rv;
|
|
my ($iface, $target);
|
|
foreach my $l (@lines) {
|
|
if ($l =~ /^Iface:\s+(\S+)/) {
|
|
$iface = { 'name' => $1,
|
|
'builtin' => ($1 eq "default" || $1 eq "iser"),
|
|
'targets' => [ ] };
|
|
push(@rv, $iface);
|
|
}
|
|
elsif ($l =~ /Target:\s+(\S+):(\S+)/) {
|
|
$target = { 'name' => $1,
|
|
'target' => $2 };
|
|
push(@{$iface->{'targets'}}, $target);
|
|
}
|
|
elsif ($l =~ /Portal:\s+(\S+):(\d+)/) {
|
|
$target->{'ip'} = $1;
|
|
$target->{'port'} = $2;
|
|
}
|
|
}
|
|
# Fetch more info for each interface
|
|
foreach my $iface (@rv) {
|
|
&clean_language();
|
|
my $out = &backquote_command(
|
|
"$config{'iscsiadm'} -m iface -I $iface->{'name'} 2>/dev/null");
|
|
&reset_environment();
|
|
foreach my $il (split(/\r?\n/, $out)) {
|
|
if ($il !~ /^#/ && $il =~ /^(iface.\S+)\s*=\s*(\S+)/) {
|
|
$iface->{$1} = $2 eq "<empty>" ? undef : $2;
|
|
}
|
|
}
|
|
}
|
|
return \@rv;
|
|
}
|
|
|
|
# create_iscsi_interface(&iface)
|
|
# Create a new interface from the given hash
|
|
sub create_iscsi_interface
|
|
{
|
|
my ($iface) = @_;
|
|
|
|
# Create the initial interface
|
|
my $cmd = "$config{'iscsiadm'} -m iface -o new".
|
|
" -I ".quotemeta($iface->{'name'});
|
|
&clean_language();
|
|
my $out = &backquote_logged("$cmd 2>&1");
|
|
&reset_environment();
|
|
return $out if ($?);
|
|
|
|
# Apply various params
|
|
foreach my $k (grep { /^iface\./ } keys %$iface) {
|
|
my $cmd = "$config{'iscsiadm'} -m iface -o update".
|
|
" -I ".quotemeta($iface->{'name'}).
|
|
" -n ".quotemeta($k)." -v ".quotemeta($iface->{$k});
|
|
&clean_language();
|
|
my $out = &backquote_logged("$cmd 2>&1");
|
|
&reset_environment();
|
|
return "Failed to set $k : $out" if ($?);
|
|
}
|
|
|
|
return undef;
|
|
}
|
|
|
|
# delete_iscsi_iface(&iface)
|
|
# Delete one iSCSI interface
|
|
sub delete_iscsi_iface
|
|
{
|
|
my ($iface) = @_;
|
|
my $cmd = "$config{'iscsiadm'} -m iface -o delete".
|
|
" -I ".quotemeta($iface->{'name'});
|
|
&clean_language();
|
|
my $out = &backquote_logged("$cmd 2>&1");
|
|
&reset_environment();
|
|
return $? ? $out : undef;
|
|
}
|
|
|
|
1;
|