diff --git a/README b/README index dd99e74..22785fc 100644 --- a/README +++ b/README @@ -3,46 +3,53 @@ Introduction Linux target framework (tgt) aims to simplify various SCSI target driver (iSCSI, Fibre Channel, SRP, etc) creation and maintenance. -Tgt consists of kernel modules, user-space daemon, and user-space -tools. Some target drivers uses all of them and some use only -user-space daemon and tools (i.e. they completely runs in user space). +Currently, tgt supports the following target drivers: -Currently, tgt supports three target drivers: +- iSCSI software target driver for Ethernet NICs -- IBM VIO server (ibmvstgt) -- iSCSI -- Xen vscsifront/back +- iSER software target driver for Infiniband and RDMA NICs -Note that tgt is under active development. Don't play with important -data. +- IBM System p VIO server -The code is under the GNU General Public License version 2. +- FCoE software target driver for Ethernet NICs (in progress) +- Qlogic qla2xxx FC target driver (in progress) -Preparation -------------- -The iSCSI target driver can works with the 2.6.X kernels. It requires -OpenSSL library (libssl-dev for debian, openssl-devel for Fedora). +Tgt consists of kernel modules, user-space daemon, and user-space +tools. iSCSI, iSER, and FCoE target drivers use only user-space daemon +and tools (i.e. they are just user-space applications. They don't need +any kernel support). + +tgt can emulate the following device types: -host:~/tgt/usr$ make ISCSI=1 +- SBC: a virtual disk drive that can use a file to store the content. -If you want IBM VIO target driver, get kernel version 2.6.20, rebuild -the kernel, and reboot with the new kernel. Note you need to enable -SCSI_TGT, SCSI_SRP, and SCSI_IBMVSCSIS kernel options. +- SMC: a virtual media jukebox that can be controlled by the "mtx" +tool (partially functional). -host:~/tgt/usr$ make KERNELSRC= IBMVIO=1 +- MMC: a virtual DVD drive that can read DVD-ROM iso files and create +burnable DVD+R. It can be combined with SMC to provide a fully +operational DVD jukebox. -Make sure that everything is built successfully. +- SSC: a virtual tape device (aka VTL) that can use a file to store +the content (in progress). -Now you can run tgt. Target drivers have their own ways for -configuration. So find an appropriate documentation in the doc -directory. +- OSD: a virtual object-based storage device that can use a file to +store the content (in progress). + +The code is under the GNU General Public License version 2. + + +Preparation +------------- +Target drivers have their own ways to build, configure, etc. So find +an appropriate documentation in the doc directory. Developer Notes ------------- The central resource for tgt development is the mailing list -(stgt-devel@lists.berlios.de). +(stgt@vger.kernel.org). First, please read the following documents (in short, follow Linux kernel development rules): diff --git a/doc/README.ibmvstgt b/doc/README.ibmvstgt index 9a9dbd8..d77f932 100644 --- a/doc/README.ibmvstgt +++ b/doc/README.ibmvstgt @@ -1,5 +1,13 @@ Starting ------------- +If you want IBM VIO target driver, get kernel version 2.6.20, rebuild +the kernel, and reboot with the new kernel. Note you need to enable +SCSI_TGT, SCSI_SRP, and SCSI_IBMVSCSIS kernel options. + +host:~/tgt/usr$ make KERNELSRC= IBMVIO=1 + +Make sure that everything is built successfully. + Try the following commands: host:~/tgt$ su diff --git a/doc/README.iscsi b/doc/README.iscsi index 5aec190..e6b4d09 100644 --- a/doc/README.iscsi +++ b/doc/README.iscsi @@ -5,6 +5,13 @@ This show a simple example to set up some targets. Starting the daemon ------------- +The iSCSI target driver works with the 2.6.X kernels. It requires +OpenSSL library (libssl-dev for debian, openssl-devel for Fedora). + +First, you need to compile the source code: + +host:~/tgt/usr$ make ISCSI=1 + Try the following commands: host:~/tgt$ su diff --git a/scripts/tgt-admin b/scripts/tgt-admin index fe95723..22c4725 100755 --- a/scripts/tgt-admin +++ b/scripts/tgt-admin @@ -30,6 +30,8 @@ This tool configures tgt targets. (see "--offline help" for more info) --ready put all or selected targets in ready state (see "--ready help" for more info) + --update update configuration for all or selected targets + (see "--update help" for more info) -s, --show show all the targets -c, --conf specify an alternative configuration file --ignore-errors continue even if tgtadm exits with non-zero code @@ -49,6 +51,7 @@ my $execute = 0; my $delete = 0; my $offline = 0; my $ready = 0; +my $update = 0; my $show = 0; my $alternate_conf="0"; my $ignore_errors = 0; @@ -62,6 +65,7 @@ my $result = GetOptions ( "delete=s" => \$delete, "offline=s" => \$offline, "ready=s" => \$ready, + "update=s" => \$update, "s|show" => \$show, "c|conf=s" => \$alternate_conf, "ignore-errors" => \$ignore_errors, @@ -147,7 +151,10 @@ my $option; my $value; sub add_targets { - + my $single_target = $_[0]; + my $configured = $_[1]; + my $connected = $_[2]; + my $in_configfile = $_[3]; foreach my $k (sort keys %conf) { if ( $k eq "default-driver" ) { @@ -170,57 +177,85 @@ sub add_targets { foreach my $k (sort keys %conf) { if ( $k eq "target" ) { foreach my $k2 (sort keys %{$conf{$k}}) { - $target = $k2; - my $allowall = 1; - if ( not defined $tgtadm_output{$k2} ) { - # We have to find available tid - $next_tid = $next_tid + 1; + # Do we run update or execute? + if (length $single_target) { + if ($single_target ne $k2) { + next; + } else { + $target = $single_target; + } + } else { + $target = $k2; } - else { - execute("# Target $target already exist!"); - execute("# Updating Target $target"); - execute("tgtadm --op update --mode target --tid=$next_tid -n state -v offline"); - execute("tgtadm --mode target --op delete --tid=$next_tid"); + + my $in_use = 0; + if (length $single_target) { + $in_use = &main_delete($target); } + my $allowall = 1; + if ((not defined $tgtadm_output{$k2}) || + ($update ne 0 && $in_use == 0) || + ($update ne 0 && $in_use == 1 && $pretend == 1 && $force == 1)) + { + # We have to find available tid + if (($in_configfile == 1) && ($configured == 0)) { + my $maxtid = &find_max_tid; + $next_tid = $maxtid + 1; + } elsif (length $single_target) { + $next_tid = $tgtadm_output_tid{$target}; + } else { + $next_tid = $next_tid + 1; + } + # Before we add a target, we need to know its type + my $driver; + foreach my $k3 (sort keys %{$conf{$k}{$k2}}) { + $option = $k3; + $value = $conf{$k}{$k2}{$k3}; + &check_value($value); + if ($option eq "driver") { + if (ref($value) eq "ARRAY") { + print "Multiple driver definitions not allowed!\n"; + print "Check your config file for errors (target: $target).\n"; + exit 1; + } + $driver = $value; + } + } + + if (not defined $driver) { + $driver = $default_driver; + } - # Before we add a target, we need to know its type - my $driver; - foreach my $k3 (sort keys %{$conf{$k}{$k2}}) { - $option = $k3; - $value = $conf{$k}{$k2}{$k3}; - &check($value); - if ( $option eq "driver" ) { - if (ref($value) eq "ARRAY") { - print "Multiple driver definitions not allowed!\n"; - print "Check your config file for errors (target: $target).\n"; - exit 1; + execute("# Adding target: $target"); + execute("tgtadm --lld $driver --op new --mode target --tid $next_tid -T $target"); + foreach my $k3 (sort keys %{$conf{$k}{$k2}}) { + $option = $k3; + $value = $conf{$k}{$k2}{$k3}; + &check_value($value); + &process_options($driver); + # If there was no option called "initiator-address", it means + # we want to allow ALL initiators for this target + if ($option eq "initiator-address") { + $allowall = 0; } - $driver = $value; } - } - if ( not defined $driver ) { - $driver = $default_driver; - } - execute("# Adding target: $target"); - execute("tgtadm --lld $driver --op new --mode target --tid $next_tid -T $target"); - foreach my $k3 (sort keys %{$conf{$k}{$k2}}) { - $option = $k3; - $value = $conf{$k}{$k2}{$k3}; - &check($value); - &process_options($driver); - # If there was no option called "initiator-address", it means - # we want to allow ALL initiators for this target - if ( $option eq "initiator-address" ) { - $allowall = 0; + if ($allowall == 1) { + execute("tgtadm --lld $driver --op bind --mode target --tid $next_tid -I ALL"); } - } - if ( $allowall == 1 ) { - execute("tgtadm --lld $driver --op bind --mode target --tid $next_tid -I ALL"); + } else { + if (not length $configured || $in_use eq 1) { + execute("# Target $target already exists!"); + } } - execute(); } + if (length $single_target && $in_configfile == 0 && $configured == 0) { + print "Target $single_target is $connected currently not configured\n"; + print "and does not exist in the config file - can't continue!\n"; + exit 1; + } + execute(); } } } @@ -262,22 +297,29 @@ sub process_options { my @value_arr = @$value; my $i = 1; foreach my $direct_store (@value_arr) { - $inq=`sg_inq $direct_store`; - if ($inq=~/Vendor identification:\s*(\w+)\s*\n*Product identification:\s*([\w\s\/\-]+)\n\s*\n*Product revision level:\s*(\w*)\s*\n*Unit serial number:\s*(\w+)/) - { - $vendor_id="$1"; - $prod_id="$2"; - $prod_rev="$3"; - $scsi_serial="$4"; - } - $vendor_id =~ s/\s+$//; - $prod_id =~ s/\s+$//; - $prod_rev =~ s/\s+$//; - $scsi_serial =~ s/\s+$//; + # Check if device exists + if ( -e $direct_store) { + $inq=`sg_inq $direct_store`; + if ($inq=~/Vendor identification:\s*(\w+)\s*\n*Product identification:\s*([\w\s\/\-]+)\n\s*\n*Product revision level:\s*(\w*)\s*\n*Unit serial number:\s*(\w+)/) + { + $vendor_id="$1"; + $prod_id="$2"; + $prod_rev="$3"; + $scsi_serial="$4"; + } + $vendor_id =~ s/\s+$//; + $prod_id =~ s/\s+$//; + $prod_rev =~ s/\s+$//; + $scsi_serial =~ s/\s+$//; - execute("tgtadm --lld $driver --op new --mode logicalunit --tid $next_tid --lun 1 -b $direct_store"); - execute("tgtadm --lld $driver --op update --mode logicalunit --tid $next_tid --lun 1 --params vendor_id=\"$vendor_id\",product_id=\"$prod_id\",product_rev=\"$prod_rev\",scsi_sn=\"$scsi_serial\""); - $i += 1; + execute("tgtadm --lld $driver --op new --mode logicalunit --tid $next_tid --lun 1 -b $direct_store"); + execute("tgtadm --lld $driver --op update --mode logicalunit --tid $next_tid --lun 1 --params vendor_id=\"$vendor_id\",product_id=\"$prod_id\",product_rev=\"$prod_rev\",scsi_sn=\"$scsi_serial\""); + $i += 1; + } + else { + print("skipping device $direct_store\n"); + print("$direct_store does not exist - please check the configuration file\n"); + } } } @@ -289,7 +331,7 @@ sub process_options { my @value_arr = @$value; foreach my $incominguser (@value_arr) { my @userpass = split(/ /, $incominguser); - &check($userpass[1]); + &check_value($userpass[1]); execute("tgtadm --lld $driver --mode account --op delete --user=$userpass[0]"); execute("tgtadm --lld $driver --mode account --op new --user=$userpass[0] --password=$userpass[1]"); execute("tgtadm --lld $driver --mode account --op bind --tid=$next_tid --user=$userpass[0]"); @@ -303,7 +345,7 @@ sub process_options { } execute("# Warning: only one outgoinguser is allowed. Will only use the first one."); my @userpass = split(/ /, @$value[0]); - &check($userpass[1]); + &check_value($userpass[1]); execute("tgtadm --lld $driver --mode account --op delete --user=$userpass[0]"); execute("tgtadm --lld $driver --mode account --op new --user=$userpass[0] --password=$userpass[1]"); execute("tgtadm --lld $driver --mode account --op bind --tid=$next_tid --user=$userpass[0] --outgoing"); @@ -322,7 +364,7 @@ sub process_options { } # If the target is configured, but not present in the config file, -# offline it and try to remove it +# try to remove it sub remove_targets { &process_targets; @@ -340,13 +382,8 @@ sub remove_targets { } if ( $dontremove == 0 ) { - # Right now, it is not possible to remove a target if any initiators - # are connected to it. We'll do our best - offline the target first - # (so it won't accept any new connections), and remove. - # Note that remove will only work if no initiator is connected. - execute("# Removing target: $existing_target"); - execute("tgtadm --op update --mode target --tid=$tgtadm_output_tid{$existing_target} -n state -v offline"); - execute("tgtadm --mode target --op delete --tid=$tgtadm_output_tid{$existing_target}"); + # Remove the target + &main_delete($existing_target); } } } @@ -360,57 +397,50 @@ sub dump_config { my @all_targets = keys %tgtadm_output_tid; - foreach my $target (@all_targets) { - foreach my $show_target_line ($tgtadm_output{$target}) { - if ( $show_target_line =~ m/^Target (\d*): (.+)/ ) { - print "\n"; - } + # If all targets use the same driver, us it only once in the config + my $skip_driver = 0; + my @drivers_combined; + foreach my $current_target (@all_targets) { + my $driver = &show_target_info($current_target, "driver"); + push (@drivers_combined, $driver); + } - if ( $show_target_line =~ m/\s+Driver: (.+)/ ) { - print "\tdriver $1\n"; - } + my %drivers_uniq; + @drivers_uniq{@drivers_combined} = (); + my @drivers_combined_uniq = sort keys %drivers_uniq; - if ( $show_target_line =~ m/\s+Backing store: (?!No backing store)(.+)/ ) { - print "\tbacking-store $1\n"; - } - } + if (scalar @drivers_combined_uniq == 1) { + print "default-driver $drivers_combined_uniq[0]\n\n"; + } - # Process account and ACL information - my $account_acl; + # Print everything else in the config + foreach my $current_target (@all_targets) { + my $target_name = &show_target_info($current_target, "target_name"); + print "\n"; - foreach my $show_target_line ($tgtadm_output{$target}) { - $account_acl .= $show_target_line + if (scalar @drivers_combined_uniq gt 1) { + my $driver = &show_target_info($current_target, "driver"); + print "\tdriver $driver\n"; } - # start with account information... - while ($account_acl =~ m{ - \s+Account\ information:\n(.*)ACL\ information: - }xmgs - ) { - - my @account = split(/\n/, $1); - - foreach my $user (@account) { - my @var = split(/^\s+/, $user); - @var = split(/\s/, $var[1]); + my @backing_stores = &show_target_info($current_target, "backing_stores"); + foreach my $backing_store (@backing_stores) { + print "\tbacking-store $backing_store\n"; + } - if ( $var[1] eq "(outgoing)" ) { - print "\toutgoinguser $var[0] PLEASE_CORRECT_THE_PASSWORD\n"; - } elsif ( ($var[0] ne "") && ($var[1] eq "") ) { - print "\tincominguser $var[0] PLEASE_CORRECT_THE_PASSWORD\n"; - } + my @account_information = &show_target_info($current_target, "account_information"); + foreach my $account (@account_information) { + if ($account =~ /(.+)\ \(outgoing\)/) { + print "\toutgoinguser $1 PLEASE_CORRECT_THE_PASSWORD\n"; + } elsif (length $account) { + print "\tincominguser $account PLEASE_CORRECT_THE_PASSWORD\n"; } } - #...and finish with ACL information - while ($account_acl =~ m{ - \s+ACL\ information:\n(.*) - }xmgs - ) { - my @ini_addresses = split(/\n/, $1); - foreach my $ini_address (@ini_addresses) { - my @var = split(/^\s+/, $ini_address); - print "\tinitiator-address $var[1]\n"; + my @acl_information = &show_target_info($current_target, "acl_information"); + if (scalar(@acl_information) != 1 || $acl_information[0] ne "ALL") { + foreach my $ini_address (@acl_information) { + print "\tinitiator-address $ini_address\n"; } } print "\n\n"; @@ -465,17 +495,42 @@ EOF sub show_target_info { my $existing_target = $_[0]; my $task = $_[1]; + # Returns target information + if ($task eq "target_name") { + if ($tgtadm_output{$existing_target} =~ m/^Target (\d*): (.+)/ ) { + return $2; + } # Returns driver information - if ($task eq "driver") { - if ( $tgtadm_output{$existing_target} =~ m/\s+Driver: (.+)/ ) { - print $1; + } elsif ($task eq "driver") { + if ($tgtadm_output{$existing_target} =~ m/\s+Driver: (.+)/ ) { return $1; } + # Returns backing store + } elsif ($task eq "backing_stores") { + if ($tgtadm_output{$existing_target} =~ m/\s+Backing store: (?!No backing store)(.+)/ ) { + my @backing_stores = $tgtadm_output{$existing_target} =~ m{\s+Backing store: (?!No backing store\n)(.+)}g; + return @backing_stores; + } + return; + # Returns account information: + } elsif ($task eq "account_information") { + if ($tgtadm_output{$existing_target} =~ m{ + \s+Account\ information:\n(.*)\n\s+ACL\ information: + }xs + ) { + my @accounts = split(/\n/, $1); + my @account_information; + foreach my $user (@accounts) { + my @var = split(/^\s+/, $user); + push(@account_information, $var[1]); + } + return @account_information; + } # Returns ACL information } elsif ($task eq "acl_information") { - while ($tgtadm_output{$existing_target} =~ m{ + if ($tgtadm_output{$existing_target} =~ m{ \s+ACL\ information:\n(.*) - }xmgs + }xs ) { my @ini_addresses = split(/\n/, $1); my @acls; @@ -487,51 +542,80 @@ sub show_target_info { } # Returns sessions } elsif ($task eq "sessions") { - my @var = split(/\n/, $tgtadm_output{$existing_target}); - my @sids; - foreach my $sid (@var) { - if ( $sid =~ m/\s+I_T nexus: (.+)/ ) { - push(@sids, $1); + my %sessions; + if ($tgtadm_output{$existing_target} =~ m{ + \s+I_T\ nexus\ information:\n(.*)LUN\ information: + }xs + ) { + my @var = split(/\n/, $1); + my $sid; + my $cid; + + foreach my $line (@var) { + if ($line =~ m/\s+I_T nexus:\ (.+)/) { + $sid = $1; + } else { + if ($line =~ m/\s+Connection:\ (.+)/) { + $cid = $1; + $sessions{$sid} = $cid; + } + } } } - return @sids; + return %sessions; } } -# Delete the targets which are not in use -sub delete_targets { - - # Check if the target is used by an initiator - sub check_in_use { - my $existing_target = $_[0]; - my $cur_option = $_[1]; - my $cur_tid = $_[2]; - if ($tgtadm_output{$existing_target} =~ m/\s+Connection:/) { - if ($force == 1) { - # Remove ACLs first - my @acl_info = &show_target_info($existing_target, "acl_information"); - foreach my $acl (@acl_info) { - execute("tgtadm --op unbind --mode target --tid $tgtadm_output_tid{$existing_target} -I $acl"); - } - # Now, remove all sessions / connections from that tid - my @sessions = &show_target_info($existing_target, "sessions"); - foreach my $session (@sessions) { - execute("tgtadm --op delete --mode conn --tid $tgtadm_output_tid{$existing_target} --sid $session --cid 0"); +# Main subroutine for deleting targets +sub main_delete { + my $current_target = $_[0]; + my $current_tid = $_[1]; + my $configured = &check_configured($current_target); + my $del_upd_text; + # Check if the target has initiators connected + if ($tgtadm_output{$current_target} =~ m/\s+Connection:/) { + if ($force == 1) { + execute("# Removing target: $current_target"); + # Remove ACLs first + my @acl_info = &show_target_info($current_target, "acl_information"); + foreach my $acl (@acl_info) { + execute("tgtadm --op unbind --mode target --tid $tgtadm_output_tid{$current_target} -I $acl"); + } + # Now, remove all sessions / connections from that tid + my %sessions = &show_target_info($current_target, "sessions"); + foreach my $sid (keys %sessions) { + foreach my $cid ($sessions{$sid}) { + execute("tgtadm --op delete --mode conn --tid $tgtadm_output_tid{$current_target} --sid $sid --cid $cid"); } - execute("tgtadm --mode target --op delete --tid=$tgtadm_output_tid{$existing_target}"); - } else { - execute("# Target with tid $tgtadm_output_tid{$existing_target} ($existing_target) is in use, it won't be deleted."); } - } elsif (length $tgtadm_output_tid{$existing_target}) { - execute("tgtadm --mode target --op delete --tid=$tgtadm_output_tid{$existing_target}"); + execute("tgtadm --mode target --op delete --tid=$tgtadm_output_tid{$current_target}"); } else { - if ($cur_option eq "tid") { - execute("# Target with tid $cur_tid does not exist!"); + if ($update ne 0) { + $del_upd_text = "updated"; } else { - execute("# Target $existing_target does not exist!"); + $del_upd_text = "deleted"; } + execute("# Target with tid $tgtadm_output_tid{$current_target} ($current_target) is in use, it won't be $del_upd_text."); + return 1; } + } elsif (length $tgtadm_output_tid{$current_target}) { + execute("# Removing target: $current_target"); + execute("tgtadm --mode target --op delete --tid=$tgtadm_output_tid{$current_target}"); + } else { + if (length $current_tid) { + execute("# Target with tid $current_tid does not exist!"); + } else { + execute("# Target with name $current_target does not exist!"); + } + } + if ($configured ne 0) { + execute(); } + return 0; +} + +# Delete the targets +sub delete_targets { if ($delete eq "help") { print < update all or selected targets + The target will be update only if it's not used + (no initiator is connected to it). + If you want to update targets which are in use, + you have to add "--force" flag. + +Example usage: + --update help - display this help + --update ALL - update all targets + --update tid=4 - update target 4 (target with tid 4) + --update iqn.2008-08.com.example:some.target - update this target + +EOF + exit; + } elsif ($update eq "ALL") { + # Run over all targets and delete them if they are not in use + &parse_configs; + &process_targets; +# my @all_targets = keys %tgtadm_output_tid; + my @targets_combined = &combine_targets; + foreach my $current_target (@targets_combined) { + my $configured = &check_configured($current_target); + my $connected = &check_connected($current_target); + my $in_configfile = &check_in_configfile($current_target); + &combine_targets; + if (($in_configfile == 0) && ($configured == 1)) { + # Delete the target if it's not in the config file + &main_delete($current_target); + } else { + &add_targets($current_target, $configured, $connected, $in_configfile); + } + + } + } elsif ($update =~ m/^tid=(.+)/) { + # Update by tid + &parse_configs; + &process_targets; + my $current_target = $tgtadm_output_name{$1}; + my $configured = &check_configured($current_target); + my $connected = &check_connected($current_target); + my $in_configfile = &check_in_configfile($current_target); + if (($in_configfile == 0) && ($configured == 1)) { + # Delete the target if it's not in the config file + &main_delete($current_target); + } elsif ($configured == 1) { + &add_targets($current_target, $configured, $connected); + } else { + print "There is no target with tid $1, can't continue!\n"; + exit 1; + } + } else { + # Update by name + &parse_configs; + &process_targets; + my $current_target = $update; + my $configured = &check_configured($current_target); + my $connected = &check_connected($current_target); + my $in_configfile = &check_in_configfile($current_target); + if (($in_configfile == 0) && ($configured == 1)) { + # Delete the target if it's not in the config file + &main_delete($current_target); + } else { + &add_targets($current_target, $configured, $connected); + } + } +} + +# Find the biggest tid +sub find_max_tid { + my @all_targets = keys %tgtadm_output_tid; + my $maxtid = 0; + foreach my $var (@all_targets) { + if ($tgtadm_output_tid{$var} > $maxtid) { + $maxtid = $tgtadm_output_tid{$var}; + } + return $maxtid; + } +} + +# Combine targets from the config file and currently configured targets +sub combine_targets { + my @targets_in_configfile; + my @all_targets = keys %tgtadm_output_tid; + my @targets_combined; + # Make an array of targets in the config file + foreach my $k (sort keys %conf) { + if ( $k eq "target" ) { + foreach my $k2 (sort keys %{$conf{$k}}) { + push(@targets_in_configfile, $k2) + } + } + } + # Use only unique elements from both arrays + foreach my $current_target (@all_targets) { + push (@targets_combined, $current_target) unless grep { $_ eq $current_target } @targets_in_configfile; + } + @targets_combined = (@targets_combined, @targets_in_configfile); + return @targets_combined; +} + +# Check if a value is correct +sub check_value { if ( not defined $_[0] or not length $_[0] ) { print "\nOption $option has a missing value!\n"; print "Check your config file for errors (target: $target)\n"; @@ -578,6 +766,42 @@ sub check { } } +# Check if the target is in the config file +sub check_in_configfile { + my $current_target = $_[0]; + my $result; + foreach my $k (sort keys %conf) { + if ( $k eq "target" ) { + foreach my $k2 (sort keys %{$conf{$k}}) { + if ($k2 eq $current_target) { + return 1; + } + } + # If we're here, we didn't find a match + return 0; + } + } +} + +# Check if the target is configured in tgtd +sub check_configured { + my $current_target = $_[0]; + if (length $tgtadm_output_tid{$current_target}) { + return 1; + } else { + return 0; + } +} + +# Check if any initiators are connected to the target +sub check_connected { + my $current_target = $_[0]; + if ($tgtadm_output{$current_target} =~ m/\s+Connection:/) { + return 1; + } else { + return 0; + } +} # Execute or just print (or both) everything we start or would start sub execute { @@ -612,6 +836,8 @@ if ($execute == 1) { &remove_targets; } elsif ($delete ne 0) { &delete_targets; +} elsif ($update ne 0) { + &update_targets; } elsif ($dump == 1) { &dump_config; } elsif ($offline ne 0) { diff --git a/usr/Makefile b/usr/Makefile index 4245709..82ddf07 100644 --- a/usr/Makefile +++ b/usr/Makefile @@ -55,10 +55,12 @@ CFLAGS += -g -O2 -Wall -Wstrict-prototypes -fPIC LIBS += -lpthread PROGRAMS += tgtd tgtadm -SCRIPTS += ../scripts/tgt-setup-lun +SCRIPTS += ../scripts/tgt-setup-lun ../scripts/tgt-admin TGTD_OBJS += tgtd.o mgmt.o target.o scsi.o log.o driver.o util.o work.o \ - parser.o spc.o sbc.o mmc.o osd.o scc.o smc.o ssc.o bs_ssc.o bs.o -MANPAGES = ../doc/manpages/tgtadm.8 ../doc/manpages/tgt-setup-lun.8 + parser.o spc.o sbc.o mmc.o osd.o scc.o smc.o ssc.o bs_ssc.o \ + bs.o +MANPAGES = ../doc/manpages/tgtadm.8 ../doc/manpages/tgt-admin.8 \ + ../doc/manpages/tgt-setup-lun.8 TGTD_DEP = $(TGTD_OBJS:.o=.d) diff --git a/usr/be_byteshift.h b/usr/be_byteshift.h new file mode 100644 index 0000000..5c6a619 --- /dev/null +++ b/usr/be_byteshift.h @@ -0,0 +1,68 @@ +#ifndef _LINUX_UNALIGNED_BE_BYTESHIFT_H +#define _LINUX_UNALIGNED_BE_BYTESHIFT_H + +static inline uint16_t __get_unaligned_be16(const uint8_t *p) +{ + return p[0] << 8 | p[1]; +} + +static inline uint32_t __get_unaligned_be32(const uint8_t *p) +{ + return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3]; +} + +static inline uint64_t __get_unaligned_be64(const uint8_t *p) +{ + return (uint64_t)__get_unaligned_be32(p) << 32 | + __get_unaligned_be32(p + 4); +} + +static inline void __put_unaligned_be16(uint16_t val, uint8_t *p) +{ + *p++ = val >> 8; + *p++ = val; +} + +static inline void __put_unaligned_be32(uint32_t val, uint8_t *p) +{ + __put_unaligned_be16(val >> 16, p); + __put_unaligned_be16(val, p + 2); +} + +static inline void __put_unaligned_be64(uint64_t val, uint8_t *p) +{ + __put_unaligned_be32(val >> 32, p); + __put_unaligned_be32(val, p + 4); +} + +static inline uint16_t get_unaligned_be16(const void *p) +{ + return __get_unaligned_be16((const uint8_t *)p); +} + +static inline uint32_t get_unaligned_be32(const void *p) +{ + return __get_unaligned_be32((const uint8_t *)p); +} + +static inline uint64_t get_unaligned_be64(const void *p) +{ + return __get_unaligned_be64((const uint8_t *)p); +} + +static inline void put_unaligned_be16(uint16_t val, void *p) +{ + __put_unaligned_be16(val, p); +} + +static inline void put_unaligned_be32(uint32_t val, void *p) +{ + __put_unaligned_be32(val, p); +} + +static inline void put_unaligned_be64(uint64_t val, void *p) +{ + __put_unaligned_be64(val, p); +} + +#endif /* _LINUX_UNALIGNED_BE_BYTESHIFT_H */ diff --git a/usr/log.c b/usr/log.c index 4b71216..076c770 100644 --- a/usr/log.c +++ b/usr/log.c @@ -108,9 +108,6 @@ static int logarea_init (int size) return 1; } - la->ops[0].sem_num = 0; - la->ops[0].sem_flg = 0; - return 0; } @@ -237,21 +234,24 @@ static void log_syslog (void * buff) static void dolog(int prio, const char *fmt, va_list ap) { struct timespec ts; + struct sembuf ops; if (la) { ts.tv_sec = 0; ts.tv_nsec = 10000; - la->ops[0].sem_op = -1; - if (semtimedop(la->semid, la->ops, 1, &ts) < 0) { + ops.sem_num = 0; + ops.sem_flg = 0; + ops.sem_op = -1; + if (semtimedop(la->semid, &ops, 1, &ts) < 0) { syslog(LOG_ERR, "semop up failed"); return; } log_enqueue(prio, fmt, ap); - la->ops[0].sem_op = 1; - if (semop(la->semid, la->ops, 1) < 0) { + ops.sem_op = 1; + if (semop(la->semid, &ops, 1) < 0) { syslog(LOG_ERR, "semop down failed"); return; } @@ -291,15 +291,21 @@ void log_debug(const char *fmt, ...) static void log_flush(void) { + struct sembuf ops; + while (!la->empty) { - la->ops[0].sem_op = -1; - if (semop(la->semid, la->ops, 1) < 0) { + ops.sem_num = 0; + ops.sem_flg = 0; + ops.sem_op = -1; + if (semop(la->semid, &ops, 1) < 0) { syslog(LOG_ERR, "semop up failed"); exit(1); } + log_dequeue(la->buff); - la->ops[0].sem_op = 1; - if (semop(la->semid, la->ops, 1) < 0) { + + ops.sem_op = 1; + if (semop(la->semid, &ops, 1) < 0) { syslog(LOG_ERR, "semop down failed"); exit(1); } diff --git a/usr/log.h b/usr/log.h index 6993235..b84f6d6 100644 --- a/usr/log.h +++ b/usr/log.h @@ -55,7 +55,6 @@ struct logarea { void *start; void *end; char *buff; - struct sembuf ops[1]; int semid; union semun semarg; }; diff --git a/usr/smc.c b/usr/smc.c index 9d7f681..ab36e9c 100644 --- a/usr/smc.c +++ b/usr/smc.c @@ -225,6 +225,41 @@ static int build_element_descriptors(uint8_t *data, struct list_head *head, } /** + * smc_initialize_element_status with range + * - INITIALIZE ELEMENT STATUS WITH RANGE op code + * + * Support the SCSI op code INITIALIZE_ELEMENT_STATUS_WITH_RANGE + * Ref: smc3r11, 6.5 + */ +static int smc_initialize_element_status_range(int host_no, struct scsi_cmd *cmd) +{ + scsi_set_in_resid_by_actual(cmd, 0); + + if (device_reserved(cmd)) + return SAM_STAT_RESERVATION_CONFLICT; + else + return SAM_STAT_GOOD; +} + +/** + * smc_initialize_element_status - INITIALIZE ELEMENT STATUS op code + * + * Some backup libraries seem to require this. + * + * Support the SCSI op code INITIALIZE_ELEMENT_STATUS + * Ref: smc3r10a, 6.2 + */ +static int smc_initialize_element_status(int host_no, struct scsi_cmd *cmd) +{ + scsi_set_in_resid_by_actual(cmd, 0); + + if (device_reserved(cmd)) + return SAM_STAT_RESERVATION_CONFLICT; + else + return SAM_STAT_GOOD; +} + +/** * smc_read_element_status - READ ELEMENT STATUS op code * * Support the SCSI op code READ ELEMENT STATUS @@ -748,7 +783,7 @@ struct device_type_template smc_template = { {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, - {spc_illegal_op,}, + {smc_initialize_element_status,}, {spc_illegal_op,}, {spc_illegal_op,}, @@ -778,7 +813,28 @@ struct device_type_template smc_template = { {spc_illegal_op,}, {spc_illegal_op,}, - [0x20 ... 0x4f] = {spc_illegal_op,}, + [0x20 ... 0x2f] = {spc_illegal_op,}, + + /* 0x30 */ + {spc_illegal_op,}, + {spc_illegal_op,}, + {spc_illegal_op,}, + {spc_illegal_op,}, + {spc_illegal_op,}, + {spc_illegal_op,}, + {spc_illegal_op,}, + {smc_initialize_element_status_range,}, + + {spc_illegal_op,}, + {spc_illegal_op,}, + {spc_illegal_op,}, + {spc_illegal_op,}, + {spc_illegal_op,}, + {spc_illegal_op,}, + {spc_illegal_op,}, + {spc_illegal_op,}, + + [0x40 ... 0x4f] = {spc_illegal_op,}, /* 0x50 */ {spc_illegal_op,}, diff --git a/usr/spc.c b/usr/spc.c index bd2c975..60fd7d7 100644 --- a/usr/spc.c +++ b/usr/spc.c @@ -318,6 +318,16 @@ int spc_test_unit(int host_no, struct scsi_cmd *cmd) return SAM_STAT_CHECK_CONDITION; } +int spc_prevent_allow_media_removal(int host_no, struct scsi_cmd *cmd) +{ + /* TODO: implement properly */ + + if (device_reserved(cmd)) + return SAM_STAT_RESERVATION_CONFLICT; + else + return SAM_STAT_GOOD; +} + int spc_mode_select(int host_no, struct scsi_cmd *cmd, int (*update)(struct scsi_cmd *, uint8_t *, int *)) { diff --git a/usr/spc.h b/usr/spc.h index 8fe3e3c..cfc9cf3 100644 --- a/usr/spc.h +++ b/usr/spc.h @@ -8,6 +8,7 @@ extern int spc_report_luns(int host_no, struct scsi_cmd *cmd); extern int spc_start_stop(int host_no, struct scsi_cmd *cmd); extern int spc_test_unit(int host_no, struct scsi_cmd *cmd); extern int spc_request_sense(int host_no, struct scsi_cmd *cmd); +extern int spc_prevent_allow_media_removal(int host_no, struct scsi_cmd *cmd); extern int spc_illegal_op(int host_no, struct scsi_cmd *cmd); extern int spc_lu_init(struct scsi_lu *lu); diff --git a/usr/ssc.c b/usr/ssc.c index 2630a6a..96c3242 100644 --- a/usr/ssc.c +++ b/usr/ssc.c @@ -192,7 +192,7 @@ static struct device_type_template ssc_template = { {spc_start_stop,}, {spc_illegal_op,}, {spc_illegal_op,}, - {spc_illegal_op,}, + {spc_prevent_allow_media_removal,}, {spc_illegal_op,}, /* 0x20 */ @@ -298,7 +298,7 @@ static struct device_type_template ssc_template = { {spc_report_luns,}, {spc_illegal_op,}, {spc_illegal_op,}, - {spc_illegal_op,}, + {spc_maint_in, maint_in_service_actions,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, diff --git a/usr/util.h b/usr/util.h index ac4b380..794c70b 100644 --- a/usr/util.h +++ b/usr/util.h @@ -6,6 +6,7 @@ #include #include #include +#include "be_byteshift.h" #define roundup(x, y) ((((x) + ((y) - 1)) / (y)) * (y)) #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))