From e6a57baa143e8fd5af94555659faef085fe1f46e Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Wed, 15 Aug 2007 09:28:54 +0000 Subject: [PATCH] The initial commit of iSCSI-SCST git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@162 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- Makefile | 30 + iscsi-scst/COPYING | 341 +++ iscsi-scst/ChangeLog | 38 + iscsi-scst/ChangeLog-IET | 435 +++ iscsi-scst/Makefile | 76 + iscsi-scst/README | 115 + iscsi-scst/README-IET | 105 + iscsi-scst/ToDo | 32 + iscsi-scst/doc/manpages/iscsi-scst-adm.8 | 240 ++ iscsi-scst/doc/manpages/iscsi-scstd.8 | 308 +++ iscsi-scst/doc/manpages/iscsi-scstd.conf.5 | 210 ++ iscsi-scst/etc/initd/initd | 67 + iscsi-scst/etc/initd/initd.debian | 133 + iscsi-scst/etc/initd/initd.gentoo | 71 + iscsi-scst/etc/initd/initd.redhat | 130 + iscsi-scst/etc/initiators.allow | 7 + iscsi-scst/etc/initiators.deny | 14 + iscsi-scst/etc/iscsi-scstd.conf | 55 + iscsi-scst/include/iscsi_u.h | 137 + iscsi-scst/kernel/Makefile | 25 + iscsi-scst/kernel/config.c | 526 ++++ iscsi-scst/kernel/conn.c | 384 +++ iscsi-scst/kernel/digest.c | 212 ++ iscsi-scst/kernel/digest.h | 19 + iscsi-scst/kernel/event.c | 115 + iscsi-scst/kernel/iscsi.c | 2425 +++++++++++++++++ iscsi-scst/kernel/iscsi.h | 464 ++++ iscsi-scst/kernel/iscsi_dbg.h | 59 + iscsi-scst/kernel/iscsi_hdr.h | 509 ++++ iscsi-scst/kernel/nthread.c | 999 +++++++ iscsi-scst/kernel/param.c | 234 ++ .../patches/put_page_callback-2.6.16.patch | 245 ++ .../patches/put_page_callback-2.6.18.patch | 254 ++ .../patches/put_page_callback-2.6.21.patch | 256 ++ .../patches/put_page_callback-2.6.22.patch | 256 ++ iscsi-scst/kernel/session.c | 180 ++ iscsi-scst/kernel/target.c | 278 ++ iscsi-scst/usr/Makefile | 38 + iscsi-scst/usr/chap.c | 633 +++++ iscsi-scst/usr/config.h | 19 + iscsi-scst/usr/conn.c | 151 + iscsi-scst/usr/ctldev.c | 435 +++ iscsi-scst/usr/event.c | 132 + iscsi-scst/usr/iscsi_adm.c | 559 ++++ iscsi-scst/usr/iscsi_adm.h | 68 + iscsi-scst/usr/iscsi_hdr.h | 213 ++ iscsi-scst/usr/iscsi_scstd.c | 585 ++++ iscsi-scst/usr/iscsid.c | 765 ++++++ iscsi-scst/usr/iscsid.h | 236 ++ iscsi-scst/usr/isns.c | 960 +++++++ iscsi-scst/usr/isns_proto.h | 200 ++ iscsi-scst/usr/log.c | 127 + iscsi-scst/usr/message.c | 172 ++ iscsi-scst/usr/misc.h | 62 + iscsi-scst/usr/param.c | 304 +++ iscsi-scst/usr/param.h | 44 + iscsi-scst/usr/plain.c | 609 +++++ iscsi-scst/usr/session.c | 173 ++ iscsi-scst/usr/target.c | 133 + iscsi-scst/usr/types.h | 35 + 60 files changed, 16637 insertions(+) create mode 100644 iscsi-scst/COPYING create mode 100644 iscsi-scst/ChangeLog create mode 100644 iscsi-scst/ChangeLog-IET create mode 100644 iscsi-scst/Makefile create mode 100644 iscsi-scst/README create mode 100644 iscsi-scst/README-IET create mode 100644 iscsi-scst/ToDo create mode 100644 iscsi-scst/doc/manpages/iscsi-scst-adm.8 create mode 100644 iscsi-scst/doc/manpages/iscsi-scstd.8 create mode 100644 iscsi-scst/doc/manpages/iscsi-scstd.conf.5 create mode 100644 iscsi-scst/etc/initd/initd create mode 100644 iscsi-scst/etc/initd/initd.debian create mode 100644 iscsi-scst/etc/initd/initd.gentoo create mode 100644 iscsi-scst/etc/initd/initd.redhat create mode 100644 iscsi-scst/etc/initiators.allow create mode 100644 iscsi-scst/etc/initiators.deny create mode 100644 iscsi-scst/etc/iscsi-scstd.conf create mode 100644 iscsi-scst/include/iscsi_u.h create mode 100644 iscsi-scst/kernel/Makefile create mode 100644 iscsi-scst/kernel/config.c create mode 100644 iscsi-scst/kernel/conn.c create mode 100644 iscsi-scst/kernel/digest.c create mode 100644 iscsi-scst/kernel/digest.h create mode 100644 iscsi-scst/kernel/event.c create mode 100644 iscsi-scst/kernel/iscsi.c create mode 100644 iscsi-scst/kernel/iscsi.h create mode 100644 iscsi-scst/kernel/iscsi_dbg.h create mode 100644 iscsi-scst/kernel/iscsi_hdr.h create mode 100644 iscsi-scst/kernel/nthread.c create mode 100644 iscsi-scst/kernel/param.c create mode 100644 iscsi-scst/kernel/patches/put_page_callback-2.6.16.patch create mode 100644 iscsi-scst/kernel/patches/put_page_callback-2.6.18.patch create mode 100644 iscsi-scst/kernel/patches/put_page_callback-2.6.21.patch create mode 100644 iscsi-scst/kernel/patches/put_page_callback-2.6.22.patch create mode 100644 iscsi-scst/kernel/session.c create mode 100644 iscsi-scst/kernel/target.c create mode 100644 iscsi-scst/usr/Makefile create mode 100644 iscsi-scst/usr/chap.c create mode 100644 iscsi-scst/usr/config.h create mode 100644 iscsi-scst/usr/conn.c create mode 100644 iscsi-scst/usr/ctldev.c create mode 100644 iscsi-scst/usr/event.c create mode 100644 iscsi-scst/usr/iscsi_adm.c create mode 100644 iscsi-scst/usr/iscsi_adm.h create mode 100644 iscsi-scst/usr/iscsi_hdr.h create mode 100644 iscsi-scst/usr/iscsi_scstd.c create mode 100644 iscsi-scst/usr/iscsid.c create mode 100644 iscsi-scst/usr/iscsid.h create mode 100644 iscsi-scst/usr/isns.c create mode 100644 iscsi-scst/usr/isns_proto.h create mode 100644 iscsi-scst/usr/log.c create mode 100644 iscsi-scst/usr/message.c create mode 100644 iscsi-scst/usr/misc.h create mode 100644 iscsi-scst/usr/param.c create mode 100644 iscsi-scst/usr/param.h create mode 100644 iscsi-scst/usr/plain.c create mode 100644 iscsi-scst/usr/session.c create mode 100644 iscsi-scst/usr/target.c create mode 100644 iscsi-scst/usr/types.h diff --git a/Makefile b/Makefile index a2d1d8e17..ef113ea79 100644 --- a/Makefile +++ b/Makefile @@ -24,22 +24,28 @@ QLA_DIR=qla2x00t/qla2x00-target LSI_DIR=mpt USR_DIR=usr/fileio +ISCSI_DIR=iscsi-scst +#ISCSI_DISTDIR=../../../../iscsi_scst_inst + all: cd $(SCST_DIR) && $(MAKE) $@ @if [ -d $(QLA_DIR) ]; then cd $(QLA_DIR) && $(MAKE) $@; fi # @if [ -d $(LSI_DIR) ]; then cd $(LSI_DIR) && $(MAKE) $@; fi + @if [ -d $(ISCSI_DIR) ]; then cd $(ISCSI_DIR) && $(MAKE) $@; fi @if [ -d $(USR_DIR) ]; then cd $(USR_DIR) && $(MAKE) $@; fi install: cd $(SCST_DIR) && $(MAKE) $@ @if [ -d $(QLA_DIR) ]; then cd $(QLA_DIR) && $(MAKE) $@; fi # @if [ -d $(LSI_DIR) ]; then cd $(LSI_DIR) && $(MAKE) $@; fi + @if [ -d $(ISCSI_DIR) ]; then cd $(ISCSI_DIR) && $(MAKE) DISTDIR=$(ISCSI_DISTDIR) $@; fi @if [ -d $(USR_DIR) ]; then cd $(USR_DIR) && $(MAKE) $@; fi uninstall: cd $(SCST_DIR) && $(MAKE) $@ @if [ -d $(QLA_DIR) ]; then cd $(QLA_DIR) && $(MAKE) $@; fi @if [ -d $(LSI_DIR) ]; then cd $(LSI_DIR) && $(MAKE) $@; fi + @if [ -d $(ISCSI_DIR) ]; then cd $(ISCSI_DIR) && $(MAKE) $@; fi @if [ -d $(USR_DIR) ]; then cd $(USR_DIR) && $(MAKE) $@; fi clean: @@ -47,6 +53,7 @@ clean: @if [ -d $(QLA_INI_DIR) ]; then cd $(QLA_INI_DIR) && $(MAKE) $@; fi @if [ -d $(QLA_DIR) ]; then cd $(QLA_DIR) && $(MAKE) $@; fi @if [ -d $(LSI_DIR) ]; then cd $(LSI_DIR) && $(MAKE) $@; fi + @if [ -d $(ISCSI_DIR) ]; then cd $(ISCSI_DIR) && $(MAKE) $@; fi @if [ -d $(USR_DIR) ]; then cd $(USR_DIR) && $(MAKE) $@; fi extraclean: @@ -54,6 +61,7 @@ extraclean: @if [ -d $(QLA_INI_DIR) ]; then cd $(QLA_INI_DIR) && $(MAKE) $@; fi @if [ -d $(QLA_DIR) ]; then cd $(QLA_DIR) && $(MAKE) $@; fi @if [ -d $(LSI_DIR) ]; then cd $(LSI_DIR) && $(MAKE) $@; fi + @if [ -d $(ISCSI_DIR) ]; then cd $(ISCSI_DIR) && $(MAKE) $@; fi @if [ -d $(USR_DIR) ]; then cd $(USR_DIR) && $(MAKE) $@; fi scst: @@ -88,6 +96,21 @@ qla_extraclean: cd $(QLA_INI_DIR)/.. && $(MAKE) extraclean cd $(QLA_DIR) && $(MAKE) extraclean +iscsi: + cd $(ISCSI_DIR) && $(MAKE) + +iscsi_install: + cd $(ISCSI_DIR) && $(MAKE) install + +iscsi_uninstall: + cd $(ISCSI_DIR) && $(MAKE) uninstall + +iscsi_clean: + cd $(ISCSI_DIR) && $(MAKE) clean + +iscsi_extraclean: + cd $(ISCSI_DIR) && $(MAKE) extraclean + lsi: cd $(LSI_DIR) && $(MAKE) @@ -137,6 +160,12 @@ help: @echo " qla_install : 2.6 qla target: install" @echo " qla_uninstall : 2.6 qla target: uninstall" @echo "" + @echo " iscsi : make new ISCSI target" + @echo " iscsi_clean : ISCSI target: clean " + @echo " iscsi_extraclean : ISCSI target: clean + clean dependencies" + @echo " iscsi_install : ISCSI target: install" + @echo " iscsi_uninstall : ISCSI target: uninstall" + @echo "" @echo " lsi : make lsi target" @echo " lsi_clean : lsi target: clean " @echo " lsi_extraclean : lsi target: clean + clean dependencies" @@ -154,5 +183,6 @@ help: .PHONY: all install uninstall clean extraclean help \ qla qla_install qla_uninstall qla_clean qla_extraclean \ lsi lsi_install lsi_uninstall lsi_clean lsi_extraclean \ + iscsi iscsi_install iscsi_uninstall iscsi_clean iscsi_extraclean \ scst scst_install scst_uninstall scst_clean scst_extraclean \ usr usr_install usr_uninstall usr_clean usr_extraclean diff --git a/iscsi-scst/COPYING b/iscsi-scst/COPYING new file mode 100644 index 000000000..afd5a9471 --- /dev/null +++ b/iscsi-scst/COPYING @@ -0,0 +1,341 @@ + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + 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 General Public License for more details. + + 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 + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/iscsi-scst/ChangeLog b/iscsi-scst/ChangeLog new file mode 100644 index 000000000..70f5ba13e --- /dev/null +++ b/iscsi-scst/ChangeLog @@ -0,0 +1,38 @@ +Summary of changes in iSCSI-SCST since it was IET +------------------------------------------------- + + - Commands processing flow changed to work with SCST. Significant + cleanup was done. Modules and config files were renamed to allow to + run with IET on the same host. + + - In IET the iSCSI negotiation isn't fully iSCSI RFC confirmed: it doesn't + support ranges of values and, more important, violates RFC in case + when in the IET config file some value for some parameter is set and + a remote initiator doesn't initiate the negotiation for this + parameter or declare its value. According to RFC, in this case IET + shall use the RFC-specified default value, but it uses config file + specified one instead. Looks like the implementation confuses IET + config file default values and iSCSI RFC ones. The default values + handling was fixed. But support for ranges remains unfixed (see ToDo + file). + + - All shutdown/restart problems (resource leaks, hangups, etc.), especially + under load, were fixed. + + - Full duplex network IO implemented. + + - Threading model reimplemented. + + - Digests calculation made multi-threaded. + + - TX data digest errors handling iSCSI RFC violation was fixed. Another one + (SNACK handling) remains unfixed. + + - Ability to send data with sense in a single response added. + + - Ability to handle initiators misbehavior a bit improved: several BUG()'s + were relaced by the proper handling. Hovewer, there is a plenty of work in + this area left, IET is known to have a lot of weaknesses here. + + - A lot of other bugfixes and code cleanups. + diff --git a/iscsi-scst/ChangeLog-IET b/iscsi-scst/ChangeLog-IET new file mode 100644 index 000000000..a9827435d --- /dev/null +++ b/iscsi-scst/ChangeLog-IET @@ -0,0 +1,435 @@ +Summary of changes from v0.4.14 to v0.4.15 +================================= + +Juhani Rautiainen + o Add RELEASE/RESERVE support + +Ross S. W. Walker + o Improve the build system to support several kernel versions + o Add block-io support + + +Summary of changes from v0.4.13 to v0.4.14 +================================= + +Arne Redlich + o Kill unused "state" in struct iscsi_cmnd. + o Fixed fileio_sync() to propagate error to the caller (and initiator). + o Don't attempt to show target/session params if communication with ietd + fails. + o Fixes to ietadm parameters handling. + +FUJITA Tomonori + o rewritten iSNS code, many iSNS fixes. + o added iSNS SCN support. + o IPv6 fixes to userspace. + +Ming Zhang + o Fix the READ_* commands error handling bug. + o fix the mode sense response. + o wrong #endif sequence in misc.h + +Richard Bollinger + o add a patch to ietd.c that allows defunct sessions to go away. + o add write-back cache and read-only support. + +Frederic Temporelli + o Fix for the combination of 32-bit userland and 64-bit kernel on mips. + +Henry Liu + o corrected many task management functions, prevent crashing on + LUN RESET, TARGET WARM RESET. + +K Chapman + o Fixed a typo in check_segment_length(). + +Emmanuel Florac + o Add ietadm manpage. + + +Summary of changes from v0.4.12 to v0.4.13 +================================= +Arne Redlich + o patch to avoid digest calculation for PDUs whose data has been skipped + already for various reasons. + o Correct a bug managing non-default MaxRxDSL. + o added to ietadm ability to show target parameters. + o add on the workaround to AIX initiator MaxCmdSN bug. + +FUJITA Tomonori + o added to ietadm ability to show the iSCSI parameters for an established + session. + +Ming Zhang + o Fixed this bug : ietd should manage the iscsi name in a case insensitive + way to conform to the RFC. + o workaround to AIX initiator MaxCmdSN bug. + o Fixed socket() return value judgment. + +Bastiaan Bakker + o add 'condrestart' command to the RedHat initscript. + +Robert Whitehead + o correct the bug that prevents iet to start if there isn't + an /etc/ietd.conf file. + +Summary of changes from v0.4.11 to v0.4.12 +================================= + +Arne Redlich + o Fix MaxRecvDataSegmentLength handling. + o Fix login parameter handling. + o Update man pages. + +Bastiaan Bakker + o Add features to specify the listen address and port. + o Fix setuid and setgid bugs in ietd daemon. + +FUJITA Tomonori + o Add IPv6 support. + +Junjiro Okajima + o Fix a bug about getting parameters from kernel space. + +Krzysztof Blaszkowski + o Fix for requests with unaligned to 4 length. + + +Summary of changes from v0.4.10 to v0.4.11 +================================= + +FUJITA Tomonori + o Fix Task Management Function bugs. + +Ming Zhang + o Update man pages. + + +Summary of changes from v0.4.9 to v0.4.10 +================================= + +Arne Redlich + o Fix 0x83 inquiry output. + o Fix iSCSI parameter handling bugs. + +FUJITA Tomonori + o Add the access control based on initiator address + and target name patterns. + +Junjiro Okajima + o Fix parameter checking bugs. + +Ming Zhang + o Add the nullio mode (only useful for performance evaluation). + + +Summary of changes from v0.4.8 to v0.4.9 +================================= + +FUJITA Tomonori + o Fix parameter negotiation handling bugs. + +Wang Zhenyu + o Fix digest negotiation handling bugs. + + +Summary of changes from v0.4.7 to v0.4.8 +================================= + +FUJITA Tomonori + o Fix unsolicited data handling bugs. + o Rewrite parameter handling code. + o Rewrite ietadm tool. + o Improve dynamic configuration support. + o Cleanups on the kernel-user interface. + o Improve wrong PDU handling. + o Implement a framework to handle multiple configuration methods. + o Implement basic access control support. + + +Summary of changes from v0.4.6 to v0.4.7 +================================= + +Florian Zierer + o Add the startup script for Gentoo. + +FUJITA Tomonori + o Rewrite parameter handling code. + o Fix task management code bug. + o Fix 0x83 inquiry output (Thanks to Christophe Varoqui). + +Ming Zhang + o Acquire T10 ID. + o Fix parameter handling bugs. + o Some user-space cleanups. + +Philipp Hug + o Fix ietd.8 man typo. + + +Summary of changes from v0.4.5 to v0.4.6 +================================= + +FUJITA Tomonori + o Replace the makeshift event notification code with netlink. + o Add task management code except for ACA and reassign stuff. + o Fix r2t lun bug (Thanks to Ming Zhang). + + +Summary of changes from v0.4.4 to v0.4.5 +================================= + +FUJITA Tomonori + o Rewrite the iSCSI command handling code. + o Rewrite the I/O data handling code. + o Fix worker thread. + o Several cleanups. + + +Summary of changes from v0.4.3 to v0.4.4 +================================= + +Krzysztof Blaszkowski + o Fix an out-of-memory bug. + + +Summary of changes from v0.4.2 to v0.4.3 +================================= + +Arne Redlich + o Fix header digest bug. + o Fix unexpected closed connection bug. + o Fix iSCSI parameter bug. + +FUJITA Tomonori + o Fix network thread. + + +Summary of changes from v0.4.1 to v0.4.2 +================================= + +FUJITA Tomonori + o Fix network thread. + o Fix MaxOutstandingR2T handling. + +Ming Zhang + o Add large volume support (over 2TB). + + +Summary of changes from v0.4.0 to v0.4.1 +================================= + +Arne Redlich + o Add mutual CHAP support. Note that you need to replace "User" + with "IncomingUser" in ietd.conf. + +FUJITA Tomonori + o Fix InitialR2T=No support. + o Fix INQUIRY command handling. + o Fix network and worker thread. + o Start to split SCSI stuff. + o Rewrite the R2T handling code. + o Several cleanups. + + +Summary of changes from v0.3.8 to v0.4.0 +================================= + +Arne Redlich + o iSNS bug fix. + +FUJITA Tomonori + o Move to 2.6 kernels. + o Rewrite the kernel thread performing network I/O. + o Add header and data digests (Thanks to Arne Redlich). + +Ming Zhang + o Add mode sense page 0x3 and 0x4 support (Thanks to K Chapman). + o iSNS bug fix. + + +Summary of changes from v0.3.7 to v0.3.8 +================================= + +Arne Redlich + o Fix ietadm global option bug. + +FUJITA Tomonori + o Fix TCP option bugs (Thanks to Chuck Berg). + o Fix REPORT LUN (handling lots of LUs). + + +Summary of changes from v0.3.6 to v0.3.7 +================================= + +Arne Redlich + o Fix target_alloc_pages(). + +FUJITA Tomonori + o Fix REPORT LUN bug. + + +Summary of changes from v0.3.5 to v0.3.6 +================================= + +Arne Redlich + o Fix bugs about rejecting PDUs. + +FUJITA Tomonori + o Cleanups on target_cmnd structure. + o Kill highmem stuff. + o Fix REPORT LUN (handling lots of LUs). + + +Summary of changes from v0.3.4 to v0.3.5 +================================= + +Arne Redlich + o Fix ietd security hole. + o Fix REPORT LUN bug. + o FIX NOOP_OUT padding bug. + +FUJITA Tomonori + o Rewrite event notification code. + +Libor Vanek + o Add max_sessions option. + o Fix command parsing bug. + +Ming Zhang + o Cleanups for 64-bit architectures. + + +Summary of changes from v0.3.3 to v0.3.4 +================================= + +FUJITA Tomonori + o Improve dynamic configuration support (adding targets and users). + + +Summary of changes from v0.3.2 to v0.3.3 +================================= + +FUJITA Tomonori + o Fix Makefile for the startup script. + + +Summary of changes from v0.3.1 to v0.3.2 +================================= + +Ali Lehmann + o Add a new startup script for Debian. + +FUJITA Tomonori + o Fix the istd's handling of connections in out-of-memory situations. + o Fix bugs in regular file support. + o Fix `ietadm --mode del all`. + +Libor Vanek + o Add a new startup script for RedHat. + +Ming Zhang + o Add uid/gid option to ietd daemon. + o Fix a access freed-memory bug in kernel/daemon.c. + + +Summary of changes from v0.3.0 to v0.3.1 +================================= + +FUJITA Tomonori + o Fix memory leaks in ietd daemon (Thanks to Ming). + o Fix bugs about REPORT_LUNS commands (Thanks to Ming). + o Fix a bug about Target Task Tag. + o Add regular file support to fileio mode. + + +Summary of changes from v0.2.6 to v0.3.0 +================================= + +Ali Lehmann + o Update ietd.8 man page. + +FUJITA Tomonori + o Fix shutdown code. + o Fix istd kernel thread bugs. + o Replace procfs interface with ioctl. + o Add dynamic configuration support. + o Update README and the boot script. + +Ming Zhang + o Add config option to ietd daemon. + + +Summary of changes from v0.2.5 to v0.2.6 +================================= + +Ali Lehmann + o Add ietd.8 and ietd.conf.5 man pages. + +FUJITA Tomonori + o Update README, Makefile, and the boot script. + + +Summary of changes from v0.2.4 to v0.2.5 +================================= + +FUJITA Tomonori + o Update README. + + +Summary of changes from v0.2.3 to v0.2.4 +================================= + +Ming Zhang + o Add a preliminary iSNS support. + o Fix merge mistakes that I made at the previous release. + + +Summary of changes from v0.2.2 to v0.2.3 +================================= + +Ming Zhang + o Improve INQUIRY, REQUEST_SENSE, and MODE_SENSE command supports + o Add fake RESERVE* and RELEASE* command supports + + +Summary of changes from v0.2.1 to v0.2.2 +================================= + +FUJITA Tomonori + o Improve the write performance of the file IO mode + +Ming Zhang + o Fix unupdated pg_cnt when allocating a new tcmnd + o Several cleanups + + +Summary of changes from v0.2.0 to v0.2.1 +================================= + +FUJITA Tomonori + o Fix a bug that makes the target use CPU unnecessarily + o Add a feature that enable you to pass options to an IO mode + + +Summary of changes from v0.1.0 to v0.2.0 +================================= + +FUJITA Tomonori + o Rewrite read and write kernel threads which perform network IO + o Fix race issues in the proc interface + o Fix shutdown code + +Ming Zhang + o Fix memory leaks in file and block IO modes + + +Summary of changes from the ardis v20040211 to v0.1.0 +================================= + +FUJITA Tomonori + o Remove a kernel patch. Multiple threads execute I/O operations + o Replace IO functions with the kernel starndard functions + o Add multiple IO modes feature + o Fix several race issues + o Fix several out-of-memory situation bugs diff --git a/iscsi-scst/Makefile b/iscsi-scst/Makefile new file mode 100644 index 000000000..0053745bb --- /dev/null +++ b/iscsi-scst/Makefile @@ -0,0 +1,76 @@ +# +# Makefile for the Linux kernel device drivers. +# +# Note! Dependencies are done automagically by 'make dep', which also +# removes any old dependencies. DON'T put your own dependencies here +# unless it's something special (not a .c file). +# +# Note 2! The CFLAGS definitions are now in the main makefile. + +SCST_DIR := $(shell pwd)/../scst/src +SUBDIRS := $(shell pwd) + +ifeq ($(KVER),) + ifeq ($(KDIR),) + KVER = $(shell uname -r) + KDIR ?= /lib/modules/$(KVER)/build + else + KVER = $$KERNELRELEASE + endif +else + KDIR ?= /lib/modules/$(KVER)/build +endif + +all: progs mods + +mods: Modules.symvers Module.symvers + $(MAKE) -C $(KDIR) SUBDIRS=$(shell pwd)/kernel modules + +progs: + $(MAKE) -C usr + +install: all kernel/iscsi-scst.ko usr/iscsi-scstd usr/iscsi-scst-adm + @install -vD usr/iscsi-scstd $(DISTDIR)/usr/local/sbin/iscsi-scstd + @install -vD usr/iscsi-scst-adm $(DISTDIR)/usr/local/sbin/iscsi-scst-adm + if [ -f /etc/debian_version ]; then \ + install -vD -m 755 etc/initd/initd.debian $(DISTDIR)/etc/init.d/iscsi-scst; \ + elif [ -f /etc/redhat-release ]; then \ + install -vD -m 755 etc/initd/initd.redhat $(DISTDIR)/etc/init.d/iscsi-scst; \ + elif [ -f /etc/gentoo-release ]; then \ + install -vD -m 755 etc/initd/initd.gentoo $(DISTDIR)/etc/init.d/iscsi-scst; \ + elif [ -f /etc/slackware-version ]; then \ + install -vD -m 755 etc/initd/initd $(DISTDIR)/etc/rc.d/iscsi-scst; \ + else \ + install -vD -m 755 etc/initd/initd $(DISTDIR)/etc/init.d/iscsi-scst; \ + fi + @eval `sed -n 's/#define UTS_RELEASE /KERNELRELEASE=/p' $(KDIR)/include/linux/version.h $(KDIR)/include/linux/utsrelease.h 2>/dev/null`; \ + install -vD -m 644 kernel/iscsi-scst.ko \ + $(DISTDIR)$(INSTALL_MOD_PATH)/lib/modules/$(KVER)/extra/iscsi-scst.ko + -/sbin/depmod -aq $(KVER) + +SCST_MOD_VERS := $(shell ls $(SCST_DIR)/Modules.symvers 2>/dev/null) +ifneq ($(SCST_MOD_VERS),) +Modules.symvers: $(SCST_DIR)/Modules.symvers + echo $(SCST_MOD_VERS) + cp $(SCST_DIR)/Modules.symvers kernel/ +else +.PHONY: Modules.symvers +endif + +# It's renamed in 2.6.18 +SCST_MOD_VERS := $(shell ls $(SCST_DIR)/Module.symvers 2>/dev/null) +ifneq ($(SCST_MOD_VERS),) +Module.symvers: $(SCST_DIR)/Module.symvers + cp $(SCST_DIR)/Module.symvers kernel/ +else +.PHONY: Module.symvers +endif + +clean: + $(MAKE) -C usr clean + $(MAKE) -C $(KDIR) SUBDIRS=$(shell pwd)/kernel clean + rm -f kernel/Modules.symvers kernel/Module.symvers + +extraclean: clean + +.PHONY: all mods progs install clean extraclean diff --git a/iscsi-scst/README b/iscsi-scst/README new file mode 100644 index 000000000..dffa1d80b --- /dev/null +++ b/iscsi-scst/README @@ -0,0 +1,115 @@ +iSCSI SCST target driver +======================== + +Version 0.9.6/XXXX, XX XXX 200X +------------------------------- + +This driver is a forked with all respects version of iSCSI Enterprise +Target (IET) (http://iscsitarget.sourceforge.net/) with updates to work +over SCST as well as with many improvements and bugfixes (see ChangeLog +file). The reason of fork is that the necessary changes are intrusive +and with the current IET merge policy, where only simple bugfix-like +patches, which doesn't touch the core code, could be merged, it is very +unlikely that they will be merged in the main IET trunk. + +To let it be installed and work at the same host together with IET +simultaneously all the driver's modules and files were renamed: + + * ietd.conf -> iscsi-scstd.conf + * ietadm -> iscsi-scst-adm + * ietd -> iscsi-scstd + * iscsi-target -> iscsi-scst + * iscsi-target.ko -> iscsi-scst.ko + +This version is compatible with SCST version 0.9.6 and higher. + +Tested on 2.6.21.1 kernel, but it should also work on other versions, +starting from 2.6.16.x. + +Installation +------------ + +Basically as in README-IET, where file names are changed as specified +above. + +To use full power of TCP zero-copy transmit functions, especially +dealing with user space supplied via scst_user module memory, iSCSI-SCST +needs to be notified when Linux networking finished data transmission. +Patch put_page_callback.patch provides such functionality. The +corresponding version of it should be applied on your kernel. This is +highly recommended, but not required. If it isn't applied, iSCSI-SCST +will work in the performance degraded mode, when for data transmission: + + - For in-kernel allocated memory (scst_vdisk and pass-through + handlers) usage of SGV cache on transmit path (READ-type commands) + will be disabled. The performance hit will be not big, performance + will still remain better, than for IET, because SGV cache will remain + used on receive path and IET doesn't have such feature. + + - For user space allocated memory (scst_user handler) all transmitted + data will be additionally copied into temporary TCP buffers. The + performance hit will be quite noticiable. + +If you need your own version of put_page_callback.patch for your custom +kernel, for which there is no prepared version, you can create it +yourself. For that it is only necessary: + +1. Apply the closest version of put_page_callback.patch on your kernel. +Resolve only failed hanks from include/ and net/core/utils.c, ignore +other failures. + +2. Grep net/ and replace everywhere, except in net/sunrpc/svc.c and +net/core/pktgen.c, put_page() by net_put_page() and get_page() by +net_get_page(). + +That's all. + +Usage +----- + +ISCSI parameters like iSNS, CHAP and target parameters are configured in +iscsi-scstd.conf. All LUN information is configured using the regular +SCST interface. The LUN information in iscsi-scstd.conf will be ignored. +This is because now responsibilities are divided (as it should be) +between the target driver (iSCSI-SCST) and the SCST core. The target +driver is responsible for handling targets and their parameters, SCST +core is responsible for handling backstorage. + +If you need to configure different LUs for different targets you should +create for each target group "Default_target_name", where target name +means name of the target, for example: +"Default_iqn.2007-05.com.example:storage.disk1.sys1.xyz", and add there +all necessary LUNs. Check SCST README file for details. + +Compilation options +------------------- + +There are the following compilation options, that could be commented +in/out in the kernel's module Makefile: + + - DEBUG - turns on some debugging code, including some logging. Makes + the driver considerably bigger and slower, producing large amount of + log data. + + - TRACING - turns on ability to log events. Makes the driver considerably + bigger and lead to some performance loss. + + - EXTRACHECKS - adds extra validity checks in the various places. + + - DEBUG_DIGEST_FAILURES - simulates digest failures in random places. + +Credits +------- + +Thanks to: + + * IET developers for IET + + * Ming Zhang for fixes + + * Krzysztof Blaszkowski for many fixes + + * Alexey Kuznetsov for comments and help in + debugging + +Vladislav Bolkhovitin , http://scst.sourceforge.net diff --git a/iscsi-scst/README-IET b/iscsi-scst/README-IET new file mode 100644 index 000000000..11009abd9 --- /dev/null +++ b/iscsi-scst/README-IET @@ -0,0 +1,105 @@ +Introduction +------------- +iSCSI Enterprise Target is for building an iSCSI storage system on +Linux. It is aimed at developing an iSCSI target satisfying enterprise +requirements. + +We borrow code from an Ardis iSCSI target (with respect to the GPL). + + +Installation +------------- +The iSCSI target requires a host running the Linux operating system +with a kernel version of 2.6.19 (2.6.14 - 2.6.18 kernels using +backward compatibility patches, see below) or newer. You need to +enable "Cryptographic API" under "Cryptographic options" in the kernel +config. You also need to enable "CRC32c CRC algorithm" if you use +header or data digests. They are the kernel options, CONFIG_CRYPTO and +CONFIG_CRYPTO_CRC32C, respectively. The user-space code requires +OpenSSL library (http://www.openssl.org/). + +The iSCSI target consists of kernel modules and a daemon. Since IET +is generally targetted at the latest stable mainline kernel, you might +need to apply a backward compatibility patch to compile it against +older kernel versions: + + patch -p0 < patches/compat-2.6.14-2.6.18.patch + +Compilation of the kernel modules require the path to the kernel +sources: + + make KSRC= + +The path can also be set by editing the main Makefile. If KSRC is +omitted, make program will try to locate the kernel sources for +current running kernel. Be sure to check whether it finds the right +kernel sources. + +This will build the modules, the daemon, and the control tool. To +install both, use: + + make KSRC= install + +The kernel modules will be install in the module directory of the +kernel. The daemon and the control tool will be installed as ietd and +ietadm under /usr/sbin. The boot script will be installed as +iscsi-targt under /etc/init.d. + +If you use Linux distribution that does not have /etc/init.d, the +boot script will not be installed. So you need to install it to an +appropriate directory manually. + + +Configuration +------------- +The daemon is configured via the configuration file /etc/ietd.conf. +See the man page and the example file for the current syntax. + +The ietadm utility is for managing IET software dynamically. You can +change the configurations of running targets. See the help message. + +The access control based on initiator address and target name patterns +is configured via two configuration files (/etc/initiators.allow and +/etc/initiators.deny). These files work like tcpd files +(/etc/hosts.allow and /etc/hosts.deny). This feature enables you to +hide a particular targets from some initiators. See the example files +for the supported expressions. You can change the configuration +dynamically. The modifications to the files become effective +immediately. + + +Starting +------------- +The target is not started automatically. So execute: + + /etc/init.d/iscsi-target start + +Note that you must edit the configuration file before starting the +target. + + +Stopping +------------- +Execute: + + /etc/init.d/iscsi-target stop + +Contact +------------- +Please send bug reports, comments, feature requests etc. to our +mailing list . + + +Developer Notes +---------------- +The central resource for IET development is the + mailing list. + +Our subversion repository can be found at: svn://svn.berlios.de/iscsitarget + +When submitting patches, please diff against the code in our repository's +trunk and adhere otherwise to the same rules that apply to Linux kernel +development, in particular the Linux kernel coding style +($KSRC/Documentation/CodingStyle) and the rules for submitting patches +($KSRC/Documentation/SubmittingPatches), i.e. please send patches inline as +plain text. diff --git a/iscsi-scst/ToDo b/iscsi-scst/ToDo new file mode 100644 index 000000000..7b5fa1253 --- /dev/null +++ b/iscsi-scst/ToDo @@ -0,0 +1,32 @@ + - Reimplement sessions parameters negotiation and storage. In IET + the negotiation isn't iSCSI RFC confirmed: it doesn't support ranges + of values and, more important, violates RFC in case when in the IET + config file some value for some parameter is set and a remote + initiator doesn't initiate the negotiation for this parameter or + declare its value. According to RFC, in this case IET shall use the + RFC-specified default value, but it will use config file specified + one instead. Looks like the implementation confuses IET config file + default values and iSCSI ones. The default values handling was fixed + in very dirty and hackish way, but ranges support remains unfixed. + Storage of set by user parameters should be reimplemented, so they + will be kept in iscsi-scstd, not in the kernel, as it is currently + done in IET's code. Using kernel to *only* store parameters is quite + questionable decision, especially considering that it leads to some + code duplication between kernel and user space, so remove all + parameters storage code from kernel. Remove target_param, leave only + one type of parameters, but separate processing of iSCSI RFC + parameters from local ones (currently they are mixed up). Also mixing + up conceptions of "key" and "param" in param_*() and struct + iscsi_key_ops functions makes the code hardly manageable, particularly + for adding support for ranges in the negotiation keys, so it needs to + be fixed as well. + + - Fix SNACK command handling. Currently it violates iSCSI RFC. + + - Consider better integration with TCP internals on receive path to + improve performance. + + - The target shouldn't crash/hang/etc. on initiators' misbehavior as + IET likes to do. + + - Minor "ToDo"'s spread in the code. diff --git a/iscsi-scst/doc/manpages/iscsi-scst-adm.8 b/iscsi-scst/doc/manpages/iscsi-scst-adm.8 new file mode 100644 index 000000000..4d30f8598 --- /dev/null +++ b/iscsi-scst/doc/manpages/iscsi-scst-adm.8 @@ -0,0 +1,240 @@ +.\" Automatically generated by Pod::Man 2.09 (Pod::Simple 3.04) +.\" +.\" Standard preamble: +.\" ======================================================================== +.de Sh \" Subsection heading +.br +.if t .Sp +.ne 5 +.PP +\fB\\$1\fR +.PP +.. +.de Sp \" Vertical space (when we can't use .PP) +.if t .sp .5v +.if n .sp +.. +.de Vb \" Begin verbatim text +.ft CW +.nf +.ne \\$1 +.. +.de Ve \" End verbatim text +.ft R +.fi +.. +.\" Set up some character translations and predefined strings. \*(-- will +.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left +.\" double quote, and \*(R" will give a right double quote. | will give a +.\" real vertical bar. \*(C+ will give a nicer C++. Capital omega is used to +.\" do unbreakable dashes and therefore won't be available. \*(C` and \*(C' +.\" expand to `' in nroff, nothing in troff, for use with C<>. +.tr \(*W-|\(bv\*(Tr +.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' +.ie n \{\ +. ds -- \(*W- +. ds PI pi +. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch +. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch +. ds L" "" +. ds R" "" +. ds C` "" +. ds C' "" +'br\} +.el\{\ +. ds -- \|\(em\| +. ds PI \(*p +. ds L" `` +. ds R" '' +'br\} +.\" +.\" If the F register is turned on, we'll generate index entries on stderr for +.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index +.\" entries marked with X<> in POD. Of course, you'll have to process the +.\" output yourself in some meaningful fashion. +.if \nF \{\ +. de IX +. tm Index:\\$1\t\\n%\t"\\$2" +.. +. nr % 0 +. rr F +.\} +.\" +.\" For nroff, turn off justification. Always turn off hyphenation; it makes +.\" way too many mistakes in technical documents. +.hy 0 +.if n .na +.\" +.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). +.\" Fear. Run. Save yourself. No user-serviceable parts. +. \" fudge factors for nroff and troff +.if n \{\ +. ds #H 0 +. ds #V .8m +. ds #F .3m +. ds #[ \f1 +. ds #] \fP +.\} +.if t \{\ +. ds #H ((1u-(\\\\n(.fu%2u))*.13m) +. ds #V .6m +. ds #F 0 +. ds #[ \& +. ds #] \& +.\} +. \" simple accents for nroff and troff +.if n \{\ +. ds ' \& +. ds ` \& +. ds ^ \& +. ds , \& +. ds ~ ~ +. ds / +.\} +.if t \{\ +. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" +. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' +. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' +. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' +. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' +. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' +.\} +. \" troff and (daisy-wheel) nroff accents +.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' +.ds 8 \h'\*(#H'\(*b\h'-\*(#H' +.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] +.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' +.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' +.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] +.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] +.ds ae a\h'-(\w'a'u*4/10)'e +.ds Ae A\h'-(\w'A'u*4/10)'E +. \" corrections for vroff +.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' +.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' +. \" for low resolution devices (crt and lpr) +.if \n(.H>23 .if \n(.V>19 \ +\{\ +. ds : e +. ds 8 ss +. ds o a +. ds d- d\h'-1'\(ga +. ds D- D\h'-1'\(hy +. ds th \o'bp' +. ds Th \o'LP' +. ds ae ae +. ds Ae AE +.\} +.rm #[ #] #H #V #F C +.\" ======================================================================== +.\" +.IX Title "ISCSI_SCST_ADM 1" +.TH ISCSI_SCST_ADM 8 "2007-05" "iSCSI SCST Target admin" "User Manuals" +.SH "NAME" +iscsi-scst-adm \- iSCSI SCST Target Administration Utility. +.SH "SYNOPSIS" +.IX Header "SYNOPSIS" +\&\fBiscsi-scst-adm \-\-op [operation] \-\-tid=[id] [\-\-sid [id]] [\-\-params [key=value,...]]\fR +.SH "DESCRIPTION" +.IX Header "DESCRIPTION" +iscsi-scst-adm is used to monitor and modify in real-time the iSCSI SCST Target +targets. +.SH "USAGE" +.IX Header "USAGE" +You'll have to get target and sessions ids from /proc/scsi_tgt/iscsi. +Some usage examples can be : +.PP +\&\fBiscsi-scst-adm \-\-op show \-\-tid=1\fR +.PP +display status of target 1 (see /proc/scsi_tgt/iscsi to get the matching target name) +.PP +\&\fBiscsi-scst-adm \-\-op new \-\-tid=2\fR +.PP +create dynamically a new target, numbered 2. \s-1CAUTION\s0 : the target will disappear if you restart iscsi-scstd, you'll have to edit /etc/iscsi-scstd.conf to make it permanent! +.SH "ERROR MESSAGES" +.IX Header "ERROR MESSAGES" +iscsi-scst-adm misses error messages. Look carefully the \s-1STDERR\s0 output : in case of error +it will send a 3 number error code, ending with \-1, for instance : +.PP +iscsi-scstd_request 203 3 \-1 +.SH "OPTIONS" +.IX Header "OPTIONS" +\&\fB\-\-op new \-\-tid=[id] \-\-params Name=[name]\fR +.PP +add a new target with [id]. [id] must not be zero. +.PP +\&\fB\-\-op delete \-\-tid=[id]\fR +.PP +delete specific target with [id]. The target must +have no active sessions. +.PP +\&\fB\-\-op show \-\-tid=[id]\fR +.PP +show target parameters of target with [id]. +.PP +\&\fB\-\-op show \-\-tid=[id] \-\-sid=[sid]\fR +.PP +show iSCSI parameters in effect for session [sid]. If +[sid] is \*(L"0\*(R" (zero), the configured parameters +will be displayed. +.PP +\&\fB\-\-op delete \-\-tid=[id] \-\-sid=[sid] \-\-cid=[cid]\fR +.PP +delete specific connection with [cid] in a session +with [sid] that the target with [id] has. +If the session has no connections after +the operation, the session will be deleted +automatically. +.PP +\&\fB\-\-op delete\fR +.PP +stop all activity. +.PP +\&\fB\-\-op update \-\-tid=[id] \-\-params=key1=value1,key2=value2,...\fR +.PP +change iSCSI \s-1SCST\s0 target parameters of specific +target with [id]. You can use parameters in iscsi-scstd.conf +as a key. +.PP +\&\fB\-\-op new \-\-tid=[id] \-\-user \-\-params=[user]=[name],Password=[pass]\fR +.PP +add a new account with [pass] for specific target. +[user] could be [IncomingUser] or [OutgoingUser]. +If you don't specify a target (omit \-\-tid option), +you add a new account for discovery sessions. +.PP +\&\fB\-\-op delete \-\-tid=[id] \-\-user \-\-params=[user]=[name]\fR +.PP +delete specific account having [name] of specific +target. [user] could be [IncomingUser] or +[OutgoingUser]. +If you don't specify a target (omit \-\-tid option), +you delete the account for discovery sessions. +.PP +\&\fB\-\-version\fR +.PP +display version and exit +.PP +\&\fB\-\-help\fR +.PP +display a list of available options and exits +.SH "KNOWN ISSUES" +.IX Header "KNOWN ISSUES" +.IP "\(bu" 4 +iscsi-scst-adm doesn't return any human-readable error message, only error codes. +.IP "\(bu" 4 +iscsi-scst-adm doesn't modify or read the /etc/iscsi-scstd.conf iscsi-scstd configuration file. +.IP "\(bu" 4 +iscsi-scst-adm can't use target names or aliases, only the tid found in /proc/scsi_tgt/iscsi. +.IP "\(bu" 4 +/proc/scsi_tgt/iscsi may list inactive sessions if the initiator doesn't logout properly. +.PP +Report bugs to . +.SH "FILES" +.IX Header "FILES" +/proc/scsi_tgt/iscsi +.SH "SEE ALSO" +.IX Header "SEE ALSO" +\&\fIiscsi-scstd\fR\|(8),\fIiscsi-scstd.conf\fR\|(5) +.\"man page written by Emmanuel Florac sid.id64 +.RE + +"need to shutdown target %s" +.RS +Value(s): target->name +.RE + +.B Errors + +"unable to create server socket (%s)!" +.RS +Value(s): strerror(errno) +.RE + +"unable to bind server socket (%s)!" +.RS +Value(s): strerror(errno) +.RE + +"unable to listen to server socket (%s)!" +.RS +Value(s): strerror(errno) +.RE + +"error reading /proc/iscsi/iscsi (%d)" +.RS +Value(s): errno +.RE + +"unable to create pid file" + +"starting daemon failed" + +"unable to lock pid file" + +"unable to open %s! (%s)" +.RS +Value(s): PROC_DEVICEDIR, strerror(errno) +.RE + +"unable to open %s! (%s)" +.RS +Value(s): PROC_TARGETDIR, strerror(errno) +.RE + +.B Debug Messages Level 0 + +"unknown user %s" +.RS +Value(s): value +.RE + +"login by %s failed" +.RS +Value(s): user->name +.RE + +"succesfull login by %s" +.RS +Value(s): user->name +.RE + +"connection request from %s" +.RS +Value(s): inet_ntoa(name.sin_addr) +.RE + +"connection closed" + +.RS +Value(s): target->id +.RE + +.B Debug Messages Level 1 + +"conn_take_fd: %u %u %u %Lx %d" +.RS +Value(s): conn->cid, conn->stat_sn, conn->exp_stat_sn, conn->sid.id64, fd +.RE + +"exp_cmd_sn: %d,%d" +.RS +Value(s): conn->exp_cmd_sn, req->cmd_sn +.RE + +"Login request (security negotiation): %d" +.RS +Value(s): conn->state +.RE + +"Login request (operational negotiation): %d" +.RS +Value(s): conn->state +.RE + +"Text request: %d" +.RS +Value(s): conn->state +.RE + +"close conn %u session %Lx target %u" +.RS +Value(s): conn_id, session_id, target_id +.RE + +"session_find_name: %s,%#Lx" +.RS +Value(s): iname, sid.id64 +.RE + +"session_find_id: %#Lx" +.RS +Value(s): sid +.RE + +"session_create: %#Lx" +.RS +Value(s): session->sid.id64 +.RE + +"session_close: %#Lx" +.RS +Value(s): session->sid.id64 +.RE + +"session_remove: %#Lx" +.RS +Value(s): session->sid.id64 +.RE + +"active target %d: %s" +.RS +Value(s): id, name +.RE + +"removing target %d" +.RS +Value(s): id +.RE + +"creaing target %u: %s" +.RS +Value(s): target_next_id, p +.RE + + +"target_find_name: %s" +.RS +Value(s): name +.RE + +"target_find_id: %u" +.RS +Value(s): id +.RE + +"target_remove: %u,%s" +.RS +Value(s): target->id, target->name +.RE + +.B Debug Messages Level 2 + +"%s %.16s" +.RS +Value(s): line, buf +.RE + +"BHS: (%p)" +.RS +Value(s): buf +.RE + +"AHS: (%p)" +.RS +Value(s): buf +.RE + +"Data: (%p)" +.RS +Value(s): buf +.RE + +.SH KNOWN ISSUES +Task attributes is incomplete (all tasks are treated as if they have the SIMPLE attribute.) + +SCSI task management is incomplete. + +NOP-In is incomplete. + +The target never sends a NOP-In of its own accord. + +Header and Data Digest in a discovery session are not implemented. + +Out-of-memory situation leads to the system crash (There are still some out-of-memory bugs.) + +.SH "SEE ALSO" +.BR iscsi-scstd.conf (5) diff --git a/iscsi-scst/doc/manpages/iscsi-scstd.conf.5 b/iscsi-scst/doc/manpages/iscsi-scstd.conf.5 new file mode 100644 index 000000000..b9768aa3b --- /dev/null +++ b/iscsi-scst/doc/manpages/iscsi-scstd.conf.5 @@ -0,0 +1,210 @@ +.\" Process this file with +.\" groff -man -Tascii iscsi-scstd.conf.5 +.\" +.TH "ISCSI_SCSTD.CONF" "5" "May 2007" "A. Lehmann, M. Zhang and A. Redlich" "File formats" +.SH "NAME" +/etc/iscsi-scstd.conf \- configuration for iSCSI SCST Target Daemon +.SH "SYNOPSIS" +/etc/iscsi-scstd.conf +.SH "DESCRIPTION" +/etc/iscsi-scstd.conf contains configuration information for the +.B iscsi-scstd (8) +command. This is the place, where you configure your iSCSI targets and daemon parameters. +.P +Only lines starting with `#' are ignored. Putting '#' in the middle of a line is disallowed. A line may be extended across multiple lines by making the last character a backslash. +.P +The "Yes" and "No" for parameter values are case sensitive. The parameter names are case insensitive. +.P +The file consists of a global part and zero or more "Target" stanzas. Everything until the first target definition belongs to the global configuration. + +Here is an example: + +IncomingUser joe secret +.br +OutgoingUser jack secret2 + +Target iqn.2007\-05.com.example:storage.disk2.sys1.xyz + IncomingUser jim othersecret + OutgoingUser james yetanothersecret + Alias Test + HeaderDigest None + DataDigest None + MaxConnections 1 + InitialR2T No + ImmediateData Yes + MaxRecvDataSegmentLength 1048576 + MaxXmitDataSegmentLength 1048576 + MaxBurstLength 1048576 + FirstBurstLength 1048576 + DefaultTime2Wait 2 + DefaultTime2Retain 20 + MaxOutstandingR2T 20 + DataPDUInOrder Yes + DataSequenceInOrder Yes + ErrorRecoveryLevel 0 +.P +Stanzas start with the word "Target" and the target name. This name must be a globally unique name, as defined by the iSCSI standard : the "iSCSI Qualified Name". The daemon brings the targets up in the order listed. +.SH "GLOBAL OPTIONS" +Global Options are case sensitive. +.TP +.B [IncomingUser ] +The +.I +and +.I +used during discovery sessions to authenticate iSCSI initiators. Several of those can be specified for discovery. If no +.B IncomingUser +is specified, any initiator is allowed to open a discovery session. +.RS +HINT: RFC 3720 requires +.I +to be 12 characters long. This is enforced e.g. by MS Initiator. +.RE +.TP +.B [OutgoingUser ] +The +.I +and +.I +used during discovery sessions to authenticate the target to initiators. Only one outgoing +.I / +combination may be specified. +.RS +HINT: RFC 3720 requires +.I +to be 12 characters long. This is enforced e.g. by MS Initiator. +.RE +.TP +.B Target iqn..[:] +A target definition and the target name. The targets name (the +.B iSCSI Qualified Name +) must be a globally unique name (as defined by the iSCSI standard) and has to start with +.I iqn +followed by a single dot. The EUI\-64 form is not supported. +.I +is the date (year and month) at which the domain is valid. This has to be followed by a single dot and the reversed domain name. +The optional +.I +\- which is freely selectable \- has to be separated by a single colon. For further details please check the iSCSI spec. + +Here is an example: + +Target iqn.2007\-05.com.example.host:storage.disk2.sys1.xyz +.SH "TARGET OPTIONS" +Target options are also case sensitive. +.TP +.B [IncomingUser ] +The +.I +and +.I +used to authenticate the iSCSI initiators to this target. It may be different from the username and password in section GLOBAL OPTIONS, which is used for discovery. If you omit the +.B IncomingUser +Option, connections are allowed without authentication. A +.I +has to be provided, if there is a +.I +given. Specifying several different +.B IncomingUser +accounts is supported. +.TP +.B [OutgoingUser ] +The +.I +and +.I +used to authenticate this iSCSI target to initiators. Only one +.B +OutgoingUser +per target is supported. It may be different from the username and password in section GLOBAL OPTIONS, which is used for discovery. A +.I +has to be provided, if there is a +.I +given. +.TP +.B [Alias ] +This assigns an optional +.I +to the target. +.TP +.B [HeaderDigest ] +Optional. If set to "CRC32C" and the initiator is configured accordingly, the integrity of an iSCSI PDU's header segments will be protected by a CRC32C checksum. The default is "None". Note that header digests are not supported during discovery sessions. +.TP +.B [DataDigest ] +Optional. If set to "CRC32C" and the initiator is configured accordingly, the integrity of an iSCSI PDU's data segment will be protected by a CRC32C checksum. The default is "None". Note that data digests are not supported during discovery sessions. +.TP +.B [MaxConnections ] +Optional. Has to be set to "1" (in words: one), which is also the default. +.TP +.B [InitialR2T ] +Optional. If set to "Yes", the initiator has to wait for the target to solicit SCSI data before sending it. Setting it to "No" (default) allows the initiator to send a burst of +.B FirstBurstLength +bytes unsolicited right after and/or (depending on the setting of +.B ImmediateData +) together with the command. Thus setting it to "No" may improve performance. +.TP +.B [ImmediateData ] +Optional. This allows the initiator to append unsolicited data to a command. To achieve better performance, this should be set to "Yes". Which is the default. +.TP +.B [MaxRecvDataSegmentLength ] +Optional. Sets the maximum data segment length that can be received. The +.I +should be set to multiples of PAGE_SIZE. Configuring too large values may lead to problems allocating sufficient memory, which in turn may lead to SCSI commands timing out at the initiator host. The default value is the highes possible for current platform (1 or 2 MB). +.TP +.B [MaxXmitDataSegmentLength ] +Optional. Sets the maximum data segment length that can be sent. The +.I +actually used is the minimum of +.B MaxXmitDataSegmentLength +and the +.B MaxRecvDataSegmentLength +announced by the initiator. The +.I +should be set to multiples of PAGE_SIZE. Configuring too large values may lead to problems allocating sufficient memory, which in turn may lead to SCSI commands timing out at the initiator host. The default value is the highes possible for current platform (1 or 2 MB). +.TP +.B [MaxBurstLength ] +Optional. Sets the maximum amount of either unsolicited or solicited data the initiator may send in a single burst. Any amount of data exceeding this value must be explicitly solicited by the target. The +.I +should be set to multiples of PAGE_SIZE. Configuring too large values may lead to problems allocating sufficient memory, which in turn may lead to SCSI commands timing out at the initiator host. The default value is the highes possible for current platform (1 or 2 MB). +.TP +.B [FirstBurstLength ] +Optional. Sets the amount of unsolicited data the initiator may transmit in the first burst of a transfer either with and/or right after the command, depending on the settings of +.B InitialR2T +and +.B ImmediateData +. +.I +should be set to multiples of PAGE_SIZE. Configuring too large values may lead to problems allocating sufficient memory, which in turn may lead to SCSI commands timing out at the initiator host. The default value is the highes possible for current platform (1 or 2 MB). +.TP +.B [DefaultTime2Wait ] +Currently not supported. +.TP +.B [DefaultTime2Retain ] +Currently not supported. +.TP +.B [MaxOutstandingR2T ] +Optional. Controls the maximum number of data transfers the target may request at once, each of up to +.B MaxBurstLength +bytes. The default is 20. +.TP +.B [DataPDUInOrder ] +Optional. Has to be set to "Yes" \- which is also the default. +.TP +.B [DataSequenceInOrder ] +Optional. Has to be set to "Yes" \- which is also the default. +.TP +.B [ErrorRecoveryLevel ] +Optional. Has to be set to "0" (in words: zero), which is also the default. +.TP +.B [QueuedCommands ] +Optional. This parameter defines a window of commands an initiator may send and that will be buffered by the target. Depending on your hardware and your (expected) workload, the +.I value +may be carefully adjusted. The default value of 32 should be sufficient for most purposes. +.SH "KNOWN BUGS/LIMITATIONS" +Currently (as of 0.4.11) not all iSCSI target parameters are used. Header and data digests are not supported during discovery sessions. +.SH "SEE ALSO" +.B iscsi-scstd (8) +.TP +You should have a look at +.B RFC 3720 +for all the glory details. diff --git a/iscsi-scst/etc/initd/initd b/iscsi-scst/etc/initd/initd new file mode 100644 index 000000000..5ff1a2a75 --- /dev/null +++ b/iscsi-scst/etc/initd/initd @@ -0,0 +1,67 @@ +#!/bin/bash +# +# Start the iSCSI Enterprise Target. +# + +PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin +MEM_SIZE=1048576 + +configure_memsize() +{ + if [ -e /proc/sys/net/core/wmem_max ]; then + echo ${MEM_SIZE} > /proc/sys/net/core/wmem_max + fi + + if [ -e /proc/sys/net/core/rmem_max ]; then + echo ${MEM_SIZE} > /proc/sys/net/core/rmem_max + fi + + if [ -e /proc/sys/net/core/wmem_default ]; then + echo ${MEM_SIZE} > /proc/sys/net/core/wmem_default + fi + + if [ -e /proc/sys/net/core/rmem_default ]; then + echo ${MEM_SIZE} > /proc/sys/net/core/rmem_default + fi + + if [ -e /proc/sys/net/ipv4/tcp_mem ]; then + echo "${MEM_SIZE} ${MEM_SIZE} ${MEM_SIZE}" > /proc/sys/net/ipv4/tcp_mem + fi + + if [ -e /proc/sys/net/ipv4/tcp_rmem ]; then + echo "${MEM_SIZE} ${MEM_SIZE} ${MEM_SIZE}" > /proc/sys/net/ipv4/tcp_rmem + fi + + if [ -e /proc/sys/net/ipv4/tcp_wmem ]; then + echo "${MEM_SIZE} ${MEM_SIZE} ${MEM_SIZE}" > /proc/sys/net/ipv4/tcp_wmem + fi +} + +start_server() +{ +# configure_memsize + modprobe -q crc32c + modprobe iscsi-scst + /usr/local/sbin/iscsi-scstd +} + +stop_server() +{ + killall iscsi-scstd + rmmod -w iscsi-scst +} + +case "$1" in + start) + start_server + ;; + stop) + stop_server + ;; + *) + echo "Usage: {start|stop}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/iscsi-scst/etc/initd/initd.debian b/iscsi-scst/etc/initd/initd.debian new file mode 100644 index 000000000..e3d4f00bb --- /dev/null +++ b/iscsi-scst/etc/initd/initd.debian @@ -0,0 +1,133 @@ +#!/bin/sh +# +# chkconfig: - 39 35 +# description: Starts and stops the iSCSI target +# debianized start-stop script + +PID_FILE=/var/run/iscsi-scst.pid +CONFIG_FILE=/etc/iscsi-scstd.conf +DAEMON=/usr/local/sbin/iscsi-scstd + +PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin + +# Don't touch this "memsize thingy" unless you are blessed +# with knowledge about it. +MEM_SIZE=1048576 + +configure_memsize() +{ + if [ -e /proc/sys/net/core/wmem_max ]; then + echo ${MEM_SIZE} > /proc/sys/net/core/wmem_max + fi + + if [ -e /proc/sys/net/core/rmem_max ]; then + echo ${MEM_SIZE} > /proc/sys/net/core/rmem_max + fi + + if [ -e /proc/sys/net/core/wmem_default ]; then + echo ${MEM_SIZE} > /proc/sys/net/core/wmem_default + fi + + if [ -e /proc/sys/net/core/rmem_default ]; then + echo ${MEM_SIZE} > /proc/sys/net/core/rmem_default + fi + + if [ -e /proc/sys/net/ipv4/tcp_mem ]; then + echo "${MEM_SIZE} ${MEM_SIZE} ${MEM_SIZE}" > /proc/sys/net/ipv4/tcp_mem + fi + + if [ -e /proc/sys/net/ipv4/tcp_rmem ]; then + echo "${MEM_SIZE} ${MEM_SIZE} ${MEM_SIZE}" > /proc/sys/net/ipv4/tcp_rmem + fi + + if [ -e /proc/sys/net/ipv4/tcp_wmem ]; then + echo "${MEM_SIZE} ${MEM_SIZE} ${MEM_SIZE}" > /proc/sys/net/ipv4/tcp_wmem + fi +} + +RETVAL=0 + +iscsi_scstd_start() +{ + echo -n "Starting iSCSI enterprise target service: " +# configure_memsize + modprobe -q crc32c + modprobe iscsi-scst + start-stop-daemon --start --exec $DAEMON --quiet + RETVAL=$? + if [ $RETVAL == "0" ]; then + echo "succeeded." + else + echo "failed." + fi +} + +iscsi_scstd_stop() +{ + echo -n "Stopping iSCSI enterprise target service: " + start-stop-daemon --stop --quiet --exec $DAEMON --pidfile $PID_FILE + RETVAL=$? + if [ $RETVAL == "0" ]; then + echo "succeeded." + else + echo "failed." + fi + # ugly, but pid file is not removed ba iscsi-scstd + rm -f $PID_FILE + + echo -n "Removing iSCSI enterprise target modules: " + rmmod -w iscsi-scst + RETVAL=$? + modprobe -r crc32c 2>/dev/null + if [ $RETVAL == "0" ]; then + echo "succeeded." + else + echo "failed." + exit 1 + fi +} + +case "$1" in + start) + iscsi_scstd_start + ;; + stop) + iscsi_scstd_stop + ;; + restart) + iscsi_scstd_stop + sleep 1 + iscsi_scstd_start + ;; + status) + PID=`pidof iscsi-scstd` + if [ $PID ]; then + echo "iSCSI enterprise target is running at pid $PID" + else + echo "no iSCSI enterprise target found!" + exit 1 + fi + ;; + dump) + DUMP=`tempfile -p iscsi-scstd` + RETVAL=$? + if [ $RETVAL != "0" ]; then + echo "Failed to create dump file $DUMP" + exit 1 + fi + iscsi-scst-adm --mode dump --all >$DUMP + RETVAL=$? + if [ $RETVAL != "0" ]; then + echo "Error dumping config from daemon" + rm $DUMP + exit 1 + fi + mv -u $DUMP $CONFIG_FILE + echo "Config dumped to $CONFIG_FILE" + ;; + *) + echo $"Usage: $0 {start|stop|restart|status|dump}" + exit 1 +esac + +exit 0 diff --git a/iscsi-scst/etc/initd/initd.gentoo b/iscsi-scst/etc/initd/initd.gentoo new file mode 100644 index 000000000..590eb015e --- /dev/null +++ b/iscsi-scst/etc/initd/initd.gentoo @@ -0,0 +1,71 @@ +#!/sbin/runscript +# +# Start the iSCSI Enterprise Target. +# + +PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin +MEM_SIZE=1048576 + +depend() +{ + use net +} + +configure_memsize() +{ + if [ -e /proc/sys/net/core/wmem_max ]; then + echo ${MEM_SIZE} > /proc/sys/net/core/wmem_max + fi + + if [ -e /proc/sys/net/core/rmem_max ]; then + echo ${MEM_SIZE} > /proc/sys/net/core/rmem_max + fi + + if [ -e /proc/sys/net/core/wmem_default ]; then + echo ${MEM_SIZE} > /proc/sys/net/core/wmem_default + fi + + if [ -e /proc/sys/net/core/rmem_default ]; then + echo ${MEM_SIZE} > /proc/sys/net/core/rmem_default + fi + + if [ -e /proc/sys/net/ipv4/tcp_mem ]; then + echo "${MEM_SIZE} ${MEM_SIZE} ${MEM_SIZE}" > /proc/sys/net/ipv4/tcp_mem + fi + + if [ -e /proc/sys/net/ipv4/tcp_rmem ]; then + echo "${MEM_SIZE} ${MEM_SIZE} ${MEM_SIZE}" > /proc/sys/net/ipv4/tcp_rmem + fi + + if [ -e /proc/sys/net/ipv4/tcp_wmem ]; then + echo "${MEM_SIZE} ${MEM_SIZE} ${MEM_SIZE}" > /proc/sys/net/ipv4/tcp_wmem + fi +} + +start_server() +{ +# configure_memsize + modprobe -q crc32c + modprobe iscsi-scst + /usr/local/sbin/iscsi-scstd +} + +stop_server() +{ + killall iscsi-scstd + rmmod -w iscsi-scst +} + +start() +{ + ebegin "Starting iscsi" + start_server + eend 0 +} + +stop() +{ + ebegin "Stopping iscsi" + stop_server + eend 0 +} diff --git a/iscsi-scst/etc/initd/initd.redhat b/iscsi-scst/etc/initd/initd.redhat new file mode 100644 index 000000000..b46d40112 --- /dev/null +++ b/iscsi-scst/etc/initd/initd.redhat @@ -0,0 +1,130 @@ +#!/bin/sh +# +# chkconfig: - 39 35 +# description: Starts and stops the iSCSI target +# +# pidfile: /var/run/iscsi-scstd.pid +# config: /etc/iscsi-scstd.conf + +# Source function library. +if [ -f /etc/init.d/functions ] ; then + . /etc/init.d/functions +elif [ -f /etc/rc.d/init.d/functions ] ; then + . /etc/rc.d/init.d/functions +else + exit 0 +fi + +PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin +MEM_SIZE=1048576 + +configure_memsize() +{ + if [ -e /proc/sys/net/core/wmem_max ]; then + echo ${MEM_SIZE} > /proc/sys/net/core/wmem_max + fi + + if [ -e /proc/sys/net/core/rmem_max ]; then + echo ${MEM_SIZE} > /proc/sys/net/core/rmem_max + fi + + if [ -e /proc/sys/net/core/wmem_default ]; then + echo ${MEM_SIZE} > /proc/sys/net/core/wmem_default + fi + + if [ -e /proc/sys/net/core/rmem_default ]; then + echo ${MEM_SIZE} > /proc/sys/net/core/rmem_default + fi + + if [ -e /proc/sys/net/ipv4/tcp_mem ]; then + echo "${MEM_SIZE} ${MEM_SIZE} ${MEM_SIZE}" > /proc/sys/net/ipv4/tcp_mem + fi + + if [ -e /proc/sys/net/ipv4/tcp_rmem ]; then + echo "${MEM_SIZE} ${MEM_SIZE} ${MEM_SIZE}" > /proc/sys/net/ipv4/tcp_rmem + fi + + if [ -e /proc/sys/net/ipv4/tcp_wmem ]; then + echo "${MEM_SIZE} ${MEM_SIZE} ${MEM_SIZE}" > /proc/sys/net/ipv4/tcp_wmem + fi +} + +RETVAL=0 + +start() +{ + echo -n "Starting iSCSI target service: " +# configure_memsize + modprobe -q crc32c + modprobe iscsi-scst + daemon /usr/local/sbin/iscsi-scstd + # -d 0xFFFF + RETVAL=$? + echo + return $RETVAL +} + +stop() +{ + echo -n "Stopping iSCSI target service: " +# iscsi-scst-adm --op delete + killall iscsi-scstd + rmmod -w iscsi-scst + RETVAL=$? + modprobe -r crc32c 2>/dev/null + if [ $RETVAL == "0" ]; then + echo_success + else + echo_failure + fi + echo + return $RETVAL +} + +restart() +{ + stop + start +} + +condrestart() +{ + PID=`pidofproc iscsi-scstd` + if [ $PID ]; then + restart + fi +} + +status() +{ + PID=`pidofproc iscsi-scstd` + if [ ! $PID ]; then + echo "iSCSI target stopped" + exit 1 + else + echo "iscsi-scstd (pid $PID) is running..." + fi +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + restart + ;; + condrestart) + condrestart + ;; + status) + status + ;; + *) + echo $"Usage: $0 {start|stop|restart|status}" + exit 1 +esac + +exit 0 diff --git a/iscsi-scst/etc/initiators.allow b/iscsi-scst/etc/initiators.allow new file mode 100644 index 000000000..0cef10d7b --- /dev/null +++ b/iscsi-scst/etc/initiators.allow @@ -0,0 +1,7 @@ +# Some examples +# +#iqn.2001-04.com.example:storage.disk1.sys1.xyz 192.168.22.2, 192.168.3.8 +#iqn.2001-04.com.example:storage.disk1.sys2.xyz [3ffe:302:11:1:211:43ff:fe31:5ae2], [3ffe:505:2:1::]/64 +#iqn.2001-04.com.example:storage.disk1.sys3.xyz ALL +#iqn.2001-04.com.example:storage.disk1.sys4.xyz 192.168.22.3 +#ALL 192.168.22.4 diff --git a/iscsi-scst/etc/initiators.deny b/iscsi-scst/etc/initiators.deny new file mode 100644 index 000000000..72fdf0bf3 --- /dev/null +++ b/iscsi-scst/etc/initiators.deny @@ -0,0 +1,14 @@ +# The semantics are: +# * By default, every initiator can see and connect to all targets. +# +# * Deny for some or every initiator access to one or all targets, +# by adding a line to this file. +# +# * Then allow some named initiators access to selected targets, +# by adding lines to initiators.allow +# +# Some examples +#iqn.2001-04.com.example:storage.disk1.sys1.xyz ALL +#iqn.2001-04.com.example:storage.disk1.sys2.xyz 192.168.12.2, 192.168.3.0/24, 192.167.1.16/28 +#iqn.2001-04.com.example:storage.disk1.sys4.xyz [3ffe:302:11:1:211:43ff:fe31:5ae2], [3ffe:505:2:1::]/64 +#ALL ALL diff --git a/iscsi-scst/etc/iscsi-scstd.conf b/iscsi-scst/etc/iscsi-scstd.conf new file mode 100644 index 000000000..6488e93bf --- /dev/null +++ b/iscsi-scst/etc/iscsi-scstd.conf @@ -0,0 +1,55 @@ +# Example iscsi target configuration +# +# Everything until the first target definition belongs +# to the global configuration. +# Right now this is only the user configuration used +# during discovery sessions. "IncomingUser" specifies credentials the +# initiator has to provide - several of these are supported. If mutual +# CHAP shall be employed, "OutgoingUser" specifies the user/pass +# combination the target will provide - only one is supported. +# Leave them alone (keep them commented out) if you don't want to use +# authentication for discovery sessions. + +#iSNSServer 192.168.1.16 +#iSNSAccessControl No + +#IncomingUser joe secret +#OutgoingUser jack 12charsecret + +# Targets definitions start with "Target" and the target name. +# The target name must be a globally unique name, the iSCSI +# standard defines the "iSCSI Qualified Name" as follows: +# +# iqn.yyyy-mm.[:identifier] +# +# "yyyy-mm" is the date at which the domain is valid and the identifier +# is freely selectable. For further details please check the iSCSI spec. + +Target iqn.2007-05.com.example:storage.disk2.sys1.xyz + # Users, who can access this target. The same rules as for discovery + # users apply here. + # Leave them alone if you don't want to use authentication. + #IncomingUser joe secret + #OutgoingUser jim 12charpasswd + # Alias name for this target + # Alias Test + # various iSCSI parameters + # (not all are used right now, see also iSCSI spec for details) + #MaxConnections 1 + #InitialR2T No + #ImmediateData Yes + #MaxRecvDataSegmentLength 1048576 + #MaxXmitDataSegmentLength 1048576 + #MaxBurstLength 1048576 + #FirstBurstLength 1048576 + #DefaultTime2Wait 2 + #DefaultTime2Retain 20 + #MaxOutstandingR2T 20 + #DataPDUInOrder Yes + #DataSequenceInOrder Yes + #ErrorRecoveryLevel 0 + #HeaderDigest CRC32C,None + #DataDigest CRC32C,None + # various target parameters + #QueuedCommands 32 + diff --git a/iscsi-scst/include/iscsi_u.h b/iscsi-scst/include/iscsi_u.h new file mode 100644 index 000000000..4f883785a --- /dev/null +++ b/iscsi-scst/include/iscsi_u.h @@ -0,0 +1,137 @@ +#ifndef _ISCSI_U_H +#define _ISCSI_U_H + +#ifndef __KERNEL__ +#include +#endif + +#define ISCSI_VERSION_STRING "0.9.6/0.4.15r133" + +/* The maximum length of 223 bytes in the RFC. */ +#define ISCSI_NAME_LEN 256 + +#define ISCSI_LISTEN_PORT 3260 + +#define SCSI_ID_LEN 24 + +#ifndef aligned_u64 +#define aligned_u64 unsigned long long __attribute__((aligned(8))) +#endif + +struct target_info { + u32 tid; + char name[ISCSI_NAME_LEN]; +}; + +struct session_info { + u32 tid; + + aligned_u64 sid; + char initiator_name[ISCSI_NAME_LEN]; + char user_name[ISCSI_NAME_LEN]; + u32 exp_cmd_sn; + u32 max_cmd_sn; +}; + +#define DIGEST_ALL (DIGEST_NONE | DIGEST_CRC32C) +#define DIGEST_NONE (1 << 0) +#define DIGEST_CRC32C (1 << 1) + +struct conn_info { + u32 tid; + aligned_u64 sid; + + u32 cid; + u32 stat_sn; + u32 exp_stat_sn; + int header_digest; + int data_digest; + int fd; +}; + +enum { + key_initial_r2t, + key_immediate_data, + key_max_connections, + key_max_recv_data_length, + key_max_xmit_data_length, + key_max_burst_length, + key_first_burst_length, + key_default_wait_time, + key_default_retain_time, + key_max_outstanding_r2t, + key_data_pdu_inorder, + key_data_sequence_inorder, + key_error_recovery_level, + key_header_digest, + key_data_digest, + key_ofmarker, + key_ifmarker, + key_ofmarkint, + key_ifmarkint, + session_key_last, +}; + +enum { + key_queued_cmnds, + target_key_last, +}; + +enum { + key_session, + key_target, +}; + +struct iscsi_param_info { + u32 tid; + aligned_u64 sid; + + u32 param_type; + u32 partial; + + u32 session_param[session_key_last]; + u32 target_param[target_key_last]; +}; + +enum iscsi_event_state { + E_CONN_CLOSE, +}; + +struct iscsi_event { + u32 tid; + aligned_u64 sid; + u32 cid; + u32 state; +}; + +#define DEFAULT_NR_QUEUED_CMNDS 32 +#define MIN_NR_QUEUED_CMNDS 1 +#define MAX_NR_QUEUED_CMNDS 256 + +#define MAX_DATA_SEG_LEN (4096/sizeof(struct iovec)*4096) + +#define NETLINK_ISCSI_SCST 25 + +#define ADD_TARGET _IOW('i', 0, struct target_info) +#define DEL_TARGET _IOW('i', 1, struct target_info) +#define ADD_SESSION _IOW('i', 2, struct session_info) +#define DEL_SESSION _IOW('i', 3, struct session_info) +#define GET_SESSION_INFO _IOWR('i', 4, struct session_info) +#define ADD_CONN _IOW('i', 5, struct conn_info) +#define DEL_CONN _IOW('i', 6, struct conn_info) +#define GET_CONN_INFO _IOWR('i', 7, struct conn_info) +#define ISCSI_PARAM_SET _IOW('i', 8, struct iscsi_param_info) +#define ISCSI_PARAM_GET _IOWR('i', 9, struct iscsi_param_info) + +static inline int iscsi_is_key_declarative(int key) +{ + switch(key) + { + case key_max_xmit_data_length: + return 1; + default: + return 0; + } +} + +#endif diff --git a/iscsi-scst/kernel/Makefile b/iscsi-scst/kernel/Makefile new file mode 100644 index 000000000..e701c775d --- /dev/null +++ b/iscsi-scst/kernel/Makefile @@ -0,0 +1,25 @@ +# +# Makefile for the Linux kernel device drivers. +# +# Note! Dependencies are done automagically by 'make dep', which also +# removes any old dependencies. DON'T put your own dependencies here +# unless it's something special (not a .c file). +# +# Note 2! The CFLAGS definitions are now in the main makefile. + +#SCST_INC_DIR := /usr/local/include/scst +SCST_INC_DIR := $(SUBDIRS)/../../scst/include + +EXTRA_CFLAGS += -I$(src)/../include -I$(SCST_INC_DIR) +# -Wextra -Wno-unused-parameter + +EXTRA_CFLAGS += -DEXTRACHECKS +#EXTRA_CFLAGS += -DTRACING +EXTRA_CFLAGS += -DDEBUG -g + +#EXTRA_CFLAGS += -DDEBUG_DIGEST_FAILURES + +obj-m += iscsi-scst.o +iscsi-scst-objs := iscsi.o nthread.o config.o digest.o \ + conn.o session.o target.o event.o param.o + diff --git a/iscsi-scst/kernel/config.c b/iscsi-scst/kernel/config.c new file mode 100644 index 000000000..59ad8cd65 --- /dev/null +++ b/iscsi-scst/kernel/config.c @@ -0,0 +1,526 @@ +/* + * (C) 2004 - 2005 FUJITA Tomonori + * + * This code is licenced under the GPL. + */ + +#include + +#include "iscsi.h" + +#define ISCSI_PROC_VERSION_NAME "version" + +#if defined(DEBUG) || defined(TRACING) + +#define ISCSI_PROC_LOG_ENTRY_NAME "trace_level" + +#include + +static struct scst_proc_log iscsi_proc_local_trace_tbl[] = +{ + { TRACE_D_READ, "d_read" }, + { TRACE_D_WRITE, "d_write" }, + { TRACE_CONN_OC, "conn" }, + { TRACE_D_IOV, "iov" }, + { TRACE_D_DUMP_PDU, "pdu" }, + { 0, NULL } +}; + +static int iscsi_log_info_show(struct seq_file *seq, void *v) +{ + int res = 0; + + TRACE_ENTRY(); + + res = scst_proc_log_entry_read(seq, trace_flag, + iscsi_proc_local_trace_tbl); + + TRACE_EXIT_RES(res); + return res; +} + +static int iscsi_proc_log_entry_write(struct file *file, const char __user *buf, + size_t length, loff_t *off) +{ + int res = 0; + + TRACE_ENTRY(); + + res = scst_proc_log_entry_write(file, buf, length, &trace_flag, + ISCSI_DEFAULT_LOG_FLAGS, iscsi_proc_local_trace_tbl); + + TRACE_EXIT_RES(res); + return res; +} +#endif + +static int iscsi_version_info_show(struct seq_file *seq, void *v) +{ + TRACE_ENTRY(); + + seq_printf(seq, "%s\n", ISCSI_VERSION_STRING); + +#ifdef EXTRACHECKS + seq_printf(seq, "EXTRACHECKS\n"); +#endif + +#ifdef TRACING + seq_printf(seq, "TRACING\n"); +#endif + +#ifdef DEBUG + seq_printf(seq, "DEBUG\n"); +#endif + +#ifdef DEBUG_DIGEST_FAILURES + seq_printf(seq, "DEBUG_DIGEST_FAILURES\n"); +#endif + + TRACE_EXIT(); + return 0; +} + +static struct scst_proc_data iscsi_version_proc_data = { + SCST_DEF_RW_SEQ_OP(NULL) + .show = iscsi_version_info_show, +}; + +#if defined(DEBUG) || defined(TRACING) +static struct scst_proc_data iscsi_log_proc_data = { + SCST_DEF_RW_SEQ_OP(iscsi_proc_log_entry_write) + .show = iscsi_log_info_show, +}; +#endif + +static __init int iscsi_proc_log_entry_build(struct scst_tgt_template *templ) +{ + int res = 0; + struct proc_dir_entry *p, *root; + + TRACE_ENTRY(); + + root = scst_proc_get_tgt_root(templ); + if (root) { + p = scst_create_proc_entry(root, ISCSI_PROC_VERSION_NAME, + &iscsi_version_proc_data); + if (p == NULL) { + PRINT_ERROR_PR("Not enough memory to register " + "target driver %s entry %s in /proc", + templ->name, ISCSI_PROC_VERSION_NAME); + res = -ENOMEM; + goto out; + } + +#if defined(DEBUG) || defined(TRACING) + /* create the proc file entry for the device */ + iscsi_log_proc_data.data = (void *)templ->name; + p = scst_create_proc_entry(root, ISCSI_PROC_LOG_ENTRY_NAME, + &iscsi_log_proc_data); + if (p == NULL) { + PRINT_ERROR_PR("Not enough memory to register " + "target driver %s entry %s in /proc", + templ->name, ISCSI_PROC_LOG_ENTRY_NAME); + res = -ENOMEM; + goto out_remove_ver; + } +#endif + } + +out: + TRACE_EXIT_RES(res); + return res; + +#if defined(DEBUG) || defined(TRACING) +out_remove_ver: + remove_proc_entry(ISCSI_PROC_VERSION_NAME, root); + goto out; +#endif +} + +static void iscsi_proc_log_entry_clean(struct scst_tgt_template *templ) +{ + struct proc_dir_entry *root; + + TRACE_ENTRY(); + + root = scst_proc_get_tgt_root(templ); + if (root) { +#if defined(DEBUG) || defined(TRACING) + remove_proc_entry(ISCSI_PROC_LOG_ENTRY_NAME, root); +#endif + remove_proc_entry(ISCSI_PROC_VERSION_NAME, root); + } + + TRACE_EXIT(); + return; +} + +struct proc_entries { + const char *name; + struct file_operations *fops; +}; + +static struct proc_entries iscsi_proc_entries[] = +{ + {"session", &session_seq_fops}, +}; + +static struct proc_dir_entry *proc_iscsi_dir; + +void iscsi_procfs_exit(void) +{ + int i; + + if (!proc_iscsi_dir) + return; + + for (i = 0; i < ARRAY_SIZE(iscsi_proc_entries); i++) + remove_proc_entry(iscsi_proc_entries[i].name, proc_iscsi_dir); + + iscsi_proc_log_entry_clean(&iscsi_template); +} + +int __init iscsi_procfs_init(void) +{ + int i, err = 0; + struct proc_dir_entry *ent; + + proc_iscsi_dir = scst_proc_get_tgt_root(&iscsi_template); + if (proc_iscsi_dir == NULL) { + err = -ESRCH; + goto out; + } + + proc_iscsi_dir->owner = THIS_MODULE; + + err = iscsi_proc_log_entry_build(&iscsi_template); + if (err < 0) + goto out; + + for (i = 0; i < ARRAY_SIZE(iscsi_proc_entries); i++) { + ent = create_proc_entry(iscsi_proc_entries[i].name, 0, proc_iscsi_dir); + if (ent) + ent->proc_fops = iscsi_proc_entries[i].fops; + else { + err = -ENOMEM; + goto err; + } + } + +out: + return err; + +err: + if (proc_iscsi_dir) + iscsi_procfs_exit(); + goto out; +} + +/* target_mutex supposed to be locked */ +static int get_conn_info(struct iscsi_target *target, unsigned long ptr) +{ + int err; + struct iscsi_session *session; + struct conn_info info; + struct iscsi_conn *conn; + + if ((err = copy_from_user(&info, (void *) ptr, sizeof(info))) < 0) + return err; + + session = session_lookup(target, info.sid); + if (!session) + return -ENOENT; + conn = conn_lookup(session, info.cid); + + info.cid = conn->cid; + info.stat_sn = conn->stat_sn; + info.exp_stat_sn = conn->exp_stat_sn; + + if (copy_to_user((void *) ptr, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +/* target_mutex supposed to be locked */ +static int add_conn(struct iscsi_target *target, unsigned long ptr) +{ + int err; + struct iscsi_session *session; + struct conn_info info; + + if ((err = copy_from_user(&info, (void *) ptr, sizeof(info))) < 0) + return err; + + if (!(session = session_lookup(target, info.sid))) + return -ENOENT; + + return conn_add(session, &info); +} + +/* target_mutex supposed to be locked */ +static int del_conn(struct iscsi_target *target, unsigned long ptr) +{ + int err; + struct iscsi_session *session; + struct conn_info info; + + if ((err = copy_from_user(&info, (void *) ptr, sizeof(info))) < 0) + return err; + + if (!(session = session_lookup(target, info.sid))) + return -ENOENT; + + return conn_del(session, &info); +} + +/* target_mutex supposed to be locked */ +static int get_session_info(struct iscsi_target *target, unsigned long ptr) +{ + int err; + struct iscsi_session *session; + struct session_info info; + + if ((err = copy_from_user(&info, (void *) ptr, sizeof(info))) < 0) + return err; + + session = session_lookup(target, info.sid); + + if (!session) + return -ENOENT; + + info.exp_cmd_sn = session->exp_cmd_sn; + info.max_cmd_sn = session->max_cmd_sn; + + if (copy_to_user((void *) ptr, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +/* target_mutex supposed to be locked */ +static int add_session(struct iscsi_target *target, unsigned long ptr) +{ + int err; + struct session_info info; + + if ((err = copy_from_user(&info, (void *) ptr, sizeof(info))) < 0) + return err; + + info.initiator_name[ISCSI_NAME_LEN-1] = '\0'; + info.user_name[ISCSI_NAME_LEN-1] = '\0'; + + return session_add(target, &info); +} + +/* target_mutex supposed to be locked */ +static int del_session(struct iscsi_target *target, unsigned long ptr) +{ + int err; + struct session_info info; + + if ((err = copy_from_user(&info, (void *) ptr, sizeof(info))) < 0) + return err; + + return session_del(target, info.sid); +} + +/* target_mutex supposed to be locked */ +static int iscsi_param_config(struct iscsi_target *target, unsigned long ptr, int set) +{ + int err; + struct iscsi_param_info info; + + if ((err = copy_from_user(&info, (void *) ptr, sizeof(info))) < 0) + goto out; + + if ((err = iscsi_param_set(target, &info, set)) < 0) + goto out; + + if (!set) + err = copy_to_user((void *) ptr, &info, sizeof(info)); + +out: + return err; +} + +/* target_mgmt_mutex supposed to be locked */ +static int add_target(unsigned long ptr) +{ + int err; + struct target_info info; + + if ((err = copy_from_user(&info, (void *) ptr, sizeof(info))) < 0) + return err; + + if (!(err = target_add(&info))) + err = copy_to_user((void *) ptr, &info, sizeof(info)); + + return err; +} + +static long ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct iscsi_target *target = NULL; + long err; + u32 id; + + if ((err = get_user(id, (u32 *) arg)) != 0) + goto out; + + if ((err = mutex_lock_interruptible(&target_mgmt_mutex)) < 0) + goto out; + + if (cmd == DEL_TARGET) { + err = target_del(id); + goto out_unlock; + } + + target = target_lookup_by_id(id); + + if (cmd == ADD_TARGET) + if (target) { + err = -EEXIST; + PRINT_ERROR_PR("Target %u already exist!", id); + goto out_unlock; + } + + switch (cmd) { + case ADD_TARGET: + err = add_target(arg); + goto out_unlock; + } + + if (!target) { + PRINT_ERROR_PR("can't find the target %u", id); + err = -EINVAL; + goto out_unlock; + } + + mutex_lock(&target->target_mutex); + + switch (cmd) { + case ADD_SESSION: + err = add_session(target, arg); + break; + + case DEL_SESSION: + err = del_session(target, arg); + break; + + case GET_SESSION_INFO: + err = get_session_info(target, arg); + break; + + case ISCSI_PARAM_SET: + err = iscsi_param_config(target, arg, 1); + break; + + case ISCSI_PARAM_GET: + err = iscsi_param_config(target, arg, 0); + break; + + case ADD_CONN: + err = add_conn(target, arg); + break; + + case DEL_CONN: + err = del_conn(target, arg); + break; + + case GET_CONN_INFO: + err = get_conn_info(target, arg); + break; + + default: + PRINT_ERROR_PR("invalid ioctl cmd %x", cmd); + err = -EINVAL; + } + + mutex_unlock(&target->target_mutex); + +out_unlock: + mutex_unlock(&target_mgmt_mutex); + +out: + return err; +} + +static int release(struct inode *inode, struct file *filp) +{ + target_del_all(); + return 0; +} + +struct file_operations ctr_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = ioctl, + .compat_ioctl = ioctl, + .release = release, +}; + +#ifdef DEBUG +void iscsi_dump_iov(struct msghdr *msg) +{ + if (trace_flag & TRACE_D_IOV) { + int i; + printk("%p, %d\n", msg->msg_iov, msg->msg_iovlen); + for (i = 0; i < min_t(size_t, msg->msg_iovlen, + ISCSI_CONN_IOV_MAX); i++) { + printk("%d: %p,%d\n", i, msg->msg_iov[i].iov_base, + msg->msg_iov[i].iov_len); + } + } +} + +static void iscsi_dump_char(int ch) +{ + static unsigned char text[16]; + static int i = 0; + + if (ch < 0) { + while ((i % 16) != 0) { + printk(" "); + text[i] = ' '; + i++; + if ((i % 16) == 0) + printk(" | %.16s |\n", text); + else if ((i % 4) == 0) + printk(" |"); + } + i = 0; + return; + } + + text[i] = (ch < 0x20 || (ch >= 0x80 && ch <= 0xa0)) ? ' ' : ch; + printk(" %02x", ch); + i++; + if ((i % 16) == 0) { + printk(" | %.16s |\n", text); + i = 0; + } else if ((i % 4) == 0) + printk(" |"); +} + +void iscsi_dump_pdu(struct iscsi_pdu *pdu) +{ + if (trace_flag & TRACE_D_DUMP_PDU) { + unsigned char *buf; + int i; + + buf = (void *)&pdu->bhs; + printk("BHS: (%p,%d)\n", buf, sizeof(pdu->bhs)); + for (i = 0; i < sizeof(pdu->bhs); i++) + iscsi_dump_char(*buf++); + iscsi_dump_char(-1); + + buf = (void *)pdu->ahs; + printk("AHS: (%p,%d)\n", buf, pdu->ahssize); + for (i = 0; i < pdu->ahssize; i++) + iscsi_dump_char(*buf++); + iscsi_dump_char(-1); + + printk("Data: (%d)\n", pdu->datasize); + } +} +#endif /* DEBUG */ diff --git a/iscsi-scst/kernel/conn.c b/iscsi-scst/kernel/conn.c new file mode 100644 index 000000000..b776bc04e --- /dev/null +++ b/iscsi-scst/kernel/conn.c @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2002-2003 Ardis Technolgies + * + * Released under the terms of the GNU GPL v2.0. + */ + +#include +#include +#include + +#include "iscsi.h" +#include "digest.h" + +static void print_conn_state(char *p, size_t size, struct iscsi_conn *conn) +{ + int printed = 0; + + if (conn->closing) { + snprintf(p, size, "%s", "closing"); + return; + } + + switch(conn->rd_state) { + case ISCSI_CONN_RD_STATE_PROCESSING: + snprintf(p, size, "%s", "read_processing "); + printed = 1; + break; + case ISCSI_CONN_RD_STATE_IN_LIST: + snprintf(p, size, "%s", "in_read_list "); + printed = 1; + break; + } + + switch(conn->wr_state) { + case ISCSI_CONN_WR_STATE_PROCESSING: + snprintf(p, size, "%s", "write_processing "); + printed = 1; + break; + case ISCSI_CONN_WR_STATE_IN_LIST: + snprintf(p, size, "%s", "in_write_list "); + printed = 1; + break; + case ISCSI_CONN_WR_STATE_SPACE_WAIT: + snprintf(p, size, "%s", "space_waiting "); + printed = 1; + break; + } + + if (!printed) + snprintf(p, size, "%s", "established idle "); +} + +static void print_digest_state(char *p, size_t size, unsigned long flags) +{ + if (DIGEST_NONE & flags) + snprintf(p, size, "%s", "none"); + else if (DIGEST_CRC32C & flags) + snprintf(p, size, "%s", "crc32c"); + else + snprintf(p, size, "%s", "unknown"); +} + +/* target_mutex supposed to be locked */ +void conn_info_show(struct seq_file *seq, struct iscsi_session *session) +{ + struct iscsi_conn *conn; + struct sock *sk; + char buf[64]; + + list_for_each_entry(conn, &session->conn_list, conn_list_entry) { + sk = conn->sock->sk; + switch (sk->sk_family) { + case AF_INET: + snprintf(buf, sizeof(buf), + "%u.%u.%u.%u", NIPQUAD(inet_sk(sk)->daddr)); + break; + case AF_INET6: + snprintf(buf, sizeof(buf), + "[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x]", + NIP6(inet6_sk(sk)->daddr)); + break; + default: + break; + } + seq_printf(seq, "\t\tcid:%u ip:%s ", conn->cid, buf); + print_conn_state(buf, sizeof(buf), conn); + seq_printf(seq, "state:%s ", buf); + print_digest_state(buf, sizeof(buf), conn->hdigest_type); + seq_printf(seq, "hd:%s ", buf); + print_digest_state(buf, sizeof(buf), conn->ddigest_type); + seq_printf(seq, "dd:%s\n", buf); + } +} + +/* target_mutex supposed to be locked */ +struct iscsi_conn *conn_lookup(struct iscsi_session *session, u16 cid) +{ + struct iscsi_conn *conn; + + list_for_each_entry(conn, &session->conn_list, conn_list_entry) { + if (conn->cid == cid) + return conn; + } + return NULL; +} + +static void iscsi_make_conn_rd_active(struct iscsi_conn *conn) +{ + TRACE_ENTRY(); + + spin_lock_bh(&iscsi_rd_lock); + + TRACE_DBG("conn %p, rd_state %x, rd_data_ready %d", conn, + conn->rd_state, conn->rd_data_ready); + + conn->rd_data_ready = 1; + + if (conn->rd_state == ISCSI_CONN_RD_STATE_IDLE) { + list_add_tail(&conn->rd_list_entry, &iscsi_rd_list); + conn->rd_state = ISCSI_CONN_RD_STATE_IN_LIST; + wake_up(&iscsi_rd_waitQ); + } + + spin_unlock_bh(&iscsi_rd_lock); + + TRACE_EXIT(); + return; +} + +void iscsi_make_conn_wr_active(struct iscsi_conn *conn) +{ + TRACE_ENTRY(); + + spin_lock_bh(&iscsi_wr_lock); + + TRACE_DBG("conn %p, wr_state %x, wr_space_ready %d", conn, + conn->wr_state, conn->wr_space_ready); + + if (conn->wr_state == ISCSI_CONN_WR_STATE_IDLE) { + list_add_tail(&conn->wr_list_entry, &iscsi_wr_list); + conn->wr_state = ISCSI_CONN_WR_STATE_IN_LIST; + wake_up(&iscsi_wr_waitQ); + } + + spin_unlock_bh(&iscsi_wr_lock); + + TRACE_EXIT(); + return; +} + +void mark_conn_closed(struct iscsi_conn *conn) +{ + spin_lock_bh(&iscsi_rd_lock); + conn->closing = 1; + spin_unlock_bh(&iscsi_rd_lock); + + iscsi_make_conn_rd_active(conn); +} + +static void iscsi_state_change(struct sock *sk) +{ + struct iscsi_conn *conn = sk->sk_user_data; + + TRACE_ENTRY(); + + if (sk->sk_state != TCP_ESTABLISHED) { + if (!conn->closing) { + PRINT_ERROR_PR("Connection with initiator %s (%p) " + "unexpectedly closed!", + conn->session->initiator_name, conn); + mark_conn_closed(conn); + } + } else + iscsi_make_conn_rd_active(conn); + + conn->old_state_change(sk); + + TRACE_EXIT(); + return; +} + +static void iscsi_data_ready(struct sock *sk, int len) +{ + struct iscsi_conn *conn = sk->sk_user_data; + + TRACE_ENTRY(); + + iscsi_make_conn_rd_active(conn); + + conn->old_data_ready(sk, len); + + TRACE_EXIT(); + return; +} + +static void iscsi_write_space_ready(struct sock *sk) +{ + struct iscsi_conn *conn = sk->sk_user_data; + + TRACE_ENTRY(); + + TRACE_DBG("Write space ready for conn %p", conn); + + spin_lock_bh(&iscsi_wr_lock); + conn->wr_space_ready = 1; + if ((conn->wr_state == ISCSI_CONN_WR_STATE_SPACE_WAIT)) { + list_add_tail(&conn->wr_list_entry, &iscsi_wr_list); + conn->wr_state = ISCSI_CONN_WR_STATE_IN_LIST; + wake_up(&iscsi_wr_waitQ); + } + spin_unlock_bh(&iscsi_wr_lock); + + conn->old_write_space(sk); + + TRACE_EXIT(); + return; +} + +static int iscsi_socket_bind(struct iscsi_conn *conn) +{ + int res = 0; + int opt = 1; + mm_segment_t oldfs; + struct iscsi_session *session = conn->session; + + TRACE_DBG("%llu", (unsigned long long) session->sid); + + conn->sock = SOCKET_I(conn->file->f_dentry->d_inode); + + if (conn->sock->ops->sendpage == NULL) { + PRINT_ERROR_PR("Socket for sid %llu doesn't support sendpage()", + session->sid); + res = -EINVAL; + goto out; + } + +#if 0 + conn->sock->sk->sk_allocation = GFP_NOIO; +#endif + conn->sock->sk->sk_user_data = conn; + + write_lock_bh(&conn->sock->sk->sk_callback_lock); + + conn->old_state_change = conn->sock->sk->sk_state_change; + conn->sock->sk->sk_state_change = iscsi_state_change; + + conn->old_data_ready = conn->sock->sk->sk_data_ready; + conn->sock->sk->sk_data_ready = iscsi_data_ready; + + conn->old_write_space = conn->sock->sk->sk_write_space; + conn->sock->sk->sk_write_space = iscsi_write_space_ready; + + write_unlock_bh(&conn->sock->sk->sk_callback_lock); + + oldfs = get_fs(); + set_fs(get_ds()); + conn->sock->ops->setsockopt(conn->sock, SOL_TCP, TCP_NODELAY, + (void *)&opt, sizeof(opt)); + set_fs(oldfs); + +out: + return res; +} + +/* target_mutex supposed to be locked */ +int conn_free(struct iscsi_conn *conn) +{ + TRACE_MGMT_DBG("Freeing conn %p (sess=%p, %#Lx %u)", conn, + conn->session, (unsigned long long)conn->session->sid, + conn->cid); + + sBUG_ON(atomic_read(&conn->conn_ref_cnt) != 0); + sBUG_ON(!list_empty(&conn->cmd_list)); + sBUG_ON(!list_empty(&conn->write_list)); + + list_del(&conn->conn_list_entry); + + fput(conn->file); + conn->file = NULL; + conn->sock = NULL; + + free_page((unsigned long)conn->read_iov); + + kfree(conn); + + return 0; +} + +/* target_mutex supposed to be locked */ +static int iscsi_conn_alloc(struct iscsi_session *session, struct conn_info *info) +{ + struct iscsi_conn *conn; + int res = 0; + + TRACE_MGMT_DBG("Creating connection for sid %#Lx, cid %u", + (unsigned long long)session->sid, info->cid); + + conn = kzalloc(sizeof(*conn), GFP_KERNEL); + if (!conn) { + res = -ENOMEM; + goto out_err; + } + + /* Changing it, change ISCSI_CONN_IOV_MAX as well !! */ + conn->read_iov = (struct iovec *)get_zeroed_page(GFP_KERNEL); + if (conn->read_iov == NULL) { + res = -ENOMEM; + goto out_err_free_conn; + } + + atomic_set(&conn->conn_ref_cnt, 0); + conn->session = session; + conn->cid = info->cid; + conn->stat_sn = info->stat_sn; + conn->exp_stat_sn = info->exp_stat_sn; + conn->rd_state = ISCSI_CONN_RD_STATE_IDLE; + conn->wr_state = ISCSI_CONN_WR_STATE_IDLE; + + conn->hdigest_type = info->header_digest; + conn->ddigest_type = info->data_digest; + res = digest_init(conn); + if (res != 0) + goto out_err_free1; + + conn->target = session->target; + spin_lock_init(&conn->cmd_list_lock); + INIT_LIST_HEAD(&conn->cmd_list); + spin_lock_init(&conn->write_list_lock); + INIT_LIST_HEAD(&conn->write_list); + + conn->file = fget(info->fd); + + res = iscsi_socket_bind(conn); + if (res != 0) + goto out_err_free2; + + list_add(&conn->conn_list_entry, &session->conn_list); + +out: + return res; + +out_err_free2: + fput(conn->file); + +out_err_free1: + free_page((unsigned long)conn->read_iov); + +out_err_free_conn: + kfree(conn); + +out_err: + goto out; +} + +/* target_mutex supposed to be locked */ +int conn_add(struct iscsi_session *session, struct conn_info *info) +{ + struct iscsi_conn *conn; + int err = -EEXIST; + + conn = conn_lookup(session, info->cid); + if (conn) + return err; + + return iscsi_conn_alloc(session, info); +} + +/* target_mutex supposed to be locked */ +int conn_del(struct iscsi_session *session, struct conn_info *info) +{ + struct iscsi_conn *conn; + int err = -EEXIST; + + conn = conn_lookup(session, info->cid); + if (!conn) + return err; + + PRINT_INFO_PR("Deleting connection with initiator %s (%p)", + conn->session->initiator_name, conn); + + mark_conn_closed(conn); + + return 0; +} diff --git a/iscsi-scst/kernel/digest.c b/iscsi-scst/kernel/digest.c new file mode 100644 index 000000000..26848c86a --- /dev/null +++ b/iscsi-scst/kernel/digest.c @@ -0,0 +1,212 @@ +/* + * iSCSI digest handling. + * (C) 2004 - 2006 Xiranet Communications GmbH + * This code is licensed under the GPL. + */ + +#include +#include + +#include "iscsi.h" +#include "digest.h" +#include + +void digest_alg_available(unsigned int *val) +{ +#if CONFIG_LIBCRC32C_MODULE || CONFIG_LIBCRC32C + int crc32c = 1; +#else + int crc32c = 0; +#endif + + if ((*val & DIGEST_CRC32C) && !crc32c) { + PRINT_ERROR_PR("%s", "CRC32C digest algorithm not available " + "in kernel"); + *val |= ~DIGEST_CRC32C; + } +} + +/** + * initialize support for digest calculation. + * + * digest_init - + * @conn: ptr to connection to make use of digests + * + * @return: 0 on success, < 0 on error + */ +int digest_init(struct iscsi_conn *conn) +{ + if (!(conn->hdigest_type & DIGEST_ALL)) + conn->hdigest_type = DIGEST_NONE; + + if (!(conn->ddigest_type & DIGEST_ALL)) + conn->ddigest_type = DIGEST_NONE; + + return 0; +} + +/* Copied from linux-iscsi initiator and slightly adjusted */ +#define SETSG(sg, p, l) do { \ + (sg).page = virt_to_page((p)); \ + (sg).offset = ((unsigned long)(p) & ~PAGE_MASK); \ + (sg).length = (l); \ +} while (0) + +static u32 evaluate_crc32_from_sg(struct scatterlist *sg, int total, + int pad_bytes) +{ + u32 crc = ~0; + +#ifdef DEBUG_DIGEST_FAILURES + if (((scst_random() % 100000) == 752)) { + PRINT_INFO_PR("%s", "Simulating digest failure"); + return 0; + } +#endif + +#if CONFIG_LIBCRC32C_MODULE || CONFIG_LIBCRC32C + while (total > 0) { + int d = min(min(total, (int)(sg->length)), + (int)(PAGE_SIZE - sg->offset)); + + crc = crc32c(crc, page_address(sg->page) + sg->offset, d); + total -= d; + sg++; + } + + if (pad_bytes) { + u32 padding = 0; + /* + * Digest includes also padding for aligned pdu length, hopefully + * it is always filled with 0s in pdu (according to crypto/crc32c.c + */ + crc = crc32c(crc, (u8 *)&padding, pad_bytes); + } +#endif + + return ~cpu_to_le32(crc); +} + +static u32 digest_header(struct iscsi_pdu *pdu) +{ + struct scatterlist sg[2]; + unsigned int nbytes = sizeof(struct iscsi_hdr); + + SETSG(sg[0], &pdu->bhs, nbytes); + if (pdu->ahssize) { + SETSG(sg[1], pdu->ahs, pdu->ahssize); + nbytes += pdu->ahssize; + } + return evaluate_crc32_from_sg(sg, nbytes, 0); +} + +static u32 digest_data(struct iscsi_cmnd *req, u32 osize, u32 offset) +{ + struct scatterlist *sg = req->sg; + int idx, count; + struct scatterlist saved_sg; + u32 size = (osize + 3) & ~3; + u32 crc; + + offset += sg[0].offset; + idx = offset >> PAGE_SHIFT; + offset &= ~PAGE_MASK; + + count = get_pgcnt(size, offset); + sBUG_ON(idx + count > get_pgcnt(req->bufflen, 0)); + sBUG_ON(count > ISCSI_CONN_IOV_MAX); + + saved_sg = sg[idx]; + sg[idx].offset = offset; + sg[idx].length -= offset - saved_sg.offset; + + crc = evaluate_crc32_from_sg(sg + idx, osize, size - osize); + + sg[idx] = saved_sg; + return crc; +} + +int digest_rx_header(struct iscsi_cmnd *cmnd) +{ + u32 crc; + + crc = digest_header(&cmnd->pdu); + if (crc != cmnd->hdigest) { + PRINT_ERROR_PR("%s", "RX header digest failed"); + return -EIO; + } else + TRACE_DBG("RX header digest OK for cmd %p", cmnd); + + return 0; +} + +void digest_tx_header(struct iscsi_cmnd *cmnd) +{ + cmnd->hdigest = digest_header(&cmnd->pdu); + TRACE_DBG("TX header digest for cmd %p: %x", cmnd, cmnd->hdigest); +} + +int digest_rx_data(struct iscsi_cmnd *cmnd) +{ + struct iscsi_cmnd *req; + struct iscsi_data_out_hdr *req_hdr; + u32 offset, crc; + int res = 0; + + switch (cmnd_opcode(cmnd)) { + case ISCSI_OP_SCSI_DATA_OUT: + req = cmnd->cmd_req; + req_hdr = (struct iscsi_data_out_hdr *)&cmnd->pdu.bhs; + offset = be32_to_cpu(req_hdr->buffer_offset); + break; + + case ISCSI_OP_SCSI_REJECT: + case ISCSI_OP_PDU_REJECT: + case ISCSI_OP_DATA_REJECT: + goto out; + + default: + req = cmnd; + offset = 0; + } + + crc = digest_data(req, cmnd->pdu.datasize, offset); + + if (crc != cmnd->ddigest) { + PRINT_ERROR_PR("%s", "RX data digest failed"); + res = -EIO; + } else + TRACE_DBG("RX data digest OK for cmd %p", cmnd); + +out: + return res; +} + +void digest_tx_data(struct iscsi_cmnd *cmnd) +{ + struct iscsi_data_in_hdr *hdr; + u32 offset; + + TRACE_DBG("%s:%d req %p, own_sg %d, sg %p, sgcnt %d cmnd %p, " + "own_sg %d, sg %p, sgcnt %d", __FUNCTION__, __LINE__, + cmnd->parent_req, cmnd->parent_req->own_sg, + cmnd->parent_req->sg, cmnd->parent_req->sg_cnt, + cmnd, cmnd->own_sg, cmnd->sg, cmnd->sg_cnt); + + switch (cmnd_opcode(cmnd)) { + case ISCSI_OP_SCSI_DATA_IN: + hdr = (struct iscsi_data_in_hdr *)&cmnd->pdu.bhs; + offset = be32_to_cpu(hdr->buffer_offset); + break; + default: + offset = 0; + } + + /* + * cmnd is used here regardless of its sg comes from parent or was + * allocated for this cmnd only, see cmnd_send_pdu() + */ + cmnd->ddigest = digest_data(cmnd, cmnd->pdu.datasize, offset); + TRACE_DBG("TX data digest for cmd %p: %x (offset %d, opcode %x)", cmnd, + cmnd->ddigest, offset, cmnd_opcode(cmnd)); +} diff --git a/iscsi-scst/kernel/digest.h b/iscsi-scst/kernel/digest.h new file mode 100644 index 000000000..349a60823 --- /dev/null +++ b/iscsi-scst/kernel/digest.h @@ -0,0 +1,19 @@ +/* + * iSCSI digest handling. + * (C) 2004 Xiranet Communications GmbH + * This code is licensed under the GPL. + */ + +#ifndef __ISCSI_DIGEST_H__ +#define __ISCSI_DIGEST_H__ + +extern void digest_alg_available(unsigned int *val); +extern int digest_init(struct iscsi_conn *conn); + +extern int digest_rx_header(struct iscsi_cmnd *cmnd); +extern int digest_rx_data(struct iscsi_cmnd *cmnd); + +extern void digest_tx_header(struct iscsi_cmnd *cmnd); +extern void digest_tx_data(struct iscsi_cmnd *cmnd); + +#endif /* __ISCSI_DIGEST_H__ */ diff --git a/iscsi-scst/kernel/event.c b/iscsi-scst/kernel/event.c new file mode 100644 index 000000000..2b0edc262 --- /dev/null +++ b/iscsi-scst/kernel/event.c @@ -0,0 +1,115 @@ +/* + * Event notification code. + * (C) 2005 FUJITA Tomonori + * This code is licenced under the GPL. + * + * Some functions are based on audit code. + */ + +#include +#include "iscsi_u.h" +#include "iscsi.h" + +static struct sock *nl; +static u32 iscsid_pid; + +static int event_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) +{ + u32 uid, pid, seq; + char *data; + + pid = NETLINK_CREDS(skb)->pid; + uid = NETLINK_CREDS(skb)->uid; + seq = nlh->nlmsg_seq; + data = NLMSG_DATA(nlh); + + iscsid_pid = pid; + + return 0; +} + +static int event_recv_skb(struct sk_buff *skb) +{ + int err; + struct nlmsghdr *nlh; + u32 rlen; + + while (skb->len >= NLMSG_SPACE(0)) { + nlh = (struct nlmsghdr *)skb->data; + if (nlh->nlmsg_len < sizeof(*nlh) || skb->len < nlh->nlmsg_len) + return 0; + rlen = NLMSG_ALIGN(nlh->nlmsg_len); + if (rlen > skb->len) + rlen = skb->len; + if ((err = event_recv_msg(skb, nlh))) { + netlink_ack(skb, nlh, -err); + } else if (nlh->nlmsg_flags & NLM_F_ACK) + netlink_ack(skb, nlh, 0); + skb_pull(skb, rlen); + } + return 0; +} + +static void event_recv(struct sock *sk, int length) +{ + struct sk_buff *skb; + + while ((skb = skb_dequeue(&sk->sk_receive_queue))) { + if (event_recv_skb(skb) && skb->len) + skb_queue_head(&sk->sk_receive_queue, skb); + else + kfree_skb(skb); + } +} + +static int notify(void *data, int len, int gfp_mask) +{ + struct sk_buff *skb; + struct nlmsghdr *nlh; + static u32 seq = 0; + + if (!(skb = alloc_skb(NLMSG_SPACE(len), gfp_mask))) + return -ENOMEM; + + nlh = __nlmsg_put(skb, iscsid_pid, seq++, NLMSG_DONE, len - sizeof(*nlh), 0); + + memcpy(NLMSG_DATA(nlh), data, len); + + return netlink_unicast(nl, skb, iscsid_pid, 0); +} + +int event_send(u32 tid, u64 sid, u32 cid, u32 state, int atomic) +{ + int err; + struct iscsi_event event; + + event.tid = tid; + event.sid = sid; + event.cid = cid; + event.state = state; + + err = notify(&event, NLMSG_SPACE(sizeof(struct iscsi_event)), 0); + + return err; +} + +int __init event_init(void) +{ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22)) + nl = netlink_kernel_create(NETLINK_ISCSI_SCST, 1, event_recv, THIS_MODULE); +#else + nl = netlink_kernel_create(NETLINK_ISCSI_SCST, 1, event_recv, NULL, + THIS_MODULE); +#endif + if (!nl) { + PRINT_ERROR_PR("%s", "netlink_kernel_create() failed"); + return -ENOMEM; + } else + return 0; +} + +void event_exit(void) +{ + if (nl) + sock_release(nl->sk_socket); +} diff --git a/iscsi-scst/kernel/iscsi.c b/iscsi-scst/kernel/iscsi.c new file mode 100644 index 000000000..b152f8d63 --- /dev/null +++ b/iscsi-scst/kernel/iscsi.c @@ -0,0 +1,2425 @@ +/* + * Copyright (C) 2002-2003 Ardis Technolgies + * + * Released under the terms of the GNU GPL v2.0. + */ + +#include +#include +#include +#include +#include + +#include "iscsi.h" +#include "digest.h" + +#ifndef NET_PAGE_CALLBACKS_DEFINED +#warning Patch put_page_callback.patch not applied on your kernel. ISCSI-SCST \ + will run in the performance degraded mode. Refer README file for \ + details. +#endif + +#define ISCSI_INIT_WRITE_WAKE 0x1 +#define ISCSI_INIT_WRITE_REMOVE_HASH 0x2 + +static int ctr_major; +static char ctr_name[] = "iscsi-scst-ctl"; +static int iscsi_template_registered; + +#if defined(DEBUG) || defined(TRACING) +unsigned long iscsi_trace_flag = ISCSI_DEFAULT_LOG_FLAGS; +#endif + +static struct kmem_cache *iscsi_cmnd_cache; + +spinlock_t iscsi_rd_lock = SPIN_LOCK_UNLOCKED; +LIST_HEAD(iscsi_rd_list); +DECLARE_WAIT_QUEUE_HEAD(iscsi_rd_waitQ); + +spinlock_t iscsi_wr_lock = SPIN_LOCK_UNLOCKED; +LIST_HEAD(iscsi_wr_list); +DECLARE_WAIT_QUEUE_HEAD(iscsi_wr_waitQ); + +static char dummy_data[1024]; + +struct iscsi_thread_t { + struct task_struct *thr; + struct list_head threads_list_entry; +}; + +static LIST_HEAD(iscsi_threads_list); + +static void cmnd_remove_hash(struct iscsi_cmnd *cmnd); +static void iscsi_send_task_mgmt_resp(struct iscsi_cmnd *req, int status); +static void cmnd_prepare_skip_pdu(struct iscsi_cmnd *cmnd); + +static inline u32 cmnd_write_size(struct iscsi_cmnd *cmnd) +{ + struct iscsi_scsi_cmd_hdr *hdr = cmnd_hdr(cmnd); + + if (hdr->flags & ISCSI_CMD_WRITE) + return be32_to_cpu(hdr->data_length); + return 0; +} + +static inline u32 cmnd_read_size(struct iscsi_cmnd *cmnd) +{ + struct iscsi_scsi_cmd_hdr *hdr = cmnd_hdr(cmnd); + + if (hdr->flags & ISCSI_CMD_READ) { + struct iscsi_rlength_ahdr *ahdr = + (struct iscsi_rlength_ahdr *)cmnd->pdu.ahs; + + if (!(hdr->flags & ISCSI_CMD_WRITE)) + return be32_to_cpu(hdr->data_length); + if (ahdr && ahdr->ahstype == ISCSI_AHSTYPE_RLENGTH) + return be32_to_cpu(ahdr->read_length); + } + return 0; +} + +static inline void iscsi_restart_cmnd(struct iscsi_cmnd *cmnd) +{ + cmnd->scst_state = ISCSI_CMD_STATE_RESTARTED; + scst_restart_cmd(cmnd->scst_cmd, SCST_PREPROCESS_STATUS_SUCCESS, + SCST_CONTEXT_THREAD); +} + +struct iscsi_cmnd *cmnd_alloc(struct iscsi_conn *conn, struct iscsi_cmnd *parent) +{ + struct iscsi_cmnd *cmnd; + + /* ToDo: __GFP_NOFAIL?? */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,17) + cmnd = kmem_cache_alloc(iscsi_cmnd_cache, GFP_KERNEL|__GFP_NOFAIL); + memset(cmnd, 0, sizeof(*cmnd)); +#else + cmnd = kmem_cache_zalloc(iscsi_cmnd_cache, GFP_KERNEL|__GFP_NOFAIL); +#endif + + atomic_set(&cmnd->ref_cnt, 1); + cmnd->scst_state = ISCSI_CMD_STATE_NEW; + cmnd->conn = conn; + cmnd->parent_req = parent; + init_waitqueue_head(&cmnd->scst_waitQ); + + if (parent == NULL) { + atomic_inc(&conn->conn_ref_cnt); +#ifdef NET_PAGE_CALLBACKS_DEFINED + atomic_set(&cmnd->net_ref_cnt, 0); +#endif + cmnd->target = conn->target; + spin_lock_init(&cmnd->rsp_cmd_lock); + INIT_LIST_HEAD(&cmnd->rsp_cmd_list); + INIT_LIST_HEAD(&cmnd->rx_ddigest_cmd_list); + + spin_lock_bh(&conn->cmd_list_lock); + list_add_tail(&cmnd->cmd_list_entry, &conn->cmd_list); + spin_unlock_bh(&conn->cmd_list_lock); + } + + TRACE_DBG("conn %p, parent %p, cmnd %p", conn, parent, cmnd); + return cmnd; +} + +/* Frees a command. Also frees the additional header. */ +void cmnd_free(struct iscsi_cmnd *cmnd) +{ + TRACE_DBG("%p", cmnd); + + /* Catch users from cmd_list or rsp_cmd_list */ + EXTRACHECKS_BUG_ON(atomic_read(&cmnd->ref_cnt) != 0); + + kfree(cmnd->pdu.ahs); + + if (unlikely(cmnd->on_write_list)) { + struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd); + + PRINT_ERROR_PR("cmnd %p still on some list?, %x, %x, %x, %x, %x, %x, %x", + cmnd, req->opcode, req->scb[0], req->flags, req->itt, + be32_to_cpu(req->data_length), + req->cmd_sn, be32_to_cpu(cmnd->pdu.datasize)); + + if (cmnd->parent_req) { + struct iscsi_scsi_cmd_hdr *req = + cmnd_hdr(cmnd->parent_req); + PRINT_ERROR_PR("%p %x %u", req, req->opcode, req->scb[0]); + } + sBUG(); + } + + kmem_cache_free(iscsi_cmnd_cache, cmnd); + return; +} + +void cmnd_done(struct iscsi_cmnd *cmnd) +{ + TRACE_DBG("%p", cmnd); + + if (unlikely(cmnd->tmfabort)) { + TRACE_MGMT_DBG("Done aborted cmd %p (scst cmd %p, state %d)", + cmnd, cmnd->scst_cmd, cmnd->scst_state); + } + + if (cmnd->parent_req == NULL) { + struct iscsi_conn *conn = cmnd->conn; + TRACE_DBG("Deleting req %p from conn %p", cmnd, conn); + spin_lock_bh(&conn->cmd_list_lock); + list_del(&cmnd->cmd_list_entry); + spin_unlock_bh(&conn->cmd_list_lock); + + smp_mb__before_atomic_dec(); + atomic_dec(&conn->conn_ref_cnt); + + EXTRACHECKS_BUG_ON(!list_empty(&cmnd->rsp_cmd_list)); + EXTRACHECKS_BUG_ON(!list_empty(&cmnd->rx_ddigest_cmd_list)); + + /* Order between above and below code is important! */ + + if (cmnd->scst_cmd) { + switch(cmnd->scst_state) { + case ISCSI_CMD_STATE_AFTER_PREPROC: + TRACE_DBG("%s", "AFTER_PREPROC"); + cmnd->scst_state = ISCSI_CMD_STATE_RESTARTED; + scst_restart_cmd(cmnd->scst_cmd, + SCST_PREPROCESS_STATUS_ERROR_FATAL, + SCST_CONTEXT_THREAD); + break; + case ISCSI_CMD_STATE_PROCESSED: + TRACE_DBG("%s", "PROCESSED"); + scst_tgt_cmd_done(cmnd->scst_cmd); + break; + default: + PRINT_ERROR_PR("Unexpected cmnd scst state %d", + cmnd->scst_state); + sBUG(); + break; + } + } + } else { + EXTRACHECKS_BUG_ON(cmnd->scst_cmd != NULL); + + spin_lock_bh(&cmnd->parent_req->rsp_cmd_lock); + TRACE_DBG("Deleting rsp %p from parent %p", cmnd, + cmnd->parent_req); + list_del(&cmnd->rsp_cmd_list_entry); + spin_unlock_bh(&cmnd->parent_req->rsp_cmd_lock); + + cmnd_put(cmnd->parent_req); + } + + /* Order between above and below code is important! */ + + if (cmnd->own_sg) { + TRACE_DBG("%s", "own_sg"); + scst_free(cmnd->sg, cmnd->sg_cnt); +#ifdef DEBUG + cmnd->own_sg = 0; + cmnd->sg = NULL; + cmnd->sg_cnt = -1; +#endif + } + + cmnd_free(cmnd); + return; +} + +void req_cmnd_release_force(struct iscsi_cmnd *req, int flags) +{ + struct iscsi_cmnd *rsp; + struct iscsi_conn *conn = req->conn; + + TRACE_ENTRY(); + + TRACE_DBG("%p", req); + + if (flags & ISCSI_FORCE_RELEASE_WRITE) { + spin_lock(&conn->write_list_lock); + while(!list_empty(&conn->write_list)) { + rsp = list_entry(conn->write_list.next, + struct iscsi_cmnd, write_list_entry); + cmd_del_from_write_list(rsp); + spin_unlock(&conn->write_list_lock); + + cmnd_put(rsp); + + spin_lock(&conn->write_list_lock); + } + spin_unlock(&conn->write_list_lock); + } + +again: + spin_lock_bh(&req->rsp_cmd_lock); + list_for_each_entry(rsp, &req->rsp_cmd_list, rsp_cmd_list_entry) { + int f; + + if (rsp->on_write_list || rsp->write_processing_started || + rsp->force_cleanup_done) + continue; + + spin_unlock_bh(&req->rsp_cmd_lock); + + /* + * Recheck is necessary to not take write_list_lock under + * rsp_cmd_lock. + */ + spin_lock(&conn->write_list_lock); + f = rsp->on_write_list || rsp->write_processing_started || + rsp->force_cleanup_done; + spin_unlock(&conn->write_list_lock); + if (f) + goto again; + + rsp->force_cleanup_done = 1; + cmnd_put(rsp); + + goto again; + } + spin_unlock_bh(&req->rsp_cmd_lock); + + req_cmnd_release(req); + + TRACE_EXIT(); + return; +} + +void req_cmnd_release(struct iscsi_cmnd *req) +{ + struct iscsi_cmnd *c, *t; + + TRACE_ENTRY(); + + TRACE_DBG("%p", req); + +#ifdef EXTRACHECKS + sBUG_ON(req->release_called); + req->release_called = 1; +#endif + + if (unlikely(req->tmfabort)) { + TRACE_MGMT_DBG("Release aborted req cmd %p (scst cmd %p, " + "state %d)", req, req->scst_cmd, req->scst_state); + } + + sBUG_ON(req->parent_req != NULL); + + list_for_each_entry_safe(c, t, &req->rx_ddigest_cmd_list, + rx_ddigest_cmd_list_entry) { + TRACE_DBG("Deleting RX ddigest cmd %p from digest " + "list of req %p", c, req); + list_del(&c->rx_ddigest_cmd_list_entry); + cmnd_put(c); + } + + if (req->hashed) + cmnd_remove_hash(req); + + cmnd_put(req); + + TRACE_EXIT(); + return; +} + +void rsp_cmnd_release(struct iscsi_cmnd *cmnd) +{ + TRACE_DBG("%p", cmnd); + +#ifdef EXTRACHECKS + sBUG_ON(cmnd->release_called); + cmnd->release_called = 1; +#endif + + sBUG_ON(cmnd->hashed); + sBUG_ON(cmnd->parent_req == NULL); + + if (unlikely(cmnd->tmfabort)) { + TRACE_MGMT_DBG("Release aborted rsp cmd %p (parent req %p, " + "scst cmd %p, state %d)", cmnd, cmnd->parent_req, + cmnd->parent_req->scst_cmd, + cmnd->parent_req->scst_state); + } + + cmnd_put(cmnd); + return; +} + +/** + * create a new command used as response. + * + * iscsi_cmnd_create_rsp_cmnd - + * @cmnd: ptr to request command + * + * @return ptr to response command or NULL + */ +static struct iscsi_cmnd *iscsi_cmnd_create_rsp_cmnd(struct iscsi_cmnd *parent) +{ + struct iscsi_cmnd *rsp = cmnd_alloc(parent->conn, parent); + + spin_lock_bh(&parent->rsp_cmd_lock); + TRACE_DBG("Adding rsp %p to parent %p", rsp, parent); + list_add_tail(&rsp->rsp_cmd_list_entry, &parent->rsp_cmd_list); + spin_unlock_bh(&parent->rsp_cmd_lock); + cmnd_get(parent); + return rsp; +} + +static inline struct iscsi_cmnd *get_rsp_cmnd(struct iscsi_cmnd *req) +{ + struct iscsi_cmnd *res; + + /* Currently this lock isn't needed, but just in case.. */ + spin_lock_bh(&req->rsp_cmd_lock); + res = list_entry(req->rsp_cmd_list.prev, struct iscsi_cmnd, + rsp_cmd_list_entry); + spin_unlock_bh(&req->rsp_cmd_lock); + + return res; +} + +static void iscsi_cmnds_init_write(struct list_head *send, int flags) +{ + struct iscsi_cmnd *cmnd = list_entry(send->next, struct iscsi_cmnd, + write_list_entry); + struct iscsi_conn *conn = cmnd->conn; + struct list_head *pos, *next; + + /* + * If we don't remove hashed req cmd from the hash list here, before + * submitting it for transmittion, we will have a race, when for + * some reason cmd's release is delayed after transmittion and + * initiator sends cmd with the same ITT => this command will be + * erroneously rejected as a duplicate. + */ + if ((flags & ISCSI_INIT_WRITE_REMOVE_HASH) && cmnd->parent_req->hashed && + (cmnd->parent_req->outstanding_r2t == 0)) + cmnd_remove_hash(cmnd->parent_req); + + list_for_each_safe(pos, next, send) { + cmnd = list_entry(pos, struct iscsi_cmnd, write_list_entry); + + TRACE_DBG("%p:%x", cmnd, cmnd_opcode(cmnd)); + + sBUG_ON(conn != cmnd->conn); + + if (!(conn->ddigest_type & DIGEST_NONE) && + (cmnd->pdu.datasize != 0)) + digest_tx_data(cmnd); + + list_del(&cmnd->write_list_entry); + + spin_lock(&conn->write_list_lock); + cmd_add_on_write_list(conn, cmnd); + spin_unlock(&conn->write_list_lock); + } + + + if (flags & ISCSI_INIT_WRITE_WAKE) + iscsi_make_conn_wr_active(conn); +} + +static void iscsi_cmnd_init_write(struct iscsi_cmnd *cmnd, int flags) +{ + LIST_HEAD(head); + + if (unlikely(cmnd->on_write_list)) { + PRINT_ERROR_PR("cmd already on write list (%x %x %x %x %u %u " + "%u %u %u %u %u %d %d", + cmnd_itt(cmnd), cmnd_ttt(cmnd), cmnd_opcode(cmnd), + cmnd_scsicode(cmnd), cmnd->r2t_sn, + cmnd->r2t_length, cmnd->is_unsolicited_data, + cmnd->target_task_tag, cmnd->outstanding_r2t, + cmnd->hdigest, cmnd->ddigest, + list_empty(&cmnd->rsp_cmd_list), cmnd->hashed); + sBUG(); + } + list_add(&cmnd->write_list_entry, &head); + iscsi_cmnds_init_write(&head, flags); +} + +static void iscsi_set_datasize(struct iscsi_cmnd *cmnd, u32 offset, u32 size) +{ + cmnd->pdu.datasize = size; + + if (cmnd->pdu.datasize & 3) { + int idx = (offset + cmnd->pdu.datasize) >> PAGE_SHIFT; + u8 *p = (u8 *)page_address(cmnd->sg[idx].page) + + ((offset + cmnd->pdu.datasize) & ~PAGE_MASK); + int i = 4 - (cmnd->pdu.datasize & 3); + while (i--) + *p++ = 0; + } +} + +static void send_data_rsp(struct iscsi_cmnd *req, u8 status, int send_status) +{ + struct iscsi_cmnd *rsp; + struct iscsi_scsi_cmd_hdr *req_hdr = cmnd_hdr(req); + struct iscsi_data_in_hdr *rsp_hdr; + u32 pdusize, expsize, scsisize, size, offset, sn; + LIST_HEAD(send); + + TRACE_DBG("req %p", req); + pdusize = req->conn->session->sess_param.max_xmit_data_length; + expsize = cmnd_read_size(req); + size = min(expsize, (u32)req->bufflen); + offset = 0; + sn = 0; + + while (1) { + rsp = iscsi_cmnd_create_rsp_cmnd(req); + TRACE_DBG("rsp %p", rsp); + rsp->sg = req->sg; + rsp->bufflen = req->bufflen; + rsp_hdr = (struct iscsi_data_in_hdr *)&rsp->pdu.bhs; + + rsp_hdr->opcode = ISCSI_OP_SCSI_DATA_IN; + rsp_hdr->itt = req_hdr->itt; + rsp_hdr->ttt = cpu_to_be32(ISCSI_RESERVED_TAG); + rsp_hdr->buffer_offset = cpu_to_be32(offset); + rsp_hdr->data_sn = cpu_to_be32(sn); + + if (size <= pdusize) { + iscsi_set_datasize(rsp, offset, size); + if (send_status) { + TRACE_DBG("status %x", status); + rsp_hdr->flags = + ISCSI_FLG_FINAL | ISCSI_FLG_STATUS; + rsp_hdr->cmd_status = status; + } + scsisize = req->bufflen; + if (scsisize < expsize) { + rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_UNDERFLOW; + size = expsize - scsisize; + } else if (scsisize > expsize) { + rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_OVERFLOW; + size = scsisize - expsize; + } else + size = 0; + rsp_hdr->residual_count = cpu_to_be32(size); + list_add_tail(&rsp->write_list_entry, &send); + break; + } + + iscsi_set_datasize(rsp, offset, pdusize); + + size -= pdusize; + offset += pdusize; + sn++; + + list_add_tail(&rsp->write_list_entry, &send); + } + iscsi_cmnds_init_write(&send, ISCSI_INIT_WRITE_REMOVE_HASH); +} + +static struct iscsi_cmnd *create_status_rsp(struct iscsi_cmnd *req, int status, + const u8 *sense_buf, int sense_len) +{ + struct iscsi_cmnd *rsp; + struct iscsi_scsi_rsp_hdr *rsp_hdr; + struct iscsi_sense_data *sense; + struct scatterlist *sg; + + rsp = iscsi_cmnd_create_rsp_cmnd(req); + TRACE_DBG("%p", rsp); + + rsp_hdr = (struct iscsi_scsi_rsp_hdr *)&rsp->pdu.bhs; + rsp_hdr->opcode = ISCSI_OP_SCSI_RSP; + rsp_hdr->flags = ISCSI_FLG_FINAL; + rsp_hdr->response = ISCSI_RESPONSE_COMMAND_COMPLETED; + rsp_hdr->cmd_status = status; + rsp_hdr->itt = cmnd_hdr(req)->itt; + + if (status == SAM_STAT_CHECK_CONDITION) { + TRACE_DBG("%s", "CHECK_CONDITION"); + /* ToDo: __GFP_NOFAIL ?? */ + sg = rsp->sg = scst_alloc(PAGE_SIZE, GFP_KERNEL|__GFP_NOFAIL, 0, + &rsp->sg_cnt); + if (sg == NULL) { + /* ToDo(); */ + } + rsp->own_sg = 1; + sense = (struct iscsi_sense_data *)page_address(sg[0].page); + sense->length = cpu_to_be16(sense_len); + memcpy(sense->data, sense_buf, sense_len); + rsp->pdu.datasize = sizeof(struct iscsi_sense_data) + sense_len; + rsp->bufflen = (rsp->pdu.datasize + 3) & -4; + if (rsp->bufflen - rsp->pdu.datasize) { + int i = rsp->pdu.datasize; + u8 *p = (u8 *)sense + i; + + while (i < rsp->bufflen) { + *p ++ = 0; + i++; + } + } + } else { + rsp->pdu.datasize = 0; + rsp->bufflen = 0; + } + + return rsp; +} + +static struct iscsi_cmnd *create_sense_rsp(struct iscsi_cmnd *req, + u8 sense_key, u8 asc, u8 ascq) +{ + u8 sense[14]; + memset(sense, 0, sizeof(sense)); + sense[0] = 0xf0; + sense[2] = sense_key; + sense[7] = 6; // Additional sense length + sense[12] = asc; + sense[13] = ascq; + return create_status_rsp(req, SAM_STAT_CHECK_CONDITION, sense, + sizeof(sense)); +} + +static void iscsi_cmnd_reject(struct iscsi_cmnd *req, int reason) +{ + struct iscsi_cmnd *rsp; + struct iscsi_reject_hdr *rsp_hdr; + struct scatterlist *sg; + char *addr; + + TRACE_MGMT_DBG("Reject: req %p, reason %x", req, reason); + + rsp = iscsi_cmnd_create_rsp_cmnd(req); + rsp_hdr = (struct iscsi_reject_hdr *)&rsp->pdu.bhs; + + rsp_hdr->opcode = ISCSI_OP_REJECT; + rsp_hdr->ffffffff = ISCSI_RESERVED_TAG; + rsp_hdr->reason = reason; + + /* ToDo: __GFP_NOFAIL ?? */ + sg = rsp->sg = scst_alloc(PAGE_SIZE, GFP_KERNEL|__GFP_NOFAIL, 0, + &rsp->sg_cnt); + if (sg == NULL) { + /* ToDo(); */ + } + rsp->own_sg = 1; + addr = page_address(sg[0].page); + clear_page(addr); + memcpy(addr, &req->pdu.bhs, sizeof(struct iscsi_hdr)); + rsp->bufflen = rsp->pdu.datasize = sizeof(struct iscsi_hdr); + cmnd_prepare_skip_pdu(req); + + req->pdu.bhs.opcode = ISCSI_OP_PDU_REJECT; +} + +static u32 cmnd_set_sn(struct iscsi_cmnd *cmnd, int set_stat_sn) +{ + struct iscsi_conn *conn = cmnd->conn; + struct iscsi_session *sess = conn->session; + u32 res; + + spin_lock(&sess->sn_lock); + + if (set_stat_sn) + cmnd->pdu.bhs.sn = cpu_to_be32(conn->stat_sn++); + cmnd->pdu.bhs.exp_sn = cpu_to_be32(sess->exp_cmd_sn); + cmnd->pdu.bhs.max_sn = cpu_to_be32(sess->exp_cmd_sn + sess->max_queued_cmnds); + + res = cpu_to_be32(conn->stat_sn); + + spin_unlock(&sess->sn_lock); + return res; +} + +/* Called under sn_lock */ +static void __update_stat_sn(struct iscsi_cmnd *cmnd) +{ + struct iscsi_conn *conn = cmnd->conn; + u32 exp_stat_sn; + + cmnd->pdu.bhs.exp_sn = exp_stat_sn = be32_to_cpu(cmnd->pdu.bhs.exp_sn); + TRACE_DBG("%x,%x", cmnd_opcode(cmnd), exp_stat_sn); + if ((int)(exp_stat_sn - conn->exp_stat_sn) > 0 && + (int)(exp_stat_sn - conn->stat_sn) <= 0) { + // free pdu resources + cmnd->conn->exp_stat_sn = exp_stat_sn; + } +} + +static inline void update_stat_sn(struct iscsi_cmnd *cmnd) +{ + spin_lock(&cmnd->conn->session->sn_lock); + __update_stat_sn(cmnd); + spin_unlock(&cmnd->conn->session->sn_lock); +} + +/* Called under sn_lock */ +static int check_cmd_sn(struct iscsi_cmnd *cmnd) +{ + struct iscsi_session *session = cmnd->conn->session; + u32 cmd_sn; + + cmnd->pdu.bhs.sn = cmd_sn = be32_to_cpu(cmnd->pdu.bhs.sn); + TRACE_DBG("%d(%d)", cmd_sn, session->exp_cmd_sn); + if ((s32)(cmd_sn - session->exp_cmd_sn) >= 0) + return 0; + PRINT_ERROR_PR("sequence error (%x,%x)", cmd_sn, session->exp_cmd_sn); + return -ISCSI_REASON_PROTOCOL_ERROR; +} + +static inline struct iscsi_cmnd *__cmnd_find_hash(struct iscsi_session *session, + u32 itt, u32 ttt) +{ + struct list_head *head; + struct iscsi_cmnd *cmnd; + + head = &session->cmnd_hash[cmnd_hashfn(itt)]; + + list_for_each_entry(cmnd, head, hash_list_entry) { + if (cmnd->pdu.bhs.itt == itt) { + if ((ttt != ISCSI_RESERVED_TAG) && (ttt != cmnd->target_task_tag)) + continue; + return cmnd; + } + } + return NULL; +} + +static struct iscsi_cmnd *cmnd_find_hash(struct iscsi_session *session, + u32 itt, u32 ttt) +{ + struct iscsi_cmnd *cmnd; + + spin_lock(&session->cmnd_hash_lock); + cmnd = __cmnd_find_hash(session, itt, ttt); + spin_unlock(&session->cmnd_hash_lock); + + return cmnd; +} + +static struct iscsi_cmnd *cmnd_find_hash_get(struct iscsi_session *session, + u32 itt, u32 ttt) +{ + struct iscsi_cmnd *cmnd; + + spin_lock(&session->cmnd_hash_lock); + cmnd = __cmnd_find_hash(session, itt, ttt); + cmnd_get(cmnd); + spin_unlock(&session->cmnd_hash_lock); + + return cmnd; +} + +static int cmnd_insert_hash(struct iscsi_cmnd *cmnd) +{ + struct iscsi_session *session = cmnd->conn->session; + struct iscsi_cmnd *tmp; + struct list_head *head; + int err = 0; + u32 itt = cmnd->pdu.bhs.itt; + + TRACE_DBG("%p:%x", cmnd, itt); + if (itt == ISCSI_RESERVED_TAG) { + err = -ISCSI_REASON_PROTOCOL_ERROR; + goto out; + } + + spin_lock(&session->cmnd_hash_lock); + + head = &session->cmnd_hash[cmnd_hashfn(cmnd->pdu.bhs.itt)]; + + tmp = __cmnd_find_hash(session, itt, ISCSI_RESERVED_TAG); + if (!tmp) { + list_add_tail(&cmnd->hash_list_entry, head); + cmnd->hashed = 1; + } else + err = -ISCSI_REASON_TASK_IN_PROGRESS; + + spin_unlock(&session->cmnd_hash_lock); + + if (!err) { + spin_lock(&session->sn_lock); + __update_stat_sn(cmnd); + err = check_cmd_sn(cmnd); + spin_unlock(&session->sn_lock); + } + +out: + return err; +} + +static void cmnd_remove_hash(struct iscsi_cmnd *cmnd) +{ + struct iscsi_session *session = cmnd->conn->session; + struct iscsi_cmnd *tmp; + + spin_lock(&session->cmnd_hash_lock); + + tmp = __cmnd_find_hash(session, cmnd->pdu.bhs.itt, ISCSI_RESERVED_TAG); + + if (tmp && tmp == cmnd) { + list_del(&cmnd->hash_list_entry); + cmnd->hashed = 0; + } else { + PRINT_ERROR_PR("%p:%x not found", cmnd, cmnd_itt(cmnd)); + } + + spin_unlock(&session->cmnd_hash_lock); +} + +static void cmnd_prepare_skip_pdu(struct iscsi_cmnd *cmnd) +{ + struct iscsi_conn *conn = cmnd->conn; + struct scatterlist *sg = cmnd->sg; + char *addr; + u32 size; + int i; + + TRACE_MGMT_DBG("Skipping (%p, %x %x %x %u, %p, scst state %d)", cmnd, + cmnd_itt(cmnd), cmnd_opcode(cmnd), cmnd_hdr(cmnd)->scb[0], + cmnd->pdu.datasize, cmnd->scst_cmd, cmnd->scst_state); + + iscsi_extracheck_is_rd_thread(conn); + + if (!(size = cmnd->pdu.datasize)) + return; + + if (sg == NULL) { + /* ToDo: __GFP_NOFAIL ?? */ + sg = cmnd->sg = scst_alloc(PAGE_SIZE, GFP_KERNEL|__GFP_NOFAIL, + 0, &cmnd->sg_cnt); + if (sg == NULL) { + /* ToDo(); */ + } + cmnd->own_sg = 1; + cmnd->bufflen = PAGE_SIZE; + } + + addr = page_address(sg[0].page); + sBUG_ON(addr == NULL); + size = (size + 3) & -4; + conn->read_size = size; + for (i = 0; size > PAGE_SIZE; i++, size -= cmnd->bufflen) { + sBUG_ON(i >= ISCSI_CONN_IOV_MAX); + conn->read_iov[i].iov_base = addr; + conn->read_iov[i].iov_len = cmnd->bufflen; + } + conn->read_iov[i].iov_base = addr; + conn->read_iov[i].iov_len = size; + conn->read_msg.msg_iov = conn->read_iov; + conn->read_msg.msg_iovlen = ++i; +} + +static void cmnd_prepare_skip_pdu_set_resid(struct iscsi_cmnd *req) +{ + struct iscsi_cmnd *rsp; + struct iscsi_scsi_rsp_hdr *rsp_hdr; + u32 size; + + TRACE_DBG("%p", req); + + rsp = get_rsp_cmnd(req); + rsp_hdr = (struct iscsi_scsi_rsp_hdr *)&rsp->pdu.bhs; + if (cmnd_opcode(rsp) != ISCSI_OP_SCSI_RSP) { + PRINT_ERROR_PR("unexpected response command %u", cmnd_opcode(rsp)); + return; + } + + size = cmnd_write_size(req); + if (size) { + rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_UNDERFLOW; + rsp_hdr->residual_count = cpu_to_be32(size); + } + size = cmnd_read_size(req); + if (size) { + if (cmnd_hdr(req)->flags & ISCSI_CMD_WRITE) { + rsp_hdr->flags |= ISCSI_FLG_BIRESIDUAL_UNDERFLOW; + rsp_hdr->bi_residual_count = cpu_to_be32(size); + } else { + rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_UNDERFLOW; + rsp_hdr->residual_count = cpu_to_be32(size); + } + } + req->pdu.bhs.opcode = + (req->pdu.bhs.opcode & ~ISCSI_OPCODE_MASK) | ISCSI_OP_SCSI_REJECT; + + cmnd_prepare_skip_pdu(req); +} + +static int cmnd_prepare_recv_pdu(struct iscsi_conn *conn, + struct iscsi_cmnd *cmd, u32 offset, u32 size) +{ + struct scatterlist *sg = cmd->sg; + int bufflen = cmd->bufflen; + int idx, i; + char *addr; + int res = 0; + + TRACE_DBG("%p %u,%u", cmd->sg, offset, size); + + iscsi_extracheck_is_rd_thread(conn); + + if ((offset >= bufflen) || + (offset + size > bufflen)) { + PRINT_ERROR_PR("Wrong ltn (%u %u %u)", offset, size, bufflen); + mark_conn_closed(conn); + res = -EIO; + goto out; + } + + offset += sg[0].offset; + idx = offset >> PAGE_SHIFT; + offset &= ~PAGE_MASK; + + conn->read_msg.msg_iov = conn->read_iov; + conn->read_size = size = (size + 3) & -4; + + i = 0; + while (1) { + sBUG_ON(sg[idx].page == NULL); + addr = page_address(sg[idx].page); + sBUG_ON(addr == NULL); + conn->read_iov[i].iov_base = addr + offset; + if (offset + size <= PAGE_SIZE) { + TRACE_DBG("idx=%d, offset=%u, size=%d, addr=%p", + idx, offset, size, addr); + conn->read_iov[i].iov_len = size; + conn->read_msg.msg_iovlen = ++i; + break; + } + conn->read_iov[i].iov_len = PAGE_SIZE - offset; + TRACE_DBG("idx=%d, offset=%u, size=%d, iov_len=%d, addr=%p", + idx, offset, size, conn->read_iov[i].iov_len, addr); + size -= conn->read_iov[i].iov_len; + offset = 0; + if (++i >= ISCSI_CONN_IOV_MAX) { + PRINT_ERROR_PR("Initiator %s violated negotiated " + "parameters by sending too much data (size " + "left %d)", conn->session->initiator_name, size); + mark_conn_closed(conn); + res = -EINVAL; + break; + } + idx++; + } + TRACE_DBG("msg_iov=%p, msg_iovlen=%d", + conn->read_msg.msg_iov, conn->read_msg.msg_iovlen); + +out: + return res; +} + +static void send_r2t(struct iscsi_cmnd *req) +{ + struct iscsi_session *session = req->conn->session; + struct iscsi_cmnd *rsp; + struct iscsi_r2t_hdr *rsp_hdr; + u32 length, offset, burst; + LIST_HEAD(send); + + /* + * There is no race with data_out_start() and __cmnd_abort(), since + * all functions called from single read thread + */ + iscsi_extracheck_is_rd_thread(req->conn); + + length = req->r2t_length; + burst = session->sess_param.max_burst_length; + offset = be32_to_cpu(cmnd_hdr(req)->data_length) - length; + + do { + rsp = iscsi_cmnd_create_rsp_cmnd(req); + rsp->pdu.bhs.ttt = req->target_task_tag; + rsp_hdr = (struct iscsi_r2t_hdr *)&rsp->pdu.bhs; + rsp_hdr->opcode = ISCSI_OP_R2T; + rsp_hdr->flags = ISCSI_FLG_FINAL; + memcpy(rsp_hdr->lun, cmnd_hdr(req)->lun, 8); + rsp_hdr->itt = cmnd_hdr(req)->itt; + rsp_hdr->r2t_sn = cpu_to_be32(req->r2t_sn++); + rsp_hdr->buffer_offset = cpu_to_be32(offset); + if (length > burst) { + rsp_hdr->data_length = cpu_to_be32(burst); + length -= burst; + offset += burst; + } else { + rsp_hdr->data_length = cpu_to_be32(length); + length = 0; + } + + TRACE(TRACE_D_WRITE, "%x %u %u %u %u", cmnd_itt(req), + be32_to_cpu(rsp_hdr->data_length), + be32_to_cpu(rsp_hdr->buffer_offset), + be32_to_cpu(rsp_hdr->r2t_sn), req->outstanding_r2t); + + list_add_tail(&rsp->write_list_entry, &send); + + if (++req->outstanding_r2t >= session->sess_param.max_outstanding_r2t) + break; + + } while (length); + + iscsi_cmnds_init_write(&send, ISCSI_INIT_WRITE_WAKE); + + req->data_waiting = 1; +} + +static int iscsi_pre_exec(struct scst_cmd *scst_cmd) +{ + int res = SCST_PREPROCESS_STATUS_SUCCESS; + struct iscsi_cmnd *req = (struct iscsi_cmnd*) + scst_cmd_get_tgt_priv(scst_cmd); + struct iscsi_cmnd *c, *t; + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(scst_cmd_atomic(scst_cmd)); + + /* If data digest isn't used this list will be empty */ + list_for_each_entry_safe(c, t, &req->rx_ddigest_cmd_list, + rx_ddigest_cmd_list_entry) { + TRACE_DBG("Checking digest of RX ddigest cmd %p", c); + if (digest_rx_data(c) != 0) { + scst_set_cmd_error(scst_cmd, + SCST_LOAD_SENSE(iscsi_sense_crc_error)); + res = SCST_PREPROCESS_STATUS_ERROR_SENSE_SET; + /* + * The rest of rx_ddigest_cmd_list will be freed + * in req_cmnd_release() + */ + goto out; + } + TRACE_DBG("Deleting RX digest cmd %p from digest list", c); + list_del(&c->rx_ddigest_cmd_list_entry); + cmnd_put(c); + } + +out: + TRACE_EXIT_RES(res); + return res; +} + + +static void scsi_cmnd_exec(struct iscsi_cmnd *cmnd) +{ + if (cmnd->r2t_length) { + if (!cmnd->is_unsolicited_data) + send_r2t(cmnd); + } else { + /* + * There is no race with send_r2t() and __cmnd_abort(), + * since all functions called from single read thread + */ + cmnd->data_waiting = 0; + iscsi_restart_cmnd(cmnd); + } +} + +static int noop_out_start(struct iscsi_cmnd *cmnd) +{ + struct iscsi_conn *conn = cmnd->conn; + u32 size, tmp; + int i, err = 0; + + TRACE_DBG("%p", cmnd); + + iscsi_extracheck_is_rd_thread(conn); + + if (cmnd_ttt(cmnd) != cpu_to_be32(ISCSI_RESERVED_TAG)) { + /* + * We don't request a NOP-Out by sending a NOP-In. + * See 10.18.2 in the draft 20. + */ + PRINT_ERROR_PR("initiator bug %x", cmnd_itt(cmnd)); + err = -ISCSI_REASON_PROTOCOL_ERROR; + goto out; + } + + if (cmnd_itt(cmnd) == cpu_to_be32(ISCSI_RESERVED_TAG)) { + if (!(cmnd->pdu.bhs.opcode & ISCSI_OP_IMMEDIATE)) + PRINT_ERROR_PR("%s","initiator bug!"); + spin_lock(&conn->session->sn_lock); + __update_stat_sn(cmnd); + err = check_cmd_sn(cmnd); + spin_unlock(&conn->session->sn_lock); + if (err) + goto out; + } else if ((err = cmnd_insert_hash(cmnd)) < 0) { + PRINT_ERROR_PR("Can't insert in hash: ignore this request %x", + cmnd_itt(cmnd)); + goto out; + } + + if ((size = cmnd->pdu.datasize)) { + size = (size + 3) & -4; + conn->read_msg.msg_iov = conn->read_iov; + if (cmnd->pdu.bhs.itt != cpu_to_be32(ISCSI_RESERVED_TAG)) { + struct scatterlist *sg; + + /* ToDo: __GFP_NOFAIL ?? */ + cmnd->sg = sg = scst_alloc(size, + GFP_KERNEL|__GFP_NOFAIL, 0, &cmnd->sg_cnt); + if (sg == NULL) { + /* ToDo(); */ + } + if (cmnd->sg_cnt > ISCSI_CONN_IOV_MAX) { + /* ToDo(); */ + } + cmnd->own_sg = 1; + cmnd->bufflen = size; + + for (i = 0; i < cmnd->sg_cnt; i++) { + conn->read_iov[i].iov_base = + page_address(sg[i].page); + tmp = min_t(u32, size, PAGE_SIZE); + conn->read_iov[i].iov_len = tmp; + conn->read_size += tmp; + size -= tmp; + } + } else { + /* + * There are no problems with the safety from concurrent + * accesses to dummy_data, since for ISCSI_RESERVED_TAG + * the data only read and then discarded. + */ + for (i = 0; i < ISCSI_CONN_IOV_MAX; i++) { + conn->read_iov[i].iov_base = dummy_data; + tmp = min_t(u32, size, sizeof(dummy_data)); + conn->read_iov[i].iov_len = tmp; + conn->read_size += tmp; + size -= tmp; + } + } + sBUG_ON(size == 0); + conn->read_msg.msg_iovlen = i; + TRACE_DBG("msg_iov=%p, msg_iovlen=%d", conn->read_msg.msg_iov, + conn->read_msg.msg_iovlen); + } +out: + return err; +} + +static inline u32 get_next_ttt(struct iscsi_conn *conn) +{ + u32 ttt; + struct iscsi_session *session = conn->session; + + iscsi_extracheck_is_rd_thread(conn); + + if (session->next_ttt == ISCSI_RESERVED_TAG) + session->next_ttt++; + ttt = session->next_ttt++; + + return cpu_to_be32(ttt); +} + +static int scsi_cmnd_start(struct iscsi_cmnd *req) +{ + struct iscsi_conn *conn = req->conn; + struct iscsi_session *session = conn->session; + struct iscsi_scsi_cmd_hdr *req_hdr = cmnd_hdr(req); + struct scst_cmd *scst_cmd; + scst_data_direction dir; + int res = 0; + + TRACE_ENTRY(); + + TRACE_DBG("scsi command: %02x", req_hdr->scb[0]); + + scst_cmd = scst_rx_cmd(session->scst_sess, + (uint8_t*)req_hdr->lun, sizeof(req_hdr->lun), + req_hdr->scb, sizeof(req_hdr->scb), SCST_NON_ATOMIC); + if (scst_cmd == NULL) { + create_status_rsp(req, SAM_STAT_BUSY, NULL, 0); + cmnd_prepare_skip_pdu_set_resid(req); + goto out; + } + + req->scst_cmd = scst_cmd; + scst_cmd_set_tag(scst_cmd, req_hdr->itt); + scst_cmd_set_tgt_priv(scst_cmd, req); +#ifndef NET_PAGE_CALLBACKS_DEFINED + scst_cmd_set_data_buf_tgt_alloc(scst_cmd); +#endif + + if (req_hdr->flags & ISCSI_CMD_READ) + dir = SCST_DATA_READ; + else if (req_hdr->flags & ISCSI_CMD_WRITE) + dir = SCST_DATA_WRITE; + else + dir = SCST_DATA_NONE; + scst_cmd_set_expected(scst_cmd, dir, be32_to_cpu(req_hdr->data_length)); + + switch(req_hdr->flags & ISCSI_CMD_ATTR_MASK) { + case ISCSI_CMD_SIMPLE: + scst_cmd->queue_type = SCST_CMD_QUEUE_SIMPLE; + break; + case ISCSI_CMD_HEAD_OF_QUEUE: + scst_cmd->queue_type = SCST_CMD_QUEUE_HEAD_OF_QUEUE; + break; + case ISCSI_CMD_ORDERED: + scst_cmd->queue_type = SCST_CMD_QUEUE_ORDERED; + break; + case ISCSI_CMD_ACA: + scst_cmd->queue_type = SCST_CMD_QUEUE_ACA; + break; + case ISCSI_CMD_UNTAGGED: + scst_cmd->queue_type = SCST_CMD_QUEUE_UNTAGGED; + break; + default: + PRINT_ERROR_PR("Unknown task code %x, use ORDERED instead", + req_hdr->flags & ISCSI_CMD_ATTR_MASK); + scst_cmd->queue_type = SCST_CMD_QUEUE_ORDERED; + break; + } + + TRACE_DBG("START Command (tag %d, queue_type %d)", + req_hdr->itt, scst_cmd->queue_type); + req->scst_state = ISCSI_CMD_STATE_RX_CMD; + scst_cmd_init_stage1_done(scst_cmd, SCST_CONTEXT_DIRECT, 0); + + wait_event(req->scst_waitQ, (req->scst_state != ISCSI_CMD_STATE_RX_CMD)); + + if (unlikely(req->scst_state != ISCSI_CMD_STATE_AFTER_PREPROC)) { + TRACE_DBG("req %p is in %x state", req, req->scst_state); + if (req->scst_state == ISCSI_CMD_STATE_PROCESSED) { + /* Response is already prepared */ + cmnd_prepare_skip_pdu_set_resid(req); + goto out; + } + if (unlikely(req->tmfabort)) { + TRACE_MGMT_DBG("req %p (scst_cmd %p) aborted", req, + req->scst_cmd); + cmnd_prepare_skip_pdu(req); + goto out; + } + sBUG(); + } + + dir = scst_cmd_get_data_direction(scst_cmd); + if (dir != SCST_DATA_WRITE) { + if (!(req_hdr->flags & ISCSI_CMD_FINAL) || req->pdu.datasize) { + PRINT_ERROR_PR("Unexpected unsolicited data (ITT %x " + "CDB %x", cmnd_itt(req), req_hdr->scb[0]); + create_sense_rsp(req, ABORTED_COMMAND, 0xc, 0xc); + cmnd_prepare_skip_pdu_set_resid(req); + goto out; + } + } + + if (dir == SCST_DATA_WRITE) { + req->is_unsolicited_data = !(req_hdr->flags & ISCSI_CMD_FINAL); + req->r2t_length = be32_to_cpu(req_hdr->data_length) - req->pdu.datasize; + } + req->target_task_tag = get_next_ttt(conn); + req->sg = scst_cmd_get_sg(scst_cmd); + req->bufflen = scst_cmd_get_bufflen(scst_cmd); + if (req->r2t_length > req->bufflen) { + PRINT_ERROR_PR("req->r2t_length %d > req->bufflen %d", + req->r2t_length, req->bufflen); + req->r2t_length = req->bufflen; + } + + TRACE_DBG("req=%p, dir=%d, is_unsolicited_data=%d, " + "r2t_length=%d, bufflen=%d", req, dir, + req->is_unsolicited_data, req->r2t_length, req->bufflen); + + if (!session->sess_param.immediate_data && + req->pdu.datasize) { + PRINT_ERROR_PR("Initiator %s violated negotiated paremeters: " + "forbidden immediate data sent (ITT %x, op %x)", + session->initiator_name, cmnd_itt(req), req_hdr->scb[0]); + res = -EINVAL; + goto out; + } + + if (session->sess_param.initial_r2t && + !(req_hdr->flags & ISCSI_CMD_FINAL)) { + PRINT_ERROR_PR("Initiator %s violated negotiated paremeters: " + "initial R2T is required (ITT %x, op %x)", + session->initiator_name, cmnd_itt(req), req_hdr->scb[0]); + res = -EINVAL; + goto out; + } + + if (req->pdu.datasize) { + if (dir != SCST_DATA_WRITE) { + PRINT_ERROR_PR("pdu.datasize(%d) >0, but dir(%x) isn't WRITE", + req->pdu.datasize, dir); + create_sense_rsp(req, ABORTED_COMMAND, 0xc, 0xc); + cmnd_prepare_skip_pdu_set_resid(req); + } else + res = cmnd_prepare_recv_pdu(conn, req, 0, req->pdu.datasize); + } +out: + /* Aborted commands will be freed in cmnd_rx_end() */ + TRACE_EXIT_RES(res); + return res; +} + +static int data_out_start(struct iscsi_conn *conn, struct iscsi_cmnd *cmnd) +{ + struct iscsi_data_out_hdr *req_hdr = (struct iscsi_data_out_hdr *)&cmnd->pdu.bhs; + struct iscsi_cmnd *req = NULL; + u32 offset = be32_to_cpu(req_hdr->buffer_offset); + int res = 0; + + TRACE_ENTRY(); + + /* + * There is no race with send_r2t() and __cmnd_abort(), since + * all functions called from single read thread + */ + iscsi_extracheck_is_rd_thread(cmnd->conn); + + update_stat_sn(cmnd); + + cmnd->cmd_req = req = cmnd_find_hash(conn->session, req_hdr->itt, + req_hdr->ttt); + if (!req) { + PRINT_ERROR_PR("unable to find scsi task %x %x", + cmnd_itt(cmnd), cmnd_ttt(cmnd)); + goto skip_pdu; + } + + if (req->r2t_length < cmnd->pdu.datasize) { + PRINT_ERROR_PR("Invalid data len %x %u %u", cmnd_itt(req), + cmnd->pdu.datasize, req->r2t_length); + mark_conn_closed(conn); + res = -EINVAL; + goto out; + } + + if (req->r2t_length + offset != cmnd_write_size(req)) { + PRINT_ERROR_PR("Wrong cmd lengths (%x %u %u %u)", + cmnd_itt(req), req->r2t_length, + offset, cmnd_write_size(req)); + mark_conn_closed(conn); + res = -EINVAL; + goto out; + } + + req->r2t_length -= cmnd->pdu.datasize; + + /* Check unsolicited burst data */ + if ((req_hdr->ttt == cpu_to_be32(ISCSI_RESERVED_TAG)) && + (req->pdu.bhs.flags & ISCSI_FLG_FINAL)) { + PRINT_ERROR_PR("unexpected data from %x %x", + cmnd_itt(cmnd), cmnd_ttt(cmnd)); + mark_conn_closed(conn); + res = -EINVAL; + goto out; + } + + TRACE(TRACE_D_WRITE, "%u %p %p %u %u", req_hdr->ttt, cmnd, req, + offset, cmnd->pdu.datasize); + + res = cmnd_prepare_recv_pdu(conn, req, offset, cmnd->pdu.datasize); + +out: + TRACE_EXIT_RES(res); + return res; + +skip_pdu: + cmnd->pdu.bhs.opcode = ISCSI_OP_DATA_REJECT; + cmnd_prepare_skip_pdu(cmnd); + goto out; +} + +static void data_out_end(struct iscsi_cmnd *cmnd) +{ + struct iscsi_data_out_hdr *req_hdr = (struct iscsi_data_out_hdr *)&cmnd->pdu.bhs; + struct iscsi_cmnd *req; + + sBUG_ON(cmnd == NULL); + req = cmnd->cmd_req; + sBUG_ON(req == NULL); + + TRACE_DBG("cmnd %p, req %p", cmnd, req); + + iscsi_extracheck_is_rd_thread(cmnd->conn); + + if (!(cmnd->conn->ddigest_type & DIGEST_NONE)) { + TRACE_DBG("Adding RX ddigest cmd %p to digest list " + "of req %p", cmnd, req); + list_add_tail(&cmnd->rx_ddigest_cmd_list_entry, + &req->rx_ddigest_cmd_list); + cmnd_get(cmnd); + } + + if (req_hdr->ttt == cpu_to_be32(ISCSI_RESERVED_TAG)) { + TRACE_DBG("ISCSI_RESERVED_TAG, FINAL %x", + req_hdr->flags & ISCSI_FLG_FINAL); + if (req_hdr->flags & ISCSI_FLG_FINAL) { + req->is_unsolicited_data = 0; + if (!req->pending) + scsi_cmnd_exec(req); + } + } else { + TRACE_DBG("FINAL %x, outstanding_r2t %d, " + "r2t_length %d", req_hdr->flags & ISCSI_FLG_FINAL, + req->outstanding_r2t, req->r2t_length); + /* ToDo : proper error handling */ + if (!(req_hdr->flags & ISCSI_FLG_FINAL) && (req->r2t_length == 0)) + PRINT_ERROR_PR("initiator error %x", cmnd_itt(req)); + + if (!(req_hdr->flags & ISCSI_FLG_FINAL)) + goto out; + + req->outstanding_r2t--; + + scsi_cmnd_exec(req); + } + +out: + cmnd_put(cmnd); + return; +} + +/* + * Called under cmd_list_lock, but may drop it inside. + * Returns >0 if cmd_list_lock was dropped inside, 0 otherwise. + */ +static inline int __cmnd_abort(struct iscsi_cmnd *cmnd) +{ + int res = 0; + + TRACE(TRACE_MGMT, "Aborting cmd %p, scst_cmd %p (scst state %x, " + "itt %x, op %x, r2t_len %x, CDB op %x, size to write %u, " + "is_unsolicited_data %u, outstanding_r2t %u)", + cmnd, cmnd->scst_cmd, cmnd->scst_state, cmnd_itt(cmnd), + cmnd_opcode(cmnd), cmnd->r2t_length, cmnd_scsicode(cmnd), + cmnd_write_size(cmnd), cmnd->is_unsolicited_data, + cmnd->outstanding_r2t); + + iscsi_extracheck_is_rd_thread(cmnd->conn); + + cmnd->tmfabort = 1; + + if (cmnd->data_waiting) { + struct iscsi_conn *conn = cmnd->conn; + res = 1; + spin_unlock_bh(&conn->cmd_list_lock); + TRACE_MGMT_DBG("Releasing data waiting cmd %p", cmnd); + req_cmnd_release_force(cmnd, ISCSI_FORCE_RELEASE_WRITE); + spin_lock_bh(&conn->cmd_list_lock); + } + + return res; +} + +static int cmnd_abort(struct iscsi_session *session, u32 itt) +{ + struct iscsi_cmnd *cmnd; + int err; + + if ((cmnd = cmnd_find_hash_get(session, itt, ISCSI_RESERVED_TAG))) { + struct iscsi_conn *conn = cmnd->conn; + spin_lock_bh(&conn->cmd_list_lock); + __cmnd_abort(cmnd); + spin_unlock_bh(&conn->cmd_list_lock); + cmnd_put(cmnd); + err = 0; + } else + err = ISCSI_RESPONSE_UNKNOWN_TASK; + + return err; +} + +static int target_abort(struct iscsi_cmnd *req, u16 *lun, int all) +{ + struct iscsi_target *target = req->conn->session->target; + struct iscsi_session *session; + struct iscsi_conn *conn; + struct iscsi_cmnd *cmnd; + + mutex_lock(&target->target_mutex); + + list_for_each_entry(session, &target->session_list, session_list_entry) { + list_for_each_entry(conn, &session->conn_list, conn_list_entry) { + spin_lock_bh(&conn->cmd_list_lock); +again: + list_for_each_entry(cmnd, &conn->cmd_list, cmd_list_entry) { + int again = 0; + if (cmnd == req) + continue; + if (all) + again = __cmnd_abort(cmnd); + else if (memcmp(lun, &cmnd_hdr(cmnd)->lun, + sizeof(cmnd_hdr(cmnd)->lun)) == 0) + again = __cmnd_abort(cmnd); + if (again) + goto again; + } + spin_unlock_bh(&conn->cmd_list_lock); + } + } + + mutex_unlock(&target->target_mutex); + return 0; +} + +static void task_set_abort(struct iscsi_cmnd *req) +{ + struct iscsi_session *session = req->conn->session; + struct iscsi_target *target = session->target; + struct iscsi_conn *conn; + struct iscsi_cmnd *cmnd; + + mutex_lock(&target->target_mutex); + + list_for_each_entry(conn, &session->conn_list, conn_list_entry) { + spin_lock_bh(&conn->cmd_list_lock); +again: + list_for_each_entry(cmnd, &conn->cmd_list, cmd_list_entry) { + if (cmnd != req) + if (__cmnd_abort(cmnd)) + goto again; + } + spin_unlock_bh(&conn->cmd_list_lock); + } + + mutex_unlock(&target->target_mutex); + return; +} + +void conn_abort(struct iscsi_conn *conn) +{ + struct iscsi_cmnd *cmnd; + + TRACE_MGMT_DBG("Aborting conn %p", conn); + + spin_lock_bh(&conn->cmd_list_lock); +again: + list_for_each_entry(cmnd, &conn->cmd_list, cmd_list_entry) { + if (__cmnd_abort(cmnd)) + goto again; + } + spin_unlock_bh(&conn->cmd_list_lock); +} + +static void execute_task_management(struct iscsi_cmnd *req) +{ + struct iscsi_conn *conn = req->conn; + struct iscsi_task_mgt_hdr *req_hdr = (struct iscsi_task_mgt_hdr *)&req->pdu.bhs; + int err = 0, function = req_hdr->function & ISCSI_FUNCTION_MASK; + + TRACE(TRACE_MGMT, "TM cmd: req %p, itt %x, fn %d, rtt %x", req, cmnd_itt(req), + function, req_hdr->rtt); + + switch (function) { + case ISCSI_FUNCTION_ABORT_TASK: + err = cmnd_abort(conn->session, req_hdr->rtt); + if (err == 0) { + err = scst_rx_mgmt_fn_tag(conn->session->scst_sess, + SCST_ABORT_TASK, req_hdr->rtt, SCST_NON_ATOMIC, + req); + } + break; + case ISCSI_FUNCTION_ABORT_TASK_SET: + task_set_abort(req); + err = scst_rx_mgmt_fn_lun(conn->session->scst_sess, + SCST_ABORT_TASK_SET, (uint8_t *)req_hdr->lun, + sizeof(req_hdr->lun), SCST_NON_ATOMIC, req); + break; + case ISCSI_FUNCTION_CLEAR_TASK_SET: + task_set_abort(req); + err = scst_rx_mgmt_fn_lun(conn->session->scst_sess, + SCST_CLEAR_TASK_SET, (uint8_t *)req_hdr->lun, + sizeof(req_hdr->lun), SCST_NON_ATOMIC, req); + break; + case ISCSI_FUNCTION_CLEAR_ACA: + err = scst_rx_mgmt_fn_lun(conn->session->scst_sess, + SCST_CLEAR_ACA, (uint8_t *)req_hdr->lun, + sizeof(req_hdr->lun), SCST_NON_ATOMIC, req); + break; + case ISCSI_FUNCTION_TARGET_COLD_RESET: + case ISCSI_FUNCTION_TARGET_WARM_RESET: + target_abort(req, 0, 1); + err = scst_rx_mgmt_fn_lun(conn->session->scst_sess, + SCST_TARGET_RESET, (uint8_t *)req_hdr->lun, + sizeof(req_hdr->lun), SCST_NON_ATOMIC, req); + break; + case ISCSI_FUNCTION_LOGICAL_UNIT_RESET: + target_abort(req, req_hdr->lun, 0); + err = scst_rx_mgmt_fn_lun(conn->session->scst_sess, + SCST_LUN_RESET, (uint8_t *)req_hdr->lun, + sizeof(req_hdr->lun), SCST_NON_ATOMIC, req); + break; + case ISCSI_FUNCTION_TASK_REASSIGN: + iscsi_send_task_mgmt_resp(req, + ISCSI_RESPONSE_FUNCTION_UNSUPPORTED); + break; + default: + iscsi_send_task_mgmt_resp(req, + ISCSI_RESPONSE_FUNCTION_REJECTED); + break; + } + + if (err != 0) { + iscsi_send_task_mgmt_resp(req, + ISCSI_RESPONSE_FUNCTION_REJECTED); + } +} + +static void noop_out_exec(struct iscsi_cmnd *req) +{ + struct iscsi_cmnd *rsp; + struct iscsi_nop_in_hdr *rsp_hdr; + + TRACE_DBG("%p", req); + + if (cmnd_itt(req) != cpu_to_be32(ISCSI_RESERVED_TAG)) { + rsp = iscsi_cmnd_create_rsp_cmnd(req); + + rsp_hdr = (struct iscsi_nop_in_hdr *)&rsp->pdu.bhs; + rsp_hdr->opcode = ISCSI_OP_NOOP_IN; + rsp_hdr->flags = ISCSI_FLG_FINAL; + rsp_hdr->itt = req->pdu.bhs.itt; + rsp_hdr->ttt = cpu_to_be32(ISCSI_RESERVED_TAG); + + if (req->pdu.datasize) + sBUG_ON(req->sg == NULL); + else + sBUG_ON(req->sg != NULL); + + if (req->sg) { + rsp->sg = req->sg; + rsp->bufflen = req->bufflen; + } + + sBUG_ON(get_pgcnt(req->pdu.datasize, 0) > ISCSI_CONN_IOV_MAX); + rsp->pdu.datasize = req->pdu.datasize; + iscsi_cmnd_init_write(rsp, + ISCSI_INIT_WRITE_REMOVE_HASH | ISCSI_INIT_WRITE_WAKE); + req_cmnd_release(req); + } else + cmnd_put(req); +} + +static void logout_exec(struct iscsi_cmnd *req) +{ + struct iscsi_logout_req_hdr *req_hdr; + struct iscsi_cmnd *rsp; + struct iscsi_logout_rsp_hdr *rsp_hdr; + + PRINT_INFO_PR("Logout received from initiator %s", + req->conn->session->initiator_name); + TRACE_DBG("%p", req); + + req_hdr = (struct iscsi_logout_req_hdr *)&req->pdu.bhs; + rsp = iscsi_cmnd_create_rsp_cmnd(req); + rsp_hdr = (struct iscsi_logout_rsp_hdr *)&rsp->pdu.bhs; + rsp_hdr->opcode = ISCSI_OP_LOGOUT_RSP; + rsp_hdr->flags = ISCSI_FLG_FINAL; + rsp_hdr->itt = req_hdr->itt; + rsp->should_close_conn = 1; + iscsi_cmnd_init_write(rsp, + ISCSI_INIT_WRITE_REMOVE_HASH | ISCSI_INIT_WRITE_WAKE); + req_cmnd_release(req); +} + +static void iscsi_cmnd_exec(struct iscsi_cmnd *cmnd) +{ + TRACE_DBG("%p,%x,%u", cmnd, cmnd_opcode(cmnd), cmnd->pdu.bhs.sn); + + if (unlikely(cmnd->tmfabort)) { + TRACE_MGMT_DBG("cmnd %p (scst_cmd %p) aborted", cmnd, + cmnd->scst_cmd); + req_cmnd_release_force(cmnd, ISCSI_FORCE_RELEASE_WRITE); + goto out; + } + + switch (cmnd_opcode(cmnd)) { + case ISCSI_OP_NOOP_OUT: + noop_out_exec(cmnd); + break; + case ISCSI_OP_SCSI_CMD: + scsi_cmnd_exec(cmnd); + break; + case ISCSI_OP_SCSI_TASK_MGT_MSG: + execute_task_management(cmnd); + break; + case ISCSI_OP_LOGOUT_CMD: + logout_exec(cmnd); + break; + case ISCSI_OP_SCSI_REJECT: + TRACE_MGMT_DBG("REJECT cmnd %p (scst_cmd %p)", cmnd, + cmnd->scst_cmd); + iscsi_cmnd_init_write(get_rsp_cmnd(cmnd), + ISCSI_INIT_WRITE_REMOVE_HASH | ISCSI_INIT_WRITE_WAKE); + req_cmnd_release(cmnd); + break; + default: + PRINT_ERROR_PR("unexpected cmnd op %x", cmnd_opcode(cmnd)); + req_cmnd_release(cmnd); + break; + } +out: + return; +} + +static void __cmnd_send_pdu(struct iscsi_conn *conn, struct iscsi_cmnd *cmnd, + u32 offset, u32 size) +{ + TRACE_DBG("%p %u,%u,%u", cmnd, offset, size, cmnd->bufflen); + + iscsi_extracheck_is_wr_thread(conn); + + sBUG_ON(offset > cmnd->bufflen); + sBUG_ON(offset + size > cmnd->bufflen); + + conn->write_offset = offset; + conn->write_size += size; +} + +static void cmnd_send_pdu(struct iscsi_conn *conn, struct iscsi_cmnd *cmnd) +{ + u32 size; + + if (!cmnd->pdu.datasize) + return; + + size = (cmnd->pdu.datasize + 3) & -4; + sBUG_ON(cmnd->sg == NULL); + sBUG_ON(cmnd->bufflen != size); + __cmnd_send_pdu(conn, cmnd, 0, size); +} + +static void set_cork(struct socket *sock, int on) +{ + int opt = on; + mm_segment_t oldfs; + + oldfs = get_fs(); + set_fs(get_ds()); + sock->ops->setsockopt(sock, SOL_TCP, TCP_CORK, (void *)&opt, sizeof(opt)); + set_fs(oldfs); +} + +void cmnd_tx_start(struct iscsi_cmnd *cmnd) +{ + struct iscsi_conn *conn = cmnd->conn; + + TRACE_DBG("%p:%p:%x", conn, cmnd, cmnd_opcode(cmnd)); + iscsi_cmnd_set_length(&cmnd->pdu); + + iscsi_extracheck_is_wr_thread(conn); + + set_cork(conn->sock, 1); + + conn->write_iop = conn->write_iov; + conn->write_iop->iov_base = &cmnd->pdu.bhs; + conn->write_iop->iov_len = sizeof(cmnd->pdu.bhs); + conn->write_iop_used = 1; + conn->write_size = sizeof(cmnd->pdu.bhs); + + switch (cmnd_opcode(cmnd)) { + case ISCSI_OP_NOOP_IN: + cmnd_set_sn(cmnd, 1); + cmnd_send_pdu(conn, cmnd); + break; + case ISCSI_OP_SCSI_RSP: + cmnd_set_sn(cmnd, 1); + cmnd_send_pdu(conn, cmnd); + break; + case ISCSI_OP_SCSI_TASK_MGT_RSP: + cmnd_set_sn(cmnd, 1); + break; + case ISCSI_OP_TEXT_RSP: + cmnd_set_sn(cmnd, 1); + break; + case ISCSI_OP_SCSI_DATA_IN: + { + struct iscsi_data_in_hdr *rsp = (struct iscsi_data_in_hdr *)&cmnd->pdu.bhs; + u32 offset = cpu_to_be32(rsp->buffer_offset); + + cmnd_set_sn(cmnd, (rsp->flags & ISCSI_FLG_FINAL) ? 1 : 0); + __cmnd_send_pdu(conn, cmnd, offset, cmnd->pdu.datasize); + break; + } + case ISCSI_OP_LOGOUT_RSP: + cmnd_set_sn(cmnd, 1); + break; + case ISCSI_OP_R2T: + cmnd->pdu.bhs.sn = cmnd_set_sn(cmnd, 0); + break; + case ISCSI_OP_ASYNC_MSG: + cmnd_set_sn(cmnd, 1); + break; + case ISCSI_OP_REJECT: + cmnd_set_sn(cmnd, 1); + cmnd_send_pdu(conn, cmnd); + break; + default: + PRINT_ERROR_PR("unexpected cmnd op %x", cmnd_opcode(cmnd)); + break; + } + + // move this? + conn->write_size = (conn->write_size + 3) & -4; + iscsi_dump_pdu(&cmnd->pdu); +} + +void cmnd_tx_end(struct iscsi_cmnd *cmnd) +{ + struct iscsi_conn *conn = cmnd->conn; + + TRACE_DBG("%p:%x (should_close_conn %d)", cmnd, cmnd_opcode(cmnd), + cmnd->should_close_conn); + + switch (cmnd_opcode(cmnd)) { + case ISCSI_OP_NOOP_IN: + case ISCSI_OP_SCSI_RSP: + case ISCSI_OP_SCSI_TASK_MGT_RSP: + case ISCSI_OP_TEXT_RSP: + case ISCSI_OP_R2T: + case ISCSI_OP_ASYNC_MSG: + case ISCSI_OP_REJECT: + case ISCSI_OP_SCSI_DATA_IN: + case ISCSI_OP_LOGOUT_RSP: + break; + default: + PRINT_ERROR_PR("unexpected cmnd op %x", cmnd_opcode(cmnd)); + sBUG(); + break; + } + + if (cmnd->should_close_conn) { + PRINT_INFO_PR("Closing connection at initiator %s request", + conn->session->initiator_name); + mark_conn_closed(conn); + } + + set_cork(cmnd->conn->sock, 0); +} + +/* + * Push the command for execution. This functions reorders the commands. + * Called from the read thread. + */ +static void iscsi_session_push_cmnd(struct iscsi_cmnd *cmnd) +{ + struct iscsi_session *session = cmnd->conn->session; + struct list_head *entry; + u32 cmd_sn; + + TRACE_DBG("%p:%x %u,%u", + cmnd, cmnd_opcode(cmnd), cmnd->pdu.bhs.sn, session->exp_cmd_sn); + + iscsi_extracheck_is_rd_thread(cmnd->conn); + + if (cmnd->pdu.bhs.opcode & ISCSI_OP_IMMEDIATE) { + iscsi_cmnd_exec(cmnd); + return; + } + + spin_lock(&session->sn_lock); + + cmd_sn = cmnd->pdu.bhs.sn; + if (cmd_sn == session->exp_cmd_sn) { + while (1) { + session->exp_cmd_sn = ++cmd_sn; + + spin_unlock(&session->sn_lock); + + iscsi_cmnd_exec(cmnd); + + if (list_empty(&session->pending_list)) + break; + cmnd = list_entry(session->pending_list.next, struct iscsi_cmnd, + pending_list_entry); + if (cmnd->pdu.bhs.sn != cmd_sn) + break; + list_del(&cmnd->pending_list_entry); + cmnd->pending = 0; + + spin_lock(&session->sn_lock); + } + } else { + cmnd->pending = 1; + if (before(cmd_sn, session->exp_cmd_sn)) { /* close the conn */ + PRINT_ERROR_PR("unexpected cmd_sn (%u,%u)", cmd_sn, + session->exp_cmd_sn); + } + + if (after(cmd_sn, session->exp_cmd_sn + session->max_queued_cmnds)) { + PRINT_ERROR_PR("too large cmd_sn (%u,%u)", cmd_sn, + session->exp_cmd_sn); + } + + spin_unlock(&session->sn_lock); + + list_for_each(entry, &session->pending_list) { + struct iscsi_cmnd *tmp = list_entry(entry, struct iscsi_cmnd, + pending_list_entry); + if (before(cmd_sn, tmp->pdu.bhs.sn)) + break; + } + + list_add_tail(&cmnd->pending_list_entry, entry); + } + + return; +} + +static int check_segment_length(struct iscsi_cmnd *cmnd) +{ + struct iscsi_conn *conn = cmnd->conn; + struct iscsi_session *session = conn->session; + + if (cmnd->pdu.datasize > session->sess_param.max_recv_data_length) { + PRINT_ERROR_PR("Initiator %s violated negotiated parameters: " + "data too long (ITT %x, datasize %u, " + "max_recv_data_length %u", session->initiator_name, + cmnd_itt(cmnd), cmnd->pdu.datasize, + session->sess_param.max_recv_data_length); + mark_conn_closed(conn); + return -EINVAL; + } + return 0; +} + +int cmnd_rx_start(struct iscsi_cmnd *cmnd) +{ + struct iscsi_conn *conn = cmnd->conn; + int res, rc; + + iscsi_dump_pdu(&cmnd->pdu); + + res = check_segment_length(cmnd); + if (res != 0) + goto out; + + switch (cmnd_opcode(cmnd)) { + case ISCSI_OP_NOOP_OUT: + rc = noop_out_start(cmnd); + break; + case ISCSI_OP_SCSI_CMD: + rc = cmnd_insert_hash(cmnd); + if (likely(rc == 0)) { + res = scsi_cmnd_start(cmnd); + if (unlikely(res != 0)) + goto out; + } + break; + case ISCSI_OP_SCSI_TASK_MGT_MSG: + rc = cmnd_insert_hash(cmnd); + break; + case ISCSI_OP_SCSI_DATA_OUT: + res = data_out_start(conn, cmnd); + rc = 0; /* to avoid compiler warning */ + if (unlikely(res != 0)) + goto out; + break; + case ISCSI_OP_LOGOUT_CMD: + rc = cmnd_insert_hash(cmnd); + break; + case ISCSI_OP_TEXT_CMD: + case ISCSI_OP_SNACK_CMD: + rc = -ISCSI_REASON_UNSUPPORTED_COMMAND; + break; + default: + rc = -ISCSI_REASON_UNSUPPORTED_COMMAND; + break; + } + + if (unlikely(rc < 0)) { + struct iscsi_scsi_cmd_hdr *hdr = cmnd_hdr(cmnd); + PRINT_ERROR_PR("Error %d (iSCSI opcode %x, ITT %x, op %x)", rc, + cmnd_opcode(cmnd), cmnd_itt(cmnd), + (cmnd_opcode(cmnd) == ISCSI_OP_SCSI_CMD ? + hdr->scb[0] : -1)); + iscsi_cmnd_reject(cmnd, -rc); + } + +out: + TRACE_EXIT_RES(res); + return res; +} + +void cmnd_rx_end(struct iscsi_cmnd *cmnd) +{ + if (unlikely(cmnd->tmfabort)) { + TRACE_MGMT_DBG("cmnd %p (scst_cmd %p) aborted", cmnd, + cmnd->scst_cmd); + req_cmnd_release_force(cmnd, ISCSI_FORCE_RELEASE_WRITE); + return; + } + + TRACE_DBG("%p:%x", cmnd, cmnd_opcode(cmnd)); + switch (cmnd_opcode(cmnd)) { + case ISCSI_OP_SCSI_REJECT: + case ISCSI_OP_NOOP_OUT: + case ISCSI_OP_SCSI_CMD: + case ISCSI_OP_SCSI_TASK_MGT_MSG: + case ISCSI_OP_LOGOUT_CMD: + iscsi_session_push_cmnd(cmnd); + break; + case ISCSI_OP_SCSI_DATA_OUT: + data_out_end(cmnd); + break; + case ISCSI_OP_PDU_REJECT: + iscsi_cmnd_init_write(get_rsp_cmnd(cmnd), + ISCSI_INIT_WRITE_REMOVE_HASH | ISCSI_INIT_WRITE_WAKE); + req_cmnd_release(cmnd); + break; + case ISCSI_OP_DATA_REJECT: + req_cmnd_release(cmnd); + break; + default: + PRINT_ERROR_PR("unexpected cmnd op %x", cmnd_opcode(cmnd)); + req_cmnd_release(cmnd); + break; + } +} + +#ifndef NET_PAGE_CALLBACKS_DEFINED +static int iscsi_alloc_data_buf(struct scst_cmd *cmd) +{ + if (scst_cmd_get_data_direction(cmd) == SCST_DATA_READ) { + /* + * sock->ops->sendpage() is async zero copy operation, + * so we must be sure not to free and reuse + * the command's buffer before the sending was completed + * by the network layers. It is possible only if we + * don't use SGV cache. + */ + scst_cmd_set_no_sgv(cmd); + } + return 1; +} +#endif + +static inline void iscsi_set_state_wake_up(struct iscsi_cmnd *req, + int new_state) +{ + /* + * We use wait_event() to wait for the state change, but it checks its + * condition without any protection, so without cmnd_get() it is + * possible that req will die "immediately" after the state assignment + * and wake_up() will operate on dead data. + */ + cmnd_get_ordered(req); + req->scst_state = new_state; + wake_up(&req->scst_waitQ); + cmnd_put(req); + return; +} + +static void iscsi_preprocessing_done(struct scst_cmd *scst_cmd) +{ + struct iscsi_cmnd *req = (struct iscsi_cmnd*) + scst_cmd_get_tgt_priv(scst_cmd); + + TRACE_DBG("req %p", req); + + EXTRACHECKS_BUG_ON(req->scst_state != ISCSI_CMD_STATE_RX_CMD); + + iscsi_set_state_wake_up(req, ISCSI_CMD_STATE_AFTER_PREPROC); + return; +} + +static void iscsi_try_local_processing(struct iscsi_conn *conn) +{ + int local; + + TRACE_ENTRY(); + + spin_lock_bh(&iscsi_wr_lock); + switch(conn->wr_state) { + case ISCSI_CONN_WR_STATE_IN_LIST: + list_del(&conn->wr_list_entry); + /* go through */ + case ISCSI_CONN_WR_STATE_IDLE: +#ifdef EXTRACHECKS + conn->wr_task = current; +#endif + conn->wr_state = ISCSI_CONN_WR_STATE_PROCESSING; + conn->wr_space_ready = 0; + local = 1; + break; + default: + local = 0; + break; + } + spin_unlock_bh(&iscsi_wr_lock); + + if (local) { + int rc = 1; + while(test_write_ready(conn)) { + rc = iscsi_send(conn); + if (rc <= 0) { + break; + } + } + + spin_lock_bh(&iscsi_wr_lock); +#ifdef EXTRACHECKS + conn->wr_task = NULL; +#endif + if ((rc <= 0) || test_write_ready(conn)) { + list_add_tail(&conn->wr_list_entry, &iscsi_wr_list); + conn->wr_state = ISCSI_CONN_WR_STATE_IN_LIST; + wake_up(&iscsi_wr_waitQ); + } else + conn->wr_state = ISCSI_CONN_WR_STATE_IDLE; + spin_unlock_bh(&iscsi_wr_lock); + } + + TRACE_EXIT(); + return; +} + +static int iscsi_xmit_response(struct scst_cmd *scst_cmd) +{ + int resp_flags = scst_cmd_get_tgt_resp_flags(scst_cmd); + struct iscsi_cmnd *req = (struct iscsi_cmnd*) + scst_cmd_get_tgt_priv(scst_cmd); + struct iscsi_conn *conn = req->conn; + int status = scst_cmd_get_status(scst_cmd); + u8 *sense = scst_cmd_get_sense_buffer(scst_cmd); + int sense_len = scst_cmd_get_sense_buffer_len(scst_cmd); + int old_state = req->scst_state; + + scst_cmd_set_tgt_priv(scst_cmd, NULL); + + req->tmfabort |= scst_cmd_aborted(scst_cmd) ? 1 : 0; + if (unlikely(req->tmfabort)) { + TRACE_MGMT_DBG("req %p (scst_cmd %p) aborted", req, + req->scst_cmd); + if (old_state == ISCSI_CMD_STATE_RESTARTED) { + req->scst_state = ISCSI_CMD_STATE_PROCESSED; + req_cmnd_release_force(req, ISCSI_FORCE_RELEASE_WRITE); + } else + iscsi_set_state_wake_up(req, ISCSI_CMD_STATE_PROCESSED); + goto out; + } + + if (unlikely(old_state != ISCSI_CMD_STATE_RESTARTED)) { + TRACE_DBG("req %p on %d state", req, old_state); + create_status_rsp(req, status, sense, sense_len); + switch(old_state) { + case ISCSI_CMD_STATE_RX_CMD: + case ISCSI_CMD_STATE_AFTER_PREPROC: + break; + default: + sBUG(); + } + iscsi_set_state_wake_up(req, ISCSI_CMD_STATE_PROCESSED); + goto out; + } + + req->scst_state = ISCSI_CMD_STATE_PROCESSED; + + req->bufflen = scst_cmd_get_resp_data_len(scst_cmd); + req->sg = scst_cmd_get_sg(scst_cmd); + + TRACE_DBG("req %p, resp_flags=%x, req->bufflen=%d, req->sg=%p", req, + resp_flags, req->bufflen, req->sg); + + if ((req->bufflen != 0) && !(resp_flags & SCST_TSC_FLAG_STATUS)) { + PRINT_ERROR_PR("%s", "Sending DATA without STATUS is unsupported"); + scst_set_cmd_error(scst_cmd, + SCST_LOAD_SENSE(scst_sense_hardw_error)); + resp_flags = scst_cmd_get_tgt_resp_flags(scst_cmd); + sBUG(); + } + + if (req->bufflen != 0) { + /* + * Check above makes sure that SCST_TSC_FLAG_STATUS is set, + * so status is valid here, but in future that could change. + * ToDo + */ + if (status != SAM_STAT_CHECK_CONDITION) { + send_data_rsp(req, status, + resp_flags & SCST_TSC_FLAG_STATUS); + } else { + struct iscsi_cmnd *rsp; + struct iscsi_scsi_rsp_hdr *rsp_hdr; + int resid; + send_data_rsp(req, 0, 0); + if (resp_flags & SCST_TSC_FLAG_STATUS) { + rsp = create_status_rsp(req, status, sense, + sense_len); + rsp_hdr = (struct iscsi_scsi_rsp_hdr *)&rsp->pdu.bhs; + resid = cmnd_read_size(req) - req->bufflen; + if (resid > 0) { + rsp_hdr->flags |= + ISCSI_FLG_RESIDUAL_UNDERFLOW; + rsp_hdr->residual_count = cpu_to_be32(resid); + } else if (resid < 0) { + rsp_hdr->flags |= + ISCSI_FLG_RESIDUAL_OVERFLOW; + rsp_hdr->residual_count = cpu_to_be32(-resid); + } + iscsi_cmnd_init_write(rsp, + ISCSI_INIT_WRITE_REMOVE_HASH); + } + } + } else if (resp_flags & SCST_TSC_FLAG_STATUS) { + struct iscsi_cmnd *rsp; + struct iscsi_scsi_rsp_hdr *rsp_hdr; + u32 resid; + rsp = create_status_rsp(req, status, sense, sense_len); + rsp_hdr = (struct iscsi_scsi_rsp_hdr *) &rsp->pdu.bhs; + resid = cmnd_read_size(req); + if (resid != 0) { + rsp_hdr->flags |= ISCSI_FLG_RESIDUAL_UNDERFLOW; + rsp_hdr->residual_count = cpu_to_be32(resid); + } + iscsi_cmnd_init_write(rsp, ISCSI_INIT_WRITE_REMOVE_HASH); + } +#ifdef EXTRACHECKS + else + sBUG(); +#endif + + atomic_inc(&conn->conn_ref_cnt); + smp_mb__after_atomic_inc(); + + req_cmnd_release(req); + + iscsi_try_local_processing(conn); + + smp_mb__before_atomic_dec(); + atomic_dec(&conn->conn_ref_cnt); + +out: + return SCST_TGT_RES_SUCCESS; +} + +static void iscsi_send_task_mgmt_resp(struct iscsi_cmnd *req, int status) +{ + struct iscsi_cmnd *rsp; + struct iscsi_task_mgt_hdr *req_hdr = + (struct iscsi_task_mgt_hdr *)&req->pdu.bhs; + struct iscsi_task_rsp_hdr *rsp_hdr; + + TRACE(TRACE_MGMT, "req %p, status %d", req, status); + + rsp = iscsi_cmnd_create_rsp_cmnd(req); + rsp_hdr = (struct iscsi_task_rsp_hdr *)&rsp->pdu.bhs; + + rsp_hdr->opcode = ISCSI_OP_SCSI_TASK_MGT_RSP; + rsp_hdr->flags = ISCSI_FLG_FINAL; + rsp_hdr->itt = req_hdr->itt; + rsp_hdr->response = status; + + if ((req_hdr->function & ISCSI_FUNCTION_MASK) == + ISCSI_FUNCTION_TARGET_COLD_RESET) + rsp->should_close_conn = 1; + + iscsi_cmnd_init_write(rsp, + ISCSI_INIT_WRITE_REMOVE_HASH | ISCSI_INIT_WRITE_WAKE); + req_cmnd_release(req); +} + +static inline int iscsi_get_mgmt_response(int status) +{ + switch(status) { + case SCST_MGMT_STATUS_SUCCESS: + return ISCSI_RESPONSE_FUNCTION_COMPLETE; + + case SCST_MGMT_STATUS_TASK_NOT_EXIST: + return ISCSI_RESPONSE_UNKNOWN_TASK; + + case SCST_MGMT_STATUS_LUN_NOT_EXIST: + return ISCSI_RESPONSE_UNKNOWN_LUN; + + case SCST_MGMT_STATUS_FN_NOT_SUPPORTED: + return ISCSI_RESPONSE_FUNCTION_UNSUPPORTED; + + case SCST_MGMT_STATUS_REJECTED: + case SCST_MGMT_STATUS_FAILED: + default: + return ISCSI_RESPONSE_FUNCTION_REJECTED; + } +} + +static void iscsi_task_mgmt_fn_done(struct scst_mgmt_cmd *scst_mcmd) +{ + struct iscsi_cmnd *req = (struct iscsi_cmnd*) + scst_mgmt_cmd_get_tgt_priv(scst_mcmd); + int status = iscsi_get_mgmt_response(scst_mgmt_cmd_get_status(scst_mcmd)); + + TRACE(TRACE_MGMT, "scst_mcmd %p, status %d", scst_mcmd, + scst_mgmt_cmd_get_status(scst_mcmd)); + + iscsi_send_task_mgmt_resp(req, status); + + scst_mgmt_cmd_set_tgt_priv(scst_mcmd, NULL); +} + +static int iscsi_target_detect(struct scst_tgt_template *templ) +{ + /* Nothing to do */ + return 0; +} + +static int iscsi_target_release(struct scst_tgt *scst_tgt) +{ + /* Nothing to do */ + return 0; +} + +struct scst_tgt_template iscsi_template = { + .name = "iscsi", + .sg_tablesize = ISCSI_CONN_IOV_MAX, + .threads_num = 0, + .no_clustering = 1, + .xmit_response_atomic = 0, + .preprocessing_done_atomic = 1, + .detect = iscsi_target_detect, + .release = iscsi_target_release, + .xmit_response = iscsi_xmit_response, +#ifndef NET_PAGE_CALLBACKS_DEFINED + .alloc_data_buf = iscsi_alloc_data_buf, +#endif + .preprocessing_done = iscsi_preprocessing_done, + .pre_exec = iscsi_pre_exec, + .task_mgmt_fn_done = iscsi_task_mgmt_fn_done, +}; + +static __init int iscsi_run_threads(int count, char *name, int (*fn)(void *)) +{ + int res = 0; + int i; + struct iscsi_thread_t *thr; + + for (i = 0; i < count; i++) { + thr = kmalloc(sizeof(*thr), GFP_KERNEL); + if (!thr) { + res = -ENOMEM; + PRINT_ERROR_PR("Failed to allocate thr %d", res); + goto out; + } + thr->thr = kthread_run(fn, NULL, "%s%d", name, i); + if (IS_ERR(thr->thr)) { + res = PTR_ERR(thr->thr); + PRINT_ERROR_PR("kthread_create() failed: %d", res); + kfree(thr); + goto out; + } + list_add(&thr->threads_list_entry, &iscsi_threads_list); + } + +out: + return res; +} + +static void iscsi_stop_threads(void) +{ + struct iscsi_thread_t *t, *tmp; + + list_for_each_entry_safe(t, tmp, &iscsi_threads_list, + threads_list_entry) { + int rc = kthread_stop(t->thr); + if (rc < 0) { + TRACE_MGMT_DBG("kthread_stop() failed: %d", rc); + } + list_del(&t->threads_list_entry); + kfree(t); + } +} + +static int __init iscsi_init(void) +{ + int err; + int num; + + PRINT_INFO_PR("iSCSI SCST Target - version %s", ISCSI_VERSION_STRING); + +#ifdef NET_PAGE_CALLBACKS_DEFINED + err = net_set_get_put_page_callbacks(iscsi_get_page_callback, + iscsi_put_page_callback); + if (err != 0) { + PRINT_INFO_PR("Unable to set page callbackes: %d", err); + goto out; + } +#else + PRINT_INFO_PR("%s", "Patch put_page_callback.patch not applied on your " + "kernel. Running in the performance degraded mode. Refer " + "README file for details"); +#endif + + BUILD_BUG_ON(MAX_DATA_SEG_LEN != (ISCSI_CONN_IOV_MAX< + * + * Released under the terms of the GNU GPL v2.0. + */ + +#ifndef __ISCSI_H__ +#define __ISCSI_H__ + +#include +#include +#include +#include +#include + +#include + +#include "iscsi_hdr.h" +#include "iscsi_u.h" + +#include "iscsi_dbg.h" + +#define iscsi_sense_crc_error ABORTED_COMMAND, 0x47, 0x5 + +struct iscsi_sess_param { + int initial_r2t; + int immediate_data; + int max_connections; + int max_recv_data_length; + int max_xmit_data_length; + int max_burst_length; + int first_burst_length; + int default_wait_time; + int default_retain_time; + int max_outstanding_r2t; + int data_pdu_inorder; + int data_sequence_inorder; + int error_recovery_level; + int header_digest; + int data_digest; + int ofmarker; + int ifmarker; + int ofmarkint; + int ifmarkint; +}; + +struct iscsi_trgt_param { + int queued_cmnds; +}; + +struct network_thread_info { + struct task_struct *task; + unsigned int ready; +}; + +struct iscsi_cmnd; + +struct iscsi_target { + struct scst_tgt *scst_tgt; + + struct mutex target_mutex; + + struct list_head session_list; /* protected by target_mutex */ + + /* Both protected by target_mutex */ + struct iscsi_sess_param trgt_sess_param; + struct iscsi_trgt_param trgt_param; + + struct list_head target_list_entry; + u32 tid; + char name[ISCSI_NAME_LEN]; +}; + +#define ISCSI_HASH_ORDER 8 +#define cmnd_hashfn(itt) hash_long((itt), ISCSI_HASH_ORDER) + +struct iscsi_session { + struct iscsi_target *target; + struct scst_session *scst_sess; + + /* All 2 unprotected, since accessed only from a single read thread */ + struct list_head pending_list; + u32 next_ttt; + + /* Both unprotected, since read-only */ + u32 max_queued_cmnds; + u32 max_cmd_sn; /* Not used ??, ToDo */ + + spinlock_t sn_lock; + u32 exp_cmd_sn; /* protected by sn_lock */ + + /* read only, if there are connection(s) */ + struct iscsi_sess_param sess_param; + + spinlock_t cmnd_hash_lock; + struct list_head cmnd_hash[1 << ISCSI_HASH_ORDER]; + + struct list_head conn_list; /* protected by target_mutex */ + + struct list_head session_list_entry; + + /* Bot don't need any protection */ + char *initiator_name; + u64 sid; +}; + +#define ISCSI_CONN_IOV_MAX (PAGE_SIZE/sizeof(struct iovec)) + +#define ISCSI_CONN_RD_STATE_IDLE 0 +#define ISCSI_CONN_RD_STATE_IN_LIST 1 +#define ISCSI_CONN_RD_STATE_PROCESSING 2 + +#define ISCSI_CONN_WR_STATE_IDLE 0 +#define ISCSI_CONN_WR_STATE_IN_LIST 1 +#define ISCSI_CONN_WR_STATE_SPACE_WAIT 2 +#define ISCSI_CONN_WR_STATE_PROCESSING 3 + +struct iscsi_conn { + struct iscsi_session *session; /* owning session */ + + /* Both protected by session->sn_lock */ + u32 stat_sn; + u32 exp_stat_sn; + + spinlock_t cmd_list_lock; /* BH lock */ + + /* + * IMPORTANT! If you find a cmd in cmd_list and immediately get_cmnd() + * it, it still can be destroyed immediately after you drop + * cmd_list_lock no matter how big is its ref_cnt! + */ + + /* Protected by cmd_list_lock */ + struct list_head cmd_list; /* in/outcoming pdus */ + + atomic_t conn_ref_cnt; + + spinlock_t write_list_lock; + /* List of data pdus to be sent, protected by write_list_lock */ + struct list_head write_list; + + /* All 2 protected by iscsi_wr_lock */ + unsigned short wr_state; + unsigned short wr_space_ready:1; + + struct list_head wr_list_entry; + +#ifdef EXTRACHECKS + struct task_struct *wr_task; +#endif + + /* All are unprotected, since accessed only from a single write thread */ + struct iscsi_cmnd *write_cmnd; + struct iovec *write_iop; + int write_iop_used; + struct iovec write_iov[2]; + u32 write_size; + u32 write_offset; + int write_state; + + /* Both don't need any protection */ + struct file *file; + struct socket *sock; + + void (*old_state_change)(struct sock *); + void (*old_data_ready)(struct sock *, int); + void (*old_write_space)(struct sock *); + + /* Both read only */ + int hdigest_type; + int ddigest_type; + + /* All 4 protected by iscsi_rd_lock */ + unsigned short rd_state; + unsigned short rd_data_ready:1; + unsigned short closing:1; /* Let's save some cache footprint by putting it here */ + + struct list_head rd_list_entry; + +#ifdef EXTRACHECKS + struct task_struct *rd_task; +#endif + + /* All are unprotected, since accessed only from a single read thread */ + struct iscsi_cmnd *read_cmnd; + struct msghdr read_msg; + u32 read_size; + int read_state; + struct iovec *read_iov; + + struct iscsi_target *target; + + struct list_head conn_list_entry; /* list entry in session conn_list */ + + /* Doesn't need any protection */ + u16 cid; +}; + +struct iscsi_pdu { + struct iscsi_hdr bhs; + void *ahs; + unsigned int ahssize; + unsigned int datasize; +}; + +typedef void (iscsi_show_info_t)(struct seq_file *seq, struct iscsi_target *target); + +/* Command's states */ +#define ISCSI_CMD_STATE_NEW 0 /* New command and SCST processes it */ +#define ISCSI_CMD_STATE_RX_CMD 1 /* SCST processes cmd after scst_rx_cmd() */ +#define ISCSI_CMD_STATE_AFTER_PREPROC 2 /* The command returned from preprocessing_done() */ +#define ISCSI_CMD_STATE_RESTARTED 3 /* scst_restart_cmd() called and SCST processing it */ +#define ISCSI_CMD_STATE_PROCESSED 4 /* SCST done processing */ + +/* + * Most of the fields don't need any protection, since accessed from only a + * single thread, except where noted. + */ +struct iscsi_cmnd { + struct iscsi_conn *conn; + + /* Some flags protected by conn->write_list_lock */ + unsigned int hashed:1; + unsigned int should_close_conn:1; + unsigned int pending:1; + unsigned int own_sg:1; + unsigned int on_write_list:1; + unsigned int write_processing_started:1; + unsigned int data_waiting:1; + unsigned int force_cleanup_done:1; +#ifdef EXTRACHECKS + unsigned int release_called:1; +#endif + + unsigned long tmfabort; /* it's async. with the above flags */ + + struct list_head hash_list_entry; + + spinlock_t rsp_cmd_lock; /* BH lock */ + + /* Unions are for readability and grepability */ + + /* + * IMPORTANT! If you find a cmd in rsp_cmd_list and immediately + * get_cmnd() it, it still can be destroyed immediately after you drop + * rsp_cmd_lock no matter how big is its ref_cnt! + */ + + union { + /* Protected by rsp_cmd_lock */ + struct list_head rsp_cmd_list; + struct list_head rsp_cmd_list_entry; + }; + + union { + struct list_head pending_list_entry; + struct list_head write_list_entry; + }; + + /* + * Unprotected, since could be accessed from only a single + * thread at time + */ + struct list_head rx_ddigest_cmd_list; + struct list_head rx_ddigest_cmd_list_entry; + + struct iscsi_cmnd *parent_req; + struct iscsi_cmnd *cmd_req; + + struct iscsi_target *target; + + wait_queue_head_t scst_waitQ; + int scst_state; + struct scst_cmd *scst_cmd; + atomic_t ref_cnt; +#ifdef NET_PAGE_CALLBACKS_DEFINED + atomic_t net_ref_cnt; +#endif + + struct iscsi_pdu pdu; + + struct scatterlist *sg; + int bufflen; + u32 r2t_sn; + u32 r2t_length; + u32 is_unsolicited_data; + u32 target_task_tag; + u32 outstanding_r2t; + + u32 hdigest; + u32 ddigest; + + int sg_cnt; /* valid only if own_sg is 1 */ + struct list_head cmd_list_entry; +}; + +#define ISCSI_OP_SCSI_REJECT ISCSI_OP_VENDOR1_CMD +#define ISCSI_OP_PDU_REJECT ISCSI_OP_VENDOR2_CMD +#define ISCSI_OP_DATA_REJECT ISCSI_OP_VENDOR3_CMD + +/* Flags for req_cmnd_release_force() */ +#define ISCSI_FORCE_RELEASE_WRITE 1 + +extern struct mutex target_mgmt_mutex; + +extern struct file_operations ctr_fops; + +extern spinlock_t iscsi_rd_lock; +extern struct list_head iscsi_rd_list; +extern wait_queue_head_t iscsi_rd_waitQ; + +extern spinlock_t iscsi_wr_lock; +extern struct list_head iscsi_wr_list; +extern wait_queue_head_t iscsi_wr_waitQ; + +/* iscsi.c */ +extern struct iscsi_cmnd *cmnd_alloc(struct iscsi_conn *, + struct iscsi_cmnd *parent); +extern int cmnd_rx_start(struct iscsi_cmnd *); +extern void cmnd_rx_end(struct iscsi_cmnd *); +extern void cmnd_tx_start(struct iscsi_cmnd *); +extern void cmnd_tx_end(struct iscsi_cmnd *); +extern void req_cmnd_release(struct iscsi_cmnd *req); +extern void req_cmnd_release_force(struct iscsi_cmnd *req, int flags); +extern void rsp_cmnd_release(struct iscsi_cmnd *); +extern void cmnd_done(struct iscsi_cmnd *cmnd); +extern void conn_abort(struct iscsi_conn *conn); + +/* conn.c */ +extern struct iscsi_conn *conn_lookup(struct iscsi_session *, u16); +extern int conn_add(struct iscsi_session *, struct conn_info *); +extern int conn_del(struct iscsi_session *, struct conn_info *); +extern int conn_free(struct iscsi_conn *); +extern void mark_conn_closed(struct iscsi_conn *); +extern void iscsi_make_conn_wr_active(struct iscsi_conn *); +extern void conn_info_show(struct seq_file *, struct iscsi_session *); + +/* nthread.c */ +extern int iscsi_send(struct iscsi_conn *conn); +#ifdef NET_PAGE_CALLBACKS_DEFINED +extern void iscsi_get_page_callback(struct page *page); +extern void iscsi_put_page_callback(struct page *page); +#endif +extern int istrd(void *arg); +extern int istwr(void *arg); + +/* target.c */ +struct iscsi_target *target_lookup_by_id(u32); +extern int target_add(struct target_info *); +extern int target_del(u32 id); +extern void target_del_all(void); + +/* config.c */ +extern int iscsi_procfs_init(void); +extern void iscsi_procfs_exit(void); +extern int iscsi_info_show(struct seq_file *, iscsi_show_info_t *); + +/* session.c */ +extern struct file_operations session_seq_fops; +extern struct iscsi_session *session_lookup(struct iscsi_target *, u64); +extern int session_add(struct iscsi_target *, struct session_info *); +extern int session_del(struct iscsi_target *, u64); +extern int session_free(struct iscsi_session *session); + +/* params.c */ +extern int iscsi_param_set(struct iscsi_target *, struct iscsi_param_info *, int); + +/* event.c */ +extern int event_send(u32, u64, u32, u32, int); +extern int event_init(void); +extern void event_exit(void); + +#define get_pgcnt(size, offset) ((((size) + ((offset) & ~PAGE_MASK)) + PAGE_SIZE - 1) >> PAGE_SHIFT) + +static inline void iscsi_cmnd_get_length(struct iscsi_pdu *pdu) +{ +#if defined(__BIG_ENDIAN) + pdu->ahssize = pdu->bhs.length.ahslength * 4; + pdu->datasize = pdu->bhs.length.datalength; +#elif defined(__LITTLE_ENDIAN) + pdu->ahssize = (pdu->bhs.length & 0xff) * 4; + pdu->datasize = be32_to_cpu(pdu->bhs.length & ~0xff); +#else +#error +#endif +} + +static inline void iscsi_cmnd_set_length(struct iscsi_pdu *pdu) +{ +#if defined(__BIG_ENDIAN) + pdu->bhs.length.ahslength = pdu->ahssize / 4; + pdu->bhs.length.datalength = pdu->datasize; +#elif defined(__LITTLE_ENDIAN) + pdu->bhs.length = cpu_to_be32(pdu->datasize) | (pdu->ahssize / 4); +#else +#error +#endif +} + +#define cmnd_hdr(cmnd) ((struct iscsi_scsi_cmd_hdr *) (&((cmnd)->pdu.bhs))) +#define cmnd_ttt(cmnd) cpu_to_be32((cmnd)->pdu.bhs.ttt) +#define cmnd_itt(cmnd) cpu_to_be32((cmnd)->pdu.bhs.itt) +#define cmnd_opcode(cmnd) ((cmnd)->pdu.bhs.opcode & ISCSI_OPCODE_MASK) +#define cmnd_scsicode(cmnd) cmnd_hdr(cmnd)->scb[0] + +extern struct scst_tgt_template iscsi_template; + +static inline void cmnd_get(struct iscsi_cmnd *cmnd) +{ + atomic_inc(&cmnd->ref_cnt); + TRACE_DBG("cmnd %p, new cmnd->ref_cnt %d", cmnd, + atomic_read(&cmnd->ref_cnt)); +} + +static inline void cmnd_get_ordered(struct iscsi_cmnd *cmnd) +{ + cmnd_get(cmnd); + smp_mb__after_atomic_inc(); +} + +static inline void cmnd_put(struct iscsi_cmnd *cmnd) +{ + TRACE_DBG("cmnd %p, new cmnd->ref_cnt %d", cmnd, + atomic_read(&cmnd->ref_cnt)-1); + sBUG_ON(atomic_read(&cmnd->ref_cnt) == 0); + if (atomic_dec_and_test(&cmnd->ref_cnt)) + cmnd_done(cmnd); +} + +/* conn->write_list_lock supposed to be locked */ +static inline void cmd_add_on_write_list(struct iscsi_conn *conn, + struct iscsi_cmnd *cmnd) +{ + TRACE_DBG("%p", cmnd); + list_add_tail(&cmnd->write_list_entry, &conn->write_list); + cmnd->on_write_list = 1; +} + +/* conn->write_list_lock supposed to be locked */ +static inline void cmd_del_from_write_list(struct iscsi_cmnd *cmnd) +{ + TRACE_DBG("%p", cmnd); + list_del(&cmnd->write_list_entry); + cmnd->on_write_list = 0; +} + +static inline int test_write_ready(struct iscsi_conn *conn) +{ + /* + * No need for write_list protection, in the worst case we will be + * restarted again. + */ + return (!list_empty(&conn->write_list) || conn->write_cmnd); +} + +#ifdef EXTRACHECKS +#define iscsi_extracheck_is_rd_thread(conn) sBUG_ON(current != (conn)->rd_task) +#define iscsi_extracheck_is_wr_thread(conn) sBUG_ON(current != (conn)->wr_task) +#else +static inline void iscsi_extracheck_is_rd_thread(struct iscsi_conn *conn) {} +static inline void iscsi_extracheck_is_wr_thread(struct iscsi_conn *conn) {} +#endif + +#endif /* __ISCSI_H__ */ diff --git a/iscsi-scst/kernel/iscsi_dbg.h b/iscsi-scst/kernel/iscsi_dbg.h new file mode 100644 index 000000000..c55f90f50 --- /dev/null +++ b/iscsi-scst/kernel/iscsi_dbg.h @@ -0,0 +1,59 @@ +#ifndef ISCSI_DBG_H +#define ISCSI_DBG_H + +#include + +#define TRACE_D_READ 0x80000000 +#define TRACE_D_WRITE 0x40000000 +#define TRACE_CONN_OC 0x20000000 +#define TRACE_D_IOV 0x10000000 +#define TRACE_D_DUMP_PDU 0x08000000 + +#define TRACE_D_DATA (TRACE_D_READ | TRACE_D_WRITE) + +#define TRACE_ALL_NO_DATA (TRACE_ALL & ~TRACE_D_IOV & ~TRACE_D_DUMP_PDU & ~TRACE_D_DATA) + +#define LOG_PREFIX "iscsi-scst" + +#ifdef DEBUG +#define ISCSI_DEFAULT_LOG_FLAGS (TRACE_FUNCTION | TRACE_LINE | TRACE_PID | \ + TRACE_OUT_OF_MEM | TRACE_MGMT | TRACE_MGMT_DEBUG | \ + TRACE_MINOR | TRACE_SPECIAL | TRACE_CONN_OC) +#else +#define ISCSI_DEFAULT_LOG_FLAGS (TRACE_FUNCTION | TRACE_PID | \ + TRACE_OUT_OF_MEM | TRACE_MGMT | \ + TRACE_MINOR | TRACE_SPECIAL) +#endif + +#ifdef DEBUG +struct msghdr; +struct iscsi_pdu; +extern void iscsi_dump_iov(struct msghdr *msg); +extern void iscsi_dump_pdu(struct iscsi_pdu *pdu); +#else +#define iscsi_dump_iov(x) do {} while (0) +#define iscsi_dump_pdu(x) do {} while (0) +#endif + +#if defined(DEBUG) || defined(TRACING) +extern unsigned long iscsi_trace_flag; +#define trace_flag iscsi_trace_flag + +#define TRACE_CONN_CLOSE(format, args...) \ +do { \ + if (trace_flag & TRACE_CONN_OC) \ + { \ + char *__tflag = LOG_FLAG; \ + if (debug_print_prefix(trace_flag, __FUNCTION__, __LINE__) > 0) \ + { \ + __tflag = NO_FLAG; \ + } \ + PRINT(NO_FLAG, "%s" format, __tflag, args); \ + } \ +} while(0) + +#else /* defined(DEBUG) || defined(TRACING) */ +#define TRACE_CONN_CLOSE(format, args...) {} +#endif + +#endif diff --git a/iscsi-scst/kernel/iscsi_hdr.h b/iscsi-scst/kernel/iscsi_hdr.h new file mode 100644 index 000000000..1233dd21e --- /dev/null +++ b/iscsi-scst/kernel/iscsi_hdr.h @@ -0,0 +1,509 @@ +/* + * Copyright (C) 2002-2003 Ardis Technolgies + * + * Released under the terms of the GNU GPL v2.0. + */ + +#ifndef __ISCSI_HDR_H__ +#define __ISCSI_HDR_H__ + +#include +#include + +#define ISCSI_VERSION 0 + +#ifndef __packed +#define __packed __attribute__ ((packed)) +#endif + +struct iscsi_hdr { + u8 opcode; /* 0 */ + u8 flags; + u8 spec1[2]; +#if defined(__BIG_ENDIAN_BITFIELD) + struct { /* 4 */ + unsigned ahslength : 8; + unsigned datalength : 24; + } length; +#elif defined(__LITTLE_ENDIAN_BITFIELD) + u32 length; /* 4 */ +#endif + u16 lun[4]; /* 8 */ + u32 itt; /* 16 */ + u32 ttt; /* 20 */ + u32 sn; /* 24 */ + u32 exp_sn; /* 28 */ + u32 max_sn; /* 32 */ + u32 spec3[3]; /* 36 */ +} __packed; /* 48 */ + +/* Opcode encoding bits */ +#define ISCSI_OP_RETRY 0x80 +#define ISCSI_OP_IMMEDIATE 0x40 +#define ISCSI_OPCODE_MASK 0x3F + +/* Client to Server Message Opcode values */ +#define ISCSI_OP_NOOP_OUT 0x00 +#define ISCSI_OP_SCSI_CMD 0x01 +#define ISCSI_OP_SCSI_TASK_MGT_MSG 0x02 +#define ISCSI_OP_LOGIN_CMD 0x03 +#define ISCSI_OP_TEXT_CMD 0x04 +#define ISCSI_OP_SCSI_DATA_OUT 0x05 +#define ISCSI_OP_LOGOUT_CMD 0x06 +#define ISCSI_OP_SNACK_CMD 0x10 + +#define ISCSI_OP_VENDOR1_CMD 0x1c +#define ISCSI_OP_VENDOR2_CMD 0x1d +#define ISCSI_OP_VENDOR3_CMD 0x1e +#define ISCSI_OP_VENDOR4_CMD 0x1f + +/* Server to Client Message Opcode values */ +#define ISCSI_OP_NOOP_IN 0x20 +#define ISCSI_OP_SCSI_RSP 0x21 +#define ISCSI_OP_SCSI_TASK_MGT_RSP 0x22 +#define ISCSI_OP_LOGIN_RSP 0x23 +#define ISCSI_OP_TEXT_RSP 0x24 +#define ISCSI_OP_SCSI_DATA_IN 0x25 +#define ISCSI_OP_LOGOUT_RSP 0x26 +#define ISCSI_OP_R2T 0x31 +#define ISCSI_OP_ASYNC_MSG 0x32 +#define ISCSI_OP_REJECT 0x3f + +struct iscsi_ahs_hdr { + u16 ahslength; + u8 ahstype; +} __packed; + +#define ISCSI_AHSTYPE_CDB 1 +#define ISCSI_AHSTYPE_RLENGTH 2 + +union iscsi_sid { + struct { + u8 isid[6]; /* Initiator Session ID */ + u16 tsih; /* Target Session ID */ + } id; + u64 id64; +} __packed; + +struct iscsi_scsi_cmd_hdr { + u8 opcode; + u8 flags; + u16 rsvd1; + u8 ahslength; + u8 datalength[3]; + u16 lun[4]; + u32 itt; + u32 data_length; + u32 cmd_sn; + u32 exp_stat_sn; + u8 scb[16]; +} __packed; + +#define ISCSI_CMD_FINAL 0x80 +#define ISCSI_CMD_READ 0x40 +#define ISCSI_CMD_WRITE 0x20 +#define ISCSI_CMD_ATTR_MASK 0x07 +#define ISCSI_CMD_UNTAGGED 0x00 +#define ISCSI_CMD_SIMPLE 0x01 +#define ISCSI_CMD_ORDERED 0x02 +#define ISCSI_CMD_HEAD_OF_QUEUE 0x03 +#define ISCSI_CMD_ACA 0x04 + +struct iscsi_cdb_ahdr { + u16 ahslength; + u8 ahstype; + u8 reserved; + u8 cdb[0]; +} __packed; + +struct iscsi_rlength_ahdr { + u16 ahslength; + u8 ahstype; + u8 reserved; + u32 read_length; +} __packed; + +struct iscsi_scsi_rsp_hdr { + u8 opcode; + u8 flags; + u8 response; + u8 cmd_status; + u8 ahslength; + u8 datalength[3]; + u32 rsvd1[2]; + u32 itt; + u32 snack; + u32 stat_sn; + u32 exp_cmd_sn; + u32 max_cmd_sn; + u32 exp_data_sn; + u32 bi_residual_count; + u32 residual_count; +} __packed; + +#define ISCSI_FLG_RESIDUAL_UNDERFLOW 0x02 +#define ISCSI_FLG_RESIDUAL_OVERFLOW 0x04 +#define ISCSI_FLG_BIRESIDUAL_UNDERFLOW 0x08 +#define ISCSI_FLG_BIRESIDUAL_OVERFLOW 0x10 + +#define ISCSI_RESPONSE_COMMAND_COMPLETED 0x00 +#define ISCSI_RESPONSE_TARGET_FAILURE 0x01 + +struct iscsi_sense_data { + u16 length; + u8 data[0]; +} __packed; + +struct iscsi_task_mgt_hdr { + u8 opcode; + u8 function; + u16 rsvd1; + u8 ahslength; + u8 datalength[3]; + u16 lun[4]; + u32 itt; + u32 rtt; + u32 cmd_sn; + u32 exp_stat_sn; + u32 ref_cmd_sn; + u32 exp_data_sn; + u32 rsvd2[2]; +} __packed; + +#define ISCSI_FUNCTION_MASK 0x7f + +#define ISCSI_FUNCTION_ABORT_TASK 1 +#define ISCSI_FUNCTION_ABORT_TASK_SET 2 +#define ISCSI_FUNCTION_CLEAR_ACA 3 +#define ISCSI_FUNCTION_CLEAR_TASK_SET 4 +#define ISCSI_FUNCTION_LOGICAL_UNIT_RESET 5 +#define ISCSI_FUNCTION_TARGET_WARM_RESET 6 +#define ISCSI_FUNCTION_TARGET_COLD_RESET 7 +#define ISCSI_FUNCTION_TASK_REASSIGN 8 + +struct iscsi_task_rsp_hdr { + u8 opcode; + u8 flags; + u8 response; + u8 rsvd1; + u8 ahslength; + u8 datalength[3]; + u32 rsvd2[2]; + u32 itt; + u32 rsvd3; + u32 stat_sn; + u32 exp_cmd_sn; + u32 max_cmd_sn; + u32 rsvd4[3]; +} __packed; + +#define ISCSI_RESPONSE_FUNCTION_COMPLETE 0 +#define ISCSI_RESPONSE_UNKNOWN_TASK 1 +#define ISCSI_RESPONSE_UNKNOWN_LUN 2 +#define ISCSI_RESPONSE_TASK_ALLEGIANT 3 +#define ISCSI_RESPONSE_FAILOVER_UNSUPPORTED 4 +#define ISCSI_RESPONSE_FUNCTION_UNSUPPORTED 5 +#define ISCSI_RESPONSE_NO_AUTHORIZATION 6 +#define ISCSI_RESPONSE_FUNCTION_REJECTED 255 + +struct iscsi_data_out_hdr { + u8 opcode; + u8 flags; + u16 rsvd1; + u8 ahslength; + u8 datalength[3]; + u16 lun[4]; + u32 itt; + u32 ttt; + u32 rsvd2; + u32 exp_stat_sn; + u32 rsvd3; + u32 data_sn; + u32 buffer_offset; + u32 rsvd4; +} __packed; + +struct iscsi_data_in_hdr { + u8 opcode; + u8 flags; + u8 rsvd1; + u8 cmd_status; + u8 ahslength; + u8 datalength[3]; + u32 rsvd2[2]; + u32 itt; + u32 ttt; + u32 stat_sn; + u32 exp_cmd_sn; + u32 max_cmd_sn; + u32 data_sn; + u32 buffer_offset; + u32 residual_count; +} __packed; + +#define ISCSI_FLG_STATUS 0x01 + +struct iscsi_r2t_hdr { + u8 opcode; + u8 flags; + u16 rsvd1; + u8 ahslength; + u8 datalength[3]; + u16 lun[4]; + u32 itt; + u32 ttt; + u32 stat_sn; + u32 exp_cmd_sn; + u32 max_cmd_sn; + u32 r2t_sn; + u32 buffer_offset; + u32 data_length; +} __packed; + +struct iscsi_async_msg_hdr { + u8 opcode; + u8 flags; + u16 rsvd1; + u8 ahslength; + u8 datalength[3]; + u16 lun[4]; + u32 ffffffff; + u32 rsvd2; + u32 stat_sn; + u32 exp_cmd_sn; + u32 max_cmd_sn; + u8 async_event; + u8 async_vcode; + u16 param1; + u16 param2; + u16 param3; + u32 rsvd3; +} __packed; + +#define ISCSI_ASYNC_SCSI 0 +#define ISCSI_ASYNC_LOGOUT 1 +#define ISCSI_ASYNC_DROP_CONNECTION 2 +#define ISCSI_ASYNC_DROP_SESSION 3 +#define ISCSI_ASYNC_PARAM_REQUEST 4 +#define ISCSI_ASYNC_VENDOR 255 + +struct iscsi_text_req_hdr { + u8 opcode; + u8 flags; + u16 rsvd1; + u8 ahslength; + u8 datalength[3]; + u32 rsvd2[2]; + u32 itt; + u32 ttt; + u32 cmd_sn; + u32 exp_stat_sn; + u32 rsvd3[4]; +} __packed; + +struct iscsi_text_rsp_hdr { + u8 opcode; + u8 flags; + u16 rsvd1; + u8 ahslength; + u8 datalength[3]; + u32 rsvd2[2]; + u32 itt; + u32 ttt; + u32 stat_sn; + u32 exp_cmd_sn; + u32 max_cmd_sn; + u32 rsvd3[3]; +} __packed; + +struct iscsi_login_req_hdr { + u8 opcode; + u8 flags; + u8 max_version; /* Max. version supported */ + u8 min_version; /* Min. version supported */ + u8 ahslength; + u8 datalength[3]; + union iscsi_sid sid; + u32 itt; /* Initiator Task Tag */ + u16 cid; /* Connection ID */ + u16 rsvd1; + u32 cmd_sn; + u32 exp_stat_sn; + u32 rsvd2[4]; +} __packed; + +struct iscsi_login_rsp_hdr { + u8 opcode; + u8 flags; + u8 max_version; /* Max. version supported */ + u8 active_version; /* Active version */ + u8 ahslength; + u8 datalength[3]; + union iscsi_sid sid; + u32 itt; /* Initiator Task Tag */ + u32 rsvd1; + u32 stat_sn; + u32 exp_cmd_sn; + u32 max_cmd_sn; + u8 status_class; /* see Login RSP ststus classes below */ + u8 status_detail; /* see Login RSP Status details below */ + u8 rsvd2[10]; +} __packed; + +#define ISCSI_FLG_FINAL 0x80 +#define ISCSI_FLG_TRANSIT 0x80 +#define ISCSI_FLG_CSG_SECURITY 0x00 +#define ISCSI_FLG_CSG_LOGIN 0x04 +#define ISCSI_FLG_CSG_FULL_FEATURE 0x0c +#define ISCSI_FLG_CSG_MASK 0x0c +#define ISCSI_FLG_NSG_SECURITY 0x00 +#define ISCSI_FLG_NSG_LOGIN 0x01 +#define ISCSI_FLG_NSG_FULL_FEATURE 0x03 +#define ISCSI_FLG_NSG_MASK 0x03 + +/* Login Status response classes */ +#define ISCSI_STATUS_SUCCESS 0x00 +#define ISCSI_STATUS_REDIRECT 0x01 +#define ISCSI_STATUS_INITIATOR_ERR 0x02 +#define ISCSI_STATUS_TARGET_ERR 0x03 + +/* Login Status response detail codes */ +/* Class-0 (Success) */ +#define ISCSI_STATUS_ACCEPT 0x00 + +/* Class-1 (Redirection) */ +#define ISCSI_STATUS_TGT_MOVED_TEMP 0x01 +#define ISCSI_STATUS_TGT_MOVED_PERM 0x02 + +/* Class-2 (Initiator Error) */ +#define ISCSI_STATUS_INIT_ERR 0x00 +#define ISCSI_STATUS_AUTH_FAILED 0x01 +#define ISCSI_STATUS_TGT_FORBIDDEN 0x02 +#define ISCSI_STATUS_TGT_NOT_FOUND 0x03 +#define ISCSI_STATUS_TGT_REMOVED 0x04 +#define ISCSI_STATUS_NO_VERSION 0x05 +#define ISCSI_STATUS_TOO_MANY_CONN 0x06 +#define ISCSI_STATUS_MISSING_FIELDS 0x07 +#define ISCSI_STATUS_CONN_ADD_FAILED 0x08 +#define ISCSI_STATUS_INV_SESSION_TYPE 0x09 +#define ISCSI_STATUS_SESSION_NOT_FOUND 0x0a +#define ISCSI_STATUS_INV_REQ_TYPE 0x0b + +/* Class-3 (Target Error) */ +#define ISCSI_STATUS_TARGET_ERROR 0x00 +#define ISCSI_STATUS_SVC_UNAVAILABLE 0x01 +#define ISCSI_STATUS_NO_RESOURCES 0x02 + +struct iscsi_logout_req_hdr { + u8 opcode; + u8 flags; + u16 rsvd1; + u8 ahslength; + u8 datalength[3]; + u32 rsvd2[2]; + u32 itt; + u16 cid; + u16 rsvd3; + u32 cmd_sn; + u32 exp_stat_sn; + u32 rsvd4[4]; +} __packed; + +struct iscsi_logout_rsp_hdr { + u8 opcode; + u8 flags; + u8 response; + u8 rsvd1; + u8 ahslength; + u8 datalength[3]; + u32 rsvd2[2]; + u32 itt; + u32 rsvd3; + u32 stat_sn; + u32 exp_cmd_sn; + u32 max_cmd_sn; + u32 rsvd4; + u16 time2wait; + u16 time2retain; + u32 rsvd5; +} __packed; + +struct iscsi_snack_req_hdr { + u8 opcode; + u8 flags; + u16 rsvd1; + u8 ahslength; + u8 datalength[3]; + u32 rsvd2[2]; + u32 itt; + u32 ttt; + u32 rsvd3; + u32 exp_stat_sn; + u32 rsvd4[2]; + u32 beg_run; + u32 run_length; +} __packed; + +struct iscsi_reject_hdr { + u8 opcode; + u8 flags; + u8 reason; + u8 rsvd1; + u8 ahslength; + u8 datalength[3]; + u32 rsvd2[2]; + u32 ffffffff; + u32 rsvd3; + u32 stat_sn; + u32 exp_cmd_sn; + u32 max_cmd_sn; + u32 data_sn; + u32 rsvd4[2]; +} __packed; + +#define ISCSI_REASON_NO_FULL_FEATURE_PHASE 0x01 +#define ISCSI_REASON_DATA_DIGEST_ERROR 0x02 +#define ISCSI_REASON_DATA_SNACK_REJECT 0x03 +#define ISCSI_REASON_PROTOCOL_ERROR 0x04 +#define ISCSI_REASON_UNSUPPORTED_COMMAND 0x05 +#define ISCSI_REASON_IMMEDIATE_COMMAND_REJECT 0x06 +#define ISCSI_REASON_TASK_IN_PROGRESS 0x07 +#define ISCSI_REASON_INVALID_SNACK 0x08 +#define ISCSI_REASON_NO_BOOKMARK 0x09 +#define ISCSI_REASON_BOOKMARK_REJECT 0x0a +#define ISCSI_REASON_NEGOTIATION_RESET 0x0b +#define ISCSI_REASON_WAITING_LOGOUT 0x0c + + +struct iscsi_nop_out_hdr { + u8 opcode; + u8 flags; + u16 rsvd1; + u8 ahslength; + u8 datalength[3]; + u16 lun[4]; + u32 itt; + u32 ttt; + u32 cmd_sn; + u32 exp_stat_sn; + u32 rsvd2[4]; +} __packed; + +struct iscsi_nop_in_hdr { + u8 opcode; + u8 flags; + u16 rsvd1; + u8 ahslength; + u8 datalength[3]; + u16 lun[4]; + u32 itt; + u32 ttt; + u32 stat_sn; + u32 exp_cmd_sn; + u32 max_cmd_sn; + u32 rsvd2[3]; +} __packed; + +#define ISCSI_RESERVED_TAG (0xffffffffU) + +#endif /* __ISCSI_HDR_H__ */ diff --git a/iscsi-scst/kernel/nthread.c b/iscsi-scst/kernel/nthread.c new file mode 100644 index 000000000..67a49ac97 --- /dev/null +++ b/iscsi-scst/kernel/nthread.c @@ -0,0 +1,999 @@ +/* + * Network thread. + * (C) 2004 - 2005 FUJITA Tomonori + * This code is licenced under the GPL. + */ + +#include +#include +#include +#include +#include +#include + +#include "iscsi.h" +#include "digest.h" + +enum rx_state { + RX_INIT_BHS, /* Must be zero. */ + RX_BHS, + + RX_INIT_AHS, + RX_AHS, + + RX_INIT_HDIGEST, + RX_HDIGEST, + RX_CHECK_HDIGEST, + + RX_INIT_DATA, + RX_DATA, + + RX_INIT_DDIGEST, + RX_DDIGEST, + RX_CHECK_DDIGEST, + + RX_END, +}; + +enum tx_state { + TX_INIT, /* Must be zero. */ + TX_BHS_DATA, + TX_INIT_DDIGEST, + TX_DDIGEST, + TX_END, +}; + +/* No locks */ +static void close_conn(struct iscsi_conn *conn) +{ + struct iscsi_session *session = conn->session; + struct iscsi_target *target = conn->target; + + TRACE_ENTRY(); + + TRACE_CONN_CLOSE("conn %p, conn_ref_cnt=%d", conn, + atomic_read(&conn->conn_ref_cnt)); + + iscsi_extracheck_is_rd_thread(conn); + + /* We want all our already send operations to complete */ + conn->sock->ops->shutdown(conn->sock, RCV_SHUTDOWN); + + conn_abort(conn); + + if (conn->read_state != RX_INIT_BHS) { + req_cmnd_release_force(conn->read_cmnd, 0); + conn->read_cmnd = NULL; + conn->read_state = RX_INIT_BHS; + } + + /* ToDo: not the best way to wait */ + while(atomic_read(&conn->conn_ref_cnt) != 0) { + struct iscsi_cmnd *cmnd; + + if (!list_empty(&session->pending_list)) { + struct list_head *pending_list = &session->pending_list; + struct iscsi_cmnd *tmp; + + TRACE_CONN_CLOSE("Disposing pending commands on conn " + "%p, conn_ref_cnt=%d", conn, + atomic_read(&conn->conn_ref_cnt)); + + list_for_each_entry_safe(cmnd, tmp, pending_list, + pending_list_entry) { + if (cmnd->conn == conn) { + TRACE_CONN_CLOSE("Freeing pending cmd %p", + cmnd); + list_del(&cmnd->pending_list_entry); + cmnd->pending = 0; + req_cmnd_release_force(cmnd, 0); + } + } + } + + iscsi_make_conn_wr_active(conn); + msleep(50); + + TRACE_CONN_CLOSE("conn %p, conn_ref_cnt %d left, wr_state %d", + conn, atomic_read(&conn->conn_ref_cnt), conn->wr_state); +#ifdef DEBUG + { +#ifdef NET_PAGE_CALLBACKS_DEFINED + struct iscsi_cmnd *rsp; +#endif + spin_lock_bh(&conn->cmd_list_lock); + list_for_each_entry(cmnd, &conn->cmd_list, cmd_list_entry) { + TRACE_DBG("cmd %p, scst_state %x, data_waiting " + "%d, ref_cnt %d, parent_req %p", cmnd, + cmnd->scst_state, cmnd->data_waiting, + atomic_read(&cmnd->ref_cnt), cmnd->parent_req); +#ifdef NET_PAGE_CALLBACKS_DEFINED + TRACE_DBG("net_ref_cnt %d, sg %p", + atomic_read(&cmnd->net_ref_cnt), cmnd->sg); + if (cmnd->sg != NULL) { + int sg_cnt, i; + sg_cnt = get_pgcnt(cmnd->bufflen, + cmnd->sg[0].offset); + for(i = 0; i < sg_cnt; i++) { + TRACE_DBG("page %p, net_priv %p, _count %d", + cmnd->sg[i].page, cmnd->sg[i].page->net_priv, + atomic_read(&cmnd->sg[i].page->_count)); + } + } + + sBUG_ON(cmnd->parent_req != NULL); + + spin_lock_bh(&cmnd->rsp_cmd_lock); + list_for_each_entry(rsp, &cmnd->rsp_cmd_list, rsp_cmd_list_entry) { + TRACE_DBG(" rsp %p, ref_cnt %d, net_ref_cnt %d, " + "sg %p", rsp, atomic_read(&rsp->ref_cnt), + atomic_read(&rsp->net_ref_cnt), rsp->sg); + if ((rsp->sg != cmnd->sg) && (rsp->sg != NULL)) { + int sg_cnt, i; + sg_cnt = get_pgcnt(rsp->bufflen, + rsp->sg[0].offset); + sBUG_ON(rsp->sg_cnt != sg_cnt); + for(i = 0; i < sg_cnt; i++) { + TRACE_DBG(" page %p, net_priv %p, " + "_count %d", rsp->sg[i].page, + rsp->sg[i].page->net_priv, + atomic_read(&rsp->sg[i].page->_count)); + } + } + } + spin_unlock_bh(&cmnd->rsp_cmd_lock); +#endif + } + spin_unlock_bh(&conn->cmd_list_lock); + } +#endif + } + + write_lock_bh(&conn->sock->sk->sk_callback_lock); + conn->sock->sk->sk_state_change = conn->old_state_change; + conn->sock->sk->sk_data_ready = conn->old_data_ready; + conn->sock->sk->sk_write_space = conn->old_write_space; + write_unlock_bh(&conn->sock->sk->sk_callback_lock); + + while(conn->wr_state != ISCSI_CONN_WR_STATE_IDLE) { + TRACE_CONN_CLOSE("Waiting for wr thread (conn %p), wr_state %x", + conn, conn->wr_state); + msleep(50); + } + + mutex_lock(&target->target_mutex); + conn_free(conn); + if (list_empty(&session->conn_list)) + session_del(target, session->sid); + mutex_unlock(&target->target_mutex); + + TRACE_CONN_CLOSE("Notifying user space about closing conn %p", conn); + event_send(target->tid, session->sid, conn->cid, E_CONN_CLOSE, 0); + + TRACE_EXIT(); + return; +} + +static inline void iscsi_conn_init_read(struct iscsi_conn *conn, void *data, size_t len) +{ + len = (len + 3) & -4; // XXX ??? + conn->read_iov[0].iov_base = data; + conn->read_iov[0].iov_len = len; + conn->read_msg.msg_iov = conn->read_iov; + conn->read_msg.msg_iovlen = 1; + conn->read_size = (len + 3) & -4; +} + +static void iscsi_conn_read_ahs(struct iscsi_conn *conn, struct iscsi_cmnd *cmnd) +{ + /* ToDo: __GFP_NOFAIL ?? */ + cmnd->pdu.ahs = kmalloc(cmnd->pdu.ahssize, __GFP_NOFAIL|GFP_KERNEL); + sBUG_ON(cmnd->pdu.ahs == NULL); + iscsi_conn_init_read(conn, cmnd->pdu.ahs, cmnd->pdu.ahssize); +} + +static struct iscsi_cmnd *iscsi_get_send_cmnd(struct iscsi_conn *conn) +{ + struct iscsi_cmnd *cmnd = NULL; + + spin_lock(&conn->write_list_lock); + if (!list_empty(&conn->write_list)) { + cmnd = list_entry(conn->write_list.next, struct iscsi_cmnd, + write_list_entry); + cmd_del_from_write_list(cmnd); + cmnd->write_processing_started = 1; + } + spin_unlock(&conn->write_list_lock); + + return cmnd; +} + +static int do_recv(struct iscsi_conn *conn, int state) +{ + mm_segment_t oldfs; + struct msghdr msg; + int res, first_len; + + if (unlikely(conn->closing)) { + res = -EIO; + goto out; + } + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = conn->read_msg.msg_iov; + msg.msg_iovlen = conn->read_msg.msg_iovlen; + first_len = msg.msg_iov->iov_len; + + oldfs = get_fs(); + set_fs(get_ds()); + res = sock_recvmsg(conn->sock, &msg, conn->read_size, MSG_DONTWAIT | MSG_NOSIGNAL); + set_fs(oldfs); + + if (res <= 0) { + switch (res) { + case -EAGAIN: + case -ERESTARTSYS: + TRACE_DBG("EAGAIN or ERESTARTSYS (%d) received for " + "conn %p", res, conn); + break; + default: + PRINT_ERROR_PR("sock_recvmsg() failed: %d", res); + mark_conn_closed(conn); + break; + } + } else { + /* + * To save some considerable effort and CPU power we suppose + * that TCP functions adjust conn->read_msg.msg_iov and + * conn->read_msg.msg_iovlen on amount of copied data. This + * BUG_ON is intended to catch if it is changed in the future. + */ + sBUG_ON((res >= first_len) && + (conn->read_msg.msg_iov->iov_len != 0)); + conn->read_size -= res; + if (conn->read_size) { + if (res >= first_len) { + int done = 1 + ((res - first_len) >> PAGE_SHIFT); + conn->read_msg.msg_iov += done; + conn->read_msg.msg_iovlen -= done; + } + } else + conn->read_state = state; + } + +out: + TRACE_EXIT_RES(res); + return res; +} + +static int rx_hdigest(struct iscsi_conn *conn) +{ + struct iscsi_cmnd *cmnd = conn->read_cmnd; + int res = digest_rx_header(cmnd); + + if (unlikely(res != 0)) { + PRINT_ERROR_PR("rx header digest for initiator %s failed " + "(%d)", conn->session->initiator_name, res); + mark_conn_closed(conn); + } + return res; +} + +static struct iscsi_cmnd *create_cmnd(struct iscsi_conn *conn) +{ + struct iscsi_cmnd *cmnd; + + cmnd = cmnd_alloc(conn, NULL); + iscsi_conn_init_read(cmnd->conn, &cmnd->pdu.bhs, sizeof(cmnd->pdu.bhs)); + conn->read_state = RX_BHS; + + return cmnd; +} + +/* Returns >0 for success, <=0 for error or successful finish */ +static int recv(struct iscsi_conn *conn) +{ + struct iscsi_cmnd *cmnd = conn->read_cmnd; + int hdigest, ddigest, res = 1, rc; + + TRACE_ENTRY(); + + hdigest = conn->hdigest_type & DIGEST_NONE ? 0 : 1; + ddigest = conn->ddigest_type & DIGEST_NONE ? 0 : 1; + + switch (conn->read_state) { + case RX_INIT_BHS: + sBUG_ON(cmnd != NULL); + cmnd = conn->read_cmnd = create_cmnd(conn); + case RX_BHS: + res = do_recv(conn, RX_INIT_AHS); + if (res <= 0 || conn->read_state != RX_INIT_AHS) + break; + case RX_INIT_AHS: + iscsi_cmnd_get_length(&cmnd->pdu); + if (cmnd->pdu.ahssize) { + iscsi_conn_read_ahs(conn, cmnd); + conn->read_state = RX_AHS; + } else + conn->read_state = hdigest ? RX_INIT_HDIGEST : RX_INIT_DATA; + + if (conn->read_state != RX_AHS) + break; + case RX_AHS: + res = do_recv(conn, hdigest ? RX_INIT_HDIGEST : RX_INIT_DATA); + if (res <= 0 || conn->read_state != RX_INIT_HDIGEST) + break; + case RX_INIT_HDIGEST: + iscsi_conn_init_read(conn, &cmnd->hdigest, sizeof(u32)); + conn->read_state = RX_HDIGEST; + case RX_HDIGEST: + res = do_recv(conn, RX_CHECK_HDIGEST); + if (res <= 0 || conn->read_state != RX_CHECK_HDIGEST) + break; + case RX_CHECK_HDIGEST: + rc = rx_hdigest(conn); + if (likely(rc == 0)) + conn->read_state = RX_INIT_DATA; + else { + res = rc; + break; + } + case RX_INIT_DATA: + rc = cmnd_rx_start(cmnd); + if (unlikely(rc != 0)) { + sBUG_ON(!conn->closing); + conn->read_state = RX_END; + res = rc; + /* cmnd will be freed in close_conn() */ + goto out; + } + conn->read_state = cmnd->pdu.datasize ? RX_DATA : RX_END; + if (conn->read_state != RX_DATA) + break; + case RX_DATA: + res = do_recv(conn, ddigest ? RX_INIT_DDIGEST : RX_END); + if (res <= 0 || conn->read_state != RX_INIT_DDIGEST) + break; + case RX_INIT_DDIGEST: + iscsi_conn_init_read(conn, &cmnd->ddigest, sizeof(u32)); + conn->read_state = RX_DDIGEST; + case RX_DDIGEST: + res = do_recv(conn, RX_CHECK_DDIGEST); + if (res <= 0 || conn->read_state != RX_CHECK_DDIGEST) + break; + case RX_CHECK_DDIGEST: + conn->read_state = RX_END; + if (cmnd_opcode(cmnd) == ISCSI_OP_SCSI_CMD) { + TRACE_DBG("Adding RX ddigest cmd %p to digest list " + "of self", cmnd); + list_add_tail(&cmnd->rx_ddigest_cmd_list_entry, + &cmnd->rx_ddigest_cmd_list); + cmnd_get(cmnd); + conn->read_state = RX_END; + } else if (cmnd_opcode(cmnd) != ISCSI_OP_SCSI_DATA_OUT) { + /* + * We could get here only for NOP-Out. ISCSI RFC doesn't + * specify how to deal with digest errors in this case. + * Is closing connection correct? + */ + TRACE_DBG("cmnd %p, opcode %x: checking RX " + "ddigest inline", cmnd, cmnd_opcode(cmnd)); + rc = digest_rx_data(cmnd); + if (unlikely(rc != 0)) { + conn->read_state = RX_CHECK_DDIGEST; + mark_conn_closed(conn); + } + } + break; + default: + PRINT_ERROR_PR("%d %x", conn->read_state, cmnd_opcode(cmnd)); + sBUG(); + } + + if (res <= 0) + goto out; + + if (conn->read_state != RX_END) + goto out; + + if (conn->read_size) { + PRINT_ERROR_PR("%d %x %d", res, cmnd_opcode(cmnd), conn->read_size); + sBUG(); + } + + cmnd_rx_end(cmnd); + + sBUG_ON(conn->read_size != 0); + + conn->read_cmnd = NULL; + conn->read_state = RX_INIT_BHS; + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* No locks, conn is rd processing */ +static int process_read_io(struct iscsi_conn *conn, int *closed) +{ + int res; + + do { + res = recv(conn); + if (unlikely(conn->closing)) { + close_conn(conn); + *closed = 1; + break; + } + } while(res > 0); + + TRACE_EXIT_RES(res); + return res; +} + +/* + * Called under iscsi_rd_lock and BHs disabled, but will drop it inside, + * then reaquire. + */ +static void scst_do_job_rd(void) +{ + TRACE_ENTRY(); + + /* We delete/add to tail connections to maintain fairness between them */ + + while(!list_empty(&iscsi_rd_list)) { + int rc, closed = 0; + struct iscsi_conn *conn = list_entry(iscsi_rd_list.next, + typeof(*conn), rd_list_entry); + + list_del(&conn->rd_list_entry); + + sBUG_ON(conn->rd_state == ISCSI_CONN_RD_STATE_PROCESSING); + conn->rd_data_ready = 0; + conn->rd_state = ISCSI_CONN_RD_STATE_PROCESSING; +#ifdef EXTRACHECKS + conn->rd_task = current; +#endif + spin_unlock_bh(&iscsi_rd_lock); + + rc = process_read_io(conn, &closed); + + spin_lock_bh(&iscsi_rd_lock); + + if (closed) + continue; + +#ifdef EXTRACHECKS + conn->rd_task = NULL; +#endif + if ((rc == 0) || conn->rd_data_ready) { + list_add_tail(&conn->rd_list_entry, &iscsi_rd_list); + conn->rd_state = ISCSI_CONN_RD_STATE_IN_LIST; + } else + conn->rd_state = ISCSI_CONN_RD_STATE_IDLE; + } + + TRACE_EXIT(); + return; +} + +static inline int test_rd_list(void) +{ + int res = !list_empty(&iscsi_rd_list) || + unlikely(kthread_should_stop()); + return res; +} + +int istrd(void *arg) +{ + TRACE_ENTRY(); + + current->flags |= PF_NOFREEZE; + + spin_lock_bh(&iscsi_rd_lock); + while(!kthread_should_stop()) { + wait_queue_t wait; + init_waitqueue_entry(&wait, current); + + if (!test_rd_list()) { + add_wait_queue_exclusive(&iscsi_rd_waitQ, &wait); + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + if (test_rd_list()) + break; + spin_unlock_bh(&iscsi_rd_lock); + schedule(); + spin_lock_bh(&iscsi_rd_lock); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&iscsi_rd_waitQ, &wait); + } + scst_do_job_rd(); + } + spin_unlock_bh(&iscsi_rd_lock); + + /* + * If kthread_should_stop() is true, we are guaranteed to be + * on the module unload, so iscsi_rd_list must be empty. + */ + sBUG_ON(!list_empty(&iscsi_rd_list)); + + TRACE_EXIT(); + return 0; +} + +#ifdef NET_PAGE_CALLBACKS_DEFINED +void iscsi_get_page_callback(struct page *page) +{ + struct iscsi_cmnd *cmd = (struct iscsi_cmnd*)page->net_priv; + int v; + + TRACE_DBG("cmd %p, page %p, _count %d, new net_ref_cnt %d", + cmd, page, atomic_read(&page->_count), + atomic_read(&cmd->net_ref_cnt)+1); + + v = atomic_inc_return(&cmd->net_ref_cnt); + if (v == 1) { + TRACE_DBG("getting cmd %p for page %p", cmd, page); + cmnd_get(cmd); + } +} + +void iscsi_put_page_callback(struct page *page) +{ + struct iscsi_cmnd *cmd = (struct iscsi_cmnd*)page->net_priv; + + TRACE_DBG("cmd %p, page %p, _count %d, new net_ref_cnt %d", + cmd, page, atomic_read(&page->_count), + atomic_read(&cmd->net_ref_cnt)-1); + + if (atomic_dec_and_test(&cmd->net_ref_cnt)) { + int i, sg_cnt = get_pgcnt(cmd->bufflen, cmd->sg[0].offset); + for(i = 0; i < sg_cnt; i++) { + TRACE_DBG("Clearing page %p", cmd->sg[i].page); + cmd->sg[i].page->net_priv = NULL; + } + cmnd_put(cmd); + } +} +#endif + +/* This is partially taken from the Ardis code. */ +static int write_data(struct iscsi_conn *conn) +{ + mm_segment_t oldfs; + struct file *file; + struct socket *sock; + ssize_t (*sendpage)(struct socket *, struct page *, int, size_t, int); + struct iscsi_cmnd *write_cmnd = conn->write_cmnd; + struct iscsi_cmnd *ref_cmd; + struct scatterlist *sg; + struct iovec *iop; + int saved_size, size, sendsize; + int offset, idx; + int flags, res, count; + + iscsi_extracheck_is_wr_thread(conn); + + if (write_cmnd->own_sg == 0) + ref_cmd = write_cmnd->parent_req; + else + ref_cmd = write_cmnd; + + file = conn->file; + saved_size = size = conn->write_size; + iop = conn->write_iop; + count = conn->write_iop_used; + + if (iop) while (1) { + loff_t off = 0; + int rest; + + sBUG_ON(count > sizeof(conn->write_iov)/sizeof(conn->write_iov[0])); +retry: + oldfs = get_fs(); + set_fs(KERNEL_DS); + res = vfs_writev(file, (struct iovec __user *)iop, count, &off); + set_fs(oldfs); + TRACE(TRACE_D_DATA, "%#Lx:%u: %d(%ld)", + (unsigned long long) conn->session->sid, conn->cid, + res, (long) iop->iov_len); + if (unlikely(res <= 0)) { + if (res == -EAGAIN) { + conn->write_iop = iop; + conn->write_iop_used = count; + goto out_iov; + } else if (res == -EINTR) + goto retry; + goto err; + } + + rest = res; + size -= res; + while (iop->iov_len <= rest && rest) { + rest -= iop->iov_len; + iop++; + count--; + } + if (count == 0) { + conn->write_iop = NULL; + conn->write_iop_used = 0; + if (size) + break; + goto out_iov; + } + sBUG_ON(iop > conn->write_iov + + sizeof(conn->write_iov)/sizeof(conn->write_iov[0])); + iop->iov_base += rest; + iop->iov_len -= rest; + } + + sg = write_cmnd->sg; + if (sg == NULL) { + PRINT_ERROR_PR("%s", "warning data missing!"); + return 0; + } + offset = conn->write_offset; + idx = offset >> PAGE_SHIFT; + offset &= ~PAGE_MASK; + + sock = conn->sock; + +#ifdef NET_PAGE_CALLBACKS_DEFINED + sendpage = sock->ops->sendpage; +#else + if ((write_cmnd->parent_req->scst_cmd != NULL) && + scst_cmd_get_data_buff_alloced(write_cmnd->parent_req->scst_cmd)) + sendpage = sock_no_sendpage; + else + sendpage = sock->ops->sendpage; +#endif + + flags = MSG_DONTWAIT; + + while (1) { +#ifdef NET_PAGE_CALLBACKS_DEFINED + if (unlikely((sg[idx].page->net_priv != NULL) && + (sg[idx].page->net_priv != ref_cmd))) { + PRINT_ERROR_PR("net_priv isn't NULL and != ref_cmd " + "(write_cmnd %p, ref_cmd %p, sg %p, idx %d, " + "net_priv %p)", write_cmnd, ref_cmd, sg, idx, + sg[idx].page->net_priv); + sBUG(); + } + sg[idx].page->net_priv = ref_cmd; +#endif + sendsize = PAGE_SIZE - offset; + if (size <= sendsize) { +retry2: + res = sendpage(sock, sg[idx].page, offset, size, flags); + TRACE(TRACE_D_DATA, "%s %#Lx:%u: %d(%lu,%u,%u)", + sock->ops->sendpage ? "sendpage" : "sock_no_sendpage", + (unsigned long long)conn->session->sid, conn->cid, + res, sg[idx].page->index, offset, size); + if (unlikely(res <= 0)) { + if (res == -EINTR) + goto retry2; + else + goto out_res; + } + if (res == size) { + conn->write_size = 0; + return saved_size; + } + offset += res; + size -= res; + continue; + } + +retry1: + res = sendpage(sock, sg[idx].page, offset, sendsize, + flags | MSG_MORE); + TRACE(TRACE_D_DATA, "%s %#Lx:%u: %d(%lu,%u,%u)", + sock->ops->sendpage ? "sendpage" : "sock_no_sendpage", + (unsigned long long ) conn->session->sid, conn->cid, + res, sg[idx].page->index, offset, sendsize); + if (unlikely(res <= 0)) { + if (res == -EINTR) + goto retry1; + else + goto out_res; + } + if (res == sendsize) { + idx++; + offset = 0; + } else + offset += res; + size -= res; + } +out: + conn->write_offset = (idx << PAGE_SHIFT) + offset; +out_iov: + conn->write_size = size; + if ((saved_size == size) && res == -EAGAIN) + return res; + + return saved_size - size; + +out_res: +#ifdef NET_PAGE_CALLBACKS_DEFINED + if (atomic_read(&ref_cmd->net_ref_cnt) == 0) { + TRACE_DBG("sendpage() returned %d, zeroing net_priv", res); + sg[idx].page->net_priv = NULL; + } +#endif + if (res == -EAGAIN) + goto out; + /* else go through */ + +err: +#ifndef DEBUG + if (!conn->closing) +#endif + { + PRINT_ERROR_PR("error %d at sid:cid %#Lx:%u, cmnd %p", res, + (unsigned long long)conn->session->sid, conn->cid, + conn->write_cmnd); + } + return res; +} + +static int exit_tx(struct iscsi_conn *conn, int res) +{ + iscsi_extracheck_is_wr_thread(conn); + + switch (res) { + case -EAGAIN: + case -ERESTARTSYS: + res = 0; + break; + default: +#ifndef DEBUG + if (!conn->closing) +#endif + { + PRINT_ERROR_PR("Sending data failed: initiator %s, " + "write_size %d, write_state %d, res %d", + conn->session->initiator_name, conn->write_size, + conn->write_state, res); + } + conn->write_state = TX_END; + conn->write_size = 0; + mark_conn_closed(conn); + break; + } + return res; +} + +static int tx_ddigest(struct iscsi_cmnd *cmnd, int state) +{ + int res, rest = cmnd->conn->write_size; + struct msghdr msg = {.msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT}; + struct kvec iov; + + iscsi_extracheck_is_wr_thread(cmnd->conn); + + TRACE_DBG("Sending data digest %x (cmd %p)", cmnd->ddigest, cmnd); + + iov.iov_base = (char *) (&cmnd->ddigest) + (sizeof(u32) - rest); + iov.iov_len = rest; + + res = kernel_sendmsg(cmnd->conn->sock, &msg, &iov, 1, rest); + if (res > 0) { + cmnd->conn->write_size -= res; + if (!cmnd->conn->write_size) + cmnd->conn->write_state = state; + } else + res = exit_tx(cmnd->conn, res); + + return res; +} + +static void init_tx_hdigest(struct iscsi_cmnd *cmnd) +{ + struct iscsi_conn *conn = cmnd->conn; + struct iovec *iop; + + iscsi_extracheck_is_wr_thread(conn); + + digest_tx_header(cmnd); + + sBUG_ON(conn->write_iop_used >= sizeof(conn->write_iov)/sizeof(conn->write_iov[0])); + iop = &conn->write_iop[conn->write_iop_used]; + conn->write_iop_used++; + iop->iov_base = &(cmnd->hdigest); + iop->iov_len = sizeof(u32); + conn->write_size += sizeof(u32); + + return; +} + +static int iscsi_do_send(struct iscsi_conn *conn, int state) +{ + int res; + + iscsi_extracheck_is_wr_thread(conn); + + res = write_data(conn); + if (res > 0) { + if (!conn->write_size) + conn->write_state = state; + } else + res = exit_tx(conn, res); + + return res; +} + +/* No locks, conn is wr processing */ +int iscsi_send(struct iscsi_conn *conn) +{ + struct iscsi_cmnd *cmnd = conn->write_cmnd; + int ddigest, res = 0; + + TRACE_ENTRY(); + + TRACE_DBG("conn %p, write_cmnd %p", conn, cmnd); + + iscsi_extracheck_is_wr_thread(conn); + + ddigest = conn->ddigest_type != DIGEST_NONE ? 1 : 0; + + switch (conn->write_state) { + case TX_INIT: + sBUG_ON(cmnd != NULL); + cmnd = conn->write_cmnd = iscsi_get_send_cmnd(conn); + if (!cmnd) + goto out; + cmnd_tx_start(cmnd); + if (!(conn->hdigest_type & DIGEST_NONE)) + init_tx_hdigest(cmnd); + conn->write_state = TX_BHS_DATA; + case TX_BHS_DATA: + res = iscsi_do_send(conn, ddigest && cmnd->pdu.datasize ? + TX_INIT_DDIGEST : TX_END); + if (res <= 0 || conn->write_state != TX_INIT_DDIGEST) + break; + case TX_INIT_DDIGEST: + cmnd->conn->write_size = sizeof(u32); + conn->write_state = TX_DDIGEST; + case TX_DDIGEST: + res = tx_ddigest(cmnd, TX_END); + break; + default: + PRINT_ERROR_PR("%d %d %x", res, conn->write_state, + cmnd_opcode(cmnd)); + sBUG(); + } + + if (res == 0) + goto out; + + if (conn->write_state != TX_END) + goto out; + + if (conn->write_size) { + PRINT_ERROR_PR("%d %x %u", res, cmnd_opcode(cmnd), + conn->write_size); + sBUG(); + } + cmnd_tx_end(cmnd); + rsp_cmnd_release(cmnd); + conn->write_cmnd = NULL; + conn->write_state = TX_INIT; + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* No locks, conn is wr processing */ +static int process_write_queue(struct iscsi_conn *conn) +{ + int res = 0; + + TRACE_ENTRY(); + + if (likely(test_write_ready(conn))) + res = iscsi_send(conn); + + TRACE_EXIT_RES(res); + return res; +} + +/* + * Called under iscsi_wr_lock and BHs disabled, but will drop it inside, + * then reaquire. + */ +static void scst_do_job_wr(void) +{ + TRACE_ENTRY(); + + /* We delete/add to tail connections to maintain fairness between them */ + + while(!list_empty(&iscsi_wr_list)) { + int rc; + struct iscsi_conn *conn = list_entry(iscsi_wr_list.next, + typeof(*conn), wr_list_entry); + + TRACE_DBG("conn %p, wr_state %x, wr_space_ready %d, " + "write ready %d", conn, conn->wr_state, + conn->wr_space_ready, test_write_ready(conn)); + + list_del(&conn->wr_list_entry); + + sBUG_ON(conn->wr_state == ISCSI_CONN_WR_STATE_PROCESSING); + + conn->wr_state = ISCSI_CONN_WR_STATE_PROCESSING; + conn->wr_space_ready = 0; +#ifdef EXTRACHECKS + conn->wr_task = current; +#endif + spin_unlock_bh(&iscsi_wr_lock); + + rc = process_write_queue(conn); + + spin_lock_bh(&iscsi_wr_lock); +#ifdef EXTRACHECKS + conn->wr_task = NULL; +#endif + if ((rc == -EAGAIN) && !conn->wr_space_ready) { + conn->wr_state = ISCSI_CONN_WR_STATE_SPACE_WAIT; + continue; + } + + if (test_write_ready(conn)) { + list_add_tail(&conn->wr_list_entry, &iscsi_wr_list); + conn->wr_state = ISCSI_CONN_WR_STATE_IN_LIST; + } else + conn->wr_state = ISCSI_CONN_WR_STATE_IDLE; + } + + TRACE_EXIT(); + return; +} + +static inline int test_wr_list(void) +{ + int res = !list_empty(&iscsi_wr_list) || + unlikely(kthread_should_stop()); + return res; +} + +int istwr(void *arg) +{ + TRACE_ENTRY(); + + current->flags |= PF_NOFREEZE; + + spin_lock_bh(&iscsi_wr_lock); + while(!kthread_should_stop()) { + wait_queue_t wait; + init_waitqueue_entry(&wait, current); + + if (!test_wr_list()) { + add_wait_queue_exclusive(&iscsi_wr_waitQ, &wait); + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + if (test_wr_list()) + break; + spin_unlock_bh(&iscsi_wr_lock); + schedule(); + spin_lock_bh(&iscsi_wr_lock); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&iscsi_wr_waitQ, &wait); + } + scst_do_job_wr(); + } + spin_unlock_bh(&iscsi_wr_lock); + + /* + * If kthread_should_stop() is true, we are guaranteed to be + * on the module unload, so iscsi_wr_list must be empty. + */ + sBUG_ON(!list_empty(&iscsi_wr_list)); + + TRACE_EXIT(); + return 0; +} diff --git a/iscsi-scst/kernel/param.c b/iscsi-scst/kernel/param.c new file mode 100644 index 000000000..d66773b62 --- /dev/null +++ b/iscsi-scst/kernel/param.c @@ -0,0 +1,234 @@ +/* + * (C) 2005 FUJITA Tomonori + * + * This code is licenced under the GPL. + */ + +#include "iscsi.h" +#include "digest.h" + +#define CHECK_PARAM(info, iparam, word, min, max) \ +do { \ + if (!info->partial || (info->partial & 1 << key_##word)) \ + if (iparam[key_##word] < min || \ + iparam[key_##word] > max) { \ + PRINT_ERROR_PR("%s: %u is out of range (%u %u)",\ + #word, iparam[key_##word], min, max); \ + iparam[key_##word] = min; \ + } \ +} while (0) + +#define SET_PARAM(param, info, iparam, word) \ +({ \ + int changed = 0; \ + if (!info->partial || (info->partial & 1 << key_##word)) { \ + if (param->word != iparam[key_##word]) \ + changed = 1; \ + param->word = iparam[key_##word]; \ + } \ + changed; \ +}) + +#define GET_PARAM(param, info, iparam, word) \ +do { \ + iparam[key_##word] = param->word; \ +} while (0) + +static const char *get_bool_name(int val) +{ + if (val) + return "Yes"; + else + return "No"; +} + +static const char *get_digest_name(int val) +{ + if (val == DIGEST_NONE) + return "None"; + else if (val == DIGEST_CRC32C) + return "CRC32C"; + else + return "Unknown"; +} + +static void log_params(struct iscsi_sess_param *param) +{ + PRINT_INFO_PR("Negotiated parameters: InitialR2T %s, ImmediateData %s, " + "MaxConnections %d, MaxRecvDataSegmentLength %d, " + "MaxXmitDataSegmentLength %d, ", get_bool_name(param->initial_r2t), + get_bool_name(param->immediate_data), param->max_connections, + param->max_recv_data_length, param->max_xmit_data_length); + PRINT_INFO_PR(" MaxBurstLength %d, FirstBurstLength %d, " + "DefaultTime2Wait %d, DefaultTime2Retain %d, ", + param->max_burst_length, param->first_burst_length, + param->default_wait_time, param->default_retain_time); + PRINT_INFO_PR(" MaxOutstandingR2T %d, DataPDUInOrder %s, " + "DataSequenceInOrder %s, ErrorRecoveryLevel %d, ", + param->max_outstanding_r2t, get_bool_name(param->data_pdu_inorder), + get_bool_name(param->data_sequence_inorder), + param->error_recovery_level); + PRINT_INFO_PR(" HeaderDigest %s, DataDigest %s, OFMarker %s, " + "IFMarker %s, OFMarkInt %d, IFMarkInt %d", + get_digest_name(param->header_digest), + get_digest_name(param->data_digest), + get_bool_name(param->ofmarker), get_bool_name(param->ifmarker), + param->ofmarkint, param->ifmarkint); +} + +/* target_mutex supposed to be locked */ +static void sess_param_check(struct iscsi_param_info *info) +{ + u32 *iparam = info->session_param; + + CHECK_PARAM(info, iparam, max_connections, 1, 1); + CHECK_PARAM(info, iparam, max_recv_data_length, 512, + (u32) (ISCSI_CONN_IOV_MAX * PAGE_SIZE)); + CHECK_PARAM(info, iparam, max_xmit_data_length, 512, + (u32) (ISCSI_CONN_IOV_MAX * PAGE_SIZE)); + CHECK_PARAM(info, iparam, error_recovery_level, 0, 0); + CHECK_PARAM(info, iparam, data_pdu_inorder, 0, 1); + CHECK_PARAM(info, iparam, data_sequence_inorder, 0, 1); + + digest_alg_available(&iparam[key_header_digest]); + digest_alg_available(&iparam[key_data_digest]); + + CHECK_PARAM(info, iparam, ofmarker, 0, 0); + CHECK_PARAM(info, iparam, ifmarker, 0, 0); +} + +/* target_mutex supposed to be locked */ +static void sess_param_set(struct iscsi_sess_param *param, struct iscsi_param_info *info) +{ + u32 *iparam = info->session_param; + + SET_PARAM(param, info, iparam, initial_r2t); + SET_PARAM(param, info, iparam, immediate_data); + SET_PARAM(param, info, iparam, max_connections); + SET_PARAM(param, info, iparam, max_recv_data_length); + SET_PARAM(param, info, iparam, max_xmit_data_length); + SET_PARAM(param, info, iparam, max_burst_length); + SET_PARAM(param, info, iparam, first_burst_length); + SET_PARAM(param, info, iparam, default_wait_time); + SET_PARAM(param, info, iparam, default_retain_time); + SET_PARAM(param, info, iparam, max_outstanding_r2t); + SET_PARAM(param, info, iparam, data_pdu_inorder); + SET_PARAM(param, info, iparam, data_sequence_inorder); + SET_PARAM(param, info, iparam, error_recovery_level); + SET_PARAM(param, info, iparam, header_digest); + SET_PARAM(param, info, iparam, data_digest); + SET_PARAM(param, info, iparam, ofmarker); + SET_PARAM(param, info, iparam, ifmarker); + SET_PARAM(param, info, iparam, ofmarkint); + SET_PARAM(param, info, iparam, ifmarkint); +} + +static void sess_param_get(struct iscsi_sess_param *param, struct iscsi_param_info *info) +{ + u32 *iparam = info->session_param; + + GET_PARAM(param, info, iparam, initial_r2t); + GET_PARAM(param, info, iparam, immediate_data); + GET_PARAM(param, info, iparam, max_connections); + GET_PARAM(param, info, iparam, max_recv_data_length); + GET_PARAM(param, info, iparam, max_xmit_data_length); + GET_PARAM(param, info, iparam, max_burst_length); + GET_PARAM(param, info, iparam, first_burst_length); + GET_PARAM(param, info, iparam, default_wait_time); + GET_PARAM(param, info, iparam, default_retain_time); + GET_PARAM(param, info, iparam, max_outstanding_r2t); + GET_PARAM(param, info, iparam, data_pdu_inorder); + GET_PARAM(param, info, iparam, data_sequence_inorder); + GET_PARAM(param, info, iparam, error_recovery_level); + GET_PARAM(param, info, iparam, header_digest); + GET_PARAM(param, info, iparam, data_digest); + GET_PARAM(param, info, iparam, ofmarker); + GET_PARAM(param, info, iparam, ifmarker); + GET_PARAM(param, info, iparam, ofmarkint); + GET_PARAM(param, info, iparam, ifmarkint); +} + +/* target_mutex supposed to be locked */ +static void trgt_param_check(struct iscsi_param_info *info) +{ + u32 *iparam = info->target_param; + + CHECK_PARAM(info, iparam, queued_cmnds, MIN_NR_QUEUED_CMNDS, MAX_NR_QUEUED_CMNDS); +} + +/* target_mutex supposed to be locked */ +static void trgt_param_set(struct iscsi_target *target, struct iscsi_param_info *info) +{ + struct iscsi_trgt_param *param = &target->trgt_param; + u32 *iparam = info->target_param; + + SET_PARAM(param, info, iparam, queued_cmnds); +} + +/* target_mutex supposed to be locked */ +static void trgt_param_get(struct iscsi_trgt_param *param, struct iscsi_param_info *info) +{ + u32 *iparam = info->target_param; + + GET_PARAM(param, info, iparam, queued_cmnds); +} + +/* target_mutex supposed to be locked */ +static int trgt_param(struct iscsi_target *target, struct iscsi_param_info *info, int set) +{ + if (set) { + trgt_param_check(info); + trgt_param_set(target, info); + } else + trgt_param_get(&target->trgt_param, info); + + return 0; +} + +/* target_mutex supposed to be locked */ +static int sess_param(struct iscsi_target *target, struct iscsi_param_info *info, int set) +{ + struct iscsi_session *session = NULL; + struct iscsi_sess_param *param; + int err = -ENOENT; + + if (set) + sess_param_check(info); + + if (info->sid) { + if (!(session = session_lookup(target, info->sid))) + goto out; + if (set && !list_empty(&session->conn_list)) { + err = -EBUSY; + goto out; + } + param = &session->sess_param; + } else + param = &target->trgt_sess_param; + + if (set) { + sess_param_set(param, info); + if (session != NULL) + log_params(param); + } else + sess_param_get(param, info); + + err = 0; +out: + return err; +} + +/* target_mutex supposed to be locked */ +int iscsi_param_set(struct iscsi_target *target, struct iscsi_param_info *info, int set) +{ + int err; + + if (info->param_type == key_session) + err = sess_param(target, info, set); + else if (info->param_type == key_target) + err = trgt_param(target, info, set); + else + err = -EINVAL; + + return err; +} diff --git a/iscsi-scst/kernel/patches/put_page_callback-2.6.16.patch b/iscsi-scst/kernel/patches/put_page_callback-2.6.16.patch new file mode 100644 index 000000000..785756e04 --- /dev/null +++ b/iscsi-scst/kernel/patches/put_page_callback-2.6.16.patch @@ -0,0 +1,245 @@ +diff -upr linux-2.6.16.29/include/linux/mm.h linux-2.6.16.29/include/linux/mm.h +--- linux-2.6.16.29/include/linux/mm.h 2006-09-12 22:02:10.000000000 +0400 ++++ linux-2.6.16.29/include/linux/mm.h 2007-08-07 19:54:27.000000000 +0400 +@@ -263,6 +263,15 @@ struct page { + void *virtual; /* Kernel virtual address (NULL if + not kmapped, ie. highmem) */ + #endif /* WANT_PAGE_VIRTUAL */ ++ /* ++ * Used to implement support for notification on zero-copy TCP transfer ++ * completeion. Not good to have this field here, it's better to have ++ * it in struct sk_buff, but it would make the code much more ++ * complicated and fragile, if maintained as a separate patch, since all ++ * skb then would have to contain only pages with the same value in this ++ * field. ++ */ ++ void *net_priv; + }; + + #define page_private(page) ((page)->private) +diff -upr linux-2.6.16.29/include/linux/net.h linux-2.6.16.29/include/linux/net.h +--- linux-2.6.16.29/include/linux/net.h 2006-09-12 22:02:10.000000000 +0400 ++++ linux-2.6.16.29/include/linux/net.h 2007-08-07 19:55:27.000000000 +0400 +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + + struct poll_table_struct; + struct inode; +@@ -294,5 +295,30 @@ extern int net_msg_cost; + extern int net_msg_burst; + #endif + ++/* Support for notification on zero-copy TCP transfer completeion */ ++#define NET_PAGE_CALLBACKS_DEFINED ++typedef void (*net_get_page_callback_t)(struct page *page); ++typedef void (*net_put_page_callback_t)(struct page *page); ++ ++extern net_get_page_callback_t net_get_page_callback; ++extern net_put_page_callback_t net_put_page_callback; ++ ++extern int net_set_get_put_page_callbacks( ++ net_get_page_callback_t get_callback, ++ net_put_page_callback_t put_callback); ++ ++static inline void net_get_page(struct page *page) ++{ ++ if (page->net_priv != 0) ++ net_get_page_callback(page); ++ get_page(page); ++} ++static inline void net_put_page(struct page *page) ++{ ++ if (page->net_priv != 0) ++ net_put_page_callback(page); ++ put_page(page); ++} ++ + #endif /* __KERNEL__ */ + #endif /* _LINUX_NET_H */ +diff -upr linux-2.6.16.29/net/core/skbuff.c linux-2.6.16.29/net/core/skbuff.c +--- linux-2.6.16.29/net/core/skbuff.c 2006-09-12 22:02:10.000000000 +0400 ++++ linux-2.6.16.29/net/core/skbuff.c 2007-08-07 19:55:51.000000000 +0400 +@@ -271,7 +271,7 @@ void skb_release_data(struct sk_buff *sk + if (skb_shinfo(skb)->nr_frags) { + int i; + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) +- put_page(skb_shinfo(skb)->frags[i].page); ++ net_put_page(skb_shinfo(skb)->frags[i].page); + } + + if (skb_shinfo(skb)->frag_list) +@@ -588,7 +588,7 @@ struct sk_buff *pskb_copy(struct sk_buff + + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + skb_shinfo(n)->frags[i] = skb_shinfo(skb)->frags[i]; +- get_page(skb_shinfo(n)->frags[i].page); ++ net_get_page(skb_shinfo(n)->frags[i].page); + } + skb_shinfo(n)->nr_frags = i; + } +@@ -642,7 +642,7 @@ int pskb_expand_head(struct sk_buff *skb + memcpy(data + size, skb->end, sizeof(struct skb_shared_info)); + + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) +- get_page(skb_shinfo(skb)->frags[i].page); ++ net_get_page(skb_shinfo(skb)->frags[i].page); + + if (skb_shinfo(skb)->frag_list) + skb_clone_fraglist(skb); +@@ -794,7 +794,7 @@ int ___pskb_trim(struct sk_buff *skb, un + return -ENOMEM; + } + if (len <= offset) { +- put_page(skb_shinfo(skb)->frags[i].page); ++ net_put_page(skb_shinfo(skb)->frags[i].page); + skb_shinfo(skb)->nr_frags--; + } else { + skb_shinfo(skb)->frags[i].size = len - offset; +@@ -940,7 +940,7 @@ pull_pages: + k = 0; + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + if (skb_shinfo(skb)->frags[i].size <= eat) { +- put_page(skb_shinfo(skb)->frags[i].page); ++ net_put_page(skb_shinfo(skb)->frags[i].page); + eat -= skb_shinfo(skb)->frags[i].size; + } else { + skb_shinfo(skb)->frags[k] = skb_shinfo(skb)->frags[i]; +@@ -1522,7 +1522,7 @@ static inline void skb_split_no_header(s + * where splitting is expensive. + * 2. Split is accurately. We make this. + */ +- get_page(skb_shinfo(skb)->frags[i].page); ++ net_get_page(skb_shinfo(skb)->frags[i].page); + skb_shinfo(skb1)->frags[0].page_offset += len - pos; + skb_shinfo(skb1)->frags[0].size -= len - pos; + skb_shinfo(skb)->frags[i].size = len - pos; +diff -upr linux-2.6.16.29/net/core/utils.c linux-2.6.16.29/net/core/utils.c +--- linux-2.6.16.29/net/core/utils.c 2006-09-12 22:02:10.000000000 +0400 ++++ linux-2.6.16.29/net/core/utils.c 2007-08-07 19:54:27.000000000 +0400 +@@ -24,11 +24,15 @@ + #include + #include + #include ++#include + + #include + #include + #include + ++net_get_page_callback_t net_get_page_callback __read_mostly; ++net_put_page_callback_t net_put_page_callback __read_mostly; ++ + /* + This is a maximally equidistributed combined Tausworthe generator + based on code from GNU Scientific Library 1.5 (30 Jun 2004) +@@ -190,3 +194,29 @@ __be32 in_aton(const char *str) + } + + EXPORT_SYMBOL(in_aton); ++ ++int net_set_get_put_page_callbacks( ++ net_get_page_callback_t get_callback, ++ net_put_page_callback_t put_callback) ++{ ++ int res = 0; ++ ++ if ((net_get_page_callback != NULL) && (get_callback != NULL) && ++ (net_get_page_callback != get_callback)) { ++ res = -EBUSY; ++ goto out; ++ } ++ ++ if ((net_put_page_callback != NULL) && (put_callback != NULL) && ++ (net_put_page_callback != put_callback)) { ++ res = -EBUSY; ++ goto out; ++ } ++ ++ net_get_page_callback = get_callback; ++ net_put_page_callback = put_callback; ++ ++out: ++ return res; ++} ++EXPORT_SYMBOL(net_set_get_put_page_callbacks); +diff -upr linux-2.6.16.29/net/ipv4/ip_output.c linux-2.6.16.29/net/ipv4/ip_output.c +--- linux-2.6.16.29/net/ipv4/ip_output.c 2006-09-12 22:02:10.000000000 +0400 ++++ linux-2.6.16.29/net/ipv4/ip_output.c 2007-08-07 19:54:27.000000000 +0400 +@@ -997,7 +997,7 @@ alloc_new_skb: + err = -EMSGSIZE; + goto error; + } +- get_page(page); ++ net_get_page(page); + skb_fill_page_desc(skb, i, page, sk->sk_sndmsg_off, 0); + frag = &skb_shinfo(skb)->frags[i]; + } +@@ -1155,7 +1155,7 @@ ssize_t ip_append_page(struct sock *sk, + if (skb_can_coalesce(skb, i, page, offset)) { + skb_shinfo(skb)->frags[i-1].size += len; + } else if (i < MAX_SKB_FRAGS) { +- get_page(page); ++ net_get_page(page); + skb_fill_page_desc(skb, i, page, offset, len); + } else { + err = -EMSGSIZE; +diff -upr linux-2.6.16.29/net/ipv4/tcp.c linux-2.6.16.29/net/ipv4/tcp.c +--- linux-2.6.16.29/net/ipv4/tcp.c 2006-09-12 22:02:10.000000000 +0400 ++++ linux-2.6.16.29/net/ipv4/tcp.c 2007-08-07 19:54:27.000000000 +0400 +@@ -558,7 +558,7 @@ new_segment: + if (can_coalesce) { + skb_shinfo(skb)->frags[i - 1].size += copy; + } else { +- get_page(page); ++ net_get_page(page); + skb_fill_page_desc(skb, i, page, offset, copy); + } + +@@ -767,7 +767,7 @@ new_segment: + goto new_segment; + } else if (page) { + if (off == PAGE_SIZE) { +- put_page(page); ++ net_put_page(page); + TCP_PAGE(sk) = page = NULL; + off = 0; + } +@@ -808,9 +808,9 @@ new_segment: + } else { + skb_fill_page_desc(skb, i, page, off, copy); + if (TCP_PAGE(sk)) { +- get_page(page); ++ net_get_page(page); + } else if (off + copy < PAGE_SIZE) { +- get_page(page); ++ net_get_page(page); + TCP_PAGE(sk) = page; + } + } +diff -upr linux-2.6.16.29/net/ipv4/tcp_output.c linux-2.6.16.29/net/ipv4/tcp_output.c +--- linux-2.6.16.29/net/ipv4/tcp_output.c 2006-09-12 22:02:10.000000000 +0400 ++++ linux-2.6.16.29/net/ipv4/tcp_output.c 2007-08-07 19:54:27.000000000 +0400 +@@ -633,7 +633,7 @@ static unsigned char *__pskb_trim_head(s + k = 0; + for (i=0; inr_frags; i++) { + if (skb_shinfo(skb)->frags[i].size <= eat) { +- put_page(skb_shinfo(skb)->frags[i].page); ++ net_put_page(skb_shinfo(skb)->frags[i].page); + eat -= skb_shinfo(skb)->frags[i].size; + } else { + skb_shinfo(skb)->frags[k] = skb_shinfo(skb)->frags[i]; +diff -upr linux-2.6.16.29/net/ipv6/ip6_output.c linux-2.6.16.29/net/ipv6/ip6_output.c +--- linux-2.6.16.29/net/ipv6/ip6_output.c 2006-09-12 22:02:10.000000000 +0400 ++++ linux-2.6.16.29/net/ipv6/ip6_output.c 2007-08-07 19:54:27.000000000 +0400 +@@ -1100,7 +1100,7 @@ alloc_new_skb: + err = -EMSGSIZE; + goto error; + } +- get_page(page); ++ net_get_page(page); + skb_fill_page_desc(skb, i, page, sk->sk_sndmsg_off, 0); + frag = &skb_shinfo(skb)->frags[i]; + } diff --git a/iscsi-scst/kernel/patches/put_page_callback-2.6.18.patch b/iscsi-scst/kernel/patches/put_page_callback-2.6.18.patch new file mode 100644 index 000000000..563379c02 --- /dev/null +++ b/iscsi-scst/kernel/patches/put_page_callback-2.6.18.patch @@ -0,0 +1,254 @@ +diff -upr linux-2.6.18.1/include/linux/mm.h linux-2.6.18.1/include/linux/mm.h +--- linux-2.6.18.1/include/linux/mm.h 2006-09-20 07:42:06.000000000 +0400 ++++ linux-2.6.18.1/include/linux/mm.h 2007-08-07 19:35:51.000000000 +0400 +@@ -267,6 +267,15 @@ struct page { + void *virtual; /* Kernel virtual address (NULL if + not kmapped, ie. highmem) */ + #endif /* WANT_PAGE_VIRTUAL */ ++ /* ++ * Used to implement support for notification on zero-copy TCP transfer ++ * completeion. Not good to have this field here, it's better to have ++ * it in struct sk_buff, but it would make the code much more ++ * complicated and fragile, if maintained as a separate patch, since all ++ * skb then would have to contain only pages with the same value in this ++ * field. ++ */ ++ void *net_priv; + }; + + #define page_private(page) ((page)->private) +diff -upr linux-2.6.18.1/include/linux/net.h linux-2.6.18.1/include/linux/net.h +--- linux-2.6.18.1/include/linux/net.h 2006-09-20 07:42:06.000000000 +0400 ++++ linux-2.6.18.1/include/linux/net.h 2007-08-07 19:35:51.000000000 +0400 +@@ -19,6 +19,7 @@ + #define _LINUX_NET_H + + #include ++#include + #include + + struct poll_table_struct; +@@ -304,5 +305,30 @@ extern int net_msg_cost; + extern int net_msg_burst; + #endif + ++/* Support for notification on zero-copy TCP transfer completeion */ ++#define NET_PAGE_CALLBACKS_DEFINED ++typedef void (*net_get_page_callback_t)(struct page *page); ++typedef void (*net_put_page_callback_t)(struct page *page); ++ ++extern net_get_page_callback_t net_get_page_callback; ++extern net_put_page_callback_t net_put_page_callback; ++ ++extern int net_set_get_put_page_callbacks( ++ net_get_page_callback_t get_callback, ++ net_put_page_callback_t put_callback); ++ ++static inline void net_get_page(struct page *page) ++{ ++ if (page->net_priv != 0) ++ net_get_page_callback(page); ++ get_page(page); ++} ++static inline void net_put_page(struct page *page) ++{ ++ if (page->net_priv != 0) ++ net_put_page_callback(page); ++ put_page(page); ++} ++ + #endif /* __KERNEL__ */ + #endif /* _LINUX_NET_H */ +diff -upr linux-2.6.18.1/net/core/skbuff.c linux-2.6.18.1/net/core/skbuff.c +--- linux-2.6.18.1/net/core/skbuff.c 2006-09-20 07:42:06.000000000 +0400 ++++ linux-2.6.18.1/net/core/skbuff.c 2007-08-07 19:35:51.000000000 +0400 +@@ -309,7 +309,7 @@ static void skb_release_data(struct sk_b + if (skb_shinfo(skb)->nr_frags) { + int i; + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) +- put_page(skb_shinfo(skb)->frags[i].page); ++ net_put_page(skb_shinfo(skb)->frags[i].page); + } + + if (skb_shinfo(skb)->frag_list) +@@ -646,7 +646,7 @@ struct sk_buff *pskb_copy(struct sk_buff + + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + skb_shinfo(n)->frags[i] = skb_shinfo(skb)->frags[i]; +- get_page(skb_shinfo(n)->frags[i].page); ++ net_get_page(skb_shinfo(n)->frags[i].page); + } + skb_shinfo(n)->nr_frags = i; + } +@@ -700,7 +700,7 @@ int pskb_expand_head(struct sk_buff *skb + memcpy(data + size, skb->end, sizeof(struct skb_shared_info)); + + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) +- get_page(skb_shinfo(skb)->frags[i].page); ++ net_get_page(skb_shinfo(skb)->frags[i].page); + + if (skb_shinfo(skb)->frag_list) + skb_clone_fraglist(skb); +@@ -882,7 +882,7 @@ drop_pages: + skb_shinfo(skb)->nr_frags = i; + + for (; i < nfrags; i++) +- put_page(skb_shinfo(skb)->frags[i].page); ++ net_put_page(skb_shinfo(skb)->frags[i].page); + + if (skb_shinfo(skb)->frag_list) + skb_drop_fraglist(skb); +@@ -1051,7 +1051,7 @@ pull_pages: + k = 0; + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + if (skb_shinfo(skb)->frags[i].size <= eat) { +- put_page(skb_shinfo(skb)->frags[i].page); ++ net_put_page(skb_shinfo(skb)->frags[i].page); + eat -= skb_shinfo(skb)->frags[i].size; + } else { + skb_shinfo(skb)->frags[k] = skb_shinfo(skb)->frags[i]; +@@ -1633,7 +1633,7 @@ static inline void skb_split_no_header(s + * where splitting is expensive. + * 2. Split is accurately. We make this. + */ +- get_page(skb_shinfo(skb)->frags[i].page); ++ net_get_page(skb_shinfo(skb)->frags[i].page); + skb_shinfo(skb1)->frags[0].page_offset += len - pos; + skb_shinfo(skb1)->frags[0].size -= len - pos; + skb_shinfo(skb)->frags[i].size = len - pos; +@@ -2002,7 +2002,7 @@ struct sk_buff *skb_segment(struct sk_bu + BUG_ON(i >= nfrags); + + *frag = skb_shinfo(skb)->frags[i]; +- get_page(frag->page); ++ net_get_page(frag->page); + size = frag->size; + + if (pos < offset) { +diff -upr linux-2.6.18.1/net/core/utils.c linux-2.6.18.1/net/core/utils.c +--- linux-2.6.18.1/net/core/utils.c 2006-09-20 07:42:06.000000000 +0400 ++++ linux-2.6.18.1/net/core/utils.c 2007-08-07 19:36:59.000000000 +0400 +@@ -24,11 +24,15 @@ + #include + #include + #include ++#include + + #include + #include + #include + ++net_get_page_callback_t net_get_page_callback __read_mostly; ++net_put_page_callback_t net_put_page_callback __read_mostly; ++ + /* + This is a maximally equidistributed combined Tausworthe generator + based on code from GNU Scientific Library 1.5 (30 Jun 2004) +@@ -191,3 +195,29 @@ __be32 in_aton(const char *str) + } + + EXPORT_SYMBOL(in_aton); ++ ++int net_set_get_put_page_callbacks( ++ net_get_page_callback_t get_callback, ++ net_put_page_callback_t put_callback) ++{ ++ int res = 0; ++ ++ if ((net_get_page_callback != NULL) && (get_callback != NULL) && ++ (net_get_page_callback != get_callback)) { ++ res = -EBUSY; ++ goto out; ++ } ++ ++ if ((net_put_page_callback != NULL) && (put_callback != NULL) && ++ (net_put_page_callback != put_callback)) { ++ res = -EBUSY; ++ goto out; ++ } ++ ++ net_get_page_callback = get_callback; ++ net_put_page_callback = put_callback; ++ ++out: ++ return res; ++} ++EXPORT_SYMBOL(net_set_get_put_page_callbacks); +diff -upr linux-2.6.18.1/net/ipv4/ip_output.c linux-2.6.18.1/net/ipv4/ip_output.c +--- linux-2.6.18.1/net/ipv4/ip_output.c 2006-09-20 07:42:06.000000000 +0400 ++++ linux-2.6.18.1/net/ipv4/ip_output.c 2007-08-07 19:37:24.000000000 +0400 +@@ -999,7 +999,7 @@ alloc_new_skb: + err = -EMSGSIZE; + goto error; + } +- get_page(page); ++ net_get_page(page); + skb_fill_page_desc(skb, i, page, sk->sk_sndmsg_off, 0); + frag = &skb_shinfo(skb)->frags[i]; + } +@@ -1159,7 +1159,7 @@ ssize_t ip_append_page(struct sock *sk, + if (skb_can_coalesce(skb, i, page, offset)) { + skb_shinfo(skb)->frags[i-1].size += len; + } else if (i < MAX_SKB_FRAGS) { +- get_page(page); ++ net_get_page(page); + skb_fill_page_desc(skb, i, page, offset, len); + } else { + err = -EMSGSIZE; +diff -upr linux-2.6.18.1/net/ipv4/tcp.c linux-2.6.18.1/net/ipv4/tcp.c +--- linux-2.6.18.1/net/ipv4/tcp.c 2006-09-20 07:42:06.000000000 +0400 ++++ linux-2.6.18.1/net/ipv4/tcp.c 2007-08-07 19:35:51.000000000 +0400 +@@ -559,7 +559,7 @@ new_segment: + if (can_coalesce) { + skb_shinfo(skb)->frags[i - 1].size += copy; + } else { +- get_page(page); ++ net_get_page(page); + skb_fill_page_desc(skb, i, page, offset, copy); + } + +@@ -762,7 +762,7 @@ new_segment: + goto new_segment; + } else if (page) { + if (off == PAGE_SIZE) { +- put_page(page); ++ net_put_page(page); + TCP_PAGE(sk) = page = NULL; + off = 0; + } +@@ -803,9 +803,9 @@ new_segment: + } else { + skb_fill_page_desc(skb, i, page, off, copy); + if (TCP_PAGE(sk)) { +- get_page(page); ++ net_get_page(page); + } else if (off + copy < PAGE_SIZE) { +- get_page(page); ++ net_get_page(page); + TCP_PAGE(sk) = page; + } + } +diff -upr linux-2.6.18.1/net/ipv4/tcp_output.c linux-2.6.18.1/net/ipv4/tcp_output.c +--- linux-2.6.18.1/net/ipv4/tcp_output.c 2006-09-20 07:42:06.000000000 +0400 ++++ linux-2.6.18.1/net/ipv4/tcp_output.c 2007-08-07 19:35:51.000000000 +0400 +@@ -657,7 +657,7 @@ static void __pskb_trim_head(struct sk_b + k = 0; + for (i=0; inr_frags; i++) { + if (skb_shinfo(skb)->frags[i].size <= eat) { +- put_page(skb_shinfo(skb)->frags[i].page); ++ net_put_page(skb_shinfo(skb)->frags[i].page); + eat -= skb_shinfo(skb)->frags[i].size; + } else { + skb_shinfo(skb)->frags[k] = skb_shinfo(skb)->frags[i]; +diff -upr linux-2.6.18.1/net/ipv6/ip6_output.c linux-2.6.18.1/net/ipv6/ip6_output.c +--- linux-2.6.18.1/net/ipv6/ip6_output.c 2006-09-20 07:42:06.000000000 +0400 ++++ linux-2.6.18.1/net/ipv6/ip6_output.c 2007-08-07 19:35:51.000000000 +0400 +@@ -1149,7 +1149,7 @@ alloc_new_skb: + err = -EMSGSIZE; + goto error; + } +- get_page(page); ++ net_get_page(page); + skb_fill_page_desc(skb, i, page, sk->sk_sndmsg_off, 0); + frag = &skb_shinfo(skb)->frags[i]; + } diff --git a/iscsi-scst/kernel/patches/put_page_callback-2.6.21.patch b/iscsi-scst/kernel/patches/put_page_callback-2.6.21.patch new file mode 100644 index 000000000..ccd695f13 --- /dev/null +++ b/iscsi-scst/kernel/patches/put_page_callback-2.6.21.patch @@ -0,0 +1,256 @@ +diff -upr linux-2.6.21.1/include/linux/mm_types.h linux-2.6.21.1/include/linux/mm_types.h +--- linux-2.6.21.1/include/linux/mm_types.h 2007-04-28 01:49:26.000000000 +0400 ++++ linux-2.6.21.1/include/linux/mm_types.h 2007-07-04 12:56:56.000000000 +0400 +@@ -62,6 +62,15 @@ struct page { + void *virtual; /* Kernel virtual address (NULL if + not kmapped, ie. highmem) */ + #endif /* WANT_PAGE_VIRTUAL */ ++ /* ++ * Used to implement support for notification on zero-copy TCP transfer ++ * completeion. Not good to have this field here, it's better to have ++ * it in struct sk_buff, but it would make the code much more ++ * complicated and fragile, if maintained as a separate patch, since all ++ * skb then would have to contain only pages with the same value in this ++ * field. ++ */ ++ void *net_priv; + }; + + #endif /* _LINUX_MM_TYPES_H */ +diff -upr linux-2.6.21.1/include/linux/net.h linux-2.6.21.1/include/linux/net.h +--- linux-2.6.21.1/include/linux/net.h 2007-04-28 01:49:26.000000000 +0400 ++++ linux-2.6.21.1/include/linux/net.h 2007-07-24 19:09:40.000000000 +0400 +@@ -19,6 +19,7 @@ + #define _LINUX_NET_H + + #include ++#include + #include + + struct poll_table_struct; +@@ -319,5 +320,30 @@ extern int net_msg_cost; + extern int net_msg_burst; + #endif + ++/* Support for notification on zero-copy TCP transfer completeion */ ++#define NET_PAGE_CALLBACKS_DEFINED ++typedef void (*net_get_page_callback_t)(struct page *page); ++typedef void (*net_put_page_callback_t)(struct page *page); ++ ++extern net_get_page_callback_t net_get_page_callback; ++extern net_put_page_callback_t net_put_page_callback; ++ ++extern int net_set_get_put_page_callbacks( ++ net_get_page_callback_t get_callback, ++ net_put_page_callback_t put_callback); ++ ++static inline void net_get_page(struct page *page) ++{ ++ if (page->net_priv != 0) ++ net_get_page_callback(page); ++ get_page(page); ++} ++static inline void net_put_page(struct page *page) ++{ ++ if (page->net_priv != 0) ++ net_put_page_callback(page); ++ put_page(page); ++} ++ + #endif /* __KERNEL__ */ + #endif /* _LINUX_NET_H */ +diff -upr linux-2.6.21.1/net/core/skbuff.c linux-2.6.21.1/net/core/skbuff.c +--- linux-2.6.21.1/net/core/skbuff.c 2007-04-28 01:49:26.000000000 +0400 ++++ linux-2.6.21.1/net/core/skbuff.c 2007-07-04 13:18:04.000000000 +0400 +@@ -257,7 +257,7 @@ static void skb_release_data(struct sk_b + if (skb_shinfo(skb)->nr_frags) { + int i; + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) +- put_page(skb_shinfo(skb)->frags[i].page); ++ net_put_page(skb_shinfo(skb)->frags[i].page); + } + + if (skb_shinfo(skb)->frag_list) +@@ -596,7 +596,7 @@ struct sk_buff *pskb_copy(struct sk_buff + + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + skb_shinfo(n)->frags[i] = skb_shinfo(skb)->frags[i]; +- get_page(skb_shinfo(n)->frags[i].page); ++ net_get_page(skb_shinfo(n)->frags[i].page); + } + skb_shinfo(n)->nr_frags = i; + } +@@ -650,7 +650,7 @@ int pskb_expand_head(struct sk_buff *skb + memcpy(data + size, skb->end, sizeof(struct skb_shared_info)); + + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) +- get_page(skb_shinfo(skb)->frags[i].page); ++ net_get_page(skb_shinfo(skb)->frags[i].page); + + if (skb_shinfo(skb)->frag_list) + skb_clone_fraglist(skb); +@@ -832,7 +832,7 @@ drop_pages: + skb_shinfo(skb)->nr_frags = i; + + for (; i < nfrags; i++) +- put_page(skb_shinfo(skb)->frags[i].page); ++ net_put_page(skb_shinfo(skb)->frags[i].page); + + if (skb_shinfo(skb)->frag_list) + skb_drop_fraglist(skb); +@@ -1001,7 +1001,7 @@ pull_pages: + k = 0; + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + if (skb_shinfo(skb)->frags[i].size <= eat) { +- put_page(skb_shinfo(skb)->frags[i].page); ++ net_put_page(skb_shinfo(skb)->frags[i].page); + eat -= skb_shinfo(skb)->frags[i].size; + } else { + skb_shinfo(skb)->frags[k] = skb_shinfo(skb)->frags[i]; +@@ -1583,7 +1583,7 @@ static inline void skb_split_no_header(s + * where splitting is expensive. + * 2. Split is accurately. We make this. + */ +- get_page(skb_shinfo(skb)->frags[i].page); ++ net_get_page(skb_shinfo(skb)->frags[i].page); + skb_shinfo(skb1)->frags[0].page_offset += len - pos; + skb_shinfo(skb1)->frags[0].size -= len - pos; + skb_shinfo(skb)->frags[i].size = len - pos; +@@ -1951,7 +1951,7 @@ struct sk_buff *skb_segment(struct sk_bu + BUG_ON(i >= nfrags); + + *frag = skb_shinfo(skb)->frags[i]; +- get_page(frag->page); ++ net_get_page(frag->page); + size = frag->size; + + if (pos < offset) { +diff -upr linux-2.6.21.1/net/core/utils.c linux-2.6.21.1/net/core/utils.c +--- linux-2.6.21.1/net/core/utils.c 2007-04-28 01:49:26.000000000 +0400 ++++ linux-2.6.21.1/net/core/utils.c 2007-07-25 12:17:19.000000000 +0400 +@@ -25,6 +25,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -33,6 +34,9 @@ + int net_msg_cost = 5*HZ; + int net_msg_burst = 10; + ++net_get_page_callback_t net_get_page_callback __read_mostly; ++net_put_page_callback_t net_put_page_callback __read_mostly; ++ + /* + * All net warning printk()s should be guarded by this function. + */ +@@ -290,3 +294,29 @@ out: + } + + EXPORT_SYMBOL(in6_pton); ++ ++int net_set_get_put_page_callbacks( ++ net_get_page_callback_t get_callback, ++ net_put_page_callback_t put_callback) ++{ ++ int res = 0; ++ ++ if ((net_get_page_callback != NULL) && (get_callback != NULL) && ++ (net_get_page_callback != get_callback)) { ++ res = -EBUSY; ++ goto out; ++ } ++ ++ if ((net_put_page_callback != NULL) && (put_callback != NULL) && ++ (net_put_page_callback != put_callback)) { ++ res = -EBUSY; ++ goto out; ++ } ++ ++ net_get_page_callback = get_callback; ++ net_put_page_callback = put_callback; ++ ++out: ++ return res; ++} ++EXPORT_SYMBOL(net_set_get_put_page_callbacks); +diff -upr linux-2.6.21.1/net/ipv4/ip_output.c linux-2.6.21.1/net/ipv4/ip_output.c +--- linux-2.6.21.1/net/ipv4/ip_output.c 2007-04-28 01:49:26.000000000 +0400 ++++ linux-2.6.21.1/net/ipv4/ip_output.c 2007-07-04 13:17:53.000000000 +0400 +@@ -991,7 +991,7 @@ alloc_new_skb: + err = -EMSGSIZE; + goto error; + } +- get_page(page); ++ net_get_page(page); + skb_fill_page_desc(skb, i, page, sk->sk_sndmsg_off, 0); + frag = &skb_shinfo(skb)->frags[i]; + } +@@ -1151,7 +1151,7 @@ ssize_t ip_append_page(struct sock *sk, + if (skb_can_coalesce(skb, i, page, offset)) { + skb_shinfo(skb)->frags[i-1].size += len; + } else if (i < MAX_SKB_FRAGS) { +- get_page(page); ++ net_get_page(page); + skb_fill_page_desc(skb, i, page, offset, len); + } else { + err = -EMSGSIZE; +diff -upr linux-2.6.21.1/net/ipv4/tcp.c linux-2.6.21.1/net/ipv4/tcp.c +--- linux-2.6.21.1/net/ipv4/tcp.c 2007-04-28 01:49:26.000000000 +0400 ++++ linux-2.6.21.1/net/ipv4/tcp.c 2007-07-04 13:17:58.000000000 +0400 +@@ -561,7 +561,7 @@ new_segment: + if (can_coalesce) { + skb_shinfo(skb)->frags[i - 1].size += copy; + } else { +- get_page(page); ++ net_get_page(page); + skb_fill_page_desc(skb, i, page, offset, copy); + } + +@@ -764,7 +764,7 @@ new_segment: + goto new_segment; + } else if (page) { + if (off == PAGE_SIZE) { +- put_page(page); ++ net_put_page(page); + TCP_PAGE(sk) = page = NULL; + off = 0; + } +@@ -805,9 +805,9 @@ new_segment: + } else { + skb_fill_page_desc(skb, i, page, off, copy); + if (TCP_PAGE(sk)) { +- get_page(page); ++ net_get_page(page); + } else if (off + copy < PAGE_SIZE) { +- get_page(page); ++ net_get_page(page); + TCP_PAGE(sk) = page; + } + } +diff -upr linux-2.6.21.1/net/ipv4/tcp_output.c linux-2.6.21.1/net/ipv4/tcp_output.c +--- linux-2.6.21.1/net/ipv4/tcp_output.c 2007-04-28 01:49:26.000000000 +0400 ++++ linux-2.6.21.1/net/ipv4/tcp_output.c 2007-07-04 13:17:05.000000000 +0400 +@@ -722,7 +722,7 @@ static void __pskb_trim_head(struct sk_b + k = 0; + for (i=0; inr_frags; i++) { + if (skb_shinfo(skb)->frags[i].size <= eat) { +- put_page(skb_shinfo(skb)->frags[i].page); ++ net_put_page(skb_shinfo(skb)->frags[i].page); + eat -= skb_shinfo(skb)->frags[i].size; + } else { + skb_shinfo(skb)->frags[k] = skb_shinfo(skb)->frags[i]; +diff -upr linux-2.6.21.1/net/ipv6/ip6_output.c linux-2.6.21.1/net/ipv6/ip6_output.c +--- linux-2.6.21.1/net/ipv6/ip6_output.c 2007-04-28 01:49:26.000000000 +0400 ++++ linux-2.6.21.1/net/ipv6/ip6_output.c 2007-07-04 13:17:49.000000000 +0400 +@@ -1227,7 +1227,7 @@ alloc_new_skb: + err = -EMSGSIZE; + goto error; + } +- get_page(page); ++ net_get_page(page); + skb_fill_page_desc(skb, i, page, sk->sk_sndmsg_off, 0); + frag = &skb_shinfo(skb)->frags[i]; + } diff --git a/iscsi-scst/kernel/patches/put_page_callback-2.6.22.patch b/iscsi-scst/kernel/patches/put_page_callback-2.6.22.patch new file mode 100644 index 000000000..288d855ed --- /dev/null +++ b/iscsi-scst/kernel/patches/put_page_callback-2.6.22.patch @@ -0,0 +1,256 @@ +diff -upr linux-2.6.22/include/linux/mm_types.h linux-2.6.22/include/linux/mm_types.h +--- linux-2.6.22/include/linux/mm_types.h 2007-07-09 03:32:17.000000000 +0400 ++++ linux-2.6.22/include/linux/mm_types.h 2007-08-07 19:12:10.000000000 +0400 +@@ -78,6 +78,15 @@ struct page { + void *virtual; /* Kernel virtual address (NULL if + not kmapped, ie. highmem) */ + #endif /* WANT_PAGE_VIRTUAL */ ++ /* ++ * Used to implement support for notification on zero-copy TCP transfer ++ * completeion. Not good to have this field here, it's better to have ++ * it in struct sk_buff, but it would make the code much more ++ * complicated and fragile, if maintained as a separate patch, since all ++ * skb then would have to contain only pages with the same value in this ++ * field. ++ */ ++ void *net_priv; + }; + + #endif /* _LINUX_MM_TYPES_H */ +diff -upr linux-2.6.22/include/linux/net.h linux-2.6.22/include/linux/net.h +--- linux-2.6.22/include/linux/net.h 2007-07-09 03:32:17.000000000 +0400 ++++ linux-2.6.22/include/linux/net.h 2007-08-07 19:12:10.000000000 +0400 +@@ -19,6 +19,7 @@ + #define _LINUX_NET_H + + #include ++#include + #include + + struct poll_table_struct; +@@ -319,5 +320,30 @@ extern int net_msg_cost; + extern int net_msg_burst; + #endif + ++/* Support for notification on zero-copy TCP transfer completeion */ ++#define NET_PAGE_CALLBACKS_DEFINED ++typedef void (*net_get_page_callback_t)(struct page *page); ++typedef void (*net_put_page_callback_t)(struct page *page); ++ ++extern net_get_page_callback_t net_get_page_callback; ++extern net_put_page_callback_t net_put_page_callback; ++ ++extern int net_set_get_put_page_callbacks( ++ net_get_page_callback_t get_callback, ++ net_put_page_callback_t put_callback); ++ ++static inline void net_get_page(struct page *page) ++{ ++ if (page->net_priv != 0) ++ net_get_page_callback(page); ++ get_page(page); ++} ++static inline void net_put_page(struct page *page) ++{ ++ if (page->net_priv != 0) ++ net_put_page_callback(page); ++ put_page(page); ++} ++ + #endif /* __KERNEL__ */ + #endif /* _LINUX_NET_H */ +diff -upr linux-2.6.22/net/core/skbuff.c linux-2.6.22/net/core/skbuff.c +--- linux-2.6.22/net/core/skbuff.c 2007-07-09 03:32:17.000000000 +0400 ++++ linux-2.6.22/net/core/skbuff.c 2007-08-07 19:12:10.000000000 +0400 +@@ -262,7 +262,7 @@ static void skb_release_data(struct sk_b + if (skb_shinfo(skb)->nr_frags) { + int i; + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) +- put_page(skb_shinfo(skb)->frags[i].page); ++ net_put_page(skb_shinfo(skb)->frags[i].page); + } + + if (skb_shinfo(skb)->frag_list) +@@ -590,7 +590,7 @@ struct sk_buff *pskb_copy(struct sk_buff + + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + skb_shinfo(n)->frags[i] = skb_shinfo(skb)->frags[i]; +- get_page(skb_shinfo(n)->frags[i].page); ++ net_get_page(skb_shinfo(n)->frags[i].page); + } + skb_shinfo(n)->nr_frags = i; + } +@@ -653,7 +653,7 @@ int pskb_expand_head(struct sk_buff *skb + sizeof(struct skb_shared_info)); + + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) +- get_page(skb_shinfo(skb)->frags[i].page); ++ net_get_page(skb_shinfo(skb)->frags[i].page); + + if (skb_shinfo(skb)->frag_list) + skb_clone_fraglist(skb); +@@ -850,7 +850,7 @@ drop_pages: + skb_shinfo(skb)->nr_frags = i; + + for (; i < nfrags; i++) +- put_page(skb_shinfo(skb)->frags[i].page); ++ net_put_page(skb_shinfo(skb)->frags[i].page); + + if (skb_shinfo(skb)->frag_list) + skb_drop_fraglist(skb); +@@ -1019,7 +1019,7 @@ pull_pages: + k = 0; + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + if (skb_shinfo(skb)->frags[i].size <= eat) { +- put_page(skb_shinfo(skb)->frags[i].page); ++ net_put_page(skb_shinfo(skb)->frags[i].page); + eat -= skb_shinfo(skb)->frags[i].size; + } else { + skb_shinfo(skb)->frags[k] = skb_shinfo(skb)->frags[i]; +@@ -1588,7 +1588,7 @@ static inline void skb_split_no_header(s + * where splitting is expensive. + * 2. Split is accurately. We make this. + */ +- get_page(skb_shinfo(skb)->frags[i].page); ++ net_get_page(skb_shinfo(skb)->frags[i].page); + skb_shinfo(skb1)->frags[0].page_offset += len - pos; + skb_shinfo(skb1)->frags[0].size -= len - pos; + skb_shinfo(skb)->frags[i].size = len - pos; +@@ -1963,7 +1963,7 @@ struct sk_buff *skb_segment(struct sk_bu + BUG_ON(i >= nfrags); + + *frag = skb_shinfo(skb)->frags[i]; +- get_page(frag->page); ++ net_get_page(frag->page); + size = frag->size; + + if (pos < offset) { +diff -upr linux-2.6.22/net/core/utils.c linux-2.6.22/net/core/utils.c +--- linux-2.6.22/net/core/utils.c 2007-07-09 03:32:17.000000000 +0400 ++++ linux-2.6.22/net/core/utils.c 2007-08-07 19:12:10.000000000 +0400 +@@ -25,6 +25,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -35,6 +36,9 @@ int net_msg_burst __read_mostly = 10; + int net_msg_warn __read_mostly = 1; + EXPORT_SYMBOL(net_msg_warn); + ++net_get_page_callback_t net_get_page_callback __read_mostly; ++net_put_page_callback_t net_put_page_callback __read_mostly; ++ + /* + * All net warning printk()s should be guarded by this function. + */ +@@ -292,3 +296,29 @@ out: + } + + EXPORT_SYMBOL(in6_pton); ++ ++int net_set_get_put_page_callbacks( ++ net_get_page_callback_t get_callback, ++ net_put_page_callback_t put_callback) ++{ ++ int res = 0; ++ ++ if ((net_get_page_callback != NULL) && (get_callback != NULL) && ++ (net_get_page_callback != get_callback)) { ++ res = -EBUSY; ++ goto out; ++ } ++ ++ if ((net_put_page_callback != NULL) && (put_callback != NULL) && ++ (net_put_page_callback != put_callback)) { ++ res = -EBUSY; ++ goto out; ++ } ++ ++ net_get_page_callback = get_callback; ++ net_put_page_callback = put_callback; ++ ++out: ++ return res; ++} ++EXPORT_SYMBOL(net_set_get_put_page_callbacks); +diff -upr linux-2.6.22/net/ipv4/ip_output.c linux-2.6.22/net/ipv4/ip_output.c +--- linux-2.6.22/net/ipv4/ip_output.c 2007-07-09 03:32:17.000000000 +0400 ++++ linux-2.6.22/net/ipv4/ip_output.c 2007-08-07 19:12:10.000000000 +0400 +@@ -996,7 +996,7 @@ alloc_new_skb: + err = -EMSGSIZE; + goto error; + } +- get_page(page); ++ net_get_page(page); + skb_fill_page_desc(skb, i, page, sk->sk_sndmsg_off, 0); + frag = &skb_shinfo(skb)->frags[i]; + } +@@ -1154,7 +1154,7 @@ ssize_t ip_append_page(struct sock *sk, + if (skb_can_coalesce(skb, i, page, offset)) { + skb_shinfo(skb)->frags[i-1].size += len; + } else if (i < MAX_SKB_FRAGS) { +- get_page(page); ++ net_get_page(page); + skb_fill_page_desc(skb, i, page, offset, len); + } else { + err = -EMSGSIZE; +diff -upr linux-2.6.22/net/ipv4/tcp.c linux-2.6.22/net/ipv4/tcp.c +--- linux-2.6.22/net/ipv4/tcp.c 2007-07-09 03:32:17.000000000 +0400 ++++ linux-2.6.22/net/ipv4/tcp.c 2007-08-07 19:12:10.000000000 +0400 +@@ -560,7 +560,7 @@ new_segment: + if (can_coalesce) { + skb_shinfo(skb)->frags[i - 1].size += copy; + } else { +- get_page(page); ++ net_get_page(page); + skb_fill_page_desc(skb, i, page, offset, copy); + } + +@@ -764,7 +764,7 @@ new_segment: + goto new_segment; + } else if (page) { + if (off == PAGE_SIZE) { +- put_page(page); ++ net_put_page(page); + TCP_PAGE(sk) = page = NULL; + off = 0; + } +@@ -805,9 +805,9 @@ new_segment: + } else { + skb_fill_page_desc(skb, i, page, off, copy); + if (TCP_PAGE(sk)) { +- get_page(page); ++ net_get_page(page); + } else if (off + copy < PAGE_SIZE) { +- get_page(page); ++ net_get_page(page); + TCP_PAGE(sk) = page; + } + } +diff -upr linux-2.6.22/net/ipv4/tcp_output.c linux-2.6.22/net/ipv4/tcp_output.c +--- linux-2.6.22/net/ipv4/tcp_output.c 2007-07-09 03:32:17.000000000 +0400 ++++ linux-2.6.22/net/ipv4/tcp_output.c 2007-08-07 19:12:10.000000000 +0400 +@@ -721,7 +721,7 @@ static void __pskb_trim_head(struct sk_b + k = 0; + for (i=0; inr_frags; i++) { + if (skb_shinfo(skb)->frags[i].size <= eat) { +- put_page(skb_shinfo(skb)->frags[i].page); ++ net_put_page(skb_shinfo(skb)->frags[i].page); + eat -= skb_shinfo(skb)->frags[i].size; + } else { + skb_shinfo(skb)->frags[k] = skb_shinfo(skb)->frags[i]; +diff -upr linux-2.6.22/net/ipv6/ip6_output.c linux-2.6.22/net/ipv6/ip6_output.c +--- linux-2.6.22/net/ipv6/ip6_output.c 2007-07-09 03:32:17.000000000 +0400 ++++ linux-2.6.22/net/ipv6/ip6_output.c 2007-08-07 19:12:10.000000000 +0400 +@@ -1291,7 +1291,7 @@ alloc_new_skb: + err = -EMSGSIZE; + goto error; + } +- get_page(page); ++ net_get_page(page); + skb_fill_page_desc(skb, i, page, sk->sk_sndmsg_off, 0); + frag = &skb_shinfo(skb)->frags[i]; + } diff --git a/iscsi-scst/kernel/session.c b/iscsi-scst/kernel/session.c new file mode 100644 index 000000000..c3580075e --- /dev/null +++ b/iscsi-scst/kernel/session.c @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2002-2003 Ardis Technolgies + * + * Released under the terms of the GNU GPL v2.0. + */ + +#include "iscsi.h" + +/* target_mutex supposed to be locked */ +struct iscsi_session *session_lookup(struct iscsi_target *target, u64 sid) +{ + struct iscsi_session *session; + + list_for_each_entry(session, &target->session_list, session_list_entry) { + if (session->sid == sid) + return session; + } + return NULL; +} + +/* target_mutex supposed to be locked */ +static int iscsi_session_alloc(struct iscsi_target *target, struct session_info *info) +{ + int err, i; + struct iscsi_session *session; + char *name = NULL; + + TRACE_MGMT_DBG("Creating session: target %p, tid %u, sid %#Lx", + target, target->tid, (unsigned long long) info->sid); + + if (!(session = kzalloc(sizeof(*session), GFP_KERNEL))) + return -ENOMEM; + + session->target = target; + session->sid = info->sid; + BUILD_BUG_ON(sizeof(session->sess_param) != + sizeof(target->trgt_sess_param)); + memcpy(&session->sess_param, &target->trgt_sess_param, + sizeof(session->sess_param)); + session->max_queued_cmnds = target->trgt_param.queued_cmnds; + + session->exp_cmd_sn = info->exp_cmd_sn; + session->max_cmd_sn = info->max_cmd_sn; + + session->initiator_name = kstrdup(info->initiator_name, GFP_KERNEL); + if (!session->initiator_name) { + err = -ENOMEM; + goto err; + } + + name = kmalloc(strlen(info->user_name) + strlen(info->initiator_name) + + 1, GFP_KERNEL); + if (name == NULL){ + err = -ENOMEM; + goto err; + } + + if (info->user_name[0] != '\0') + sprintf(name, "%s@%s", info->user_name, info->initiator_name); + else + sprintf(name, "%s", info->initiator_name); + + INIT_LIST_HEAD(&session->conn_list); + INIT_LIST_HEAD(&session->pending_list); + + spin_lock_init(&session->sn_lock); + + spin_lock_init(&session->cmnd_hash_lock); + for (i = 0; i < ARRAY_SIZE(session->cmnd_hash); i++) + INIT_LIST_HEAD(&session->cmnd_hash[i]); + + session->next_ttt = 1; + + session->scst_sess = scst_register_session(target->scst_tgt, 0, + name, NULL, NULL); + if (session->scst_sess == NULL) { + PRINT_ERROR_PR("%s", "scst_register_session() failed"); + err = -ENOMEM; + goto err; + } + + kfree(name); + + list_add(&session->session_list_entry, &target->session_list); + + return 0; +err: + if (session) { + kfree(session->initiator_name); + kfree(session); + kfree(name); + } + return err; +} + +/* target_mutex supposed to be locked */ +int session_add(struct iscsi_target *target, struct session_info *info) +{ + struct iscsi_session *session; + int err = -EEXIST; + + session = session_lookup(target, info->sid); + if (session) + return err; + + err = iscsi_session_alloc(target, info); + + return err; +} + +/* target_mutex supposed to be locked */ +int session_free(struct iscsi_session *session) +{ + int i; + + TRACE_MGMT_DBG("Freeing session %p:%#Lx", session, + (unsigned long long)session->sid); + + sBUG_ON(!list_empty(&session->conn_list)); + + for (i = 0; i < ARRAY_SIZE(session->cmnd_hash); i++) + sBUG_ON(!list_empty(&session->cmnd_hash[i])); + + scst_unregister_session(session->scst_sess, 1, NULL); + + list_del(&session->session_list_entry); + + kfree(session->initiator_name); + kfree(session); + + return 0; +} + +/* target_mutex supposed to be locked */ +int session_del(struct iscsi_target *target, u64 sid) +{ + struct iscsi_session *session; + + session = session_lookup(target, sid); + if (!session) + return -ENOENT; + + if (!list_empty(&session->conn_list)) { + PRINT_ERROR_PR("%llu still have connections", + (unsigned long long)session->sid); + return -EBUSY; + } + + return session_free(session); +} + +/* target_mutex supposed to be locked */ +static void iscsi_session_info_show(struct seq_file *seq, struct iscsi_target *target) +{ + struct iscsi_session *session; + + list_for_each_entry(session, &target->session_list, session_list_entry) { + seq_printf(seq, "\tsid:%llu initiator:%s\n", + (unsigned long long) session->sid, session->initiator_name); + conn_info_show(seq, session); + } +} + +static int iscsi_sessions_info_show(struct seq_file *seq, void *v) +{ + return iscsi_info_show(seq, iscsi_session_info_show); +} + +static int iscsi_session_seq_open(struct inode *inode, struct file *file) +{ + return single_open(file, iscsi_sessions_info_show, NULL); +} + +struct file_operations session_seq_fops = { + .owner = THIS_MODULE, + .open = iscsi_session_seq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; diff --git a/iscsi-scst/kernel/target.c b/iscsi-scst/kernel/target.c new file mode 100644 index 000000000..0d09eb236 --- /dev/null +++ b/iscsi-scst/kernel/target.c @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2002-2003 Ardis Technolgies + * + * Released under the terms of the GNU GPL v2.0. + */ + +#include + +#include "iscsi.h" +#include "digest.h" + +#define MAX_NR_TARGETS (1UL << 30) + +DEFINE_MUTEX(target_mgmt_mutex); + +/* All 3 protected by target_mgmt_mutex */ +static LIST_HEAD(target_list); +static u32 next_target_id; +static u32 nr_targets; + +static struct iscsi_sess_param default_session_param = { + .initial_r2t = 1, + .immediate_data = 1, + .max_connections = 1, + .max_recv_data_length = 8192, + .max_xmit_data_length = 8192, + .max_burst_length = 262144, + .first_burst_length = 65536, + .default_wait_time = 2, + .default_retain_time = 20, + .max_outstanding_r2t = 1, + .data_pdu_inorder = 1, + .data_sequence_inorder = 1, + .error_recovery_level = 0, + .header_digest = DIGEST_NONE, + .data_digest = DIGEST_NONE, + .ofmarker = 0, + .ifmarker = 0, + .ofmarkint = 2048, + .ifmarkint = 2048, +}; + +static struct iscsi_trgt_param default_target_param = { + .queued_cmnds = DEFAULT_NR_QUEUED_CMNDS, +}; + +/* target_mgmt_mutex supposed to be locked */ +struct iscsi_target *target_lookup_by_id(u32 id) +{ + struct iscsi_target *target; + + list_for_each_entry(target, &target_list, target_list_entry) { + if (target->tid == id) + return target; + } + return NULL; +} + +/* target_mgmt_mutex supposed to be locked */ +static struct iscsi_target *target_lookup_by_name(char *name) +{ + struct iscsi_target *target; + + list_for_each_entry(target, &target_list, target_list_entry) { + if (!strcmp(target->name, name)) + return target; + } + return NULL; +} + +/* target_mgmt_mutex supposed to be locked */ +static int iscsi_target_create(struct target_info *info, u32 tid) +{ + int err = -EINVAL, len; + char *name = info->name; + struct iscsi_target *target; + + TRACE_MGMT_DBG("Creating target tid %u, name %s", tid, name); + + if (!(len = strlen(name))) { + PRINT_ERROR_PR("The length of the target name is zero %u", tid); + goto out; + } + + if (!try_module_get(THIS_MODULE)) { + PRINT_ERROR_PR("Fail to get module %u", tid); + goto out; + } + + if (!(target = kzalloc(sizeof(*target), GFP_KERNEL))) { + err = -ENOMEM; + goto out_put; + } + + target->tid = info->tid = tid; + + memcpy(&target->trgt_sess_param, &default_session_param, + sizeof(default_session_param)); + memcpy(&target->trgt_param, &default_target_param, + sizeof(default_target_param)); + + strncpy(target->name, name, sizeof(target->name) - 1); + + mutex_init(&target->target_mutex); + INIT_LIST_HEAD(&target->session_list); + + list_add(&target->target_list_entry, &target_list); + + target->scst_tgt = scst_register(&iscsi_template, target->name); + if (!target->scst_tgt) { + PRINT_ERROR_PR("%s", "scst_register() failed"); + goto out_free; + } + + return 0; + +out_free: + kfree(target); + +out_put: + module_put(THIS_MODULE); + +out: + return err; +} + +/* target_mgmt_mutex supposed to be locked */ +int target_add(struct target_info *info) +{ + int err = -EEXIST; + u32 tid = info->tid; + + if (nr_targets > MAX_NR_TARGETS) { + err = -EBUSY; + goto out; + } + + if (target_lookup_by_name(info->name)) + goto out; + + if (tid && target_lookup_by_id(tid)) + goto out; + + if (!tid) { + do { + if (!++next_target_id) + ++next_target_id; + } while(target_lookup_by_id(next_target_id)); + + tid = next_target_id; + } + + if (!(err = iscsi_target_create(info, tid))) + nr_targets++; +out: + return err; +} + +static void target_destroy(struct iscsi_target *target) +{ + TRACE_MGMT_DBG("Destroying target tid %u", target->tid); + + scst_unregister(target->scst_tgt); + + kfree(target); + + module_put(THIS_MODULE); +} + +/* target_mgmt_mutex supposed to be locked */ +int target_del(u32 id) +{ + struct iscsi_target *target; + int err; + + if (!(target = target_lookup_by_id(id))) { + err = -ENOENT; + goto out; + } + + mutex_lock(&target->target_mutex); + + if (!list_empty(&target->session_list)) { + err = -EBUSY; + goto out_unlock; + } + + list_del(&target->target_list_entry); + nr_targets--; + + mutex_unlock(&target->target_mutex); + + target_destroy(target); + return 0; + +out_unlock: + mutex_unlock(&target->target_mutex); + +out: + return err; +} + +void target_del_all(void) +{ + struct iscsi_target *target, *t; + + TRACE_ENTRY(); + + TRACE(TRACE_MGMT, "%s", "Deleting all targets"); + + /* Complete brain damage, ToDo */ + while(1) { + mutex_lock(&target_mgmt_mutex); + + if (list_empty(&target_list)) + break; + + list_for_each_entry_safe(target, t, &target_list, target_list_entry) { + struct iscsi_session *session, *ts; + mutex_lock(&target->target_mutex); + if (!list_empty(&target->session_list)) { + TRACE_DBG("target %p", target); + list_for_each_entry_safe(session, ts, &target->session_list, + session_list_entry) { + TRACE_DBG("session %p", session); + if (!list_empty(&session->conn_list)) { + struct iscsi_conn *conn, *tc; + list_for_each_entry_safe(conn, tc, + &session->conn_list, + conn_list_entry) { + TRACE_DBG("conn %p", conn); + mark_conn_closed(conn); + } + } else { + TRACE_DBG("session %p with empty " + "connection list", session); + } + } + mutex_unlock(&target->target_mutex); + } else { + TRACE_DBG("deleting target %p", target); + list_del(&target->target_list_entry); + nr_targets--; + mutex_unlock(&target->target_mutex); + target_destroy(target); + continue; + } + } + mutex_unlock(&target_mgmt_mutex); + msleep(100); + } + + mutex_unlock(&target_mgmt_mutex); + + TRACE_EXIT(); + return; +} + +int iscsi_info_show(struct seq_file *seq, iscsi_show_info_t *func) +{ + int err; + struct iscsi_target *target; + + if ((err = mutex_lock_interruptible(&target_mgmt_mutex)) < 0) + return err; + + list_for_each_entry(target, &target_list, target_list_entry) { + seq_printf(seq, "tid:%u name:%s\n", target->tid, target->name); + + mutex_lock(&target->target_mutex); + func(seq, target); + mutex_unlock(&target->target_mutex); + } + + mutex_unlock(&target_mgmt_mutex); + + return 0; +} diff --git a/iscsi-scst/usr/Makefile b/iscsi-scst/usr/Makefile new file mode 100644 index 000000000..4fbde899e --- /dev/null +++ b/iscsi-scst/usr/Makefile @@ -0,0 +1,38 @@ +SRCS_D = iscsid.c iscsi_scstd.c conn.c session.c target.c message.c ctldev.c \ + log.c chap.c event.c param.c plain.c isns.c +OBJS_D = $(SRCS_D:.c=.o) + +SRCS_ADM = iscsi_adm.c param.c +OBJS_ADM = $(SRCS_ADM:.c=.o) + +CFLAGS += -O2 -fno-inline -Wall -Wstrict-prototypes -g -I../include +PROGRAMS = iscsi-scstd iscsi-scst-adm +LIBS = -lcrypto + +all: $(PROGRAMS) + +iscsi-scstd: .depend_d $(OBJS_D) + $(CC) $(OBJS_D) $(LIBS) -o $@ + +iscsi-scst-adm: .depend_adm $(OBJS_ADM) + $(CC) $(OBJS_ADM) $(LIBS) -o $@ + +ifeq (.depend_d,$(wildcard .depend_d)) +-include .depend_d +endif + +ifeq (.depend_adm,$(wildcard .depend_adm)) +-include .depend_adm +endif + +%.o: %.c Makefile + $(CC) -c -o $(@) $(CFLAGS) $(<) + +.depend_d: + $(CC) -M $(CFLAGS) $(SRCS_D) >$(@) + +.depend_adm: + $(CC) -M $(CFLAGS) $(SRCS_ADM) >$(@) + +clean: + rm -f *.o $(PROGRAMS) .depend* diff --git a/iscsi-scst/usr/chap.c b/iscsi-scst/usr/chap.c new file mode 100644 index 000000000..48de87e32 --- /dev/null +++ b/iscsi-scst/usr/chap.c @@ -0,0 +1,633 @@ +/* + * chap.c - support for (mutual) CHAP authentication. + * (C) 2004 Xiranet Communications GmbH + * available under the terms of the GNU GPL v2.0 + * + * heavily based on code from iscsid.c: + * Copyright (C) 2002-2003 Ardis Technolgies , + * licensed under the terms of the GNU GPL v2.0, + * + * and code taken from UNH iSCSI software: + * Copyright (C) 2001-2003 InterOperability Lab (IOL) + * University of New Hampshire (UNH) + * Durham, NH 03824 + * licensed under the terms of the GNU GPL v2.0 + */ + +#include +#include +#include +#include +#include + +#include "iscsid.h" + +#define HEX_FORMAT 0x01 +#define BASE64_FORMAT 0x02 + +#define CHAP_DIGEST_ALG_MD5 5 +#define CHAP_DIGEST_ALG_SHA1 7 + +#define CHAP_MD5_DIGEST_LEN 16 +#define CHAP_SHA1_DIGEST_LEN 20 + +#define CHAP_INITIATOR_ERROR -1 +#define CHAP_AUTH_ERROR -2 +#define CHAP_TARGET_ERROR -3 + +#define CHAP_AUTH_STATE_START AUTH_STATE_START +#define CHAP_AUTH_STATE_CHALLENGE 1 +#define CHAP_AUTH_STATE_RESPONSE 2 + +#define CHAP_INITIATOR_AUTH 0 +#define CHAP_TARGET_AUTH 1 + +#define CHAP_CHALLENGE_MAX 50 + +static inline int decode_hex_digit(char c) +{ + switch (c) { + case '0' ... '9': + return c - '0'; + case 'a' ... 'f': + return c - 'a' + 10; + case 'A' ... 'F': + return c - 'A' + 10; + } + return 0; +} + +static void decode_hex_string(char *hex_string, u8 *intnum, int intlen) +{ + char *ptr; + int j; + + j = strlen(hex_string); + ptr = hex_string + j; + j = --intlen; + do { + intnum[j] = decode_hex_digit(*--ptr); + intnum[j] |= decode_hex_digit(*--ptr) << 4; + j--; + } while (ptr > hex_string); + + while (j >= 0) + intnum[j--] = 0; +} + + +/* Base64 decoding, taken from UNH-iSCSI "Base64codeToNumber()" */ +static u8 decode_base64_digit(char base64) +{ + switch (base64) { + case '=': + return 64; + case '/': + return 63; + case '+': + return 62; + default: + if ((base64 >= 'A') && (base64 <= 'Z')) + return base64 - 'A'; + else if ((base64 >= 'a') && (base64 <= 'z')) + return 26 + (base64 - 'a'); + else if ((base64 >= '0') && (base64 <= '9')) + return 52 + (base64 - '0'); + else + return -1; + } +} + +/* Base64 decoding, taken from UNH-iSCSI "Base64StringToInteger()" */ +static void decode_base64_string(char *string, u8 *intnum, int int_len) +{ + int len; + int count; + int intptr; + u8 num[4]; + int octets; + + if ((string == NULL) || (intnum == NULL)) + return; + len = strlen(string); + if (len == 0) + return; + if ((len % 4) != 0) + return; + count = 0; + intptr = 0; + while (count < len - 4) { + num[0] = decode_base64_digit(string[count]); + num[1] = decode_base64_digit(string[count + 1]); + num[2] = decode_base64_digit(string[count + 2]); + num[3] = decode_base64_digit(string[count + 3]); + if ((num[0] == 65) || (num[1] == 65) || (num[2] == 65) || (num[3] == 65)) + return; + count += 4; + octets = + (num[0] << 18) | (num[1] << 12) | (num[2] << 6) | num[3]; + intnum[intptr] = (octets & 0xFF0000) >> 16; + intnum[intptr + 1] = (octets & 0x00FF00) >> 8; + intnum[intptr + 2] = octets & 0x0000FF; + intptr += 3; + } + num[0] = decode_base64_digit(string[count]); + num[1] = decode_base64_digit(string[count + 1]); + num[2] = decode_base64_digit(string[count + 2]); + num[3] = decode_base64_digit(string[count + 3]); + if ((num[0] == 64) || (num[1] == 64)) + return; + if (num[2] == 64) { + if (num[3] != 64) + return; + intnum[intptr] = (num[0] << 2) | (num[1] >> 4); + } else if (num[3] == 64) { + intnum[intptr] = (num[0] << 2) | (num[1] >> 4); + intnum[intptr + 1] = (num[1] << 4) | (num[2] >> 2); + } else { + octets = + (num[0] << 18) | (num[1] << 12) | (num[2] << 6) | num[3]; + intnum[intptr] = (octets & 0xFF0000) >> 16; + intnum[intptr + 1] = (octets & 0x00FF00) >> 8; + intnum[intptr + 2] = octets & 0x0000FF; + } +} + +static inline void encode_hex_string(u8 *intnum, long length, char *string) +{ + int i; + char *strptr; + + strptr = string; + for (i = 0; i < length; i++, strptr += 2) + sprintf(strptr, "%.2hhx", intnum[i]); +} + +/* Base64 encoding, taken from UNH iSCSI "IntegerToBase64String()" */ +static void encode_base64_string(u8 *intnum, long length, char *string) +{ + int count, octets, strptr, delta; + static const char base64code[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', + 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', 'i', + 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', + 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', + '/', '=' }; + + if ((!intnum) || (!string) || (!length)) + return; + + count = 0; + octets = 0; + strptr = 0; + + while ((delta = (length - count)) > 2) { + octets = (intnum[count] << 16) | (intnum[count + 1] << 8) | intnum[count + 2]; + string[strptr] = base64code[(octets & 0xfc0000) >> 18]; + string[strptr + 1] = base64code[(octets & 0x03f000) >> 12]; + string[strptr + 2] = base64code[(octets & 0x000fc0) >> 6]; + string[strptr + 3] = base64code[octets & 0x00003f]; + count += 3; + strptr += 4; + } + if (delta == 1) { + string[strptr] = base64code[(intnum[count] & 0xfc) >> 2]; + string[strptr + 1] = base64code[(intnum[count] & 0x03) << 4]; + string[strptr + 2] = base64code[64]; + string[strptr + 3] = base64code[64]; + strptr += 4; + } else if (delta == 2) { + string[strptr] = base64code[(intnum[count] & 0xfc) >> 2]; + string[strptr + 1] = base64code[((intnum[count] & 0x03) << 4) | ((intnum[count + 1] & 0xf0) >> 4)]; + string[strptr + 2] = base64code[(intnum[count + 1] & 0x0f) << 2]; + string[strptr + 3] = base64code[64]; + strptr += 4; + } + string[strptr] = '\0'; +} + +static inline int chap_check_encoding_format(char *encoded) +{ + int encoding_fmt; + + if (!encoded) + return -1; + if ((strlen(encoded) < 3) || (encoded[0] != '0')) + return -1; + + if (encoded[1] == 'x' || encoded[1] == 'X') + encoding_fmt = HEX_FORMAT; + else if (encoded[1] == 'b' || encoded[1] == 'B') + encoding_fmt = BASE64_FORMAT; + else + return -1; + + return encoding_fmt; +} + +static int chap_alloc_decode_buffer(char *encoded, u8 **decode_buf, int encoding_fmt) +{ + int i; + int decode_len = 0; + + i = strlen(encoded); + i -= 2; + + if (encoding_fmt == HEX_FORMAT) + decode_len = (i - 1) / 2 + 1; + else if (encoding_fmt == BASE64_FORMAT) { + if (i % 4) + return CHAP_INITIATOR_ERROR; + + decode_len = i / 4 * 3; + if (encoded[i + 1] == '=') + decode_len--; + if (encoded[i] == '=') + decode_len--; + } + + if (!decode_len) + return CHAP_INITIATOR_ERROR; + + *decode_buf = malloc(decode_len); + if (!*decode_buf) + return CHAP_TARGET_ERROR; + + return decode_len; +} + +static int chap_decode_string(char *encoded, u8 *decode_buf, int buf_len, int encoding_fmt) +{ + if (encoding_fmt == HEX_FORMAT) { + if ((strlen(encoded) - 2) > (2 * buf_len)) { + log_error("%s(%d) BUG? " + " buf[%d] !sufficient to decode string[%d]", + __FUNCTION__, __LINE__, buf_len, (int) strlen(encoded)); + return CHAP_TARGET_ERROR; + } + decode_hex_string(encoded + 2, decode_buf, buf_len); + + } else if (encoding_fmt == BASE64_FORMAT) { + if ((strlen(encoded) - 2) > ((buf_len - 1) / 3 + 1) * 4) { + log_error("%s(%d) BUG? " + " buf[%d] !sufficient to decode string[%d]", + __FUNCTION__, __LINE__, buf_len, (int) strlen(encoded)); + return CHAP_TARGET_ERROR; + } + decode_base64_string(encoded + 2, decode_buf, buf_len); + + } else + return CHAP_INITIATOR_ERROR; + + return 0; +} + +static inline void chap_encode_string(u8 *intnum, int buf_len, char *encode_buf, int encoding_fmt) +{ + encode_buf[0] = '0'; + if (encoding_fmt == HEX_FORMAT) { + encode_buf[1] = 'x'; + encode_hex_string(intnum, buf_len, encode_buf + 2); + } else if (encoding_fmt == BASE64_FORMAT) { + encode_buf[1] = 'b'; + encode_base64_string(intnum, buf_len, encode_buf + 2); + } +} + +static inline void chap_calc_digest_md5(char chap_id, char *secret, int secret_len, u8 *challenge, int challenge_len, u8 *digest) +{ + MD5_CTX ctx; + + MD5_Init(&ctx); + MD5_Update(&ctx, &chap_id, 1); + MD5_Update(&ctx, secret, secret_len); + MD5_Update(&ctx, challenge, challenge_len); + MD5_Final(digest, &ctx); +} + +static inline void chap_calc_digest_sha1(char chap_id, char *secret, int secret_len, u8 *challenge, int challenge_len, u8 *digest) +{ + SHA_CTX ctx; + + SHA1_Init(&ctx); + SHA1_Update(&ctx, &chap_id, 1); + SHA1_Update(&ctx, secret, secret_len); + SHA1_Update(&ctx, challenge, challenge_len); + SHA1_Final(digest, &ctx); +} + +static int chap_initiator_auth_create_challenge(struct connection *conn) +{ + char *value, *p; + char text[CHAP_CHALLENGE_MAX * 2 + 8]; + static int chap_id; + int i; + + value = text_key_find(conn, "CHAP_A"); + if (!value) + return CHAP_INITIATOR_ERROR; + while ((p = strsep(&value, ","))) { + if (!strcmp(p, "5")) { + conn->auth.chap.digest_alg = CHAP_DIGEST_ALG_MD5; + conn->auth_state = CHAP_AUTH_STATE_CHALLENGE; + break; + } else if (!strcmp(p, "7")) { + conn->auth.chap.digest_alg = CHAP_DIGEST_ALG_SHA1; + conn->auth_state = CHAP_AUTH_STATE_CHALLENGE; + break; + } + } + if (!p) + return CHAP_INITIATOR_ERROR; + + text_key_add(conn, "CHAP_A", p); + conn->auth.chap.id = ++chap_id; + sprintf(text, "%u", (unsigned char)conn->auth.chap.id); + text_key_add(conn, "CHAP_I", text); + + /* + * FIXME: does a random challenge length provide any benefits security- + * wise, or should we rather always use the max. allowed length of + * 1024 for the (unencoded) challenge? + */ + conn->auth.chap.challenge_size = (rand() % (CHAP_CHALLENGE_MAX / 2)) + CHAP_CHALLENGE_MAX / 2; + + conn->auth.chap.challenge = malloc(conn->auth.chap.challenge_size); + if (!conn->auth.chap.challenge) + return CHAP_TARGET_ERROR; + + p = text; + strcpy(p, "0x"); + p += 2; + for (i = 0; i < conn->auth.chap.challenge_size; i++) { + conn->auth.chap.challenge[i] = rand(); + sprintf(p, "%.2hhx", conn->auth.chap.challenge[i]); + p += 2; + } + text_key_add(conn, "CHAP_C", text); + + return 0; +} + +static int chap_initiator_auth_check_response(struct connection *conn) +{ + char *value; + u8 *his_digest = NULL, *our_digest = NULL; + int digest_len = 0, retval = 0, encoding_format; + char pass[ISCSI_NAME_LEN]; + + memset(pass, 0, sizeof(pass)); + if (cops->account_query(conn->tid, AUTH_DIR_INCOMING, pass, pass) < 0) { + log_warning("CHAP initiator auth.: " + "No CHAP credentials configured"); + retval = CHAP_TARGET_ERROR; + goto out; + } + + if (!(value = text_key_find(conn, "CHAP_N"))) { + retval = CHAP_INITIATOR_ERROR; + goto out; + } + + conn->user = strdup(value); + if (conn->user == NULL) { + log_error("Unable to dublicate initiator's USER %s", value); + } + + memset(pass, 0, sizeof(pass)); + if (cops->account_query(conn->tid, AUTH_DIR_INCOMING, value, pass) < 0) { + log_warning("CHAP initiator auth.: " + "No valid user/pass combination for initiator %s " + "found", conn->initiator); + retval = CHAP_AUTH_ERROR; + goto out; + } + + if (!(value = text_key_find(conn, "CHAP_R"))) { + retval = CHAP_INITIATOR_ERROR; + goto out; + } + + if ((encoding_format = chap_check_encoding_format(value)) < 0) { + retval = CHAP_INITIATOR_ERROR; + goto out; + } + + switch (conn->auth.chap.digest_alg) { + case CHAP_DIGEST_ALG_MD5: + digest_len = CHAP_MD5_DIGEST_LEN; + break; + case CHAP_DIGEST_ALG_SHA1: + digest_len = CHAP_SHA1_DIGEST_LEN; + break; + default: + retval = CHAP_TARGET_ERROR; + goto out; + } + + if (!(his_digest = malloc(digest_len))) { + retval = CHAP_TARGET_ERROR; + goto out; + } + if (!(our_digest = malloc(digest_len))) { + retval = CHAP_TARGET_ERROR; + goto out; + } + + if (chap_decode_string(value, his_digest, digest_len, encoding_format) < 0) { + retval = CHAP_INITIATOR_ERROR; + goto out; + } + + switch (conn->auth.chap.digest_alg) { + case CHAP_DIGEST_ALG_MD5: + chap_calc_digest_md5(conn->auth.chap.id, pass, strlen(pass), + conn->auth.chap.challenge, + conn->auth.chap.challenge_size, + our_digest); + break; + case CHAP_DIGEST_ALG_SHA1: + chap_calc_digest_sha1(conn->auth.chap.id, pass, strlen(pass), + conn->auth.chap.challenge, + conn->auth.chap.challenge_size, + our_digest); + break; + default: + retval = CHAP_TARGET_ERROR; + goto out; + } + + if (memcmp(our_digest, his_digest, digest_len)) { + log_warning("CHAP initiator auth.: " + "authentication of %s failed (wrong secret!?)", + conn->initiator); + retval = CHAP_AUTH_ERROR; + goto out; + } + + conn->state = CHAP_AUTH_STATE_RESPONSE; + out: + if (his_digest) + free(his_digest); + if (our_digest) + free(our_digest); + return retval; +} + +static int chap_target_auth_create_response(struct connection *conn) +{ + char chap_id, *value, *response = NULL; + u8 *challenge = NULL, *digest = NULL; + int encoding_format, response_len; + int challenge_len = 0, digest_len = 0, retval = 0; + char pass[ISCSI_NAME_LEN], name[ISCSI_NAME_LEN]; + + if (!(value = text_key_find(conn, "CHAP_I"))) { + /* initiator doesn't want target auth!? */ + conn->state = STATE_SECURITY_DONE; + retval = 0; + goto out; + } + chap_id = strtol(value, &value, 10); + + memset(pass, 0, sizeof(pass)); + memset(name, 0, sizeof(name)); + if (cops->account_query(conn->tid, AUTH_DIR_OUTGOING, name, pass) < 0) { + log_warning("CHAP target auth.: " + "no outgoing credentials configured%s", + conn->tid ? "." : " for discovery."); + retval = CHAP_AUTH_ERROR; + goto out; + } + + if (!(value = text_key_find(conn, "CHAP_C"))) { + log_warning("CHAP target auth.: " + "got no challenge from initiator %s", + conn->initiator); + retval = CHAP_INITIATOR_ERROR; + goto out; + } + + if ((encoding_format = chap_check_encoding_format(value)) < 0) { + retval = CHAP_INITIATOR_ERROR; + goto out; + } + + retval = chap_alloc_decode_buffer(value, &challenge, encoding_format); + if (retval <= 0) + goto out; + else if (retval > 1024) { + log_warning("CHAP target auth.: " + "initiator %s sent challenge of invalid length %d", + conn->initiator, challenge_len); + retval = CHAP_INITIATOR_ERROR; + goto out; + } + + challenge_len = retval; + retval = 0; + + switch (conn->auth.chap.digest_alg) { + case CHAP_DIGEST_ALG_MD5: + digest_len = CHAP_MD5_DIGEST_LEN; + break; + case CHAP_DIGEST_ALG_SHA1: + digest_len = CHAP_SHA1_DIGEST_LEN; + break; + default: + retval = CHAP_TARGET_ERROR; + goto out; + } + + if (encoding_format == HEX_FORMAT) + response_len = 2 * digest_len; + else + response_len = ((digest_len - 1) / 3 + 1) * 4; + //"0x" / "0b" and "\0": + response_len += 3; + + if (!(digest = malloc(digest_len))) { + retval = CHAP_TARGET_ERROR; + goto out; + } + if (!(response = malloc(response_len))) { + retval = CHAP_TARGET_ERROR; + goto out; + } + + if (chap_decode_string(value, challenge, challenge_len, encoding_format) < 0) { + retval = CHAP_INITIATOR_ERROR; + goto out; + } + + /* RFC 3720, 8.2.1: CHAP challenges MUST NOT be reused */ + if (challenge_len == conn->auth.chap.challenge_size) { + if (!memcmp(challenge, conn->auth.chap.challenge, + challenge_len)) { + //FIXME: RFC 3720 demands to close TCP conn. + log_warning("CHAP target auth.: " + "initiator %s reflected our challenge", + conn->initiator); + retval = CHAP_INITIATOR_ERROR; + goto out; + } + } + + switch (conn->auth.chap.digest_alg) { + case CHAP_DIGEST_ALG_MD5: + chap_calc_digest_md5(chap_id, pass, strlen(pass), + challenge, challenge_len, digest); + break; + case CHAP_DIGEST_ALG_SHA1: + chap_calc_digest_sha1(chap_id, pass, strlen(pass), + challenge, challenge_len, digest); + break; + default: + retval = CHAP_TARGET_ERROR; + goto out; + } + + memset(response, 0x0, response_len); + chap_encode_string(digest, digest_len, response, encoding_format); + text_key_add(conn, "CHAP_N", name); + text_key_add(conn, "CHAP_R", response); + + conn->state = STATE_SECURITY_DONE; + out: + if (challenge) + free(challenge); + if (digest) + free(digest); + if (response) + free(response); + return retval; +} + +int cmnd_exec_auth_chap(struct connection *conn) +{ + int res; + + switch(conn->auth_state) { + case CHAP_AUTH_STATE_START: + res = chap_initiator_auth_create_challenge(conn); + break; + case CHAP_AUTH_STATE_CHALLENGE: + res = chap_initiator_auth_check_response(conn); + if (res < 0) + break; + /* fall through */ + case CHAP_AUTH_STATE_RESPONSE: + res = chap_target_auth_create_response(conn); + break; + default: + log_error("%s(%d): BUG. unknown conn->auth_state %d", + __FUNCTION__, __LINE__, conn->auth_state); + res = CHAP_TARGET_ERROR; + } + + return res; +} diff --git a/iscsi-scst/usr/config.h b/iscsi-scst/usr/config.h new file mode 100644 index 000000000..0f44544b7 --- /dev/null +++ b/iscsi-scst/usr/config.h @@ -0,0 +1,19 @@ +#ifndef CONFIG_H +#define CONFIG_H + +struct config_operations { + int (*init) (char *, char **, int *); + int (*default_load) (char *); + int (*target_add) (u32 *, char *); + int (*target_stop) (u32); + int (*target_del) (u32); + int (*param_set) (u32, u64, int, u32, struct iscsi_param *); + int (*account_add) (u32, int, char *, char *); + int (*account_del) (u32, int, char *); + int (*account_query) (u32, int, char *, char *); + int (*initiator_access) (u32, int); +}; + +extern struct config_operations *cops; + +#endif diff --git a/iscsi-scst/usr/conn.c b/iscsi-scst/usr/conn.c new file mode 100644 index 000000000..110585cbd --- /dev/null +++ b/iscsi-scst/usr/conn.c @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2002-2003 Ardis Technolgies + * + * Released under the terms of the GNU GPL v2.0. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "iscsid.h" + +#define ISCSI_CONN_NEW 1 +#define ISCSI_CONN_EXIT 5 + +struct connection *conn_alloc(void) +{ + struct connection *conn; + + if (!(conn = malloc(sizeof(*conn)))) + return NULL; + + memset(conn, 0, sizeof(*conn)); + conn->state = STATE_FREE; + param_set_defaults(conn->session_param, session_keys); + + return conn; +} + +void conn_free(struct connection *conn) +{ + free(conn->initiator); + free(conn->user); + free(conn); +} + +int conn_test(struct connection *conn) +{ + FILE *f; + char buf[8192], *p; + u32 tid, t_tid, cid, t_cid; + u64 sid, t_sid; + int err = -ENOENT, find = 0; + + t_tid = conn->tid; + t_sid = conn->session->sid.id64; + t_cid = conn->cid; + + if ((f = fopen(PROC_SESSION, "r")) == NULL) { + fprintf(stderr, "Can't open %s\n", PROC_SESSION); + return -errno; + } + + while (fgets(buf, sizeof(buf), f)) { + p = buf; + while (isspace((int) *p)) + p++; + + if (!strncmp(p, "tid:", 4)) { + if (sscanf(p, "tid:%u", &tid) != 1) { + err = -EIO; + goto out; + } + if (tid == t_tid) + find = 1; + else + find = 0; + } else if (!strncmp(p, "sid:", 4)) { + if (!find) + continue; + if (sscanf(p, "sid:%" SCNu64, &sid) != 1) { + err = -EIO; + goto out; + } + + if (sid == t_sid) + find = 1; + else + find = 0; + } else if (!strncmp(p, "cid:", 4)) { + if (!find) + continue; + if (sscanf(p, "cid:%u", &cid) != 1) { + err = -EIO; + goto out; + } + + if (cid == t_cid) { + err = 0; + goto out; + } + } + } + +out: + fclose(f); + + return err; +} + +void conn_take_fd(struct connection *conn, int fd) +{ + int err; + log_debug(1, "conn_take_fd: %d %u %u %u %" PRIx64, + fd, conn->cid, conn->stat_sn, conn->exp_stat_sn, conn->sid.id64); + + conn->session->conn_cnt++; + + err = ki->conn_create(conn->tid, conn->session->sid.id64, conn->cid, + conn->stat_sn, conn->exp_stat_sn, fd, + conn->session_param[key_header_digest].exec_val, + conn->session_param[key_data_digest].exec_val); + + return; +} + +void conn_read_pdu(struct connection *conn) +{ + conn->iostate = IOSTATE_READ_BHS; + conn->buffer = (void *)&conn->req.bhs; + conn->rwsize = BHS_SIZE; +} + +void conn_write_pdu(struct connection *conn) +{ + conn->iostate = IOSTATE_WRITE_BHS; + memset(&conn->rsp, 0, sizeof(conn->rsp)); + conn->buffer = (void *)&conn->rsp.bhs; + conn->rwsize = BHS_SIZE; +} + +void conn_free_pdu(struct connection *conn) +{ + conn->iostate = IOSTATE_FREE; + if (conn->req.ahs) { + free(conn->req.ahs); + conn->req.ahs = NULL; + } + if (conn->rsp.ahs) { + free(conn->rsp.ahs); + conn->rsp.ahs = NULL; + } + if (conn->rsp.data) { + free(conn->rsp.data); + conn->rsp.data = NULL; + } +} diff --git a/iscsi-scst/usr/ctldev.c b/iscsi-scst/usr/ctldev.c new file mode 100644 index 000000000..d25beb57b --- /dev/null +++ b/iscsi-scst/usr/ctldev.c @@ -0,0 +1,435 @@ +/* + * (C) 2004 - 2005 FUJITA Tomonori + * + * This code is licenced under the GPL. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "iscsid.h" + +#define CTL_DEVICE "/dev/iscsi-scst-ctl" + +struct session_file_operations { + int (*target_op) (int fd, u32 tid, void *arg); + int (*session_op) (int fd, u32 tid, u64 sid, void *arg); + int (*connection_op) (int fd, u32 tid, u64 sid, u32 cid, void *arg); +}; + +static int ctrdev_open(void) +{ + FILE *f; + char devname[256]; + char buf[256]; + int devn; + int ctlfd; + + if (!(f = fopen("/proc/devices", "r"))) { + perror("Cannot open control path to the driver\n"); + return -1; + } + + devn = 0; + while (!feof(f)) { + if (!fgets(buf, sizeof (buf), f)) { + break; + } + if (sscanf(buf, "%d %s", &devn, devname) != 2) { + continue; + } + if (!strcmp(devname, "iscsi-scst-ctl")) { + break; + } + devn = 0; + } + + fclose(f); + if (!devn) { + printf + ("cannot find iscsictl in /proc/devices - " + "make sure the module is loaded\n"); + return -1; + } + + unlink(CTL_DEVICE); + if (mknod(CTL_DEVICE, (S_IFCHR | 0600), (devn << 8))) { + printf("cannot create %s %d\n", CTL_DEVICE, errno); + return -1; + } + + ctlfd = open(CTL_DEVICE, O_RDWR); + if (ctlfd < 0) { + printf("cannot open %s %d\n", CTL_DEVICE, errno); + return -1; + } + + return ctlfd; +} + +static int iscsi_target_create(u32 *tid, char *name) +{ + int err; + struct target_info info; + + memset(&info, 0, sizeof(info)); + + memcpy(info.name, name, sizeof(info.name) - 1); + info.tid = *tid; + if ((err = ioctl(ctrl_fd, ADD_TARGET, &info)) < 0) + log_warning("can't create a target %d %u\n", errno, info.tid); + + *tid = info.tid; + return err; +} + +static int iscsi_target_destroy(u32 tid) +{ + struct target_info info; + + memset(&info, 0, sizeof(info)); + info.tid = tid; + + return ioctl(ctrl_fd, DEL_TARGET, &info); +} + +static int iscsi_conn_destroy(u32 tid, u64 sid, u32 cid) +{ + int err; + struct conn_info info; + + info.tid = tid; + info.sid = sid; + info.cid = cid; + + if ((err = ioctl(ctrl_fd, DEL_CONN, &info)) < 0) + err = errno; + + return err; +} + +/** + ** ToDo: the below code is a brain damage, rewrite it. + **/ + +static int __conn_close(int fd, u32 tid, u64 sid, u32 cid, void *arg) +{ + return ki->conn_destroy(tid, sid, cid); +} + +static int __target_del(int fd, u32 tid, void *arg) +{ + return ki->target_destroy(tid); +} + +static int proc_session_parse(int fd, struct session_file_operations *ops, + int op_tid, void *arg) +{ + FILE *f; + char buf[8192], *p; + u32 tid, cid; + u64 sid; + int err, skip, done = 0; + + if ((f = fopen(PROC_SESSION, "r")) == NULL) { + fprintf(stderr, "Can't open %s\n", PROC_SESSION); + return errno; + } + + skip = 0; + while (fgets(buf, sizeof(buf), f)) { + p = buf; + while (isspace((int) *p)) + p++; + + if (!strncmp(p, "tid:", 4)) { + if (sscanf(p, "tid:%u", &tid) != 1) + break; + if (op_tid != -1) { + if (tid == op_tid) + skip = 0; + else { + skip = 1; + if (done) + break; + else + continue; + } + } + if (ops->target_op) + if ((err = ops->target_op(fd, tid, arg)) < 0) + goto out; + continue; + } + if (skip) + continue; + if (!strncmp(p, "sid:", 4)) { + if (sscanf(p, "sid:%" SCNu64, &sid) != 1) { + log_error("Unknown %s sid syntax: %s\n", PROC_SESSION, p); + break; + } + + if (ops->session_op) + if ((err = ops->session_op(fd, tid, sid, arg)) < 0) + goto out; + } else if (!strncmp(p, "cid:", 4)) { + if (sscanf(p, "cid:%u", &cid) != 1) { + log_error("Unknown %s cid syntax: %s\n", PROC_SESSION, p); + break; + } + if (ops->connection_op) + if ((err = ops->connection_op(fd, tid, sid, cid, arg)) < 0) + goto out; + } else + log_error("Unknown %s string: %s\n", PROC_SESSION, p); + + done = 1; + } + + err = 0; +out: + fclose(f); + + return err; +} + +static int session_retry (int fd, u32 tid, u64 sid, void *arg) +{ + return -EAGAIN; +} + +static int conn_retry (int fd, u32 tid, u64 sid, u32 cid, void *arg) +{ + return -EAGAIN; +} + +static int __sess_cleanup(int fd, u32 tid, void *arg) +{ + wait_4_iscsi_event(100); + return 0; +} + +static struct session_file_operations conn_close_ops = { + .connection_op = __conn_close, +}; + +static struct session_file_operations conn_sess_cleanup_ops = { + .target_op = __sess_cleanup, +}; + +static struct session_file_operations shutdown_wait_ops = { + .session_op = session_retry, + .connection_op = conn_retry, +}; + +static struct session_file_operations target_del_ops = { + .target_op = __target_del, +}; + +int server_stop(void) +{ + conn_blocked = 1; + + proc_session_parse(ctrl_fd, &conn_close_ops, -1, NULL); + + while (proc_session_parse(ctrl_fd, &shutdown_wait_ops, -1, NULL) < 0) + sleep(1); + + proc_session_parse(ctrl_fd, &target_del_ops, -1, NULL); + + isns_exit(); + + return 0; +} + +int target_destroy(u32 tid) +{ + int err; + + conn_blocked = 1; + + proc_session_parse(ctrl_fd, &conn_close_ops, tid, NULL); + + while (proc_session_parse(ctrl_fd, &shutdown_wait_ops, tid, NULL) < 0) { + sleep(1); + } + proc_session_parse(ctrl_fd, &conn_sess_cleanup_ops, tid, NULL); + + err = proc_session_parse(ctrl_fd, &target_del_ops, tid, NULL); + + conn_blocked = 0; + + return err; +} + +struct session_conn_close_arg { + u64 sid; +}; + +static int session_conn_close(int fd, u32 tid, u64 sid, u32 cid, void *opaque) +{ + struct session_conn_close_arg *arg = (struct session_conn_close_arg *) opaque; + int err; + + if (arg->sid == sid) + err = ki->conn_destroy(tid, sid, cid); + + return 0; +} + +struct session_file_operations session_conns_close_ops = { + .connection_op = session_conn_close, +}; + +int session_conns_close(u32 tid, u64 sid) +{ + int err; + struct session_conn_close_arg arg = {sid}; + + err = proc_session_parse(ctrl_fd, &session_conns_close_ops, tid, &arg); + + return err; +} + +static int iscsi_param_get(u32 tid, u64 sid, int type, struct iscsi_param *param, + int local) +{ + int err, i; + struct iscsi_param_info info; + + memset(&info, 0, sizeof(info)); + info.tid = tid; + info.sid = sid; + info.param_type = type; + + if ((err = ioctl(ctrl_fd, ISCSI_PARAM_GET, &info)) < 0) + log_error("Can't get session param %d %d\n", info.tid, errno); + + if (local) { + if (type == key_session) + for (i = 0; i < session_key_last; i++) + param[i].local_val = info.session_param[i]; + else + for (i = 0; i < target_key_last; i++) + param[i].local_val = info.target_param[i]; + } else { + if (type == key_session) + for (i = 0; i < session_key_last; i++) + param[i].exec_val = info.session_param[i]; + else + for (i = 0; i < target_key_last; i++) + param[i].exec_val = info.target_param[i]; + } + + return err; +} + +static int iscsi_param_set(u32 tid, u64 sid, int type, u32 partial, + struct iscsi_param *param, int local) +{ + int i, err; + struct iscsi_param_info info; + + memset(&info, 0, sizeof(info)); + info.tid = tid; + info.sid = sid; + info.param_type = type; + info.partial = partial; + + if (local) { + if (info.param_type == key_session) + for (i = 0; i < session_key_last; i++) + info.session_param[i] = param[i].local_val; + else + for (i = 0; i < target_key_last; i++) + info.target_param[i] = param[i].local_val; + } else { + if (info.param_type == key_session) + for (i = 0; i < session_key_last; i++) + info.session_param[i] = param[i].exec_val; + else + for (i = 0; i < target_key_last; i++) + info.target_param[i] = param[i].exec_val; + } + + if ((err = ioctl(ctrl_fd, ISCSI_PARAM_SET, &info)) < 0) + fprintf(stderr, "%d %d %u " "%" PRIu64 " %d %u\n", + err, errno, tid, sid, type, partial); + + return err; +} + +static int iscsi_session_create(u32 tid, u64 sid, u32 exp_cmd_sn, + u32 max_cmd_sn, char *name, char *user) +{ + struct session_info info; + + memset(&info, 0, sizeof(info)); + + info.tid = tid; + info.sid = sid; + info.exp_cmd_sn = exp_cmd_sn; + info.max_cmd_sn = max_cmd_sn; + strncpy(info.initiator_name, name, sizeof(info.initiator_name) - 1); + strncpy(info.user_name, user, sizeof(info.user_name) - 1); + + return ioctl(ctrl_fd, ADD_SESSION, &info); +} + +static int iscsi_session_destroy(u32 tid, u64 sid) +{ + struct session_info info; + int res; + + memset(&info, 0, sizeof(info)); + + info.tid = tid; + info.sid = sid; + + do { + res = ioctl(ctrl_fd, DEL_SESSION, &info); + } while (res < 0 && errno == EINTR); + + return res; +} + +static int iscsi_conn_create(u32 tid, u64 sid, u32 cid, u32 stat_sn, u32 exp_stat_sn, + int fd, u32 hdigest, u32 ddigest) +{ + struct conn_info info; + + memset(&info, 0, sizeof(info)); + + info.tid = tid; + info.sid = sid; + info.cid = cid; + info.stat_sn = stat_sn; + info.exp_stat_sn = exp_stat_sn; + info.fd = fd; + info.header_digest = hdigest; + info.data_digest = ddigest; + + return ioctl(ctrl_fd, ADD_CONN, &info); +} + +struct iscsi_kernel_interface ioctl_ki = { + .ctldev_open = ctrdev_open, + .param_get = iscsi_param_get, + .param_set = iscsi_param_set, + .target_create = iscsi_target_create, + .target_destroy = iscsi_target_destroy, + .session_create = iscsi_session_create, + .session_destroy = iscsi_session_destroy, + .conn_create = iscsi_conn_create, + .conn_destroy = iscsi_conn_destroy, +}; + +struct iscsi_kernel_interface *ki = &ioctl_ki; diff --git a/iscsi-scst/usr/event.c b/iscsi-scst/usr/event.c new file mode 100644 index 000000000..9d7a4d146 --- /dev/null +++ b/iscsi-scst/usr/event.c @@ -0,0 +1,132 @@ +/* + * Event notification code. + * (C) 2005 FUJITA Tomonori + * This code is licenced under the GPL. + * + * Some functions are based on open-iscsi code + * written by Dmitry Yusupov, Alex Aizman. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "iscsid.h" + +static struct sockaddr_nl src_addr, dest_addr; + +static int nl_write(int fd, void *data, int len) +{ + struct iovec iov[2]; + struct msghdr msg; + struct nlmsghdr nlh; + + iov[0].iov_base = &nlh; + iov[0].iov_len = sizeof(nlh); + iov[1].iov_base = data; + iov[1].iov_len = NLMSG_SPACE(len) - sizeof(nlh); + + nlh.nlmsg_len = NLMSG_SPACE(len); + nlh.nlmsg_pid = getpid(); + nlh.nlmsg_flags = 0; + nlh.nlmsg_type = 0; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name= (void*)&dest_addr; + msg.msg_namelen = sizeof(dest_addr); + msg.msg_iov = iov; + msg.msg_iovlen = 2; + + return sendmsg(fd, &msg, 0); +} + +static int nl_read(int fd, void *data, int len) +{ + struct iovec iov[2]; + struct msghdr msg; + struct nlmsghdr nlh; + + iov[0].iov_base = &nlh; + iov[0].iov_len = sizeof(nlh); + iov[1].iov_base = data; + iov[1].iov_len = len; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name= (void*)&src_addr; + msg.msg_namelen = sizeof(src_addr); + msg.msg_iov = iov; + msg.msg_iovlen = 2; + + return recvmsg(fd, &msg, MSG_DONTWAIT); +} + +void handle_iscsi_events(int fd) +{ + struct session *session; + struct iscsi_event event; + int res; + +retry: + if ((res = nl_read(fd, &event, sizeof(event))) < 0) { + if (errno == EAGAIN) + return; + if (errno == EINTR) + goto retry; + log_error("read netlink fd (%d)", errno); + exit(1); + } + + log_debug(1, "conn %u session %#" PRIx64 " target %u, state %u", + event.cid, event.sid, event.tid, event.state); + + switch (event.state) { + case E_CONN_CLOSE: + if (!(session = session_find_id(event.tid, event.sid))) { + log_warning("session %#" PRIx64 " not found?", event.sid); + goto retry; + } + + if (!--session->conn_cnt) + session_remove(session); + break; + default: + log_warning("%s(%d) %u\n", __FUNCTION__, __LINE__, event.state); + exit(-1); + break; + } +} + +int nl_open(void) +{ + int nl_fd, res; + + nl_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ISCSI_SCST); + if (nl_fd == -1) { + log_error("%s %d\n", __FUNCTION__, errno); + return -1; + } + + memset(&src_addr, 0, sizeof(src_addr)); + src_addr.nl_family = AF_NETLINK; + src_addr.nl_pid = getpid(); + src_addr.nl_groups = 0; /* not in mcast groups */ + + memset(&dest_addr, 0, sizeof(dest_addr)); + dest_addr.nl_family = AF_NETLINK; + dest_addr.nl_pid = 0; /* kernel */ + dest_addr.nl_groups = 0; /* unicast */ + + res = nl_write(nl_fd, NULL, 0); + if (res < 0) { + log_error("%s %d\n", __FUNCTION__, res); + return res; + } + + return nl_fd; +} diff --git a/iscsi-scst/usr/iscsi_adm.c b/iscsi-scst/usr/iscsi_adm.c new file mode 100644 index 000000000..97776f2cc --- /dev/null +++ b/iscsi-scst/usr/iscsi_adm.c @@ -0,0 +1,559 @@ +/* + * iscsi_adm - manage iSCSI Enterprise Target software. + * + * (C) 2004 - 2005 FUJITA Tomonori + * + * This code is licenced under the GPL. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "iscsid.h" +#include "iscsi_adm.h" + +#define SET_TARGET (1 << 0) +#define SET_SESSION (1 << 1) +#define SET_CONNECTION (1 << 2) +#define SET_USER (1 << 4) + +enum iscsi_adm_op { + OP_NEW, + OP_DELETE, + OP_UPDATE, + OP_SHOW, +}; + +static char program_name[] = "iscsi-scst-adm"; + +static struct option const long_options[] = +{ + {"op", required_argument, NULL, 'o'}, + {"tid", required_argument, NULL, 't'}, + {"sid", required_argument, NULL, 's'}, + {"cid", required_argument, NULL, 'c'}, + {"params", required_argument, NULL, 'p'}, + {"user", no_argument, NULL, 'u'}, + {"version", no_argument, NULL, 'v'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0}, +}; + +static void usage(int status) +{ + if (status != 0) + fprintf(stderr, "Try `%s --help' for more information.\n", program_name); + else { + printf("Usage: %s [OPTION]\n", program_name); + printf("\ +iSCSI Enterprise Target Administration Utility.\n\ +\n\ + --op new --tid=[id] --params Name=[name]\n\ + add a new target with [id]. [id] must not be zero.\n\ + --op delete --tid=[id]\n\ + delete specific target with [id]. The target must\n\ + have no active sessions.\n\ + --op show --tid=[id]\n\ + show target parameters of target with [id].\n\ + --op show --tid=[id] --sid=[sid]\n\ + show iSCSI parameters in effect for session [sid]. If\n\ + [sid] is \"0\" (zero), the configured parameters\n\ + will be displayed.\n\ + --op delete --tid=[id] --sid=[sid] --cid=[cid]\n\ + delete specific connection with [cid] in a session\n\ + with [sid] that the target with [id] has.\n\ + If the session has no connections after\n\ + the operation, the session will be deleted\n\ + automatically.\n\ + --op delete stop all activity.\n\ + --op update --tid=[id] --params=key1=value1,key2=value2,...\n\ + change SCST iSCSI target parameters of specific\n\ + target with [id]. You can use parameters in iscsi-scstd.conf\n\ + as a key.\n\ + --op new --tid=[id] --user --params=[user]=[name],Password=[pass]\n\ + add a new account with [pass] for specific target.\n\ + [user] could be [IncomingUser] or [OutgoingUser].\n\ + If you don't specify a target (omit --tid option),\n\ + you add a new account for discovery sessions.\n\ + --op delete --tid=[id] --user --params=[user]=[name]\n\ + delete specific account having [name] of specific\n\ + target. [user] could be [IncomingUser] or\n\ + [OutgoingUser].\n\ + If you don't specify a target (omit --tid option),\n\ + you delete the account for discovery sessions.\n\ + --version display version and exit\n\ + --help display this help and exit\n\ +\n\ +Report bugs to .\n"); + } + exit(status == 0 ? 0 : -1); +} + +static int str_to_op(char *str) +{ + int op; + + if (!strcmp("new", str)) + op = OP_NEW; + else if (!strcmp("delete", str)) + op = OP_DELETE; + else if (!strcmp("update", str)) + op = OP_UPDATE; + else if (!strcmp("show", str)) + op = OP_SHOW; + else + op = -1; + + return op; +} + +static int iscsid_request_send(int fd, struct iscsi_adm_req *req) +{ + int err, ret; + + do { + ret = write(fd, req, sizeof(*req)); + } while (ret < 0 && errno == EINTR); + + if (ret != sizeof(*req)) { + err = (ret < 0) ? -errno : -EIO; + fprintf(stderr, "%s %d %d %d\n", __FUNCTION__, __LINE__, ret, + err); + } else + err = 0; + + return err; +} + +static int iscsid_response_recv(int fd, struct iscsi_adm_req *req) +{ + int err, ret; + struct iovec iov[2]; + struct iscsi_adm_rsp rsp; + + iov[0].iov_base = req; + iov[0].iov_len = sizeof(*req); + iov[1].iov_base = &rsp; + iov[1].iov_len = sizeof(rsp); + + do { + ret = readv(fd, iov, 2); + } while (ret < 0 && errno == EINTR); + + if (ret != sizeof(rsp) + sizeof(*req)) { + err = (ret < 0) ? -errno : -EIO; + fprintf(stderr, "%s %d %d %d\n", __FUNCTION__, __LINE__, ret, + err); + } else + err = rsp.err; + + return err; +} + +static int iscsid_connect(void) +{ + int fd; + struct sockaddr_un addr; + + fd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (fd < 0) + return -errno; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_LOCAL; + memcpy((char *) &addr.sun_path + 1, ISCSI_ADM_NAMESPACE, strlen(ISCSI_ADM_NAMESPACE)); + + if (connect(fd, (struct sockaddr *) &addr, sizeof(addr))) + fd = -errno; + + return fd; +} + +static int iscsid_request(struct iscsi_adm_req *req) +{ + int fd = -1, err = -EIO; + + if ((fd = iscsid_connect()) < 0) { + err = fd; + goto out; + } + + if ((err = iscsid_request_send(fd, req)) < 0) + goto out; + + err = iscsid_response_recv(fd, req); + +out: + if (fd > 0) + close(fd); + + if (err < 0) + fprintf(stderr, "%s.\n", strerror(-err)); + + return err; +} + +static void show_iscsi_param(int type, struct iscsi_param *param) +{ + int i, nr; + char buf[1024], *p; + struct iscsi_key *keys; + + if (type == key_session) { + nr = session_key_last; + keys = session_keys; + } else { + nr = target_key_last; + keys = target_keys; + } + + for (i = 0; i < nr; i++) { + memset(buf, 0, sizeof(buf)); + strcpy(buf, keys[i].name); + p = buf + strlen(buf); + *p++ = '='; + param_val_to_str(keys, i, param[i].local_val, p); + printf("%s\n", buf); + } +} + +static int parse_trgt_params(struct msg_trgt *msg, char *params) +{ + char *p, *q; + + while ((p = strsep(¶ms, ",")) != NULL) { + int idx; + u32 val; + if (!*p) + continue; + if (!(q = strchr(p, '='))) + continue; + *q++ = '\0'; + + if (!((idx = param_index_by_name(p, target_keys)) < 0)) { + if (param_str_to_val(target_keys, idx, q, &val)) { + fprintf(stderr, + "Invalid %s value \"%s\".\n", + target_keys[idx].name, q); + return -EINVAL; + } + if (!param_check_val(target_keys, idx, &val)) + msg->target_partial |= (1 << idx); + msg->target_param[idx].local_val = val; + msg->type |= 1 << key_target; + + continue; + } + + if (!((idx = param_index_by_name(p, session_keys)) < 0)) { + if (param_str_to_val(session_keys, idx, q, &val)) { + fprintf(stderr, + "Invalid %s value \"%s\".\n", + session_keys[idx].name, q); + return -EINVAL; + } + if (!param_check_val(session_keys, idx, &val)) + msg->session_partial |= (1 << idx); + msg->session_param[idx].local_val = val; + msg->type |= 1 << key_session; + + continue; + } + fprintf(stderr, "Unknown parameter \"%s\".\n", p); + return -EINVAL; + } + + return 0; +} + +static int trgt_handle(int op, u32 set, u32 tid, char *params) +{ + int err = -EINVAL; + struct iscsi_adm_req req; + + if (!(set & SET_TARGET)) + goto out; + + memset(&req, 0, sizeof(req)); + req.tid = tid; + + switch (op) { + case OP_NEW: + { + char *p = params; + + if (!params || !(p = strchr(params, '='))) + goto out; + *p++ = '\0'; + if (strcmp(params, "Name")) + goto out; + req.rcmnd = C_TRGT_NEW; + strncpy(req.u.trgt.name, p, sizeof(req.u.trgt.name) - 1); + break; + } + case OP_DELETE: + req.rcmnd = C_TRGT_DEL; + break; + case OP_UPDATE: + req.rcmnd = C_TRGT_UPDATE; + if ((err = parse_trgt_params(&req.u.trgt, params)) < 0) + goto out; + break; + case OP_SHOW: + req.rcmnd = C_TRGT_SHOW; + break; + } + + err = iscsid_request(&req); + if (!err && req.rcmnd == C_TRGT_SHOW) + show_iscsi_param(key_target, req.u.trgt.target_param); + +out: + return err; +} + +static int sess_handle(int op, u32 set, u32 tid, u64 sid, char *params) +{ + int err = -EINVAL; + struct iscsi_adm_req req; + + if (op == OP_NEW || op == OP_UPDATE) { + fprintf(stderr, "Unsupported.\n"); + goto out; + } + + if (!((set & SET_TARGET) && (set & SET_SESSION))) + goto out; + + req.tid = tid; + req.sid = sid; + req.u.trgt.type = key_session; + + switch (op) { + case OP_DELETE: + /* close all connections */ + break; + case OP_SHOW: + req.rcmnd = C_SESS_SHOW; + err = iscsid_request(&req); + if (!err) + show_iscsi_param(key_session, req.u.trgt.session_param); + break; + } + +out: + return err; +} + +static int user_handle(int op, u32 set, u32 tid, char *params) +{ + int err = -EINVAL; + char *p, *q, *user = NULL, *pass = NULL; + struct iscsi_adm_req req; + + if (set & ~(SET_TARGET | SET_USER)) + goto out; + + memset(&req, 0, sizeof(req)); + req.tid = tid; + + switch (op) { + case OP_NEW: + req.rcmnd = C_ACCT_NEW; + break; + case OP_DELETE: + req.rcmnd = C_ACCT_DEL; + break; + case OP_UPDATE: + case OP_SHOW: + fprintf(stderr, "Unsupported.\n"); + goto out; + } + + while ((p = strsep(¶ms, ",")) != NULL) { + if (!*p) + continue; + + if (!(q = strchr(p, '='))) + continue; + *q++ = '\0'; + if (isspace(*q)) + q++; + + if (!strcasecmp(p, "IncomingUser")) { + if (user) + fprintf(stderr, "Already specified user %s\n", q); + user = q; + req.u.acnt.auth_dir = AUTH_DIR_INCOMING; + } else if (!strcasecmp(p, "OutgoingUser")) { + if (user) + fprintf(stderr, "Already specified user %s\n", q); + user = q; + req.u.acnt.auth_dir = AUTH_DIR_OUTGOING; + } else if (!strcasecmp(p, "Password")) { + if (pass) + fprintf(stderr, "Already specified pass %s\n", q); + pass = q; + } else { + fprintf(stderr, "Unknown parameter %p\n", q); + goto out; + } + } + + if ((op == OP_NEW && ((user && !pass) || (!user && pass) || (!user && !pass))) || + (op == OP_DELETE && ((!user && pass) || (!user && !pass)))) { + fprintf(stderr, + "You need to specify a user and its password %s %s\n", pass, user); + goto out; + } + + strncpy(req.u.acnt.user, user, sizeof(req.u.acnt.user) - 1); + if (pass) + strncpy(req.u.acnt.pass, pass, sizeof(req.u.acnt.pass) - 1); + + err = iscsid_request(&req); +out: + return err; +} + +static int conn_handle(int op, u32 set, u32 tid, u64 sid, u32 cid, char *params) +{ + int err = -EINVAL; + struct iscsi_adm_req req; + + if (op == OP_NEW || op == OP_UPDATE) { + fprintf(stderr, "Unsupported.\n"); + goto out; + } + + if (!((set & SET_TARGET) && (set & SET_SESSION) && (set & SET_CONNECTION))) + goto out; + + memset(&req, 0, sizeof(req)); + req.tid = tid; + req.sid = sid; + req.cid = cid; + + switch (op) { + case OP_DELETE: + req.rcmnd = C_CONN_DEL; + break; + case OP_SHOW: + req.rcmnd = C_CONN_SHOW; + /* TODO */ + break; + } + + err = iscsid_request(&req); +out: + return err; +} + +static int sys_handle(int op, u32 set, char *params) +{ + int err = -EINVAL; + struct iscsi_adm_req req; + + memset(&req, 0, sizeof(req)); + + switch (op) { + case OP_NEW: + break; + case OP_DELETE: + req.rcmnd = C_SYS_DEL; + break; + case OP_UPDATE: + break; + case OP_SHOW: + break; + } + + err = iscsid_request(&req); + + return err; +} + +int main(int argc, char **argv) +{ + int ch, longindex; + int err = -EINVAL, op = -1; + u32 tid = 0, cid = 0, set = 0; + u64 sid = 0; + char *params = NULL; + + while ((ch = getopt_long(argc, argv, "o:t:s:c:l:p:uvh", + long_options, &longindex)) >= 0) { + switch (ch) { + case 'o': + op = str_to_op(optarg); + break; + case 't': + tid = strtoul(optarg, NULL, 10); + set |= SET_TARGET; + break; + case 's': + sid = strtoull(optarg, NULL, 10); + set |= SET_SESSION; + break; + case 'c': + cid = strtoul(optarg, NULL, 10); + set |= SET_CONNECTION; + break; + case 'p': + params = optarg; + break; + case 'u': + set |= SET_USER; + break; + case 'v': + printf("%s version %s\n", program_name, ISCSI_VERSION_STRING); + exit(0); + break; + case 'h': + usage(0); + break; + default: + usage(-1); + } + } + + if (op < 0) { + fprintf(stderr, "You must specify the operation type\n"); + goto out; + } + + if (optind < argc) { + fprintf(stderr, "unrecognized: "); + while (optind < argc) + fprintf(stderr, "%s", argv[optind++]); + fprintf(stderr, "\n"); + usage(-1); + } + + if (set & SET_USER) + err = user_handle(op, set, tid, params); + else if (set & SET_CONNECTION) + err = conn_handle(op, set, tid, sid, cid, params); + else if (set & SET_SESSION) + err = sess_handle(op, set, tid, sid, params); + else if (set & SET_TARGET) + err = trgt_handle(op, set, tid, params); + else if (!set) + err = sys_handle(op, set, params); + else + usage(-1); + +out: + return err; +} diff --git a/iscsi-scst/usr/iscsi_adm.h b/iscsi-scst/usr/iscsi_adm.h new file mode 100644 index 000000000..698a0ab3e --- /dev/null +++ b/iscsi-scst/usr/iscsi_adm.h @@ -0,0 +1,68 @@ +#ifndef _ISCSI_ADM_H +#define _ISCSI_ADM_H + +#define ISCSI_ADM_NAMESPACE "ISCSI_SCST_ADM" + +struct msg_trgt { + char name[ISCSI_NAME_LEN]; + char alias[ISCSI_NAME_LEN]; + + u32 type; + u32 session_partial; + u32 target_partial; + struct iscsi_param session_param[session_key_last]; + struct iscsi_param target_param[target_key_last]; +}; + +struct msg_acnt { + u32 auth_dir; + char user[ISCSI_NAME_LEN]; + char pass[ISCSI_NAME_LEN]; +}; + +enum iscsi_adm_cmnd { + C_TRGT_NEW, + C_TRGT_DEL, + C_TRGT_UPDATE, + C_TRGT_SHOW, + + C_SESS_NEW, + C_SESS_DEL, + C_SESS_UPDATE, + C_SESS_SHOW, + + C_CONN_NEW, + C_CONN_DEL, + C_CONN_UPDATE, + C_CONN_SHOW, + + C_ACCT_NEW, + C_ACCT_DEL, + C_ACCT_UPDATE, + C_ACCT_SHOW, + + C_SYS_NEW, + C_SYS_DEL, + C_SYS_UPDATE, + C_SYS_SHOW, +}; + +struct iscsi_adm_req { + enum iscsi_adm_cmnd rcmnd; + + u32 tid; + u64 sid; + u32 cid; + u32 lun; + + union { + struct msg_trgt trgt; + struct msg_acnt acnt; + } u; +}; + +struct iscsi_adm_rsp { + int err; +}; + +#endif diff --git a/iscsi-scst/usr/iscsi_hdr.h b/iscsi-scst/usr/iscsi_hdr.h new file mode 100644 index 000000000..7e2e3e16f --- /dev/null +++ b/iscsi-scst/usr/iscsi_hdr.h @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2002-2003 Ardis Technolgies + * + * Released under the terms of the GNU GPL v2.0. + */ + +#ifndef ISCSI_HDR_H +#define ISCSI_HDR_H + +#define ISCSI_VERSION 0 + +#define __packed __attribute__ ((packed)) + +struct iscsi_hdr { + u8 opcode; /* 0 */ + u8 flags; + u8 spec1[2]; + u8 ahslength; /* 4 */ + u8 datalength[3]; + u16 lun[4]; /* 8 */ + u32 itt; /* 16 */ + u32 ttt; /* 20 */ + u32 sn; /* 24 */ + u32 exp_sn; /* 28 */ + u32 max_sn; /* 32 */ + u32 spec3[3]; /* 36 */ +} __packed; /* 48 */ + +/* Opcode encoding bits */ +#define ISCSI_OP_RETRY 0x80 +#define ISCSI_OP_IMMEDIATE 0x40 +#define ISCSI_OPCODE_MASK 0x3F + +/* Client to Server Message Opcode values */ +#define ISCSI_OP_NOOP_OUT 0x00 +#define ISCSI_OP_SCSI_CMD 0x01 +#define ISCSI_OP_SCSI_TASK_MGT_MSG 0x02 +#define ISCSI_OP_LOGIN_CMD 0x03 +#define ISCSI_OP_TEXT_CMD 0x04 +#define ISCSI_OP_SCSI_DATA 0x05 +#define ISCSI_OP_LOGOUT_CMD 0x06 +#define ISCSI_OP_SNACK_CMD 0x10 + +/* Server to Client Message Opcode values */ +#define ISCSI_OP_NOOP_IN 0x20 +#define ISCSI_OP_SCSI_RSP 0x21 +#define ISCSI_OP_SCSI_TASK_MGT_RSP 0x22 +#define ISCSI_OP_LOGIN_RSP 0x23 +#define ISCSI_OP_TEXT_RSP 0x24 +#define ISCSI_OP_SCSI_DATA_RSP 0x25 +#define ISCSI_OP_LOGOUT_RSP 0x26 +#define ISCSI_OP_R2T_RSP 0x31 +#define ISCSI_OP_ASYNC_EVENT 0x32 +#define ISCSI_OP_REJECT_MSG 0x3f + +struct iscsi_ahs_hdr { + u16 ahslength; + u8 ahstype; +} __packed; + +#define ISCSI_AHSTYPE_CDB 1 +#define ISCSI_AHSTYPE_RLENGTH 2 + +union iscsi_sid { + struct { + u8 isid[6]; /* Initiator Session ID */ + u16 tsih; /* Target Session ID */ + } id; + u64 id64; +} __packed; + +struct iscsi_text_req_hdr { + u8 opcode; + u8 flags; + u16 rsvd1; + u8 ahslength; + u8 datalength[3]; + u32 rsvd2[2]; + u32 itt; + u32 ttt; + u32 cmd_sn; + u32 exp_stat_sn; + u32 rsvd3[4]; +} __packed; + +struct iscsi_text_rsp_hdr { + u8 opcode; + u8 flags; + u16 rsvd1; + u8 ahslength; + u8 datalength[3]; + u32 rsvd2[2]; + u32 itt; + u32 ttt; + u32 stat_sn; + u32 exp_cmd_sn; + u32 max_cmd_sn; + u32 rsvd3[3]; +} __packed; + +struct iscsi_login_req_hdr { + u8 opcode; + u8 flags; + u8 max_version; /* Max. version supported */ + u8 min_version; /* Min. version supported */ + u8 ahslength; + u8 datalength[3]; + union iscsi_sid sid; + u32 itt; /* Initiator Task Tag */ + u16 cid; /* Connection ID */ + u16 rsvd1; + u32 cmd_sn; + u32 exp_stat_sn; + u32 rsvd2[4]; +} __packed; + +struct iscsi_login_rsp_hdr { + u8 opcode; + u8 flags; + u8 max_version; /* Max. version supported */ + u8 active_version; /* Active version */ + u8 ahslength; + u8 datalength[3]; + union iscsi_sid sid; + u32 itt; /* Initiator Task Tag */ + u32 rsvd1; + u32 stat_sn; + u32 exp_cmd_sn; + u32 max_cmd_sn; + u8 status_class; /* see Login RSP ststus classes below */ + u8 status_detail; /* see Login RSP Status details below */ + u8 rsvd2[10]; +} __packed; + +#define ISCSI_FLG_FINAL 0x80 +#define ISCSI_FLG_TRANSIT 0x80 +#define ISCSI_FLG_CSG_SECURITY 0x00 +#define ISCSI_FLG_CSG_LOGIN 0x04 +#define ISCSI_FLG_CSG_FULL_FEATURE 0x0c +#define ISCSI_FLG_CSG_MASK 0x0c +#define ISCSI_FLG_NSG_SECURITY 0x00 +#define ISCSI_FLG_NSG_LOGIN 0x01 +#define ISCSI_FLG_NSG_FULL_FEATURE 0x03 +#define ISCSI_FLG_NSG_MASK 0x03 + +/* Login Status response classes */ +#define ISCSI_STATUS_SUCCESS 0x00 +#define ISCSI_STATUS_REDIRECT 0x01 +#define ISCSI_STATUS_INITIATOR_ERR 0x02 +#define ISCSI_STATUS_TARGET_ERR 0x03 + +/* Login Status response detail codes */ +/* Class-0 (Success) */ +#define ISCSI_STATUS_ACCEPT 0x00 + +/* Class-1 (Redirection) */ +#define ISCSI_STATUS_TGT_MOVED_TEMP 0x01 +#define ISCSI_STATUS_TGT_MOVED_PERM 0x02 + +/* Class-2 (Initiator Error) */ +#define ISCSI_STATUS_INIT_ERR 0x00 +#define ISCSI_STATUS_AUTH_FAILED 0x01 +#define ISCSI_STATUS_TGT_FORBIDDEN 0x02 +#define ISCSI_STATUS_TGT_NOT_FOUND 0x03 +#define ISCSI_STATUS_TGT_REMOVED 0x04 +#define ISCSI_STATUS_NO_VERSION 0x05 +#define ISCSI_STATUS_TOO_MANY_CONN 0x06 +#define ISCSI_STATUS_MISSING_FIELDS 0x07 +#define ISCSI_STATUS_CONN_ADD_FAILED 0x08 +#define ISCSI_STATUS_INV_SESSION_TYPE 0x09 +#define ISCSI_STATUS_SESSION_NOT_FOUND 0x0a +#define ISCSI_STATUS_INV_REQ_TYPE 0x0b + +/* Class-3 (Target Error) */ +#define ISCSI_STATUS_TARGET_ERROR 0x00 +#define ISCSI_STATUS_SVC_UNAVAILABLE 0x01 +#define ISCSI_STATUS_NO_RESOURCES 0x02 + +struct iscsi_logout_req_hdr { + u8 opcode; + u8 flags; + u16 rsvd1; + u8 ahslength; + u8 datalength[3]; + u32 rsvd2[2]; + u32 itt; + u16 cid; + u16 rsvd3; + u32 cmd_sn; + u32 exp_stat_sn; + u32 rsvd4[4]; +} __packed; + +struct iscsi_logout_rsp_hdr { + u8 opcode; + u8 flags; + u8 response; + u8 rsvd1; + u8 ahslength; + u8 datalength[3]; + u32 rsvd2[2]; + u32 itt; + u32 rsvd3; + u32 stat_sn; + u32 exp_cmd_sn; + u32 max_cmd_sn; + u32 rsvd4; + u16 time2wait; + u16 time2retain; + u32 rsvd5; +} __packed; + +#endif /* ISCSI_HDR_H */ diff --git a/iscsi-scst/usr/iscsi_scstd.c b/iscsi-scst/usr/iscsi_scstd.c new file mode 100644 index 000000000..d843aa053 --- /dev/null +++ b/iscsi-scst/usr/iscsi_scstd.c @@ -0,0 +1,585 @@ +/* + * Copyright (C) 2002-2003 Ardis Technolgies + * + * Released under the terms of the GNU GPL v2.0. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "iscsid.h" +#include "iscsi_adm.h" + +#define LISTEN_MAX 8 +#define INCOMING_MAX 32 + +enum { + POLL_LISTEN, + POLL_IPC = POLL_LISTEN + LISTEN_MAX, + POLL_NL, + POLL_ISNS, + POLL_SCN_LISTEN, + POLL_SCN, + POLL_INCOMING, + POLL_MAX = POLL_INCOMING + INCOMING_MAX, +}; + +static char* server_address; +uint16_t server_port = ISCSI_LISTEN_PORT; + +static struct pollfd poll_array[POLL_MAX]; +static struct connection *incoming[INCOMING_MAX]; +static int incoming_cnt; +int ctrl_fd, ipc_fd, nl_fd; +int conn_blocked; + +static char program_name[] = "iscsi-scstd"; + +static struct option const long_options[] = +{ + {"config", required_argument, 0, 'c'}, + {"foreground", no_argument, 0, 'f'}, + {"debug", required_argument, 0, 'd'}, + {"uid", required_argument, 0, 'u'}, + {"gid", required_argument, 0, 'g'}, + {"address", required_argument, 0, 'a'}, + {"port", required_argument, 0, 'p'}, + {"version", no_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0}, +}; + +/* This will be comfigurable by command line options */ +struct config_operations *cops = &plain_ops; + +static void usage(int status) +{ + if (status != 0) + fprintf(stderr, "Try `%s --help' for more information.\n", program_name); + else { + printf("Usage: %s [OPTION]\n", program_name); + printf("\ +iSCSI target daemon.\n\ + -c, --config=[path] Execute in the config file.\n"); + printf("\ + -f, --foreground make the program run in the foreground\n\ + -d, --debug debuglevel print debugging information\n\ + -u, --uid=uid run as uid, default is current user\n\ + -g, --gid=gid run as gid, default is current user group\n\ + -a, --address=address listen on specified local address instead of all\n\ + -p, --port=port listen on specified port instead of 3260\n\ + -h, --help display this help and exit\n\ +"); + } + exit(1); +} + +static void set_non_blocking(int fd) +{ + int res = fcntl(fd, F_GETFL); + + if (res != -1) { + res = fcntl(fd, F_SETFL, res | O_NONBLOCK); + if (res) + log_warning("unable to set fd flags (%s)!", strerror(errno)); + } else + log_warning("unable to get fd flags (%s)!", strerror(errno)); +} + +static void sock_set_keepalive(int sock, int timeout) +{ + if (timeout) { /* timeout [s] */ + int opt = 2; + + if (setsockopt(sock, SOL_TCP, TCP_KEEPCNT, &opt, sizeof(opt))) + log_warning("unable to set TCP_KEEPCNT on server socket (%s)!", strerror(errno)); + + if (setsockopt(sock, SOL_TCP, TCP_KEEPIDLE, &timeout, sizeof(timeout))) + log_warning("unable to set TCP_KEEPIDLE on server socket (%s)!", strerror(errno)); + + opt = 3; + if (setsockopt(sock, SOL_TCP, TCP_KEEPINTVL, &opt, sizeof(opt))) + log_warning("unable to set KEEPINTVL on server socket (%s)!", strerror(errno)); + + opt = 1; + if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt))) + log_warning("unable to set SO_KEEPALIVE on server socket (%s)!", strerror(errno)); + } +} + +static void create_listen_socket(struct pollfd *array) +{ + struct addrinfo hints, *res, *res0; + char servname[64]; + int i, sock, opt; + + memset(servname, 0, sizeof(servname)); + snprintf(servname, sizeof(servname), "%d", server_port); + + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + + if (getaddrinfo(server_address, servname, &hints, &res0)) { + log_error("unable to get address info (%s)!", strerror(errno)); + exit(1); + } + + for (i = 0, res = res0; res && i < LISTEN_MAX; i++, res = res->ai_next) { + sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (sock < 0) { + log_error("unable to create server socket (%s) %d %d %d!", + strerror(errno), res->ai_family, + res->ai_socktype, res->ai_protocol); + continue; + } + + sock_set_keepalive(sock, 50); + + opt = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) + log_warning("unable to set SO_REUSEADDR on server socket (%s)!", + strerror(errno)); + opt = 1; + if (res->ai_family == AF_INET6 && + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt))) + continue; + + if (bind(sock, res->ai_addr, res->ai_addrlen)) { + log_error("unable to bind server socket (%s)!", strerror(errno)); + continue; + } + + if (listen(sock, INCOMING_MAX)) { + log_error("unable to listen to server socket (%s)!", strerror(errno)); + continue; + } + + set_non_blocking(sock); + + array[i].fd = sock; + array[i].events = POLLIN; + } + + freeaddrinfo(res0); +} + +static void accept_connection(int listen) +{ + struct sockaddr_storage from; + socklen_t namesize; + struct pollfd *pollfd; + struct connection *conn; + int fd, i; + + namesize = sizeof(from); + if ((fd = accept(listen, (struct sockaddr *) &from, &namesize)) < 0) { + if (errno != EINTR && errno != EAGAIN) { + perror("accept(incoming_socket)\n"); + exit(1); + } + return; + } + + if (conn_blocked) { + log_warning("A connection refused\n"); + close(fd); + return; + } + + for (i = 0; i < INCOMING_MAX; i++) { + if (!incoming[i]) + break; + } + if (i >= INCOMING_MAX) { + log_error("unable to find incoming slot? %d\n", i); + exit(1); + } + + if (!(conn = conn_alloc())) { + log_error("fail to allocate %s", "conn\n"); + exit(1); + } + conn->fd = fd; + incoming[i] = conn; + conn_read_pdu(conn); + + set_non_blocking(fd); + pollfd = &poll_array[POLL_INCOMING + i]; + pollfd->fd = fd; + pollfd->events = POLLIN; + pollfd->revents = 0; + + incoming_cnt++; + if (incoming_cnt >= INCOMING_MAX) + poll_array[POLL_LISTEN].events = 0; +} + +static void __set_fd(int idx, int fd) +{ + poll_array[idx].fd = fd; + poll_array[idx].events = fd ? POLLIN : 0; +} + +void isns_set_fd(int isns, int scn_listen, int scn) +{ + __set_fd(POLL_ISNS, isns); + __set_fd(POLL_SCN_LISTEN, scn_listen); + __set_fd(POLL_SCN, scn); +} + +static void event_conn(struct connection *conn, struct pollfd *pollfd) +{ + int res, opt; + + switch (conn->iostate) { + case IOSTATE_READ_BHS: + case IOSTATE_READ_AHS_DATA: + read_again: + res = read(pollfd->fd, conn->buffer, conn->rwsize); + if (res <= 0) { + if (res == 0 || (errno != EINTR && errno != EAGAIN)) + conn->state = STATE_CLOSE; + else if (errno == EINTR) + goto read_again; + break; + } + conn->rwsize -= res; + conn->buffer += res; + if (conn->rwsize) + break; + + switch (conn->iostate) { + case IOSTATE_READ_BHS: + conn->iostate = IOSTATE_READ_AHS_DATA; + conn->req.ahssize = conn->req.bhs.ahslength * 4; + conn->req.datasize = ((conn->req.bhs.datalength[0] << 16) + + (conn->req.bhs.datalength[1] << 8) + + conn->req.bhs.datalength[2]); + conn->rwsize = (conn->req.ahssize + conn->req.datasize + 3) & -4; + if (conn->rwsize > INCOMING_BUFSIZE) { + log_warning("Recv PDU with invalid size %d " + "(max: %d)", conn->rwsize, + INCOMING_BUFSIZE); + conn->state = STATE_CLOSE; + goto out; + } + if (conn->rwsize) { + if (!conn->req_buffer) { + conn->req_buffer = malloc(INCOMING_BUFSIZE); + if (!conn->req_buffer) { + log_error("Failed to alloc recv buffer"); + conn->state = STATE_CLOSE; + goto out; + } + } + conn->buffer = conn->req_buffer; + conn->req.ahs = conn->buffer; + conn->req.data = conn->buffer + conn->req.ahssize; + goto read_again; + } + + case IOSTATE_READ_AHS_DATA: + conn_write_pdu(conn); + pollfd->events = POLLOUT; + + log_pdu(2, &conn->req); + if (!cmnd_execute(conn)) + conn->state = STATE_CLOSE; + break; + } + break; + + case IOSTATE_WRITE_BHS: + case IOSTATE_WRITE_AHS: + case IOSTATE_WRITE_DATA: + write_again: + opt = 1; + setsockopt(pollfd->fd, SOL_TCP, TCP_CORK, &opt, sizeof(opt)); + res = write(pollfd->fd, conn->buffer, conn->rwsize); + if (res < 0) { + if (errno != EINTR && errno != EAGAIN) + conn->state = STATE_CLOSE; + else if (errno == EINTR) + goto write_again; + break; + } + + conn->rwsize -= res; + conn->buffer += res; + if (conn->rwsize) + goto write_again; + + switch (conn->iostate) { + case IOSTATE_WRITE_BHS: + if (conn->rsp.ahssize) { + conn->iostate = IOSTATE_WRITE_AHS; + conn->buffer = conn->rsp.ahs; + conn->rwsize = conn->rsp.ahssize; + goto write_again; + } + case IOSTATE_WRITE_AHS: + if (conn->rsp.datasize) { + int o; + + conn->iostate = IOSTATE_WRITE_DATA; + conn->buffer = conn->rsp.data; + conn->rwsize = conn->rsp.datasize; + o = conn->rwsize & 3; + if (o) { + for (o = 4 - o; o; o--) + *((u8 *) conn->buffer + conn->rwsize++) = + 0; + } + goto write_again; + } + case IOSTATE_WRITE_DATA: + opt = 0; + setsockopt(pollfd->fd, SOL_TCP, TCP_CORK, &opt, sizeof(opt)); + cmnd_finish(conn); + + switch (conn->state) { + case STATE_KERNEL: + conn_take_fd(conn, pollfd->fd); + conn->state = STATE_CLOSE; + break; + case STATE_EXIT: + case STATE_CLOSE: + break; + default: + conn_read_pdu(conn); + pollfd->events = POLLIN; + break; + } + break; + } + + break; + default: + log_error("illegal iostate %d for port %d!\n", conn->iostate, + pollfd->fd); + exit(1); + } +out: + return; +} + +void wait_4_iscsi_event(int timeout) +{ + int res; + + do { + res = poll(&poll_array[POLL_NL], 1, timeout); + } while (res < 0 && errno == EINTR); + + if (poll_array[POLL_NL].revents && res > 0) + handle_iscsi_events(nl_fd); + else { + log_error("%s: unexpected error %d %d\n", __FUNCTION__, res, + errno); + } +} + +static void event_loop(int timeout) +{ + int res, i; + + create_listen_socket(poll_array + POLL_LISTEN); + + poll_array[POLL_IPC].fd = ipc_fd; + poll_array[POLL_IPC].events = POLLIN; + poll_array[POLL_NL].fd = nl_fd; + poll_array[POLL_NL].events = POLLIN; + + for (i = 0; i < INCOMING_MAX; i++) { + poll_array[POLL_INCOMING + i].fd = -1; + poll_array[POLL_INCOMING + i].events = 0; + incoming[i] = NULL; + } + + while (1) { + res = poll(poll_array, POLL_MAX, timeout); + if (res == 0) { + isns_handle(1, &timeout); + continue; + } else if (res < 0) { + if (res < 0 && errno != EINTR) { + log_error("%s:poll() %d", __FUNCTION__, errno); + exit(1); + } + continue; + } + + for (i = 0; i < LISTEN_MAX; i++) { + if (poll_array[POLL_LISTEN + i].revents + && incoming_cnt < INCOMING_MAX) + accept_connection(poll_array[POLL_LISTEN + i].fd); + } + + if (poll_array[POLL_NL].revents) + handle_iscsi_events(nl_fd); + + if (poll_array[POLL_IPC].revents) + iscsi_adm_request_handle(ipc_fd); + + if (poll_array[POLL_ISNS].revents) + isns_handle(0, &timeout); + + if (poll_array[POLL_SCN_LISTEN].revents) + isns_scn_handle(1); + + if (poll_array[POLL_SCN].revents) + isns_scn_handle(0); + + for (i = 0; i < INCOMING_MAX; i++) { + struct connection *conn = incoming[i]; + struct pollfd *pollfd = &poll_array[POLL_INCOMING + i]; + + if (!conn || !pollfd->revents) + continue; + + pollfd->revents = 0; + + event_conn(conn, pollfd); + + if (conn->state == STATE_CLOSE) { + log_debug(0, "connection closed"); + conn_free_pdu(conn); + conn_free(conn); + close(pollfd->fd); + pollfd->fd = -1; + incoming[i] = NULL; + incoming_cnt--; + } + } + } +} + +int main(int argc, char **argv) +{ + int ch, longindex, timeout = -1; + char *config = NULL; + uid_t uid = 0; + gid_t gid = 0; + char *isns = NULL; + int isns_ac = 0; + + while ((ch = getopt_long(argc, argv, "c:fd:s:u:g:a:p:vh", long_options, &longindex)) >= 0) { + switch (ch) { + case 'c': + config = optarg; + break; + case 'f': + log_daemon = 0; + break; + case 'd': + log_level = atoi(optarg); + break; + case 'u': + uid = strtoul(optarg, NULL, 10); + break; + case 'g': + gid = strtoul(optarg, NULL, 10); + break; + case 'a': + server_address = strdup(optarg); + break; + case 'p': + server_port = (uint16_t)strtoul(optarg, NULL, 10); + break; + case 'v': + printf("%s version %s\n", program_name, ISCSI_VERSION_STRING); + exit(0); + break; + case 'h': + usage(0); + break; + default: + usage(1); + break; + } + } + + if ((nl_fd = nl_open()) < 0) { + perror("netlink fd\n"); + exit(-1); + }; + + if ((ctrl_fd = ki->ctldev_open()) < 0) { + perror("ctldev fd\n"); + exit(-1); + } + + if ((ipc_fd = iscsi_adm_request_listen()) < 0) { + perror("ipc fd\n"); + exit(-1); + } + + log_init(); + if (log_daemon) { + char buf[64]; + pid_t pid; + int fd; + + fd = open("/var/run/iscsi-scstd.pid", O_WRONLY|O_CREAT, 0644); + if (fd < 0) { + log_error("unable to create pid file"); + exit(1); + } + pid = fork(); + if (pid < 0) { + log_error("starting daemon failed"); + exit(1); + } else if (pid) + exit(0); + + chdir("/"); + if (lockf(fd, F_TLOCK, 0) < 0) { + log_error("unable to lock pid file"); + exit(1); + } + ftruncate(fd, 0); + sprintf(buf, "%d\n", getpid()); + write(fd, buf, strlen(buf)); + + close(0); + open("/dev/null", O_RDWR); + dup2(0, 1); + dup2(0, 2); + setsid(); + } + + cops->init(config, &isns, &isns_ac); + if (isns) + timeout = isns_init(isns, isns_ac); + + cops->default_load(config); + + if (gid && setgid(gid) < 0) + perror("setgid\n"); + + if (uid && setuid(uid) < 0) + perror("setuid\n"); + + event_loop(timeout); + + return 0; +} diff --git a/iscsi-scst/usr/iscsid.c b/iscsi-scst/usr/iscsid.c new file mode 100644 index 000000000..393a1ae29 --- /dev/null +++ b/iscsi-scst/usr/iscsid.c @@ -0,0 +1,765 @@ +/* + * Copyright (C) 2002-2003 Ardis Technolgies + * + * Released under the terms of the GNU GPL v2.0. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "iscsid.h" + +static struct iscsi_key login_keys[] = { + {"InitiatorName",}, + {"InitiatorAlias",}, + {"SessionType",}, + {"TargetName",}, + {NULL, 0, 0, 0, 0, NULL}, +}; + +char *text_key_find(struct connection *conn, char *searchKey) +{ + char *data, *key, *value; + int keylen, datasize; + + keylen = strlen(searchKey); + data = conn->req.data; + datasize = conn->req.datasize; + + while (1) { + for (key = data; datasize > 0 && *data != '='; data++, datasize--) + ; + if (!datasize) + return NULL; + data++; + datasize--; + + for (value = data; datasize > 0 && *data != 0; data++, datasize--) + ; + if (!datasize) + return NULL; + data++; + datasize--; + + if (keylen == value - key - 1 + && !strncmp(key, searchKey, keylen)) + return value; + } +} + +static char *next_key(char **data, int *datasize, char **value) +{ + char *key, *p, *q; + int size = *datasize; + + key = p = *data; + for (; size > 0 && *p != '='; p++, size--) + ; + if (!size) + return NULL; + *p++ = 0; + size--; + + for (q = p; size > 0 && *p != 0; p++, size--) + ; + if (!size) + return NULL; + p++; + size--; + + *data = p; + *value = q; + *datasize = size; + + return key; +} + +void text_key_add(struct connection *conn, char *key, char *value) +{ + int keylen = strlen(key); + int valuelen = strlen(value); + int len = keylen + valuelen + 2; + char *buffer; + + if (!conn->rsp.datasize) { + if (!conn->rsp_buffer) { + conn->rsp_buffer = malloc(INCOMING_BUFSIZE); + if (!conn->rsp_buffer) { + log_error("Failed to alloc send buffer"); + return; + } + } + conn->rsp.data = conn->rsp_buffer; + } + if (conn->rwsize + len > INCOMING_BUFSIZE) { + log_warning("Dropping key (%s=%s)", key, value); + return; + } + + buffer = conn->rsp_buffer; + buffer += conn->rsp.datasize; + conn->rsp.datasize += len; + + strcpy(buffer, key); + buffer += keylen; + *buffer++ = '='; + strcpy(buffer, value); +} + +static void text_key_add_reject(struct connection *conn, char *key) +{ + text_key_add(conn, key, "Reject"); +} + +static int account_empty(u32 tid, int dir) +{ + char pass[ISCSI_NAME_LEN]; + + memset(pass, 0, sizeof(pass)); + return cops->account_query(tid, dir, pass, pass) < 0 ? 1 : 0; +} + +static void text_scan_security(struct connection *conn) +{ + struct iscsi_login_rsp_hdr *rsp = (struct iscsi_login_rsp_hdr *)&conn->rsp.bhs; + char *key, *value, *data, *nextValue; + int datasize; + + data = conn->req.data; + datasize = conn->req.datasize; + + while ((key = next_key(&data, &datasize, &value))) { + if (!(param_index_by_name(key, login_keys) < 0)) + ; + else if (!strcmp(key, "AuthMethod")) { + do { + nextValue = strchr(value, ','); + if (nextValue) + *nextValue++ = 0; + + if (!strcmp(value, "None")) { + if (!account_empty(conn->tid, AUTH_DIR_INCOMING)) + continue; + conn->auth_method = AUTH_NONE; + text_key_add(conn, key, "None"); + break; + } else if (!strcmp(value, "CHAP")) { + if (account_empty(conn->tid, AUTH_DIR_INCOMING)) + continue; + conn->auth_method = AUTH_CHAP; + text_key_add(conn, key, "CHAP"); + break; + } + } while ((value = nextValue)); + + if (conn->auth_method == AUTH_UNKNOWN) + text_key_add_reject(conn, key); + } else + text_key_add(conn, key, "NotUnderstood"); + } + if (conn->auth_method == AUTH_UNKNOWN) { + rsp->status_class = ISCSI_STATUS_INITIATOR_ERR; + rsp->status_detail = ISCSI_STATUS_AUTH_FAILED; + conn->state = STATE_EXIT; + } +} + +static void login_security_done(struct connection *conn) +{ + int err; + struct iscsi_login_req_hdr *req = (struct iscsi_login_req_hdr *)&conn->req.bhs; + struct iscsi_login_rsp_hdr *rsp = (struct iscsi_login_rsp_hdr *)&conn->rsp.bhs; + struct session *session; + + if (!conn->tid) + return; + + if ((session = session_find_name(conn->tid, conn->initiator, req->sid))) { + if (!req->sid.id.tsih) { + /* do session reinstatement */ + session_conns_close(conn->tid, session->sid.id64); + session = NULL; + } else if (req->sid.id.tsih != session->sid.id.tsih) { + /* fail the login */ + rsp->status_class = ISCSI_STATUS_INITIATOR_ERR; + rsp->status_detail = ISCSI_STATUS_SESSION_NOT_FOUND; + conn->state = STATE_EXIT; + return; + } else if ((err = conn_test(conn)) == -ENOENT) { + /* do connection reinstatement */ + } + /* add a new connection to the session */ + conn->session = session; + } else { + if (req->sid.id.tsih) { + /* fail the login */ + rsp->status_class = ISCSI_STATUS_INITIATOR_ERR; + rsp->status_detail = ISCSI_STATUS_SESSION_NOT_FOUND; + conn->state = STATE_EXIT; + return; + } + /* instantiate a new session */ + } +} + +static void text_scan_login(struct connection *conn) +{ + char *key, *value, *data; + int datasize, idx; + struct iscsi_login_rsp_hdr *rsp = (struct iscsi_login_rsp_hdr *)&conn->rsp.bhs; + + data = conn->req.data; + datasize = conn->req.datasize; + + while ((key = next_key(&data, &datasize, &value))) { + if (!(param_index_by_name(key, login_keys) < 0)) + ; + else if (!strcmp(key, "AuthMethod")) + ; + else if (!((idx = param_index_by_name(key, session_keys)) < 0)) { + unsigned int val; + char buf[32]; + + if (idx == key_max_xmit_data_length) { + text_key_add(conn, key, "NotUnderstood"); + continue; + } + if (idx == key_max_recv_data_length) + idx = key_max_xmit_data_length; + + if (param_str_to_val(session_keys, idx, value, &val) < 0) { + if (conn->session_param[idx].state + == KEY_STATE_START) { + text_key_add_reject(conn, key); + continue; + } else { + rsp->status_class = ISCSI_STATUS_INITIATOR_ERR; + rsp->status_detail = ISCSI_STATUS_INIT_ERR; + conn->state = STATE_EXIT; + goto out; + } + } + + param_check_val(session_keys, idx, &val); + param_set_val(session_keys, conn->session_param, idx, &val); + + switch (conn->session_param[idx].state) { + case KEY_STATE_START: + if (iscsi_is_key_declarative(idx)) + break; + memset(buf, 0, sizeof(buf)); + param_val_to_str(session_keys, idx, val, buf); + text_key_add(conn, key, buf); + break; + case KEY_STATE_REQUEST: + if (val != conn->session_param[idx].exec_val) { + rsp->status_class = ISCSI_STATUS_INITIATOR_ERR; + rsp->status_detail = ISCSI_STATUS_INIT_ERR; + conn->state = STATE_EXIT; + log_warning("%s %u %u\n", key, + val, conn->session_param[idx].exec_val); + goto out; + } + break; + case KEY_STATE_DONE: + break; + } + conn->session_param[idx].state = KEY_STATE_DONE; + } else + text_key_add(conn, key, "NotUnderstood"); + } + +out: + return; +} + +static int text_check_param(struct connection *conn) +{ + struct iscsi_param *p = conn->session_param; + char buf[32]; + int i, cnt; + + for (i = 0, cnt = 0; session_keys[i].name; i++) { + if (p[i].state == KEY_STATE_START && p[i].exec_val != session_keys[i].rfc_def) { + switch (conn->state) { + case STATE_LOGIN_FULL: + case STATE_SECURITY_FULL: + if (iscsi_is_key_declarative(i)) { + p[i].state = KEY_STATE_DONE; + continue; + } + break; + case STATE_LOGIN: + if (iscsi_is_key_declarative(i)) + continue; + memset(buf, 0, sizeof(buf)); + param_val_to_str(session_keys, i, p[i].exec_val, + buf); + text_key_add(conn, session_keys[i].name, buf); + if (i == key_max_recv_data_length) { + p[i].state = KEY_STATE_DONE; + continue; + } + p[i].state = KEY_STATE_REQUEST; + break; + default: + if (iscsi_is_key_declarative(i)) + continue; + } + cnt++; + } + } + + return cnt; +} + +static void login_start(struct connection *conn) +{ + struct iscsi_login_req_hdr *req = (struct iscsi_login_req_hdr *)&conn->req.bhs; + struct iscsi_login_rsp_hdr *rsp = (struct iscsi_login_rsp_hdr *)&conn->rsp.bhs; + char *name, *alias, *session_type, *target_name; + + conn->cid = be16_to_cpu(req->cid); + conn->sid.id64 = req->sid.id64; + if (!conn->sid.id64) { + rsp->status_class = ISCSI_STATUS_INITIATOR_ERR; + rsp->status_detail = ISCSI_STATUS_MISSING_FIELDS; + conn->state = STATE_EXIT; + return; + } + + name = text_key_find(conn, "InitiatorName"); + if (!name) { + rsp->status_class = ISCSI_STATUS_INITIATOR_ERR; + rsp->status_detail = ISCSI_STATUS_MISSING_FIELDS; + conn->state = STATE_EXIT; + return; + } + conn->initiator = strdup(name); + alias = text_key_find(conn, "InitiatorAlias"); + session_type = text_key_find(conn, "SessionType"); + target_name = text_key_find(conn, "TargetName"); + + conn->auth_method = -1; + conn->session_type = SESSION_NORMAL; + + if (session_type) { + if (!strcmp(session_type, "Discovery")) + conn->session_type = SESSION_DISCOVERY; + else if (strcmp(session_type, "Normal")) { + rsp->status_class = ISCSI_STATUS_INITIATOR_ERR; + rsp->status_detail = ISCSI_STATUS_INV_SESSION_TYPE; + conn->state = STATE_EXIT; + return; + } + } + + if (conn->session_type == SESSION_NORMAL) { + if (!target_name) { + rsp->status_class = ISCSI_STATUS_INITIATOR_ERR; + rsp->status_detail = ISCSI_STATUS_MISSING_FIELDS; + conn->state = STATE_EXIT; + return; + } + + if (!(conn->tid = target_find_by_name(target_name)) || + cops->initiator_access(conn->tid, conn->fd) || + isns_scn_access(conn->tid, conn->fd, name)) { + rsp->status_class = ISCSI_STATUS_INITIATOR_ERR; + rsp->status_detail = ISCSI_STATUS_TGT_NOT_FOUND; + conn->state = STATE_EXIT; + return; + } + + if (ki->param_get(conn->tid, 0, key_session, + conn->session_param, 1)) { + rsp->status_class = ISCSI_STATUS_TARGET_ERROR; + rsp->status_detail = ISCSI_STATUS_SVC_UNAVAILABLE; + conn->state = STATE_EXIT; + } + conn->session_param[key_max_recv_data_length].exec_val = + conn->session_param[key_max_recv_data_length].local_val; + } + conn->exp_cmd_sn = be32_to_cpu(req->cmd_sn); + log_debug(1, "exp_cmd_sn: %d,%d", conn->exp_cmd_sn, req->cmd_sn); + text_key_add(conn, "TargetPortalGroupTag", "1"); +} + +static void login_finish(struct connection *conn) +{ + switch (conn->session_type) { + case SESSION_NORMAL: + if (!conn->session) + session_create(conn); + conn->sid = conn->session->sid; + break; + case SESSION_DISCOVERY: + /* set a dummy tsih value */ + conn->sid.id.tsih = 1; + break; + } +} + +static int cmnd_exec_auth(struct connection *conn) +{ + int res; + + switch (conn->auth_method) { + case AUTH_CHAP: + res = cmnd_exec_auth_chap(conn); + break; + case AUTH_NONE: + res = 0; + break; + default: + log_error("Unknown auth. method %d", conn->auth_method); + res = -3; + } + + return res; +} + +static void cmnd_exec_login(struct connection *conn) +{ + struct iscsi_login_req_hdr *req = (struct iscsi_login_req_hdr *)&conn->req.bhs; + struct iscsi_login_rsp_hdr *rsp = (struct iscsi_login_rsp_hdr *)&conn->rsp.bhs; + int stay = 0, nsg_disagree = 0; + + memset(rsp, 0, BHS_SIZE); + if ((req->opcode & ISCSI_OPCODE_MASK) != ISCSI_OP_LOGIN_CMD || + !(req->opcode & ISCSI_OP_IMMEDIATE)) { + //reject + } + + rsp->opcode = ISCSI_OP_LOGIN_RSP; + rsp->max_version = ISCSI_VERSION; + rsp->active_version = ISCSI_VERSION; + rsp->itt = req->itt; + + if (/*req->max_version < ISCSI_VERSION ||*/ + req->min_version > ISCSI_VERSION) { + rsp->status_class = ISCSI_STATUS_INITIATOR_ERR; + rsp->status_detail = ISCSI_STATUS_NO_VERSION; + conn->state = STATE_EXIT; + return; + } + + switch (req->flags & ISCSI_FLG_CSG_MASK) { + case ISCSI_FLG_CSG_SECURITY: + log_debug(1, "Login request (security negotiation): %d", conn->state); + rsp->flags = ISCSI_FLG_CSG_SECURITY; + + switch (conn->state) { + case STATE_FREE: + conn->state = STATE_SECURITY; + login_start(conn); + if (rsp->status_class) + return; + //else fall through + case STATE_SECURITY: + text_scan_security(conn); + if (rsp->status_class) + return; + if (conn->auth_method != AUTH_NONE) { + conn->state = STATE_SECURITY_AUTH; + conn->auth_state = AUTH_STATE_START; + } + break; + case STATE_SECURITY_AUTH: + switch (cmnd_exec_auth(conn)) { + case 0: + break; + default: + case -1: + goto init_err; + case -2: + goto auth_err; + } + break; + default: + goto init_err; + } + + break; + case ISCSI_FLG_CSG_LOGIN: + log_debug(1, "Login request (operational negotiation): %d", conn->state); + rsp->flags = ISCSI_FLG_CSG_LOGIN; + + switch (conn->state) { + case STATE_FREE: + conn->state = STATE_LOGIN; + + login_start(conn); + if (!account_empty(conn->tid, AUTH_DIR_INCOMING)) + goto auth_err; + if (rsp->status_class) + return; + text_scan_login(conn); + if (rsp->status_class) + return; + stay = text_check_param(conn); + break; + case STATE_LOGIN: + text_scan_login(conn); + if (rsp->status_class) + return; + stay = text_check_param(conn); + break; + default: + goto init_err; + } + break; + default: + goto init_err; + } + + if (rsp->status_class) + return; + if (conn->state != STATE_SECURITY_AUTH && req->flags & ISCSI_FLG_TRANSIT) { + int nsg = req->flags & ISCSI_FLG_NSG_MASK; + + switch (nsg) { + case ISCSI_FLG_NSG_LOGIN: + switch (conn->state) { + case STATE_SECURITY: + case STATE_SECURITY_DONE: + conn->state = STATE_SECURITY_LOGIN; + login_security_done(conn); + break; + default: + goto init_err; + } + break; + case ISCSI_FLG_NSG_FULL_FEATURE: + switch (conn->state) { + case STATE_SECURITY: + case STATE_SECURITY_DONE: + if ((nsg_disagree = text_check_param(conn))) { + conn->state = STATE_LOGIN; + nsg = ISCSI_FLG_NSG_LOGIN; + break; + } + conn->state = STATE_SECURITY_FULL; + login_security_done(conn); + break; + case STATE_LOGIN: + if (stay) + nsg = ISCSI_FLG_NSG_LOGIN; + else + conn->state = STATE_LOGIN_FULL; + break; + default: + goto init_err; + } + if (!stay && !nsg_disagree) { + text_check_param(conn); + login_finish(conn); + } + break; + default: + goto init_err; + } + rsp->flags |= nsg | (stay ? 0 : ISCSI_FLG_TRANSIT); + } + + rsp->sid = conn->sid; + rsp->stat_sn = cpu_to_be32(conn->stat_sn++); + rsp->exp_cmd_sn = cpu_to_be32(conn->exp_cmd_sn); + conn->max_cmd_sn = conn->exp_cmd_sn + 1; + rsp->max_cmd_sn = cpu_to_be32(conn->max_cmd_sn); + return; +init_err: + rsp->flags = 0; + rsp->status_class = ISCSI_STATUS_INITIATOR_ERR; + rsp->status_detail = ISCSI_STATUS_INIT_ERR; + conn->state = STATE_EXIT; + return; +auth_err: + rsp->flags = 0; + rsp->status_class = ISCSI_STATUS_INITIATOR_ERR; + rsp->status_detail = ISCSI_STATUS_AUTH_FAILED; + conn->state = STATE_EXIT; + return; +} + +static void text_scan_text(struct connection *conn) +{ + char *key, *value, *data; + int datasize; + + data = conn->req.data; + datasize = conn->req.datasize; + + while ((key = next_key(&data, &datasize, &value))) { + if (!strcmp(key, "SendTargets")) { + struct sockaddr_storage ss; + socklen_t slen, blen; + char *p, *addr, buf[NI_MAXHOST + 128]; + int suppress_ip6 = 0; + + if (value[0] == 0) + continue; + + p = addr = buf + 1; + buf[0] = '['; + blen = sizeof(buf) - 1; + + slen = sizeof(ss); + + getpeername(conn->fd, (struct sockaddr *) &ss, &slen); + if (ss.ss_family == AF_INET) + suppress_ip6 = 1; + else { + getnameinfo((struct sockaddr *) &ss, slen, p, + blen, NULL, 0, NI_NUMERICHOST); + + if (strstr(p, "::ffff:") == p) + suppress_ip6 = 1; // ipv4-mapped ipv6 ? + } + + getsockname(conn->fd, (struct sockaddr *) &ss, &slen); + slen = sizeof(ss); + + getnameinfo((struct sockaddr *) &ss, slen, p, blen, + NULL, 0, NI_NUMERICHOST); + + if (ss.ss_family == AF_INET6 && suppress_ip6) { + if (strstr(p, "::ffff:") != p) { + log_error("%s, %s:%d.", __FILE__, + __FUNCTION__, __LINE__); + suppress_ip6 = 0; + } else + addr += 7; + } + + p += strlen(p); + + if (ss.ss_family == AF_INET6 && !suppress_ip6) { + *p++ = ']'; + addr = buf; + } + + sprintf(p, ":%d,1", server_port); + target_list_build(conn, addr, + strcmp(value, "All") ? value : NULL); + } else + text_key_add(conn, key, "NotUnderstood"); + } +} + +static void cmnd_exec_text(struct connection *conn) +{ + struct iscsi_text_req_hdr *req = (struct iscsi_text_req_hdr *)&conn->req.bhs; + struct iscsi_text_rsp_hdr *rsp = (struct iscsi_text_rsp_hdr *)&conn->rsp.bhs; + + memset(rsp, 0, BHS_SIZE); + + if (be32_to_cpu(req->ttt) != 0xffffffff) { + /* reject */; + } + rsp->opcode = ISCSI_OP_TEXT_RSP; + rsp->itt = req->itt; + //rsp->ttt = rsp->ttt; + rsp->ttt = 0xffffffff; + conn->exp_cmd_sn = be32_to_cpu(req->cmd_sn); + if (!(req->opcode & ISCSI_OP_IMMEDIATE)) + conn->exp_cmd_sn++; + + log_debug(1, "Text request: %d", conn->state); + text_scan_text(conn); + + if (req->flags & ISCSI_FLG_FINAL) + rsp->flags = ISCSI_FLG_FINAL; + + rsp->stat_sn = cpu_to_be32(conn->stat_sn++); + rsp->exp_cmd_sn = cpu_to_be32(conn->exp_cmd_sn); + conn->max_cmd_sn = conn->exp_cmd_sn + 1; + rsp->max_cmd_sn = cpu_to_be32(conn->max_cmd_sn); +} + +static void cmnd_exec_logout(struct connection *conn) +{ + struct iscsi_logout_req_hdr *req = (struct iscsi_logout_req_hdr *)&conn->req.bhs; + struct iscsi_logout_rsp_hdr *rsp = (struct iscsi_logout_rsp_hdr *)&conn->rsp.bhs; + + memset(rsp, 0, BHS_SIZE); + rsp->opcode = ISCSI_OP_LOGOUT_RSP; + rsp->flags = ISCSI_FLG_FINAL; + rsp->itt = req->itt; + conn->exp_cmd_sn = be32_to_cpu(req->cmd_sn); + if (!(req->opcode & ISCSI_OP_IMMEDIATE)) + conn->exp_cmd_sn++; + + rsp->stat_sn = cpu_to_be32(conn->stat_sn++); + rsp->exp_cmd_sn = cpu_to_be32(conn->exp_cmd_sn); + conn->max_cmd_sn = conn->exp_cmd_sn + 1; + rsp->max_cmd_sn = cpu_to_be32(conn->max_cmd_sn); +} + +int cmnd_execute(struct connection *conn) +{ + int res = 1; + + switch (conn->req.bhs.opcode & ISCSI_OPCODE_MASK) { + case ISCSI_OP_LOGIN_CMD: + //if conn->state == STATE_FULL -> reject + cmnd_exec_login(conn); + conn->rsp.bhs.ahslength = conn->rsp.ahssize / 4; + conn->rsp.bhs.datalength[0] = conn->rsp.datasize >> 16; + conn->rsp.bhs.datalength[1] = conn->rsp.datasize >> 8; + conn->rsp.bhs.datalength[2] = conn->rsp.datasize; + log_pdu(2, &conn->rsp); + break; + case ISCSI_OP_TEXT_CMD: + //if conn->state != STATE_FULL -> reject + cmnd_exec_text(conn); + conn->rsp.bhs.ahslength = conn->rsp.ahssize / 4; + conn->rsp.bhs.datalength[0] = conn->rsp.datasize >> 16; + conn->rsp.bhs.datalength[1] = conn->rsp.datasize >> 8; + conn->rsp.bhs.datalength[2] = conn->rsp.datasize; + log_pdu(2, &conn->rsp); + break; + case ISCSI_OP_LOGOUT_CMD: + //if conn->state != STATE_FULL -> reject + cmnd_exec_logout(conn); + conn->rsp.bhs.ahslength = conn->rsp.ahssize / 4; + conn->rsp.bhs.datalength[0] = conn->rsp.datasize >> 16; + conn->rsp.bhs.datalength[1] = conn->rsp.datasize >> 8; + conn->rsp.bhs.datalength[2] = conn->rsp.datasize; + log_pdu(2, &conn->rsp); + break; + default: + //reject + res = 0; + break; + } + + return res; +} + +void cmnd_finish(struct connection *conn) +{ + switch (conn->state) { + case STATE_EXIT: + conn->state = STATE_CLOSE; + break; + case STATE_SECURITY_LOGIN: + conn->state = STATE_LOGIN; + break; + case STATE_SECURITY_FULL: + //fall through + case STATE_LOGIN_FULL: + if (conn->session_type == SESSION_NORMAL) + conn->state = STATE_KERNEL; + else + conn->state = STATE_FULL; + break; + } +} diff --git a/iscsi-scst/usr/iscsid.h b/iscsi-scst/usr/iscsid.h new file mode 100644 index 000000000..6c997831a --- /dev/null +++ b/iscsi-scst/usr/iscsid.h @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2002-2003 Ardis Technolgies + * + * Released under the terms of the GNU GPL v2.0. + */ + +#ifndef ISCSID_H +#define ISCSID_H + +#include +#include + +#include "types.h" +#include "iscsi_hdr.h" +#include "iscsi_u.h" +#include "param.h" +#include "config.h" +#include "misc.h" + +#define PROC_SESSION "/proc/scsi_tgt/iscsi/session" + +struct PDU { + struct iscsi_hdr bhs; + void *ahs; + unsigned int ahssize; + void *data; + unsigned int datasize; +}; + +#define KEY_STATE_START 0 +#define KEY_STATE_REQUEST 1 +#define KEY_STATE_DONE 2 + +struct session { + struct qelem slist; + + char *initiator; + struct target *target; + union iscsi_sid sid; + + int conn_cnt; +}; + +struct connection { + int state; + int iostate; + int fd; + + struct session *session; + + u32 tid; + struct iscsi_param session_param[session_key_last]; + + char *initiator; + char *user; + union iscsi_sid sid; + u16 cid; + u16 pad; + int session_type; + int auth_method; + + u32 stat_sn; + u32 exp_stat_sn; + + u32 cmd_sn; + u32 exp_cmd_sn; + u32 max_cmd_sn; + + struct PDU req; + void *req_buffer; + struct PDU rsp; + void *rsp_buffer; + unsigned char *buffer; + int rwsize; + + int auth_state; + union { + struct { + int digest_alg; + int id; + int challenge_size; + unsigned char *challenge; + } chap; + } auth; +}; + +#define IOSTATE_FREE 0 +#define IOSTATE_READ_BHS 1 +#define IOSTATE_READ_AHS_DATA 2 +#define IOSTATE_WRITE_BHS 3 +#define IOSTATE_WRITE_AHS 4 +#define IOSTATE_WRITE_DATA 5 + +#define STATE_FREE 0 +#define STATE_SECURITY 1 +#define STATE_SECURITY_AUTH 2 +#define STATE_SECURITY_DONE 3 +#define STATE_SECURITY_LOGIN 4 +#define STATE_SECURITY_FULL 5 +#define STATE_LOGIN 6 +#define STATE_LOGIN_FULL 7 +#define STATE_FULL 8 +#define STATE_KERNEL 9 +#define STATE_CLOSE 10 +#define STATE_EXIT 11 + +#define AUTH_STATE_START 0 +#define AUTH_STATE_CHALLENGE 1 + +/* don't touch these */ +#define AUTH_DIR_INCOMING 0 +#define AUTH_DIR_OUTGOING 1 + +#define SESSION_NORMAL 0 +#define SESSION_DISCOVERY 1 +#define AUTH_UNKNOWN -1 +#define AUTH_NONE 0 +#define AUTH_CHAP 1 +#define DIGEST_UNKNOWN -1 + +#define BHS_SIZE 48 + +#define INCOMING_BUFSIZE 8192 + +struct target { + struct qelem tlist; + + struct qelem sessions_list; + + u32 tid; + char name[ISCSI_NAME_LEN]; + char *alias; + + int max_nr_sessions; + int nr_sessions; + + struct qelem isns_head; +}; + +extern struct config_operations plain_ops; +extern int ctrl_fd; +extern int conn_blocked; + +/* chap.c */ +extern int cmnd_exec_auth_chap(struct connection *conn); + +/* conn.c */ +extern struct connection *conn_alloc(void); +extern void conn_free(struct connection *conn); +extern int conn_test(struct connection *conn); +extern void conn_take_fd(struct connection *conn, int fd); +extern void conn_read_pdu(struct connection *conn); +extern void conn_write_pdu(struct connection *conn); +extern void conn_free_pdu(struct connection *conn); + +/* iscsi_scstd.c */ +extern uint16_t server_port; +extern void isns_set_fd(int isns, int scn_listen, int scn); +extern void wait_4_iscsi_event(int timeout); + +/* iscsid.c */ +extern int iscsi_debug; + +extern int cmnd_execute(struct connection *conn); +extern void cmnd_finish(struct connection *conn); +extern char *text_key_find(struct connection *conn, char *searchKey); +extern void text_key_add(struct connection *conn, char *key, char *value); + +/* log.c */ +extern int log_daemon; +extern int log_level; + +extern void log_init(void); +extern void log_warning(const char *fmt, ...) + __attribute__ ((format (printf, 1, 2))); +extern void log_error(const char *fmt, ...) + __attribute__ ((format (printf, 1, 2))); +extern void log_debug(int level, const char *fmt, ...) + __attribute__ ((format (printf, 2, 3))); +extern void log_pdu(int level, struct PDU *pdu); + +/* session.c */ +extern struct session *session_find_name(u32 tid, const char *iname, union iscsi_sid sid); +extern struct session *session_find_id(u32 tid, u64 sid); +extern void session_create(struct connection *conn); +extern void session_remove(struct session *session); + +/* target.c */ +extern struct qelem targets_list; +extern int target_add(u32 *, char *); +extern int target_del(u32); +extern u32 target_find_by_name(const char *name); +struct target * target_find_by_id(u32); +extern void target_list_build(struct connection *, char *, char *); + +/* message.c */ +extern int iscsi_adm_request_listen(void); +extern int iscsi_adm_request_handle(int accept_fd); + +/* ctldev.c */ +struct iscsi_kernel_interface { + int (*ctldev_open) (void); + int (*param_get) (u32, u64, int, struct iscsi_param *, int); + int (*param_set) (u32, u64, int, u32, struct iscsi_param *, int); + int (*target_create) (u32 *, char *); + int (*target_destroy) (u32); + int (*session_create) (u32, u64, u32, u32, char *, char *); + int (*session_destroy) (u32, u64); + int (*conn_create) (u32, u64, u32, u32, u32, int, u32, u32); + int (*conn_destroy) (u32 tid, u64 sid, u32 cid); +}; + +extern struct iscsi_kernel_interface *ki; + +/* the following functions should be killed */ +extern int session_conns_close(u32 tid, u64 sid); +extern int server_stop(void); +extern int target_destroy(u32 tid); + +/* event.c */ +extern void handle_iscsi_events(int fd); +extern int nl_open(void); + +/* param.c */ +extern int param_index_by_name(char *name, struct iscsi_key *keys); + +/* isns.c */ +extern int isns_init(char *addr, int isns_ac); +extern int isns_handle(int is_timeout, int *timeout); +extern int isns_scn_handle(int accept); +extern int isns_scn_access(u32 tid, int fd, char *name); +extern int isns_target_register(char *name); +extern int isns_target_deregister(char *name); +extern void isns_exit(void); + +#endif /* ISCSID_H */ diff --git a/iscsi-scst/usr/isns.c b/iscsi-scst/usr/isns.c new file mode 100644 index 000000000..5dd60a7ed --- /dev/null +++ b/iscsi-scst/usr/isns.c @@ -0,0 +1,960 @@ +/* + * iSNS functions + * + * Copyright (C) 2006 FUJITA Tomonori + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * 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 + * General Public License for more details. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "iscsid.h" +#include "isns_proto.h" + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#define BUFSIZE (1 << 18) + +struct isns_io { + char *buf; + int offset; +}; + +struct isns_qry_mgmt { + char name[ISCSI_NAME_LEN]; + uint16_t transaction; + struct qelem qlist; +}; + +struct isns_initiator { + char name[ISCSI_NAME_LEN]; + struct qelem ilist; +}; + +static LIST_HEAD(qry_list); +static uint16_t scn_listen_port; +static int use_isns, use_isns_ac, isns_fd, scn_listen_fd, scn_fd; +static struct isns_io isns_rx, scn_rx; +static char *rxbuf; +static uint16_t transaction; +static uint32_t current_timeout = 30; /* seconds */ +static char eid[ISCSI_NAME_LEN]; +static uint8_t ip[16]; /* SCST iSCSI supports only one portal */ +static struct sockaddr_storage ss; + +int isns_scn_access(uint32_t tid, int fd, char *name) +{ + struct isns_initiator *ini; + struct target *target = target_find_by_id(tid); + + if (!use_isns || !use_isns_ac) + return 0; + + if (!target) + return -EPERM; + + list_for_each_entry(ini, &target->isns_head, ilist) { + if (!strcmp(ini->name, name)) + return 0; + } + return -EPERM; +} + +static int isns_get_ip(int fd) +{ + int err, i; + uint32_t addr; + struct sockaddr_storage lss; + socklen_t slen = sizeof(lss); + + err = getsockname(fd, (struct sockaddr *) &lss, &slen); + if (err) { + log_error("getsockname error %s!", gai_strerror(err)); + return err; + } + + err = getnameinfo((struct sockaddr *) &lss, sizeof(lss), + eid, sizeof(eid), NULL, 0, 0); + if (err) { + log_error("getaddrinfo error %s!", gai_strerror(err)); + return err; + } + + switch (lss.ss_family) { + case AF_INET: + addr = (((struct sockaddr_in *) &lss)->sin_addr.s_addr); + + ip[10] = ip[11] = 0xff; + ip[15] = 0xff & (addr >> 24); + ip[14] = 0xff & (addr >> 16); + ip[13] = 0xff & (addr >> 8); + ip[12] = 0xff & addr; + break; + case AF_INET6: + for (i = 0; i < ARRAY_SIZE(ip); i++) + ip[i] = ((struct sockaddr_in6 *) &lss)->sin6_addr.s6_addr[i]; + break; + } + + return 0; +} + +static int isns_connect(void) +{ + int fd, err; + + fd = socket(ss.ss_family, SOCK_STREAM, IPPROTO_TCP); + if (fd < 0) { + log_error("unable to create (%s) %d!", strerror(errno), + ss.ss_family); + return -1; + } + + err = connect(fd, (struct sockaddr *) &ss, sizeof(ss)); + if (err < 0) { + log_error("unable to connect (%s) %d!", strerror(errno), + ss.ss_family); + close(fd); + return -1; + } + + log_error("%s %d: new connection %d", __FUNCTION__, __LINE__, fd); + + if (!strlen(eid)) { + err = isns_get_ip(fd); + if (err) { + close(fd); + return -1; + } + } + + isns_fd = fd; + isns_set_fd(fd, scn_listen_fd, scn_fd); + + return fd; +} + +static void isns_hdr_init(struct isns_hdr *hdr, uint16_t function, + uint16_t length, uint16_t flags, + uint16_t trans, uint16_t sequence) +{ + hdr->version = htons(0x0001); + hdr->function = htons(function); + hdr->length = htons(length); + hdr->flags = htons(flags); + hdr->transaction = htons(trans); + hdr->sequence = htons(sequence); +} + +static int isns_tlv_set(struct isns_tlv **tlv, uint32_t tag, uint32_t length, + void *value) +{ + if (length) + memcpy((*tlv)->value, value, length); + if (length % ISNS_ALIGN) + length += (ISNS_ALIGN - (length % ISNS_ALIGN)); + + (*tlv)->tag = htonl(tag); + (*tlv)->length = htonl(length); + + length += sizeof(struct isns_tlv); + *tlv = (struct isns_tlv *) ((char *) *tlv + length); + + return length; +} + +static int isns_scn_deregister(char *name) +{ + int err; + uint16_t flags, length = 0; + char buf[2048]; + struct isns_hdr *hdr = (struct isns_hdr *) buf; + struct isns_tlv *tlv; + + if (!isns_fd) + if (isns_connect() < 0) + return 0; + + memset(buf, 0, sizeof(buf)); + tlv = (struct isns_tlv *) hdr->pdu; + + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_NAME, strlen(name), name); + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_NAME, strlen(name), name); + + flags = ISNS_FLAG_CLIENT | ISNS_FLAG_LAST_PDU | ISNS_FLAG_FIRST_PDU; + isns_hdr_init(hdr, ISNS_FUNC_SCN_DEREG, length, flags, + ++transaction, 0); + + err = write(isns_fd, buf, length + sizeof(struct isns_hdr)); + if (err < 0) + log_error("%s %d: %s", __FUNCTION__, __LINE__, strerror(errno)); + + return 0; +} + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define set_scn_flag(x) \ +{ \ + x = (x & 0x55555555) << 1 | (x & 0xaaaaaaaa) >> 1; \ + x = (x & 0x33333333) << 2 | (x & 0xcccccccc) >> 2; \ + x = (x & 0x0f0f0f0f) << 4 | (x & 0xf0f0f0f0) >> 4; \ + x = (x & 0x00ff00ff) << 8 | (x & 0xff00ff00) >> 8; \ + x = (x & 0x0000ffff) << 16 | (x & 0xffff0000) >> 16; \ +} +#else +#define set_scn_flag(x) (x) +#endif + +static int isns_scn_register(void) +{ + int err; + uint16_t flags, length = 0; + uint32_t scn_flags; + char buf[4096]; + struct isns_hdr *hdr = (struct isns_hdr *) buf; + struct isns_tlv *tlv; + struct target *target; + + if (list_empty(&targets_list)) + return 0; + + if (!isns_fd) + if (isns_connect() < 0) + return 0; + + memset(buf, 0, sizeof(buf)); + tlv = (struct isns_tlv *) hdr->pdu; + + target = list_entry(targets_list.q_forw, struct target, tlist); + + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_NAME, + strlen(target->name), target->name); + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_NAME, + strlen(target->name), target->name); + length += isns_tlv_set(&tlv, 0, 0, 0); + + scn_flags = ISNS_SCN_FLAG_INITIATOR | ISNS_SCN_FLAG_OBJECT_REMOVE | + ISNS_SCN_FLAG_OBJECT_ADDED | ISNS_SCN_FLAG_OBJECT_UPDATED; + scn_flags = htonl(set_scn_flag(scn_flags)); + + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_SCN_BITMAP, + sizeof(scn_flags), &scn_flags); + + flags = ISNS_FLAG_CLIENT | ISNS_FLAG_LAST_PDU | ISNS_FLAG_FIRST_PDU; + isns_hdr_init(hdr, ISNS_FUNC_SCN_REG, length, flags, ++transaction, 0); + + err = write(isns_fd, buf, length + sizeof(struct isns_hdr)); + if (err < 0) + log_error("%s %d: %s", __FUNCTION__, __LINE__, strerror(errno)); + + return 0; +} + +static int isns_attr_query(char *name) +{ + int err; + uint16_t flags, length = 0; + char buf[4096]; + struct isns_hdr *hdr = (struct isns_hdr *) buf; + struct isns_tlv *tlv; + struct target *target; + uint32_t node = htonl(ISNS_NODE_INITIATOR); + struct isns_qry_mgmt *mgmt; + + if (list_empty(&targets_list)) + return 0; + + if (!isns_fd) + if (isns_connect() < 0) + return 0; + + mgmt = malloc(sizeof(*mgmt)); + if (!mgmt) + return 0; + insque(&mgmt->qlist, &qry_list); + + memset(buf, 0, sizeof(buf)); + tlv = (struct isns_tlv *) hdr->pdu; + + if (name) + snprintf(mgmt->name, sizeof(mgmt->name), name); + else { + mgmt->name[0] = '\0'; + target = list_entry(targets_list.q_forw, struct target, tlist); + name = target->name; + } + + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_NAME, strlen(name), name); + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_NODE_TYPE, + sizeof(node), &node); + length += isns_tlv_set(&tlv, 0, 0, 0); + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_NAME, 0, 0); + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_NODE_TYPE, 0, 0); + length += isns_tlv_set(&tlv, ISNS_ATTR_PORTAL_IP_ADDRESS, 0, 0); + + flags = ISNS_FLAG_CLIENT | ISNS_FLAG_LAST_PDU | ISNS_FLAG_FIRST_PDU; + isns_hdr_init(hdr, ISNS_FUNC_DEV_ATTR_QRY, length, flags, + ++transaction, 0); + mgmt->transaction = transaction; + + err = write(isns_fd, buf, length + sizeof(struct isns_hdr)); + if (err < 0) + log_error("%s %d: %s", __FUNCTION__, __LINE__, strerror(errno)); + + return 0; +} + +static int isns_deregister(void) +{ + int err; + uint16_t flags, length = 0; + char buf[4096]; + struct isns_hdr *hdr = (struct isns_hdr *) buf; + struct isns_tlv *tlv; + struct target *target; + + if (list_empty(&targets_list)) + return 0; + + if (!isns_fd) + if (isns_connect() < 0) + return 0; + + memset(buf, 0, sizeof(buf)); + tlv = (struct isns_tlv *) hdr->pdu; + + target = list_entry(targets_list.q_forw, struct target, tlist); + + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_NAME, + strlen(target->name), target->name); + length += isns_tlv_set(&tlv, 0, 0, 0); + length += isns_tlv_set(&tlv, ISNS_ATTR_ENTITY_IDENTIFIER, + strlen(eid), eid); + + flags = ISNS_FLAG_CLIENT | ISNS_FLAG_LAST_PDU | ISNS_FLAG_FIRST_PDU; + isns_hdr_init(hdr, ISNS_FUNC_DEV_DEREG, length, flags, + ++transaction, 0); + + err = write(isns_fd, buf, length + sizeof(struct isns_hdr)); + if (err < 0) + log_error("%s %d: %s", __FUNCTION__, __LINE__, strerror(errno)); + return 0; +} + +int isns_target_register(char *name) +{ + char buf[4096]; + uint16_t flags = 0, length = 0; + struct isns_hdr *hdr = (struct isns_hdr *) buf; + struct isns_tlv *tlv; + uint32_t port = htonl(ISCSI_LISTEN_PORT); + uint32_t node = htonl(ISNS_NODE_TARGET); + uint32_t type = htonl(2); + struct target *target; + int err, initial = list_length_is_one(&targets_list); + + if (!use_isns) + return 0; + + if (!isns_fd) + if (isns_connect() < 0) + return 0; + + memset(buf, 0, sizeof(buf)); + tlv = (struct isns_tlv *) hdr->pdu; + + target = list_entry(targets_list.q_back, struct target, tlist); + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_NAME, + strlen(target->name), target->name); + + length += isns_tlv_set(&tlv, ISNS_ATTR_ENTITY_IDENTIFIER, + strlen(eid), eid); + + length += isns_tlv_set(&tlv, 0, 0, 0); + length += isns_tlv_set(&tlv, ISNS_ATTR_ENTITY_IDENTIFIER, + strlen(eid), eid); + if (initial) { + length += isns_tlv_set(&tlv, ISNS_ATTR_ENTITY_PROTOCOL, + sizeof(type), &type); + length += isns_tlv_set(&tlv, ISNS_ATTR_PORTAL_IP_ADDRESS, + sizeof(ip), &ip); + length += isns_tlv_set(&tlv, ISNS_ATTR_PORTAL_PORT, + sizeof(port), &port); + flags = ISNS_FLAG_REPLACE; + + if (scn_listen_port) { + uint32_t sport = htonl(scn_listen_port); + length += isns_tlv_set(&tlv, ISNS_ATTR_SCN_PORT, + sizeof(sport), &sport); + } + } + + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_NAME, strlen(name), name); + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_NODE_TYPE, + sizeof(node), &node); + + flags |= ISNS_FLAG_CLIENT | ISNS_FLAG_LAST_PDU | ISNS_FLAG_FIRST_PDU; + isns_hdr_init(hdr, ISNS_FUNC_DEV_ATTR_REG, length, flags, + ++transaction, 0); + + err = write(isns_fd, buf, length + sizeof(struct isns_hdr)); + if (err < 0) + log_error("%s %d: %s", __FUNCTION__, __LINE__, strerror(errno)); + + if (scn_listen_port) + isns_scn_register(); + + isns_attr_query(name); + + return 0; +} + +static void free_all_acl(struct target *target) +{ + struct isns_initiator *ini; + + while (!list_empty(&target->isns_head)) { + ini = list_entry(target->isns_head.q_forw, typeof(*ini), ilist); + remque(&ini->ilist); + } +} + +static struct target *target_lookup_by_name(char *name) +{ + uint32_t tid; + + tid = target_find_by_name(name); + if (!tid) + return NULL; + return target_find_by_id(tid); +} + +int isns_target_deregister(char *name) +{ + char buf[4096]; + uint16_t flags, length = 0; + struct isns_hdr *hdr = (struct isns_hdr *) buf; + struct isns_tlv *tlv; + int err, last = list_empty(&targets_list); + struct target *target; + + target = target_lookup_by_name(name); + if (target) + free_all_acl(target); + + if (!use_isns) + return 0; + + if (!isns_fd) + if (isns_connect() < 0) + return 0; + + isns_scn_deregister(name); + + memset(buf, 0, sizeof(buf)); + tlv = (struct isns_tlv *) hdr->pdu; + + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_NAME, strlen(name), name); + length += isns_tlv_set(&tlv, 0, 0, 0); + if (last) + length += isns_tlv_set(&tlv, ISNS_ATTR_ENTITY_IDENTIFIER, + strlen(eid), eid); + else + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_NAME, + strlen(name), name); + + flags = ISNS_FLAG_CLIENT | ISNS_FLAG_LAST_PDU | ISNS_FLAG_FIRST_PDU; + isns_hdr_init(hdr, ISNS_FUNC_DEV_DEREG, length, flags, + ++transaction, 0); + + err = write(isns_fd, buf, length + sizeof(struct isns_hdr)); + if (err < 0) + log_error("%s %d: %s", __FUNCTION__, __LINE__, strerror(errno)); + + return 0; +} + +static int recv_hdr(int fd, struct isns_io *rx, struct isns_hdr *hdr) +{ + int err; + + if (rx->offset < sizeof(*hdr)) { + err = read(fd, rx->buf + rx->offset, + sizeof(*hdr) - rx->offset); + if (err < 0) { + if (errno == EAGAIN || errno == EINTR) + return -EAGAIN; + log_error("header read error %d %d %d %d", + fd, err, errno, rx->offset); + return -1; + } else if (err == 0) + return -1; + + log_debug(1, "header %d %d bytes!", fd, err); + rx->offset += err; + + if (rx->offset < sizeof(*hdr)) { + log_debug(1, "header wait %d %d", rx->offset, err); + return -EAGAIN; + } + } + + return 0; +} + +#define get_hdr_param(hdr, function, length, flags, transaction, sequence) \ +{ \ + function = ntohs(hdr->function); \ + length = ntohs(hdr->length); \ + flags = ntohs(hdr->flags); \ + transaction = ntohs(hdr->transaction); \ + sequence = ntohs(hdr->sequence); \ +} + +static int recv_pdu(int fd, struct isns_io *rx, struct isns_hdr *hdr) +{ + uint16_t function, length, flags, transaction, sequence; + int err; + + err = recv_hdr(fd, rx, hdr); + if (err) + return err; + + /* Now we got a complete header */ + get_hdr_param(hdr, function, length, flags, transaction, sequence); + log_debug(1, "got a header %x %u %x %u %u", function, length, flags, + transaction, sequence); + + if (length + sizeof(*hdr) > BUFSIZE) { + log_error("FIXME we cannot handle this yet %u!", length); + return -1; + } + + if (rx->offset < length + sizeof(*hdr)) { + err = read(fd, rx->buf + rx->offset, + length + sizeof(*hdr) - rx->offset); + if (err < 0) { + if (errno == EAGAIN || errno == EINTR) + return -EAGAIN; + log_error("pdu read error %d %d %d %d", + fd, err, errno, rx->offset); + return -1; + } else if (err == 0) + return -1; + + log_debug(1, "pdu %u %u", fd, err); + rx->offset += err; + + if (rx->offset < length + sizeof(*hdr)) { + log_error("pdu wait %d %d", rx->offset, err); + return -EAGAIN; + } + } + + /* Now we got everything. */ + rx->offset = 0; + + return 0; +} + +#define print_unknown_pdu(hdr) \ +{ \ + uint16_t function, length, flags, transaction, sequence; \ + get_hdr_param(hdr, function, length, flags, transaction, \ + sequence) \ + log_error("%s %d: unknown function %x %u %x %u %u", \ + __FUNCTION__, __LINE__, \ + function, length, flags, transaction, sequence); \ +} + +static char *print_scn_pdu(struct isns_hdr *hdr) +{ + struct isns_tlv *tlv = (struct isns_tlv *) hdr->pdu; + uint16_t function, length, flags, transaction, sequence; + char *name = NULL; + + get_hdr_param(hdr, function, length, flags, transaction, sequence); + + while (length) { + uint32_t vlen = ntohl(tlv->length); + + switch (ntohl(tlv->tag)) { + case ISNS_ATTR_ISCSI_NAME: + log_error("scn name: %u, %s", vlen, (char *) tlv->value); + if (!name) + name = (char *) tlv->value; + break; + case ISNS_ATTR_TIMESTAMP: +/* log_error("%u : %u : %" PRIx64, ntohl(tlv->tag), vlen, */ +/* *((uint64_t *) tlv->value)); */ + break; + case ISNS_ATTR_ISCSI_SCN_BITMAP: + log_error("scn bitmap : %x", *((uint32_t *) tlv->value)); + break; + } + + length -= (sizeof(*tlv) + vlen); + tlv = (struct isns_tlv *) ((char *) tlv->value + vlen); + } + + return name; +} + +static void qry_rsp_handle(struct isns_hdr *hdr) +{ + struct isns_tlv *tlv; + uint16_t function, length, flags, transaction, sequence; + uint32_t status = (uint32_t) (*hdr->pdu); + struct isns_qry_mgmt *mgmt, *n; + struct target *target; + struct isns_initiator *ini; + char *name = NULL; + + get_hdr_param(hdr, function, length, flags, transaction, sequence); + + list_for_each_entry_safe(mgmt, n, &qry_list, qlist) { + if (mgmt->transaction == transaction) { + remque(&mgmt->qlist); + goto found; + } + } + + log_error("%s %d: transaction not found %u", + __FUNCTION__, __LINE__, transaction); + + return; +found: + + if (status) { + log_error("%s %d: error response %u", + __FUNCTION__, __LINE__, status); + + goto free_qry_mgmt; + } + + if (!strlen(mgmt->name)) { + log_debug(1, "%s %d: skip %u", + __FUNCTION__, __LINE__, transaction); + goto free_qry_mgmt; + } + + target = target_lookup_by_name(mgmt->name); + if (!target) { + log_error("%s %d: invalid tid %s", + __FUNCTION__, __LINE__, mgmt->name); + goto free_qry_mgmt; + } + + free_all_acl(target); + + /* skip status */ + tlv = (struct isns_tlv *) ((char *) hdr->pdu + 4); + length -= 4; + + while (length) { + uint32_t vlen = ntohl(tlv->length); + + switch (ntohl(tlv->tag)) { + case ISNS_ATTR_ISCSI_NAME: + name = (char *) tlv->value; + break; + case ISNS_ATTR_ISCSI_NODE_TYPE: + if (ntohl(*(tlv->value)) == ISNS_NODE_INITIATOR && name) { + log_error("%s %d: %s", __FUNCTION__, __LINE__, + (char *) name); + ini = malloc(sizeof(*ini)); + if (!ini) + goto free_qry_mgmt; + snprintf(ini->name, sizeof(ini->name), name); + insque(&ini->ilist, &target->isns_head); + } else + name = NULL; + break; + default: + name = NULL; + break; + } + + length -= (sizeof(*tlv) + vlen); + tlv = (struct isns_tlv *) ((char *) tlv->value + vlen); + } + +free_qry_mgmt: + free(mgmt); +} + +int isns_handle(int is_timeout, int *timeout) +{ + int err; + struct isns_io *rx = &isns_rx; + struct isns_hdr *hdr = (struct isns_hdr *) rx->buf; + uint32_t result; + uint16_t function, length, flags, transaction, sequence; + char *name = NULL; + + if (is_timeout) + return isns_attr_query(NULL); + + err = recv_pdu(isns_fd, rx, hdr); + if (err) { + if (err == -EAGAIN) + return err; + log_debug(1, "%s %d: close connection %d", __FUNCTION__, __LINE__, + isns_fd); + close(isns_fd); + isns_fd = 0; + isns_set_fd(0, scn_listen_fd, scn_fd); + return err; + } + + get_hdr_param(hdr, function, length, flags, transaction, sequence); + result = ntohl((uint32_t) hdr->pdu[0]); + + switch (function) { + case ISNS_FUNC_DEV_ATTR_REG_RSP: + break; + case ISNS_FUNC_DEV_ATTR_QRY_RSP: + qry_rsp_handle(hdr); + break; + case ISNS_FUNC_DEV_DEREG_RSP: + case ISNS_FUNC_SCN_REG_RSP: + break; + case ISNS_FUNC_SCN: + name = print_scn_pdu(hdr); + if (name) { + log_error("%s %d: %s", __FUNCTION__, __LINE__, name); + isns_attr_query(name); + } + break; + default: + print_unknown_pdu(hdr); + } + + return 0; +} + +static int scn_accept_connection(void) +{ + struct sockaddr_storage from; + socklen_t slen; + int fd, err, opt = 1; + + slen = sizeof(from); + fd = accept(scn_listen_fd, (struct sockaddr *) &from, &slen); + if (fd < 0) { + log_error("%s %d: accept error %s", __FUNCTION__, __LINE__, + strerror(errno)); + return -errno; + } + log_error("Accept scn connection %d", fd); + + err = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); + if (err) + log_error("%s %d: %s\n", __FUNCTION__, __LINE__, + strerror(errno)); + /* not critical, so ignore. */ + + scn_fd = fd; + isns_set_fd(isns_fd, scn_listen_fd, scn_fd); + + return 0; +} + +static void send_scn_rsp(char *name, uint16_t transaction) +{ + char buf[1024]; + struct isns_hdr *hdr = (struct isns_hdr *) buf; + struct isns_tlv *tlv; + uint16_t flags, length = 0; + int err; + + memset(buf, 0, sizeof(buf)); + *((uint32_t *) hdr->pdu) = 0; + tlv = (struct isns_tlv *) ((char *) hdr->pdu + 4); + length +=4; + + length += isns_tlv_set(&tlv, ISNS_ATTR_ISCSI_NAME, strlen(name), name); + + flags = ISNS_FLAG_CLIENT | ISNS_FLAG_LAST_PDU | ISNS_FLAG_FIRST_PDU; + isns_hdr_init(hdr, ISNS_FUNC_SCN_RSP, length, flags, transaction, 0); + + err = write(scn_fd, buf, length + sizeof(struct isns_hdr)); + if (err < 0) + log_error("%s %d: %s", __FUNCTION__, __LINE__, strerror(errno)); +} + +int isns_scn_handle(int is_accept) +{ + int err; + struct isns_io *rx = &scn_rx; + struct isns_hdr *hdr = (struct isns_hdr *) rx->buf; + uint16_t function, length, flags, transaction, sequence; + char *name = NULL; + + log_error("%s %d: %d", __FUNCTION__, __LINE__, is_accept); + + if (is_accept) + return scn_accept_connection(); + + err = recv_pdu(scn_fd, rx, hdr); + if (err) { + if (err == -EAGAIN) + return err; + log_debug(1, "%s %d: close connection %d", __FUNCTION__, __LINE__, + scn_fd); + close(scn_fd); + scn_fd = 0; + isns_set_fd(isns_fd, scn_listen_fd, 0); + return err; + } + + get_hdr_param(hdr, function, length, flags, transaction, sequence); + + switch (function) { + case ISNS_FUNC_SCN: + name = print_scn_pdu(hdr); + break; + default: + print_unknown_pdu(hdr); + } + + if (name) { + send_scn_rsp(name, transaction); + isns_attr_query(name); + } + + return 0; +} + +static int scn_init(char *addr) +{ + int fd, opt, err; + struct sockaddr_storage lss; + socklen_t slen; + + fd = socket(ss.ss_family, SOCK_STREAM, IPPROTO_TCP); + if (fd < 0) { + log_error("%s %d: %s\n", __FUNCTION__, __LINE__, strerror(errno)); + return -errno; + } + + opt = 1; + if (ss.ss_family == AF_INET6) { + err = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)); + if (err) + log_error("%s %d: %s\n", __FUNCTION__, __LINE__, + strerror(errno)); + goto out; + } + + err = listen(fd, 5); + if (err) { + log_error("%s %d: %s\n", __FUNCTION__, __LINE__, strerror(errno)); + goto out; + } + + slen = sizeof(lss); + err = getsockname(fd, (struct sockaddr *) &lss, &slen); + if (err) { + log_error("%s %d: %s\n", __FUNCTION__, __LINE__, strerror(errno)); + goto out; + } + + /* protocol independent way ? */ + if (lss.ss_family == AF_INET6) + scn_listen_port = ntohs(((struct sockaddr_in6 *) &lss)->sin6_port); + else + scn_listen_port = ntohs(((struct sockaddr_in *) &lss)->sin_port); + + log_error("scn listen port %u %d %d\n", scn_listen_port, fd, err); +out: + if (err) + close(fd); + else { + scn_listen_fd = fd; + isns_set_fd(isns_fd, scn_listen_fd, scn_fd); + } + + return err; +} + +int isns_init(char *addr, int isns_ac) +{ + int err; + char port[8]; + struct addrinfo hints, *res; + + snprintf(port, sizeof(port), "%d", ISNS_PORT); + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo(addr, (char *) &port, &hints, &res); + if (err) { + log_error("getaddrinfo error %s, %s", gai_strerror(err), addr); + return -1; + } + memcpy(&ss, res->ai_addr, sizeof(ss)); + freeaddrinfo(res); + + rxbuf = calloc(2, BUFSIZE); + if (!rxbuf) { + log_error("oom!"); + return -1; + } + + scn_init(addr); + + isns_rx.buf = rxbuf; + isns_rx.offset = 0; + scn_rx.buf = rxbuf + BUFSIZE; + scn_rx.offset = 0; + + use_isns = 1; + use_isns_ac = isns_ac; + + return current_timeout * 1000; +} + +void isns_exit(void) +{ + struct target *target; + + if (!use_isns) + return; + + list_for_each_entry(target, &targets_list, tlist) + isns_scn_deregister(target->name); + + isns_deregister(); + /* we can't receive events any more. */ + isns_set_fd(0, 0, 0); + + if (isns_fd) + close(isns_fd); + if (scn_listen_fd) + close(scn_listen_fd); + if (scn_fd) + close(scn_fd); + + free(rxbuf); +} diff --git a/iscsi-scst/usr/isns_proto.h b/iscsi-scst/usr/isns_proto.h new file mode 100644 index 000000000..a070d0331 --- /dev/null +++ b/iscsi-scst/usr/isns_proto.h @@ -0,0 +1,200 @@ +/* + * iSNS protocol data types + * + * Copyright (C) 2006 FUJITA Tomonori + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * 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 + * General Public License for more details. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef ISNS_PROTO_H +#define ISNS_PROTO_H + +#define ISNS_PORT 3205 +#define ISNS_ALIGN 4 + +struct isns_hdr { + uint16_t version; + uint16_t function; + uint16_t length; + uint16_t flags; + uint16_t transaction; + uint16_t sequence; + uint32_t pdu[0]; +} __attribute__ ((packed)); + +struct isns_tlv { + uint32_t tag; + uint32_t length; + uint32_t value[0]; +} __attribute__ ((packed)); + +/* Commands and responses (4.1.3) */ +#define ISNS_FUNC_DEV_ATTR_REG 0x0001 +#define ISNS_FUNC_DEV_ATTR_QRY 0x0002 +#define ISNS_FUNC_DEV_GET_NEXT 0x0003 +#define ISNS_FUNC_DEV_DEREG 0x0004 +#define ISNS_FUNC_SCN_REG 0x0005 +#define ISNS_FUNC_SCN_DEREG 0x0006 +#define ISNS_FUNC_SCN_EVENT 0x0007 +#define ISNS_FUNC_SCN 0x0008 +#define ISNS_FUNC_DD_REG 0x0009 +#define ISNS_FUNC_DD_DEREG 0x000a +#define ISNS_FUNC_DDS_REG 0x000b +#define ISNS_FUNC_DDS_DEREG 0x000c +#define ISNS_FUNC_ESI 0x000d +#define ISNS_FUNC_HEARTBEAT 0x000e + +#define ISNS_FUNC_DEV_ATTR_REG_RSP 0x8001 +#define ISNS_FUNC_DEV_ATTR_QRY_RSP 0x8002 +#define ISNS_FUNC_DEV_GET_NEXT_RSP 0x8003 +#define ISNS_FUNC_DEV_DEREG_RSP 0x8004 +#define ISNS_FUNC_SCN_REG_RSP 0x8005 +#define ISNS_FUNC_SCN_DEREG_RSP 0x8006 +#define ISNS_FUNC_SCN_EVENT_RSP 0x8007 +#define ISNS_FUNC_SCN_RSP 0x8008 +#define ISNS_FUNC_DD_REG_RSP 0x8009 +#define ISNS_FUNC_DD_DEREG_RSP 0x800a +#define ISNS_FUNC_DDS_REG_RSP 0x800b +#define ISNS_FUNC_DDS_DEREG_RSP 0x800c +#define ISNS_FUNC_ESI_RSP 0x800d + +/* iSNSP flags (5.1.4) */ +#define ISNS_FLAG_CLIENT (1U << 15) +#define ISNS_FLAG_SERVER (1U << 14) +#define ISNS_FLAG_AUTH (1U << 13) +#define ISNS_FLAG_REPLACE (1U << 12) +#define ISNS_FLAG_LAST_PDU (1U << 11) +#define ISNS_FLAG_FIRST_PDU (1U << 10) + +/* Response Status Codes (5.4) */ +#define ISNS_STATUS_SUCCESS 0 +#define ISNS_STATUS_UNKNOWN_ERROR 1 +#define ISNS_STATUS_FORMAT_ERROR 2 +#define ISNS_STATUS_INVALID_REGISTRATION 3 +#define ISNS_STATUS_RESERVED 4 +#define ISNS_STATUS_INVALID_QUERY 5 +#define ISNS_STATUS_SOURCE_UNKNOWN 6 +#define ISNS_STATUS_SOURCE_ABSENT 7 +#define ISNS_STATUS_SOURCE_UNAUTHORIZED 8 +#define ISNS_STATUS_NO_SUCH_ENTRY 9 +#define ISNS_STATUS_VERSION_NOT_SUPPORTED 10 +#define ISNS_STATUS_INTERNAL_ERROR 11 +#define ISNS_STATUS_BUSY 12 +#define ISNS_STATUS_OPTION_NOT_UNDERSTOOD 13 +#define ISNS_STATUS_INVALID_UPDATE 14 +#define ISNS_STATUS_MESSAGE_NOT_SUPPORTED 15 +#define ISNS_STATUS_SCN_EVENT_REJECTED 16 +#define ISNS_STATUS_SCN_REGISTRATION_REJECTED 17 +#define ISNS_STATUS_ATTRIBUTE_NOT_IMPLEMENTED 18 +#define ISNS_STATUS_FC_DOMAIN_ID_NOT_AVAILABLE 19 +#define ISNS_STATUS_FC_DOMAIN_ID_NOT_ALLOCATED 20 +#define ISNS_STATUS_ESI_NOT_AVAILABLE 21 +#define ISNS_STATUS_INVALIDE_DEREGISTRATION 22 +#define ISNS_STATUS_REGISTRATION_NOT_SUPPORTED 23 + +/* Node type (5.4.2) */ +#define ISNS_NODE_CONTROL (1U << 2) +#define ISNS_NODE_INITIATOR (1U << 1) +#define ISNS_NODE_TARGET (1U << 0) + +/* Attributes (6.1) */ +#define ISNS_ATTR_DELIMITER 0 +#define ISNS_ATTR_ENTITY_IDENTIFIER 1 +#define ISNS_ATTR_ENTITY_PROTOCOL 2 +#define ISNS_ATTR_MANAGEMENT_IP_ADDRESS 3 +#define ISNS_ATTR_TIMESTAMP 4 +#define ISNS_ATTR_PROTOCOL_VERSION_RANGE 5 +#define ISNS_ATTR_REGISTRATION_PERIOD 6 +#define ISNS_ATTR_ENTITY_INDEX 7 +#define ISNS_ATTR_ENTITY_NEXT_INDEX 8 +#define ISNS_ATTR_ISAKMP_PHASE1 11 +#define ISNS_ATTR_CERTIFICATE 12 +#define ISNS_ATTR_PORTAL_IP_ADDRESS 16 +#define ISNS_ATTR_PORTAL_PORT 17 +#define ISNS_ATTR_PORTAL_SYMBOLIC_NAME 18 +#define ISNS_ATTR_ESI_INTERVAL 19 +#define ISNS_ATTR_ESI_PORT 20 +#define ISNS_ATTR_PORTAL_INDEX 22 +#define ISNS_ATTR_SCN_PORT 23 +#define ISNS_ATTR_PORTAL_NEXT_INDEX 24 +#define ISNS_ATTR_PORTAL_SECURITY_BITMAP 27 +#define ISNS_ATTR_PORTAL_ISAKMP_PHASE1 28 +#define ISNS_ATTR_PORTAL_ISAKMP_PHASE2 29 +#define ISNS_ATTR_PORTAL_CERTIFICATE 31 +#define ISNS_ATTR_ISCSI_NAME 32 +#define ISNS_ATTR_ISCSI_NODE_TYPE 33 +#define ISNS_ATTR_ISCSI_ALIAS 34 +#define ISNS_ATTR_ISCSI_SCN_BITMAP 35 +#define ISNS_ATTR_ISCSI_NODE_INDEX 36 +#define ISNS_ATTR_WWNN_TOKEN 37 +#define ISNS_ATTR_ISCSI_NODE_NEXT_INDEX 38 +#define ISNS_ATTR_ISCSI_AUTHMETHOD 42 +#define ISNS_ATTR_PG_ISCSI_NAME 48 +#define ISNS_ATTR_PG_PORTAL_IP_ADDRESS 49 +#define ISNS_ATTR_PG_PORTAL_PORT 50 +#define ISNS_ATTR_PG_TAG 51 +#define ISNS_ATTR_PG_INDEX 52 +#define ISNS_ATTR_PG_NEXT_INDEX 53 +#define ISNS_ATTR_FC_PORT_NAME_WWPN 64 +#define ISNS_ATTR_PORT_ID 65 +#define ISNS_ATTR_PORT_TYPE 66 +#define ISNS_ATTR_SYMBOLIC_PORT_NAME 67 +#define ISNS_ATTR_FABRIC_PORT_NAME 68 +#define ISNS_ATTR_HARD_ADDRESS 69 +#define ISNS_ATTR_PORT_IP_ADDRESS 70 +#define ISNS_ATTR_CLASS_OF_SERVICE 71 +#define ISNS_ATTR_FC4_TYPES 72 +#define ISNS_ATTR_FC4_DESCRIPOTR 73 +#define ISNS_ATTR_FC4_FEATURES 74 +#define ISNS_ATTR_IFCP_SCN_BITMAP 75 +#define ISNS_ATTR_PORT_ROLE 76 +#define ISNS_ATTR_PERMANENT_PORT_NAME 77 +#define ISNS_ATTR_FC4_TYPE_CODE 95 +#define ISNS_ATTR_FC_NODE_NAME_WWNN 96 +#define ISNS_ATTR_SYMBOLIC_NODE_NAME 97 +#define ISNS_ATTR_NODE_IP_ADDRESS 98 +#define ISNS_ATTR_NODE_IPA 99 +#define ISNS_ATTR_PORXY_ISCSI_NAME 101 +#define ISNS_ATTR_SWITCH_NAME 128 +#define ISNS_ATTR_PREFERRED_ID 129 +#define ISNS_ATTR_ASSIGNED_ID 130 +#define ISNS_ATTR_VIRTUAL_FABRIC_ID 131 +#define ISNS_ATTR_ISNS_SERVER_VENDOR_OUI 256 +#define ISNS_ATTR_DD_SET_ID 2049 +#define ISNS_ATTR_DD_SET_SYM_NAME 2050 +#define ISNS_ATTR_DD_SET_STATUS 2051 +#define ISNS_ATTR_DD_SET_NEXT_ID 2052 +#define ISNS_ATTR_DD_ID 2065 +#define ISNS_ATTR_DD_SYMBOLIC_NAME 2066 +#define ISNS_ATTR_DD_MEMBER_ISCSI_INDEX 2067 +#define ISNS_ATTR_DD_MEMBER_ISCSI_NAME 2068 +#define ISNS_ATTR_DD_MEMBER_FC_PORT_NAME 2069 +#define ISNS_ATTR_DD_MEMBER_PORTAL_INDEX 2070 +#define ISNS_ATTR_DD_MEMBER_IP_ADDR 2071 +#define ISNS_ATTR_DD_MEMBER_TCP_UDP 2072 +#define ISNS_ATTR_DD_FEATURES 2078 +#define ISNS_ATTR_DD_ID_NEXT_ID 2079 + +/* SCN flags (6.4.4) */ +#define ISNS_SCN_FLAG_INITIATOR (1U << 24) +#define ISNS_SCN_FLAG_TARGET (1U << 25) +#define ISNS_SCN_FLAG_MANAGEMENT (1U << 26) +#define ISNS_SCN_FLAG_OBJECT_REMOVE (1U << 27) +#define ISNS_SCN_FLAG_OBJECT_ADDED (1U << 28) +#define ISNS_SCN_FLAG_OBJECT_UPDATED (1U << 29) +#define ISNS_SCN_FLAG_DD_REMOVED (1U << 30) +#define ISNS_SCN_FLAG_DD_ADDED (1U << 31) +#endif diff --git a/iscsi-scst/usr/log.c b/iscsi-scst/usr/log.c new file mode 100644 index 000000000..fd58cac42 --- /dev/null +++ b/iscsi-scst/usr/log.c @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2002-2003 Ardis Technolgies + * + * Released under the terms of the GNU GPL v2.0. + */ + +#include +#include +#include +#include +#include + +#include "iscsid.h" + +int log_daemon = 1; +int log_level = 0; + +void log_init(void) +{ + if (log_daemon) + openlog("iscsi-scstd", 0, LOG_DAEMON); +} + +static void dolog(int prio, const char *fmt, va_list ap) +{ + if (log_daemon) + vsyslog(prio, fmt, ap); + else { + struct timeval time; + + gettimeofday(&time, NULL); + fprintf(stderr, "%ld.%06ld: ", time.tv_sec, time.tv_usec); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + fflush(stderr); + } +} + +void log_warning(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + dolog(LOG_WARNING, fmt, ap); + va_end(ap); +} + +void log_error(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + dolog(LOG_ERR, fmt, ap); + va_end(ap); +} + +void log_debug(int level, const char *fmt, ...) +{ + if (log_level > level) { + va_list ap; + va_start(ap, fmt); + dolog(LOG_DEBUG, fmt, ap); + va_end(ap); + } +} + +static void __dump_line(int level, unsigned char *buf, int *cp) +{ + char line[16*3+5], *lp = line; + int i, cnt; + + cnt = *cp; + if (!cnt) + return; + for (i = 0; i < 16; i++) { + if (i < cnt) + lp += sprintf(lp, " %02x", buf[i]); + else + lp += sprintf(lp, " "); + if ((i % 4) == 3) + lp += sprintf(lp, " |"); + if (i >= cnt || !isprint(buf[i])) + buf[i] = ' '; + } + log_debug(level, "%s %.16s |", line, buf); + *cp = 0; +} + +static void __dump_char(int level, unsigned char *buf, int *cp, int ch) +{ + int cnt = (*cp)++; + + buf[cnt] = ch; + if (cnt == 15) + __dump_line(level, buf, cp); +} + +#define dump_line() __dump_line(level, char_buf, &char_cnt) +#define dump_char(ch) __dump_char(level, char_buf, &char_cnt, ch) + +void log_pdu(int level, struct PDU *pdu) +{ + unsigned char char_buf[16]; + int char_cnt = 0; + unsigned char *buf; + int i; + return; + + if (log_level <= level) + return; + + buf = (void *)&pdu->bhs; + log_debug(level, "BHS: (%p)", buf); + for (i = 0; i < BHS_SIZE; i++) + dump_char(*buf++); + dump_line(); + + buf = (void *)pdu->ahs; + log_debug(level, "AHS: (%p)", buf); + for (i = 0; i < pdu->ahssize; i++) + dump_char(*buf++); + dump_line(); + + buf = (void *)pdu->data; + log_debug(level, "Data: (%p)", buf); + for (i = 0; i < pdu->datasize; i++) + dump_char(*buf++); + dump_line(); +} diff --git a/iscsi-scst/usr/message.c b/iscsi-scst/usr/message.c new file mode 100644 index 000000000..3e3c5371d --- /dev/null +++ b/iscsi-scst/usr/message.c @@ -0,0 +1,172 @@ +/* + * (C) 2004 - 2005 FUJITA Tomonori + * + * This code is licenced under the GPL. + */ + +#include +#include +#include + +#include +#include +#include + +#include "iscsid.h" +#include "iscsi_adm.h" + +int iscsi_adm_request_listen(void) +{ + int fd, err; + struct sockaddr_un addr; + + fd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (fd < 0) + return fd; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_LOCAL; + memcpy((char *) &addr.sun_path + 1, ISCSI_ADM_NAMESPACE, + strlen(ISCSI_ADM_NAMESPACE)); + + if ((err = bind(fd, (struct sockaddr *) &addr, sizeof(addr))) < 0) + return err; + + if ((err = listen(fd, 32)) < 0) + return err; + + return fd; +} + +static void iscsi_adm_request_exec(struct iscsi_adm_req *req, struct iscsi_adm_rsp *rsp) +{ + int err = 0; + + log_debug(1, "%u %u %" PRIu64 " %u %u", req->rcmnd, req->tid, + req->sid, req->cid, req->lun); + + switch (req->rcmnd) { + case C_TRGT_NEW: + err = cops->target_add(&req->tid, req->u.trgt.name); + break; + case C_TRGT_DEL: + err = cops->target_del(req->tid); + break; + case C_TRGT_UPDATE: + if (req->u.trgt.type & (1 << key_session)) + err = cops->param_set(req->tid, req->sid, + key_session, + req->u.trgt.session_partial, + req->u.trgt.session_param); + + if (err < 0) + goto out; + + if (req->u.trgt.type & (1 << key_target)) + err = cops->param_set(req->tid, req->sid, key_target, + req->u.trgt.target_partial, + req->u.trgt.target_param); + break; + case C_TRGT_SHOW: + err = ki->param_get(req->tid, req->sid, key_target, + req->u.trgt.target_param, 0); + break; + + case C_SESS_NEW: + case C_SESS_DEL: + case C_SESS_UPDATE: + break; + case C_SESS_SHOW: + err = ki->param_get(req->tid, req->sid, key_session, + req->u.trgt.session_param, 0); + break; + + case C_CONN_NEW: + case C_CONN_DEL: + conn_blocked = 1; + err = ki->conn_destroy(req->tid, req->sid, req->cid); + sleep(1); + conn_blocked = 0; + break; + case C_CONN_UPDATE: + case C_CONN_SHOW: + break; + + case C_ACCT_NEW: + err = cops->account_add(req->tid, req->u.acnt.auth_dir, req->u.acnt.user, + req->u.acnt.pass); + break; + case C_ACCT_DEL: + err = cops->account_del(req->tid, req->u.acnt.auth_dir, req->u.acnt.user); + break; + case C_ACCT_UPDATE: + case C_ACCT_SHOW: + break; + case C_SYS_NEW: + break; + case C_SYS_DEL: + err = server_stop(); + break; + case C_SYS_UPDATE: + case C_SYS_SHOW: + break; + default: + break; + } + +out: + rsp->err = err; +} + +int iscsi_adm_request_handle(int accept_fd) +{ + struct sockaddr addr; + struct ucred cred; + int fd, err; + socklen_t len; + struct iscsi_adm_req req; + struct iscsi_adm_rsp rsp; + struct iovec iov[2]; + + memset(&rsp, 0, sizeof(rsp)); + len = sizeof(addr); + if ((fd = accept(accept_fd, (struct sockaddr *) &addr, &len)) < 0) { + if (errno == EINTR) + err = -EINTR; + else + err = -EIO; + + goto out; + } + + len = sizeof(cred); + if ((err = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, (void *) &cred, &len)) < 0) { + rsp.err = -EPERM; + goto send; + } + + if (cred.uid || cred.gid) { + rsp.err = -EPERM; + goto send; + } + + if ((err = read(fd, &req, sizeof(req))) != sizeof(req)) { + if (err >= 0) + err = -EIO; + goto out; + } + + iscsi_adm_request_exec(&req, &rsp); + +send: + iov[0].iov_base = &req; + iov[0].iov_len = sizeof(req); + iov[1].iov_base = &rsp; + iov[1].iov_len = sizeof(rsp); + + err = writev(fd, iov, 2); +out: + if (fd > 0) + close(fd); + return err; +} diff --git a/iscsi-scst/usr/misc.h b/iscsi-scst/usr/misc.h new file mode 100644 index 000000000..c467aabc4 --- /dev/null +++ b/iscsi-scst/usr/misc.h @@ -0,0 +1,62 @@ +/* + * Released under the terms of the GNU GPL v2.0. + */ + +#ifndef MISC_H +#define MISC_H + +struct qelem { + struct qelem *q_forw; + struct qelem *q_back; +}; + +/* stolen list stuff from Linux kernel */ + +#undef offsetof +#ifdef __compiler_offsetof +#define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER) +#else +#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) +#endif + +#define LIST_HEAD_INIT(name) { &(name), &(name) } +#define LIST_HEAD(name) \ + struct qelem name = LIST_HEAD_INIT(name) + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->q_forw = (ptr); (ptr)->q_back = (ptr); \ +} while (0) + +static inline int list_empty(const struct qelem *head) +{ + return head->q_forw == head; +} + +static inline int list_length_is_one(const struct qelem *head) +{ + return head->q_forw == head->q_back; +} + +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +#define list_for_each_entry(pos, head, member) \ + for (pos = list_entry((head)->q_forw, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.q_forw, typeof(*pos), member)) + +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_entry((head)->q_forw, typeof(*pos), member), \ + n = list_entry(pos->member.q_forw, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.q_forw, typeof(*n), member)) + +#ifndef IPV6_V6ONLY +#define IPV6_V6ONLY 26 +#endif + +#endif diff --git a/iscsi-scst/usr/param.c b/iscsi-scst/usr/param.c new file mode 100644 index 000000000..27249c6db --- /dev/null +++ b/iscsi-scst/usr/param.c @@ -0,0 +1,304 @@ +/* + * (C) 2005 FUJITA Tomonori + * + * This code is licenced under the GPL. + */ + +#include +#include +#include +#include +#include + +#include "iscsid.h" + +int param_index_by_name(char *name, struct iscsi_key *keys) +{ + int i, err = -ENOENT; + + for (i = 0; keys[i].name; i++) { + if (!strcasecmp(keys[i].name, name)) { + err = i; + break; + } + } + + return err; +} + +void param_set_defaults(struct iscsi_param *params, struct iscsi_key *keys) +{ + int i; + + for (i = 0; keys[i].name; i++) { + if (i == key_max_recv_data_length) + params[i].exec_val = keys[i].local_def; + else + params[i].exec_val = keys[i].rfc_def; + params[i].local_val = keys[i].local_def; + } +} + +static int range_val_to_str(unsigned int val, char *str) +{ + sprintf(str, "%u", val); + return 0; +} + +static int range_str_to_val(char *str, unsigned int *val) +{ + *val = strtol(str, NULL, 0); + return 0; +} + +static int bool_val_to_str(unsigned int val, char *str) +{ + int err = 0; + + switch (val) { + case 0: + strcpy(str, "No"); + break; + case 1: + strcpy(str, "Yes"); + break; + default: + err = -EINVAL; + } + + return err; +} + +static int bool_str_to_val(char *str, unsigned int *val) +{ + int err = 0; + + if (!strcmp(str, "Yes")) + *val = 1; + else if (!strcmp(str, "No")) + *val = 0; + else + err = -EINVAL; + + return err; +} + +static int or_set_val(struct iscsi_param *param, int idx, unsigned int *val) +{ + *val |= param[idx].local_val; + param[idx].exec_val = *val; + + return 0; +} + +static int and_set_val(struct iscsi_param *param, int idx, unsigned int *val) +{ + *val &= param[idx].local_val; + param[idx].exec_val = *val; + + return 0; +} + +static int num_check_val(struct iscsi_key *key, unsigned int *val) +{ + int err = 0; + + if (*val < key->min) { + *val = key->min; + err = -EINVAL; + } else if (*val > key->max) { + *val = key->max; + err = -EINVAL; + } + + return err; +} + +static int minimum_set_val(struct iscsi_param *param, int idx, unsigned int *val) +{ + if (*val > param[idx].local_val) + *val = param[idx].local_val; + param[idx].exec_val = *val; + return 0; +} + +static int maximum_set_val(struct iscsi_param *param, int idx, unsigned int *val) +{ + if (param[idx].local_val > *val) + *val = param[idx].local_val; + param[idx].exec_val = *val; + return 0; +} + +static int digest_val_to_str(unsigned int val, char *str) +{ + int err = 0; + + if (val & DIGEST_CRC32C) + strcpy(str, "CRC32C"); + else if (val & DIGEST_NONE) + strcpy(str, "None"); + else + err = -EINVAL; + + return err; +} + +static int digest_str_to_val(char *str, unsigned int *val) +{ + int err = 0; + char *p, *q; + p = str; + + *val = DIGEST_NONE; + do { + if (!strncmp(p, "None", strlen("None"))) + *val |= DIGEST_NONE; + else if (!strncmp(p, "CRC32C", strlen("CRC32C"))) + *val |= DIGEST_CRC32C; + else { + err = -EINVAL; + break; + } + + if ((q = strchr(p, ','))) + p = q + 1; + } while (q); + + return err; +} + +static int digest_set_val(struct iscsi_param *param, int idx, unsigned int *val) +{ + if (*val & DIGEST_CRC32C && param[idx].local_val & DIGEST_CRC32C) + *val = DIGEST_CRC32C; + else + *val = DIGEST_NONE; + + param[idx].exec_val = *val; + + return 0; +} + +static int marker_val_to_str(unsigned int val, char *str) +{ + if (val == 0) + strcpy(str, "Irrelevant"); + else + strcpy(str, "Reject"); + + return 0; +} + +static int marker_set_val(struct iscsi_param *param, int idx, unsigned int *val) +{ + if ((idx == key_ofmarkint && param[key_ofmarker].state == KEY_STATE_DONE) || + (idx == key_ifmarkint && param[key_ifmarker].state == KEY_STATE_DONE)) + *val = 0; + else + *val = 1; + + param[idx].exec_val = *val; + + return 0; +} + +int param_val_to_str(struct iscsi_key *keys, int idx, unsigned int val, char *str) +{ + if (keys[idx].ops->val_to_str) + return keys[idx].ops->val_to_str(val, str); + else + return 0; +} + +int param_str_to_val(struct iscsi_key *keys, int idx, char *str, unsigned int *val) +{ + if (keys[idx].ops->str_to_val) + return keys[idx].ops->str_to_val(str, val); + else + return 0; +} + +int param_check_val(struct iscsi_key *keys, int idx, unsigned int *val) +{ + if (keys[idx].ops->check_val) + return keys[idx].ops->check_val(&keys[idx], val); + else + return 0; +} + +int param_set_val(struct iscsi_key *keys, struct iscsi_param *param, + int idx, unsigned int *val) +{ + if (keys[idx].ops->set_val) + return keys[idx].ops->set_val(param, idx, val); + else + return 0; +} + +static struct iscsi_key_ops minimum_ops = { + .val_to_str = range_val_to_str, + .str_to_val = range_str_to_val, + .check_val = num_check_val, + .set_val = minimum_set_val, +}; + +static struct iscsi_key_ops maximum_ops = { + .val_to_str = range_val_to_str, + .str_to_val = range_str_to_val, + .check_val = num_check_val, + .set_val = maximum_set_val, +}; + +static struct iscsi_key_ops or_ops = { + .val_to_str = bool_val_to_str, + .str_to_val = bool_str_to_val, + .set_val = or_set_val, +}; + +static struct iscsi_key_ops and_ops = { + .val_to_str = bool_val_to_str, + .str_to_val = bool_str_to_val, + .set_val = and_set_val, +}; + +static struct iscsi_key_ops digest_ops = { + .val_to_str = digest_val_to_str, + .str_to_val = digest_str_to_val, + .set_val = digest_set_val, +}; + +static struct iscsi_key_ops marker_ops = { + .val_to_str = marker_val_to_str, + .set_val = marker_set_val, +}; + +#define SET_KEY_VALUES(x) DEFAULT_NR_##x,DEFAULT_NR_##x,MIN_NR_##x,MAX_NR_##x + +struct iscsi_key target_keys[] = { + {"QueuedCommands", SET_KEY_VALUES(QUEUED_CMNDS), &minimum_ops}, + {NULL,}, +}; + +struct iscsi_key session_keys[] = { + {"InitialR2T", 1, 0, 0, 1, &or_ops}, + {"ImmediateData", 1, 1, 0, 1, &and_ops}, + {"MaxConnections", 1, 1, 1, 1, &minimum_ops}, + {"MaxRecvDataSegmentLength", 8192, MAX_DATA_SEG_LEN, 512, MAX_DATA_SEG_LEN, &minimum_ops}, + {"MaxXmitDataSegmentLength", 8192, MAX_DATA_SEG_LEN, 512, MAX_DATA_SEG_LEN, &minimum_ops}, + {"MaxBurstLength", 262144, MAX_DATA_SEG_LEN, 512, MAX_DATA_SEG_LEN, &minimum_ops}, + {"FirstBurstLength", 65536, MAX_DATA_SEG_LEN, 512, MAX_DATA_SEG_LEN, &minimum_ops}, + {"DefaultTime2Wait", 2, 2, 0, 3600, &maximum_ops}, + {"DefaultTime2Retain", 20, 20, 0, 3600, &minimum_ops}, + {"MaxOutstandingR2T", 1, 20, 1, 65535, &minimum_ops}, + {"DataPDUInOrder", 1, 0, 0, 1, &or_ops}, + {"DataSequenceInOrder", 1, 0, 0, 1, &or_ops}, + {"ErrorRecoveryLevel", 0, 0, 0, 0, &minimum_ops}, + {"HeaderDigest", DIGEST_NONE, DIGEST_NONE, DIGEST_NONE, DIGEST_ALL, &digest_ops}, + {"DataDigest", DIGEST_NONE, DIGEST_NONE, DIGEST_NONE, DIGEST_ALL, &digest_ops}, + {"OFMarker", 0, 0, 0, 0, &and_ops}, + {"IFMarker", 0, 0, 0, 0, &and_ops}, + {"OFMarkInt", 2048, 2048, 1, 65535, &marker_ops}, + {"IFMarkInt", 2048, 2048, 1, 65535, &marker_ops}, + {NULL,}, +}; diff --git a/iscsi-scst/usr/param.h b/iscsi-scst/usr/param.h new file mode 100644 index 000000000..1075c1aac --- /dev/null +++ b/iscsi-scst/usr/param.h @@ -0,0 +1,44 @@ +/* + * (C) 2005 FUJITA Tomonori + * + * This code is licenced under the GPL. + */ + +#ifndef PARAMS_H +#define PARAMS_H + +struct iscsi_key; + +struct iscsi_param { + int state; + unsigned int exec_val; + unsigned int local_val; +}; + +struct iscsi_key_ops { + int (*val_to_str)(unsigned int, char *); + int (*str_to_val)(char *, unsigned int *); + int (*check_val)(struct iscsi_key *, unsigned int *); + int (*set_val)(struct iscsi_param *, int, unsigned int *); +}; + +struct iscsi_key { + char *name; + unsigned int rfc_def; + unsigned int local_def; + unsigned int min; + unsigned int max; + struct iscsi_key_ops *ops; +}; + +extern struct iscsi_key session_keys[]; +extern struct iscsi_key target_keys[]; + +extern void param_set_defaults(struct iscsi_param *, struct iscsi_key *); +extern int param_index_by_name(char *, struct iscsi_key *); +extern int param_val_to_str(struct iscsi_key *, int, unsigned int, char *); +extern int param_str_to_val(struct iscsi_key *, int, char *, unsigned int *); +extern int param_check_val(struct iscsi_key *, int, unsigned int *); +extern int param_set_val(struct iscsi_key *, struct iscsi_param *, int, unsigned int *); + +#endif diff --git a/iscsi-scst/usr/plain.c b/iscsi-scst/usr/plain.c new file mode 100644 index 000000000..2f618db04 --- /dev/null +++ b/iscsi-scst/usr/plain.c @@ -0,0 +1,609 @@ +/* + * Plain file-based configuration file code. + * + * (C) 2005 FUJITA Tomonori + * This code is licenced under the GPL. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "iscsid.h" + +#define BUFSIZE 4096 +#define CONFIG_FILE "/etc/iscsi-scstd.conf" +#define ACCT_CONFIG_FILE CONFIG_FILE + +/* + * Account configuration code + */ + +struct user { + struct qelem ulist; + + u32 tid; + char *name; + char *password; +}; + +/* this is the orignal Ardis code. */ +static char *target_sep_string(char **pp) +{ + char *p = *pp; + char *q; + + for (p = *pp; isspace(*p); p++) + ; + for (q = p; *q && !isspace(*q); q++) + ; + if (*q) + *q++ = 0; + else + p = NULL; + *pp = q; + return p; +} + +static struct iscsi_key user_keys[] = { + {"IncomingUser",}, + {"OutgoingUser",}, + {NULL,}, +}; + +static struct qelem discovery_users_in = LIST_HEAD_INIT(discovery_users_in); +static struct qelem discovery_users_out = LIST_HEAD_INIT(discovery_users_out); + +#define HASH_ORDER 4 +#define acct_hash(x) ((x) & ((1 << HASH_ORDER) - 1)) + +static struct qelem trgt_acct_in[1 << HASH_ORDER]; +static struct qelem trgt_acct_out[1 << HASH_ORDER]; + +static struct qelem *account_list_get(u32 tid, int dir) +{ + struct qelem *list = NULL; + + if (tid) { + list = (dir == AUTH_DIR_INCOMING) ? + &trgt_acct_in[acct_hash(tid)] : &trgt_acct_out[acct_hash(tid)]; + } else + list = (dir == AUTH_DIR_INCOMING) ? + &discovery_users_in : &discovery_users_out; + + return list; +} + +static int plain_account_init(char *filename) +{ + FILE *fp; + char buf[BUFSIZE], *p, *q; + u32 tid; + int idx; + + if (!(fp = fopen(filename, "r"))) + return -EIO; + + tid = 0; + while (fgets(buf, sizeof(buf), fp)) { + q = buf; + p = target_sep_string(&q); + if (!p || *p == '#') + continue; + + if (!strcasecmp(p, "Target")) { + tid = 0; + if (!(p = target_sep_string(&q))) + continue; + tid = target_find_by_name(p); + } else if (!((idx = param_index_by_name(p, user_keys)) < 0)) { + char *name, *pass; + name = target_sep_string(&q); + pass = target_sep_string(&q); + + if (cops->account_add(tid, idx, name, pass) < 0) + fprintf(stderr, "%s %s\n", name, pass); + } + } + + fclose(fp); + + return 0; +} + +/* Return the first account if the length of name is zero */ +static struct user *account_lookup_by_name(u32 tid, int dir, char *name) +{ + struct qelem *list = account_list_get(tid, dir); + struct user *user = NULL; + + list_for_each_entry(user, list, ulist) { + fprintf(stderr, "%u %s %s\n", user->tid, user->password, user->name); + if (user->tid != tid) + continue; + if (!strlen(name)) + return user; + if (!strcmp(user->name, name)) + return user; + } + + return NULL; +} + +static int plain_account_query(u32 tid, int dir, char *name, char *pass) +{ + struct user *user; + + if (!(user = account_lookup_by_name(tid, dir, name))) + return -ENOENT; + + if (!strlen(name)) + strncpy(name, user->name, ISCSI_NAME_LEN); + + strncpy(pass, user->password, ISCSI_NAME_LEN); + + return 0; +} + +static void account_destroy(struct user *user) +{ + if (!user) + return; + remque(&user->ulist); + free(user->name); + free(user->password); + free(user); +} + +static int plain_account_del(u32 tid, int dir, char *name) +{ + struct user *user; + + if (!name || !(user = account_lookup_by_name(tid, dir, name))) + return -ENOENT; + + account_destroy(user); + + /* update the file here. */ + return 0; +} + +static struct user *account_create(void) +{ + struct user *user; + + if (!(user = malloc(sizeof(*user)))) + return NULL; + + memset(user, 0, sizeof(*user)); + INIT_LIST_HEAD(&user->ulist); + + return user; +} + +static int plain_account_add(u32 tid, int dir, char *name, char *pass) +{ + int err = -ENOMEM; + struct user *user; + struct qelem *list; + + if (!name || !pass) + return -EINVAL; + + if (tid) { + /* check here */ +/* return -ENOENT; */ + } + + /* Check for minimum RFC defined value */ + if (strlen(pass) < 12) { + log_error("Secret for user %s is too short. At least 12 bytes " + "are required\n", name); + return -EINVAL; + } + + if (!(user = account_create()) || + !(user->name = strdup(name)) || + !(user->password = strdup(pass))) + goto out; + + user->tid = tid; + list = account_list_get(tid, dir); + + if (dir == AUTH_DIR_OUTGOING && !list_empty(list)) { + struct user *old; + log_warning("Only one outgoing %s account is supported." + " Replacing the old one.\n", + tid ? "target" : "discovery"); + + old = (struct user *) list->q_forw; + account_destroy(old); + } + + insque(user, list); + + /* update the file here. */ + return 0; +out: + account_destroy(user); + + return err; +} + +/* + * Access control code + */ + +static int netmask_match_v6(struct sockaddr *sa1, struct sockaddr *sa2, uint32_t mbit) +{ + uint16_t mask, a1[8], a2[8]; + int i; + + for (i = 0; i < 8; i++) { + a1[i] = ntohs(((struct sockaddr_in6 *) sa1)->sin6_addr.s6_addr16[i]); + a2[i] = ntohs(((struct sockaddr_in6 *) sa2)->sin6_addr.s6_addr16[i]); + } + + for (i = 0; i < mbit / 16; i++) + if (a1[i] ^ a2[i]) + return 0; + + if (mbit % 16) { + mask = ~((1 << (16 - (mbit % 16))) - 1); + if ((mask & a1[mbit / 16]) ^ (mask & a2[mbit / 16])) + return 0; + } + + return 1; +} + +static int netmask_match_v4(struct sockaddr *sa1, struct sockaddr *sa2, uint32_t mbit) +{ + uint32_t s1, s2, mask = ~((1 << (32 - mbit)) - 1); + + s1 = htonl(((struct sockaddr_in *) sa1)->sin_addr.s_addr); + s2 = htonl(((struct sockaddr_in *) sa2)->sin_addr.s_addr); + + if (~mask & s1) + return 0; + + if (!((mask & s2) ^ (mask & s1))) + return 1; + + return 0; +} + +static int netmask_match(struct sockaddr *sa1, struct sockaddr *sa2, char *buf) +{ + uint32_t mbit; + uint8_t family = sa1->sa_family; + + mbit = strtoul(buf, NULL, 0); + if (mbit < 0 || + (family == AF_INET && mbit > 31) || + (family == AF_INET6 && mbit > 127)) + return 0; + + if (family == AF_INET) + return netmask_match_v4(sa1, sa2, mbit); + + return netmask_match_v6(sa1, sa2, mbit); +} + +static int address_match(struct sockaddr *sa1, struct sockaddr *sa2) +{ + if (sa1->sa_family == AF_INET) + return ((struct sockaddr_in *) sa1)->sin_addr.s_addr == + ((struct sockaddr_in *) sa2)->sin_addr.s_addr; + else { + struct in6_addr *a1, *a2; + + a1 = &((struct sockaddr_in6 *) sa1)->sin6_addr; + a2 = &((struct sockaddr_in6 *) sa2)->sin6_addr; + + return (a1->s6_addr32[0] == a2->s6_addr32[0] && + a1->s6_addr32[1] == a2->s6_addr32[1] && + a1->s6_addr32[2] == a2->s6_addr32[2] && + a1->s6_addr32[3] == a2->s6_addr32[3]); + } + + return 0; +} + +static int __initiator_match(int fd, char *str) +{ + struct sockaddr_storage from; + struct addrinfo hints, *res; + socklen_t len; + char *p, *q; + int err = 0; + + len = sizeof(from); + if (getpeername(fd, (struct sockaddr *) &from, &len) < 0) + return 0; + + while ((p = strsep(&str, ","))) { + while (isspace(*p)) + p++; + + if (!strcmp(p, "ALL")) + return 1; + + if (*p == '[') { + p++; + if (!(q = strchr(p, ']'))) + return 0; + *(q++) = '\0'; + } else + q = p; + + if ((q = strchr(q, '/'))) + *(q++) = '\0'; + + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_NUMERICHOST; + + if (getaddrinfo(p, NULL, &hints, &res) < 0) + return 0; + + if (q) + err = netmask_match(res->ai_addr, + (struct sockaddr *) &from, q); + else + err = address_match(res->ai_addr, + (struct sockaddr *) &from); + + freeaddrinfo(res); + + if (err) + break; + } + + return err; +} + +static int initiator_match(u32 tid, int fd, char *filename) +{ + int err = 0; + FILE *fp; + char buf[BUFSIZE], *p; + + if (!(fp = fopen(filename, "r"))) + return err; + + /* + * Every time we are called, we read the file. So we don't need to + * implement 'reload feature'. It's slow, however, it doesn't matter. + */ + while ((p = fgets(buf, sizeof(buf), fp))) { + if (!p || *p == '#') + continue; + + p = &buf[strlen(buf) - 1]; + if (*p != '\n') + continue; + *p = '\0'; + + if (!(p = strchr(buf, ' '))) + continue; + *(p++) = '\0'; + + if (target_find_by_name(buf) != tid && strcmp(buf, "ALL")) + continue; + + err = __initiator_match(fd, p); + break; + } + + fclose(fp); + return err; +} + +static int plain_initiator_access(u32 tid, int fd) +{ + if (initiator_match(tid, fd, "/etc/initiators.deny") && + !initiator_match(tid, fd, "/etc/initiators.allow")) + return -EPERM; + else + return 0; +} + +/* + * Main configuration code + */ + +static int __plain_target_create(u32 *tid, char *name, int update) +{ + int err; + struct iscsi_param params[session_key_last]; + + if (target_find_by_name(name)) { + log_error("duplicated target %s", name); + return -EINVAL; + } + if ((err = target_add(tid, name)) < 0) + return err; + + param_set_defaults(params, session_keys); + if ((err = ki->param_set(*tid, 0, key_session, 0, params, 1)) < 0) + return err; + + if (update) + ; /* Update the config file here. */ + + return err; +} + +static int plain_target_create(u32 *tid, char *name) +{ + return __plain_target_create(tid, name, 1); +} + +static int plain_target_destroy(u32 tid) +{ + int err; + + if ((err = target_del(tid)) < 0) + return err; + + /* Update the config file here. */ + return err; +} + +static int __plain_param_set(u32 tid, u64 sid, int type, + u32 partial, struct iscsi_param *param, int update) +{ + int err; + + if ((err = ki->param_set(tid, sid, type, partial, param, 1)) < 0) + return err; + + if (update) + ; + + return err; +} + +static int plain_param_set(u32 tid, u64 sid, int type, + u32 partial, struct iscsi_param *param) +{ + return __plain_param_set(tid, sid, type, partial, param, 1); +} + +static int iscsi_param_partial_set(u32 tid, u64 sid, int type, int key, u32 val) +{ + struct iscsi_param *param; + struct iscsi_param session_param[session_key_last]; + struct iscsi_param target_param[target_key_last]; + + if (type == key_session) + param = session_param; + else + param = target_param; + + param[key].local_val = val; + + return __plain_param_set(tid, sid, type, 1 << key, param, 0); +} + +static int plain_main_init(char *filename) +{ + FILE *config; + char buf[BUFSIZE]; + char *p, *q; + int idx; + u32 tid, val; + + if (!(config = fopen(filename, "r"))) + return -errno; + + tid = 0; + while (fgets(buf, BUFSIZE, config)) { + q = buf; + p = target_sep_string(&q); + if (!p || *p == '#') + continue; + if (!strcasecmp(p, "Target")) { + tid = 0; + if (!(p = target_sep_string(&q))) + continue; + if (__plain_target_create(&tid, p, 0)) + log_debug(1, "creating target %s", p); + } else if (!strcasecmp(p, "Alias") && tid) { + ; + } else if (!((idx = param_index_by_name(p, target_keys)) < 0) && tid) { + val = strtol(q, &q, 0); + if (param_check_val(target_keys, idx, &val) < 0) + log_warning("Wrong value %u for parameter %s\n", + val, target_keys[idx].name); + iscsi_param_partial_set(tid, 0, key_target, idx, val); + } else if (!((idx = param_index_by_name(p, session_keys)) < 0) && tid) { + char *str = target_sep_string(&q); + if (param_str_to_val(session_keys, idx, str, &val) < 0) { + log_warning("Wrong value %s for parameter %s\n", + str, session_keys[idx].name); + continue; + } + if (param_check_val(session_keys, idx, &val) < 0) + log_warning("Wrong value %u for parameter %s\n", + val, session_keys[idx].name); + iscsi_param_partial_set(tid, 0, key_session, idx, val); + } else if (param_index_by_name(p, user_keys) < 0) + log_warning("Unknown iscsi-scstd.conf param: %s\n", p); + } + + fclose(config); + return 0; +} + +static int plain_default_load(char *params) +{ + int i, err; + + for (i = 0; i < 1 << HASH_ORDER; i++) { + INIT_LIST_HEAD(&trgt_acct_in[i]); + INIT_LIST_HEAD(&trgt_acct_out[i]); + } + + /* First, we must finish the main configuration. */ + if ((err = plain_main_init(params ? params : CONFIG_FILE))) + return err; + + if ((err = plain_account_init(ACCT_CONFIG_FILE)) < 0) + return err; + + /* TODO: error handling */ + + return err; +} + +static int plain_init(char *params, char **isns, int *isns_ac) +{ + FILE *config; + char buf[BUFSIZE]; + char *p, *q; + + if (!(config = fopen(params ? : CONFIG_FILE, "r"))) + return -errno; + + while (fgets(buf, BUFSIZE, config)) { + q = buf; + p = target_sep_string(&q); + if (!p || *p == '#') + continue; + if (!strcasecmp(p, "iSNSServer")) { + *isns = strdup(target_sep_string(&q)); + } else if (!strcasecmp(p, "iSNSAccessControl")) { + char *str = target_sep_string(&q); + if (!strcasecmp(str, "Yes")) + *isns_ac = 1; + } + } + + fclose(config); + return 0; +} + +struct config_operations plain_ops = { + .init = plain_init, + .default_load = plain_default_load, + .target_add = plain_target_create, + .target_del = plain_target_destroy, + .param_set = plain_param_set, + .account_add = plain_account_add, + .account_del = plain_account_del, + .account_query = plain_account_query, + .initiator_access = plain_initiator_access, +}; diff --git a/iscsi-scst/usr/session.c b/iscsi-scst/usr/session.c new file mode 100644 index 000000000..e4b75b67d --- /dev/null +++ b/iscsi-scst/usr/session.c @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2002-2003 Ardis Technolgies + * + * Released under the terms of the GNU GPL v2.0. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "iscsid.h" + +static struct session *session_alloc(u32 tid) +{ + struct session *session; + struct target *target = target_find_by_id(tid); + + if (!target) + return NULL; + if (!(session = malloc(sizeof(*session)))) + return NULL; + memset(session, 0, sizeof(*session)); + + session->target = target; + INIT_LIST_HEAD(&session->slist); + insque(&session->slist, &target->sessions_list); + + return session; +} + +struct session *session_find_name(u32 tid, const char *iname, union iscsi_sid sid) +{ + struct session *session; + struct target *target; + + if (!(target = target_find_by_id(tid))) + return NULL; + + log_debug(1, "session_find_name: %s %#" PRIx64, iname, sid.id64); + list_for_each_entry(session, &target->sessions_list, slist) { + if (!memcmp(sid.id.isid, session->sid.id.isid, 6) && + !strcmp(iname, session->initiator)) + return session; + } + + return NULL; +} + +struct session *session_find_id(u32 tid, u64 sid) +{ + struct session *session; + struct target *target; + + if (!(target = target_find_by_id(tid))) + return NULL; + + log_debug(1, "session_find_id: %#" PRIx64, sid); + list_for_each_entry(session, &target->sessions_list, slist) { + if (session->sid.id64 == sid) + return session; + } + + return NULL; +} + +static int session_test(u32 t_tid, u64 t_sid) +{ + FILE *f; + char buf[8192], *p; + u32 tid; + u64 sid; + int err = -ENOENT, find = 0; + + if ((f = fopen(PROC_SESSION, "r")) == NULL) { + fprintf(stderr, "Can't open %s\n", PROC_SESSION); + return -errno; + } + + while (fgets(buf, sizeof(buf), f)) { + p = buf; + while (isspace((int) *p)) + p++; + + if (!strncmp(p, "tid:", 4)) { + if (sscanf(p, "tid:%u", &tid) != 1) { + err = -EIO; + goto out; + } + if (tid == t_tid) + find = 1; + else + find = 0; + } else if (!strncmp(p, "sid:", 4)) { + if (!find) + continue; + if (sscanf(p, "sid:%" SCNu64, &sid) != 1) { + err = -EIO; + goto out; + } + + if (sid == t_sid) { + err = 0; + goto out; + } + } + } + +out: + fclose(f); + + return err; +} + +void session_create(struct connection *conn) +{ + struct session *session; + static u16 tsih = 1; + char *user; + + if (!(session = session_alloc(conn->tid))) + return; + + session->sid = conn->sid; + session->sid.id.tsih = tsih; + + while (1) { + int err = session_test(conn->tid, session->sid.id64); + + if (err == -ENOENT) + break; + else if (err < 0) + return; + session->sid.id.tsih++; + } + tsih = session->sid.id.tsih + 1; + + conn->session = session; + conn->session->initiator = strdup(conn->initiator); + + log_debug(1, "session_create: %#" PRIx64, session->sid.id64); + + if (conn->user != NULL) + user = conn->user; + else + user = ""; + + ki->session_create(conn->tid, session->sid.id64, conn->exp_cmd_sn, + conn->max_cmd_sn, session->initiator, user); + ki->param_set(conn->tid, session->sid.id64, key_session, 0, + conn->session_param, 0); +} + +void session_remove(struct session *session) +{ + log_debug(1, "session_remove: %#" PRIx64, session->sid.id64); + + if (!session->sid.id.tsih) + ki->session_destroy(session->target->tid, session->sid.id64); + + if (session->target) { + remque(&session->slist); +/* session->target->nr_sessions--; */ + } + + free(session->initiator); + free(session); +} diff --git a/iscsi-scst/usr/target.c b/iscsi-scst/usr/target.c new file mode 100644 index 000000000..5c33e3e6c --- /dev/null +++ b/iscsi-scst/usr/target.c @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2002-2003 Ardis Technolgies + * + * Released under the terms of the GNU GPL v2.0. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "iscsid.h" + +struct qelem targets_list = LIST_HEAD_INIT(targets_list); + +void target_list_build(struct connection *conn, char *addr, char *name) +{ + struct target *target; + + list_for_each_entry(target, &targets_list, tlist) { + if (name && strcmp(target->name, name)) + continue; + if (cops->initiator_access(target->tid, conn->fd) || + isns_scn_access(target->tid, conn->fd, conn->initiator)) + continue; + + text_key_add(conn, "TargetName", target->name); + text_key_add(conn, "TargetAddress", addr); + } +} + +u32 target_find_by_name(const char *name) +{ + struct target *target; + + list_for_each_entry(target, &targets_list, tlist) { + if (!strcasecmp(target->name, name)) + return target->tid; + } + + return 0; +} + +struct target *target_find_by_id(u32 tid) +{ + struct target *target; + + list_for_each_entry(target, &targets_list, tlist) { + if (target->tid == tid) + return target; + } + + return NULL; +} + +static void all_accounts_del(u32 tid, int dir) +{ + char name[ISCSI_NAME_LEN], pass[ISCSI_NAME_LEN]; + + memset(name, 0, sizeof(name)); + + for (;cops->account_query(tid, dir, name, pass) != -ENOENT; + memset(name, 0, sizeof(name))) { + cops->account_del(tid, dir, name); + } + +} + +int target_del(u32 tid) +{ + int err; + struct target* target; + + if (!(target = target_find_by_id(tid))) + return -ENOENT; + + if (target->nr_sessions) + return -EBUSY; + + if ((err = target_destroy(tid)) < 0) + return err; + + remque(&target->tlist); + + if (!list_empty(&target->sessions_list)) { + log_error("%s still have sessions %d\n", __FUNCTION__, tid); + exit(-1); + } + + all_accounts_del(tid, AUTH_DIR_INCOMING); + all_accounts_del(tid, AUTH_DIR_OUTGOING); + + isns_target_deregister(target->name); + free(target); + + return 0; +} + +int target_add(u32 *tid, char *name) +{ + struct target *target; + int err; + + if (!name) + return -EINVAL; + + if (!(target = malloc(sizeof(*target)))) + return -ENOMEM; + + memset(target, 0, sizeof(*target)); + memcpy(target->name, name, sizeof(target->name) - 1); + + if ((err = ki->target_create(tid, name)) < 0) { + log_warning("can't create a target %d %u\n", errno, *tid); + goto out; + } + + INIT_LIST_HEAD(&target->tlist); + INIT_LIST_HEAD(&target->sessions_list); + INIT_LIST_HEAD(&target->isns_head); + target->tid = *tid; + insque(&target->tlist, &targets_list); + + isns_target_register(name); + + return 0; +out: + free(target); + return err; +} diff --git a/iscsi-scst/usr/types.h b/iscsi-scst/usr/types.h new file mode 100644 index 000000000..96d23a886 --- /dev/null +++ b/iscsi-scst/usr/types.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2002-2003 Ardis Technolgies + * + * Released under the terms of the GNU GPL v2.0. + */ + +#ifndef TYPES_H +#define TYPES_H + +#include +#include +#include +#include +#include + +#if __BYTE_ORDER == __BIG_ENDIAN +#define cpu_to_be16(x) (x) +#define be16_to_cpu(x) (x) +#define cpu_to_be32(x) (x) +#define be32_to_cpu(x) (x) +#elif __BYTE_ORDER == __LITTLE_ENDIAN +#define cpu_to_be16(x) bswap_16(x) +#define be16_to_cpu(x) bswap_16(x) +#define cpu_to_be32(x) bswap_32(x) +#define be32_to_cpu(x) bswap_32(x) +#else +#error "unknown endianess!" +#endif + +typedef u_int8_t u8; +typedef u_int16_t u16; +typedef u_int32_t u32; +typedef u_int64_t u64; + +#endif /* TYPES_H */