mirror of
https://github.com/webmin/webmin.git
synced 2026-02-03 06:03:28 +00:00
487 lines
12 KiB
Perl
487 lines
12 KiB
Perl
# iscsi-tgtd-lib.pl
|
|
# Common functions for managing and configuring the iSCSI TGTD server
|
|
|
|
BEGIN { push(@INC, ".."); };
|
|
use strict;
|
|
use warnings;
|
|
no warnings 'redefine';
|
|
no warnings 'uninitialized';
|
|
use WebminCore;
|
|
&init_config();
|
|
&foreign_require("raid");
|
|
&foreign_require("fdisk");
|
|
&foreign_require("lvm");
|
|
&foreign_require("mount");
|
|
our (%text, %config, %gconfig, $module_config_file);
|
|
our ($list_disks_partitions_cache, $get_raidtab_cache,
|
|
$list_logical_volumes_cache, $get_tgtd_config_cache);
|
|
|
|
# check_config()
|
|
# Returns undef if the iSCSI server is installed, or an error message if
|
|
# missing
|
|
sub check_config
|
|
{
|
|
return $text{'check_econfigset'} if (!$config{'config_file'});
|
|
return &text('check_econfig', "<tt>$config{'config_file'}</tt>")
|
|
if (!-r $config{'config_file'});
|
|
return &text('check_etgtadm', "<tt>$config{'tgtadm'}</tt>")
|
|
if (!&has_command($config{'tgtadm'}));
|
|
#&foreign_require("init");
|
|
#return &text('check_einit', "<tt>$config{'init_name'}</tt>")
|
|
# if (&init::action_status($config{'init_name'}) == 0);
|
|
return undef;
|
|
}
|
|
|
|
# get_tgtd_config()
|
|
# Parses the iSCSI server config file in an array ref of objects
|
|
sub get_tgtd_config
|
|
{
|
|
if (!$get_tgtd_config_cache) {
|
|
$get_tgtd_config_cache = &read_tgtd_config_file($config{'config_file'});
|
|
}
|
|
return $get_tgtd_config_cache;
|
|
}
|
|
|
|
# read_tgtd_config_file(file)
|
|
# Parses a single config file into an array ref
|
|
sub read_tgtd_config_file
|
|
{
|
|
my ($file) = @_;
|
|
my @rv;
|
|
my $lnum = 0;
|
|
my $parent;
|
|
my $lref = &read_file_lines($file, 1);
|
|
my @pstack;
|
|
foreach my $ol (@$lref) {
|
|
my $l = $ol;
|
|
$l =~ s/#.*$//;
|
|
if ($l =~ /^\s*include\s(\S+)/) {
|
|
# Include some other files
|
|
my $ifile = $1;
|
|
foreach my $iglob (glob($ifile)) {
|
|
next if (!-r $iglob);
|
|
my $inc = &read_tgtd_config_file($iglob);
|
|
push(@rv, @$inc);
|
|
}
|
|
}
|
|
elsif ($l =~ /^\s*<(\S+)\s+(.*)>/) {
|
|
# Start of a block
|
|
my $dir = { 'name' => $1,
|
|
'value' => $2,
|
|
'values' => [ split(/\s+/, $2) ],
|
|
'type' => 1,
|
|
'members' => [ ],
|
|
'file' => $file,
|
|
'line' => $lnum,
|
|
'eline' => $lnum };
|
|
if ($parent) {
|
|
push(@{$parent->{'members'}}, $dir);
|
|
}
|
|
else {
|
|
push(@rv, $dir);
|
|
}
|
|
push(@pstack, $parent);
|
|
$parent = $dir;
|
|
}
|
|
elsif ($l =~ /^\s*<\/(\S+)>/) {
|
|
# End of a block
|
|
$parent->{'eline'} = $lnum;
|
|
$parent = pop(@pstack);
|
|
}
|
|
elsif ($l =~ /^\s*(\S+)\s+(\S.*)/) {
|
|
# Some directive in a block
|
|
my $dir = { 'name' => $1,
|
|
'value' => $2,
|
|
'values' => [ split(/\s+/, $2) ],
|
|
'type' => 0,
|
|
'file' => $file,
|
|
'line' => $lnum,
|
|
'eline' => $lnum };
|
|
if ($parent) {
|
|
push(@{$parent->{'members'}}, $dir);
|
|
}
|
|
else {
|
|
push(@rv, $dir);
|
|
}
|
|
}
|
|
$lnum++;
|
|
}
|
|
return \@rv;
|
|
}
|
|
|
|
# save_directive(&config, [&old|old-name], [&new], [&parent], [add-file-file])
|
|
# Replaces, creates or deletes some directive
|
|
sub save_directive
|
|
{
|
|
my ($conf, $olddir, $newdir, $parent, $addfile) = @_;
|
|
my $file;
|
|
if ($olddir && !ref($olddir)) {
|
|
# Lookup the old directive by name
|
|
$olddir = &find($parent ? $parent->{'members'} : $conf, $olddir);
|
|
}
|
|
if ($olddir) {
|
|
# Modifying old directive's file
|
|
$file = $olddir->{'file'};
|
|
}
|
|
elsif ($addfile) {
|
|
# Adding to a specific file
|
|
$file = $addfile;
|
|
}
|
|
elsif ($parent) {
|
|
# Adding to parent's file
|
|
$file = $parent->{'file'};
|
|
}
|
|
else {
|
|
# Adding to the default config file
|
|
$file = $config{'config_file'};
|
|
}
|
|
my $lref = $file ? &read_file_lines($file) : undef;
|
|
my @lines = $newdir ? &directive_lines($newdir) : ( );
|
|
my $oldlen = $olddir ? $olddir->{'eline'} - $olddir->{'line'} + 1 : undef;
|
|
my $oldidx = $olddir && $parent ? &indexof($olddir, @{$parent->{'members'}}) :
|
|
$olddir ? &indexof($olddir, @$conf) : undef;
|
|
my ($renumline, $renumoffset);
|
|
if ($olddir && $newdir) {
|
|
# Replace some directive
|
|
if ($lref) {
|
|
splice(@$lref, $olddir->{'line'}, $oldlen, @lines);
|
|
$newdir->{'file'} = $olddir->{'file'};
|
|
$newdir->{'line'} = $olddir->{'line'};
|
|
$newdir->{'eline'} = $newdir->{'line'} + scalar(@lines) - 1;
|
|
if ($parent) {
|
|
$parent->{'eline'} += scalar(@lines) - $oldlen;
|
|
}
|
|
}
|
|
if ($parent) {
|
|
$parent->{'members'}->[$oldidx] = $newdir;
|
|
}
|
|
else {
|
|
$conf->[$oldidx] = $newdir;
|
|
}
|
|
$renumline = $newdir->{'eline'};
|
|
$renumoffset = scalar(@lines) - $oldlen;
|
|
}
|
|
elsif ($olddir) {
|
|
# Remove some directive
|
|
if ($lref) {
|
|
splice(@$lref, $olddir->{'line'}, $oldlen);
|
|
}
|
|
if ($parent) {
|
|
# From inside parent
|
|
splice(@{$parent->{'members'}}, $oldidx, 1);
|
|
if ($lref) {
|
|
$parent->{'eline'} -= $oldlen;
|
|
}
|
|
}
|
|
else {
|
|
# From top-level
|
|
splice(@$conf, $oldidx, 1);
|
|
}
|
|
$renumline = $olddir->{'line'};
|
|
$renumoffset = $oldlen;
|
|
}
|
|
elsif ($newdir) {
|
|
# Add some directive
|
|
if ($lref) {
|
|
$newdir->{'file'} = $file;
|
|
}
|
|
if ($parent) {
|
|
# Inside parent
|
|
if ($lref) {
|
|
$newdir->{'line'} = $parent->{'eline'};
|
|
$newdir->{'eline'} = $newdir->{'line'} +
|
|
scalar(@lines) - 1;
|
|
$parent->{'eline'} += scalar(@lines);
|
|
splice(@$lref, $newdir->{'line'}, 0, @lines);
|
|
}
|
|
$parent->{'members'} ||= [ ];
|
|
$parent->{'type'} ||= 1;
|
|
push(@{$parent->{'members'}}, $newdir);
|
|
}
|
|
else {
|
|
# At end of file
|
|
if ($lref) {
|
|
$newdir->{'line'} = scalar(@lines);
|
|
$newdir->{'eline'} = $newdir->{'line'} +
|
|
scalar(@lines) - 1;
|
|
push(@$lref, @lines);
|
|
}
|
|
push(@$conf, $newdir);
|
|
}
|
|
$renumline = $newdir->{'eline'};
|
|
$renumoffset = scalar(@lines);
|
|
}
|
|
|
|
# Apply any renumbering to the config (recursively)
|
|
if ($renumoffset && $lref) {
|
|
&recursive_renumber($conf, $file, $renumline, $renumoffset,
|
|
[ $newdir, $parent ? ( $parent ) : ( ) ]);
|
|
}
|
|
}
|
|
|
|
# save_multiple_directives(&config, name, &directives, &parent)
|
|
# Update all existing directives with some name
|
|
sub save_multiple_directives
|
|
{
|
|
my ($conf, $name, $newdirs, $parent) = @_;
|
|
my $olddirs = [ &find($parent ? $parent->{'members'} : $conf, $name) ];
|
|
for(my $i=0; $i<@$olddirs || $i<@$newdirs; $i++) {
|
|
&save_directive($conf,
|
|
$i<@$olddirs ? $olddirs->[$i] : undef,
|
|
$i<@$newdirs ? $newdirs->[$i] : undef,
|
|
$parent);
|
|
}
|
|
}
|
|
|
|
# delete_if_empty(file)
|
|
# Remove some file if after modification it contains no non-whitespace lines
|
|
sub delete_if_empty
|
|
{
|
|
my ($file) = @_;
|
|
my $lref = &read_file_lines($file, 1);
|
|
foreach my $l (@$lref) {
|
|
return 0 if ($l =~ /\S/);
|
|
}
|
|
&unlink_file($file);
|
|
&unflush_file_lines($file);
|
|
return 1;
|
|
}
|
|
|
|
# recursive_renumber(&directives, file, after-line, offset, &ignore-list)
|
|
sub recursive_renumber
|
|
{
|
|
my ($conf, $file, $renumline, $renumoffset, $ignore) = @_;
|
|
foreach my $c (@$conf) {
|
|
if ($c->{'file'} eq $file && &indexof($c, @$ignore) < 0) {
|
|
$c->{'line'} += $renumoffset if ($c->{'line'} > $renumline);
|
|
$c->{'eline'} += $renumoffset if ($c->{'eline'} > $renumline);
|
|
}
|
|
if ($c->{'type'}) {
|
|
&recursive_renumber($c->{'members'}, $file, $renumline,
|
|
$renumoffset, $ignore);
|
|
}
|
|
}
|
|
}
|
|
|
|
# directive_lines(&dir, [indent])
|
|
# Returns the lines of text for some directive
|
|
sub directive_lines
|
|
{
|
|
my ($dir, $indent) = @_;
|
|
$indent ||= 0;
|
|
my $istr = " " x $indent;
|
|
my @rv;
|
|
if ($dir->{'type'}) {
|
|
# Has sub-directives
|
|
push(@rv, $istr."<".$dir->{'name'}.
|
|
($dir->{'value'} ? " ".$dir->{'value'} : "").">");
|
|
foreach my $s (@{$dir->{'members'}}) {
|
|
push(@rv, &directive_lines($s, $indent+1));
|
|
}
|
|
push(@rv, $istr."</".$dir->{'name'}.">");
|
|
}
|
|
else {
|
|
# Just a name/value
|
|
push(@rv, $istr.$dir->{'name'}.
|
|
($dir->{'value'} ? " ".$dir->{'value'} : ""));
|
|
}
|
|
return @rv;
|
|
}
|
|
|
|
# find(&config|&object, name)
|
|
# Returns all config objects with the given name
|
|
sub find
|
|
{
|
|
my ($conf, $name) = @_;
|
|
$conf = $conf->{'members'} if (ref($conf) eq 'HASH');
|
|
my @rv = grep { lc($_->{'name'}) eq lc($name) } @$conf;
|
|
return wantarray ? @rv : $rv[0];
|
|
}
|
|
|
|
# find_value(&config|&object, name)
|
|
# Returns config values with the given name
|
|
sub find_value
|
|
{
|
|
my ($conf, $name) = @_;
|
|
$conf = $conf->{'members'} if (ref($conf) eq 'HASH');
|
|
my @rv = map { $_->{'value'} } &find($conf, $name);
|
|
return wantarray ? @rv : $rv[0];
|
|
}
|
|
|
|
# is_tgtd_running()
|
|
# Returns the PID if the server process is running, or 0 if not
|
|
sub is_tgtd_running
|
|
{
|
|
my $pid = &find_byname("tgtd");
|
|
return $pid;
|
|
}
|
|
|
|
# setup_tgtd_init()
|
|
# If no init script exists, create one
|
|
sub setup_tgtd_init
|
|
{
|
|
&foreign_require("init");
|
|
return 0 if (&init::action_status($config{'init_name'}));
|
|
&init::enable_at_boot($config{'init_name'},
|
|
"Start TGTd iSCSI server",
|
|
&has_command($config{'tgtd'}).
|
|
" && sleep 2 && ".
|
|
&has_command($config{'tgtadmin'})." -e",
|
|
"killall -9 tgtd",
|
|
undef,
|
|
{ 'fork' => 1 },
|
|
);
|
|
}
|
|
|
|
# start_iscsi_tgtd()
|
|
# Run the init script to start the server
|
|
sub start_iscsi_tgtd
|
|
{
|
|
if ($config{'start_cmd'}) {
|
|
my $out = &backquote_command("$config{'start_cmd'} 2>&1 </dev/null");
|
|
return $? ? $out : undef;
|
|
}
|
|
else {
|
|
&setup_tgtd_init();
|
|
&foreign_require("init");
|
|
my ($ok, $out) = &init::start_action($config{'init_name'});
|
|
return $ok ? undef : $out;
|
|
}
|
|
}
|
|
|
|
# stop_iscsi_tgtd()
|
|
# Run the init script to stop the server
|
|
sub stop_iscsi_tgtd
|
|
{
|
|
if ($config{'stop_cmd'}) {
|
|
my $out = &backquote_command("$config{'stop_cmd'} 2>&1 </dev/null");
|
|
return $? ? $out : undef;
|
|
}
|
|
else {
|
|
&setup_tgtd_init();
|
|
&foreign_require("init");
|
|
my ($ok, $out) = &init::stop_action($config{'init_name'});
|
|
return $ok ? undef : $out;
|
|
}
|
|
}
|
|
|
|
# restart_iscsi_tgtd()
|
|
# Sends a HUP signal to re-read the configuration
|
|
sub restart_iscsi_tgtd
|
|
{
|
|
if ($config{'restart_cmd'}) {
|
|
my $out = &backquote_command("$config{'restart_cmd'} 2>&1 </dev/null");
|
|
return $? ? $out : undef;
|
|
}
|
|
else {
|
|
&stop_iscsi_tgtd();
|
|
# Wait for process to exit
|
|
for(my $i=0; $i<20; $i++) {
|
|
last if (!&is_tgtd_running());
|
|
sleep(1);
|
|
}
|
|
return &start_iscsi_tgtd();
|
|
}
|
|
}
|
|
|
|
# get_device_size(device, "part"|"raid"|"lvm"|"other")
|
|
# Returns the size in bytes of some device, which can be a partition, RAID
|
|
# device, logical volume or regular file.
|
|
sub get_device_size
|
|
{
|
|
my ($dev, $type) = @_;
|
|
if (!$type) {
|
|
$type = $dev =~ /^\/dev\/md\d+$/ ? "raid" :
|
|
$dev =~ /^\/dev\/([^\/]+)\/([^\/]+)$/ ? "lvm" :
|
|
$dev =~ /^\/dev\/(s|h|v|xv)d[a-z]+\d*$/ ? "part" : "other";
|
|
}
|
|
if ($type eq "part") {
|
|
# A partition or whole disk
|
|
foreach my $d (&list_disks_partitions_cached()) {
|
|
if ($d->{'device'} eq $dev) {
|
|
# Whole disk
|
|
return $d->{'cylinders'} * $d->{'cylsize'};
|
|
}
|
|
foreach my $p (@{$d->{'parts'}}) {
|
|
if ($p->{'device'} eq $dev) {
|
|
return ($p->{'end'} - $p->{'start'} + 1) *
|
|
$d->{'cylsize'};
|
|
}
|
|
}
|
|
}
|
|
return undef;
|
|
}
|
|
elsif ($type eq "raid") {
|
|
# A RAID device
|
|
my $conf = &get_raidtab_cached();
|
|
foreach my $c (@$conf) {
|
|
if ($c->{'value'} eq $dev) {
|
|
return $c->{'size'} * 1024;
|
|
}
|
|
}
|
|
return undef;
|
|
}
|
|
elsif ($type eq "lvm") {
|
|
# LVM volume group
|
|
foreach my $l (&list_logical_volumes_cached()) {
|
|
if ($l->{'device'} eq $dev) {
|
|
return $l->{'size'} * 1024;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
# A regular file
|
|
my @st = stat($dev);
|
|
return @st ? $st[7] : undef;
|
|
}
|
|
}
|
|
|
|
sub list_disks_partitions_cached
|
|
{
|
|
$list_disks_partitions_cache ||= [ &fdisk::list_disks_partitions() ];
|
|
return @$list_disks_partitions_cache;
|
|
}
|
|
|
|
sub get_raidtab_cached
|
|
{
|
|
$get_raidtab_cache ||= &raid::get_raidtab();
|
|
return $get_raidtab_cache;
|
|
}
|
|
|
|
sub list_logical_volumes_cached
|
|
{
|
|
if (!$list_logical_volumes_cache) {
|
|
$list_logical_volumes_cache = [ ];
|
|
foreach my $v (&lvm::list_volume_groups()) {
|
|
push(@$list_logical_volumes_cache,
|
|
&lvm::list_logical_volumes($v->{'name'}));
|
|
}
|
|
}
|
|
return @$list_logical_volumes_cache;
|
|
}
|
|
|
|
# find_host_name(&config)
|
|
# Returns the first host name part of the first target
|
|
sub find_host_name
|
|
{
|
|
my ($conf) = @_;
|
|
my %hcount;
|
|
foreach my $t (&find_value($conf, "target")) {
|
|
my ($host) = split(/:/, $t);
|
|
$hcount{$host}++;
|
|
}
|
|
my @hosts = sort { $hcount{$b} <=> $hcount{$a} } (keys %hcount);
|
|
return $hosts[0];
|
|
}
|
|
|
|
# generate_host_name()
|
|
# Returns the first part of a target name, in the standard format
|
|
sub generate_host_name
|
|
{
|
|
my @tm = localtime(time());
|
|
return sprintf("iqn.%.4d-%.2d.%s", $tm[5]+1900, $tm[4]+1,
|
|
join(".", reverse(split(/\./, &get_system_hostname()))));
|
|
}
|
|
|
|
1;
|