pax_global_header00006660000000000000000000000064143541727620014525gustar00rootroot0000000000000052 comment=186bdc514c518740cef9bdc03ea5a63a1cd2b2f9 tgt-1.0.85/000077500000000000000000000000001435417276200124165ustar00rootroot00000000000000tgt-1.0.85/.gitignore000066400000000000000000000003231435417276200144040ustar00rootroot00000000000000# # Normal rules # .* *.o *.o.* *.a *.s *.ko *.so *.mod.c *.i *.lst *.symtypes *.d *.orig *.rej # # for GLOBAL # GTAGS GRTAGS GPATH GSYMS # # programs # usr/tgtd usr/tgtadm usr/tgtimg # cscope files cscope.* tgt-1.0.85/LICENSE000066400000000000000000000432541435417276200134330ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser General Public License instead of this License. tgt-1.0.85/Makefile000066400000000000000000000034031435417276200140560ustar00rootroot00000000000000VERSION ?= 1.0.85 CHECK_CC = cgcc CHECK_CC_FLAGS = '$(CHECK_CC) -Wbitwise -Wno-return-void -no-compile $(ARCH)' # Define a common prefix where binaries and docs install PREFIX ?= /usr # Export VERSION and PREFIX so sub-make knows about them export VERSION PREFIX # Export the feature switches so sub-make knows about them export ISCSI_RDMA export CEPH_RBD export GLFS_BD export SD_NOTIFY .PHONY: all all: programs doc conf scripts # Targets for the /usr/sbin utilities .PHONY: programs install-programs clean-programs programs: $(MAKE) -C usr install-programs: $(MAKE) -C usr install clean-programs: $(MAKE) -C usr clean # Targets for man pages and other documentation .PHONY: doc install-doc clean-doc doc: $(MAKE) -C doc install-doc: $(MAKE) -C doc install clean-doc: $(MAKE) -C doc clean # Targets for scripts .PHONY: scripts install-scripts clean-scripts scripts: $(MAKE) -C scripts install-scripts: $(MAKE) -C scripts install clean-scripts: $(MAKE) -C scripts clean # Targets for configuration stubs .PHONY: conf install-conf clean-conf conf: $(MAKE) -C conf install-conf: $(MAKE) -C conf install clean-conf: $(MAKE) -C conf clean .PHONY: install install: install-programs install-doc install-conf install-scripts .PHONY: rpm rpm: @./scripts/build-pkg.sh rpm .PHONY: deb deb: @./scripts/build-pkg.sh deb .PHONY: clean clean-pkg: rm -fr pkg .PHONY: clean clean: clean-programs clean-doc clean-conf clean-scripts clean-pkg .PHONY:check check: ARCH=$(shell sh scripts/checkarch.sh) check: CC=$(CHECK_CC_FLAGS) $(MAKE) all .PHONY:check32 check32: override ARCH=-m32 check32: CC=$(CHECK_CC_FLAGS) $(MAKE) all .PHONY:check64 check64: override ARCH=-m64 check64: CC=$(CHECK_CC_FLAGS) $(MAKE) all cscope: find -name '*.[ch]' > cscope.files cscope -bq tgt-1.0.85/README.md000066400000000000000000000044741435417276200137060ustar00rootroot00000000000000## Introduction The Linux target framework (tgt) is a user space SCSI target framework that supports the iSCSI and iSER transport protocols and that also supports multiple methods for accessing block storage. Tgt consists of user-space daemon and tools. Currently, tgt supports the following SCSI transport protocols: - iSCSI software target driver for Ethernet NICs - iSER software target driver for Infiniband and RDMA NICs Tgt supports the following methods for accessing local storage: - aio, the asynchronous I/O interface also known as libaio. - rdwr, smc and mmc, synchronous I/O based on the pread() and pwrite() system calls. - null, discards all data and reads zeroes. - ssc, SCSI tape support. - sg and bsg, SCSI pass-through. - glfs, the GlusterFS network filesystem. - rbd, Ceph's distributed-storage RADOS Block Device. - sheepdog, a distributed object storage system. Tgt can emulate the following SCSI device types: - SBC: a virtual disk drive that can use a file to store the content. - SMC: a virtual media jukebox that can be controlled by the "mtx" tool. - MMC: a virtual DVD drive that can read DVD-ROM iso files and create burnable DVD+R. It can be combined with SMC to provide a fully operational DVD jukebox. - SSC: a virtual tape device (aka VTL) that can use a file to store the content. - OSD: a virtual object-based storage device that can use a file to store the content (in progress). ## License The code is released under the GNU General Public License version 2. ## Requirements Linux kernel version 2.6.22 or newer is recommended because tgt can get better performance with signalfd. Target drivers have their own ways to build, configure, etc. Please find an appropriate documentation in the doc directory. ## Developer Notes The central communication channel for tgt development is the mailing list (stgt@vger.kernel.org). First, please read the following documents (in short, follow Linux kernel development rules): https://www.kernel.org/doc/Documentation/CodingStyle https://www.kernel.org/doc/Documentation/SubmittingPatches Then, check your patches with the patch style checker prior to submission (scripts/checkpatch.pl) like the following example. fujita@arbre:~/git/tgt$ ./scripts/checkpatch.pl ~/0001-add-bidi-support.patch Your patch has no obvious style problems and is ready for submission. tgt-1.0.85/conf/000077500000000000000000000000001435417276200133435ustar00rootroot00000000000000tgt-1.0.85/conf/Makefile000066400000000000000000000010461435417276200150040ustar00rootroot00000000000000sysconfdir ?= /etc EXAMPLES = targets.conf.example targets.conf.vtl.L700 targets.conf.vtl.MSL2024 .PHONY: all all: .PHONY: install install: install -d -m 755 $(DESTDIR)$(sysconfdir)/tgt if [ ! -f $(DESTDIR)$(sysconfdir)/tgt/targets.conf ] ; then \ install -m 644 targets.conf $(DESTDIR)$(sysconfdir)/tgt ; \ fi install -d -m 755 $(DESTDIR)$(sysconfdir)/tgt/examples for f in $(EXAMPLES) ; do \ install -m 644 examples/$$f $(DESTDIR)$(sysconfdir)/tgt/examples ;\ done install -d $(DESTDIR)$(sysconfdir)/tgt/conf.d .PHONY: clean clean: tgt-1.0.85/conf/examples/000077500000000000000000000000001435417276200151615ustar00rootroot00000000000000tgt-1.0.85/conf/examples/targets.conf.example000066400000000000000000000157361435417276200211470ustar00rootroot00000000000000# This is a sample config file for tgt-admin. # By default, tgt-admin looks for its config file in /etc/tgt/targets.conf # This one includes other config files: include /etc/tgt/temp/*.conf # Set the driver. If not specified, defaults to "iscsi". default-driver iscsi # Set iSNS parameters, if needed #iSNSServerIP 192.168.111.222 #iSNSServerPort 3205 #iSNSAccessControl On #iSNS On # Continue if tgtadm exits with non-zero code (equivalent of # --ignore-errors command line option) #ignore-errors yes # Sample target with one LUN only. Defaults to allow access for all initiators: backing-store /dev/LVM/somedevice # Similar, but we use "direct-store" instead of "backing-store". # "direct-store" reads drive parameters with sg_inq command and sets them to # the target. # Parameters fatched with sg_inq are: # - Vendor identification # - Product identification # - Product revision level # - Unit serial number (if present) # We also specify "incominguser". direct-store /dev/sdd incominguser someuser secretpass12 # An example with multiple LUNs, disabled write-cache (tgtd enables write-cache # by default) and vendor identification set to "MyVendor" backing-store /dev/LVM/somedevice1 # Becomes LUN 1 backing-store /dev/LVM/somedevice2 # Becomes LUN 2 backing-store /dev/LVM/somedevice3 # Becomes LUN 3 write-cache off vendor_id MyCompany Inc. # Similar to the one above, but we fetch vendor_id, product_id, product_rev and # scsi_sn from the disks. # Vendor identification (vendor_id) is replaced in all disks by "MyVendor" direct-store /dev/sdb # Becomes LUN 1 direct-store /dev/sdc # Becomes LUN 2 direct-store /dev/sdd # Becomes LUN 3 write-cache off vendor_id MyCompany Inc. # Note that "first-device-first-lun numbering" will work only for simple # scenarios above, where _only_ direct-store _or_ backing-store is used. # If you mix backing-store and direct-store, then all backing-store entries # are processed before direct-store-entries. direct-store /dev/sdb # Becomes LUN 3 backing-store /dev/sdc # Becomes LUN 1 direct-store /dev/sdd # Becomes LUN 4 backing-store /dev/sde # Becomes LUN 2 # Even more complicated example - each device has different parameters. # You can use indentation to make the config file more readable. # Note that LUNs will be assigned more or less randomly here (and still # backing-store get LUNs assigned before drect-store). # You can specify multiple mode_page parameters (they are commented out # in this example). # Note that some parameters (write-cache, scsi_sn) were specified "globally". # "Global" parameters will be applied to all LUNs; they can be overwritten # "locally", per LUN. # If lun is not specified, it will be allocated automatically (first available). vendor_id VENDOR1 removable 1 device-type cd lun 1 vendor_id VENDOR2 lun 2 vendor_id back1 scsi_sn SERIAL write-cache on # lun 3 # lun is commented out - will be allocated automatically vendor_id back2 #mode_page 8:0:18:0x10:0:0xff.... #mode_page 8:0:18:0x10:0:0xff.... #bs-type aio #params element_type=4,start_address=500,quantity=3,media_home=/root/tapes #params element_type=4,address=500,tid=1,lun=1 lun 15 # Some more parameters which can be specified locally or globally: #scsi_id ... #scsi_sn ... #vendor_id ... #product_id ... #product_rev ... #sense_format ... #removable ... #online ... #readonly ... #path ... #mode_page 8:0:18:0x10:0:0xff.... #mode_page 8:0:18:0x10:0:0xff.... #device-type ... #bs-type ... # backing store type - default rdwr, can be aio, etc... #params element_type=4,start_address=500,quantity=3,media_home=/root/tapes #params element_type=4,address=500,tid=1,lun=1 #allow-in-use yes # if specified globally, can't be overwritten locally write-cache off scsi_sn multipath-10 # Parameters below are only global. They can't be configured per LUN. # Only allow connections from 192.168.100.1 and 192.168.200.5 initiator-address 192.168.100.1 initiator-address 192.168.200.5 # Tuning parameters (global, per target) #MaxRecvDataSegmentLength 8192 #MaxXmitDataSegmentLength 8192 #HeaderDigest None #DataDigest None #InitialR2T Yes #MaxOutstandingR2T 1 #ImmediateData Yes #FirstBurstLength 65536 #MaxBurstLength 262144 #DataPDUInOrder Yes #DataSequenceInOrder Yes #ErrorRecoveryLevel 0 #IFMarker No #OFMarker No #DefaultTime2Wait 2 #DefaultTime2Retain 20 #OFMarkInt Reject #IFMarkInt Reject #MaxConnections 1 # Allowed incoming users incominguser user1 secretpass12 incominguser user2 secretpass23 # Outgoing user outgoinguser userA secretpassA # TID of controller controller_tid 10 # The device will have lun 1 unless you specify something else backing-store /dev/LVM/somedevice lun 10 # Devices which are in use (by system: mounted, for swap, part of RAID, or by # userspace: dd, by tgtd for another target etc.) can't be used, unless you use # --force flag or add 'allow-in-use yes' option backing-store /dev/LVM/somedevice allow-in-use yes scsi_sn serial1 scsi_sn serial2 allow-in-use yes # Specify controller TID of target # Must be unique for all targets # To reduce risk of duplicating controller TIDs, specify TID for all targets # or none backing-store /dev/LVM/somedevice allow-in-use yes controller_tid 10 # Not supported configurations, and therefore, commented out: # # backing-store /dev/LVM/somedevice1 # backing-store /dev/LVM/somedevice2 # lun 10 # lun 11 # # # # vendor_id VENDOR1 # # # direct-store /dev/sdc # # This one will break the parser: # # # vendor_id VENDOR1 # # # direct-store /dev/sdc # # # vendor_id VENDOR1 # # tgt-1.0.85/conf/examples/targets.conf.vtl.L700000066400000000000000000000070651435417276200207360ustar00rootroot00000000000000# Virtual tape library example for a STK L700 tape library # # In this case, tapes are stored in the directory /root/tapes # size is in MB (1 GB in this case) # using the command "tgtimg --op=new --device-type=tape --barcode="A00000001" --size=10240 --type=data --file=A00000001" # # The tapes can be added after startup with # "tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 4 --params element_type=2,address=1000,barcode=A0000001,sides=1" # for slot 0 (is nr 1000) # include /etc/tgt/temp/*.conf default-driver iscsi allow-in-use yes # # For every drive We need a backing store, although the tape drive will be empty, # so we create a dummy tape "notape" in directory /root/tapes # with the command "tgtimg --op=new --device-type=tape --barcode="" --size=1 --type=clean --file=notape" # and create symbolic links for every drive (limitation of tgt) # link -s /root/tapes/notape /root/tapes/notape1 # link -s /root/tapes/notape /root/tapes/notape2 # link -s /root/tapes/notape /root/tapes/notape2 # lun 1 device-type tape removable 1 vendor_id "HP" product_id "LTO3 ULTRIUM" product_rev "0001" scsi_sn "HUM1A00001" scsi_id "HP LTO3 ULTRIUM" lun 2 device-type tape removable 1 vendor_id "HP" product_id "LTO3 ULTRIUM" product_rev "0001" scsi_sn "HUM1A00002" scsi_id "HP LTO3 ULTRIUM" lun 3 device-type tape removable 1 vendor_id "HP" product_id "LTO3 ULTRIUM" product_rev "0001" scsi_sn "HUM1A00003" scsi_id "HP LTO3 ULTRIUM" lun 4 device-type changer removable 1 vendor_id "STK" product_id "L700" product_rev "0001" scsi_sn "123:456:789:000" # Dummy 'page 0' mode_page "0:0:0" # Page 0x02: Disconnect/Reconnect SPC-3 mode_page "0x02:0:14:0x80:0x80:0:0xa:0:0:0:0:0:0:0:0:0:0" # Page 0x1a: Power Condition SPC-3 mode_page "0x1a:0:18:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0" # Page 0x1c: Informational Exceptions Control SPC-3 mode_page "0x1c:0:10:8:0:0:0:0:0:0:0:0:0" # Page 0x1d: Element Address Assignment SMC-3 7.3.4 mode_page "0x1d:0:0x12:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0" # Page 0x1e: Transport Geometry Parameters SMC-3 7.3.5 mode_page "0x1e:0:2:0:0" # Page 0x1f: Device Capabilities SMC-3 7.3.2 # Page 0x1f/Subpage 0x41: Extended Device Capabilities SMC-3 7.3.3 mode_page "0x1f:0:0x12:0x0f:7:0x0f:0x0f:0x0f:0x0f:0:0:0:0:0x0f:0x0f:0x0f:0x0f:0:0:0:0" # Type 1: Medium Transport Elements (robot arm/picker) params element_type=1,start_address=1,quantity=1,media_home=/root/tapes # Type 2: Storage Elements (tape slots) params element_type=2,start_address=1000,quantity=216,media_home=/root/tapes # Type 3: Import/Export Elements (CAP) params element_type=3,start_address=10,quantity=20,media_home=/root/tapes # Type 4: Add Data Transfer devices (drives) params element_type=4,start_address=500,quantity=3,media_home=/root/tapes params element_type=4,address=500,tid=1,lun=1 params element_type=4,address=500,tid=1,lun=2 params element_type=4,address=500,tid=1,lun=3 tgt-1.0.85/conf/examples/targets.conf.vtl.MSL2024000066400000000000000000000054771435417276200212640ustar00rootroot00000000000000# Virtual tape library example for an HP MSL-2024 tape library # # In this case, tapes are stored in the directory /root/tapes # size is in MB (1 GB in this case) # using the command "tgtimg --op=new --device-type=tape --barcode="A00000001" --size=10240 --type=data --file=A00000001" # # The tapes can be added after startup with # "tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 4 --params element_type=2,address=1000,barcode=A0000001,sides=1" # for slot 0 (is nr 1000) # # Please note that an MSL-2024 has no IMPORT/EXPORT elements (type 3) include /etc/tgt/temp/*.conf default-driver iscsi allow-in-use yes # # We need a backing store, although the tape drive will be empty, # so we create a dummy tape "notape" in directory /root/tapes # with the command "tgtimg --op=new --device-type=tape --barcode="" --size=1 --type=clean --file=notape" # lun 1 device-type tape removable 1 vendor_id "HP " product_id "Ultrium 3-SCSI" product_rev "D21W" scsi_sn "HU012345AB" scsi_id "HP LTO3 ULTRIUM" # # For the tape changer we need also a backing store, this can be a file containing zeros, like this: # "dd if=/dev/zero of=$HOME/smc bs=1k count=1" # lun 4 device-type changer removable 1 vendor_id "HP " product_id "MSL G3 Series " product_rev "3.00" scsi_sn "ABC01234G3" # Dummy 'page 0' mode_page "0:0:0" # Page 0x02: Disconnect/Reconnect SPC-3 mode_page "0x02:0:14:0x80:0x80:0:0xa:0:0:0:0:0:0:0:0:0:0" # Page 0x1a: Power Condition SPC-3 mode_page "0x1a:0:18:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0" # Page 0x1c: Informational Exceptions Control SPC-3 mode_page "0x1c:0:10:8:0:0:0:0:0:0:0:0:0" # Page 0x1d: Element Address Assignment SMC-3 7.3.4 mode_page "0x1d:0:0x12:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0" # Page 0x1e: Transport Geometry Parameters SMC-3 7.3.5 mode_page "0x1e:0:2:0:0" # Page 0x1f: Device Capabilities SMC-3 7.3.2 # Page 0x1f/Subpage 0x41: Extended Device Capabilities SMC-3 7.3.3 mode_page "0x1f:0:0x12:0x0f:7:0x0f:0x0f:0x0f:0x0f:0:0:0:0:0x0f:0x0f:0x0f:0x0f:0:0:0:0" # Type 1: Medium Transport Elements (robot arm/picker) params element_type=1,start_address=1,quantity=1,media_home=/root/tapes # Type 2: Storage Elements (tape slots) params element_type=2,start_address=1000,quantity=24,media_home=/root/tapes # Type 4: Add Data Transfer devices (drives) params element_type=4,start_address=2,quantity=1,media_home=/root/tapes params element_type=4,address=2,tid=1,lun=1 tgt-1.0.85/conf/targets.conf000066400000000000000000000003071435417276200156630ustar00rootroot00000000000000# Empty targets configuration file -- please see the package # documentation directory for an example. # # You can drop individual config snippets into /etc/tgt/conf.d include /etc/tgt/conf.d/*.conf tgt-1.0.85/doc/000077500000000000000000000000001435417276200131635ustar00rootroot00000000000000tgt-1.0.85/doc/Makefile000066400000000000000000000060761435417276200146340ustar00rootroot00000000000000mandir ?= $(PREFIX)/share/man docdir ?= $(PREFIX)/share/doc/tgt MANPAGES = manpages/tgtadm.8 manpages/tgt-admin.8 manpages/tgtimg.8 \ manpages/tgt-setup-lun.8 manpages/tgtd.8 \ manpages/targets.conf.5 DOCS = README.iscsi README.iser \ README.lu_configuration README.mmc tmf.txt \ README.rbd XSLTPROC = /usr/bin/xsltproc XMLMAN = manpages/tgtd.8 manpages/tgtadm.8 manpages/tgtimg.8 \ manpages/tgt-admin.8 manpages/targets.conf.5 \ manpages/tgt-setup-lun.8 XMLHTML = htmlpages/tgtd.8.html htmlpages/tgtadm.8.html \ htmlpages/tgtimg.8.html htmlpages/tgt-admin.8.html \ htmlpages/targets.conf.5.html htmlpages/tgt-setup-lun.8.html .PHONY:all all: xmlman xmlhtml .PHONY: install install: $(MANPAGES) $(DOCS) install -d -m 755 $(DESTDIR)$(mandir)/man8 $(DESTDIR)$(mandir)/man5 install -m 644 $(filter %.8,$(MANPAGES)) $(DESTDIR)$(mandir)/man8 install -m 644 $(filter %.5,$(MANPAGES)) $(DESTDIR)$(mandir)/man5 install -d -m 755 $(DESTDIR)$(docdir) install -m 644 $(DOCS) $(DESTDIR)$(docdir) install -d -m 755 $(DESTDIR)$(docdir)/html install -m 644 $(XMLHTML) $(DESTDIR)$(docdir)/html .PHONY: clean clean: rm -f $(XMLMAN) $(XMLHTML) -rm -f manpages htmlpages manpages/tgtd.8: tgtd.8.xml -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $< htmlpages/tgtd.8.html: tgtd.8.xml -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $< manpages/tgtadm.8: tgtadm.8.xml -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $< htmlpages/tgtadm.8.html: tgtadm.8.xml -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $< manpages/tgt-admin.8: tgt-admin.8.xml -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $< htmlpages/tgt-admin.8.html: tgt-admin.8.xml -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $< manpages/tgtimg.8: tgtimg.8.xml -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $< htmlpages/tgtimg.8.html: tgtimg.8.xml -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $< manpages/targets.conf.5: targets.conf.5.xml -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $< htmlpages/targets.conf.5.html: targets.conf.5.xml -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $< manpages/tgt-setup-lun.8: tgt-setup-lun.8.xml -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $< htmlpages/tgt-setup-lun.8.html: tgt-setup-lun.8.xml -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $< xmlman: $(XMLMAN) xmlhtml: $(XMLHTML) tgt-1.0.85/doc/README.dvdjukebox000077500000000000000000000062731435417276200162220ustar00rootroot00000000000000#!/bin/sh # # This is a script to set up a SCSI DVD JUKEBOX for all your ISO images. # # This script should be run from a directory that contains ISO images # with the extension .iso . # When run, the script will create an iSCSI target containing one DVD drive # and one media changer. The DVD drive will not have a disk mounted but # the medium changer will have one slot for each ISO image found. # Use the 'mtx' tool to load/unload media. # # By default, any ISO images found will be populated with a barcode of the # form DVD_%04d and a volume_tag based on the filename. # # CON: This is the connection index for TGTD. This is needed if you run # multiple instances of TGTD on the same host. Each instance must use a unique # CON value. # # TARGETNAME: The iSCSI name to use for the target. # # TARGETPORT: The TCP port that this target will be hosted from. CON=1 TARGETNAME=iqn.ronnie.iso:distros TARGETPORT=3261 nc -z 127.0.0.1 $TARGETPORT || { echo "Starting iSCSI target on port $TARGETPORT" tgtd -C $CON --iscsi portal=0.0.0.0:$TARGETPORT sleep 3 } tgtadm -C $CON --op delete --mode target --tid 1 --force 2>/dev/null tgtadm -C $CON --op new --mode target --tid 1 -T $TARGETNAME # Create a DVD drive tgtadm -C $CON --op new --mode logicalunit --tid 1 --lun 1 -Y cd tgtadm -C $CON --op update --mode logicalunit --tid 1 --lun 1 --params vendor_id=STGT_DVD,product_id=DVD101,product_rev=0010,scsi_sn=STGTDVD01,removable=1 # We need a backend store file for the media changer SMC=`pwd`/smc-$CON if [ ! -f $SMC ]; then dd if=/dev/zero of=$SMC bs=1k count=1 fi # Create the SMC device and give it a nice name tgtadm -C $CON --mode logicalunit --op new --tid 1 --lun 2 -b $SMC --device-type=changer tgtadm -C $CON --mode logicalunit --op update --tid 1 --lun 2 --params vendor_id=STK,product_id=L700,product_rev=0010,scsi_sn=XYZZY_0,removable=1 # Add a Data Transfer devices (1 drive) tgtadm -C $CON --mode logicalunit --op update --tid 1 --lun 2 --params element_type=4,start_address=1,quantity=1 # Specify that the DVD above (LUN 1) is the data transfer device we created tgtadm -C $CON --mode logicalunit --op update --tid 1 --lun 2 --params element_type=4,address=1,tid=1,lun=1 # Medium Transport Elements (robot arm / picker) tgtadm -C $CON --mode logicalunit --op update --tid 1 --lun 2 --params element_type=1,start_address=16,quantity=1 # define path to virtual media VTL=`pwd`/vtl mkdir -p ${VTL} 2>/dev/null rm ${VTL}/* tgtadm -C $CON --mode logicalunit --op update --tid 1 --lun 2 --params media_home=${VTL} NUM_ELEM=`ls *.iso | wc -l` echo "Creating $NUM_ELEM storage elements for media" # Storage Elements - starting at addr 1024 tgtadm -C $CON --mode logicalunit --op update --tid 1 --lun 2 --params element_type=2,start_address=1024,quantity=$NUM_ELEM # Populate the slots SLOT=0 ls *.iso | sed -e "s/.iso$//" | while read ISO; do BARCODE=`echo $SLOT | awk '{printf "DVD_%04d", $1}'` ln `pwd`/${ISO}.iso ${VTL}/${BARCODE} tgtadm -C $CON --mode logicalunit --op update --tid 1 --lun 2 --params element_type=2,address=`expr "1024" "+" "$SLOT"`,barcode="${BARCODE}",volume_tag="${ISO}",sides=1 SLOT=`expr "$SLOT" "+" "1"` done tgtadm -C $CON --op bind --mode target --tid 1 -I ALL tgtadm -C $CON --op show --mode target tgt-1.0.85/doc/README.glfs000066400000000000000000000044361435417276200150040ustar00rootroot00000000000000The 'glfs' backing-store driver provides block access to a file within the gluster distributed file system (www.gluster.org). The file represents a LUN visible to the initiator (the file may be a regular file in gluster's underlying XFS filesystem, or a gluster "block device"). This configuration gives gluster support for any access method supported by the target driver, such as iSCSI. The backing-store driver uses the interfaces to gluster in libgfapi.so. This file is part of the glusterfs-api package which can be downloaded from www.gluster.org. To build the glfs backing store driver, set the GLFS_BS environment variable (export GLFS_BD=1) before running Make. When a LUN on a target is created with backing store of type glfs (--bstype glfs), a handle to gluster is created. The handle is destroyed when the target is closed. The handle represents a particular volume on a gluster server and a particular file representing the LUN. To set the gluster volume and file, --backing-store takes the form: volume@hostname:filename For example, if the volume name was vol1 , and the host gprfs010, and file name was "disk1", you would set --backing-store to: --backing-store="vol1@gprfs010:disk1" For each CDB, the driver issues an appropriate gluster api call against the handle. For example, WRITE_16 becomes glfs_pwrite(), SYNCHRONIZE_CACHE becomes glfs_fdatasync() and UNMAP becomes glfs_discard(). Each call is synchronous, meaning the thread will wait for a response from gluster before returning to the caller. The libgfapi interfaces support asynchronous calls, and an asynchronous version of the driver has been tested. For more information on the asynchronous version please contact dlambrig@redhat.com. If the backing store driver was not used, the linux target driver could still write data to gluster by loopback mounting the gluster file system. The backing-store would be a file within the file system. Gluster uses FUSE to forward I/O from the kernel to it's user space daemon. The overhead incured by FUSE and the kernel is removed when the backing store driver and libgfapi package is used. The libgfapi interfaces supports logs. You can use the --bsopts option to set the logfile and loglevel. --bsops="logfile=glfs.log;loglevel=3" tgt-1.0.85/doc/README.iscsi000066400000000000000000000313571435417276200151650ustar00rootroot00000000000000Preface ------------- This show a simple example to set up some targets. Starting the daemon ------------- The iSCSI target driver works with the 2.6.X kernels. First, you need to compile the source code: host:~/tgt/usr$ make Try the following commands: host:~/tgt$ su host:~/tgt# ./usr/tgtd Configuration ------------- Everyting is configured via the tgtadm management tool. The following example creates a target with id 1 (the iqn is iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz) and adds a logical unit (backed by /dev/hdc1) with lun 1. Let's create one target device. host:~/tgt$ su host:~/tgt# ./usr/tgtadm --lld iscsi --op new --mode target --tid 1 -T iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz You can get the current configuration: host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target Target 1: iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz System information: Driver: iscsi Status: running I_T nexus information: LUN information: LUN: 0 Type: controller SCSI ID: deadbeaf1:0 SCSI SN: beaf10 Size: 0 Backing store: No backing store Account information: ACL information: The controller device for management with lun 0 was created automatically. You can't remove it. Now it's time to add a logical unit to the target: host:~/tgt# ./usr/tgtadm --lld iscsi --op new --mode logicalunit --tid 1 --lun 1 -b /dev/hdc1 You can get the current configuration: host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target Target 1: iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz System information: Driver: iscsi Status: running I_T nexus information: LUN information: LUN: 0 Type: controller SCSI ID: deadbeaf1:0 SCSI SN: beaf10 Size: 0 Backing store: No backing store LUN: 1 Type: disk SCSI ID: deadbeaf1:1 SCSI SN: beaf11 Size: 55G Backing store: /dev/hdc1 Account information: ACL information: If you don't need to configure this target any more, enable the target to accept any initiators: host:~/tgt# ./usr/tgtadm --lld iscsi --op bind --mode target --tid 1 -I ALL host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target Target 1: iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz System information: Driver: iscsi Status: running I_T nexus information: LUN information: LUN: 0 Type: controller SCSI ID: deadbeaf1:0 SCSI SN: beaf10 Size: 0 Backing store: No backing store LUN: 1 Type: disk SCSI ID: deadbeaf1:1 SCSI SN: beaf11 Size: 55G Backing store: /dev/hdc1 Account information: ACL information: ALL Note "ACL information" section. "ALL" means that this target accepts any initiators. The ACL feature also provides the access control based on initiators' addresses. First, let's remove "ALL" option: host:~/tgt# ./usr/tgtadm --lld iscsi --op unbind --mode target --tid 1 -I ALL host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target Target 1: iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz System information: Driver: iscsi Status: running I_T nexus information: LUN information: LUN: 0 Type: controller SCSI ID: deadbeaf1:0 SCSI SN: beaf10 Size: 0 Backing store: No backing store LUN: 1 Type: disk SCSI ID: deadbeaf1:1 SCSI SN: beaf11 Size: 55G Backing store: /dev/hdc1 Account information: ACL information: Here are some examples: host:~/tgt# ./usr/tgtadm --lld iscsi --op bind --mode target --tid 1 -I 192.0.2.229 host:~/tgt# ./usr/tgtadm --lld iscsi --op bind --mode target --tid 1 -I 198.51.100.0/24 host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target Target 1: iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz System information: Driver: iscsi Status: running I_T nexus information: LUN information: LUN: 0 Type: controller SCSI ID: deadbeaf1:0 SCSI SN: beaf10 Size: 0 Backing store: No backing store LUN: 1 Type: disk SCSI ID: deadbeaf1:1 SCSI SN: beaf11 Size: 55G Backing store: /dev/hdc1 Account information: ACL information: 192.0.2.229 198.51.100.0/24 You can add lots of logical units: host:~/tgt# ./usr/tgtadm --lld iscsi --op new --mode logicalunit --tid 1 --lun 2 -b /dev/hdd1 host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target Target 1: iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz System information: Driver: iscsi Status: running I_T nexus information: LUN information: LUN: 0 Type: controller SCSI ID: deadbeaf1:0 SCSI SN: beaf10 Size: 0 Backing store: No backing store LUN: 1 Type: disk SCSI ID: deadbeaf1:1 SCSI SN: beaf11 Size: 55G Backing store: /dev/hdc1 LUN: 2 Type: disk SCSI ID: deadbeaf1:2 SCSI SN: beaf12 Size: 55G Backing store: /dev/hdd1 Account information: ACL information: You can get iSCSI parameters of the target: host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target --tid 1 MaxRecvDataSegmentLength=8192 MaxXmitDataSegmentLength=8192 HeaderDigest=None DataDigest=None InitialR2T=Yes MaxOutstandingR2T=1 ImmediateData=Yes FirstBurstLength=65536 MaxBurstLength=262144 DataPDUInOrder=Yes DataSequenceInOrder=Yes ErrorRecoveryLevel=0 IFMarker=No OFMarker=No DefaultTime2Wait=2 DefaultTime2Retain=20 OFMarkInt=Reject IFMarkInt=Reject MaxConnections=1 You can change iSCSI parameters like the following (e.g. set MaxRecvDataSegmentLength to 16384): host:~/tgt# ./usr/tgtadm --lld iscsi --mode target --op update --tid 1 --name MaxRecvDataSegmentLength --value 16384 You can get iSCSI parameters again to see its change: host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target --tid 1 MaxRecvDataSegmentLength=16384 MaxXmitDataSegmentLength=8192 HeaderDigest=None DataDigest=None InitialR2T=Yes MaxOutstandingR2T=1 ImmediateData=Yes FirstBurstLength=65536 MaxBurstLength=262144 DataPDUInOrder=Yes DataSequenceInOrder=Yes ErrorRecoveryLevel=0 IFMarker=No OFMarker=No DefaultTime2Wait=2 DefaultTime2Retain=20 OFMarkInt=Reject IFMarkInt=Reject MaxConnections=1 The following is another example to enable header digest: host:~/tgt# ./usr/tgtadm --lld iscsi --mode target --op update --tid 1 --name HeaderDigest --value CRC32C host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target --tid 1 MaxRecvDataSegmentLength=16384 MaxXmitDataSegmentLength=8192 HeaderDigest=CRC32C DataDigest=None InitialR2T=Yes MaxOutstandingR2T=1 ImmediateData=Yes FirstBurstLength=65536 MaxBurstLength=262144 DataPDUInOrder=Yes DataSequenceInOrder=Yes ErrorRecoveryLevel=0 IFMarker=No OFMarker=No DefaultTime2Wait=2 DefaultTime2Retain=20 OFMarkInt=Reject IFMarkInt=Reject MaxConnections=1 The target accepts CRC32C and None. Currently, there is no way to configure a target to accept only CRC32C. Authentication ------------- Let's create a new account: host:~/tgt# ./usr/tgtadm --lld iscsi --op new --mode account --user fujita --password tomo host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode account Account list: fujita You can assign this account to any targets: host:~/tgt# ./usr/tgtadm --lld iscsi --op bind --mode account --tid 1 --user fujita host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target Target 1: iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz System information: Driver: iscsi Status: running I_T nexus information: LUN information: LUN: 0 Type: controller SCSI ID: deadbeaf1:0 SCSI SN: beaf10 Size: 0 Backing store: No backing store LUN: 1 Type: disk SCSI ID: deadbeaf1:1 SCSI SN: beaf11 Size: 55G Backing store: /dev/hdc1 Account information: fujita ACL information: ALL You can set up an outgoing account. First, you need to create a new account like the previous example: host:~/tgt# ./usr/tgtadm --lld iscsi --op new --mode account --user hoge --password deadbeaf host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode account Account list: hoge fujita host:~/tgt# ./usr/tgtadm --lld iscsi --op bind --mode account --tid 1 --user hoge --outgoing host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target Target 1: iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz System information: Driver: iscsi Status: running I_T nexus information: LUN information: LUN: 0 Type: controller SCSI ID: deadbeaf1:0 SCSI SN: beaf10 Size: 0 Backing store: No backing store LUN: 1 Type: disk SCSI ID: deadbeaf1:1 SCSI SN: beaf11 Size: 55G Backing store: /dev/hdc1 Account information: fujita hoge (outgoing) ACL information: ALL Initiator Information ------------- After the target accepts initiators, the system information would be something like the followings: host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target Target 1: iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz System information: Driver: iscsi Status: running I_T nexus information: I_T nexus: 2 Initiator: iqn.1987-05.com.cisco:01.4438aca09387 Connection: 0 IP Address: 192.0.2.115 LUN information: LUN: 0 Type: controller SCSI ID: deadbeaf1:0 SCSI SN: beaf10 Size: 0 Backing store: No backing store LUN: 1 Type: disk SCSI ID: deadbeaf1:1 SCSI SN: beaf11 Size: 55G Backing store: /dev/hdc1 Account information: ACL information: ALL One initiator (192.0.2.113) logs in now. Let's try again: host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target Target 1: iqn.2001-04.com.example:storage.disk2.amiens.sys1.xyz System information: Driver: iscsi Status: running I_T nexus information: I_T nexus: 2 Initiator: iqn.1987-05.com.cisco:01.4438aca09387 Connection: 0 IP Address: 192.0.2.115 I_T nexus: 3 Initiator: iqn.1991-05.com.microsoft:kernel Connection: 1 IP Address: 192.0.2.113 LUN information: LUN: 0 Type: controller SCSI ID: deadbeaf1:0 SCSI SN: beaf10 Size: 0 Backing store: No backing store LUN: 1 Type: disk SCSI ID: deadbeaf1:1 SCSI SN: beaf11 Size: 55G Backing store: /dev/hdc1 Account information: ACL information: ALL Now we have two initiators. You can see the parameters that the target and initiator negotiated (use the values follows "I_T nexus:"): host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode session --tid 1 --sid 3 MaxRecvDataSegmentLength=8192 MaxXmitDataSegmentLength=8192 HeaderDigest=None DataDigest=None InitialR2T=Yes MaxOutstandingR2T=1 ImmediateData=Yes FirstBurstLength=65536 MaxBurstLength=262144 DataPDUInOrder=Yes DataSequenceInOrder=Yes ErrorRecoveryLevel=0 IFMarker=No OFMarker=No DefaultTime2Wait=2 DefaultTime2Retain=20 OFMarkInt=Reject IFMarkInt=Reject MaxConnections=1 Performance tips ------------- If you need multiple logical units, you had better create multiple targets including one logical unit instead of creating one target including multiple logical units. Shutdown ------------- host:~/tgt# killall -9 tgtd We will support better methods later. iSNS ------------- iSNS support is still experimental. host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode sys iSNS: iSNS=Off iSNSServerIP= iSNSServerPort=3205 iSNSAccessControl=Off host:~/tgt# ./usr/tgtadm --op update --mode sys --name iSNSServerIP -v 192.0.2.113 host:~/tgt# ./usr/tgtadm --op update --mode sys --name iSNS -v On host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode sys iSNS: iSNS=On iSNSServerIP=192.0.2.113 iSNSServerPort=3205 iSNSAccessControl=Off Now you are ready to add targets. Now there are some limitations: - once you enable iSNS, you can't disable it. - you need to enable iSNS before adding targets. tgt-1.0.85/doc/README.iser000066400000000000000000000433201435417276200150060ustar00rootroot00000000000000*** iSCSI Extensions for RDMA (iSER) in tgt *** Copyright (C) 2007 Pete Wyckoff Copyright (C) 2011 Alexander Nezhinsky 1. Background 1.1. Standards (iSCSI, iSER) The IETF standards track RFC 5046 extends the iSCSI protocol to work on RDMA-capable networks as well as on traditional TCP/IP: Internet Small Computer System Interface (iSCSI) Extensions for Remote Direct Memory Access (RDMA), Mike Ko, October 2007. It is available online: http://tools.ietf.org/html/rfc5046 RDMA stands for Remote Direct Memory Access, a way of accessing memory of a remote node directly through the network without involving the processor of that remote node. Many network devices implement some form of RDMA. Two of the more popular network devices are InfiniBand (IB) and iWARP. IB uses its own physical and network layer, while iWARP sits on top of TCP/IP (or SCTP). Using these devices requires a new application programming interface (API). The Linux kernel has many components of the OpenFabrics software stack, including APIs for access from user space and drivers for some popular RDMA-capable NICs, including IB cards with the chipset from Mellanox and QLogic, and iWARP cards from NetEffect, Chelsio, and Ammasso. Most Linux distributions ship the user space libraries for device access and RDMA connection management. There is an ongoing activity, which is still in progress, intended to improve upon RFC 5046 and address some existing issues. The text of the latest proposal is available online (note, though, that it may become outdated quickly): http://tools.ietf.org/html/draft-ietf-storm-iser-01 1.2. iSER in tgtd tgtd is a user space target that supports multiple transports, including iSCSI/TCP and iSER on RDMA devices. The original iSER code was written in early 2007 by researchers at the Ohio Supercomputer Center: Dennis Dalessandro Ananth Devulapalli Pete Wyckoff The authors wanted to use a faster transport to test the capabilities of an object-based storage device (OSD) emulator. A report describing this implementation and some performance results appears in IEEE conference proceedings as: Dennis Dalessandro, Ananth Devulapalli and Pete Wyckoff, "iSER Storage Target for Object-based Storage Devices", Proceedings of MSST'07, SNAPI Workshop, San Diego, CA, September 2007. and is available at: http://www.osc.edu/~pw/papers/iser-snapi07.pdf Slides of the talk with more results and analysis are available at: http://www.osc.edu/~pw/papers/wyckoff-iser-snapi07-talk.pdf The original code lived in iscsi/iscsi_rdma.c, with a few places in iscsi/iscsid.c. RDMA transport was added and some more functions where TCP and RDMA behaved differently were virtualized. There was a bug that resulted in occasional data corruption. The new implementation was written by Alexander Nezhinsky . It defines iSER as a separate transport (and not as a sub-transport of iSCSI/TCP). One of the main differences between iSCSI/TCP and iSER is that the former enjoys the stream semantics of TCP and may work in a synchronous manner, while the latter's flow is intrinsically asynchronous and message based. Implementing a synchronous flow within an asynchronous framework is relatively natural, while fitting an asynchronous flow within a synchronous framework is usually met with a few obstacles resulting in a sub-optimal design. The main reason to define iser as a separate transport (which is an example of such obstacle) was to decouple rx/tx flow from using EPOLLIN/EPOLLOUT events originally used to poll TCP sockets. See "Event Management" section below for details. Although one day we may return to a common tcp/rdma transport, for now a separate transport LLD (named "iser") is defined. Other changes include avoiding memory copies, using a memory pool shared between connections with "patient" memory allocation mechanism, etc. Source-wise, a new header "iser.h" is created, "iscsi_rdma.c" is replaced by "iser.c". File iser_text.c contains the iscsi-text processing code replicated from iscsid.c. This is done because the functions there are not general enough, and rely on specifics of iscsi/tcp structs. This file will hopefully be removed in the future. 2. Design 2.1. General Notes In general, a SCSI system includes two components, an initiator and a target. The initiator submits commands and awaits responses. The target services commands from initiators and returns responses. Data may flow from the initiator, from the client, or both (bidirectional). The iSER specification requires all data transfers to be started by the target, regardless of direction. In a read operation, the target uses RDMA Write to move data to the initiator, while a write operation uses RDMA Read to fetch data from the initiator. 2.2. Memory registration One of the most severe stumbling blocks in moving any application to take advantage of RDMA features is memory registration. Before using RDMA, both the sending and receiving buffers must be registered with the operating system. This operation ensures that the underlying hardware pages will not be modified during the transfer, and provides the physical addresses of the buffers to the network card. However, the process itself is time consuming, and CPU intensive. Previous investigations have shown that for InfiniBand, the throughput drops by up to 40% when memory registration and deregistration are included in the critical path. This iSER implementation uses pre-registered buffers for RDMA operations. In general such a scheme is difficult to justify due to the large per-connection resource requirements. However, in this application it may be appropriate. Since the target always initiates RDMA operations and never advertises RDMA buffers, it can securely use one pool of buffers for multiple clients and can manage its memory resources explicitly. Also, the architecture of the code is such that the iSCSI layer dictates incoming and outgoing buffer locations to the storage device layer, so supplying a registered buffer is relatively easy. 2.3. Event management As mentioned above, there is a mismatch between what the iscsid framework assumes and what the RDMA notification interface provides. The existing TCP-based iSCSI target code has one file descriptor per connection and it is driven by readability or writeability of the socket. A single poll system call returns which sockets can be serviced, driving the TCP code to read or write as appropriate. The RDMA interface is also represented by a single file descriptor created by the driver responsible for the hardware. This file descriptor readability may be used by requesting interrupts from the network card on work request completions, after a sufficiently long period of quiescence. Furter completions can be polled and retrieved without re-arming the interrupts. Beside this first difference, the RDMA device file descriptor can not and should not be polled for writability, as any messages or RDMA transfer requests may be issued assynhcronously. Moreover, the existing sockets-based code goes beyond this and changes the bitmask of requested events to control its code flow. For instance, after it finishes sending a response, it will modify the bitmask to only look for readability. Even if the socket is writeable, there is no data to write, hence polling for that status is not useful. The code also disables new message arrival during command execution as a sort of exclusion facility, again by modifying the bitmask. As it can not be done with the RDMA interface, the original code had to maintain an active list of tasks having data to write and to drive a progress engine to service them. The progress was tracked by a counter, and the tgtd event loop checked this counter and called into the iSER-specific while the counter is still non-zero. This scheme was quite unnatural and error-prone. The new implementation issues all SEND requests asynchronously. Besides, it relies heavily upon the scheduled events that are injected into the event loop with no dependence on file descriptors. It schedules such events to poll for new RDMA completion events, in hope that new ones are ready. If no event arrives after a certain number of polls then interrupts are requested and further progress will be driven through the file-based event mechanism. Note that only the first event is signal in this manner and while new completions are constantly arriving, they will be retrieved by polling only. Other internal events of the same kind (like tasks requesting a send, commands that are ready for submition etc.) are grouped on appropriate lists and special events are scheduled for them. This allows to process few tasks in a batched manner in order to optimise RDMA and other operations, if possible. 2.4. RDMA-only mode The code implies RDMA-only mode of work. This means the "first burst" including immediate data should be disabled, so that the entire data transfer is performed using RDMA. This mode is perhaps the most suitable one for iser in the majority of work scenarios. The only concern is about relatively small WRITE I/Os, which may enjoy theoretically lower latencies using IB SEND instead of RDMA-RD. Implementing this mode is meanwhile precluded because it would lead to multiple buffers per iSER task (e.g. ImmediateData buffer received with the command PDU, and the rest of the data retrieved using RDMA-RD), which is not supported by the existing tgt backing stores. The RDMA-only mode is achieved by setting: target->session_param[ISCSI_PARAM_INITIAL_R2T_EN].val = 1; target->session_param[ISCSI_PARAM_IMM_DATA_EN].val = 0; which is hardcoded in iser_target_create(). 2.5. Padding The iSCSI specification clearly states that all segments in the protocol data unit (PDU) must be individually padded to four-byte boundaries. However, the iSER specification remains mute on the subject of padding. It is clear from an implementation perspective that padding data segments is both unnecessary and would add considerable overhead to implement. (Possibly a memory copy or extra SG entry on the initiator when sending directly from user memory.) RDMA is used to move all data, with byte granularity provided by the network. The need for padding in the TCP case was motivated by the optional marker support to work around the limitations of the streaming mode of TCP. IB and iWARP are message-based networks and would never need markers. And finally, the Linux initiator does not add padding either. 3. Using iSER 3.1. Building tgtd Compile tgtd with: make ISCSI_RDMA=1 As iSCSI support is compiled in by default, there is no need to specify additional symbols for it. For build purposes, you'll need to have two libraries installed on your system: libibverbs-devel and librdmacm-devel. To run iser, you'lll need the binary form of these libraries, libibverbs.so and librdmacm.so along with device specific user space library such as libmlx4 for Mellanox ConnectX HCAs. If they are installed in the normal system paths (/usr/include and /usr/lib or /usr/lib64), they will be found automatically. Otherwise, edit CFLAGS and LIBS in usr/Makefile under ISER to specify the paths by hand, e.g., for a /usr/local install, it should look like: ifneq ($(ISCSI_RDMA),) CFLAGS += -DISCSI_RDMA -I/usr/local/include TGTD_OBJS += iscsi/iser.o LIBS += -L/usr/local/lib -libverbs -lrdmacm endif If these libraries are not in the normal system paths, you may possibly also have to set, e.g., LD_LIBRARY_PATH=/usr/local/lib in your environment to find the shared libraries at runtime. The libraries mentioned is this section have pre-set packages provided the by various distributions, e.g RPMs for Fedora, RedHat and Suse, or DEBs for Debian and Ubuntu. 3.2. Running tgtd Start the daemon (as root): ./tgtd It will send messages to syslog. You can add "-d 1" to turn on debug messages. Debug messages can be also turned on and off during run time using the following commnds: ./tgtadm --mode system --op update --name debug --value on ./tgtadm --mode system --op update --name debug --value off The target will listen on all TCP interfaces (as usual), as well as all RDMA devices. Both use the same default iSCSI port, 3260. Clients on TCP or RDMA will connect to the same tgtd instance. When using iser, you need to make sure the kernel IB/RDMA stack is up and running. It's recommended to use the rping tool provided by librdmacm-utils for quick RDMA connectivity testing between the initiator and the target nodes. 3.3. Configuring tgtd Configure the running target with one or more devices, using the tgtadm program you just built (also as root). Full information is available in doc/README.iscsi. The difference is only in the name of LLD which should be "iser". Here is a quick-start example: ./tgtadm --lld iser --mode target \ --op new --tid 1 --targetname "iqn.$(hostname).t1" ./tgtadm --lld iser --mode target \ --op bind --tid 1 --initiator-address ALL ./tgtadm --lld iser --mode logicalunit \ --op new --tid 1 --lun 1 \ --backing-store /dev/sde --bstype rdwr 3.4. Initiator side To make your initiator use RDMA, make sure the "ib_iser" module is loaded in your kernel. Then do discovery as usual, over TCP: iscsiadm -m discovery -t sendtargets -p $targetip where $targetip is the ethernet address of your IPoIB device. Discovery traffic will use IPoIB, but login and full feature phase will use RDMA natively. Then do something like the following to change the transport type: iscsiadm -m node -p $targetip -T $targetname --op update \ -n node.transport_name -v iser Next, login as usual: iscsiadm -m node -p $targetip -T $targetname --login And access the new block device, e.g. /dev/sdb. Note that separate iscsi and iser transports mean that you should know which targets are configured as iser and which as iscsi/tcp. If you try to login to a target configured as iser over tcp, this will fail. And vice versa, trying to login to a target configured as iscsi/tcp over iser will not succeed as well. Because an iscsi target has no means for reporting its RDMA capabilities you have to try to login over iser to every target reported by SendTargets. If it fails and you still want to access the target over tcp, then change the transport name back to "tcp" and try to login again. Some distributions include a script named "iscsi_discovery", which accomplishes just this. If you wish to login either over iser or over tcp: iscsi_discovery $targetip -t iser This will login changing transports if necessary. Then, if succesful, it will logout leaving the target's record with the appropriate transport setting. If you are interested only in iser targets, then add "-f", forcing the transport to be iser. Note also that the port can be specified explicitely: iscsi_discovery $targetip -p $targetport -t iser -f This will cancel login retry over tcp in case of the initial failure. 4. Errata 4.1. Pre-2.6.21 mthca driver bug There is a major bug in the mthca driver in linux kernels before 2.6.21. This includes the popular rhel5 kernels, such as 2.6.18-8.1.6.el5 and possibly later. The critical commit is: 608d8268be392444f825b4fc8fc7c8b509627129 IB/mthca: Fix data corruption after FMR unmap on Sinai If you use single-port memfree cards, SCSI read operations will frequently result in randomly corrupted memory, leading to bad application data or unexplainable kernel crashes. Older kernels are also missing some nice iSCSI changes that avoids crashes in some situations where the target goes away. Stock kernel.org linux after 2.6.21 have been tested and are known to work. 4.2. Bidirectional commands The Linux kernel iSER initiator is currently lacking support for bidirectional transfers, and for extended command descriptors (CDBs). Progress toward adding this is being made, with patches frequently appearing on the relevant mailing lists. 4.3. ZBVA The Linux kernel iSER initiator uses a different header structure on its packets than is in the iSER specification. This is described in an InfiniBand document and is required for that network, which only supports for Zero-Based Virtual Addressing (ZBVA). If you are using a non-IB initiator that doesn't need this header extension, it won't work with tgtd. There may be some way to negotiate the header format. Using iWARP hardware devices with the Linux kernel iSER initiator also will not work due to its reliance on fast memory registration (FMR), an InfiniBand-only feature. 4.4. MaxOutstandingUnexpectedPDUs The current code sizes its per-connection resource consumption based on negotiatied parameters. However, the Linux iSER initiator does not support negotiation of MaxOutstandingUnexpectedPDUs, so that value is hard-coded in the target. 4.5. TargetRecvDataSegmentLength Also, open-iscsi is hard-coded with a very small value of TargetRecvDataSegmentLength, so even though the target would be willing to accept a larger size, it cannot. This may limit performance of small transfers on high-speed networks: transfers bigger than 8 kB, but not large enough to amortize a round-trip for RDMA setup. 4.6. Multiple devices The iser code has been successfully tested with multiple Infiniband devices. 4.7. SCSI command size A single buffer per SCSI command limitation has another implication (except the "RDMA-only mode", see above). The RDMA buffers pool is currently created with buffers of 512KB each (see "Memory registration" section above). This is suitable to work with most of the linux initiators, which split all transfers into SCSI commands of up to 128KB, 256KB or 512KB (depending on the system). Initiators that issue explicit SCSI commands with the size greater than 512KB will be unable to work with the current iser implementation. Once multiple buffers are supported by the backing stores this limitation can be eliminated in a relatively simple manner. tgt-1.0.85/doc/README.lu_configuration000066400000000000000000000114101435417276200174060ustar00rootroot00000000000000Preface -------------- This show a simple example to set up some logical units (lu). Please refer to README.iscsi on instructions to create logical unit(s). tgtadm options -------------- You are able to modify some logical unit parameters as well as modify behaviour of SCSI Sense op code. NOTE: It is not recommended to change these parameters after the target/logical unit has been enabled to accept initiators. It is currently possible to change/modify the following: Vendor Identification Product Identification Product Revision Format of returned 'sense data' Define if the lu is capable of supporting removable media Define/set if the lu is online / offline. Params are passed using the 'tgtadm' utility: Format of options are: vendor_id="string" product_id="string" product_rev="string" removable=<0|1> - 0 = non-removable, 1 = removable media sense_format=<0|1> - 0 = Clasic sense format, 1 = Support descriptor format online=<0|1> - 0 == Unit offline, 1 == Unit Online The options are passed to the logical unit via the "--params" switch to tgtadm e.g. tgtadm --lld iscsi --mode logicalunit --op update --tid --lun \ --params vendor_id=QUANTUM,product_id=HD100,product_rev=0010 tgtadm --lld iscsi --mode logicalunit --op update --tid --lun \ --params removable=1,sense_format=1,online=1 Or it can be performed in one go: tgtadm --lld iscsi --mode logicalunit --op update --tid --lun \ --params vendor_id=QUANTUM,product_id=HD100,product_rev=0010,scsi_sn=FRED00,removable=1,sense_format=0,online=1 # sg_inq -v /dev/sg5 inquiry cdb: 12 00 00 00 24 00 standard INQUIRY: inquiry cdb: 12 00 00 00 42 00 PQual=0 Device_type=0 RMB=1 version=0x05 [SPC-3] [AERC=0] [TrmTsk=1] NormACA=0 HiSUP=0 Resp_data_format=2 SCCS=0 ACC=0 TGPS=0 3PC=0 Protect=0 BQue=0 EncServ=0 MultiP=0 [MChngr=0] [ACKREQQ=0] Addr16=0 [RelAdr=0] WBus16=0 Sync=0 Linked=0 [TranDis=0] CmdQue=1 Clocking=0x0 QAS=0 IUS=0 length=66 (0x42) Peripheral device type: disk Vendor identification: QUANTUM Product identification: HD100 Product revision level: 0010 inquiry cdb: 12 01 00 00 fc 00 inquiry: requested 252 bytes but got 7 bytes inquiry cdb: 12 01 80 00 fc 00 inquiry: requested 252 bytes but got 12 bytes Unit serial number: FRED00 As can be seen from above 'sg_inq' output, the RMB (removable) bit is set to 1. The Unit serial number page updated with 'FRED00' Mode Page Creation ------------------ Create mode page '2', subpage 0 and 14 bytes of data. tgtadm --mode logicalunit --op update --tid 1 --lun 2 \ --params mode_page=2:0:14:0x80:0x80:0:0xa:0:0:0:0:0:0:0:0:0:0 Create mode page '3', subpage 0 and 22 bytes of data. tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 \ --params mode_page=3:0:22:0:0:0:0:0:0:0:0:1:0:2:0:0:0:0:0:0:0:0:13:0:0 Create mode page '10', subpage 0 and 10 bytes of data. tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 \ --params mode_page=10:0:10:2:0:0:0:0:0:0:0:2:0 Create mode page '0x1c', subpage 0 and 10 bytes of data. tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 \ --params mode_page=0x1c:0:10:8:0:0:0:0:0:0:0:0:0 SMC unique options ------------------ --params have several unique key=value pairs ontop of all other modules. - element_type=<1|2|3|4> - start_address=Number between 1 & 65535 - quantity=Number between 1 & 65535 - sides=1|2 - address=Number between 1 & 65535 - barcode="Char string up to 10 chars" - tid= - lun= - media_home=/path/to/virtual/media The 'barcode' of media is appended to this path specified in media_home and it is this file which is used for the backing store when reading/writing to DATA TRANSFER DEVICE at address 'tid=x lun=x' Several of these parameters 'work together' e.g. To add 'quantity' slots as 'element_type' starting at 'start_address' - element_type=<1|2|3|4> - start_address=Number between 1 & 65535 - quantity=Number between 1 & 65535 Note: start_address + quantity should not overlap with any other slots.. While 'configuring slot 'address' of 'element_type': - Set barcode of meda (occupy slot) - If element type is DATA TRANSFER DEVICE, then define TID & LUN of device. - element_type=<1|2|3|4> - address=Number between 1 & 65535 - barcode="String up to 10 chars" - sides=<1|2> - tid= - lun= It is the responsibility of the user not to configure overlapping slots of differing types. Slot types: 1 -> Medium Transport (picker arm) 2 -> Storage Element 3 -> Import/Export Element 4 -> Data Transfer device (CD drive, tape drive, MO drive etc) Please refer to scripts/tgt-core-test for a working example. tgt-1.0.85/doc/README.mmc000066400000000000000000000230761435417276200146260ustar00rootroot00000000000000The MMC emulation in TGTD can emulate a DVD device capable of writing/burning to emulated blank disks. The emulation supports three types of behaviour (profiles) which are automatically selected by TGTD at runtime. These three types are : NO_PROFILE : This is the behaviour used when the device is OFFLINE or when there is no backing store file present. In this profile the emulation will behave as if there is no media (disk) inserted in the drive tray. DVD-ROM : This is used if the device is ONLINE and also if the backing store file exists and contains data (i.e. the file size is >0). In this mode TGTD will behave as if a read-only DVD-ROM disk is inserted in the tray. DVD+R : This is used if the device is ONLINE and the backing store file exists and is empty (i.e. the file size is == 0). In this mode the emulation will behave as if there is a blank, writeable, DVD+R disk in the tray. In this mode you can burn an ISO image onto the device. Once the burning process has finished and the application issues the CLOSE TRACK command to finalize the track, the emulation will morph from DVD+R and into DVD-ROM so that subsequent access to the drive will behave as if there is now a DVD-ROM disk in the tray. Compatibility : The MMC commandset for writeable media is extensive. This emulation currently only implements a small subset of the full MMC specification. In particular, during development the windows application DVD-Decrypter was used to view which commands and subcommands were required to successfully burn a disk. This emulation supports all commands that that application uses when accessing a DVD drive to identify and write to a blank disk. Other applications may use a different subset of MMC to perform the same operations in which case it may or may not work. It is the intent that the emulation will be enhanced to provide compatibility with all DVD burning applications on all important platforms. The emulation is also compatible with Linux:dvdrecorder but you must specify the command arguments -dao -ignsize -overburn Example: dvdrecord -dao -ignsize -overburn dev=/dev/sg3 ./IMAGE.iso Please report incompatibilities with other burning applications other than Windows/DVD-Decrypter to ronniesahlberg@gmail.com and I will try to enhance the emulation to also support your application of choice. I can however only test with Windows free-ware or linux applications. # # Example 1: How to create a MMC device that contains an empty writeable # DVD+R disk which can be written to using DVD-Decrypter # # Create a target tgtadm --lld iscsi --mode target --op new --tid 1 -T iqn.2007-03:virtual-dvd:`hostname` # Create an empty file that will represent a writeable DVD+R disk rm -rf /tmp/empty-disk.iso tgtimg --op new --device-type cd --type dvd+r --file /tmp/empty-disk.iso # Create LUN - CD/ROM tgtadm --lld iscsi --mode logicalunit --op new --tid 1 --lun 1 -b /tmp/empty-disk.iso --device-type=cd # Give it a nice name SN=XYZ123 tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 1 --params vendor_id=STGT_DVD,product_id=DVD101,product_rev=0010,scsi_sn=$SN,removable=1 # Allow ALL initiators to connect to this target tgtadm --lld iscsi --mode target --op bind --tid 1 -I ALL # Show all our good work. tgtadm --lld iscsi --mode target --op show # # Example 2: How to mark the device "offline" meaning the device will show up # as "no disk in tray" # tgtadm --tid 1 --lun 1 --op update --mode logicalunit --name online --value No # # Example 3: While the device is "offline" we can swap the disk in the tray, # by just pointing the device to a different backing store file. # In this example we just swap it to another blank DVD+R disk. # rm -rf /tmp/empty-disk2.iso tgtimg --op new --device-type cd --type dvd+r --file /tmp/empty-disk2.iso tgtadm --tid 1 --lun 1 --op update --mode logicalunit --name path --value /tmp/empty-disk2.iso # note: when we updated the device to a new bakend storage file the device # automatically goes online again with the new media present. # # Example 4: How to create a DVD jukebox with 8 disk trays # # Create a target tgtadm --lld iscsi --mode target --op new --tid 1 -T iqn.2007-03:virtual-dvd:`hostname` # Create a DVD drive and give it a nice name # The dvd starts out without a backing store file, i.e. no disk loaded tgtadm --op new --mode logicalunit --tid 1 --lun 1 -Y cd tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 1 --params vendor_id=STGT_DVD,product_id=DVD101,product_rev=0010,scsi_sn=STGTDVD01,removable=1 # We need a backend store file for the media changer if [ ! -f $HOME/smc ]; then dd if=/dev/zero of=$HOME/smc bs=1k count=1 fi # Create the SMC device and give it a nice name tgtadm --lld iscsi --mode logicalunit --op new --tid 1 --lun 2 -b $HOME/smc --device-type=changer tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params vendor_id=STK,product_id=L700,product_rev=0010,scsi_sn=XYZZY_0,removable=1 # Add a Data Transfer devices (1 drive) tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=4,start_address=1,quantity=1 # Specify that the DVD above (LUN 1) is the data transfer device we created tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=4,address=1,tid=1,lun=1 # Medium Transport Elements (robot arm / picker) tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=1,start_address=16,quantity=1 # define path to virtual media VTL=${HOME}/vtl mkdir -p ${VTL} 2>/dev/null tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params media_home=${VTL} # Storage Elements - 8 starting at addr 1024 tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=2,start_address=1024,quantity=8 # Add 'media' to slots 1 and 2 and leave the other 6 slots empty # slot 1 tgtimg --op new --device-type cd --type dvd+r --file ${VTL}/DISK_001 tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=2,address=1024,barcode=DISK_001,sides=1 # slot 2 tgtimg --op new --device-type cd --type dvd+r --file ${VTL}/DISK_002 tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=2,address=1025,barcode=DISK_002,sides=1 # Allow ALL initiators to connect to this target tgtadm --lld iscsi --mode target --op bind --tid 1 -I ALL # Show all our good work. tgtadm --lld iscsi --mode target --op show Now you should be able to access the devices and try it out : tgtadm --lld iscsi --mode target --op show Target 1: iqn.2007-03:virtual-dvd:ronnie System information: Driver: iscsi State: ready I_T nexus information: LUN information: LUN: 0 Type: controller SCSI ID: deadbeaf1:0 SCSI SN: beaf10 Size: 0 MB Online: Yes Removable media: No Backing store: No backing store LUN: 1 Type: cd/dvd SCSI ID: deadbeaf1:1 SCSI SN: STGTDVD0 Size: 0 MB Online: No Removable media: Yes Backing store: No backing store LUN: 2 Type: changer SCSI ID: deadbeaf1:2 SCSI SN: XYZZY_0 Size: 0 MB Online: Yes Removable media: Yes Backing store: /root/smc Account information: ACL information: ALL You can use the 'mtx' command under linux to manage the jukebox (man mtx) mtx -f /dev/sg4 status Storage Changer /dev/sg4:1 Drives, 8 Slots ( 0 Import/Export ) Data Transfer Element 0:Empty Storage Element 1:Full :VolumeTag=DISK_001 Storage Element 2:Full :VolumeTag=DISK_002 Storage Element 3:Empty:VolumeTag= Storage Element 4:Empty:VolumeTag= Storage Element 5:Empty:VolumeTag= Storage Element 6:Empty:VolumeTag= Storage Element 7:Empty:VolumeTag= Storage Element 8:Empty:VolumeTag= Now load the disk in slot 1 : mtx -f /dev/sg4 load 1 mtx -f /dev/sg4 status Storage Changer /dev/sg4:1 Drives, 8 Slots ( 0 Import/Export ) Data Transfer Element 0:Full (Storage Element 1 Loaded):VolumeTag = DISK_00 Storage Element 1:Empty:VolumeTag= Storage Element 2:Full :VolumeTag=DISK_002 Storage Element 3:Empty:VolumeTag= Storage Element 4:Empty:VolumeTag= Storage Element 5:Empty:VolumeTag= Storage Element 6:Empty:VolumeTag= Storage Element 7:Empty:VolumeTag= Storage Element 8:Empty:VolumeTag= And if we look at the devices again we see the DVD drive is now online and has a disk loaded : tgtadm --lld iscsi --mode target --op show Target 1: iqn.2007-03:virtual-dvd:ronnie System information: Driver: iscsi State: ready I_T nexus information: I_T nexus: 1 Initiator: iqn.1993-08.org.debian:ronnie Connection: 0 IP Address: 127.0.0.1 LUN information: LUN: 0 Type: controller SCSI ID: deadbeaf1:0 SCSI SN: beaf10 Size: 0 MB Online: Yes Removable media: No Backing store: No backing store LUN: 1 Type: cd/dvd SCSI ID: deadbeaf1:1 SCSI SN: STGTDVD0 Size: 0 MB Online: Yes Removable media: Yes Backing store: /root/vtl/DISK_001 LUN: 2 Type: changer SCSI ID: deadbeaf1:2 SCSI SN: XYZZY_0 Size: 0 MB Online: Yes Removable media: Yes Backing store: /root/smc Account information: ACL information: ALL Have fun building big DVD jukeboxes !!! tgt-1.0.85/doc/README.passthrough000066400000000000000000000150051435417276200164120ustar00rootroot00000000000000Preface ------------- This documents an example to set up tgt targets utilising existing SCSI targets on the host utilizing tgt passthrough module and /dev/sg paths. The hardware to be 'exported' via the SCSI target framework is an IBM 3573-TL library with a single LTO4 drive. Physical tape library connected and configured to host: # lsscsi -g [0:0:0:0] disk MAXTOR ATLAS10K4_36SCA DFM0 /dev/sda /dev/sg0 [0:0:6:0] process PE/PV 1x3 SCSI BP 1.1 - /dev/sg1 [2:0:1:0] tape IBM ULT3580-TD4 8192 /dev/st1 /dev/sg4 [2:0:1:1] mediumx IBM 3573-TL 6.50 - /dev/sg5 Please refer to the README.iscsi or README.iser for instructions specific to setting up the transport layer specific section. Many of the examples below are using the iSCSI lld (low Level) transport. Starting the daemon ------------- Please refer to 'Starting the daemon' in the README.iscsi or README.iser. on instructions for correctly starting the daemon for your transport of choice. Configuration ------------- Everyting is configured via the tgtadm management tool. Please refer to "Configuration" in README.iscsi or README.iser on how to configure your target for the transport of choice. Return to here for further instructions on setting up each logical unit using the passthrough module. An example to setup the target (target ID 1) for iSCSI host:~/tgt# ./usr/tgtadm --lld iscsi --op new --mode target --tid 1 \ -T iqn.2001-04.com.example:storage.vtl.amiens.sys1.xyz To display your target configuration: host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target Target 1: iqn.2001-04.com.example:storage.vtl.amiens.sys1.xyz System information: Driver: iscsi Status: running I_T nexus information: LUN information: LUN: 0 Type: controller SCSI ID: deadbeaf1:0 SCSI SN: beaf10 Size: 0 Backing store: No backing store Account information: ACL information: The controller device for management with lun 0 was created automatically. You can't remove it. Now it's time to add a logical unit to the target: (The tape drive connected via /dev/sg4 - refer to 'lsscsi' output above) The important flags are: - Specify device type as passthruough "--device-type=pt" - Backing store type is the '/dev/sg' paths "--bstype=sg" - Backing store path to use "-b /dev/sg4" host:~/tgt# ./usr/tgtadm --lld iscsi --op new --mode logicalunit \ --tid 1 --lun 1 --bstype=sg --device-type=pt -b /dev/sg4 To display the current configuration: host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target Target 1: iqn.2001-04.com.example:storage.vtl.amiens.sys1.xyz System information: Driver: iscsi Status: running I_T nexus information: LUN information: LUN: 0 Type: controller SCSI ID: deadbeaf1:0 SCSI SN: beaf10 Size: 0 Backing store: No backing store LUN: 1 Type: (null) SCSI ID: IET 00010002 SCSI SN: beaf12 Size: 0 MB Online: Yes Removable media: No Backing store type: sg Backing store path: /dev/sg4 Backing store flags: Account information: ACL information: To add another logical unit to this target: host:~/tgt# ./usr/tgtadm --lld iscsi --op new --mode logicalunit \ --tid 1 --lun 2 --bstype=sg --device-type=pt -b /dev/sg5 If you don't need to configure this target any more, enable the target to accept any initiators: host:~/tgt# ./usr/tgtadm --lld iscsi --op bind --mode target --tid 1 -I ALL Note "ACL information" section. "ALL" means that this target accepts any initiators. The ACL feature also provides the access control based on initiators' addresses. For further instructions on ACL and account setup, please refer to the README.iscsi As above, use the '--op show' option to display your current setup. host:~/tgt# ./usr/tgtadm --lld iscsi --op show --mode target Target 1: iqn.2001-04.com.example:storage.vtl.amiens.sys1.xyz System information: Driver: iscsi Status: running I_T nexus information: LUN information: LUN: 0 Type: controller SCSI ID: deadbeaf1:0 SCSI SN: beaf10 Size: 0 Backing store: No backing store LUN: 1 Type: (null) SCSI ID: IET 00010002 SCSI SN: beaf12 Size: 0 MB Online: Yes Removable media: No Backing store type: sg Backing store path: /dev/sg4 Backing store flags: LUN: 2 Type: (null) SCSI ID: IET 00010002 SCSI SN: beaf12 Size: 0 MB Online: Yes Removable media: No Backing store type: sg Backing store path: /dev/sg5 Backing store flags: Account information: ACL information: ALL iSCSI Initiator Information ------------- After the target accepts initiators, the system information would be something like the following: Linux open-iscsi initiator hostb:~ # iscsiadm -m discovery -t sendtargets -p 10.251.60.20 10.251.60.20:3260,1 iqn.2001-04.com.example:storage.vtl.amiens.sys1.xyz hostb:~ # iscsiadm -m node -T iqn.2001-04.com.example:storage.vtl.amiens.sys1.xyz \ -p 10.251.60.20 --login Logging in to [iface: default, target: iqn.2001-04.com.example:storage.vtl.amiens.sys1.xyz, portal: 10.251.60.20,3260] Login to [iface: default, target: iqn.2001-04.com.example:storage.vtl.amiens.sys1.xyz, portal: 10.251.60.20,3260]: successful hostb:~ # lsscsi -g [0:0:0:0] cd/dvd TSSTcorp DVD-ROM TS-L333A D100 /dev/sr0 /dev/sg0 [2:0:0:0] disk SEAGATE ST3400755SS NS25 /dev/sda /dev/sg1 [2:0:1:0] disk SEAGATE ST3400755SS NS25 /dev/sdb /dev/sg2 [3:0:0:0] disk DGC DISK 0429 /dev/sdc /dev/sg3 [3:0:1:0] disk DGC DISK 0429 /dev/sdd /dev/sg4 [11:0:0:0] storage IET Controller 0001 - /dev/sg5 [11:0:0:1] tape IBM ULT3580-TD4 8192 /dev/st0 /dev/sg6 [11:0:0:2] mediumx IBM 3573-TL 6.50 - /dev/sg7 Shutdown the target cleanly --------------------------- host:~/tgt# tgtadm --op unbind --mode target --tid 1 -I ALL host:~/tgt# tgtadm --op delete --mode conn --tid 1 host:~/tgt# tgtadm --op delete --mode target --tid 1 host:~/tgt# tgtadm --op delete --mode system tgt-1.0.85/doc/README.rbd000066400000000000000000000045071435417276200146170ustar00rootroot00000000000000The 'rbd' backing-store driver provides access to Ceph (http://ceph.com) 'RADOS block devices (rbd)" through tgtd. Ceph is a distributed storage system that provides clustered redundant object storage; one of the ways to use such storage is through the abstraction of a "block device". rbd devices can be mapped through a kernel driver and show up as block devices directly, or they can be used through userland libraries to provide backing store for things like QEMU virtual disks. The bs_rbd backing-store driver allows tgtd to use rbd images as backing storage without requiring the 'rbd' kernel block device driver, and so avoids kernel version dependencies and the extra context switching between kernel and userland. It also allows for userland caching of block images, managed by librbd. To use bs_rbd, select --bstype rbd, and set --backing-store to an rbd image specifier. This is of the form [poolname/]imagename[@snapname] where poolname is the RADOS pool containing the rbd image (defaults to "rbd"), imagename is the image you want to export, and snapname is the optional (readonly) snapshot of the image imagename. (Obviously when a snapshot is exported, writes are not supported.) You may also want to use the --bsopts parameter to pass Ceph-specific parameters to bs_rbd. Accepted keywords are conf= This sets the path to the ceph.conf file for the cluster that stgt should connect to for this target/lun creation (luns may use different cluster connections). id= This sets the id portion of the ceph "client name" (which is "type.id"). Type is fixed at "client", and the default name is "client.admin", but you probably want this to set id to something like "tgt" so that the name of the ceph client is "client.tgt". This lets you control permissions differently for the tgt client compared to others, and sets the default log path, etc. See the Ceph documentation regarding client names. cluster= This sets the Ceph cluster name, if you have multiple clusters or if your cluster name is anything other than "ceph". This is in turn used by librados to find the conf file and key files. To specify multiple options, separate them with ';', and since you are, make sure to quote the option string to protect the semicolon from the shell: --bsopts "conf=/usr/local/ceph/ceph.conf;id=tgt" tgt-1.0.85/doc/README.sbcjukebox000077500000000000000000000102011435417276200161760ustar00rootroot00000000000000#!/bin/sh # EXAMPLE of sbc jukebox # # This is a simple script to create and drive a sbcjukebox that contains # the $NUMSLOT most recent snapshots of a production SBC LUN. # It requires that you store the LUN files on a filesystem that can create # file level cow snapshots using "cp --reflink". # On linux this is supported on BTRFS and possibly other filesystems too. # # It creates a new target with one SBC decice and a mediachanger. # # # TID is the target id to use # TNAME is the target iqn name to use # FILE is the backing store file for a production LUN. This file must reside on # a cow snapshot capable filesystem. # SNAPDIR is where we keep the SMC media file as well as where all snapshots are # kept. This must be the same filesystem as FILE # NUMSLOTS is how many old snapshots we want to keep. # READONLYSNAPS is whether or not the snapshot device is readonly or not # sbcjukebox.sh start # to create the target and populate the storage elements. # # sbcjukebox.sh list # to show which snapshots are available # # sbcjukebox.sh snapshot # to take a new snapshot of the production LUN # TID=2 TNAME="iqn.ronnie.test-snapshot" FILE="/data/RHEL6/RHEL6-1.img" SNAPDIR="${FILE}.snapshots" NUMSLOTS=8 READONLYSNAPS="yes" [ -d "$SNAPDIR" ] || mkdir "$SNAPDIR" usage() { echo "sbcjukebox {start|list|snapshot|usage}" } prune_old_snapshots() { ls $SNAPDIR | egrep -v "^smc$" | sort -n -r | tail -n +${NUMSLOTS} | while read OLD; do echo "Deleting old snapshot $OLD" rm -f ${SNAPDIR}/${OLD} done } populate_elements() { ELEMENT=1024 SNAPSHOTS=`ls $SNAPDIR | egrep -v "^smc$" | sort -n | head -n ${NUMSLOTS}` for SNAP in $SNAPSHOTS; do tgtadm --mode logicalunit --op update --tid $TID --lun 2 --params element_type=2,address=$ELEMENT,barcode=$SNAP,sides=1 ELEMENT=`expr "$ELEMENT" "+" "1"` done LAST=`expr "1024" "+" "$NUMSLOTS"` while [ $ELEMENT -lt $LAST ]; do tgtadm --mode logicalunit --op update --tid $TID --lun 2 --params element_type=2,address=$ELEMENT,clear_slot=1 ELEMENT=`expr "$ELEMENT" "+" "1"` done tgtadm --mode logicalunit --op update --tid $TID --lun 2 --params element_type=2,address=`expr "$LAST" "-" "1"`,clear_slot=1 } snapshot() { SNAP=`date +"%y%m%d%H%M"` echo Create snapshot $SNAP cp --reflink $FILE $SNAPDIR/$SNAP prune_old_snapshots populate_elements } list() { echo Snapshots: ls $SNAPDIR | egrep -v "^smc$" } start() { #create a target tgtadm --lld iscsi --op new --mode target --tid $TID -T $TNAME # create the data transfer device tgtadm --op new --mode logicalunit --tid $TID --lun 1 -Y disk tgtadm --op update --mode logicalunit --tid $TID --lun 1 --params vendor_id=STGT,product_id=SNAPSHOT_SBC,product_rev=0010 [ "$READONLYSNAPS" != "yes" ] || { tgtadm --op update --mode logicalunit --tid $TID --lun 1 --params readonly=1 } # create backing store for SMC [ -f "${SNAPDIR}/smc" ] || dd if=/dev/zero of="${SNAPDIR}/smc" bs=1k count=1 # create the media changer tgtadm --op new --mode logicalunit --tid $TID --lun 2 -b "${SNAPDIR}/smc" --device-type=changer tgtadm --op update --mode logicalunit --tid $TID --lun 2 --params vendor_id=STGT,product_id=SNAPSHOT_SMC,product_rev=0010,removable=1 # Add a Data Transfer devices (1 drive) tgtadm --op update --mode logicalunit --tid $TID --lun 2 --params element_type=4,start_address=1,quantity=1 # Specify that the DISK above (LUN 1) is the data transfer device tgtadm --op update --mode logicalunit --tid $TID --lun 2 --params element_type=4,address=1,tid=$TID,lun=1 # Medium Transport Elements (robot arm / picker) tgtadm --op update --mode logicalunit --tid $TID --lun 2 --params element_type=1,start_address=16,quantity=1 # define path to virtual media tgtadm --op update --mode logicalunit --tid $TID --lun 2 --params media_home=${SNAPDIR} # create the storage elements tgtadm --op update --mode logicalunit --tid $TID --lun 2 --params element_type=2,start_address=1024,quantity=$NUMSLOTS prune_old_snapshots populate_elements tgtadm --lld iscsi --op bind --mode target --tid $TID -I ALL tgtadm --lld iscsi --op show --mode target } case "$1" in start) start ;; snapshot) snapshot ;; list) list ;; *) usage ;; esac tgt-1.0.85/doc/README.sheepdog000066400000000000000000000075771435417276200156600ustar00rootroot00000000000000Sheepdog is a distributed storage system for QEMU, iSCSI clients and RESTful services. It provides highly available block level storage volumes that can be attached to QEMU-based virtual machines. The volumes can also be attached to other virtual machines and operating systems run on baremetal hardware if they support iSCSI protocol. Sheepdog scales to several hundreds nodes, and supports advanced volume management features such as snapshot, cloning, and thin provisioning. Stuff like volumes, snapshots, QEMU's vm-state (from live snapshot), even ISO files can be stored in the Sheepdog cluster. With tgt, sheepdog's virtual disks can be used by iSCSI initiators. Below is a brief description of setup. 1. Install and launch tgt $ git clone https://github.com/fujita/tgt.git $ cd tgt $ make # make install 2. Create a sheepdog VDI There is no special procedures in this step. $ dog vdi create tgt0 100G Of course you can use existing VDIs as iSCSI logical units. 3. Setup iSCSI target provided by tgt One logical unit corresponds to one VDI of sheepdog. In this step, we create iSCSI target and logical unit which can be seen by iSCSI initiator. # tgtd # tgtadm --op new --mode target --tid 1 --lld iscsi --targetname iqn.2013-10.org.sheepdog-project # tgtadm --op new --mode lu --tid 1 --lun 2 --bstype sheepdog --backing-store unix:/sheep_store/sock:tgt0 # tgtadm --lld iscsi --op bind --mode target --tid 1 -I ALL The most important parameter is --backing-store which is required by tgtadm when we create the logical unit in the target (the third line of the above commands). With this parameter, we tell the tgtd process how to connect to the sheep process, which VDI we use as the logical unit, which snapshot ID of the VDI we use. The form of the --backing-store option is like this: unix:path_of_unix_domain_socket:vdi unix:path_of_unix_domain_socket:vdi:tag unix:path_of_unix_domain_socket:vdi:snapid tcp:host:port:vdi tcp:host:port:vdi:tag tcp:host:port:vdi:snapid We use a special case of the first one in the above command sequence. It means "connect to sheep process via unix domain socket protocol, path of unix domain socket is /sheep_store/sock, use VDI named tgt0 as a backing store of newly created logical unit". You can also specify sheep which is running on remote host via TCP. But if tgtd process is running on a same host of sheep process, there is no benefit of using TCP. Unix domain socket can provide faster connection than TCP. You can also specify tag or ID of snapshots via suffix of the --backing-store option. 4. Setup iSCSI session (example of the open-iscsi initiator on Linux) After setting up iSCSI target, you can use the VDIs from any virtual machines and operating systems which supports iSCSI initiator. Many of popular hypervisors and operating systems support it (e.g. VMware ESX Family, Linux, Windows, etc). In this example, the way of Linux + open-iscsi is described. At first, you have to install open-iscsi ( http://www.open-iscsi.org/ ) and launch it. Major linux distros provide their open-iscsi package. Below is a way of installation in Debian and Ubuntu based systems. # apt-get install open-iscsi # /etc/init.d/open-iscsi start Next, we need to let iscsid discover and login to the target we've already created in the above sequence. If the initiator is running on different host from the target, you have to change the IP addresses in the below commands. # iscsiadm -m discovery -t st -p 127.0.0.1 # iscsiadm -m node --targetname iqn.2013-10.org.sheepdog-project --portal 127.0.0.1:3260 --login New device files, e.g. /dev/sdb, will be created on your system after login completion. Now your system can use the speepdog VDIs like ordinal HDDs. ToDo Currently, iSCSI multipath is not supported by sheepdog. It will be implemented in v0.9.0. The latest version of this guide can be found here: https://github.com/sheepdog/sheepdog/wiki/General-protocol-support-%28iSCSI-and-NBD%29 tgt-1.0.85/doc/README.ssc000066400000000000000000000112261435417276200146340ustar00rootroot00000000000000Introduction ------------- The SSC emulation in TGTD can emulate tape drives by using files. This virtualization technology is known as a virtual tape library (VTL). Solaris tips ------------- If one wants to get VTL working on a Solaris initiator (and this target of course under Linux) then it is rather simple to get a tape drive working. Getting the changer device working under Solaris (OpenSolaris) take a bit more effort. This file describes the way to do it. Although most of it is probably well know to Solaris fans, it took me a bit of time to figure out to get the VTL target working fine under OpenSolaris. For those who want to give it a try, here are the commands: For normal disk and cd targets no files have to be changed. All you need is the following: # svcadm enable iscsi_initiator and check if it is online: # svcs iscsi_initiator the state should be online. Now we add the discovery address to the initiator: # iscsiadm add discovery-address Now we configure the discovery mode to sendtargets: # iscsiadm modify discovery -t enable Check the target: # iscsiadm list target -S And create the device files: # devfsadm -i iscsi Tape devices will be shown properly, but not the changer device, this takes a bit more work. Two files need to be changed, and a reboot is needed to set things up properly. In the file /etc/driver_aliases the two lines sgen "scsa,08.bfcp" sgen "scsa,08.bvhci" need to be replaced with sgen "scsiclass,08" The file /kernel/drv/sgen.conf, which is basically all commented out needs to have the following lines: device-type-config-list="changer","sequential"; inquiry-config-list= "*", "*"; name="sgen" class="scsi" target=0 lun=4; name="sgen" class="scsi" target=1 lun=4; name="sgen" class="scsi" target=2 lun=4; name="sgen" class="scsi" target=3 lun=4; name="sgen" class="scsi" target=4 lun=4; name="sgen" class="scsi" target=5 lun=4; name="sgen" class="scsi" target=6 lun=4; name="sgen" class="scsi" target=7 lun=4; name="sgen" class="scsi" target=8 lun=4; name="sgen" class="scsi" target=9 lun=4; name="sgen" class="scsi" target=10 lun=4; name="sgen" class="scsi" target=11 lun=4; name="sgen" class="scsi" target=12 lun=4; name="sgen" class="scsi" target=13 lun=4; name="sgen" class="scsi" target=14 lun=4; name="sgen" class="scsi" target=15 lun=4; For the last bit it should be noted that my changer was set to lun 4, and since I don't know which target I put all target numbers in (0-15). After these modifications the best is to reboot the machine, and do the iscsi commands above, and he presto it works: In my case, three tape drives and one changer: root@solar:/kernel/drv# iscsiadm list target -S Target: iqn.2008-09.com.example:server.tape Alias: - TPGT: 1 ISID: 4000002a0000 Connections: 1 LUN: 4 Vendor: STK Product: L700 OS Device Name: /dev/scsi/changer/c0t0d0 LUN: 3 Vendor: HP Product: LTO3 ULTRIUM OS Device Name: /dev/rmt/2n LUN: 2 Vendor: HP Product: LTO3 ULTRIUM OS Device Name: /dev/rmt/1n LUN: 1 Vendor: HP Product: LTO3 ULTRIUM OS Device Name: /dev/rmt/0n root@solar:/kernel/drv# mtx -f /dev/scsi/changer/c0t0d0 status Storage Changer /dev/scsi/changer/c0t0d0:3 Drives, 29 Slots ( 5 Import/Export ) Data Transfer Element 0:Empty Data Transfer Element 1:Empty Data Transfer Element 2:Empty Storage Element 1:Full :VolumeTag=A0000001 Storage Element 2:Full :VolumeTag=A0000002 Storage Element 3:Full :VolumeTag=A0000003 Storage Element 4:Full :VolumeTag=A0000004 Storage Element 5:Full :VolumeTag=A0000005 Storage Element 6:Full :VolumeTag=A0000006 Storage Element 7:Full :VolumeTag=A0000007 Storage Element 8:Full :VolumeTag=A0000008 Storage Element 9:Empty:VolumeTag= Storage Element 10:Empty:VolumeTag= Storage Element 11:Empty:VolumeTag= Storage Element 12:Empty:VolumeTag= Storage Element 13:Empty:VolumeTag= Storage Element 14:Empty:VolumeTag= Storage Element 15:Empty:VolumeTag= Storage Element 16:Empty:VolumeTag= Storage Element 17:Empty:VolumeTag= Storage Element 18:Empty:VolumeTag= Storage Element 19:Empty:VolumeTag= Storage Element 20:Empty:VolumeTag= Storage Element 21:Empty:VolumeTag= Storage Element 22:Empty:VolumeTag= Storage Element 23:Empty:VolumeTag= Storage Element 24:Full :VolumeTag=CLN00001 Storage Element 25 IMPORT/EXPORT:Empty:VolumeTag= Storage Element 26 IMPORT/EXPORT:Empty:VolumeTag= Storage Element 27 IMPORT/EXPORT:Empty:VolumeTag= Storage Element 28 IMPORT/EXPORT:Empty:VolumeTag= Storage Element 29 IMPORT/EXPORT:Empty:VolumeTag= tgt-1.0.85/doc/README.vtl000077500000000000000000000072331435417276200146570ustar00rootroot00000000000000#!/bin/sh # This is a simple script to aid in setting up a virtual tape library # (VTL) using STGT # # You can tweak the settings for how many tape drives and how many # and how big tapes the library should have using the variables below # Connection number. You can use multiple concurrent TGTD instances as # long as you give them unique CN numbers and they listen on different # portals CN=1 # Listen to port 3260 on wildcard for both ipv4 and ipv6 PORTAL=:3260 # iSCSI name for this target TARGET=iqn.2012-08.ronnie.vtl # Number of TAPE Drives for this virtual tape library NUM_DRIVES=2 # Number of tapes NUM_TAPES=8 # Tape size in MB TAPE_SIZE=1024 # Directory where the tape library state and media will be stored VTLDIR=$HOME/vtl # Create the directory to store the tapes if [ ! -d $VTLDIR ]; then echo Creating directory for VTL : $VTLDIR mkdir -p $VTLDIR fi # make sure we have a backing store for the media changer if [ ! -f $VTLDIR/smc ]; then echo Creating a backing store for the media changer : $VTLDIR/smc dd if=/dev/zero of=$VTLDIR/smc bs=1k count=1 2>/dev/null fi # Create the tapes seq 1 $NUM_TAPES | while read IDX; do TAPE=`echo $IDX | awk '{printf "%08d", $1}'` if [ ! -f $VTLDIR/$TAPE ]; then echo Creating blank tape : $TAPE tgtimg --op new --device-type tape --barcode $TAPE --size $TAPE_SIZE --type data --file $VTLDIR/$TAPE fi done # Restart tgtd echo Restart TGTD tgt-admin -C $CN --update ALL -c /dev/null -f tgtadm -C $CN --op delete --mode system tgtd -C $CN --iscsi portal=$PORTAL sleep 1 echo Create target tgtadm -C $CN --lld iscsi --op new --mode target --tid 1 -T $TARGET # Create the tape drives echo Create tape drives seq 1 $NUM_DRIVES | while read IDX; do TAPE=`echo $IDX | awk '{printf "%08d", $1}'` echo Create tape drive $IDX tgtadm -C $CN --lld iscsi --mode logicalunit --op new --tid 1 --lun $IDX -b $VTLDIR/$TAPE --device-type=tape tgtadm -C $CN --lld iscsi --mode logicalunit --op update --tid 1 --lun $IDX --params online=0 done # Create the changer echo Create media changer device LUN=`expr "$NUM_DRIVES" "+" "1"` tgtadm -C $CN --lld iscsi --mode logicalunit --op new --tid 1 --lun $LUN -b $VTLDIR/smc --device-type=changer tgtadm -C $CN --lld iscsi --mode logicalunit --op update --tid 1 --lun $LUN --params media_home=$VTLDIR # Add data transfer devices (one for each tape drive) echo Adding data transfer devices tgtadm -C $CN --lld iscsi --mode logicalunit --op update --tid 1 --lun $LUN --params element_type=4,start_address=1,quantity=$NUM_DRIVES seq 1 $NUM_DRIVES | while read IDX; do echo Adding data transfer device for drive $IDX tgtadm -C $CN --lld iscsi --mode logicalunit --op update --tid 1 --lun $LUN --params element_type=4,address=$IDX,tid=1,lun=$IDX done # Add medium transport elements (robot arm / picker) echo Adding medium transport elements tgtadm -C $CN --lld iscsi --mode logicalunit --op update --tid 1 --lun $LUN --params element_type=1,start_address=$LUN,quantity=1 # Storage elements echo Adding storage elements STORAGE=`expr "$LUN" "+" "1"` tgtadm -C $CN --lld iscsi --mode logicalunit --op update --tid 1 --lun $LUN --params element_type=2,start_address=$STORAGE,quantity=$NUM_TAPES # Add media to the storage slots seq 1 $NUM_TAPES | while read IDX; do TAPE=`echo $IDX | awk '{printf "%08d", $1}'` echo Loading $TAPE into storage slot $STORAGE tgtadm -C $CN --lld iscsi --mode logicalunit --op update --tid 1 --lun $LUN --params element_type=2,address=$STORAGE,barcode=$TAPE,sides=1 STORAGE=`expr "$STORAGE" "+" "1"` done # Allow anyone to access this target and the VTL tgtadm -C $CN --lld iscsi --op bind --mode target --tid 1 -I ALL # show all our good work tgtadm -C $CN --lld iscsi --op show --mode target tgt-1.0.85/doc/targets.conf.5.xml000066400000000000000000000323501435417276200164500ustar00rootroot00000000000000 targets.conf 5 targets.conf Linux SCSI Target Configuration File DESCRIPTION tgt-admin uses /etc/tgt/targets.conf to initialize tgtd configuration, by default. Its layout uses a HTML-like structure, with a hierarchy of nested tags to define targets and LUNs. CONFIGURATION FILE SYNTAX Global directives configure options that are global to tgtd's configuration, as well as defining exported targets, via target sections. Target sections contain directives specific to one target device. They define each target's exported LUNs via "backing-store" and "direct-store" directives, as well as other target-wide options. LUN directives ("backing-store" and "direct-store") may contain options specific to a single exported logical unit. GLOBAL DIRECTIVES With the exception of target directives, each of these should occur at most once. Defines a the start of a target definition. IQN is an ISCSI Qualified Name such as "iqn.2001-04.com.example:storage1". Within this block should be target-level directives, as documented below. The target definition ends with "</target>" Instead of specifying a driver type for each target, default-driver defines a default low-level driver for all exported targets. It may be overriden on a per-target basis. Valid lld values are "iscsi" or "iser". Since iscsi is assumed if this directive is not present, it is only generally needed if iser is the desired default. Include the configuration from another configuration file. Wildcards are allowed, so an example like "include /etc/tgt/xen/*.conf" is allowed. Errors from tgtadm will be ignored. Default is no. Define a different local socket key for communicating with tgtd. Defaults to 0. This is only generally useful if multiple instances of tgtd are in use. Define the address of the iSNS server in IPv4 dotted-quad address format. Required when using iSNS, otherwise ignored. Set iSNS access control. Valid values are "On" or "Off". Required when using iSNS, otherwise ignored. Set a different iSNS server port. Default is 3205. Optional when using iSNS, otherwise ignored. Enable iSNS. Only valid value is "On". Default is off. Define iscsi incoming discovery authentication setting. If no value is given, no authentication is performed. Define iscsi outgoing discovery authentication setting. If no value is given, no authentication is performed. TARGET-LEVEL DIRECTIVES Each target may export multiple block devices, or logical units (LUNs). For the purposes of LUN numbering, backing-store directives are processed before direct-store directives. Defines a logical unit (LUN) exported by the target. This may specify either a regular file, or a block device. Defines a direct mapped logical unit (LUN) with the same properties as the physical device (such as VENDOR_ID, SERIAL_NUM, etc.) Define the low-level driver to use for this target, either "iscsi" or "iser" (without quotes). This overrides the "default-driver" global directive. Allows connections only from the specified IP address. Defaults to ALL if no initiator-address directive is specified. Allows connections only from the specified initiator name. Define iscsi incoming authentication setting. If no "incominguser" is specified, it is not used. This directive may be used multiple times per target. Define iscsi outgoing authentication setting. If no "outgoinguser" is specified, it is not used. This directive may be used multiple times per target. Define the tid of the controller. Default is next available integer. LUN-LEVEL DIRECTIVES All of these may be listed at the target level (and apply to all LUNs) or within an individual LUN's definition, if it is defined using the container-style, multi-line definition, rather than single-line definition format: <backing-store /dev/sdb1> # LUN directives go here </backing-store> <direct-store /dev/sdb1> # LUN directives go here </direct-store> It is recommended to use either single-line or container-style LUN definitions within a target. Mixing styles can cause parser errors. "on" or "off", default on. Specify the block size for this LUN. Specify the Logical blocks per physical block exponent. By default TGTD will set the lbppbe to automatically match the underlying filesystem. Use this parameter to override that setting. This is an internal option that should not be set directly. Specify the lowest aligned logical block address. This is an internal option that should not be set directly. Specify the optimal transfer granularity, to be reflected in the Block Limits VPD. This is an internal option that should not be set directly. Specify the optimal transfer length, to be reflected in the Block Limits VPD. This is an internal option that should not be set directly. Pass additional parameters to tgtadm. CONFIGURATION FILE EXAMPLE Example configuration file: <target iqn.2007-04.com.example:san.monitoring> backing-store /dev/san/monitoring # if no "incominguser" is specified, it is not used incominguser backup secretpass12 # defaults to ALL if no "initiator-address" is specified initiator-address 192.168.1.2 </target> <target iqn.2007-02.com.example:san.xen1> backing-store /dev/san/xen1-disk1 # LUN1 direct-store /dev/san/xen1-disk2 # LUN2 initiator-address 192.168.1.2 # Allowed IP initiator-address 192.168.5.6 # Allowed IP incominguser user1 secretpass12 incominguser user2 secretpass23 outgoinguser userA secretpassA </target> <target iqn.2007-02.com.example:san.xen2> backing-store /dev/san/xen2 </target> <target iqn.2007-06.com.example:san.vmware1> backing-store /dev/san/vmware1 </target> FILES Configuration file for tgt-admin. Example configuration file for tgt-admin. SEE ALSO tgtd(8), tgtadm(8), tgtimg(8), tgt-setup-lun(8). REPORTING BUGS Report bugs to <stgt@vger.kernel.org> tgt-1.0.85/doc/tgt-admin.8.xml000066400000000000000000000162151435417276200157440ustar00rootroot00000000000000 tgt-admin 8 tgt-admin Linux SCSI Target Configuration Tool tgt-admin -e --execute --delete <value> --offline <value> --ready <value> -s --show -C --control-port <NNNN> -c --conf <file> --ignore-errors -f --force -p --pretend --dump -v --verbose -h --help DESCRIPTION tgt-admin is a utility which allows a persistent configuration of targets and luns. It uses tgtadm commands to create, delete and show targets. OPTIONS Read /etc/tgt/targets.conf and execute tgtadm commands. If the target already exists, it won't be processed. See --update. Delete all or selected targets. The target will be deleted only if it's not used (no initiator is connected to it). If you want to delete targets which are in use, you have to add the "--force" flag. Example usage: --delete ALL - delete all targets --delete tid=4 - delete target 4 (target with tid 4) --delete iqn.2008-08.com.example:some.target - delete this target Put all or selected targets in offline state. Example usage: --offline ALL - offline all targets --offline tid=4 - offline target 4 (target with tid 4) --offline iqn.2008-08.com.example:some.target - offline this target Put all or selected targets in ready state. Example usage: --ready ALL - ready all targets --ready tid=4 - ready target 4 (target with tid 4) --ready iqn.2008-08.com.example:some.target - ready this target Update all or selected targets. The target will be updated only if it's not used (no initiator is connected to it). If you want to update targets which are in use, you have to add "--force" flag. Example usage: --update ALL - ready all targets --update tid=4 - ready target 4 (target with tid 4) --update iqn.2008-08.com.example:some.target - update this target Show all the targets. It is possible to run multiple concurrent instances of tgtd on a host. This argument is used to control which instance the tgt-admin command will operate on. Specify an alternative configuration file instead of /etc/tgt/targets.conf, which is the default. Continue even if tgtadm exits with non-zero code. Force some operations even if the target is in use. Only print tgtadm options which would be used; don't execute any actions. Assumes -v. Dump current tgtd configuration. Note: does not include detailed parameters, like write caching. Increase verbosity (show tgtadm commands used). Display a list of available options and exit. FILES Configuration file for tgt-admin. Example configuration file for tgt-admin. SEE ALSO targets.conf(5), tgtd(8), tgtadm(8), tgtimg(8), tgt-setup-lun(8). REPORTING BUGS Report bugs to <stgt@vger.kernel.org> tgt-1.0.85/doc/tgt-setup-lun.8.xml000066400000000000000000000055401435417276200166070ustar00rootroot00000000000000 tgt-setup-lun 8 tgt-setup-lun Helper script that creates a target, adds a device to the target, and defines initiators that can connect to the target tgt-setup-lun -d <device> -n <targetname> -b <bsname> -t <transport> -C <control_port> initiator_IP -h DESCRIPTION Creates a target according to the supplied target_name. tgtd must be running. The format of the default IQN generated from the target name is: "iqn.2001-04.com.<hostname>-<target_name>", or a complete IQN may be given. The target name must be unique. The script then adds the requested device to the target. If specific IP addresses are defined, it adds them to the list of allowed initiators for that target. If no IP addresses is defined, it defines that the target accepts any initiator. EXAMPLES Create a target that uses /dev/sdb1 and allows connections only from 192.168.10.81: tgt-setup-lun -d /dev/sdb1 -n my_target 192.168.10.81 Create a target that uses /dev/sdb1 and allows connections only from 192.168.10.81 and 192.168.10.82: tgt-setup-lun -d /dev/sdb1 -n my_target 192.168.10.81 192.168.10.82 Create a target that uses /dev/sdb1 and allows connections from any initiator: tgt-setup-lun -d /dev/sdb1 -n my_target Display help: tgt-setup-lun -h AUTHOR Written by Erez Zilber SEE ALSO targets.conf(5), tgtd(8), tgtadm(8), tgtimg(8), tgt-admin(8). REPORTING BUGS Report bugs to <stgt@vger.kernel.org> COPYRIGHT Copyright � Voltaire Ltd. 2008. tgt-1.0.85/doc/tgtadm.8.xml000066400000000000000000000764771435417276200153600ustar00rootroot00000000000000 tgtadm 8 tgtadm Linux SCSI Target Administration Utility tgtadm [OPTIONS]... -C --control-port <port> -L --lld <driver> -o --op <operation> -m --mode <mode> -t --tid <id> -T --targetname <targetname> -y --blocksize <size> -Y --device-type <type> -l --lun <lun> -b --backing-store <path> -f --bsoflags {direct|sync} -S --bsopts {backing-store opt string} -E --bstype <type> -I --initiator-address <address> -Q --initiator-name <name> -n --name <parameter> -v --value <value> -P --params <param=value[,param=value...]> -F --force -h --help DESCRIPTION tgtadm is used to monitor and modify everything about Linux SCSI target software: targets, volumes, etc. OPTIONS It is possible to run multiple concurrent instances of tgtd on a host. This argument is used to control which instance the tgtadm command will operate on. Block devices are created with a default block size of 512 bytes. This argument can be used to create block devices with different block sizes. Example: tgtadm --lld iscsi --mode logicalunit --op new \ --tid <TID> --lun <LUN> \ -b <backing-file> --blocksize=4096 When creating a LUN, this parameter specifies the type of device to create. Default is disk. Possible device-types are: disk : emulate a disk device tape : emulate a tape reader ssc : same as tape cd : emulate a DVD drive changer : emulate a media changer device pt : passthrough type to export a /dev/sg device When creating a LUN, this parameter specifies the type of backend storage to to use. Possible backend types are: rdwr : Use normal file I/O. This is the default for disk devices aio : Use Asynchronous I/O rbd : Use Ceph's distributed-storage RADOS Block Device sg : Special backend type for passthrough devices ssc : Special backend type for tape emulation Add a new target with <id> and <name>. Delete specific target with <id>. The target must have no active I_T nexus. Delete specific target forcibly with <id>. Show all the targets. Show target parameters of a target with <id>. Add a new logical unit with <lun> to specific target with <id>. The logical unit is offered to the initiators. <path> must be block device files (including LVM and RAID devices) or regular files, or an RBD image or snapshot name for --bstype rbd. lun0 is reserved for a special device automatically created. Example: If tgt is compiled with the bs_rbd backing store for Ceph RBD images (see tgtadm --mode system --op show to verify), set up a target mapping the rbd image named "rbdimage", and pass options to bs_rbd: tgtadm --lld iscsi --op new --mode logicalunit --tid 1 \ --lun 1 --bstype=rbd --backing-store=rbdimage \ --bsopts="conf=/etc/ceph/ceph.conf;id=tgt" Delete specific logical unit with <lun> that the target with <id> has. Add the address to the access lists of the target with <id>. Initiators with the address can access to the target. 'ALL' is a special address to allow all initiators to access to a target. Add the initiator's name to the access lists of the target with <id>. Initiators with the names can access to the target. Delete the address from the access lists of the target with <id>. Delete the initiator's name from the access lists of the target with <id>. Change the value of <parameter> of the target with <id> to <value>. Sets/changes the value of one or more target parameters. Sets/changes the value of one or more logical unit parameters. Start the specified lld without restarting the tgtd process. Can be used to initialize lld driver in case required modules were loaded after tgtd was already executed. Display a list of available options and exits. LUN PARAMETERS These parameters are only applicable for "--mode logicalunit". This parameter sets the Vendor Identification string that a LUN will report in INQURY data. This parameter sets the Product Identification string that a LUN will report in INQURY data. This parameter sets the Product Revision string that a LUN will report in INQURY data. Example: tgtadm --lld iscsi --mode logicalunit --op update \ --tid <TID> --lun <LUN> \ --params vendor_id=TGTD,product_id=VirtualHD,product_rev=0103 This can be used to override/change the default setting for the removable flag. Disk devices default to non-removable while DVD and TAPE devices default to removable. This flag controls the format of sense data that the device will return. 0 = Clasic sense format, 1 = Support descriptor format. This controls whether a device is online or not. Devices default to be online when created but can be brought offline using this parameter. Behaviour of offline devices depend on device type. An MMC/DVD device that is offline will report that there is no disk in the unit but the actual MMC/DVD unit itself can still be communicated with. All other device types will fail all I/O with a sense code of Not Ready. Example: tgtadm --lld iscsi --mode logicalunit --op update \ --tid 1 --lun 1 \ --params removable=1,sense_format=1,online=1 This parameter is used to set specific mode pages for the device and the mode page contents. Most devices default to reasonable default mode pages automatically when the LUN is created, but this allows special settings. Examples: Create mode page '2', subpage 0 and 14 bytes of data. This is Disconnect-Reconnect mode page. tgtadm --mode logicalunit --op update --tid 1 --lun 2 \ --params mode_page=2:0:14:0x80:0x80:0:0xa:0:0:0:0:0:0:0:0:0:0 Create mode page '10', subpage 0 and 10 bytes of data. This is Control Extension mode page. tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 \ --params mode_page=10:0:10:2:0:0:0:0:0:0:0:2:0 Create mode page '0x1c', subpage 0 and 10 bytes of data. This is Informational Exceptions Control mode page. tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 \ --params mode_page=0x1c:0:10:8:0:0:0:0:0:0:0:0:0 This sets the read-only flag of a LUN. A read-only LUN will refuse any attempts to write data to it. This parameter only applies to DISK devices. tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 1 \ --params readonly=1 This controls the provisioning for the LUN. A thin-provisioned LUN is represented as a sparse file. TGTD supports provisioning type 2 for sparse files. When initiators use the SCSI UNMAP command TGTD will release the affected areas back to the filesystem using FALLOC_FL_PUNCH_HOLE. This parameter only applies to DISK devices. Thin-provisioning only works for LUNs stored on filesystems that support FALLOC_FL_PUNCH_HOLE. tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 1 \ --params thin_provisioning=1 SMC SPECIFIC LUN PARAMETERS These parameters are only applicable for luns that are of type "changer" i.e. the media changer device for a DVD Jukebox or a Virtual Tape Library. This controls which type of element a certain slot in the jukebox/vtl is. Slot types: 1 -> Medium Transport (picker arm) 2 -> Storage Element 3 -> Import/Export Element 4 -> Data Transfer device (CD drive, tape drive, MO drive etc) This is used to create/operate on a single slot. Address specifies the slot on which to perform the operation. This is used to create/operate on an entire range of slots at once. Start_address specifies the first address to operate on and quantity specifies the number of consequtive slots. When creating storage elements, i.e. "element_type=2", this parameter specifies if the media has 1 or two sides to hold data. This option is used to clear a storage element and remove any media that may be present. Once this command completes the storage element will show up as "Empty". This is used to assign a barcode to an element. Barcodes are limited to 10 characters in tgtd. This is used to assign a volume tag to SMC storage elements. If no volume tag is specified tgtd will use fall back to the barcode. The volume tag can be up to 32 characters. This parameter specifies a directory where all virtual media for the dvd/tape device elements are stored. To assign a media image file to a storage element slot, you assign "barcode" to be the name of the image file in the "media_home" directory. Example: How to create a DVD jukebox with eight disk trays and two empty DVD-R disks. # Create a target tgtadm --lld iscsi --mode target --op new --tid 1 --targetname iqn.2007-03:virtual-dvd:`hostname` # Create a DVD drive and give it a nice name # The dvd starts out without a backing store file, i.e. no disk loaded tgtadm --op new --mode logicalunit --tid 1 --lun 1 --device-type cd tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 1 --params vendor_id=STGT_DVD,product_id=DVD101,product_rev=0010,scsi_sn=STGTDVD01,removable=1 # We need a backend store file for the media changer if [ ! -f $HOME/smc ]; then dd if=/dev/zero of=$HOME/smc bs=1k count=1 fi # Create the SMC device and give it a nice name tgtadm --lld iscsi --mode logicalunit --op new --tid 1 --lun 2 --backing-store $HOME/smc --device-type changer tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params vendor_id=STK,product_id=L700,product_rev=0010,scsi_sn=XYZZY_0,removable=1 # Add a Data Transfer devices (1 drive) tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=4,start_address=1,quantity=1 # Specify that the DVD above (LUN 1) is the data transfer device we created tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=4,address=1,tid=1,lun=1 # Medium Transport Elements (robot arm / picker) tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=1,start_address=16,quantity=1 # define path to virtual media VTL=${HOME}/vtl mkdir -p ${VTL} tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params media_home=${VTL} # Storage Elements - 8 starting at addr 1024 tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=2,start_address=1024,quantity=8 # Add 'media' to slots 1 and 2 and leave the other 6 slots empty # slot 1 # Create empty writeable virtual DVD-R media tgtimg --op new --device-type cd --type dvd+r --file ${VTL}/DISK_001 tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=2,address=1024,barcode=DISK_001,volume_tag="A blank disk",sides=1 # slot 2 tgtimg --op new --device-type cd --type dvd+r --file ${VTL}/DISK_002 tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 2 --params element_type=2,address=1025,barcode=DISK_002,volume_tag="My second blank disk",sides=1 # Allow ALL initiators to connect to this target tgtadm --lld iscsi --mode target --op bind --tid 1 --initiator-address ALL # Show all our good work. tgtadm --lld iscsi --mode target --op show Passthrough devices In addition to device emulation TGTD also supports utilizing existing SG devices on the host and exporting these through a special passthrough device type. This specifies that an SG devices is used. This specifies that passthrough device type is used. This specifies which device to export through TGTD. This argument is used when creating a LUN to specify extra flags to use when opening the backing file. O_DIRECT is specified by "direct" and O_SYNC by "sync". Example: Make /dev/sg4 available to initiators connecting to TGTD. tgtadm --lld iscsi --op new --mode logicalunit --tid 1 --lun 1 --bstype=sg --device-type=pt --backing-store=/dev/sg4 Example: Open the backing file with O_SYNC. tgtadm --lld iscsi --op new --mode logicalunit --tid 1 --lun 1 --bsoflags="sync" --backing-store=/data/100m_image.raw Header Digest and Data Digest Header and data digests can be set on a per target parameter. TGTD supports two modes, None and CRC32C. When the digest is set to None, TDTD will negotiate that digests will not be used, and when CRC32C is set, TGTD will force the connection to use digest. Viewing the current settings This command is used to view the current settings for header/data digest. tgtadm --op show --mode target --tid 1 ... HeaderDigest=None DataDigest=None ... Setting digest Set header digest to CRC32C: tgtadm --op update --mode target --tid 1 -n HeaderDigest -v CRC32C Set data digest to None: tgtadm --op update --mode target --tid 1 -n DataDigest -v None CHAP Authentication CHAP authentication is supported to require authentication before an initiator is allowed to log in and access devices. CHAP main-phase authentication is set on the target level. To set up CHAP authentication we first need to create an account and its associated password, then we bind the account to one or more targets. Setting CHAP on a target These two commands create a user account and binds it to target 1. tgtadm --lld iscsi --op new --mode account --user ronnie --password password tgtadm --lld iscsi --op bind --mode account --tid 1 --user ronnie List all accounts This command is used to list all accounts that have been created. tgtadm --lld iscsi --op show --mode account Account list: ronnie Show if a target requires authentication When listing the targets, each target that has authantication enabled will contain a listing of all accoutns bound to that target. tgtadm --lld iscsi --op show --mode target Target 1: iqn.ronnie.test ... Account information: ronnie ... NOP-OUT Probes TGTD can send NOP-OUT probes to connected initiators to determine when an initiator is dead and then automatically clear and tear down the TCP connection. This can either be set as a global default from the tgtd command-line or it can be set for individual targets using the tgtadm command. Check the current NOP-OUT setting The tgtadm command is used to view the current setting for if/when to send NOP-OUT probes to connected initiators. If the target is configured to send NOP-OUT probes this will show up as two parameter lines in the target printout. If the target is not configured to send NOP-OUT these lines will not be printed at all. tgtadm --lld iscsi --op show --mode target Target 1: iqn.ronnie.test System information: Driver: iscsi State: ready Nop interval: 5 Nop count: 5 I_T nexus information: Setting NOP-OUT for a target The tgtadm command is used to change the NOP-OUT settings. tgtadm --op update --mode target --tid 1 -n nop_count -v 5 tgtadm --op update --mode target --tid 1 -n nop_interval -v 5 iSCSI PORTALS iSCSI portals can be viewed, added and removed at runtime. List portals This command is used to list the current iSCSI portals defined on the target: tgtadm --lld iscsi --op show --mode portal Portal: 10.1.1.101:3260,1 Portal: 127.0.0.1:3260,1 Add portal This command is used to add a portal to the target : tgtadm --lld iscsi --op new --mode portal --param portal=10.1.1.101:3260 Remove portal This command is used to remove a portal from the target : tgtadm --lld iscsi --op delete --mode portal --param portal=10.1.1.101:3260 iSCSI CONNECTIONS iSCSI connections can be viewed and forced closed at runtime. List all active connections for a target This command is used to list the all the active iSCSI connections to the target with connection id, initiator name and ip address for the initiator : tgtadm --lld iscsi --op show --mode conn --tid 1 Session: 2 Connection: 0 Initiator: iqn.2008-11.org.linux-kvm: IP Address: 127.0.0.1 Close an existing connection This command is used to close an iSCSI connection. Note that forcibly closing iSCSI connections can lead to data-loss. tgtadm --lld iscsi --op delete --mode conn --tid 1 --sid 2 --cid 0 Online/Offline Status Tgtd LUNs can be in online or offline status. LUNs that are Offline behave slightly different depending on the device type. Offline devices behave as if there is no media available and any operations that access media will fail with an check-condition error. Devices can not be set to Offline mode while there are "PREVENT ALLOW MEDIUM REMOVAL" locks on the device. Similarly media in Online devices can not be software ejected while there are such locks on the device (the 'eject' command will fail). Show Online/Offline status Finding the Online/Offline status of a LUN is done through the tgtd command. If "Prevent removal" is "Yes" this indicates that an application holds a "prevent media removal" lock on the device. tgtadm --lld iscsi --mode target --op show ... LUN: 2 Type: cd/dvd SCSI ID: IET 00010002 SCSI SN: beaf12 Size: 3432 MB, Block size: 1 Online: Yes Removable media: Yes Prevent removal: Yes ... Changing a LUN to Offline A LUN is changed to Offline status using the tgtadm command. When devices are set Offline these devices will behave as if there is no media loaded into the drive. Change a LUN to become offline. (no disk in the drive) tgtadm --tid 1 --lun 2 --op update --mode logicalunit -P Online=No iSNS PARAMETERS iSNS configuration for a target is by using the tgtadm command. This specifies the IP address of the iSNS server. TGTD only supprots one iSNS server. Example: tgtadm --op update --mode sys --name iSNSServerIP --value 192.168.11.133 This setting enables(on)/disables(off) iSNS. Example: tgtadm --op update --mode sys --name iSNS --value On This setting specifies the port to use for iSNS. Example: tgtadm --op update --mode sys --name iSNSServerPort --value 3205 Enable/disable access control for iSNS. Example: tgtadm --op update --mode sys --name iSNSAccessControl --value Off SEE ALSO tgtd(8), tgt-admin(8), tgtimg(8), tgt-setup-lun(8). REPORTING BUGS Report bugs to <stgt@vger.kernel.org> tgt-1.0.85/doc/tgtd.8.xml000066400000000000000000000134641435417276200150250ustar00rootroot00000000000000 tgtd 8 tgtd The SCSI Target Daemon tgtd tgtd -C --control-port <INTEGER> -d --debug <INTEGER> -f --foregound -h --help --iscsi <...> DESCRIPTION Tgtd is a SCSI Target daemon. It can be used to provide SCSI target service to a network. The most common service is iSCSI but other services are also supported. Device types Tgtd provides support for both emulated and passthrough of real devices. Tgtd can emulate the following types of devices: disk : Normal disk device. Emulates a SCSI harddisk. tape : Tape device. Emulates a SCSI tape drive. cd : CD device. Emulates a SCSI DVD burner. changer : Media changer. Emulate the changer device for a virtual tape library or DVD jukebox. OPTIONS -d --debug <INTEGER> Set to non-zero value to activate additional debugging messages to be logged. -f --foreground Run the daemon in the foreground. -h --help Print help text to the screen. -C --control-port <INTEGER> This comamnd is used to specify the control port to use for this instance of tgtd. This allows to run multiple instances of TGTD on a host. TGTADM has a matching argument to control which instance to connect to. --iscsi <...> ISCSI specific options. See the ISCSI section below for options specific to this frontend. ISCSI options These parameters apply only to the iSCSI frontend. portal=<ip-address[:port]> This option is used to bind tgtd to a specific ip-address/portal and/or port. By default tgtd will bind to port 3260 on the wildcard address. The ip-address part (before the ":") can be missing to designate the wildcard address with a none-default port. Example: to bind tgtd to a specific address and port tgtd --iscsi portal=192.0.2.1:3260 Example: to bind tgtd to any address but a none-default port tgtd --iscsi portal=:3251 nop_interval=<integer> This sets the default interval for sending NOP-OUT to probe for connected initiators. This parameter only controls the default value for targets. Individual targets can be controlled using tgtadm. The default value is 0 which means that the feature is disabled TGTD will not send any NOP-OUT probes. nop_count=<integer> This sets the default value for after how many failed probes TGTD will consider the initiator dead and tear down the session. This parameter only controls the default value for targets. Individual targets can be controlled using tgtadm. The default value is 0. Example: send NOP-OUT every 5 seconds and abort the session after 6 failures. tgtd --iscsi portal=192.0.2.1:3260,nop_interval=5,nop_count=6 ENVIRONMENT VARIABLES TGT_IPC_SOCKET=<path> When set tgtd and tgtadm will use the specified path as the IPC socket instead of the default '/var/run/tgtd/socket.0' SEE ALSO targets.conf(5), tgtadm(8), tgt-admin(8), tgtimg(8), tgt-setup-lun(8). COPYRIGHT/LICENSE 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, see http://www.gnu.org/licenses/. tgt-1.0.85/doc/tgtimg.8.xml000066400000000000000000000132541435417276200153530ustar00rootroot00000000000000 tgtimg 8 tgtimg Linux SCSI Target Framework Image File Utility tgtimg -o --op <operation> -Y --device-type <device-type> -b --barcode <barcode> -s --size <size> -t --type <media-type> -f --file <path> -T --thin-provisioning tgtimg --help DESCRIPTION Tgtimg is a utility to create and manage the image files used by the TGTD device emulation. This command is used to initialize device image files with the additional metadata, such as barcode, list of blocks, total size, etc that the TGTD emulation needs. OPTIONS Show a help screen and exit. Operation. Is either new to create a new image file or show to show the content of an existing image file. This specifies the type of image file. Supported device types are : cd : to create media for a dvd device disk : to create media for a disk device tape : to create media for a tape device When creating a new image, this specifies the type of media to emulate. The available mediatypes depend on the type of the device. Supported media types for cd devices are : dvd+r : create a blank writeable DVD+R disk Supported media types for disk devices are : disk : create an empty disk Supported media types for tape devices are : data : create a normal data tape clean : create a cleaning tape worm : create a worm When creating a new image, this argument specifies the barcode to use with this image file. Backup application software often uses barcodes to identify specific tapes. When emulating a VTL, make sure that all tape images use unique barcodes. When creating a new image, this specifies the size in megabytes for the virtual tape image. The filename of the image file. This argument makes the allocation of the image format use thin-provisioning. This means that the file created will be a sparse file that will allocate blocks from the filesystem on demand. Be careful when using thin-provisioning. If the filesystem fills up a iSCSI write to a thin-provisioned LUN can fail. Initiators generally do not handle "out of space" errors gracefully. Thin-provisioning uses FALLOC_FL_PUNCH_HOLE which is only available on some linux filesystems. Thin-provisioning can only be used for DISK images. EXAMPLES To create a new 100MByte disk tgtimg --op new --device-type disk --type disk --size 100 --file /data/hd001.raw To create a new tape image tgtimg --op new --device-type tape --barcode 12345 --size 100 --type data --file /data/tape001.img To view the content of an existing image tgtimg --op show --device-type tape --file /data/tape001.img To create a new blank DVD+R image tgtimg --op new --device-type cd --type dvd+r --file /data/dvd001.iso SEE ALSO tgtd(8), tgtadm(8), tgt-admin(8), tgt-setup-lun(8). REPORTING BUGS Report bugs to <stgt@vger.kernel.org> tgt-1.0.85/doc/tmf.txt000066400000000000000000000043271435417276200145200ustar00rootroot00000000000000The tgt Task Management Functions (TMF) works in the followings: - When LLDs queue scsi commands to tgt (scsi_tgt_queue_command), they need to specify unique 'tag' for each command. - LLDs call 'int scsi_tgt_tsk_mgmt_request(struct Scsi_Host *host, int, u64 tag, struct scsi_lun *lun, void *data)'. - int (* tsk_mgmt_response)(u64 data, int result) is added to scsi_host_template. The tag arg in scsi_tgt_queue_command is used only for ABORT_TASK to identify a single command. Initiators send a command with a unique tag so LLDs simply call scsi_tgt_queue_command() with it. With FCP and iSCSI, tag is 32-bit, however, unfortunately, SRP uses 64-bit tag. So we need 64-bit for it. When an initiator sends a task management request, the LLD calls scsi_tgt_tsk_mgmt_request. the LLD can use whatever it wants for the data arg. The data arg is used later as the arg in the tsk_mgmt_response callback. tgt core just sends the task management request to user space (by using TGT_KEVENT_TSK_MGMT_REQ). In the case of ABORT_TASK, tgtd finds a single command to abort and sends TGT_UEVENT_CMD_RSP and TGT_UEVENT_TSK_MGMT_RSP events. tgt core calls eh_abort_handler for TGT_UEVENT_CMD_RSP and then tsk_mgmt_response for TGT_UEVENT_TSK_MGMT_RSP. If tgtd fails to find a command to abort, it sends only TGT_UEVENT_TSK_MGMT_RSP event (no TGT_UEVENT_CMD_RSP event). In the case of the rests task management functions (like ABORT_TASK_SET), tgt needs to abort multiple commands. Thus, tgtd finds multiple commands to abort and sends multiple TGT_UEVENT_CMD_RSP events and a single TGT_UEVENT_TSK_MGMT_RSP event. tgt core calls eh_abort_handler multiple times and tsk_mgmt_response once. eh_abort_handler enables LLDs to safely free resource related with a command to abort. Note that when tgtd finds that a TGT_KEVENT_TSK_MGMT_REQ event tries to abort commands between in TGT_UEVENT_CMD_RSP and TGT_KEVENT_CMD_DONE states (that is, tgt calls something like bio_map_user for the commands), tgtd gives up. After all, we cannot abort such commands. For example, possibly, The commands already touch page cache. In this case, tgtd waits for the completion of the commands (TGT_KEVENT_CMD_DONE) and sends TGT_UEVENT_TSK_MGMT_RSP event with an appropriate result value. tgt-1.0.85/scripts/000077500000000000000000000000001435417276200141055ustar00rootroot00000000000000tgt-1.0.85/scripts/Makefile000066400000000000000000000003441435417276200155460ustar00rootroot00000000000000sbindir ?= $(PREFIX)/sbin SCRIPTS += tgt-setup-lun tgt-admin .PHONY: all all: .PHONY: install install: $(SCRIPTS) install -d -m 755 $(DESTDIR)$(sbindir) install -m 755 $(SCRIPTS) $(DESTDIR)$(sbindir) .PHONY: clean clean: tgt-1.0.85/scripts/build-pkg.sh000077500000000000000000000051051435417276200163230ustar00rootroot00000000000000#!/bin/bash # # Copyright (C) 2012 Roi Dayan # TARGET=$1 usage() { echo "Usage: `basename $0` [rpm|deb]" exit 1 } if [ "$TARGET" != "rpm" -a "$TARGET" != "deb" ]; then usage fi DIR=$(cd `dirname $0`; pwd) BASE=`cd $DIR/.. ; pwd` _TOP="$BASE/pkg" SPEC="tgtd.spec" LOG=/tmp/`basename $0`-$$.log # get branch name branch=`git branch | grep '^*' | sed 's/^..\(.*\)/\1/'` # get version tag version=`git describe --tags --abbrev=0 | sed "s/^v//g"` # release is number of commits since the version tag release=`git describe --tags | cut -d- -f2 | tr - _` if [ "$version" = "$release" ]; then # no commits and release can't be empty release=0 fi if [ "$branch" != "master" ]; then # if not under master branch include hash tag hash=`git rev-parse HEAD | cut -c 1-6` release="$release.$hash" fi echo "Building version: $version-$release" cp_src() { local dest=$1 cp -a conf $dest cp -a doc $dest cp -a scripts $dest cp -a usr $dest cp -a README $dest cp -a Makefile $dest } check() { local rc=$? local msg="$1" if (( rc )) ; then echo $msg exit 1 fi } build_rpm() { name=scsi-target-utils-$version-$release TARBALL=$name.tgz SRPM=$_TOP/SRPMS/$name.src.rpm echo "Creating rpm build dirs under $_TOP" mkdir -p $_TOP/{RPMS,SRPMS,SOURCES,BUILD,SPECS,tmp} mkdir -p $_TOP/tmp/$name cp_src $_TOP/tmp/$name echo "Creating tgz $TARBALL" tar -czf $_TOP/SOURCES/$TARBALL -C $_TOP/tmp $name echo "Creating rpm" sed -r "s/^Version:(\s*).*/Version:\1$version/;s/^Release:(\s*).*/Release:\1$release/" scripts/$SPEC > $_TOP/SPECS/$SPEC rpmbuild -bs --define="_topdir $_TOP" $_TOP/SPECS/$SPEC check "Failed to create source rpm." rpmbuild -bb --define="_topdir $_TOP" $_TOP/SPECS/$SPEC > $LOG 2>&1 check "Failed to build rpm. LOG: $LOG" # display created rpm files grep ^Wrote $LOG rm -fr $LOG } build_deb() { if ! which debuild >/dev/null 2>&1 ; then echo "Missing debuild. Please install devscripts package." exit 1 fi name=tgt_$version TARBALL=$name.orig.tar.gz echo "Building under $_TOP/$name" mkdir -p $_TOP/$name cp_src $_TOP/$name tar -czf $_TOP/$TARBALL -C $_TOP $name mkdir -p $_TOP/$name/debian cp -a scripts/deb/* $_TOP/$name/debian cd $_TOP/$name sed -i -r "s/^tgt \(([0-9.-]+)\) (.*)/tgt \($version-$release\) \2/" debian/changelog debuild -uc -us check "Failed building deb package." cd ../.. ls -l $_TOP/$name*.deb } cd $BASE build_$TARGET echo "Done." tgt-1.0.85/scripts/checkarch.sh000077500000000000000000000004141435417276200163560ustar00rootroot00000000000000#!/bin/sh arch=`gcc -dumpmachine` case $arch in `echo $arch | grep x86_64`) echo -m64 ;; `echo $arch | grep "i[3-6]86"`) echo -m32 ;; *) echo ' Failed to parse your architecture. Please run $ make check32 or $ make check64 manually. ' exit 1 ;; esac tgt-1.0.85/scripts/checkpatch.pl000077500000000000000000002764041435417276200165570ustar00rootroot00000000000000#!/usr/bin/perl -w # (c) 2001, Dave Jones. (the file handling bit) # (c) 2005, Joel Schopp (the ugly bit) # (c) 2007,2008, Andy Whitcroft (new conditions, test suite) # (c) 2008-2010 Andy Whitcroft # Licensed under the terms of the GNU GPL License version 2 use strict; my $P = $0; $P =~ s@.*/@@g; my $V = '0.32'; use Getopt::Long qw(:config no_auto_abbrev); my $quiet = 0; my $tree = 0; my $chk_signoff = 1; my $chk_patch = 1; my $tst_only; my $emacs = 0; my $terse = 0; my $file = 0; my $check = 0; my $summary = 1; my $mailback = 0; my $summary_file = 0; my $show_types = 0; my $root; my %debug; my %ignore_type = (); my @ignore = (); my $help = 0; my $configuration_file = ".checkpatch.conf"; sub help { my ($exitcode) = @_; print << "EOM"; Usage: $P [OPTION]... [FILE]... Version: $V (modified for stgt) Options: -q, --quiet quiet --tree run with a kernel tree --no-signoff do not check for 'Signed-off-by' line --patch treat FILE as patchfile (default) --emacs emacs compile window format -t, --terse one line per report -f, --file treat FILE as regular source file --subjective, --strict enable more subjective tests --ignore TYPE(,TYPE2...) ignore various comma separated message types --show-types show the message "types" in the output --root=PATH PATH to the kernel tree root --no-summary suppress the per-file summary --mailback only produce a report in case of warnings/errors --summary-file include the filename in summary --debug KEY=[0|1] turn on/off debugging of KEY, where KEY is one of 'values', 'possible', 'type', and 'attr' (default is all off) --test-only=WORD report only warnings/errors containing WORD literally -h, --help, --version display this help and exit When FILE is - read standard input. EOM exit($exitcode); } my $conf = which_conf($configuration_file); if (-f $conf) { my @conf_args; open(my $conffile, '<', "$conf") or warn "$P: Can't find a readable $configuration_file file $!\n"; while (<$conffile>) { my $line = $_; $line =~ s/\s*\n?$//g; $line =~ s/^\s*//g; $line =~ s/\s+/ /g; next if ($line =~ m/^\s*#/); next if ($line =~ m/^\s*$/); my @words = split(" ", $line); foreach my $word (@words) { last if ($word =~ m/^#/); push (@conf_args, $word); } } close($conffile); unshift(@ARGV, @conf_args) if @conf_args; } GetOptions( 'q|quiet+' => \$quiet, 'tree+' => \$tree, 'signoff!' => \$chk_signoff, 'patch!' => \$chk_patch, 'emacs!' => \$emacs, 't|terse!' => \$terse, 'f|file!' => \$file, 'subjective!' => \$check, 'strict!' => \$check, 'ignore=s' => \@ignore, 'show-types!' => \$show_types, 'root=s' => \$root, 'summary!' => \$summary, 'mailback!' => \$mailback, 'summary-file!' => \$summary_file, 'debug=s' => \%debug, 'test-only=s' => \$tst_only, 'h|help' => \$help, 'version' => \$help ) or help(1); help(0) if ($help); my $exit = 0; if ($#ARGV < 0) { print "$P: no input files\n"; exit(1); } @ignore = split(/,/, join(',',@ignore)); foreach my $word (@ignore) { $word =~ s/\s*\n?$//g; $word =~ s/^\s*//g; $word =~ s/\s+/ /g; $word =~ tr/[a-z]/[A-Z]/; next if ($word =~ m/^\s*#/); next if ($word =~ m/^\s*$/); $ignore_type{$word}++; } my $dbg_values = 0; my $dbg_possible = 0; my $dbg_type = 0; my $dbg_attr = 0; for my $key (keys %debug) { ## no critic eval "\${dbg_$key} = '$debug{$key}';"; die "$@" if ($@); } my $rpt_cleaners = 0; if ($terse) { $emacs = 1; $quiet++; } if ($tree) { if (defined $root) { if (!top_of_kernel_tree($root)) { die "$P: $root: --root does not point at a valid tree\n"; } } else { if (top_of_kernel_tree('.')) { $root = '.'; } elsif ($0 =~ m@(.*)/scripts/[^/]*$@ && top_of_kernel_tree($1)) { $root = $1; } } if (!defined $root) { print "Must be run from the top-level dir. of a kernel tree\n"; exit(2); } } my $emitted_corrupt = 0; our $Ident = qr{ [A-Za-z_][A-Za-z\d_]* (?:\s*\#\#\s*[A-Za-z_][A-Za-z\d_]*)* }x; our $Storage = qr{extern|static|asmlinkage}; our $Sparse = qr{ __user| __kernel| __force| __iomem| __must_check| __init_refok| __kprobes| __ref| __rcu }x; # Notes to $Attribute: # We need \b after 'init' otherwise 'initconst' will cause a false positive in a check our $Attribute = qr{ const| __percpu| __nocast| __safe| __bitwise__| __packed__| __packed2__| __naked| __maybe_unused| __always_unused| __noreturn| __used| __cold| __noclone| __deprecated| __read_mostly| __kprobes| __(?:mem|cpu|dev|)(?:initdata|initconst|init\b)| ____cacheline_aligned| ____cacheline_aligned_in_smp| ____cacheline_internodealigned_in_smp| __weak }x; our $Modifier; our $Inline = qr{inline|__always_inline|noinline}; our $Member = qr{->$Ident|\.$Ident|\[[^]]*\]}; our $Lval = qr{$Ident(?:$Member)*}; our $Constant = qr{(?i:(?:[0-9]+|0x[0-9a-f]+)[ul]*)}; our $Assignment = qr{(?:\*\=|/=|%=|\+=|-=|<<=|>>=|&=|\^=|\|=|=)}; our $Compare = qr{<=|>=|==|!=|<|>}; our $Operators = qr{ <=|>=|==|!=| =>|->|<<|>>|<|>|!|~| &&|\|\||,|\^|\+\+|--|&|\||\+|-|\*|\/|% }x; our $NonptrType; our $Type; our $Declare; our $NON_ASCII_UTF8 = qr{ [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 }x; our $UTF8 = qr{ [\x09\x0A\x0D\x20-\x7E] # ASCII | $NON_ASCII_UTF8 }x; our $typeTypedefs = qr{(?x: (?:__)?(?:u|s|be|le)(?:8|16|32|64)| atomic_t )}; our $logFunctions = qr{(?x: printk(?:_ratelimited|_once|)| [a-z0-9]+_(?:printk|emerg|alert|crit|err|warning|warn|notice|info|debug|dbg|vdbg|devel|cont|WARN)(?:_ratelimited|_once|)| WARN(?:_RATELIMIT|_ONCE|)| panic| MODULE_[A-Z_]+ )}; our $signature_tags = qr{(?xi: Signed-off-by:| Acked-by:| Tested-by:| Reviewed-by:| Reported-by:| To:| Cc: )}; our @typeList = ( qr{void}, qr{(?:unsigned\s+)?char}, qr{(?:unsigned\s+)?short}, qr{(?:unsigned\s+)?int}, qr{(?:unsigned\s+)?long}, qr{(?:unsigned\s+)?long\s+int}, qr{(?:unsigned\s+)?long\s+long}, qr{(?:unsigned\s+)?long\s+long\s+int}, qr{unsigned}, qr{float}, qr{double}, qr{bool}, qr{struct\s+$Ident}, qr{union\s+$Ident}, qr{enum\s+$Ident}, qr{${Ident}_t}, qr{${Ident}_handler}, qr{${Ident}_handler_fn}, ); our @modifierList = ( qr{fastcall}, ); our $allowed_asm_includes = qr{(?x: irq| memory )}; # memory.h: ARM has a custom one sub build_types { my $mods = "(?x: \n" . join("|\n ", @modifierList) . "\n)"; my $all = "(?x: \n" . join("|\n ", @typeList) . "\n)"; $Modifier = qr{(?:$Attribute|$Sparse|$mods)}; $NonptrType = qr{ (?:$Modifier\s+|const\s+)* (?: (?:typeof|__typeof__)\s*\([^\)]*\)| (?:$typeTypedefs\b)| (?:${all}\b) ) (?:\s+$Modifier|\s+const)* }x; $Type = qr{ $NonptrType (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*|\[\])+|(?:\s*\[\s*\])+)? (?:\s+$Inline|\s+$Modifier)* }x; $Declare = qr{(?:$Storage\s+)?$Type}; } build_types(); our $Typecast = qr{\s*(\(\s*$NonptrType\s*\)){0,1}\s*}; # Using $balanced_parens, $LvalOrFunc, or $FuncArg # requires at least perl version v5.10.0 # Any use must be runtime checked with $^V our $balanced_parens = qr/(\((?:[^\(\)]++|(?-1))*\))/; our $LvalOrFunc = qr{($Lval)\s*($balanced_parens{0,1})\s*}; our $FuncArg = qr{$Typecast{0,1}($LvalOrFunc|$Constant)}; sub deparenthesize { my ($string) = @_; return "" if (!defined($string)); $string =~ s@^\s*\(\s*@@g; $string =~ s@\s*\)\s*$@@g; $string =~ s@\s+@ @g; return $string; } $chk_signoff = 0 if ($file); my @dep_includes = (); my @dep_functions = (); my $removal = "Documentation/feature-removal-schedule.txt"; if ($tree && -f "$root/$removal") { open(my $REMOVE, '<', "$root/$removal") || die "$P: $removal: open failed - $!\n"; while (<$REMOVE>) { if (/^Check:\s+(.*\S)/) { for my $entry (split(/[, ]+/, $1)) { if ($entry =~ m@include/(.*)@) { push(@dep_includes, $1); } elsif ($entry !~ m@/@) { push(@dep_functions, $entry); } } } } close($REMOVE); } my @rawlines = (); my @lines = (); my $vname; for my $filename (@ARGV) { my $FILE; if ($file) { open($FILE, '-|', "diff -u /dev/null $filename") || die "$P: $filename: diff failed - $!\n"; } elsif ($filename eq '-') { open($FILE, '<&STDIN'); } else { open($FILE, '<', "$filename") || die "$P: $filename: open failed - $!\n"; } if ($filename eq '-') { $vname = 'Your patch'; } else { $vname = $filename; } while (<$FILE>) { chomp; push(@rawlines, $_); } close($FILE); if (!process($filename)) { $exit = 1; } @rawlines = (); @lines = (); } exit($exit); sub top_of_kernel_tree { my ($root) = @_; my @tree_check = ( "COPYING", "CREDITS", "Kbuild", "MAINTAINERS", "Makefile", "README", "Documentation", "arch", "include", "drivers", "fs", "init", "ipc", "kernel", "lib", "scripts", ); foreach my $check (@tree_check) { if (! -e $root . '/' . $check) { return 0; } } return 1; } sub parse_email { my ($formatted_email) = @_; my $name = ""; my $address = ""; my $comment = ""; if ($formatted_email =~ /^(.*)<(\S+\@\S+)>(.*)$/) { $name = $1; $address = $2; $comment = $3 if defined $3; } elsif ($formatted_email =~ /^\s*<(\S+\@\S+)>(.*)$/) { $address = $1; $comment = $2 if defined $2; } elsif ($formatted_email =~ /(\S+\@\S+)(.*)$/) { $address = $1; $comment = $2 if defined $2; $formatted_email =~ s/$address.*$//; $name = $formatted_email; $name =~ s/^\s+|\s+$//g; $name =~ s/^\"|\"$//g; # If there's a name left after stripping spaces and # leading quotes, and the address doesn't have both # leading and trailing angle brackets, the address # is invalid. ie: # "joe smith joe@smith.com" bad # "joe smith ]+>$/) { $name = ""; $address = ""; $comment = ""; } } $name =~ s/^\s+|\s+$//g; $name =~ s/^\"|\"$//g; $address =~ s/^\s+|\s+$//g; $address =~ s/^\<|\>$//g; if ($name =~ /[^\w \-]/i) { ##has "must quote" chars $name =~ s/(?"; } return $formatted_email; } sub which_conf { my ($conf) = @_; foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) { if (-e "$path/$conf") { return "$path/$conf"; } } return ""; } sub expand_tabs { my ($str) = @_; my $res = ''; my $n = 0; for my $c (split(//, $str)) { if ($c eq "\t") { $res .= ' '; $n++; for (; ($n % 8) != 0; $n++) { $res .= ' '; } next; } $res .= $c; $n++; } return $res; } sub copy_spacing { (my $res = shift) =~ tr/\t/ /c; return $res; } sub line_stats { my ($line) = @_; # Drop the diff line leader and expand tabs $line =~ s/^.//; $line = expand_tabs($line); # Pick the indent from the front of the line. my ($white) = ($line =~ /^(\s*)/); return (length($line), length($white)); } my $sanitise_quote = ''; sub sanitise_line_reset { my ($in_comment) = @_; if ($in_comment) { $sanitise_quote = '*/'; } else { $sanitise_quote = ''; } } sub sanitise_line { my ($line) = @_; my $res = ''; my $l = ''; my $qlen = 0; my $off = 0; my $c; # Always copy over the diff marker. $res = substr($line, 0, 1); for ($off = 1; $off < length($line); $off++) { $c = substr($line, $off, 1); # Comments we are wacking completly including the begin # and end, all to $;. if ($sanitise_quote eq '' && substr($line, $off, 2) eq '/*') { $sanitise_quote = '*/'; substr($res, $off, 2, "$;$;"); $off++; next; } if ($sanitise_quote eq '*/' && substr($line, $off, 2) eq '*/') { $sanitise_quote = ''; substr($res, $off, 2, "$;$;"); $off++; next; } if ($sanitise_quote eq '' && substr($line, $off, 2) eq '//') { $sanitise_quote = '//'; substr($res, $off, 2, $sanitise_quote); $off++; next; } # A \ in a string means ignore the next character. if (($sanitise_quote eq "'" || $sanitise_quote eq '"') && $c eq "\\") { substr($res, $off, 2, 'XX'); $off++; next; } # Regular quotes. if ($c eq "'" || $c eq '"') { if ($sanitise_quote eq '') { $sanitise_quote = $c; substr($res, $off, 1, $c); next; } elsif ($sanitise_quote eq $c) { $sanitise_quote = ''; } } #print "c<$c> SQ<$sanitise_quote>\n"; if ($off != 0 && $sanitise_quote eq '*/' && $c ne "\t") { substr($res, $off, 1, $;); } elsif ($off != 0 && $sanitise_quote eq '//' && $c ne "\t") { substr($res, $off, 1, $;); } elsif ($off != 0 && $sanitise_quote && $c ne "\t") { substr($res, $off, 1, 'X'); } else { substr($res, $off, 1, $c); } } if ($sanitise_quote eq '//') { $sanitise_quote = ''; } # The pathname on a #include may be surrounded by '<' and '>'. if ($res =~ /^.\s*\#\s*include\s+\<(.*)\>/) { my $clean = 'X' x length($1); $res =~ s@\<.*\>@<$clean>@; # The whole of a #error is a string. } elsif ($res =~ /^.\s*\#\s*(?:error|warning)\s+(.*)\b/) { my $clean = 'X' x length($1); $res =~ s@(\#\s*(?:error|warning)\s+).*@$1$clean@; } return $res; } sub ctx_statement_block { my ($linenr, $remain, $off) = @_; my $line = $linenr - 1; my $blk = ''; my $soff = $off; my $coff = $off - 1; my $coff_set = 0; my $loff = 0; my $type = ''; my $level = 0; my @stack = (); my $p; my $c; my $len = 0; my $remainder; while (1) { @stack = (['', 0]) if ($#stack == -1); #warn "CSB: blk<$blk> remain<$remain>\n"; # If we are about to drop off the end, pull in more # context. if ($off >= $len) { for (; $remain > 0; $line++) { last if (!defined $lines[$line]); next if ($lines[$line] =~ /^-/); $remain--; $loff = $len; $blk .= $lines[$line] . "\n"; $len = length($blk); $line++; last; } # Bail if there is no further context. #warn "CSB: blk<$blk> off<$off> len<$len>\n"; if ($off >= $len) { last; } if ($level == 0 && substr($blk, $off) =~ /^.\s*#\s*define/) { $level++; $type = '#'; } } $p = $c; $c = substr($blk, $off, 1); $remainder = substr($blk, $off); #warn "CSB: c<$c> type<$type> level<$level> remainder<$remainder> coff_set<$coff_set>\n"; # Handle nested #if/#else. if ($remainder =~ /^#\s*(?:ifndef|ifdef|if)\s/) { push(@stack, [ $type, $level ]); } elsif ($remainder =~ /^#\s*(?:else|elif)\b/) { ($type, $level) = @{$stack[$#stack - 1]}; } elsif ($remainder =~ /^#\s*endif\b/) { ($type, $level) = @{pop(@stack)}; } # Statement ends at the ';' or a close '}' at the # outermost level. if ($level == 0 && $c eq ';') { last; } # An else is really a conditional as long as its not else if if ($level == 0 && $coff_set == 0 && (!defined($p) || $p =~ /(?:\s|\}|\+)/) && $remainder =~ /^(else)(?:\s|{)/ && $remainder !~ /^else\s+if\b/) { $coff = $off + length($1) - 1; $coff_set = 1; #warn "CSB: mark coff<$coff> soff<$soff> 1<$1>\n"; #warn "[" . substr($blk, $soff, $coff - $soff + 1) . "]\n"; } if (($type eq '' || $type eq '(') && $c eq '(') { $level++; $type = '('; } if ($type eq '(' && $c eq ')') { $level--; $type = ($level != 0)? '(' : ''; if ($level == 0 && $coff < $soff) { $coff = $off; $coff_set = 1; #warn "CSB: mark coff<$coff>\n"; } } if (($type eq '' || $type eq '{') && $c eq '{') { $level++; $type = '{'; } if ($type eq '{' && $c eq '}') { $level--; $type = ($level != 0)? '{' : ''; if ($level == 0) { if (substr($blk, $off + 1, 1) eq ';') { $off++; } last; } } # Preprocessor commands end at the newline unless escaped. if ($type eq '#' && $c eq "\n" && $p ne "\\") { $level--; $type = ''; $off++; last; } $off++; } # We are truly at the end, so shuffle to the next line. if ($off == $len) { $loff = $len + 1; $line++; $remain--; } my $statement = substr($blk, $soff, $off - $soff + 1); my $condition = substr($blk, $soff, $coff - $soff + 1); #warn "STATEMENT<$statement>\n"; #warn "CONDITION<$condition>\n"; #print "coff<$coff> soff<$off> loff<$loff>\n"; return ($statement, $condition, $line, $remain + 1, $off - $loff + 1, $level); } sub statement_lines { my ($stmt) = @_; # Strip the diff line prefixes and rip blank lines at start and end. $stmt =~ s/(^|\n)./$1/g; $stmt =~ s/^\s*//; $stmt =~ s/\s*$//; my @stmt_lines = ($stmt =~ /\n/g); return $#stmt_lines + 2; } sub statement_rawlines { my ($stmt) = @_; my @stmt_lines = ($stmt =~ /\n/g); return $#stmt_lines + 2; } sub statement_block_size { my ($stmt) = @_; $stmt =~ s/(^|\n)./$1/g; $stmt =~ s/^\s*{//; $stmt =~ s/}\s*$//; $stmt =~ s/^\s*//; $stmt =~ s/\s*$//; my @stmt_lines = ($stmt =~ /\n/g); my @stmt_statements = ($stmt =~ /;/g); my $stmt_lines = $#stmt_lines + 2; my $stmt_statements = $#stmt_statements + 1; if ($stmt_lines > $stmt_statements) { return $stmt_lines; } else { return $stmt_statements; } } sub ctx_statement_full { my ($linenr, $remain, $off) = @_; my ($statement, $condition, $level); my (@chunks); # Grab the first conditional/block pair. ($statement, $condition, $linenr, $remain, $off, $level) = ctx_statement_block($linenr, $remain, $off); #print "F: c<$condition> s<$statement> remain<$remain>\n"; push(@chunks, [ $condition, $statement ]); if (!($remain > 0 && $condition =~ /^\s*(?:\n[+-])?\s*(?:if|else|do)\b/s)) { return ($level, $linenr, @chunks); } # Pull in the following conditional/block pairs and see if they # could continue the statement. for (;;) { ($statement, $condition, $linenr, $remain, $off, $level) = ctx_statement_block($linenr, $remain, $off); #print "C: c<$condition> s<$statement> remain<$remain>\n"; last if (!($remain > 0 && $condition =~ /^(?:\s*\n[+-])*\s*(?:else|do)\b/s)); #print "C: push\n"; push(@chunks, [ $condition, $statement ]); } return ($level, $linenr, @chunks); } sub ctx_block_get { my ($linenr, $remain, $outer, $open, $close, $off) = @_; my $line; my $start = $linenr - 1; my $blk = ''; my @o; my @c; my @res = (); my $level = 0; my @stack = ($level); for ($line = $start; $remain > 0; $line++) { next if ($rawlines[$line] =~ /^-/); $remain--; $blk .= $rawlines[$line]; # Handle nested #if/#else. if ($lines[$line] =~ /^.\s*#\s*(?:ifndef|ifdef|if)\s/) { push(@stack, $level); } elsif ($lines[$line] =~ /^.\s*#\s*(?:else|elif)\b/) { $level = $stack[$#stack - 1]; } elsif ($lines[$line] =~ /^.\s*#\s*endif\b/) { $level = pop(@stack); } foreach my $c (split(//, $lines[$line])) { ##print "C<$c>L<$level><$open$close>O<$off>\n"; if ($off > 0) { $off--; next; } if ($c eq $close && $level > 0) { $level--; last if ($level == 0); } elsif ($c eq $open) { $level++; } } if (!$outer || $level <= 1) { push(@res, $rawlines[$line]); } last if ($level == 0); } return ($level, @res); } sub ctx_block_outer { my ($linenr, $remain) = @_; my ($level, @r) = ctx_block_get($linenr, $remain, 1, '{', '}', 0); return @r; } sub ctx_block { my ($linenr, $remain) = @_; my ($level, @r) = ctx_block_get($linenr, $remain, 0, '{', '}', 0); return @r; } sub ctx_statement { my ($linenr, $remain, $off) = @_; my ($level, @r) = ctx_block_get($linenr, $remain, 0, '(', ')', $off); return @r; } sub ctx_block_level { my ($linenr, $remain) = @_; return ctx_block_get($linenr, $remain, 0, '{', '}', 0); } sub ctx_statement_level { my ($linenr, $remain, $off) = @_; return ctx_block_get($linenr, $remain, 0, '(', ')', $off); } sub ctx_locate_comment { my ($first_line, $end_line) = @_; # Catch a comment on the end of the line itself. my ($current_comment) = ($rawlines[$end_line - 1] =~ m@.*(/\*.*\*/)\s*(?:\\\s*)?$@); return $current_comment if (defined $current_comment); # Look through the context and try and figure out if there is a # comment. my $in_comment = 0; $current_comment = ''; for (my $linenr = $first_line; $linenr < $end_line; $linenr++) { my $line = $rawlines[$linenr - 1]; #warn " $line\n"; if ($linenr == $first_line and $line =~ m@^.\s*\*@) { $in_comment = 1; } if ($line =~ m@/\*@) { $in_comment = 1; } if (!$in_comment && $current_comment ne '') { $current_comment = ''; } $current_comment .= $line . "\n" if ($in_comment); if ($line =~ m@\*/@) { $in_comment = 0; } } chomp($current_comment); return($current_comment); } sub ctx_has_comment { my ($first_line, $end_line) = @_; my $cmt = ctx_locate_comment($first_line, $end_line); ##print "LINE: $rawlines[$end_line - 1 ]\n"; ##print "CMMT: $cmt\n"; return ($cmt ne ''); } sub raw_line { my ($linenr, $cnt) = @_; my $offset = $linenr - 1; $cnt++; my $line; while ($cnt) { $line = $rawlines[$offset++]; next if (defined($line) && $line =~ /^-/); $cnt--; } return $line; } sub cat_vet { my ($vet) = @_; my ($res, $coded); $res = ''; while ($vet =~ /([^[:cntrl:]]*)([[:cntrl:]]|$)/g) { $res .= $1; if ($2 ne '') { $coded = sprintf("^%c", unpack('C', $2) + 64); $res .= $coded; } } $res =~ s/$/\$/; return $res; } my $av_preprocessor = 0; my $av_pending; my @av_paren_type; my $av_pend_colon; sub annotate_reset { $av_preprocessor = 0; $av_pending = '_'; @av_paren_type = ('E'); $av_pend_colon = 'O'; } sub annotate_values { my ($stream, $type) = @_; my $res; my $var = '_' x length($stream); my $cur = $stream; print "$stream\n" if ($dbg_values > 1); while (length($cur)) { @av_paren_type = ('E') if ($#av_paren_type < 0); print " <" . join('', @av_paren_type) . "> <$type> <$av_pending>" if ($dbg_values > 1); if ($cur =~ /^(\s+)/o) { print "WS($1)\n" if ($dbg_values > 1); if ($1 =~ /\n/ && $av_preprocessor) { $type = pop(@av_paren_type); $av_preprocessor = 0; } } elsif ($cur =~ /^(\(\s*$Type\s*)\)/ && $av_pending eq '_') { print "CAST($1)\n" if ($dbg_values > 1); push(@av_paren_type, $type); $type = 'c'; } elsif ($cur =~ /^($Type)\s*(?:$Ident|,|\)|\(|\s*$)/) { print "DECLARE($1)\n" if ($dbg_values > 1); $type = 'T'; } elsif ($cur =~ /^($Modifier)\s*/) { print "MODIFIER($1)\n" if ($dbg_values > 1); $type = 'T'; } elsif ($cur =~ /^(\#\s*define\s*$Ident)(\(?)/o) { print "DEFINE($1,$2)\n" if ($dbg_values > 1); $av_preprocessor = 1; push(@av_paren_type, $type); if ($2 ne '') { $av_pending = 'N'; } $type = 'E'; } elsif ($cur =~ /^(\#\s*(?:undef\s*$Ident|include\b))/o) { print "UNDEF($1)\n" if ($dbg_values > 1); $av_preprocessor = 1; push(@av_paren_type, $type); } elsif ($cur =~ /^(\#\s*(?:ifdef|ifndef|if))/o) { print "PRE_START($1)\n" if ($dbg_values > 1); $av_preprocessor = 1; push(@av_paren_type, $type); push(@av_paren_type, $type); $type = 'E'; } elsif ($cur =~ /^(\#\s*(?:else|elif))/o) { print "PRE_RESTART($1)\n" if ($dbg_values > 1); $av_preprocessor = 1; push(@av_paren_type, $av_paren_type[$#av_paren_type]); $type = 'E'; } elsif ($cur =~ /^(\#\s*(?:endif))/o) { print "PRE_END($1)\n" if ($dbg_values > 1); $av_preprocessor = 1; # Assume all arms of the conditional end as this # one does, and continue as if the #endif was not here. pop(@av_paren_type); push(@av_paren_type, $type); $type = 'E'; } elsif ($cur =~ /^(\\\n)/o) { print "PRECONT($1)\n" if ($dbg_values > 1); } elsif ($cur =~ /^(__attribute__)\s*\(?/o) { print "ATTR($1)\n" if ($dbg_values > 1); $av_pending = $type; $type = 'N'; } elsif ($cur =~ /^(sizeof)\s*(\()?/o) { print "SIZEOF($1)\n" if ($dbg_values > 1); if (defined $2) { $av_pending = 'V'; } $type = 'N'; } elsif ($cur =~ /^(if|while|for)\b/o) { print "COND($1)\n" if ($dbg_values > 1); $av_pending = 'E'; $type = 'N'; } elsif ($cur =~/^(case)/o) { print "CASE($1)\n" if ($dbg_values > 1); $av_pend_colon = 'C'; $type = 'N'; } elsif ($cur =~/^(return|else|goto|typeof|__typeof__)\b/o) { print "KEYWORD($1)\n" if ($dbg_values > 1); $type = 'N'; } elsif ($cur =~ /^(\()/o) { print "PAREN('$1')\n" if ($dbg_values > 1); push(@av_paren_type, $av_pending); $av_pending = '_'; $type = 'N'; } elsif ($cur =~ /^(\))/o) { my $new_type = pop(@av_paren_type); if ($new_type ne '_') { $type = $new_type; print "PAREN('$1') -> $type\n" if ($dbg_values > 1); } else { print "PAREN('$1')\n" if ($dbg_values > 1); } } elsif ($cur =~ /^($Ident)\s*\(/o) { print "FUNC($1)\n" if ($dbg_values > 1); $type = 'V'; $av_pending = 'V'; } elsif ($cur =~ /^($Ident\s*):(?:\s*\d+\s*(,|=|;))?/) { if (defined $2 && $type eq 'C' || $type eq 'T') { $av_pend_colon = 'B'; } elsif ($type eq 'E') { $av_pend_colon = 'L'; } print "IDENT_COLON($1,$type>$av_pend_colon)\n" if ($dbg_values > 1); $type = 'V'; } elsif ($cur =~ /^($Ident|$Constant)/o) { print "IDENT($1)\n" if ($dbg_values > 1); $type = 'V'; } elsif ($cur =~ /^($Assignment)/o) { print "ASSIGN($1)\n" if ($dbg_values > 1); $type = 'N'; } elsif ($cur =~/^(;|{|})/) { print "END($1)\n" if ($dbg_values > 1); $type = 'E'; $av_pend_colon = 'O'; } elsif ($cur =~/^(,)/) { print "COMMA($1)\n" if ($dbg_values > 1); $type = 'C'; } elsif ($cur =~ /^(\?)/o) { print "QUESTION($1)\n" if ($dbg_values > 1); $type = 'N'; } elsif ($cur =~ /^(:)/o) { print "COLON($1,$av_pend_colon)\n" if ($dbg_values > 1); substr($var, length($res), 1, $av_pend_colon); if ($av_pend_colon eq 'C' || $av_pend_colon eq 'L') { $type = 'E'; } else { $type = 'N'; } $av_pend_colon = 'O'; } elsif ($cur =~ /^(\[)/o) { print "CLOSE($1)\n" if ($dbg_values > 1); $type = 'N'; } elsif ($cur =~ /^(-(?![->])|\+(?!\+)|\*|\&\&|\&)/o) { my $variant; print "OPV($1)\n" if ($dbg_values > 1); if ($type eq 'V') { $variant = 'B'; } else { $variant = 'U'; } substr($var, length($res), 1, $variant); $type = 'N'; } elsif ($cur =~ /^($Operators)/o) { print "OP($1)\n" if ($dbg_values > 1); if ($1 ne '++' && $1 ne '--') { $type = 'N'; } } elsif ($cur =~ /(^.)/o) { print "C($1)\n" if ($dbg_values > 1); } if (defined $1) { $cur = substr($cur, length($1)); $res .= $type x length($1); } } return ($res, $var); } sub possible { my ($possible, $line) = @_; my $notPermitted = qr{(?: ^(?: $Modifier| $Storage| $Type| DEFINE_\S+ )$| ^(?: goto| return| case| else| asm|__asm__| do| \#| \#\#| )(?:\s|$)| ^(?:typedef|struct|enum)\b )}x; warn "CHECK<$possible> ($line)\n" if ($dbg_possible > 2); if ($possible !~ $notPermitted) { # Check for modifiers. $possible =~ s/\s*$Storage\s*//g; $possible =~ s/\s*$Sparse\s*//g; if ($possible =~ /^\s*$/) { } elsif ($possible =~ /\s/) { $possible =~ s/\s*$Type\s*//g; for my $modifier (split(' ', $possible)) { if ($modifier !~ $notPermitted) { warn "MODIFIER: $modifier ($possible) ($line)\n" if ($dbg_possible); push(@modifierList, $modifier); } } } else { warn "POSSIBLE: $possible ($line)\n" if ($dbg_possible); push(@typeList, $possible); } build_types(); } else { warn "NOTPOSS: $possible ($line)\n" if ($dbg_possible > 1); } } my $prefix = ''; sub show_type { return !defined $ignore_type{$_[0]}; } sub report { if (!show_type($_[1]) || (defined $tst_only && $_[2] !~ /\Q$tst_only\E/)) { return 0; } my $line; if ($show_types) { $line = "$prefix$_[0]:$_[1]: $_[2]\n"; } else { $line = "$prefix$_[0]: $_[2]\n"; } $line = (split('\n', $line))[0] . "\n" if ($terse); push(our @report, $line); return 1; } sub report_dump { our @report; } sub ERROR { if (report("ERROR", $_[0], $_[1])) { our $clean = 0; our $cnt_error++; } } sub WARN { if (report("WARNING", $_[0], $_[1])) { our $clean = 0; our $cnt_warn++; } } sub CHK { if ($check && report("CHECK", $_[0], $_[1])) { our $clean = 0; our $cnt_chk++; } } sub check_absolute_file { my ($absolute, $herecurr) = @_; my $file = $absolute; ##print "absolute<$absolute>\n"; # See if any suffix of this path is a path within the tree. while ($file =~ s@^[^/]*/@@) { if (-f "$root/$file") { ##print "file<$file>\n"; last; } } if (! -f _) { return 0; } # It is, so see if the prefix is acceptable. my $prefix = $absolute; substr($prefix, -length($file)) = ''; ##print "prefix<$prefix>\n"; if ($prefix ne ".../") { WARN("USE_RELATIVE_PATH", "use relative pathname instead of absolute in changelog text\n" . $herecurr); } } sub pos_last_openparen { my ($line) = @_; my $pos = 0; my $opens = $line =~ tr/\(/\(/; my $closes = $line =~ tr/\)/\)/; my $last_openparen = 0; if (($opens == 0) || ($closes >= $opens)) { return -1; } my $len = length($line); for ($pos = 0; $pos < $len; $pos++) { my $string = substr($line, $pos); if ($string =~ /^($FuncArg|$balanced_parens)/) { $pos += length($1) - 1; } elsif (substr($line, $pos, 1) eq '(') { $last_openparen = $pos; } elsif (index($string, '(') == -1) { last; } } return $last_openparen + 1; } sub process { my $filename = shift; my $linenr=0; my $prevline=""; my $prevrawline=""; my $stashline=""; my $stashrawline=""; my $length; my $indent; my $previndent=0; my $stashindent=0; our $clean = 1; my $signoff = 0; my $is_patch = 0; my $in_header_lines = 1; my $in_commit_log = 0; #Scanning lines before patch our @report = (); our $cnt_lines = 0; our $cnt_error = 0; our $cnt_warn = 0; our $cnt_chk = 0; # Trace the real file/line as we go. my $realfile = ''; my $realline = 0; my $realcnt = 0; my $here = ''; my $in_comment = 0; my $comment_edge = 0; my $first_line = 0; my $p1_prefix = ''; my $prev_values = 'E'; # suppression flags my %suppress_ifbraces; my %suppress_whiletrailers; my %suppress_export; my $suppress_statement = 0; # Pre-scan the patch sanitizing the lines. # Pre-scan the patch looking for any __setup documentation. # my @setup_docs = (); my $setup_docs = 0; sanitise_line_reset(); my $line; foreach my $rawline (@rawlines) { $linenr++; $line = $rawline; if ($rawline=~/^\+\+\+\s+(\S+)/) { $setup_docs = 0; if ($1 =~ m@Documentation/kernel-parameters.txt$@) { $setup_docs = 1; } #next; } if ($rawline=~/^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@/) { $realline=$1-1; if (defined $2) { $realcnt=$3+1; } else { $realcnt=1+1; } $in_comment = 0; # Guestimate if this is a continuing comment. Run # the context looking for a comment "edge". If this # edge is a close comment then we must be in a comment # at context start. my $edge; my $cnt = $realcnt; for (my $ln = $linenr + 1; $cnt > 0; $ln++) { next if (defined $rawlines[$ln - 1] && $rawlines[$ln - 1] =~ /^-/); $cnt--; #print "RAW<$rawlines[$ln - 1]>\n"; last if (!defined $rawlines[$ln - 1]); if ($rawlines[$ln - 1] =~ m@(/\*|\*/)@ && $rawlines[$ln - 1] !~ m@"[^"]*(?:/\*|\*/)[^"]*"@) { ($edge) = $1; last; } } if (defined $edge && $edge eq '*/') { $in_comment = 1; } # Guestimate if this is a continuing comment. If this # is the start of a diff block and this line starts # ' *' then it is very likely a comment. if (!defined $edge && $rawlines[$linenr] =~ m@^.\s*(?:\*\*+| \*)(?:\s|$)@) { $in_comment = 1; } ##print "COMMENT:$in_comment edge<$edge> $rawline\n"; sanitise_line_reset($in_comment); } elsif ($realcnt && $rawline =~ /^(?:\+| |$)/) { # Standardise the strings and chars within the input to # simplify matching -- only bother with positive lines. $line = sanitise_line($rawline); } push(@lines, $line); if ($realcnt > 1) { $realcnt-- if ($line =~ /^(?:\+| |$)/); } else { $realcnt = 0; } #print "==>$rawline\n"; #print "-->$line\n"; if ($setup_docs && $line =~ /^\+/) { push(@setup_docs, $line); } } $prefix = ''; $realcnt = 0; $linenr = 0; foreach my $line (@lines) { $linenr++; my $rawline = $rawlines[$linenr - 1]; #extract the line range in the file after the patch is applied if ($line=~/^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@/) { $is_patch = 1; $first_line = $linenr + 1; $realline=$1-1; if (defined $2) { $realcnt=$3+1; } else { $realcnt=1+1; } annotate_reset(); $prev_values = 'E'; %suppress_ifbraces = (); %suppress_whiletrailers = (); %suppress_export = (); $suppress_statement = 0; next; # track the line number as we move through the hunk, note that # new versions of GNU diff omit the leading space on completely # blank context lines so we need to count that too. } elsif ($line =~ /^( |\+|$)/) { $realline++; $realcnt-- if ($realcnt != 0); # Measure the line length and indent. ($length, $indent) = line_stats($rawline); # Track the previous line. ($prevline, $stashline) = ($stashline, $line); ($previndent, $stashindent) = ($stashindent, $indent); ($prevrawline, $stashrawline) = ($stashrawline, $rawline); #warn "line<$line>\n"; } elsif ($realcnt == 1) { $realcnt--; } my $hunk_line = ($realcnt != 0); #make up the handle for any error we report on this line $prefix = "$filename:$realline: " if ($emacs && $file); $prefix = "$filename:$linenr: " if ($emacs && !$file); $here = "#$linenr: " if (!$file); $here = "#$realline: " if ($file); # extract the filename as it passes if ($line =~ /^diff --git.*?(\S+)$/) { $realfile = $1; $realfile =~ s@^([^/]*)/@@; $in_commit_log = 0; } elsif ($line =~ /^\+\+\+\s+(\S+)/) { $realfile = $1; $realfile =~ s@^([^/]*)/@@; $in_commit_log = 0; $p1_prefix = $1; if (!$file && $tree && $p1_prefix ne '' && -e "$root/$p1_prefix") { WARN("PATCH_PREFIX", "patch prefix '$p1_prefix' exists, appears to be a -p0 patch\n"); } if ($realfile =~ m@^include/asm/@) { ERROR("MODIFIED_INCLUDE_ASM", "do not modify files in include/asm, change architecture specific files in include/asm-\n" . "$here$rawline\n"); } next; } $here .= "FILE: $realfile:$realline:" if ($realcnt != 0); my $hereline = "$here\n$rawline\n"; my $herecurr = "$here\n$rawline\n"; my $hereprev = "$here\n$prevrawline\n$rawline\n"; $cnt_lines++ if ($realcnt != 0); # Check for incorrect file permissions if ($line =~ /^new (file )?mode.*[7531]\d{0,2}$/) { my $permhere = $here . "FILE: $realfile\n"; if ($realfile =~ /(Makefile|Kconfig|\.c|\.h|\.S|\.tmpl)$/) { ERROR("EXECUTE_PERMISSIONS", "do not set execute permissions for source files\n" . $permhere); } } # Check the patch for a signoff: if ($line =~ /^\s*signed-off-by:/i) { $signoff++; $in_commit_log = 0; } # Check signature styles if (!$in_header_lines && $line =~ /^(\s*)($signature_tags)(\s*)(.*)/) { my $space_before = $1; my $sign_off = $2; my $space_after = $3; my $email = $4; my $ucfirst_sign_off = ucfirst(lc($sign_off)); if (defined $space_before && $space_before ne "") { WARN("BAD_SIGN_OFF", "Do not use whitespace before $ucfirst_sign_off\n" . $herecurr); } if ($sign_off =~ /-by:$/i && $sign_off ne $ucfirst_sign_off) { WARN("BAD_SIGN_OFF", "'$ucfirst_sign_off' is the preferred signature form\n" . $herecurr); } if (!defined $space_after || $space_after ne " ") { WARN("BAD_SIGN_OFF", "Use a single space after $ucfirst_sign_off\n" . $herecurr); } my ($email_name, $email_address, $comment) = parse_email($email); my $suggested_email = format_email(($email_name, $email_address)); if ($suggested_email eq "") { ERROR("BAD_SIGN_OFF", "Unrecognized email address: '$email'\n" . $herecurr); } else { my $dequoted = $suggested_email; $dequoted =~ s/^"//; $dequoted =~ s/" $comment" ne $email && "$suggested_email$comment" ne $email) { WARN("BAD_SIGN_OFF", "email address '$email' might be better as '$suggested_email$comment'\n" . $herecurr); } } } # Check for wrappage within a valid hunk of the file if ($realcnt != 0 && $line !~ m{^(?:\+|-| |\\ No newline|$)}) { ERROR("CORRUPTED_PATCH", "patch seems to be corrupt (line wrapped?)\n" . $herecurr) if (!$emitted_corrupt++); } # Check for absolute kernel paths. if ($tree) { while ($line =~ m{(?:^|\s)(/\S*)}g) { my $file = $1; if ($file =~ m{^(.*?)(?::\d+)+:?$} && check_absolute_file($1, $herecurr)) { # } else { check_absolute_file($file, $herecurr); } } } # UTF-8 regex found at http://www.w3.org/International/questions/qa-forms-utf-8.en.php if (($realfile =~ /^$/ || $line =~ /^\+/) && $rawline !~ m/^$UTF8*$/) { my ($utf8_prefix) = ($rawline =~ /^($UTF8*)/); my $blank = copy_spacing($rawline); my $ptr = substr($blank, 0, length($utf8_prefix)) . "^"; my $hereptr = "$hereline$ptr\n"; CHK("INVALID_UTF8", "Invalid UTF-8, patch and commit message should be encoded in UTF-8\n" . $hereptr); } # Check if it's the start of a commit log # (not a header line and we haven't seen the patch filename) if ($in_header_lines && $realfile =~ /^$/ && $rawline !~ /^(commit\b|from\b|[\w-]+:).+$/i) { $in_header_lines = 0; $in_commit_log = 1; } # Still not yet in a patch, check for any UTF-8 if ($in_commit_log && $realfile =~ /^$/ && $rawline =~ /$NON_ASCII_UTF8/) { CHK("UTF8_BEFORE_PATCH", "8-bit UTF-8 used in possible commit log\n" . $herecurr); } # ignore non-hunk lines and lines being removed next if (!$hunk_line || $line =~ /^-/); #trailing whitespace if ($line =~ /^\+.*\015/) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; ERROR("DOS_LINE_ENDINGS", "DOS line endings\n" . $herevet); } elsif ($rawline =~ /^\+.*\S\s+$/ || $rawline =~ /^\+\s+$/) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; ERROR("TRAILING_WHITESPACE", "trailing whitespace\n" . $herevet); $rpt_cleaners = 1; } # check for Kconfig help text having a real description # Only applies when adding the entry originally, after that we do not have # sufficient context to determine whether it is indeed long enough. if ($realfile =~ /Kconfig/ && $line =~ /.\s*config\s+/) { my $length = 0; my $cnt = $realcnt; my $ln = $linenr + 1; my $f; my $is_start = 0; my $is_end = 0; for (; $cnt > 0 && defined $lines[$ln - 1]; $ln++) { $f = $lines[$ln - 1]; $cnt-- if ($lines[$ln - 1] !~ /^-/); $is_end = $lines[$ln - 1] =~ /^\+/; next if ($f =~ /^-/); if ($lines[$ln - 1] =~ /.\s*(?:bool|tristate)\s*\"/) { $is_start = 1; } elsif ($lines[$ln - 1] =~ /.\s*(?:---)?help(?:---)?$/) { $length = -1; } $f =~ s/^.//; $f =~ s/#.*//; $f =~ s/^\s+//; next if ($f =~ /^$/); if ($f =~ /^\s*config\s/) { $is_end = 1; last; } $length++; } WARN("CONFIG_DESCRIPTION", "please write a paragraph that describes the config symbol fully\n" . $herecurr) if ($is_start && $is_end && $length < 4); #print "is_start<$is_start> is_end<$is_end> length<$length>\n"; } if (($realfile =~ /Makefile.*/ || $realfile =~ /Kbuild.*/) && ($line =~ /\+(EXTRA_[A-Z]+FLAGS).*/)) { my $flag = $1; my $replacement = { 'EXTRA_AFLAGS' => 'asflags-y', 'EXTRA_CFLAGS' => 'ccflags-y', 'EXTRA_CPPFLAGS' => 'cppflags-y', 'EXTRA_LDFLAGS' => 'ldflags-y', }; WARN("DEPRECATED_VARIABLE", "Use of $flag is deprecated, please use \`$replacement->{$flag} instead.\n" . $herecurr) if ($replacement->{$flag}); } # check we are in a valid source file if not then ignore this hunk next if ($realfile !~ /\.(h|c|s|S|pl|sh)$/); #80 column limit if ($line =~ /^\+/ && $prevrawline !~ /\/\*\*/ && $rawline !~ /^.\s*\*\s*\@$Ident\s/ && !($line =~ /^\+\s*$logFunctions\s*\(\s*(?:(KERN_\S+\s*|[^"]*))?"[X\t]*"\s*(?:|,|\)\s*;)\s*$/ || $line =~ /^\+\s*"[^"]*"\s*(?:\s*|,|\)\s*;)\s*$/) && $length > 80) { WARN("LONG_LINE", "line over 80 characters\n" . $herecurr); } # Check for user-visible strings broken across lines, which breaks the ability # to grep for the string. Limited to strings used as parameters (those # following an open parenthesis), which almost completely eliminates false # positives, as well as warning only once per parameter rather than once per # line of the string. Make an exception when the previous string ends in a # newline (multiple lines in one string constant) or \n\t (common in inline # assembly to indent the instruction on the following line). if ($line =~ /^\+\s*"/ && $prevline =~ /"\s*$/ && $prevline =~ /\(/ && $prevrawline !~ /\\n(?:\\t)*"\s*$/) { WARN("SPLIT_STRING", "quoted string split across lines\n" . $hereprev); } # check for spaces before a quoted newline if ($rawline =~ /^.*\".*\s\\n/) { WARN("QUOTED_WHITESPACE_BEFORE_NEWLINE", "unnecessary whitespace before a quoted newline\n" . $herecurr); } # check for adding lines without a newline. if ($line =~ /^\+/ && defined $lines[$linenr] && $lines[$linenr] =~ /^\\ No newline at end of file/) { WARN("MISSING_EOF_NEWLINE", "adding a line without newline at end of file\n" . $herecurr); } # Blackfin: use hi/lo macros if ($realfile =~ m@arch/blackfin/.*\.S$@) { if ($line =~ /\.[lL][[:space:]]*=.*&[[:space:]]*0x[fF][fF][fF][fF]/) { my $herevet = "$here\n" . cat_vet($line) . "\n"; ERROR("LO_MACRO", "use the LO() macro, not (... & 0xFFFF)\n" . $herevet); } if ($line =~ /\.[hH][[:space:]]*=.*>>[[:space:]]*16/) { my $herevet = "$here\n" . cat_vet($line) . "\n"; ERROR("HI_MACRO", "use the HI() macro, not (... >> 16)\n" . $herevet); } } # check we are in a valid source file C or perl if not then ignore this hunk next if ($realfile !~ /\.(h|c|pl)$/); # at the beginning of a line any tabs must come first and anything # more than 8 must use tabs. if ($rawline =~ /^\+\s* \t\s*\S/ || $rawline =~ /^\+\s* \s*/) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; ERROR("CODE_INDENT", "code indent should use tabs where possible\n" . $herevet); $rpt_cleaners = 1; } # check for space before tabs. if ($rawline =~ /^\+/ && $rawline =~ / \t/) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; WARN("SPACE_BEFORE_TAB", "please, no space before tabs\n" . $herevet); } # check for && or || at the start of a line if ($rawline =~ /^\+\s*(&&|\|\|)/) { CHK("LOGICAL_CONTINUATIONS", "Logical continuations should be on the previous line\n" . $hereprev); } # check multi-line statement indentation matches previous line if ($^V && $^V ge 5.10.0 && $prevline =~ /^\+(\t*)(if \(|$Ident\().*(\&\&|\|\||,)\s*$/) { $prevline =~ /^\+(\t*)(.*)$/; my $oldindent = $1; my $rest = $2; my $pos = pos_last_openparen($rest); if ($pos >= 0) { $line =~ /^\+([ \t]*)/; my $newindent = $1; my $goodtabindent = $oldindent . "\t" x ($pos / 8) . " " x ($pos % 8); my $goodspaceindent = $oldindent . " " x $pos; if ($newindent ne $goodtabindent && $newindent ne $goodspaceindent) { CHK("PARENTHESIS_ALIGNMENT", "Alignment should match open parenthesis\n" . $hereprev); } } } if ($line =~ /^\+.*\*[ \t]*\)[ \t]+/) { CHK("SPACING", "No space is necessary after a cast\n" . $hereprev); } # check for spaces at the beginning of a line. # Exceptions: # 1) within comments # 2) indented preprocessor commands # 3) hanging labels if ($rawline =~ /^\+ / && $line !~ /\+ *(?:$;|#|$Ident:)/) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; WARN("LEADING_SPACE", "please, no spaces at the start of a line\n" . $herevet); } # check we are in a valid C source file if not then ignore this hunk next if ($realfile !~ /\.(h|c)$/); # check for RCS/CVS revision markers if ($rawline =~ /^\+.*\$(Revision|Log|Id)(?:\$|)/) { WARN("CVS_KEYWORD", "CVS style keyword markers, these will _not_ be updated\n". $herecurr); } # Blackfin: don't use __builtin_bfin_[cs]sync if ($line =~ /__builtin_bfin_csync/) { my $herevet = "$here\n" . cat_vet($line) . "\n"; ERROR("CSYNC", "use the CSYNC() macro in asm/blackfin.h\n" . $herevet); } if ($line =~ /__builtin_bfin_ssync/) { my $herevet = "$here\n" . cat_vet($line) . "\n"; ERROR("SSYNC", "use the SSYNC() macro in asm/blackfin.h\n" . $herevet); } # Check for potential 'bare' types my ($stat, $cond, $line_nr_next, $remain_next, $off_next, $realline_next); #print "LINE<$line>\n"; if ($linenr >= $suppress_statement && $realcnt && $line =~ /.\s*\S/) { ($stat, $cond, $line_nr_next, $remain_next, $off_next) = ctx_statement_block($linenr, $realcnt, 0); $stat =~ s/\n./\n /g; $cond =~ s/\n./\n /g; #print "linenr<$linenr> <$stat>\n"; # If this statement has no statement boundaries within # it there is no point in retrying a statement scan # until we hit end of it. my $frag = $stat; $frag =~ s/;+\s*$//; if ($frag !~ /(?:{|;)/) { #print "skip<$line_nr_next>\n"; $suppress_statement = $line_nr_next; } # Find the real next line. $realline_next = $line_nr_next; if (defined $realline_next && (!defined $lines[$realline_next - 1] || substr($lines[$realline_next - 1], $off_next) =~ /^\s*$/)) { $realline_next++; } my $s = $stat; $s =~ s/{.*$//s; # Ignore goto labels. if ($s =~ /$Ident:\*$/s) { # Ignore functions being called } elsif ($s =~ /^.\s*$Ident\s*\(/s) { } elsif ($s =~ /^.\s*else\b/s) { # declarations always start with types } elsif ($prev_values eq 'E' && $s =~ /^.\s*(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?((?:\s*$Ident)+?)\b(?:\s+$Sparse)?\s*\**\s*(?:$Ident|\(\*[^\)]*\))(?:\s*$Modifier)?\s*(?:;|=|,|\()/s) { my $type = $1; $type =~ s/\s+/ /g; possible($type, "A:" . $s); # definitions in global scope can only start with types } elsif ($s =~ /^.(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?($Ident)\b\s*(?!:)/s) { possible($1, "B:" . $s); } # any (foo ... *) is a pointer cast, and foo is a type while ($s =~ /\(($Ident)(?:\s+$Sparse)*[\s\*]+\s*\)/sg) { possible($1, "C:" . $s); } # Check for any sort of function declaration. # int foo(something bar, other baz); # void (*store_gdt)(x86_descr_ptr *); if ($prev_values eq 'E' && $s =~ /^(.(?:typedef\s*)?(?:(?:$Storage|$Inline)\s*)*\s*$Type\s*(?:\b$Ident|\(\*\s*$Ident\))\s*)\(/s) { my ($name_len) = length($1); my $ctx = $s; substr($ctx, 0, $name_len + 1, ''); $ctx =~ s/\)[^\)]*$//; for my $arg (split(/\s*,\s*/, $ctx)) { if ($arg =~ /^(?:const\s+)?($Ident)(?:\s+$Sparse)*\s*\**\s*(:?\b$Ident)?$/s || $arg =~ /^($Ident)$/s) { possible($1, "D:" . $s); } } } } # # Checks which may be anchored in the context. # # Check for switch () and associated case and default # statements should be at the same indent. if ($line=~/\bswitch\s*\(.*\)/) { my $err = ''; my $sep = ''; my @ctx = ctx_block_outer($linenr, $realcnt); shift(@ctx); for my $ctx (@ctx) { my ($clen, $cindent) = line_stats($ctx); if ($ctx =~ /^\+\s*(case\s+|default:)/ && $indent != $cindent) { $err .= "$sep$ctx\n"; $sep = ''; } else { $sep = "[...]\n"; } } if ($err ne '') { ERROR("SWITCH_CASE_INDENT_LEVEL", "switch and case should be at the same indent\n$hereline$err"); } } # if/while/etc brace do not go on next line, unless defining a do while loop, # or if that brace on the next line is for something else if ($line =~ /(.*)\b((?:if|while|for|switch)\s*\(|do\b|else\b)/ && $line !~ /^.\s*\#/) { my $pre_ctx = "$1$2"; my ($level, @ctx) = ctx_statement_level($linenr, $realcnt, 0); if ($line =~ /^\+\t{6,}/) { WARN("DEEP_INDENTATION", "Too many leading tabs - consider code refactoring\n" . $herecurr); } my $ctx_cnt = $realcnt - $#ctx - 1; my $ctx = join("\n", @ctx); my $ctx_ln = $linenr; my $ctx_skip = $realcnt; while ($ctx_skip > $ctx_cnt || ($ctx_skip == $ctx_cnt && defined $lines[$ctx_ln - 1] && $lines[$ctx_ln - 1] =~ /^-/)) { ##print "SKIP<$ctx_skip> CNT<$ctx_cnt>\n"; $ctx_skip-- if (!defined $lines[$ctx_ln - 1] || $lines[$ctx_ln - 1] !~ /^-/); $ctx_ln++; } #print "realcnt<$realcnt> ctx_cnt<$ctx_cnt>\n"; #print "pre<$pre_ctx>\nline<$line>\nctx<$ctx>\nnext<$lines[$ctx_ln - 1]>\n"; if ($ctx !~ /{\s*/ && defined($lines[$ctx_ln -1]) && $lines[$ctx_ln - 1] =~ /^\+\s*{/) { ERROR("OPEN_BRACE", "that open brace { should be on the previous line\n" . "$here\n$ctx\n$rawlines[$ctx_ln - 1]\n"); } if ($level == 0 && $pre_ctx !~ /}\s*while\s*\($/ && $ctx =~ /\)\s*\;\s*$/ && defined $lines[$ctx_ln - 1]) { my ($nlength, $nindent) = line_stats($lines[$ctx_ln - 1]); if ($nindent > $indent) { WARN("TRAILING_SEMICOLON", "trailing semicolon indicates no statements, indent implies otherwise\n" . "$here\n$ctx\n$rawlines[$ctx_ln - 1]\n"); } } } # Check relative indent for conditionals and blocks. if ($line =~ /\b(?:(?:if|while|for)\s*\(|do\b)/ && $line !~ /^.\s*#/ && $line !~ /\}\s*while\s*/) { ($stat, $cond, $line_nr_next, $remain_next, $off_next) = ctx_statement_block($linenr, $realcnt, 0) if (!defined $stat); my ($s, $c) = ($stat, $cond); substr($s, 0, length($c), ''); # Make sure we remove the line prefixes as we have # none on the first line, and are going to readd them # where necessary. $s =~ s/\n./\n/gs; # Find out how long the conditional actually is. my @newlines = ($c =~ /\n/gs); my $cond_lines = 1 + $#newlines; # We want to check the first line inside the block # starting at the end of the conditional, so remove: # 1) any blank line termination # 2) any opening brace { on end of the line # 3) any do (...) { my $continuation = 0; my $check = 0; $s =~ s/^.*\bdo\b//; $s =~ s/^\s*{//; if ($s =~ s/^\s*\\//) { $continuation = 1; } if ($s =~ s/^\s*?\n//) { $check = 1; $cond_lines++; } # Also ignore a loop construct at the end of a # preprocessor statement. if (($prevline =~ /^.\s*#\s*define\s/ || $prevline =~ /\\\s*$/) && $continuation == 0) { $check = 0; } my $cond_ptr = -1; $continuation = 0; while ($cond_ptr != $cond_lines) { $cond_ptr = $cond_lines; # If we see an #else/#elif then the code # is not linear. if ($s =~ /^\s*\#\s*(?:else|elif)/) { $check = 0; } # Ignore: # 1) blank lines, they should be at 0, # 2) preprocessor lines, and # 3) labels. if ($continuation || $s =~ /^\s*?\n/ || $s =~ /^\s*#\s*?/ || $s =~ /^\s*$Ident\s*:/) { $continuation = ($s =~ /^.*?\\\n/) ? 1 : 0; if ($s =~ s/^.*?\n//) { $cond_lines++; } } } my (undef, $sindent) = line_stats("+" . $s); my $stat_real = raw_line($linenr, $cond_lines); # Check if either of these lines are modified, else # this is not this patch's fault. if (!defined($stat_real) || $stat !~ /^\+/ && $stat_real !~ /^\+/) { $check = 0; } if (defined($stat_real) && $cond_lines > 1) { $stat_real = "[...]\n$stat_real"; } #print "line<$line> prevline<$prevline> indent<$indent> sindent<$sindent> check<$check> continuation<$continuation> s<$s> cond_lines<$cond_lines> stat_real<$stat_real> stat<$stat>\n"; if ($check && (($sindent % 8) != 0 || ($sindent <= $indent && $s ne ''))) { WARN("SUSPECT_CODE_INDENT", "suspect code indent for conditional statements ($indent, $sindent)\n" . $herecurr . "$stat_real\n"); } } # Track the 'values' across context and added lines. my $opline = $line; $opline =~ s/^./ /; my ($curr_values, $curr_vars) = annotate_values($opline . "\n", $prev_values); $curr_values = $prev_values . $curr_values; if ($dbg_values) { my $outline = $opline; $outline =~ s/\t/ /g; print "$linenr > .$outline\n"; print "$linenr > $curr_values\n"; print "$linenr > $curr_vars\n"; } $prev_values = substr($curr_values, -1); #ignore lines not being added if ($line=~/^[^\+]/) {next;} # TEST: allow direct testing of the type matcher. if ($dbg_type) { if ($line =~ /^.\s*$Declare\s*$/) { ERROR("TEST_TYPE", "TEST: is type\n" . $herecurr); } elsif ($dbg_type > 1 && $line =~ /^.+($Declare)/) { ERROR("TEST_NOT_TYPE", "TEST: is not type ($1 is)\n". $herecurr); } next; } # TEST: allow direct testing of the attribute matcher. if ($dbg_attr) { if ($line =~ /^.\s*$Modifier\s*$/) { ERROR("TEST_ATTR", "TEST: is attr\n" . $herecurr); } elsif ($dbg_attr > 1 && $line =~ /^.+($Modifier)/) { ERROR("TEST_NOT_ATTR", "TEST: is not attr ($1 is)\n". $herecurr); } next; } # check for initialisation to aggregates open brace on the next line if ($line =~ /^.\s*{/ && $prevline =~ /(?:^|[^=])=\s*$/) { ERROR("OPEN_BRACE", "that open brace { should be on the previous line\n" . $hereprev); } # # Checks which are anchored on the added line. # # check for malformed paths in #include statements (uses RAW line) if ($rawline =~ m{^.\s*\#\s*include\s+[<"](.*)[">]}) { my $path = $1; if ($path =~ m{//}) { ERROR("MALFORMED_INCLUDE", "malformed #include filename\n" . $herecurr); } } # no C99 // comments if ($line =~ m{//}) { ERROR("C99_COMMENTS", "do not use C99 // comments\n" . $herecurr); } # Remove C99 comments. $line =~ s@//.*@@; $opline =~ s@//.*@@; # EXPORT_SYMBOL should immediately follow the thing it is exporting, consider # the whole statement. #print "APW <$lines[$realline_next - 1]>\n"; if (defined $realline_next && exists $lines[$realline_next - 1] && !defined $suppress_export{$realline_next} && ($lines[$realline_next - 1] =~ /EXPORT_SYMBOL.*\((.*)\)/ || $lines[$realline_next - 1] =~ /EXPORT_UNUSED_SYMBOL.*\((.*)\)/)) { # Handle definitions which produce identifiers with # a prefix: # XXX(foo); # EXPORT_SYMBOL(something_foo); my $name = $1; if ($stat =~ /^(?:.\s*}\s*\n)?.([A-Z_]+)\s*\(\s*($Ident)/ && $name =~ /^${Ident}_$2/) { #print "FOO C name<$name>\n"; $suppress_export{$realline_next} = 1; } elsif ($stat !~ /(?: \n.}\s*$| ^.DEFINE_$Ident\(\Q$name\E\)| ^.DECLARE_$Ident\(\Q$name\E\)| ^.LIST_HEAD\(\Q$name\E\)| ^.(?:$Storage\s+)?$Type\s*\(\s*\*\s*\Q$name\E\s*\)\s*\(| \b\Q$name\E(?:\s+$Attribute)*\s*(?:;|=|\[|\() )/x) { #print "FOO A<$lines[$realline_next - 1]> stat<$stat> name<$name>\n"; $suppress_export{$realline_next} = 2; } else { $suppress_export{$realline_next} = 1; } } if (!defined $suppress_export{$linenr} && $prevline =~ /^.\s*$/ && ($line =~ /EXPORT_SYMBOL.*\((.*)\)/ || $line =~ /EXPORT_UNUSED_SYMBOL.*\((.*)\)/)) { #print "FOO B <$lines[$linenr - 1]>\n"; $suppress_export{$linenr} = 2; } if (defined $suppress_export{$linenr} && $suppress_export{$linenr} == 2) { WARN("EXPORT_SYMBOL", "EXPORT_SYMBOL(foo); should immediately follow its function/variable\n" . $herecurr); } # check for global initialisers. if ($line =~ /^.$Type\s*$Ident\s*(?:\s+$Modifier)*\s*=\s*(0|NULL|false)\s*;/) { ERROR("GLOBAL_INITIALISERS", "do not initialise globals to 0 or NULL\n" . $herecurr); } # check for static initialisers. if ($line =~ /\bstatic\s.*=\s*(0|NULL|false)\s*;/) { ERROR("INITIALISED_STATIC", "do not initialise statics to 0 or NULL\n" . $herecurr); } # check for static const char * arrays. if ($line =~ /\bstatic\s+const\s+char\s*\*\s*(\w+)\s*\[\s*\]\s*=\s*/) { WARN("STATIC_CONST_CHAR_ARRAY", "static const char * array should probably be static const char * const\n" . $herecurr); } # check for static char foo[] = "bar" declarations. if ($line =~ /\bstatic\s+char\s+(\w+)\s*\[\s*\]\s*=\s*"/) { WARN("STATIC_CONST_CHAR_ARRAY", "static char array declaration should probably be static const char\n" . $herecurr); } # check for declarations of struct pci_device_id if ($line =~ /\bstruct\s+pci_device_id\s+\w+\s*\[\s*\]\s*\=\s*\{/) { WARN("DEFINE_PCI_DEVICE_TABLE", "Use DEFINE_PCI_DEVICE_TABLE for struct pci_device_id\n" . $herecurr); } # check for new typedefs, only function parameters and sparse annotations # make sense. if ($line =~ /\btypedef\s/ && $line !~ /\btypedef\s+$Type\s*\(\s*\*?$Ident\s*\)\s*\(/ && $line !~ /\btypedef\s+$Type\s+$Ident\s*\(/ && $line !~ /\b$typeTypedefs\b/ && $line !~ /\b__bitwise(?:__|)\b/) { WARN("NEW_TYPEDEFS", "do not add new typedefs\n" . $herecurr); } # * goes on variable not on type # (char*[ const]) while ($line =~ m{(\($NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)\))}g) { #print "AA<$1>\n"; my ($from, $to) = ($2, $2); # Should start with a space. $to =~ s/^(\S)/ $1/; # Should not end with a space. $to =~ s/\s+$//; # '*'s should not have spaces between. while ($to =~ s/\*\s+\*/\*\*/) { } #print "from<$from> to<$to>\n"; if ($from ne $to) { ERROR("POINTER_LOCATION", "\"(foo$from)\" should be \"(foo$to)\"\n" . $herecurr); } } while ($line =~ m{(\b$NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)($Ident))}g) { #print "BB<$1>\n"; my ($from, $to, $ident) = ($2, $2, $3); # Should start with a space. $to =~ s/^(\S)/ $1/; # Should not end with a space. $to =~ s/\s+$//; # '*'s should not have spaces between. while ($to =~ s/\*\s+\*/\*\*/) { } # Modifiers should have spaces. $to =~ s/(\b$Modifier$)/$1 /; #print "from<$from> to<$to> ident<$ident>\n"; if ($from ne $to && $ident !~ /^$Modifier$/) { ERROR("POINTER_LOCATION", "\"foo${from}bar\" should be \"foo${to}bar\"\n" . $herecurr); } } # # no BUG() or BUG_ON() # if ($line =~ /\b(BUG|BUG_ON)\b/) { # print "Try to use WARN_ON & Recovery code rather than BUG() or BUG_ON()\n"; # print "$herecurr"; # $clean = 0; # } if ($line =~ /\bLINUX_VERSION_CODE\b/) { WARN("LINUX_VERSION_CODE", "LINUX_VERSION_CODE should be avoided, code should be for the version to which it is merged\n" . $herecurr); } # check for uses of printk_ratelimit if ($line =~ /\bprintk_ratelimit\s*\(/) { WARN("PRINTK_RATELIMITED", "Prefer printk_ratelimited or pr__ratelimited to printk_ratelimit\n" . $herecurr); } # printk should use KERN_* levels. Note that follow on printk's on the # same line do not need a level, so we use the current block context # to try and find and validate the current printk. In summary the current # printk includes all preceding printk's which have no newline on the end. # we assume the first bad printk is the one to report. if ($line =~ /\bprintk\((?!KERN_)\s*"/) { my $ok = 0; for (my $ln = $linenr - 1; $ln >= $first_line; $ln--) { #print "CHECK<$lines[$ln - 1]\n"; # we have a preceding printk if it ends # with "\n" ignore it, else it is to blame if ($lines[$ln - 1] =~ m{\bprintk\(}) { if ($rawlines[$ln - 1] !~ m{\\n"}) { $ok = 1; } last; } } if ($ok == 0) { WARN("PRINTK_WITHOUT_KERN_LEVEL", "printk() should include KERN_ facility level\n" . $herecurr); } } if ($line =~ /\bprintk\s*\(\s*KERN_([A-Z]+)/) { my $orig = $1; my $level = lc($orig); $level = "warn" if ($level eq "warning"); WARN("PREFER_PR_LEVEL", "Prefer pr_$level(... to printk(KERN_$1, ...\n" . $herecurr); } if ($line =~ /\bpr_warning\s*\(/) { WARN("PREFER_PR_LEVEL", "Prefer pr_warn(... to pr_warning(...\n" . $herecurr); } # function brace can't be on same line, except for #defines of do while, # or if closed on same line if (($line=~/$Type\s*$Ident\(.*\).*\s\{/) and !($line=~/\#\s*define.*do\s\{/) and !($line=~/\}/)) { ERROR("OPEN_BRACE", "open brace '{' following function declarations go on the next line\n" . $herecurr); } # open braces for enum, union and struct go on the same line. if ($line =~ /^.\s*{/ && $prevline =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident)?\s*$/) { ERROR("OPEN_BRACE", "open brace '{' following $1 go on the same line\n" . $hereprev); } # missing space after union, struct or enum definition if ($line =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident)?(?:\s+$Ident)?[=\{]/) { WARN("SPACING", "missing space after $1 definition\n" . $herecurr); } # check for spacing round square brackets; allowed: # 1. with a type on the left -- int [] a; # 2. at the beginning of a line for slice initialisers -- [0...10] = 5, # 3. inside a curly brace -- = { [0...10] = 5 } while ($line =~ /(.*?\s)\[/g) { my ($where, $prefix) = ($-[1], $1); if ($prefix !~ /$Type\s+$/ && ($where != 0 || $prefix !~ /^.\s+$/) && $prefix !~ /[{,]\s+$/) { ERROR("BRACKET_SPACE", "space prohibited before open square bracket '['\n" . $herecurr); } } # check for spaces between functions and their parentheses. while ($line =~ /($Ident)\s+\(/g) { my $name = $1; my $ctx_before = substr($line, 0, $-[1]); my $ctx = "$ctx_before$name"; # Ignore those directives where spaces _are_ permitted. if ($name =~ /^(?: if|for|while|switch|return|case| volatile|__volatile__| __attribute__|format|__extension__| asm|__asm__)$/x) { # cpp #define statements have non-optional spaces, ie # if there is a space between the name and the open # parenthesis it is simply not a parameter group. } elsif ($ctx_before =~ /^.\s*\#\s*define\s*$/) { # cpp #elif statement condition may start with a ( } elsif ($ctx =~ /^.\s*\#\s*elif\s*$/) { # If this whole things ends with a type its most # likely a typedef for a function. } elsif ($ctx =~ /$Type$/) { } else { WARN("SPACING", "space prohibited between function name and open parenthesis '('\n" . $herecurr); } } # check for whitespace before a non-naked semicolon if ($line =~ /^\+.*\S\s+;/) { CHK("SPACING", "space prohibited before semicolon\n" . $herecurr); } # Check operator spacing. if (!($line=~/\#\s*include/)) { my $ops = qr{ <<=|>>=|<=|>=|==|!=| \+=|-=|\*=|\/=|%=|\^=|\|=|&=| =>|->|<<|>>|<|>|=|!|~| &&|\|\||,|\^|\+\+|--|&|\||\+|-|\*|\/|%| \?|: }x; my @elements = split(/($ops|;)/, $opline); my $off = 0; my $blank = copy_spacing($opline); for (my $n = 0; $n < $#elements; $n += 2) { $off += length($elements[$n]); # Pick up the preceding and succeeding characters. my $ca = substr($opline, 0, $off); my $cc = ''; if (length($opline) >= ($off + length($elements[$n + 1]))) { $cc = substr($opline, $off + length($elements[$n + 1])); } my $cb = "$ca$;$cc"; my $a = ''; $a = 'V' if ($elements[$n] ne ''); $a = 'W' if ($elements[$n] =~ /\s$/); $a = 'C' if ($elements[$n] =~ /$;$/); $a = 'B' if ($elements[$n] =~ /(\[|\()$/); $a = 'O' if ($elements[$n] eq ''); $a = 'E' if ($ca =~ /^\s*$/); my $op = $elements[$n + 1]; my $c = ''; if (defined $elements[$n + 2]) { $c = 'V' if ($elements[$n + 2] ne ''); $c = 'W' if ($elements[$n + 2] =~ /^\s/); $c = 'C' if ($elements[$n + 2] =~ /^$;/); $c = 'B' if ($elements[$n + 2] =~ /^(\)|\]|;)/); $c = 'O' if ($elements[$n + 2] eq ''); $c = 'E' if ($elements[$n + 2] =~ /^\s*\\$/); } else { $c = 'E'; } my $ctx = "${a}x${c}"; my $at = "(ctx:$ctx)"; my $ptr = substr($blank, 0, $off) . "^"; my $hereptr = "$hereline$ptr\n"; # Pull out the value of this operator. my $op_type = substr($curr_values, $off + 1, 1); # Get the full operator variant. my $opv = $op . substr($curr_vars, $off, 1); # Ignore operators passed as parameters. if ($op_type ne 'V' && $ca =~ /\s$/ && $cc =~ /^\s*,/) { # # Ignore comments # } elsif ($op =~ /^$;+$/) { # ; should have either the end of line or a space or \ after it } elsif ($op eq ';') { if ($ctx !~ /.x[WEBC]/ && $cc !~ /^\\/ && $cc !~ /^;/) { ERROR("SPACING", "space required after that '$op' $at\n" . $hereptr); } # // is a comment } elsif ($op eq '//') { # No spaces for: # -> # : when part of a bitfield } elsif ($op eq '->' || $opv eq ':B') { if ($ctx =~ /Wx.|.xW/) { ERROR("SPACING", "spaces prohibited around that '$op' $at\n" . $hereptr); } # , must have a space on the right. } elsif ($op eq ',') { if ($ctx !~ /.x[WEC]/ && $cc !~ /^}/) { ERROR("SPACING", "space required after that '$op' $at\n" . $hereptr); } # '*' as part of a type definition -- reported already. } elsif ($opv eq '*_') { #warn "'*' is part of type\n"; # unary operators should have a space before and # none after. May be left adjacent to another # unary operator, or a cast } elsif ($op eq '!' || $op eq '~' || $opv eq '*U' || $opv eq '-U' || $opv eq '&U' || $opv eq '&&U') { if ($ctx !~ /[WEBC]x./ && $ca !~ /(?:\)|!|~|\*|-|\&|\||\+\+|\-\-|\{)$/) { ERROR("SPACING", "space required before that '$op' $at\n" . $hereptr); } if ($op eq '*' && $cc =~/\s*$Modifier\b/) { # A unary '*' may be const } elsif ($ctx =~ /.xW/) { ERROR("SPACING", "space prohibited after that '$op' $at\n" . $hereptr); } # unary ++ and unary -- are allowed no space on one side. } elsif ($op eq '++' or $op eq '--') { if ($ctx !~ /[WEOBC]x[^W]/ && $ctx !~ /[^W]x[WOBEC]/) { ERROR("SPACING", "space required one side of that '$op' $at\n" . $hereptr); } if ($ctx =~ /Wx[BE]/ || ($ctx =~ /Wx./ && $cc =~ /^;/)) { ERROR("SPACING", "space prohibited before that '$op' $at\n" . $hereptr); } if ($ctx =~ /ExW/) { ERROR("SPACING", "space prohibited after that '$op' $at\n" . $hereptr); } # << and >> may either have or not have spaces both sides } elsif ($op eq '<<' or $op eq '>>' or $op eq '&' or $op eq '^' or $op eq '|' or $op eq '+' or $op eq '-' or $op eq '*' or $op eq '/' or $op eq '%') { if ($ctx =~ /Wx[^WCE]|[^WCE]xW/) { ERROR("SPACING", "need consistent spacing around '$op' $at\n" . $hereptr); } # A colon needs no spaces before when it is # terminating a case value or a label. } elsif ($opv eq ':C' || $opv eq ':L') { if ($ctx =~ /Wx./) { ERROR("SPACING", "space prohibited before that '$op' $at\n" . $hereptr); } # All the others need spaces both sides. } elsif ($ctx !~ /[EWC]x[CWE]/) { my $ok = 0; # Ignore email addresses if (($op eq '<' && $cc =~ /^\S+\@\S+>/) || ($op eq '>' && $ca =~ /<\S+\@\S+$/)) { $ok = 1; } # Ignore ?: if (($opv eq ':O' && $ca =~ /\?$/) || ($op eq '?' && $cc =~ /^:/)) { $ok = 1; } if ($ok == 0) { ERROR("SPACING", "spaces required around that '$op' $at\n" . $hereptr); } } $off += length($elements[$n + 1]); } } # check for multiple assignments if ($line =~ /^.\s*$Lval\s*=\s*$Lval\s*=(?!=)/) { CHK("MULTIPLE_ASSIGNMENTS", "multiple assignments should be avoided\n" . $herecurr); } ## # check for multiple declarations, allowing for a function declaration ## # continuation. ## if ($line =~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Ident.*/ && ## $line !~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Type\s*$Ident.*/) { ## ## # Remove any bracketed sections to ensure we do not ## # falsly report the parameters of functions. ## my $ln = $line; ## while ($ln =~ s/\([^\(\)]*\)//g) { ## } ## if ($ln =~ /,/) { ## WARN("MULTIPLE_DECLARATION", ## "declaring multiple variables together should be avoided\n" . $herecurr); ## } ## } #need space before brace following if, while, etc if (($line =~ /\(.*\)\{/ && $line !~ /\($Type\){/) || $line =~ /do\{/) { ERROR("SPACING", "space required before the open brace '{'\n" . $herecurr); } # closing brace should have a space following it when it has anything # on the line if ($line =~ /}(?!(?:,|;|\)))\S/) { ERROR("SPACING", "space required after that close brace '}'\n" . $herecurr); } # check spacing on square brackets if ($line =~ /\[\s/ && $line !~ /\[\s*$/) { ERROR("SPACING", "space prohibited after that open square bracket '['\n" . $herecurr); } if ($line =~ /\s\]/) { ERROR("SPACING", "space prohibited before that close square bracket ']'\n" . $herecurr); } # check spacing on parentheses if ($line =~ /\(\s/ && $line !~ /\(\s*(?:\\)?$/ && $line !~ /for\s*\(\s+;/) { ERROR("SPACING", "space prohibited after that open parenthesis '('\n" . $herecurr); } if ($line =~ /(\s+)\)/ && $line !~ /^.\s*\)/ && $line !~ /for\s*\(.*;\s+\)/ && $line !~ /:\s+\)/) { ERROR("SPACING", "space prohibited before that close parenthesis ')'\n" . $herecurr); } #goto labels aren't indented, allow a single space however if ($line=~/^.\s+[A-Za-z\d_]+:(?![0-9]+)/ and !($line=~/^. [A-Za-z\d_]+:/) and !($line=~/^.\s+default:/)) { WARN("INDENTED_LABEL", "labels should not be indented\n" . $herecurr); } # Return is not a function. if (defined($stat) && $stat =~ /^.\s*return(\s*)(\(.*);/s) { my $spacing = $1; my $value = $2; # Flatten any parentheses $value =~ s/\(/ \(/g; $value =~ s/\)/\) /g; while ($value =~ s/\[[^\[\]]*\]/1/ || $value !~ /(?:$Ident|-?$Constant)\s* $Compare\s* (?:$Ident|-?$Constant)/x && $value =~ s/\([^\(\)]*\)/1/) { } #print "value<$value>\n"; if ($value =~ /^\s*(?:$Ident|-?$Constant)\s*$/) { ERROR("RETURN_PARENTHESES", "return is not a function, parentheses are not required\n" . $herecurr); } elsif ($spacing !~ /\s+/) { ERROR("SPACING", "space required before the open parenthesis '('\n" . $herecurr); } } # Return of what appears to be an errno should normally be -'ve if ($line =~ /^.\s*return\s*(E[A-Z]*)\s*;/) { my $name = $1; if ($name ne 'EOF' && $name ne 'ERROR') { WARN("USE_NEGATIVE_ERRNO", "return of an errno should typically be -ve (return -$1)\n" . $herecurr); } } # Need a space before open parenthesis after if, while etc if ($line=~/\b(if|while|for|switch)\(/) { ERROR("SPACING", "space required before the open parenthesis '('\n" . $herecurr); } # Check for illegal assignment in if conditional -- and check for trailing # statements after the conditional. if ($line =~ /do\s*(?!{)/) { ($stat, $cond, $line_nr_next, $remain_next, $off_next) = ctx_statement_block($linenr, $realcnt, 0) if (!defined $stat); my ($stat_next) = ctx_statement_block($line_nr_next, $remain_next, $off_next); $stat_next =~ s/\n./\n /g; ##print "stat<$stat> stat_next<$stat_next>\n"; if ($stat_next =~ /^\s*while\b/) { # If the statement carries leading newlines, # then count those as offsets. my ($whitespace) = ($stat_next =~ /^((?:\s*\n[+-])*\s*)/s); my $offset = statement_rawlines($whitespace) - 1; $suppress_whiletrailers{$line_nr_next + $offset} = 1; } } if (!defined $suppress_whiletrailers{$linenr} && $line =~ /\b(?:if|while|for)\s*\(/ && $line !~ /^.\s*#/) { my ($s, $c) = ($stat, $cond); if ($c =~ /\bif\s*\(.*[^<>!=]=[^=].*/s) { ERROR("ASSIGN_IN_IF", "do not use assignment in if condition\n" . $herecurr); } # Find out what is on the end of the line after the # conditional. substr($s, 0, length($c), ''); $s =~ s/\n.*//g; $s =~ s/$;//g; # Remove any comments if (length($c) && $s !~ /^\s*{?\s*\\*\s*$/ && $c !~ /}\s*while\s*/) { # Find out how long the conditional actually is. my @newlines = ($c =~ /\n/gs); my $cond_lines = 1 + $#newlines; my $stat_real = ''; $stat_real = raw_line($linenr, $cond_lines) . "\n" if ($cond_lines); if (defined($stat_real) && $cond_lines > 1) { $stat_real = "[...]\n$stat_real"; } ERROR("TRAILING_STATEMENTS", "trailing statements should be on next line\n" . $herecurr . $stat_real); } } # Check for bitwise tests written as boolean if ($line =~ / (?: (?:\[|\(|\&\&|\|\|) \s*0[xX][0-9]+\s* (?:\&\&|\|\|) | (?:\&\&|\|\|) \s*0[xX][0-9]+\s* (?:\&\&|\|\||\)|\]) )/x) { WARN("HEXADECIMAL_BOOLEAN_TEST", "boolean test with hexadecimal, perhaps just 1 \& or \|?\n" . $herecurr); } # if and else should not have general statements after it if ($line =~ /^.\s*(?:}\s*)?else\b(.*)/) { my $s = $1; $s =~ s/$;//g; # Remove any comments if ($s !~ /^\s*(?:\sif|(?:{|)\s*\\?\s*$)/) { ERROR("TRAILING_STATEMENTS", "trailing statements should be on next line\n" . $herecurr); } } # if should not continue a brace if ($line =~ /}\s*if\b/) { ERROR("TRAILING_STATEMENTS", "trailing statements should be on next line\n" . $herecurr); } # case and default should not have general statements after them if ($line =~ /^.\s*(?:case\s*.*|default\s*):/g && $line !~ /\G(?: (?:\s*$;*)(?:\s*{)?(?:\s*$;*)(?:\s*\\)?\s*$| \s*return\s+ )/xg) { ERROR("TRAILING_STATEMENTS", "trailing statements should be on next line\n" . $herecurr); } # Check for }else {, these must be at the same # indent level to be relevant to each other. if ($prevline=~/}\s*$/ and $line=~/^.\s*else\s*/ and $previndent == $indent) { ERROR("ELSE_AFTER_BRACE", "else should follow close brace '}'\n" . $hereprev); } if ($prevline=~/}\s*$/ and $line=~/^.\s*while\s*/ and $previndent == $indent) { my ($s, $c) = ctx_statement_block($linenr, $realcnt, 0); # Find out what is on the end of the line after the # conditional. substr($s, 0, length($c), ''); $s =~ s/\n.*//g; if ($s =~ /^\s*;/) { ERROR("WHILE_AFTER_BRACE", "while should follow close brace '}'\n" . $hereprev); } } #studly caps, commented out until figure out how to distinguish between use of existing and adding new # if (($line=~/[\w_][a-z\d]+[A-Z]/) and !($line=~/print/)) { # print "No studly caps, use _\n"; # print "$herecurr"; # $clean = 0; # } #no spaces allowed after \ in define if ($line=~/\#\s*define.*\\\s$/) { WARN("WHITESPACE_AFTER_LINE_CONTINUATION", "Whitepspace after \\ makes next lines useless\n" . $herecurr); } #warn if is #included and is available (uses RAW line) if ($tree && $rawline =~ m{^.\s*\#\s*include\s*\}) { my $file = "$1.h"; my $checkfile = "include/linux/$file"; if (-f "$root/$checkfile" && $realfile ne $checkfile && $1 !~ /$allowed_asm_includes/) { if ($realfile =~ m{^arch/}) { CHK("ARCH_INCLUDE_LINUX", "Consider using #include instead of \n" . $herecurr); } else { WARN("INCLUDE_LINUX", "Use #include instead of \n" . $herecurr); } } } # multi-statement macros should be enclosed in a do while loop, grab the # first statement and ensure its the whole macro if its not enclosed # in a known good container if ($realfile !~ m@/vmlinux.lds.h$@ && $line =~ /^.\s*\#\s*define\s*$Ident(\()?/) { my $ln = $linenr; my $cnt = $realcnt; my ($off, $dstat, $dcond, $rest); my $ctx = ''; ($dstat, $dcond, $ln, $cnt, $off) = ctx_statement_block($linenr, $realcnt, 0); $ctx = $dstat; #print "dstat<$dstat> dcond<$dcond> cnt<$cnt> off<$off>\n"; #print "LINE<$lines[$ln-1]> len<" . length($lines[$ln-1]) . "\n"; $dstat =~ s/^.\s*\#\s*define\s+$Ident(?:\([^\)]*\))?\s*//; $dstat =~ s/$;//g; $dstat =~ s/\\\n.//g; $dstat =~ s/^\s*//s; $dstat =~ s/\s*$//s; # Flatten any parentheses and braces while ($dstat =~ s/\([^\(\)]*\)/1/ || $dstat =~ s/\{[^\{\}]*\}/1/ || $dstat =~ s/\[[^\[\]]*\]/1/) { } # Flatten any obvious string concatentation. while ($dstat =~ s/("X*")\s*$Ident/$1/ || $dstat =~ s/$Ident\s*("X*")/$1/) { } my $exceptions = qr{ $Declare| module_param_named| MODULE_PARAM_DESC| DECLARE_PER_CPU| DEFINE_PER_CPU| __typeof__\(| union| struct| \.$Ident\s*=\s*| ^\"|\"$ }x; #print "REST<$rest> dstat<$dstat> ctx<$ctx>\n"; if ($dstat ne '' && $dstat !~ /^(?:$Ident|-?$Constant),$/ && # 10, // foo(), $dstat !~ /^(?:$Ident|-?$Constant);$/ && # foo(); $dstat !~ /^[!~-]?(?:$Ident|$Constant)$/ && # 10 // foo() // !foo // ~foo // -foo $dstat !~ /^'X'$/ && # character constants $dstat !~ /$exceptions/ && $dstat !~ /^\.$Ident\s*=/ && # .foo = $dstat !~ /^do\s*$Constant\s*while\s*$Constant;?$/ && # do {...} while (...); // do {...} while (...) $dstat !~ /^for\s*$Constant$/ && # for (...) $dstat !~ /^for\s*$Constant\s+(?:$Ident|-?$Constant)$/ && # for (...) bar() $dstat !~ /^do\s*{/ && # do {... $dstat !~ /^\(\{/) # ({... { $ctx =~ s/\n*$//; my $herectx = $here . "\n"; my $cnt = statement_rawlines($ctx); for (my $n = 0; $n < $cnt; $n++) { $herectx .= raw_line($linenr, $n) . "\n"; } if ($dstat =~ /;/) { ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE", "Macros with multiple statements should be enclosed in a do - while loop\n" . "$herectx"); } else { ERROR("COMPLEX_MACRO", "Macros with complex values should be enclosed in parenthesis\n" . "$herectx"); } } } # make sure symbols are always wrapped with VMLINUX_SYMBOL() ... # all assignments may have only one of the following with an assignment: # . # ALIGN(...) # VMLINUX_SYMBOL(...) if ($realfile eq 'vmlinux.lds.h' && $line =~ /(?:(?:^|\s)$Ident\s*=|=\s*$Ident(?:\s|$))/) { WARN("MISSING_VMLINUX_SYMBOL", "vmlinux.lds.h needs VMLINUX_SYMBOL() around C-visible symbols\n" . $herecurr); } # check for redundant bracing round if etc if ($line =~ /(^.*)\bif\b/ && $1 !~ /else\s*$/) { my ($level, $endln, @chunks) = ctx_statement_full($linenr, $realcnt, 1); #print "chunks<$#chunks> linenr<$linenr> endln<$endln> level<$level>\n"; #print "APW: <<$chunks[1][0]>><<$chunks[1][1]>>\n"; if ($#chunks > 0 && $level == 0) { my @allowed = (); my $allow = 0; my $seen = 0; my $herectx = $here . "\n"; my $ln = $linenr - 1; for my $chunk (@chunks) { my ($cond, $block) = @{$chunk}; # If the condition carries leading newlines, then count those as offsets. my ($whitespace) = ($cond =~ /^((?:\s*\n[+-])*\s*)/s); my $offset = statement_rawlines($whitespace) - 1; $allowed[$allow] = 0; #print "COND<$cond> whitespace<$whitespace> offset<$offset>\n"; # We have looked at and allowed this specific line. $suppress_ifbraces{$ln + $offset} = 1; $herectx .= "$rawlines[$ln + $offset]\n[...]\n"; $ln += statement_rawlines($block) - 1; substr($block, 0, length($cond), ''); $seen++ if ($block =~ /^\s*{/); #print "cond<$cond> block<$block> allowed<$allowed[$allow]>\n"; if (statement_lines($cond) > 1) { #print "APW: ALLOWED: cond<$cond>\n"; $allowed[$allow] = 1; } if ($block =~/\b(?:if|for|while)\b/) { #print "APW: ALLOWED: block<$block>\n"; $allowed[$allow] = 1; } if (statement_block_size($block) > 1) { #print "APW: ALLOWED: lines block<$block>\n"; $allowed[$allow] = 1; } $allow++; } if ($seen) { my $sum_allowed = 0; foreach (@allowed) { $sum_allowed += $_; } if ($sum_allowed == 0) { WARN("BRACES", "braces {} are not necessary for any arm of this statement\n" . $herectx); } elsif ($sum_allowed != $allow && $seen != $allow) { CHK("BRACES", "braces {} should be used on all arms of this statement\n" . $herectx); } } } } if (!defined $suppress_ifbraces{$linenr - 1} && $line =~ /\b(if|while|for|else)\b/) { my $allowed = 0; # Check the pre-context. if (substr($line, 0, $-[0]) =~ /(\}\s*)$/) { #print "APW: ALLOWED: pre<$1>\n"; $allowed = 1; } my ($level, $endln, @chunks) = ctx_statement_full($linenr, $realcnt, $-[0]); # Check the condition. my ($cond, $block) = @{$chunks[0]}; #print "CHECKING<$linenr> cond<$cond> block<$block>\n"; if (defined $cond) { substr($block, 0, length($cond), ''); } if (statement_lines($cond) > 1) { #print "APW: ALLOWED: cond<$cond>\n"; $allowed = 1; } if ($block =~/\b(?:if|for|while)\b/) { #print "APW: ALLOWED: block<$block>\n"; $allowed = 1; } if (statement_block_size($block) > 1) { #print "APW: ALLOWED: lines block<$block>\n"; $allowed = 1; } # Check the post-context. if (defined $chunks[1]) { my ($cond, $block) = @{$chunks[1]}; if (defined $cond) { substr($block, 0, length($cond), ''); } if ($block =~ /^\s*\{/) { #print "APW: ALLOWED: chunk-1 block<$block>\n"; $allowed = 1; } } if ($level == 0 && $block =~ /^\s*\{/ && !$allowed) { my $herectx = $here . "\n"; my $cnt = statement_rawlines($block); for (my $n = 0; $n < $cnt; $n++) { $herectx .= raw_line($linenr, $n) . "\n"; } WARN("BRACES", "braces {} are not necessary for single statement blocks\n" . $herectx); } } # don't include deprecated include files (uses RAW line) for my $inc (@dep_includes) { if ($rawline =~ m@^.\s*\#\s*include\s*\<$inc>@) { ERROR("DEPRECATED_INCLUDE", "Don't use <$inc>: see Documentation/feature-removal-schedule.txt\n" . $herecurr); } } # don't use deprecated functions for my $func (@dep_functions) { if ($line =~ /\b$func\b/) { ERROR("DEPRECATED_FUNCTION", "Don't use $func(): see Documentation/feature-removal-schedule.txt\n" . $herecurr); } } # no volatiles please my $asm_volatile = qr{\b(__asm__|asm)\s+(__volatile__|volatile)\b}; if ($line =~ /\bvolatile\b/ && $line !~ /$asm_volatile/) { WARN("VOLATILE", "Use of volatile is usually wrong: see Documentation/volatile-considered-harmful.txt\n" . $herecurr); } # warn about #if 0 if ($line =~ /^.\s*\#\s*if\s+0\b/) { CHK("REDUNDANT_CODE", "if this code is redundant consider removing it\n" . $herecurr); } # check for needless kfree() checks if ($prevline =~ /\bif\s*\(([^\)]*)\)/) { my $expr = $1; if ($line =~ /\bkfree\(\Q$expr\E\);/) { WARN("NEEDLESS_KFREE", "kfree(NULL) is safe this check is probably not required\n" . $hereprev); } } # check for needless usb_free_urb() checks if ($prevline =~ /\bif\s*\(([^\)]*)\)/) { my $expr = $1; if ($line =~ /\busb_free_urb\(\Q$expr\E\);/) { WARN("NEEDLESS_USB_FREE_URB", "usb_free_urb(NULL) is safe this check is probably not required\n" . $hereprev); } } # prefer usleep_range over udelay if ($line =~ /\budelay\s*\(\s*(\w+)\s*\)/) { # ignore udelay's < 10, however if (! (($1 =~ /(\d+)/) && ($1 < 10)) ) { CHK("USLEEP_RANGE", "usleep_range is preferred over udelay; see Documentation/timers/timers-howto.txt\n" . $line); } } # warn about unexpectedly long msleep's if ($line =~ /\bmsleep\s*\((\d+)\);/) { if ($1 < 20) { WARN("MSLEEP", "msleep < 20ms can sleep for up to 20ms; see Documentation/timers/timers-howto.txt\n" . $line); } } # warn about #ifdefs in C files # if ($line =~ /^.\s*\#\s*if(|n)def/ && ($realfile =~ /\.c$/)) { # print "#ifdef in C files should be avoided\n"; # print "$herecurr"; # $clean = 0; # } # warn about spacing in #ifdefs if ($line =~ /^.\s*\#\s*(ifdef|ifndef|elif)\s\s+/) { ERROR("SPACING", "exactly one space required after that #$1\n" . $herecurr); } # check for spinlock_t definitions without a comment. if ($line =~ /^.\s*(struct\s+mutex|spinlock_t)\s+\S+;/ || $line =~ /^.\s*(DEFINE_MUTEX)\s*\(/) { my $which = $1; if (!ctx_has_comment($first_line, $linenr)) { CHK("UNCOMMENTED_DEFINITION", "$1 definition without comment\n" . $herecurr); } } # check for memory barriers without a comment. if ($line =~ /\b(mb|rmb|wmb|read_barrier_depends|smp_mb|smp_rmb|smp_wmb|smp_read_barrier_depends)\(/) { if (!ctx_has_comment($first_line, $linenr)) { CHK("MEMORY_BARRIER", "memory barrier without comment\n" . $herecurr); } } # check of hardware specific defines if ($line =~ m@^.\s*\#\s*if.*\b(__i386__|__powerpc64__|__sun__|__s390x__)\b@ && $realfile !~ m@include/asm-@) { CHK("ARCH_DEFINES", "architecture specific defines should be avoided\n" . $herecurr); } # Check that the storage class is at the beginning of a declaration if ($line =~ /\b$Storage\b/ && $line !~ /^.\s*$Storage\b/) { WARN("STORAGE_CLASS", "storage class should be at the beginning of the declaration\n" . $herecurr) } # check the location of the inline attribute, that it is between # storage class and type. if ($line =~ /\b$Type\s+$Inline\b/ || $line =~ /\b$Inline\s+$Storage\b/) { ERROR("INLINE_LOCATION", "inline keyword should sit between storage class and type\n" . $herecurr); } # Check for __inline__ and __inline, prefer inline if ($line =~ /\b(__inline__|__inline)\b/) { WARN("INLINE", "plain inline is preferred over $1\n" . $herecurr); } # Check for __attribute__ packed, prefer __packed if ($line =~ /\b__attribute__\s*\(\s*\(.*\bpacked\b/) { WARN("PREFER_PACKED", "__packed is preferred over __attribute__((packed))\n" . $herecurr); } # Check for __attribute__ aligned, prefer __aligned if ($line =~ /\b__attribute__\s*\(\s*\(.*aligned/) { WARN("PREFER_ALIGNED", "__aligned(size) is preferred over __attribute__((aligned(size)))\n" . $herecurr); } # Check for __attribute__ format(printf, prefer __printf if ($line =~ /\b__attribute__\s*\(\s*\(\s*format\s*\(\s*printf/) { WARN("PREFER_PRINTF", "__printf(string-index, first-to-check) is preferred over __attribute__((format(printf, string-index, first-to-check)))\n" . $herecurr); } # Check for __attribute__ format(scanf, prefer __scanf if ($line =~ /\b__attribute__\s*\(\s*\(\s*format\s*\(\s*scanf\b/) { WARN("PREFER_SCANF", "__scanf(string-index, first-to-check) is preferred over __attribute__((format(scanf, string-index, first-to-check)))\n" . $herecurr); } # check for sizeof(&) if ($line =~ /\bsizeof\s*\(\s*\&/) { WARN("SIZEOF_ADDRESS", "sizeof(& should be avoided\n" . $herecurr); } # check for line continuations in quoted strings with odd counts of " if ($rawline =~ /\\$/ && $rawline =~ tr/"/"/ % 2) { WARN("LINE_CONTINUATIONS", "Avoid line continuations in quoted strings\n" . $herecurr); } # Check for misused memsets if ($^V && $^V ge 5.10.0 && defined $stat && $stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*$FuncArg\s*\)/s) { my $ms_addr = $2; my $ms_val = $7; my $ms_size = $12; if ($ms_size =~ /^(0x|)0$/i) { ERROR("MEMSET", "memset to 0's uses 0 as the 2nd argument, not the 3rd\n" . "$here\n$stat\n"); } elsif ($ms_size =~ /^(0x|)1$/i) { WARN("MEMSET", "single byte memset is suspicious. Swapped 2nd/3rd argument?\n" . "$here\n$stat\n"); } } # typecasts on min/max could be min_t/max_t if ($^V && $^V ge 5.10.0 && defined $stat && $stat =~ /^\+(?:.*?)\b(min|max)\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\)/) { if (defined $2 || defined $7) { my $call = $1; my $cast1 = deparenthesize($2); my $arg1 = $3; my $cast2 = deparenthesize($7); my $arg2 = $8; my $cast; if ($cast1 ne "" && $cast2 ne "" && $cast1 ne $cast2) { $cast = "$cast1 or $cast2"; } elsif ($cast1 ne "") { $cast = $cast1; } else { $cast = $cast2; } WARN("MINMAX", "$call() should probably be ${call}_t($cast, $arg1, $arg2)\n" . "$here\n$stat\n"); } } # check for new externs in .c files. if ($realfile =~ /\.c$/ && defined $stat && $stat =~ /^.\s*(?:extern\s+)?$Type\s+($Ident)(\s*)\(/s) { my $function_name = $1; my $paren_space = $2; my $s = $stat; if (defined $cond) { substr($s, 0, length($cond), ''); } if ($s =~ /^\s*;/ && $function_name ne 'uninitialized_var') { WARN("AVOID_EXTERNS", "externs should be avoided in .c files\n" . $herecurr); } if ($paren_space =~ /\n/) { WARN("FUNCTION_ARGUMENTS", "arguments for function declarations should follow identifier\n" . $herecurr); } } elsif ($realfile =~ /\.c$/ && defined $stat && $stat =~ /^.\s*extern\s+/) { WARN("AVOID_EXTERNS", "externs should be avoided in .c files\n" . $herecurr); } # checks for new __setup's if ($rawline =~ /\b__setup\("([^"]*)"/) { my $name = $1; if (!grep(/$name/, @setup_docs)) { CHK("UNDOCUMENTED_SETUP", "__setup appears un-documented -- check Documentation/kernel-parameters.txt\n" . $herecurr); } } # check for pointless casting of kmalloc return if ($line =~ /\*\s*\)\s*[kv][czm]alloc(_node){0,1}\b/) { WARN("UNNECESSARY_CASTS", "unnecessary cast may hide bugs, see http://c-faq.com/malloc/mallocnocast.html\n" . $herecurr); } # check for multiple semicolons if ($line =~ /;\s*;\s*$/) { WARN("ONE_SEMICOLON", "Statements terminations use 1 semicolon\n" . $herecurr); } # check for gcc specific __FUNCTION__ if ($line =~ /__FUNCTION__/) { WARN("USE_FUNC", "__func__ should be used instead of gcc specific __FUNCTION__\n" . $herecurr); } # check for use of yield() if ($line =~ /\byield\s*\(\s*\)/) { WARN("YIELD", "Using yield() is generally wrong. See yield() kernel-doc (sched/core.c)\n" . $herecurr); } # check for semaphores initialized locked if ($line =~ /^.\s*sema_init.+,\W?0\W?\)/) { WARN("CONSIDER_COMPLETION", "consider using a completion\n" . $herecurr); } # recommend kstrto* over simple_strto* and strict_strto* if ($line =~ /\b((simple|strict)_(strto(l|ll|ul|ull)))\s*\(/) { WARN("CONSIDER_KSTRTO", "$1 is obsolete, use k$3 instead\n" . $herecurr); } # check for __initcall(), use device_initcall() explicitly please if ($line =~ /^.\s*__initcall\s*\(/) { WARN("USE_DEVICE_INITCALL", "please use device_initcall() instead of __initcall()\n" . $herecurr); } # check for various ops structs, ensure they are const. my $struct_ops = qr{acpi_dock_ops| address_space_operations| backlight_ops| block_device_operations| dentry_operations| dev_pm_ops| dma_map_ops| extent_io_ops| file_lock_operations| file_operations| hv_ops| ide_dma_ops| intel_dvo_dev_ops| item_operations| iwl_ops| kgdb_arch| kgdb_io| kset_uevent_ops| lock_manager_operations| microcode_ops| mtrr_ops| neigh_ops| nlmsvc_binding| pci_raw_ops| pipe_buf_operations| platform_hibernation_ops| platform_suspend_ops| proto_ops| rpc_pipe_ops| seq_operations| snd_ac97_build_ops| soc_pcmcia_socket_ops| stacktrace_ops| sysfs_ops| tty_operations| usb_mon_operations| wd_ops}x; if ($line !~ /\bconst\b/ && $line =~ /\bstruct\s+($struct_ops)\b/) { WARN("CONST_STRUCT", "struct $1 should normally be const\n" . $herecurr); } # use of NR_CPUS is usually wrong # ignore definitions of NR_CPUS and usage to define arrays as likely right if ($line =~ /\bNR_CPUS\b/ && $line !~ /^.\s*\s*#\s*if\b.*\bNR_CPUS\b/ && $line !~ /^.\s*\s*#\s*define\b.*\bNR_CPUS\b/ && $line !~ /^.\s*$Declare\s.*\[[^\]]*NR_CPUS[^\]]*\]/ && $line !~ /\[[^\]]*\.\.\.[^\]]*NR_CPUS[^\]]*\]/ && $line !~ /\[[^\]]*NR_CPUS[^\]]*\.\.\.[^\]]*\]/) { WARN("NR_CPUS", "usage of NR_CPUS is often wrong - consider using cpu_possible(), num_possible_cpus(), for_each_possible_cpu(), etc\n" . $herecurr); } # check for %L{u,d,i} in strings my $string; while ($line =~ /(?:^|")([X\t]*)(?:"|$)/g) { $string = substr($rawline, $-[1], $+[1] - $-[1]); $string =~ s/%%/__/g; if ($string =~ /(?mutex.\n" . $herecurr); } } if ($line =~ /debugfs_create_file.*S_IWUGO/ || $line =~ /DEVICE_ATTR.*S_IWUGO/ ) { WARN("EXPORTED_WORLD_WRITABLE", "Exporting world writable files is usually an error. Consider more restrictive permissions.\n" . $herecurr); } } # If we have no input at all, then there is nothing to report on # so just keep quiet. if ($#rawlines == -1) { exit(0); } # In mailback mode only produce a report in the negative, for # things that appear to be patches. if ($mailback && ($clean == 1 || !$is_patch)) { exit(0); } # This is not a patch, and we are are in 'no-patch' mode so # just keep quiet. if (!$chk_patch && !$is_patch) { exit(0); } if (!$is_patch) { ERROR("NOT_UNIFIED_DIFF", "Does not appear to be a unified-diff format patch\n"); } if ($is_patch && $chk_signoff && $signoff == 0) { ERROR("MISSING_SIGN_OFF", "Missing Signed-off-by: line(s)\n"); } print report_dump(); if ($summary && !($clean == 1 && $quiet == 1)) { print "$filename " if ($summary_file); print "total: $cnt_error errors, $cnt_warn warnings, " . (($check)? "$cnt_chk checks, " : "") . "$cnt_lines lines checked\n"; print "\n" if ($quiet == 0); } if ($quiet == 0) { if ($^V lt 5.10.0) { print("NOTE: perl $^V is not modern enough to detect all possible issues.\n"); print("An upgrade to at least perl v5.10.0 is suggested.\n\n"); } # If there were whitespace errors which cleanpatch can fix # then suggest that. if ($rpt_cleaners) { print "NOTE: whitespace errors detected, you may wish to use scripts/cleanpatch or\n"; print " scripts/cleanfile\n\n"; $rpt_cleaners = 0; } } if ($quiet == 0 && keys %ignore_type) { print "NOTE: Ignored message types:"; foreach my $ignore (sort keys %ignore_type) { print " $ignore"; } print "\n\n"; } if ($clean == 1 && $quiet == 0) { print "$vname has no obvious style problems and is ready for submission.\n" } if ($clean == 0 && $quiet == 0) { print << "EOM"; $vname has style problems, please review. If any of these errors are false positives, please report them to the maintainer, see CHECKPATCH in MAINTAINERS. EOM } return $clean; } tgt-1.0.85/scripts/deb/000077500000000000000000000000001435417276200146375ustar00rootroot00000000000000tgt-1.0.85/scripts/deb/changelog000066400000000000000000000002611435417276200165100ustar00rootroot00000000000000tgt (1.0.37-1) UNRELEASED; urgency=low * Non-maintainer upload. * * Initial release. (Closes: #XXXXXX) -- Roi Dayan Thu, 18 Jul 2013 09:31:00 +0300 tgt-1.0.85/scripts/deb/compat000066400000000000000000000000021435417276200160350ustar00rootroot000000000000008 tgt-1.0.85/scripts/deb/control000066400000000000000000000011201435417276200162340ustar00rootroot00000000000000Source: tgt Maintainer: FUJITA Tomonori Section: net Priority: optional Standards-Version: 3.9.1 Build-Depends: debhelper (>= 8), dpkg-dev (>= 1.13.19), bash-completion, librdmacm-dev, libibverbs-dev, xsltproc, docbook-xsl Homepage: http://stgt.berlios.de/ Package: tgt Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, librdmacm1, libconfig-general-perl Description: The SCSI target daemon and utility programs The SCSI target package contains the daemon and tools to setup a SCSI targets. Currently, software iSCSI targets are supported. tgt-1.0.85/scripts/deb/copyright000066400000000000000000000012231435417276200165700ustar00rootroot00000000000000License: 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, see http://www.gnu.org/licenses/. tgt-1.0.85/scripts/deb/init000077500000000000000000000073261435417276200155400ustar00rootroot00000000000000#!/bin/sh # This is an example init.d script for stopping/starting/reconfiguring tgtd. ### BEGIN INIT INFO # Provides: tgtd # Required-Start: $network # Required-Stop: $network # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Starts and stops the generic storage target daemon # Description: tgtd provides the SCSI and software transport target state machine daemon. ### END INIT INFO TGTD_CONFIG=/etc/tgt/targets.conf TASK=$1 start() { echo "Starting target framework daemon" # Start tgtd first. tgtd &>/dev/null RETVAL=$? if [ "$RETVAL" -ne 0 ] ; then echo "Could not start tgtd (is tgtd already running?)" exit 1 fi sleep 1 # Put tgtd into "offline" state until all the targets are configured. # We don't want initiators to (re)connect and fail the connection # if it's not ready. tgtadm --op update --mode sys --name State -v offline # Configure the targets. tgt-admin -e -c $TGTD_CONFIG # Put tgtd into "ready" state. tgtadm --op update --mode sys --name State -v ready } stop() { if [ -n "$RUNLEVEL" ] && [ "$RUNLEVEL" -eq 0 -o "$RUNLEVEL" -eq 6 ] ; then forcedstop fi echo "Stopping target framework daemon" # Remove all targets. It only removes targets which are not in use. tgt-admin --update ALL -c /dev/null &>/dev/null # tgtd will exit if all targets were removed tgtadm --op delete --mode system &>/dev/null RETVAL=$? if [ "$RETVAL" -eq 107 ] ; then echo "tgtd is not running" [ "$TASK" != "restart" ] && exit 1 elif [ "$RETVAL" -ne 0 ] ; then echo "Some initiators are still connected - could not stop tgtd" exit 2 fi echo -n } forcedstop() { # NOTE: Forced shutdown of the iscsi target may cause data corruption # for initiators that are connected. echo "Force-stopping target framework daemon" # Offline everything first. May be needed if we're rebooting, but # expect the initiators to reconnect cleanly when we boot again # (i.e. we don't want them to reconnect to a tgtd which is still # working, but the target is gone). tgtadm --op update --mode sys --name State -v offline &>/dev/null RETVAL=$? if [ "$RETVAL" -eq 107 ] ; then echo "tgtd is not running" [ "$TASK" != "restart" ] && exit 1 else tgt-admin --offline ALL # Remove all targets, even if they are still in use. tgt-admin --update ALL -c /dev/null -f # It will shut down tgtd only after all targets were removed. tgtadm --op delete --mode system RETVAL=$? if [ "$RETVAL" -ne 0 ] ; then echo "Failed to shutdown tgtd" exit 1 fi fi echo -n } reload() { echo "Updating target framework daemon configuration" # Update configuration for targets. Only targets which # are not in use will be updated. tgt-admin --update ALL -c $TGTD_CONFIG &>/dev/null RETVAL=$? if [ "$RETVAL" -eq 107 ] ; then echo "tgtd is not running" exit 1 fi } forcedreload() { echo "Force-updating target framework daemon configuration" # Update configuration for targets, even those in use. tgt-admin --update ALL -f -c $TGTD_CONFIG &>/dev/null RETVAL=$? if [ "$RETVAL" -eq 107 ] ; then echo "tgtd is not running" exit 1 fi } status() { # Don't name this script "tgtd"... TGTD_PROC=$(ps -C tgtd | grep -c tgtd) if [ "$TGTD_PROC" -eq 2 ] ; then echo "tgtd is running. Run 'tgt-admin -s' to see detailed target info." else echo "tgtd is NOT running." fi } case $1 in start) start ;; stop) stop ;; forcedstop) forcedstop ;; restart) TASK=restart stop && start ;; forcedrestart) TASK=restart forcedstop && start ;; reload) reload ;; force-reload) forcedreload ;; status) status ;; *) echo "Usage: $0 {start|stop|forcedstop|restart|forcedrestart|reload|forcedreload|status}" exit 2 ;; esac tgt-1.0.85/scripts/deb/patches/000077500000000000000000000000001435417276200162665ustar00rootroot00000000000000tgt-1.0.85/scripts/deb/patches/0001-Use-local-docbook-for-generating-docs.patch000066400000000000000000000074161435417276200267720ustar00rootroot00000000000000From 1edc4f6428dfdbd86a2d47177148e12fd617c3b1 Mon Sep 17 00:00:00 2001 From: Roi Dayan Date: Thu, 10 Apr 2014 17:36:45 +0300 Subject: [PATCH] Use local docbook for generating docs Signed-off-by: Roi Dayan --- doc/Makefile | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/doc/Makefile b/doc/Makefile index 0516dc8..e56695d 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -34,40 +34,40 @@ clean: -rm -f manpages htmlpages manpages/tgtd.8: tgtd.8.xml - -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $< + -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/manpages/docbook.xsl $< htmlpages/tgtd.8.html: tgtd.8.xml - -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $< + -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/html/docbook.xsl $< manpages/tgtadm.8: tgtadm.8.xml - -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $< + -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/manpages/docbook.xsl $< htmlpages/tgtadm.8.html: tgtadm.8.xml - -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $< + -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/html/docbook.xsl $< manpages/tgt-admin.8: tgt-admin.8.xml - -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $< + -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/manpages/docbook.xsl $< htmlpages/tgt-admin.8.html: tgt-admin.8.xml - -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $< + -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/html/docbook.xsl $< manpages/tgtimg.8: tgtimg.8.xml - -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $< + -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/manpages/docbook.xsl $< htmlpages/tgtimg.8.html: tgtimg.8.xml - -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $< + -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/html/docbook.xsl $< manpages/targets.conf.5: targets.conf.5.xml - -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $< + -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/manpages/docbook.xsl $< htmlpages/targets.conf.5.html: targets.conf.5.xml - -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $< + -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/html/docbook.xsl $< manpages/tgt-setup-lun.8: tgt-setup-lun.8.xml - -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $< + -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/manpages/docbook.xsl $< htmlpages/tgt-setup-lun.8.html: tgt-setup-lun.8.xml - -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $< + -test -z "$(XSLTPROC)" || $(XSLTPROC) -o $@ /usr/share/xml/docbook/stylesheet/docbook-xsl/html/docbook.xsl $< xmlman: $(XMLMAN) -- 1.8.1.2 tgt-1.0.85/scripts/deb/rules000077500000000000000000000003311435417276200157140ustar00rootroot00000000000000#!/usr/bin/make -f DEB_MAKE_ENVVARS += ISCSI_RDMA=1 %: dh $@ --with bash-completion override_dh_auto_build: dh_auto_build -- $(DEB_MAKE_ENVVARS) override_dh_auto_install: dh_auto_install -- $(DEB_MAKE_ENVVARS) tgt-1.0.85/scripts/deb/source/000077500000000000000000000000001435417276200161375ustar00rootroot00000000000000tgt-1.0.85/scripts/deb/source/format000066400000000000000000000000141435417276200173450ustar00rootroot000000000000003.0 (quilt) tgt-1.0.85/scripts/deb/tgt.bash-completion000066400000000000000000000000331435417276200204370ustar00rootroot00000000000000scripts/tgt.bashcomp.sh tgttgt-1.0.85/scripts/initd.sample000077500000000000000000000073071435417276200164310ustar00rootroot00000000000000#!/bin/sh # This is an example init.d script for stopping/starting/reconfiguring tgtd. # chkconfig: 345 20 80 # ### BEGIN INIT INFO # Provides: tgtd # Required-Start: $network # Required-Stop: $network # Default-Start: 3 4 5 # Default-Stop: 0 1 2 6 # Short-Description: Starts and stops the generic storage target daemon # Description: tgtd provides the SCSI and software transport target state machine daemon. ### END INIT INFO TGTD_CONFIG=/etc/tgt/targets.conf TASK=$1 start() { echo "Starting target framework daemon" # Start tgtd first. tgtd &>/dev/null RETVAL=$? if [ "$RETVAL" -ne 0 ] ; then echo "Could not start tgtd (is tgtd already running?)" exit 1 fi # Put tgtd into "offline" state until all the targets are configured. # We don't want initiators to (re)connect and fail the connection # if it's not ready. tgtadm --op update --mode sys --name State -v offline # Configure the targets. tgt-admin -e -c $TGTD_CONFIG # Put tgtd into "ready" state. tgtadm --op update --mode sys --name State -v ready } stop() { if [ "$RUNLEVEL" == 0 -o "$RUNLEVEL" == 6 ] ; then forcedstop fi echo "Stopping target framework daemon" # Remove all targets. It only removes targets which are not in use. tgt-admin --update ALL -c /dev/null &>/dev/null # tgtd will exit if all targets were removed tgtadm --op delete --mode system &>/dev/null RETVAL=$? if [ "$RETVAL" -eq 107 ] ; then echo "tgtd is not running" [ "$TASK" != "restart" ] && exit 1 elif [ "$RETVAL" -ne 0 ] ; then echo "Some initiators are still connected - could not stop tgtd" exit 2 fi echo -n } forcedstop() { # NOTE: Forced shutdown of the iscsi target may cause data corruption # for initiators that are connected. echo "Force-stopping target framework daemon" # Offline everything first. May be needed if we're rebooting, but # expect the initiators to reconnect cleanly when we boot again # (i.e. we don't want them to reconnect to a tgtd which is still # working, but the target is gone). tgtadm --op update --mode sys --name State -v offline &>/dev/null RETVAL=$? if [ "$RETVAL" -eq 107 ] ; then echo "tgtd is not running" [ "$TASK" != "restart" ] && exit 1 else tgt-admin --offline ALL # Remove all targets, even if they are still in use. tgt-admin --update ALL -c /dev/null -f # It will shut down tgtd only after all targets were removed. tgtadm --op delete --mode system RETVAL=$? if [ "$RETVAL" -ne 0 ] ; then echo "Failed to shutdown tgtd" exit 1 fi fi echo -n } reload() { echo "Updating target framework daemon configuration" # Update configuration for targets. Only targets which # are not in use will be updated. tgt-admin --update ALL -c $TGTD_CONFIG &>/dev/null RETVAL=$? if [ "$RETVAL" -eq 107 ] ; then echo "tgtd is not running" exit 1 fi } forcedreload() { echo "Force-updating target framework daemon configuration" # Update configuration for targets, even those in use. tgt-admin --update ALL -f -c $TGTD_CONFIG &>/dev/null RETVAL=$? if [ "$RETVAL" -eq 107 ] ; then echo "tgtd is not running" exit 1 fi } status() { # Don't name this script "tgtd"... TGTD_PROC=$(ps -C tgtd | grep -c tgtd) if [ "$TGTD_PROC" -eq 2 ] ; then echo "tgtd is running. Run 'tgt-admin -s' to see detailed target info." else echo "tgtd is NOT running." fi } case $1 in start) start ;; stop) stop ;; forcedstop) forcedstop ;; restart) TASK=restart stop && start ;; forcedrestart) TASK=restart forcedstop && start ;; reload) reload ;; forcedreload) forcedreload ;; status) status ;; *) echo "Usage: $0 {start|stop|forcedstop|restart|forcedrestart|reload|forcedreload|status}" exit 2 ;; esac tgt-1.0.85/scripts/tgt-admin000066400000000000000000001235231435417276200157220ustar00rootroot00000000000000#!/usr/bin/perl # # This tools parses /etc/tgt/targets.conf file and configures tgt # # Author: Tomasz Chmielewski # License: GPLv2 # use strict; use Config::General qw(ParseConfig); use Getopt::Long; # Our config file my $configfile = "/etc/tgt/targets.conf"; sub usage { print < delete all or selected targets (see "--delete help" for more info) --offline put all or selected targets in offline state (see "--offline help" for more info) --ready put all or selected targets in ready state (see "--ready help" for more info) --update update configuration for all or selected targets (see "--update help" for more info) -s, --show show all the targets -C, --control-port specify the control port to connect to -c, --conf specify an alternative configuration file --ignore-errors continue even if tgtadm exits with non-zero code -f, --force force some operations even if the target is in use -p, --pretend only print tgtadm options --dump dump current tgtd configuration (note: does not include detailed parameters, like write caching) -v, --verbose increase verbosity (show tgtadm commands) -h, --help show this help EOF exit; } my %conf; my $param = $ARGV[0]; my $execute = 0; my $delete = 0; my $offline = 0; my $ready = 0; my $update = 0; my $show = 0; my $alternate_conf="0"; my $ignore_errors = 0; my $force = 0; my $pretend = 0; my $dump = 0; my $verbose = 0; my $help = 0; my $control_port=0; Getopt::Long::Configure("no_ignore_case"); my $result = GetOptions ( "e|execute" => \$execute, "delete=s" => \$delete, "offline=s" => \$offline, "ready=s" => \$ready, "update=s" => \$update, "s|show" => \$show, "c|conf=s" => \$alternate_conf, "ignore-errors" => \$ignore_errors, "f|force" => \$force, "p|pretend" => \$pretend, "dump" => \$dump, "v|verbose" => \$verbose, "h|help" => \$help, "C|control-port=s" => \$control_port, ); if (($help == 1) || ($param eq undef)) { usage; } # Show all the targets and exit if ($show == 1) { execute("tgtadm -C $control_port --op show --mode target"); exit; } # Some variables/arrays/hashes we will use globally my %tgtadm_output; my %tgtadm_output_tid; my %tgtadm_output_name; my @largest_tid; my $next_tid; my %existing_accounts; # Look up if iSNS is already on sub check_isns { # We need to run as root if ( $> ) { die("You must be root to run this program.\n"); } my @show_sys = `tgtadm -C $control_port --op show --mode sys`; foreach my $sys_line (@show_sys) { if ($sys_line =~ m/iSNS=On/) { return 1; } } # iSNS is not enabled if we're here return 0; } # Look up which targets are configured sub process_targets { # We need to run as root if ( $> ) { die("You must be root to run this program.\n"); } my @show_target = `tgtadm -C $control_port --op show --mode target`; my $tid; my $targetname; # Here, we create hashes of target names (all target data) and target tids foreach my $show_target_line (@show_target) { if ( $show_target_line =~ m/^Target (\d*): (.+)/ ) { $tid = $1; $targetname = $2; $tgtadm_output{$targetname} = $show_target_line; $tgtadm_output_tid{$targetname} = $tid; $tgtadm_output_name{$tid} = $targetname; } else { $tgtadm_output{$targetname} .= $show_target_line; } } # What is the largest tid? my @tids = values %tgtadm_output_tid; @largest_tid = sort { $a <=> $b } @tids; $next_tid = $largest_tid[$#largest_tid]; } sub process_accounts { # We need to run as root if ( $> ) { die("You must be root to run this program.\n"); } my @show_account = `tgtadm -C $control_port --op show --mode account`; # Here, we create a hash of accounts foreach my $show_account_line (@show_account) { if ( $show_account_line =~ m/^\s+(.*?)$/ ) { $existing_accounts{$1} = 1; } } } # Parse config file(s) sub parse_configs { # Parse the config if ($alternate_conf ne 0) { # Check if alternative configuration file exists if (-e "$alternate_conf") { execute("# Using $alternate_conf as configuration file\n"); %conf = ParseConfig(-ConfigFile => "$alternate_conf", -UseApacheInclude => 1, -IncludeDirectories => 1, -IncludeGlob => 1, -MergeDuplicateBlocks => 1); } else { die("Config file $alternate_conf not found. Exiting...\n"); } } else { # Parse the config file with Config::General if (-e "$configfile") { %conf = ParseConfig(-ConfigFile => "$configfile", -UseApacheInclude => 1, -IncludeDirectories => 1, -IncludeGlob => 1, -MergeDuplicateBlocks => 1); } else { die("Config file $configfile not found. Exiting...\n"); } } } # Add targets, if they are not configured already my $default_driver; my $target; my $option; my $value; my $lun; sub add_targets { my $single_target = $_[0]; my $configured = $_[1]; my $connected = $_[2]; my $in_configfile = $_[3]; my $isns_enabled = check_isns; foreach my $k (sort keys %conf) { if ($k eq "default-driver") { if (not length ref($conf{$k})) { $default_driver = $conf{$k}; } else { print "Multiple default-driver definitions are not allowed!\n"; print "Check your config file for errors.\n"; exit 1; } } elsif ($k eq "ignore-errors") { if ($conf{$k} eq "yes") { $ignore_errors = 1; } } elsif (($k eq "iSNSAccessControl") || ($k eq "iSNSServerIP") || ($k eq "iSNSServerPort")) { if ($isns_enabled eq 0) { check_if_hash_array($conf{$k}, $k); execute("tgtadm -C $control_port --op update --mode sys --name $k -v $conf{$k}"); } } elsif (($k eq "incomingdiscoveryuser") || ($k eq "outgoingdiscoveryuser")) { my @userpass = split(/ /, $conf{$k}); check_value($userpass[1]); # Only delete or create account if it doesn't already exist if (! exists $existing_accounts{$userpass[0]} ) { execute("tgtadm -C $control_port --mode account --op new --user=$userpass[0] --password=$userpass[1]"); $existing_accounts{$userpass[0]} = 1; } my $extra_str = ""; if ($k eq "outgoingdiscoveryuser") { $extra_str = " --outgoing"; } execute("tgtadm -C $control_port --mode account --op bind --user=$userpass[0] $extra_str"); } } foreach my $k (sort keys %conf) { if ($k eq "iSNS") { if ($isns_enabled eq 0) { check_if_hash_array($conf{$k}, $k); execute("tgtadm -C $control_port --op update --mode sys --name $k -v $conf{$k}"); } else { execute("# iSNS already enabled"); } } elsif ($k eq "control-port") { if ($control_port eq 0) { $control_port = $conf{$k}; } else { print "Multiple control-port definitions are not allowed!\n"; print "Check your config file for errors.\n"; exit 1; } } } # If $default_driver is empty, default to iscsi if (not defined $default_driver) { execute("# default-driver not defined, defaulting to iscsi.\n"); $default_driver = "iscsi"; } foreach my $k (sort keys %conf) { if ($k eq "target") { foreach my $k2 (sort keys %{$conf{$k}}) { # Do we run update or execute? if (length $single_target) { if ($single_target ne $k2) { next; } else { $target = $single_target; } } else { $target = $k2; } my $in_use = 0; if (length $single_target) { $in_use = main_delete($target); } my $allowall = 1; if ((not defined $tgtadm_output{$k2}) || ($update ne 0 && $in_use == 0) || ($update ne 0 && $in_use == 1 && $pretend == 1 && $force == 1)) { # We have to find available tid if ($in_configfile == 1 && $configured == 0 && $pretend == 0) { my $maxtid = find_max_tid(); $next_tid = $maxtid + 1; } elsif (length $single_target && $configured == 1) { $next_tid = $tgtadm_output_tid{$target}; } else { $next_tid = $next_tid + 1; } # Before we add a target, we need to know its type # and other parameters which can be specified globally my %target_options; my $target_options_ref; my $data_key; foreach my $k3 (sort keys %{$conf{$k}{$k2}}) { $lun = 1; $option = $k3; $value = $conf{$k}{$k2}{$k3}; check_value($value); $target_options{$option} = $value; $target_options_ref = \%target_options; $data_key = make_key($target_options_ref, "lun", "allow-in-use"); } if (defined $target_options{"controller_tid"}) { $next_tid = $target_options{"controller_tid"}; } if (not defined $target_options{"driver"}) { $target_options{"driver"} = $default_driver; } my $driver = $target_options{"driver"}; execute("# Adding target: $target"); execute("tgtadm -C $control_port --lld $driver --op new --mode target --tid $next_tid -T $target"); foreach my $k3 (sort keys %{$conf{$k}{$k2}}) { $option = $k3; $value = $conf{$k}{$k2}{$k3}; check_value($value); process_options($target_options_ref,$data_key); # If there was no option called "initiator-address", it means # we want to allow ALL initiators for this target if ($option eq "initiator-address") { $allowall = 0; } } if ($allowall == 1) { execute("tgtadm -C $control_port --lld $driver --op bind --mode target --tid $next_tid -I ALL"); } } else { if (not length $configured || $in_use eq 1) { execute("# Target $target already exists!"); } } $tgtadm_output_tid{$target} = $next_tid; } if (length $single_target && $in_configfile == 0 && $configured == 0) { print "Target $single_target is currently not configured\n"; print "and does not exist in the config file - can't continue!\n"; exit 1; } execute(); } } } # Pre-parse the config and get some values we need sub make_key { my $target_options_ref = shift; my @actions = @_; my %data_key; foreach my $action (@actions) { if (ref $$target_options_ref{'backing-store'} eq "HASH") { foreach my $testlun (keys %{$$target_options_ref{'backing-store'}}) { $data_key{$testlun}{$action} = $$target_options_ref{'backing-store'}{$testlun}{$action}; } } if (ref $$target_options_ref{'direct-store'} eq "HASH") { foreach my $testlun (keys %{$$target_options_ref{'direct-store'}}) { $data_key{$testlun}{$action} = $$target_options_ref{'direct-store'}{$testlun}{$action}; } } } return \%data_key; } # Some options can be specified only once sub check_if_hash_array { my $check = $_[0]; my $definition = $_[1]; if (ref($check) eq 'ARRAY' || ref($check) eq "HASH") { if ($option) { print "Multiple '$definition' definitions in '$option' not allowed!\n"; print "Check your config file for errors (target: $target).\n"; } else { print "Multiple '$definition' definitions not allowed!\n"; print "Check your config file for errors.\n"; } exit 1; } } # Force an array if we just have one command sub force_array { unless (ref($value) eq 'ARRAY') { $value = [ $value ]; } } # If we start any external command, we want to know if it exists sub check_exe { my $command = $_[0]; my $option = $_[1]; my @path = split(":", $ENV{PATH}); my $exists = 0; foreach my $path (@path) { if ( -x "$path/$command" && -f "$path/$command" ) { $exists = 1 } } if ($exists == 0) { if ($command eq "sg_inq") { print "Command '$command' (needed by '$option') is not in your path - can't continue!\n"; exit 1; } elsif ($command eq "lsof") { execute("# Command '$command' is not in your path."); execute("# Can't reliably check if device is not in use."); return 1; } } } # Apply additional parameters sub add_params { my $param = shift; my $param_value = shift; my $lun = shift; my $driver = shift; if ($param eq "write-cache") { if ($param_value eq "off") { return("tgtadm -C $control_port --lld $driver --op update --mode logicalunit --tid $next_tid --lun=$lun --params mode_page=8:0:18:0x10:0:0xff:0xff:0:0:0xff:0xff:0xff:0xff:0x80:0x14:0:0:0:0:0:0"); } elsif ($param_value eq "on" || not length $param_value) { return("# Write cache is enabled (default) for lun $lun."); } else { return("# WARNING! Unknown value ($param_value) to write-cache! Accepted values are \"on\" and \"off\"."); } } if ($param eq "scsi_id" || $param eq "scsi_sn" || $param eq "vendor_id" || $param eq "product_id" || $param eq "product_rev" || $param eq "sense_format" || $param eq "removable" || $param eq "online" || $param eq "path" || $param eq "mode_page" || $param eq "lbppbe" || $param eq "la_lba" || $param eq "optimal_xfer_gran" || $param eq "optimal_xfer_len" || $param eq "readonly") { return("tgtadm -C $control_port --lld $driver --op update --mode logicalunit --tid $next_tid --lun=$lun --params $param=\"$param_value\""); } if ($param eq "params") { return("tgtadm -C $control_port --lld $driver --op update --mode logicalunit --tid $next_tid --lun=$lun --params \"$param_value\""); } } # Find next available LUN sub find_next_lun { my $backing_store = $_[0]; my $data_key_ref = $_[1]; my $lun_collision = 0; my $lun_is_free = 0; my $found_lun = 1; while ($lun_is_free == 0) { foreach my $testlun (keys %$data_key_ref) { foreach my $testlun2 (values %{$$data_key_ref{$testlun}}) { if ($found_lun eq $testlun2) { $lun_collision = 1; } } } if ($lun_collision == 0) { $lun_is_free = 1; } else { $found_lun += 1; } $lun_collision = 0; } $$data_key_ref{$backing_store}{'lun'} = $found_lun; return $found_lun; } # Add backing or direct store sub add_backing_direct { my $backing_store = $_[0]; my $target_options_ref = $_[1]; my $data_key_ref = $_[2]; my $direct_store = $_[3]; my $driver = $$target_options_ref{"driver"}; my $bstype = $$target_options_ref{"bs-type"}; # Is the device in use? my $can_alloc = 1; if ($bstype !~ "rbd" && $force != 1 && $$target_options_ref{'allow-in-use'} ne "yes") { $can_alloc = check_device($backing_store,$data_key_ref); } if ($can_alloc == 1 && ($bstype =~ "glfs" || $bstype =~ "rbd" || (-e $backing_store && ! -d $backing_store))) { my @exec_commands; my $device_type; my $bs_type; my $bsopts; my $bsoflags; my $block_size; my %luns; my @added_luns; # Find out LUNs which are "reserved" in the config file if (ref $value eq "HASH") { if (length $$data_key_ref{$backing_store}{'lun'}) { $lun = $$data_key_ref{$backing_store}{'lun'}; } else { # Find an available lun if it wasn't specified $lun = find_next_lun($backing_store,$data_key_ref); } } # Process parameters for each lun / backing store if (ref $value eq "HASH") { my %params_added; my @mode_page; my @params; foreach my $store (keys %$value) { # lun if (length $$target_options_ref{"lun"}) { check_if_hash_array($$target_options_ref{"lun"}, "lun"); $lun = $$target_options_ref{"lun"}; } if (ref $$value{$store} eq "HASH" && $store eq $backing_store) { foreach my $store_option (keys %{$$value{$store}}) { my $result = $$value{$store}{$store_option}; check_value($result); if ($store_option ne "mode_page" && $store_option ne "params") { check_if_hash_array($result,$store_option) } # write-cache can be set globally per target and overridden per lun, # so we treat it differently if ($store_option ne "mode_page" && $store_option ne "write-cache" && $store_option ne "params") { my $exec_command = add_params($store_option, $result, $lun, $driver); push(@exec_commands, $exec_command); $params_added{$store_option} = 1; } if ($store_option eq "write-cache") { my $exec_command = add_params($store_option, $result, $lun, $driver); $params_added{write_cache} = 1; push(@exec_commands, $exec_command); } if ($store_option eq "device-type") { $device_type = $result; $params_added{$store_option} = 1; } if ($store_option eq "bs-type") { $bs_type = $result; $params_added{$store_option} = 1; } if ($store_option eq "bsopts") { $bsopts = $result; $params_added{$store_option} = 1; } if ($store_option eq "bsoflags") { $bsoflags = $result; $params_added{$store_option} = 1; } if ($store_option eq "block-size") { $block_size = $result; $params_added{$store_option} = 1; } if ($store_option eq "mode_page") { unless (ref($result) eq 'ARRAY') { $result = [ $result ]; } @mode_page = @$result; foreach my $mode_page (@mode_page) { my $exec_command = add_params($store_option, $mode_page, $lun, $driver); push(@exec_commands, $exec_command); } } if ($store_option eq "params") { unless (ref($result) eq 'ARRAY') { $result = [ $result ]; } @params = @$result; foreach my $param (@params) { my $exec_command = add_params($store_option, $param, $lun, $driver); push(@exec_commands, $exec_command); } } } } } # Used only if lun is a direct-store my $sg_inq; my $sg_readcap; my %direct_params; if ($direct_store == 1) { $sg_inq=`sg_inq "$backing_store"`; if ($sg_inq=~m { Vendor\ identification:\s+?(.*?)\n \s+Product\ identification:\s+(.*?)\n \s+Product\ revision\ level:\s+(.*?)\n (?:\s+Unit\ serial\ number:\s+(.*?)\n)? }xs ) { # If they were not defined globally for a target, # add them now if (not length $$target_options_ref{vendor_id}) { $direct_params{vendor_id} = $1; } if (not length $$target_options_ref{product_id}) { $direct_params{product_id} = $2; } if (not length $$target_options_ref{product_rev}) { $direct_params{product_rev} = $3; } if (not length $$target_options_ref{scsi_sn}) { $direct_params{scsi_sn} = $4; } } $sg_inq=`sg_inq -p 0xb0 "$backing_store"`; if ($sg_inq=~m { Optimal\ transfer\ length\ granularity:\s+?(\d+).*?\n \s+Maximum\ transfer\ length:\s+?(\d+).*?\n \s+Optimal\ transfer\ length:\s+?(\d+).*?\n }xs ) { if (not length $$target_options_ref{optimal_xfer_gran}) { $direct_params{optimal_xfer_gran} = $1; } # $2 unused if (not length $$target_options_ref{optimal_xfer_len}) { $direct_params{optimal_xfer_len} = $3; } } $sg_readcap=`sg_readcap -16 "$backing_store"`; if ($sg_readcap=~ m { Logical\ block\ length=(\d+).*?\n \s+Logical\ blocks\ per\ physical\ block\ exponent=(\d+).*?\n \s+Lowest\ aligned\ logical\ block\ address=(\d+).*?\n }xs) { if (not length $block_size) { $block_size = $1; } if (not length $$target_options_ref{lbppbe}) { $direct_params{lbppbe} = $2; } if (not length $$target_options_ref{la_lba}) { $direct_params{la_lba} = $3; } } } # Add these parameters if they were not overwritten in the config file my @opts = ("scsi_id", "sense_format", "removable", "online", "path", "readonly"); foreach my $single_opt (@opts) { check_if_hash_array($$target_options_ref{$single_opt},$single_opt); if ($params_added{$single_opt} ne 1 && length $$target_options_ref{$single_opt}) { my $exec_command = add_params($single_opt, $$target_options_ref{$single_opt}, $lun, $driver); push(@exec_commands, $exec_command); $params_added{$single_opt} = 1; } } # These options can be fetched by sg_inq for direct-store my @opts = ("vendor_id", "product_id", "product_rev", "scsi_sn", "lbppbe", "la_lba", "optimal_xfer_gran", "optimal_xfer_len"); foreach my $single_opt (@opts) { check_if_hash_array($$target_options_ref{$single_opt},$single_opt); my $this_opt; if (length $$target_options_ref{$single_opt}) { $this_opt = $$target_options_ref{$single_opt}; } elsif (length $direct_params{$single_opt}) { $this_opt = $direct_params{$single_opt}; } if ($params_added{$single_opt} ne 1 && length $this_opt) { my $exec_command = add_params($single_opt, $this_opt, $lun, $driver); push(@exec_commands, $exec_command); $params_added{$single_opt} = 1; } } # write-cache if ($params_added{write_cache} ne 1) { my $exec_command = add_params("write-cache", $$target_options_ref{"write-cache"}, $lun, $driver); push(@exec_commands, $exec_command); $params_added{write_cache} = 1; } # mode_page unless (ref($$target_options_ref{mode_page}) eq 'ARRAY') { $$target_options_ref{mode_page} = [ $$target_options_ref{mode_page} ]; } foreach my $mode_page (@{$$target_options_ref{"mode_page"}}) { if (length $mode_page) { my $exec_command = add_params("mode_page", $mode_page, $lun, $driver); push(@exec_commands, $exec_command); } } # params unless (ref($$target_options_ref{params}) eq 'ARRAY') { $$target_options_ref{params} = [ $$target_options_ref{params} ]; } foreach my $param (@{$$target_options_ref{"params"}}) { if (length $param) { my $exec_command = add_params("params", $param, $lun, $driver); push(@exec_commands, $exec_command); } } # device-type if ($params_added{"device-type"} ne 1) { check_if_hash_array($$target_options_ref{"device-type"}, "device-type"); $device_type = $$target_options_ref{"device-type"}; } # bs-type if ($params_added{"bs-type"} ne 1) { check_if_hash_array($$target_options_ref{"bs-type"}, "bs-type"); $bs_type = $$target_options_ref{"bs-type"}; } # bsopts if ($params_added{"bsopts"} ne 1) { check_if_hash_array($$target_options_ref{"bsopts"}, "bsopts"); $bsopts = $$target_options_ref{"bsopts"}; } # bsoflags if ($params_added{"bsoflags"} ne 1) { check_if_hash_array($$target_options_ref{"bsoflags"}, "bsoflags"); $bsoflags = $$target_options_ref{"bsoflags"}; } } else { print "If you got here, this means your config file is not supported.\n"; print "Please report it to stgt mailing list and attach your config files.\n"; exit 1; } # Execute commands for a given LUN if (length $device_type) { $device_type = "--device-type $device_type" }; if (length $bs_type) { $bs_type = "--bstype $bs_type" }; if (length $bsopts) { $bsopts = "--bsopts $bsopts" }; if (length $bsoflags) { $bsoflags = "--bsoflags $bsoflags" }; if (length $block_size) { $block_size = "--blocksize $block_size" }; execute("tgtadm -C $control_port --lld $driver --op new --mode logicalunit --tid $next_tid --lun $lun -b \"$backing_store\" $device_type $bs_type $bsopts $bsoflags $block_size"); # Commands should be executed in order my @execute_last; foreach my $exec_command (@exec_commands) { if (length $exec_command) { if ($exec_command =~ m/params mode_page/) { execute($exec_command); } else { push(@execute_last, $exec_command); } } } foreach my $exec_command (@execute_last) { execute($exec_command); } $lun += 1; return $lun; } elsif ($can_alloc == 0) { execute("# Skipping device $backing_store - it is in use."); execute("# You can override it with --force or 'allow-in-use yes' config option."); execute("# Note - do so only if you know what you're doing, you may damage your data."); } else { execute("# Skipping device: $backing_store"); execute("# $backing_store does not exist - please check the configuration file"); } } # Process options from the config file sub process_options { my $target_options_ref = $_[0]; my $data_key_ref = $_[1]; my $driver = $$target_options_ref{"driver"}; if ($option eq "backing-store" || $option eq "direct-store") { my $direct_store = 0; if ($option eq "direct-store") { check_exe("sg_inq", "option direct-store"); $direct_store = 1; } # We want to make everything a hash to use it # in the same way later on unless (ref($value)) { $value = { $value } } my %arrvalue; if (ref($value) eq "ARRAY") { foreach my $backing_store (@$value) { $arrvalue{$backing_store} = 1; } $value = \%arrvalue; } if (ref($value) eq "HASH") { foreach my $backing_store (sort keys %$value) { if ($backing_store =~ m/HASH/) { print "\nYour config file is not supported. See targets.conf.example for details.\n"; exit 1; } } foreach my $backing_store (sort keys %$value) { add_backing_direct($backing_store,$target_options_ref,$data_key_ref,$direct_store); } } } if ($option eq "incominguser") { # if we have one command, force it to be an array anyway force_array(); my @value_arr = @$value; foreach my $incominguser (@value_arr) { my @userpass = split(/ /, $incominguser); check_value($userpass[1]); # Only delete or create account if it doesn't already exist if (! exists $existing_accounts{$userpass[0]} ) { execute("tgtadm -C $control_port --lld $driver --mode account --op new --user=$userpass[0] --password=$userpass[1]"); $existing_accounts{$userpass[0]} = 1; } execute("tgtadm -C $control_port --lld $driver --mode account --op bind --tid=$next_tid --user=$userpass[0]"); } } if ($option eq "outgoinguser") { # if we have one command, force it to be an array anyway force_array(); if (length @$value[1]) { execute("# Warning: only one $option is allowed. Will only use the first one."); } my @userpass = split(/ /, @$value[0]); check_value($userpass[1]); # Only delete or create account if it doesn't already exist if (! exists $existing_accounts{$userpass[0]} ) { execute("tgtadm -C $control_port --lld $driver --mode account --op new --user=$userpass[0] --password=$userpass[1]"); $existing_accounts{$userpass[0]} = 1; } execute("tgtadm -C $control_port --lld $driver --mode account --op bind --tid=$next_tid --user=$userpass[0] --outgoing"); } if ($option eq "initiator-address") { # if we have one command, force it to be an array anyway force_array(); my @value_arr = @$value; foreach my $initiator_address (@value_arr) { check_value($initiator_address); execute("tgtadm -C $control_port --lld $driver --op bind --mode target --tid $next_tid -I $initiator_address"); } } if ($option eq "initiator-name") { # if we have one command, force it to be an array anyway force_array(); my @value_arr = @$value; foreach my $initiator_name (@value_arr) { check_value($initiator_name); execute("tgtadm -C $control_port --lld $driver --op bind --mode target --tid $next_tid -Q $initiator_name"); } } if ($option =~ m/^MaxRecvDataSegmentLength$| ^MaxXmitDataSegmentLength$| ^HeaderDigest$| ^DataDigest$| ^InitialR2T$| ^MaxOutstandingR2T$| ^ImmediateData$| ^FirstBurstLength$| ^MaxBurstLength$| ^DataPDUInOrder$| ^DataSequenceInOrder$| ^ErrorRecoveryLevel$| ^IFMarker$| ^OFMarker$| ^DefaultTime2Wait$| ^DefaultTime2Retain$| ^OFMarkInt$| ^IFMarkInt$| ^MaxConnections$| ^state/x) { # if we have one command, force it to be an array anyway force_array(); if (length @$value[1]) { execute("# Warning: only one $option is allowed. Will only use the first one."); } check_value($option); execute("tgtadm -C $control_port --lld $driver --mode target --op update --tid $next_tid --name $option --value $$value[0]"); } } # If the target is configured, but not present in the config file, # try to remove it sub remove_targets { process_targets; my @all_targets = keys %tgtadm_output_tid; foreach my $existing_target (@all_targets) { my $dontremove = 0; my $k2; foreach my $k (sort keys %conf) { if ( $k eq "target" ) { foreach $k2 (sort keys %{$conf{$k}}) { if ( $k2 eq $existing_target ) { $dontremove = 1; } } if ( $dontremove == 0 ) { # Remove the target main_delete($existing_target); } } } } } # Dump current tgtd configuration sub dump_config { process_targets; my @all_targets = keys %tgtadm_output_tid; # If all targets use the same driver, use it only once in the config my $skip_driver = 0; my @drivers_combined; foreach my $current_target (@all_targets) { my $driver = show_target_info($current_target, "driver"); push (@drivers_combined, $driver); } my %drivers_uniq; @drivers_uniq{@drivers_combined} = (); my @drivers_combined_uniq = sort keys %drivers_uniq; if (scalar @drivers_combined_uniq == 1) { print "default-driver $drivers_combined_uniq[0]\n\n"; } # Print everything else in the config foreach my $current_target (@all_targets) { my $target_name = show_target_info($current_target, "target_name"); print "\n"; if (scalar @drivers_combined_uniq gt 1) { my $driver = show_target_info($current_target, "driver"); print "\tdriver $driver\n"; } my @backing_stores = show_target_info($current_target, "backing_stores"); foreach my $backing_store (@backing_stores) { print "\tbacking-store $backing_store\n"; } my @account_information = show_target_info($current_target, "account_information"); foreach my $account (@account_information) { if ($account =~ /(.+)\ \(outgoing\)/) { print "\toutgoinguser $1 PLEASE_CORRECT_THE_PASSWORD\n"; } elsif (length $account) { print "\tincominguser $account PLEASE_CORRECT_THE_PASSWORD\n"; } } my @acl_information = show_target_info($current_target, "acl_information"); if (scalar(@acl_information) != 1 || $acl_information[0] ne "ALL") { foreach my $ini_address (@acl_information) { # determine here if acl is an IP or IQN if ( $ini_address =~ /^[0-9,.E]+$/ ) { print "\tinitiator-address $ini_address\n"; } else { print "\tinitiator-name $ini_address\n"; } } } print "\n\n"; } } # Offline or ready targets sub ready_offline_targets { my $var = $_[0]; # This variable is either "offline" or "ready" my $off_ready; if ($ready eq 0) { $off_ready = $offline } elsif ($offline eq 0) { $off_ready = $ready } else { print "Invalid value (you can't use both ready and offline)!\n"; exit 1; } if ($off_ready eq "help") { print < $var all or selected targets Example usage: --$var help - display this help --$var ALL - $var all targets --$var tid=4 - $var target 4 (target with tid 4) --$var iqn.2008-08.com.example:some.target - $var this target EOF } elsif ($off_ready eq "ALL") { process_targets; # Run over all targets and offline/ready them my @all_targets = keys %tgtadm_output_tid; foreach my $existing_target (@all_targets) { execute("tgtadm -C $control_port --op update --mode target --tid=$tgtadm_output_tid{$existing_target} -n state -v $var"); } } elsif ($off_ready =~ m/tid=(.+)/) { process_targets; execute("tgtadm -C $control_port --op update --mode target --tid=$1 -n state -v $var"); } else { process_targets; if (length $tgtadm_output_tid{$off_ready}) { execute("tgtadm -C $control_port --op update --mode target --tid=$tgtadm_output_tid{$off_ready} --name=\"$off_ready\" -n state -v $var"); } else { print "There is no target with name \"$off_ready\", can't $var it!\n"; exit 1; } } } # Show info for a given target sub show_target_info { my $existing_target = $_[0]; my $task = $_[1]; # Returns target information if ($task eq "target_name") { if ($tgtadm_output{$existing_target} =~ m/^Target (\d*): (.+)/ ) { return $2; } # Returns driver information } elsif ($task eq "driver") { if ($tgtadm_output{$existing_target} =~ m/\s+Driver: (.+)/ ) { return $1; } # Returns backing store } elsif ($task eq "backing_stores") { if ($tgtadm_output{$existing_target} =~ m/\s+Backing store path: (?!None)(.+)/ ) { my @backing_stores = $tgtadm_output{$existing_target} =~ m{\s+Backing store path: (?!None\n)(.+)}g; return @backing_stores; } return; # Returns account information: } elsif ($task eq "account_information") { if ($tgtadm_output{$existing_target} =~ m{ \s+Account\ information:\n(.*)\n\s+ACL\ information: }xs ) { my @accounts = split(/\n/, $1); my @account_information; foreach my $user (@accounts) { my @var = split(/^\s+/, $user); push(@account_information, $var[1]); } return @account_information; } # Returns ACL information } elsif ($task eq "acl_information") { if ($tgtadm_output{$existing_target} =~ m{ \s+ACL\ information:\n(.*) }xs ) { my @ini_addresses = split(/\n/, $1); my @acls; foreach my $ini_address (@ini_addresses) { my @var = split(/^\s+/, $ini_address); push(@acls, $var[1]); } return @acls; } # Returns sessions } elsif ($task eq "sessions") { my %sessions; if ($tgtadm_output{$existing_target} =~ m{ \s+I_T\ nexus\ information:\n(.*)LUN\ information: }xs ) { my @var = split(/\n/, $1); my $sid; my $cid; foreach my $line (@var) { if ($line =~ m/\s+I_T nexus:\ (.+)/) { $sid = $1; } else { if ($line =~ m/\s+Connection:\ (.+)/) { $cid = $1; $sessions{$sid} = $cid; } } } } return %sessions; } } # Main subroutine for deleting targets sub main_delete { my $current_target = $_[0]; my $current_tid = $_[1]; my $configured = check_configured($current_target); my $del_upd_text; # Check if the target has initiators connected if ($tgtadm_output{$current_target} =~ m/\s+Connection:/) { if ($force == 1) { execute("# Removing target: $current_target"); # Remove ACLs first my @acl_info = show_target_info($current_target, "acl_information"); foreach my $acl (@acl_info) { execute("tgtadm -C $control_port --op unbind --mode target --tid $tgtadm_output_tid{$current_target} -I $acl"); } # Now, remove all sessions / connections from that tid my %sessions = show_target_info($current_target, "sessions"); foreach my $sid (keys %sessions) { foreach my $cid ($sessions{$sid}) { execute("tgtadm -C $control_port --op delete --mode conn --tid $tgtadm_output_tid{$current_target} --sid $sid --cid $cid"); } } execute("tgtadm -C $control_port --mode target --op delete --tid=$tgtadm_output_tid{$current_target}"); } else { if ($update ne 0) { $del_upd_text = "updated"; } else { $del_upd_text = "deleted"; } execute("# Target with tid $tgtadm_output_tid{$current_target} ($current_target) is in use, it won't be $del_upd_text."); return 1; } } elsif (length $tgtadm_output_tid{$current_target}) { execute("# Removing target: $current_target"); execute("tgtadm -C $control_port --mode target --op delete --tid=$tgtadm_output_tid{$current_target}"); } else { if (length $current_tid) { execute("# Target with tid $current_tid does not exist!"); } else { execute("# Target with name $current_target does not exist!"); } } if ($configured ne 0) { execute(); } return 0; } # Delete the targets sub delete_targets { if ($delete eq "help") { print < delete all or selected targets The target will be deleted only if it's not used (no initiator is connected to it). If you want to delete targets which are in use, you have to add "--force" flag. Example usage: --delete help - display this help --delete ALL - delete all targets --delete tid=4 - delete target 4 (target with tid 4) --delete iqn.2008-08.com.example:some.target - delete this target EOF exit; } elsif ($delete eq "ALL") { process_targets; # Run over all targets and delete them my @all_targets = keys %tgtadm_output_tid; foreach my $current_target (@all_targets) { main_delete($current_target); } } elsif ($delete =~ m/^tid=(.+)/) { # Delete by tid process_targets; my $current_target = $tgtadm_output_name{$1}; main_delete($current_target, $1); } else { # Delete by name process_targets; my $current_target = $delete; main_delete($current_target); } } # Update targets sub update_targets { if ($update eq "help") { print < update all or selected targets The target will be updated only if it's not used (no initiator is connected to it). If you want to update targets which are in use, you have to add "--force" flag. Example usage: --update help - display this help --update ALL - update all targets --update tid=4 - update target 4 (target with tid 4) --update iqn.2008-08.com.example:some.target - update this target EOF exit; } elsif ($update eq "ALL") { # Run over all targets and delete them if they are not in use parse_configs; process_targets; my @targets_combined = combine_targets(); foreach my $current_target (@targets_combined) { my $configured = check_configured($current_target); my $connected = check_connected($current_target); my $in_configfile = check_in_configfile($current_target); combine_targets(); if (($in_configfile == 0) && ($configured == 1)) { # Delete the target if it's not in the config file main_delete($current_target); } else { add_targets($current_target, $configured, $connected, $in_configfile); } } } elsif ($update =~ m/^tid=(.+)/) { # Update by tid parse_configs; process_targets; my $current_target = $tgtadm_output_name{$1}; my $configured = check_configured($current_target); my $connected = check_connected($current_target); my $in_configfile = check_in_configfile($current_target); if (($in_configfile == 0) && ($configured == 1)) { # Delete the target if it's not in the config file main_delete($current_target); } elsif ($configured == 1) { add_targets($current_target, $configured, $connected, $in_configfile); } else { print "There is no target with tid $1, can't continue!\n"; exit 1; } } else { # Update by name parse_configs; process_targets; my $current_target = $update; my $configured = check_configured($current_target); my $connected = check_connected($current_target); my $in_configfile = check_in_configfile($current_target); if ($in_configfile == 0 && $configured == 1) { # Delete the target if it's not in the config file main_delete($current_target); } else { add_targets($current_target, $configured, $connected, $in_configfile); } } } # Find the biggest tid sub find_max_tid { my @all_targets = keys %tgtadm_output_tid; my $maxtid = 0; foreach my $var (@all_targets) { if ($tgtadm_output_tid{$var} > $maxtid) { $maxtid = $tgtadm_output_tid{$var}; } } return $maxtid; } # Combine targets from the config file and currently configured targets sub combine_targets { my @targets_in_configfile; my @all_targets = keys %tgtadm_output_tid; my @targets_combined; # Make an array of targets in the config file foreach my $k (sort keys %conf) { if ( $k eq "target" ) { foreach my $k2 (sort keys %{$conf{$k}}) { push(@targets_in_configfile, $k2) } } } # Use only unique elements from both arrays foreach my $current_target (@all_targets) { push (@targets_combined, $current_target) unless grep { $_ eq $current_target } @targets_in_configfile; } @targets_combined = (@targets_combined, @targets_in_configfile); return @targets_combined; } # Check if a value is correct sub check_value { if ( not defined $_[0] or not length $_[0] ) { print "\nOption $option has a missing value!\n"; print "Check your config file for errors (target: $target)\n"; exit 1; } } # Check if the target is in the config file sub check_in_configfile { my $current_target = $_[0]; my $result; foreach my $k (sort keys %conf) { if ( $k eq "target" ) { foreach my $k2 (sort keys %{$conf{$k}}) { if ($k2 eq $current_target) { return 1; } } # If we're here, we didn't find a match return 0; } } } # Check if the target is configured in tgtd sub check_configured { my $current_target = $_[0]; if (length $tgtadm_output_tid{$current_target}) { return 1; } else { return 0; } } # Check if any initiators are connected to the target sub check_connected { my $current_target = $_[0]; if ($tgtadm_output{$current_target} =~ m/\s+Connection:/) { return 1; } else { return 0; } } # Check if a device can be allocated # Device can be used "by system" (i.e. mounted, used as swap, as a part of # a RAID array etc.) or "by user" - i.e., already by tgtd, or someone doing: # dd if=/dev/1st_device of=/dev/2nd_device # We shouldn't allow a device to be used more than one time, as it could # cause corruption when written several times. Unless the user really wants to. sub check_device { my $backing_store = $_[0]; my $data_key_ref = $_[1]; # If allow-in-use is "yes", there is no need to do # farther tests if ($$data_key_ref{$backing_store}{'allow-in-use'} eq "yes") { return 1; } if (! -e $backing_store) { return 1; } # Check if the system uses this device use Fcntl qw(O_RDONLY O_EXCL); use Errno; sysopen(FH, $backing_store, O_RDONLY | O_EXCL); if ($!{EBUSY}) { execute("# Device $backing_store is used by the system (mounted, used by swap?)."); return 0; } close(FH); # Check if userspace uses this device my $lsof_check = check_exe("lsof"); if ($lsof_check ne 1) { system("lsof \"$backing_store\" >/dev/null 2>&1"); my $exit_value = $? >> 8; if ($exit_value eq 0) { execute("# Device $backing_store is used (already tgtd target?)."); execute("# Run 'lsof $backing_store' to see the details."); return 0; } } return 1; } # Execute or just print (or both) everything we start or would start sub execute { if ($pretend == 0) { my $args = "@_"; if ($verbose == 1) { print "$args\n"; } # Don't try to execute if it's a comment my @execargs = split(/#/, $args); if ($execargs[0] ne undef) { system($args); # If non-zero exit code was return, exit my $exit_value = $? >> 8; if (($exit_value != 0) && ($ignore_errors == 0)) { print "Command:\n\t$args\nexited with code: $exit_value.\n"; exit $exit_value; } } } elsif ($pretend == 1) { print "@_\n"; } } if ($execute == 1) { parse_configs; process_targets; process_accounts; add_targets; remove_targets; } elsif ($delete ne 0) { delete_targets; } elsif ($update ne 0) { process_accounts; update_targets; } elsif ($dump == 1) { dump_config; } elsif ($offline ne 0) { ready_offline_targets("offline"); } elsif ($ready ne 0) { ready_offline_targets("ready"); } else { print "No action specified.\n"; } tgt-1.0.85/scripts/tgt-core-test000077500000000000000000000167071435417276200165470ustar00rootroot00000000000000#!/bin/bash # Parent directory for data files.. HOME=/d/01 # Start tgtd if not running.. P=`ps -ef|grep -v grep|grep tgtd|wc -l` if [ "X"$P == "X0" ]; then tgtd -d 1 sleep 1 fi if [ ! -d $HOME ]; then mkdir -p $HOME fi if [ ! -f $HOME/hd_block ]; then dd if=/dev/zero of=$HOME/hd_block bs=1M count=8 fi set -x ############################################################################### # Set up SBC HDD device ############################################################################### TID=1 # Create Target ID 1.. tgtadm --lld iscsi --mode target --op new --tid $TID \ -T iqn.2007-03:marks-vtl_tgt:`hostname` sleep 1 # Create first LUN - Disk LUN=1 tgtadm --lld iscsi --mode logicalunit --op new --tid $TID --lun $LUN -b $HOME/hd_block --device-type=disk tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params scsi_sn=FRED00,scsi_id=Fred tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params vendor_id=QUANTUM,product_id=HD100,product_rev=0010,removable=1,sense_format=0 #### Set up mode pages #### # First try a couple of attempts with incorrect data.. # i.e. Expect the first two to fail! # - Length too long & Incorrect value (300) as one if the params... tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params mode_page=2:0:14:0x80:0x80:0:0xa:0:300:0:0:0:0:0:0:0:0:3 # - Length too short... tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params mode_page=2:0:14:0x80:0x80:0:0xa:0:0:0:0:0:0:0:0:0 # # From here on - should work OK.. # # Vendor Uniq - Mode page 0.. tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params mode_page=0:0:0 # Disconnect page tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params mode_page=2:0:14:0x80:0x80:0:0xa:0:0:0:0:0:0:0:0:0:0 # Format mode page tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params mode_page=3:0:22:0:0:0:0:0:0:0:0:1:0:2:0:0:0:0:0:0:0:0:13:0:0 # Geo mode page tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params mode_page=4:0:22:0:0:0:0x40:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0x3a:0x98:0:0 # Caching Page tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params mode_page=8:0:18:0x14:0:0xff:0xff:0:0:0xff:0xff:0xff:0xff:0x80:0x14:0:0:0:0:0:0 # ctrl mode page tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params mode_page=10:0:10:2:0:0:0:0:0:0:0:2:0 # Informational Exceptions Control Mode Page tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params mode_page=0x1c:0:10:8:0:0:0:0:0:0:0:0:0 for LUN in 2 3 4; do if [ ! -f $HOME/cdrom$LUN ]; then dd if=/dev/zero of=$HOME/cdrom$LUN bs=1M count=8 fi # Create LUN - CD/ROM tgtadm --lld iscsi --mode logicalunit --op new --tid $TID --lun $LUN -b $HOME/cdrom$LUN --device-type=cd tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params vendor_id=VirtualCD,product_id=CD101,product_rev=0010,scsi_sn=XYZZY1$LUN,removable=1 # Vendor Uniq - Mode page 0.. tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params mode_page=0:0:0 # ctrl mode page tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params mode_page=10:0:10:2:0:0:0:0:0:0:0:2:0 # Informational Exceptions Control Mode Page tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params mode_page=0x1c:0:10:8:0:0:0:0:0:0:0:0:0 done ############################################################################### # Set up SMC Medium Changer ############################################################################### LUN=5 if [ ! -f $HOME/smc ]; then dd if=/dev/zero of=$HOME/smc bs=1k count=1 fi tgtadm --lld iscsi --mode logicalunit --op new --tid $TID --lun $LUN \ -b $HOME/smc --device-type=changer #### Set up mode pages #### # From smc3-06.pdf # Page 0x02: Disconnect/Reconnect SPC-3 # Page 0x0a: Control SPC-3 # Page 0x18: Protocol Specific LUN SPC-3 # Page 0x19: Protocol Specific Port SPC-3 # Page 0x1a: Power Condition SPC-3 # Page 0x1c: Informational Exceptions Control SPC-3 # Page 0x1d: Element Address Assignment SMC-3 7.3.4 # Page 0x1e: Transport Geometry Parameters SMC-3 7.3.5 # Page 0x1f: Device Capabilities SMC-3 7.3.2 # Page 0x1f/Subpage 0x41: Extended Device Capabilities SMC-3 7.3.3 # Dummy 'page 0' tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params mode_page=0:0:0 # Disconnect/Reconnect tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params mode_page=2:0:14:0x80:0x80:0:0xa:0:0:0:0:0:0:0:0:0:0 # Power Condition tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params mode_page=0x1a:0:18:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0 # Informational Exceptions Control Mode Page tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params mode_page=0x1c:0:10:8:0:0:0:0:0:0:0:0:0 # Element Address Assignment - Setup afterwards. tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params mode_page=0x1d:0:0x12:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0 # Transport Geometry Parameters tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params mode_page=0x1e:0:2:0:0 # Device Capabilities tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params mode_page=0x1f:0:0x12:0x0f:7:0x0f:0x0f:0x0f:0x0f:0:0:0:0:0x0f:0x0f:0x0f:0x0f:0:0:0:0 tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params vendor_id=STK,product_id=L700,product_rev=0010,scsi_sn=XYZZY_0,removable=1 ## Add Data Transfer devices (3 drives) # Define slot address for devices. tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params element_type=4,start_address=1,quantity=3 # Now define which device at each address. tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params element_type=4,address=1,tid=1,lun=2 tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params element_type=4,address=2,tid=1,lun=3 tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params element_type=4,address=3,tid=1,lun=4 # Medium Transport Elements (robot arm / picker) tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params element_type=1,start_address=16,quantity=1 ## Storage Elements - 8 starting at addr 1024 tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params element_type=2,start_address=1024,quantity=8 # Add 'media' to slots tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params element_type=2,address=1024,barcode=ABC123,sides=1 tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params element_type=2,address=1026,barcode=ULT001L3,sides=1 # Import/Export Elements - 5 starting at addr 32 tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params element_type=3,start_address=32,quantity=5 # define path to virtual media tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params media_home=/d/01/vtl # Dump the list of configured slots to syslog... tgtadm --lld iscsi --mode logicalunit --op update --tid $TID --lun $LUN \ --params dump=1 # Allow ALL initiators to connect to this target tgtadm --lld iscsi --mode target --op bind --tid $TID -I ALL # Show all our good work. tgtadm --lld iscsi --mode target --op show tgt-1.0.85/scripts/tgt-setup-lun000077500000000000000000000160611435417276200165670ustar00rootroot00000000000000#!/bin/bash # LUN assignment script # # Copyright (C) 2007 Erez Zilber # Copyright (C) 2012 Roi Dayan # # 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, version 2 of the # License. # # 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 usage() { name=$(basename $0); echo "usage:"; echo -e "\t$name -n tgt_name -d dev -b bs_name -B block_size -t transport -C control_port [initiator_IP1 initiator_IP2 ...]"; echo "defaults:"; echo -e "\tbacking store: rdwr"; echo -e "\ttransport: iscsi"; echo -e "\tinitiator: ALL"; echo -e "\tcontrol port: -"; echo "examples:"; echo -e "\t$name -n tgt-1 -d /dev/sdb1 192.168.1.2"; echo -e "\t$name -n tgt-2 -d /tmp/null -b null -t iser"; echo -e "\t$name -n tgt-3 -d ~/disk3.bin -b rdwr 192.168.1.2 192.168.1.3"; echo -e "WARNING:" echo -e "\tPlease remember to set blocksize to 4096 for newer Advanced Format (4K) disks larger than 2TB. Otherwise, your existing filesystems will not be readable." } verify_params() { if ! [ "$dev" -o "$bs_type" == "null" ]; then echo "Error: a device is mandatory"; exit 1; else return; fi # Make sure that the device exists if ! [ -b $dev -o -f $dev -o -c $dev ]; then echo "Error: $dev is not a device"; exit 1; fi if ! [ "$tgt_name" ]; then echo "Error: target name is mandatory"; exit 1; fi if ! [ "$lld_name" ]; then echo "Error: lld name empty"; exit 1; fi } find_vacant_tgt_id() { id_list=$($TGTADM --lld $lld_name --op show --mode target | grep Target | cut -d" " -f2 | sed s/://) next_vacant_id=1 for id in $id_list; do if (($id > $next_vacant_id)); then break; else next_vacant_id=$((next_vacant_id+1)) fi done return $next_vacant_id } find_vacant_lun() { tid=$1 tgt_found=0 next_vacant_lun=0 tmp_file=/tmp/target_list.txt $TGTADM --lld $lld_name --op show --mode target > $tmp_file while read line; do # Check if we finished going over this target if ((tgt_found == 1 && $(echo $line | grep -c "^Target") == 1)); then break fi # Check if we found the requested target if (($(echo $line | grep -c "Target $tid:") == 1)); then tgt_found=1 continue fi if ((tgt_found == 1 && $(echo $line | grep -c "LUN:") == 1)); then curr_lun=$(echo $line | cut -d" " -f2) if (($curr_lun > $next_vacant_lun)); then break else next_vacant_lun=$((next_vacant_lun+1)) fi fi done < $tmp_file rm -f $tmp_file if ((tgt_found == 0)); then echo "Error: could not find a LUN for target $tid" return -1 fi return $next_vacant_lun } get_acl() { tid=$1 tgt_found=0 acl_found=0 next_vacant_lun=0 tmp_file=/tmp/target_list.txt local _acl="" $TGTADM --lld $lld_name --op show --mode target > $tmp_file while read line; do # Check if we finished going over this target if ((tgt_found == 1 && $(echo $line | grep -c "^Target") == 1)); then break fi # Check if we found the requested target if (($(echo $line | grep -c "Target $tid:") == 1)); then tgt_found=1 continue fi if ((tgt_found == 1 && $(echo $line | grep -c "ACL information") == 1)); then acl_found=1 continue fi if ((tgt_found == 1 && acl_found == 1)); then curr_acl=$(echo $line) _acl=`echo $_acl $curr_acl` fi done < $tmp_file rm -f $tmp_file echo $_acl } err_exit() { if ((new_tgt == 0)); then exit 1 fi echo "Deleting new target, tid=$tid" $TGTADM --lld $lld_name --op delete --mode target --tid $tid res=$? if [ $res -ne 0 ]; then echo "Error: failed to delete target, tid=$tid" fi exit 1 } check_if_tgt_exists() { tgt_list=$($TGTADM --lld $lld_name --op show --mode target | grep Target | cut -d" " -f3) for curr_tgt in $tgt_list; do if [ $tgt_name = $curr_tgt ]; then return 1 fi done return 0 } if [ $# -eq 0 ]; then usage exit 1 fi TGTADM="tgtadm" lld_name="iscsi" control_port="" while getopts "d:n:b:B:t:h:C:" opt do case ${opt} in d) dev=$OPTARG;; n) tgt_name=$OPTARG;; b) bs_type=$OPTARG;; B) block_size=$OPTARG;; t) lld_name=$OPTARG;; C) control_port=$OPTARG;; h*) usage exit 1 esac done shift $(($OPTIND - 1)) initiators=$* verify_params # Check if tgtd is running (we should have 2 daemons) tgtd_count=`pidof tgtd | wc -w` if [ $tgtd_count -lt 1 ]; then echo "tgtd is not running" echo "Exiting..." exit 1 fi if [ -n "$control_port" ]; then echo "Control port: $control_port" TGTADM="$TGTADM -C $control_port" fi echo "Using transport: $lld_name" if ! [[ $tgt_name =~ ^iqn ]]; then tgt_name="iqn.2001-04.com.$(hostname -s)-$tgt_name" fi # Make sure that a target with the same name doesn't exist check_if_tgt_exists if [ $? -eq 1 ]; then echo "Error: target named $tgt_name already exists" read -p "Add a new lun to the existing target? (yes/NO): " add_lun if [ "$add_lun" != "yes" ]; then exit 1 fi tid=$($TGTADM --lld $lld_name --op show --mode target | grep $tgt_name | cut -d" " -f2) tid=${tid%:} new_tgt=0 else find_vacant_tgt_id tid=$? # Create the new target echo "Creating new target (name=$tgt_name, tid=$tid)" $TGTADM --lld $lld_name --op new --mode target --tid $tid -T $tgt_name res=$? if [ $res -ne 0 ]; then echo "Error: failed to create target (name=$tgt_name, tid=$tid)" exit 1 fi new_tgt=1 fi find_vacant_lun $tid lun=$? # Add a logical unit to the target echo "Adding a logical unit ($dev) to target, tid=$tid" if [ $bs_type ]; then echo "Setting backing store type: $bs_type" bs_opt="-E $bs_type" fi if [ $block_size ]; then blksize_opt="--blocksize $block_size" fi $TGTADM --lld $lld_name --op new --mode logicalunit --tid $tid --lun $lun -b $dev $bs_opt $blksize_opt res=$? if [ $res -ne 0 ]; then echo "Error: failed to add a logical unit ($dev) to target, tid=$tid" err_exit fi # Define which initiators can use this target tgt_acl=`get_acl $tid` if test "$initiators" ; then # Allow access only for specific initiators echo "Accepting connections from $initiators" for initiator in $initiators; do # Check if record already exists if ((`echo $tgt_acl | grep -c "\b$initiator\b"` == 1)) ; then continue fi $TGTADM --lld $lld_name --op bind --mode target --tid $tid -I $initiator res=$? if [ $res -ne 0 ]; then echo "Error: could not assign initiator $initiator to the target" fi done else # Check if record already exists if ((`echo $tgt_acl | grep -c "\bALL\b"` == 1)) ; then exit 0 fi # Allow access for everyone echo "Accepting connections from all initiators" $TGTADM --lld $lld_name --op bind --mode target --tid $tid -I ALL res=$? if [ $res -ne 0 ]; then echo "Error: failed to set access for all initiators" err_exit fi fi tgt-1.0.85/scripts/tgt.bashcomp.sh000066400000000000000000000142541435417276200170400ustar00rootroot00000000000000# list available target names _tgt_targets() { COMPREPLY=( $(compgen -W "$(tgt-admin --show|\ grep Target | cut -d' ' -f3\ ) ALL help" -- "${cur}") ) } have tgtd && _tgtd() { local optslist split=false local cur prev COMPREPLY=() cur=$(_get_cword "=") prev="${COMP_WORDS[COMP_CWORD-1]}" _expand || return 0 optslist=' --foreground -f --control-port -C --nr_iothreads -t --debug -d --version -V --help -h ' _split_longopt && split=true case "${prev}" in --nr_iothreads|-t|\ --control-port|-C|\ --debug|-d) return 0;; esac $split && return 0 case "${cur}" in -*) COMPREPLY=( $(compgen -W "${optslist}" -- "${cur}") ) return 0;; *) _filedir return 0;; esac } && complete -F _tgtd ${nospace} tgtd have tgtimg && _tgtimg() { local optslist split=false local cur prev COMPREPLY=() cur=$(_get_cword "=") prev="${COMP_WORDS[COMP_CWORD-1]}" _expand || return 0 optslist=' --help -h --op -o --device-type -Y --barcode -b --size -s --type -t --file -f --thin-provisioning -T ' _split_longopt && split=true case "${prev}" in --op|-o) COMPREPLY=( $(compgen -W "new show" -- "${cur}") ) return 0;; --device-type|-Y) COMPREPLY=( $(compgen -W "cd disk tape" -- "${cur}") ) return 0;; --type|-t) COMPREPLY=( $(compgen -W "dvd+r disk data clean worm" -- "${cur}") ) return 0;; --file|-f) _filedir return 0;; --barcode|-b|\ --size|-s) return 0;; esac $split && return 0 case "${cur}" in -*) COMPREPLY=( $(compgen -W "${optslist}" -- "${cur}") ) return 0;; *) _filedir return 0;; esac } && complete -F _tgtimg ${nospace} tgtimg have tgtadm && _tgtadm() { local optslist split=false local cur prev COMPREPLY=() cur=$(_get_cword "=") prev="${COMP_WORDS[COMP_CWORD-1]}" _expand || return 0 optslist=' --debug -d --help -h --version -V --lld -L --op -o --mode -m --tid -t --sid -s --cid -c --lun -l --name -n --value -v --backing-store -b --bstype -E --bsoflags -f --blocksize -y --targetname -T --initiator-address -I --initiator-name -Q --user -u --password -p --host -H --force -F --params -P --bus -B --device-type -Y --outgoing -O --control-port -C ' _split_longopt && split=true case "${prev}" in --lld|-L) COMPREPLY=( $(compgen -W "iscsi iser" -- "${cur}") ) return 0;; --op|-o) COMPREPLY=( $(compgen -W "new delete bind unbind show update stat start stop" -- "${cur}") ) return 0;; --mode|-m) COMPREPLY=( $(compgen -W "system sys target tgt logicalunit lu portal pt session sess connection conn account lld" -- "${cur}") ) return 0;; --bstype|-E) COMPREPLY=( $(compgen -W "rdwr aio rbd sg ssc" -- "${cur}") ) return 0;; --bsoflags|-f) COMPREPLY=( $(compgen -W "direct sync" -- "${cur}") ) return 0;; --device-type|-Y) COMPREPLY=( $(compgen -W "disk tape cd changer osd ssc pt" -- "${cur}") ) return 0;; --targetname|-T) _tgt_targets return 0;; --backing-store|-b) _filedir return 0;; --blocksize|-y|\ --bus|-B|\ --cid|-c|\ --control-port|-C|\ --host|-H|\ --initiator-address|-I|\ --initiator-name|-Q|\ --lun|-l|\ --name|-n|\ --params|-P|\ --password|-p|\ --sid|-s|\ --tid|-t|\ --user|-u|\ --value|-v) return 0;; esac $split && return 0 case "${cur}" in -*) COMPREPLY=( $(compgen -W "${optslist}" -- "${cur}") ) return 0;; *) _filedir return 0;; esac } && complete -F _tgtadm ${nospace} tgtadm have tgt-admin && _tgt_admin() { local optslist split=false local cur prev COMPREPLY=() cur=$(_get_cword "=") prev="${COMP_WORDS[COMP_CWORD-1]}" _expand || return 0 optslist=' --execute -e --delete --offline --ready --update --show -s --conf -c --ignore-errors --force -f --pretend -p --dump --verbose -v --help -h --control-port -C ' _split_longopt && split=true case "${prev}" in --delete|-d|\ --offline|\ --ready|\ --update) _tgt_targets return 0;; --conf|-c) _filedir return 0;; --control-port|-C) return 0;; esac $split && return 0 case "${cur}" in -*) COMPREPLY=( $(compgen -W "${optslist}" -- "${cur}") ) return 0;; *) _filedir return 0;; esac } && complete -F _tgt_admin ${nospace} tgt-admin have tgt-setup-lun && _tgt_setup_lun() { local optslist split=false local cur prev COMPREPLY=() cur=$(_get_cword "=") prev="${COMP_WORDS[COMP_CWORD-1]}" _expand || return 0 optslist=' -d -n -b -t -C -h ' _split_longopt && split=true case "${prev}" in -n) _tgt_targets return 0;; -d) _filedir return 0;; esac $split && return 0 case "${cur}" in -*) COMPREPLY=( $(compgen -W "${optslist}" -- "${cur}") ) return 0;; *) _filedir return 0;; esac } && complete -F _tgt_setup_lun ${nospace} tgt-setup-lun tgt-1.0.85/scripts/tgtd.service000066400000000000000000000016731435417276200164400ustar00rootroot00000000000000[Unit] Description=iSCSI target framework daemon Documentation=man:tgtd(8) After=network.target # On systems without virtual consoles, don't start any getty. Note # that serial gettys are covered by serial-getty@.service, not this # unit. ConditionPathExists=/etc/tgt/targets.conf [Service] Type=forking Environment=TGTD_CONFIG=/etc/tgt/targets.conf ExecStart=/usr/sbin/tgtd ExecStartPost=/usr/sbin/tgtadm --op update --mode sys --name State -v offline ExecStartPost=/usr/sbin/tgtadm --op update --mode sys --name State -v ready ExecStartPost=/usr/sbin/tgt-admin -e -c ${TGTD_CONFIG} ExecReload=/usr/sbin/tgt-admin --update ALL -f -c ${TGTD_CONFIG} ExecStop=/usr/sbin/tgtadm --op update --mode sys --name State -v offline ExecStop=/usr/sbin/tgt-admin --offline ALL ExecStop=/usr/sbin/tgt-admin --update ALL -c /dev/null -f ExecStop=/usr/sbin/tgtadm --op delete --mode system # Exit code: 107 tgtd not running [Install] WantedBy=multi-user.target tgt-1.0.85/scripts/tgtd.spec000066400000000000000000000053561435417276200157340ustar00rootroot00000000000000Name: scsi-target-utils Version: 1.0.24 Release: 2%{?dist} Summary: The SCSI target daemon and utility programs Packager: Roi Dayan Group: System Environment/Daemons License: GPLv2 URL: http://stgt.sourceforge.net/ Source0: %{name}-%{version}-%{release}.tgz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRequires: pkgconfig libibverbs-devel librdmacm-devel libxslt libaio-devel %if %{defined suse_version} BuildRequires: docbook-xsl-stylesheets Requires: aaa_base %else BuildRequires: docbook-style-xsl Requires(post): chkconfig Requires(preun): chkconfig Requires(preun): initscripts %endif Requires: lsof sg3_utils ExcludeArch: s390 s390x %description The SCSI target package contains the daemon and tools to setup a SCSI targets. Currently, software iSCSI targets are supported. %prep %setup -q -n %{name}-%{version}-%{release} %build %{__make} %{?_smp_mflags} ISCSI_RDMA=1 %install %{__rm} -rf %{buildroot} %{__install} -d %{buildroot}%{_sbindir} %{__install} -d %{buildroot}%{_mandir}/man5 %{__install} -d %{buildroot}%{_mandir}/man8 %{__install} -d %{buildroot}%{_initrddir} %{__install} -d %{buildroot}/etc/bash_completion.d %{__install} -d %{buildroot}/etc/tgt %{__install} -p -m 0755 scripts/tgt-setup-lun %{buildroot}%{_sbindir} %{__install} -p -m 0755 scripts/initd.sample %{buildroot}%{_initrddir}/tgtd %{__install} -p -m 0755 scripts/tgt-admin %{buildroot}/%{_sbindir}/tgt-admin %{__install} -p -m 0644 scripts/tgt.bashcomp.sh %{buildroot}/etc/bash_completion.d/tgt %{__install} -p -m 0644 doc/manpages/targets.conf.5 %{buildroot}/%{_mandir}/man5 %{__install} -p -m 0644 doc/manpages/tgtadm.8 %{buildroot}/%{_mandir}/man8 %{__install} -p -m 0644 doc/manpages/tgt-admin.8 %{buildroot}/%{_mandir}/man8 %{__install} -p -m 0644 doc/manpages/tgt-setup-lun.8 %{buildroot}/%{_mandir}/man8 %{__install} -p -m 0644 doc/manpages/tgtimg.8 %{buildroot}/%{_mandir}/man8 %{__install} -p -m 0600 conf/targets.conf %{buildroot}/etc/tgt pushd usr %{__make} install DESTDIR=%{buildroot} sbindir=%{_sbindir} %post /sbin/chkconfig --add tgtd %postun if [ "$1" = "1" ] ; then /sbin/service tgtd condrestart > /dev/null 2>&1 || : fi %preun if [ "$1" = "0" ] ; then /sbin/chkconfig tgtd stop > /dev/null 2>&1 /sbin/chkconfig --del tgtd fi %clean %{__rm} -rf %{buildroot} %files %defattr(-, root, root, -) %doc README doc/README.iscsi doc/README.iser doc/README.lu_configuration doc/README.mmc %{_sbindir}/tgtd %{_sbindir}/tgtadm %{_sbindir}/tgt-setup-lun %{_sbindir}/tgt-admin %{_sbindir}/tgtimg %{_mandir}/man5/* %{_mandir}/man8/* %{_initrddir}/tgtd /etc/bash_completion.d/tgt %attr(0600,root,root) %config(noreplace) /etc/tgt/targets.conf tgt-1.0.85/usr/000077500000000000000000000000001435417276200132275ustar00rootroot00000000000000tgt-1.0.85/usr/Makefile000066400000000000000000000051221435417276200146670ustar00rootroot00000000000000sbindir ?= $(PREFIX)/sbin libdir ?= $(PREFIX)/lib/tgt ifneq ($(shell test -e /usr/include/linux/signalfd.h && echo 1),) CFLAGS += -DUSE_SIGNALFD endif ifneq ($(shell test -n $(shell find /usr/include -name "timerfd.h" | head -n1) && echo 1),) CFLAGS += -DUSE_TIMERFD endif TGTD_OBJS += $(addprefix iscsi/, conn.o param.o session.o \ iscsid.o target.o chap.o sha1.o md5.o transport.o iscsi_tcp.o \ isns.o) ifneq ($(CEPH_RBD),) MODULES += bs_rbd.so endif ifneq ($(GLFS_BD),) MODULES += bs_glfs.so endif ifneq ($(SD_NOTIFY),) CFLAGS += -DUSE_SYSTEMD endif ifneq ($(shell test -e /usr/include/sys/eventfd.h && test -e /usr/include/libaio.h && echo 1),) CFLAGS += -DUSE_EVENTFD TGTD_OBJS += bs_aio.o LIBS += -laio endif ifneq ($(ISCSI_RDMA),) TGTD_OBJS += iscsi/iser.o iscsi/iser_text.o LIBS += -libverbs -lrdmacm endif INCLUDES += -I. CFLAGS += -D_GNU_SOURCE CFLAGS += $(INCLUDES) ifneq ($(DEBUG),) CFLAGS += -g -O0 -ggdb -rdynamic else CFLAGS += -g -O2 -fno-strict-aliasing endif CFLAGS += -Wall -Wstrict-prototypes -Werror -fPIC CFLAGS += -DTGT_VERSION=\"$(VERSION)$(EXTRAVERSION)\" CFLAGS += -DBSDIR=\"$(DESTDIR)$(libdir)/backing-store\" LIBS += -lpthread -ldl ifneq ($(SD_NOTIFY),) LIBS += -lsystemd endif PROGRAMS += tgtd tgtadm tgtimg TGTD_OBJS += tgtd.o mgmt.o target.o scsi.o log.o driver.o util.o work.o \ concat_buf.o parser.o spc.o sbc.o mmc.o osd.o scc.o smc.o \ ssc.o libssc.o bs_rdwr.o bs_ssc.o \ bs_null.o bs_sg.o bs.o libcrc32c.o bs_sheepdog.o TGTD_DEP = $(TGTD_OBJS:.o=.d) LDFLAGS = -Wl,-E,-rpath=$(libdir) .PHONY:all all: $(PROGRAMS) $(MODULES) tgtd: $(TGTD_OBJS) echo $(CC) $^ -o $@ $(LIBS) $(CC) $^ -o $@ $(LDFLAGS) $(LIBS) -include $(TGTD_DEP) TGTADM_OBJS = tgtadm.o concat_buf.o TGTADM_DEP = $(TGTADM_OBJS:.o=.d) tgtadm: $(TGTADM_OBJS) $(CC) $^ -o $@ -include $(TGTADM_DEP) TGTIMG_OBJS = tgtimg.o libssc.o libcrc32c.o TGTIMG_DEP = $(TGTIMG_OBJS:.o=.d) tgtimg: $(TGTIMG_OBJS) $(CC) $^ -o $@ -include $(TGTIMG_DEP) %.o: %.c $(CC) -c $(CFLAGS) $*.c -o $*.o @$(CC) -MM $(CFLAGS) -MF $*.d -MT $*.o $*.c %.so: %.c $(CC) -shared $(CFLAGS) $*.c -o $*.so bs_rbd.so: bs_rbd.c $(CC) -shared $(CFLAGS) bs_rbd.c -o bs_rbd.so -lrados -lrbd bs_glfs.so: bs_glfs.c $(CC) -I/usr/include/glusterfs/api -shared $(CFLAGS) bs_glfs.c -o bs_glfs.so -lgfapi .PHONY: install install: $(PROGRAMS) $(MODULES) install -d -m 755 $(DESTDIR)$(sbindir) install -m 755 $(PROGRAMS) $(DESTDIR)$(sbindir) ifneq ($(MODULES),) install -d -m 755 $(DESTDIR)$(libdir)/backing-store install -m 755 $(MODULES) $(DESTDIR)$(libdir)/backing-store endif .PHONY: clean clean: rm -f *.[od] *.so $(PROGRAMS) iscsi/*.[od] tgt-1.0.85/usr/be_byteshift.h000066400000000000000000000033771435417276200160610ustar00rootroot00000000000000#ifndef _LINUX_UNALIGNED_BE_BYTESHIFT_H #define _LINUX_UNALIGNED_BE_BYTESHIFT_H static inline uint16_t __get_unaligned_be16(const uint8_t *p) { return p[0] << 8 | p[1]; } static inline uint32_t __get_unaligned_be32(const uint8_t *p) { return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3]; } static inline uint64_t __get_unaligned_be64(const uint8_t *p) { return (uint64_t)__get_unaligned_be32(p) << 32 | __get_unaligned_be32(p + 4); } static inline void __put_unaligned_be16(uint16_t val, uint8_t *p) { *p++ = val >> 8; *p++ = val; } static inline void __put_unaligned_be32(uint32_t val, uint8_t *p) { __put_unaligned_be16(val >> 16, p); __put_unaligned_be16(val, p + 2); } static inline void __put_unaligned_be64(uint64_t val, uint8_t *p) { __put_unaligned_be32(val >> 32, p); __put_unaligned_be32(val, p + 4); } static inline uint16_t get_unaligned_be16(const void *p) { return __get_unaligned_be16((const uint8_t *)p); } static inline uint32_t get_unaligned_be24(const uint8_t *p) { return p[0] << 16 | p[1] << 8 | p[2]; } static inline uint32_t get_unaligned_be32(const void *p) { return __get_unaligned_be32((const uint8_t *)p); } static inline uint64_t get_unaligned_be64(const void *p) { return __get_unaligned_be64((const uint8_t *)p); } static inline void put_unaligned_be16(uint16_t val, void *p) { __put_unaligned_be16(val, p); } static inline void put_unaligned_be24(uint32_t val, void *p) { ((uint8_t *)p)[0] = (val >> 16) & 0xff; ((uint8_t *)p)[1] = (val >> 8) & 0xff; ((uint8_t *)p)[2] = val & 0xff; } static inline void put_unaligned_be32(uint32_t val, void *p) { __put_unaligned_be32(val, p); } static inline void put_unaligned_be64(uint64_t val, void *p) { __put_unaligned_be64(val, p); } #endif /* _LINUX_UNALIGNED_BE_BYTESHIFT_H */ tgt-1.0.85/usr/bs.c000066400000000000000000000237661435417276200140150ustar00rootroot00000000000000/* * backing store routine * * Copyright (C) 2007 FUJITA Tomonori * Copyright (C) 2007 Mike Christie * * 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, version 2 of the * License. * * 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 #include #include #include #include #include #include #include #include #include #include "list.h" #include "tgtd.h" #include "tgtadm_error.h" #include "util.h" #include "bs_thread.h" #include "scsi.h" LIST_HEAD(bst_list); static LIST_HEAD(finished_list); static pthread_mutex_t finished_lock; /* used by both bs_rdwr.c and bs_rbd.c */ int nr_iothreads = 16; static int sig_fd = -1; static int command_fd[2]; static int done_fd[2]; static pthread_t ack_thread; /* protected by pipe */ static LIST_HEAD(ack_list); static pthread_cond_t finished_cond; void bs_create_opcode_map(struct backingstore_template *bst, unsigned char *opcodes, int num) { int i; for (i = 0; i < num; i++) set_bit(opcodes[i], bst->bs_supported_ops); } int is_bs_support_opcode(struct backingstore_template *bst, int op) { /* * assumes that this bs doesn't support supported_ops yet so * returns success for the compatibility. */ if (!test_bit(TEST_UNIT_READY, bst->bs_supported_ops)) return 1; return test_bit(op, bst->bs_supported_ops); } int register_backingstore_template(struct backingstore_template *bst) { list_add(&bst->backingstore_siblings, &bst_list); return 0; } struct backingstore_template *get_backingstore_template(const char *name) { struct backingstore_template *bst; list_for_each_entry(bst, &bst_list, backingstore_siblings) { if (!strcmp(name, bst->bs_name)) return bst; } return NULL; } /* threading helper functions */ static void *bs_thread_ack_fn(void *arg) { int command, ret, nr; struct scsi_cmd *cmd; retry: ret = read(command_fd[0], &command, sizeof(command)); if (ret < 0) { eprintf("ack pthread will be dead, %m\n"); if (errno == EAGAIN || errno == EINTR) goto retry; goto out; } pthread_mutex_lock(&finished_lock); while (list_empty(&finished_list)) pthread_cond_wait(&finished_cond, &finished_lock); while (!list_empty(&finished_list)) { cmd = list_first_entry(&finished_list, struct scsi_cmd, bs_list); dprintf("found %p\n", cmd); list_del(&cmd->bs_list); list_add_tail(&cmd->bs_list, &ack_list); } pthread_mutex_unlock(&finished_lock); nr = 1; rewrite: ret = write(done_fd[1], &nr, sizeof(nr)); if (ret < 0) { eprintf("can't ack tgtd, %m\n"); if (errno == EAGAIN || errno == EINTR) goto rewrite; goto out; } goto retry; out: pthread_exit(NULL); } static void bs_thread_request_done(int fd, int events, void *data) { struct scsi_cmd *cmd; int nr_events, ret; ret = read(done_fd[0], &nr_events, sizeof(nr_events)); if (ret < 0) { eprintf("wrong wakeup\n"); return; } while (!list_empty(&ack_list)) { cmd = list_first_entry(&ack_list, struct scsi_cmd, bs_list); dprintf("back to tgtd, %p\n", cmd); list_del(&cmd->bs_list); target_cmd_io_done(cmd, scsi_get_result(cmd)); } rewrite: ret = write(command_fd[1], &nr_events, sizeof(nr_events)); if (ret < 0) { eprintf("can't write done, %m\n"); if (errno == EAGAIN || errno == EINTR) goto rewrite; return; } } static void bs_sig_request_done(int fd, int events, void *data) { int ret; struct scsi_cmd *cmd; struct signalfd_siginfo siginfo[16]; LIST_HEAD(list); ret = read(fd, (char *)siginfo, sizeof(siginfo)); if (ret <= 0) { return; } pthread_mutex_lock(&finished_lock); list_splice_init(&finished_list, &list); pthread_mutex_unlock(&finished_lock); while (!list_empty(&list)) { cmd = list_first_entry(&list, struct scsi_cmd, bs_list); list_del(&cmd->bs_list); target_cmd_io_done(cmd, scsi_get_result(cmd)); } } /* Unlock mutex even if thread is cancelled */ static void mutex_cleanup(void *mutex) { pthread_mutex_unlock(mutex); } static void *bs_thread_worker_fn(void *arg) { struct bs_thread_info *info = arg; struct scsi_cmd *cmd; sigset_t set; sigfillset(&set); sigprocmask(SIG_BLOCK, &set, NULL); while (1) { pthread_mutex_lock(&info->pending_lock); pthread_cleanup_push(mutex_cleanup, &info->pending_lock); while (list_empty(&info->pending_list)) pthread_cond_wait(&info->pending_cond, &info->pending_lock); cmd = list_first_entry(&info->pending_list, struct scsi_cmd, bs_list); list_del(&cmd->bs_list); pthread_cleanup_pop(1); /* Unlock pending_lock mutex */ info->request_fn(cmd); pthread_mutex_lock(&finished_lock); list_add_tail(&cmd->bs_list, &finished_list); pthread_mutex_unlock(&finished_lock); if (sig_fd < 0) pthread_cond_signal(&finished_cond); else kill(getpid(), SIGUSR2); } pthread_exit(NULL); } static int bs_init_signalfd(void) { sigset_t mask; int ret; DIR *dir; dir = opendir(BSDIR); if (dir == NULL) { /* not considered an error if there are no modules */ dprintf("could not open backing-store module directory %s\n", BSDIR); } else { struct dirent *dirent; void *handle; while ((dirent = readdir(dir))) { char *soname; void (*register_bs_module)(void); if (dirent->d_name[0] == '.') { continue; } ret = asprintf(&soname, "%s/%s", BSDIR, dirent->d_name); if (ret == -1) { eprintf("out of memory\n"); continue; } handle = dlopen(soname, RTLD_NOW|RTLD_LOCAL); if (handle == NULL) { eprintf("failed to dlopen backing-store " "module %s error %s \n", soname, dlerror()); free(soname); continue; } register_bs_module = dlsym(handle, "register_bs_module"); if (register_bs_module == NULL) { eprintf("could not find register_bs_module " "symbol in module %s\n", soname); free(soname); continue; } register_bs_module(); free(soname); } closedir(dir); } pthread_mutex_init(&finished_lock, NULL); sigemptyset(&mask); sigaddset(&mask, SIGUSR2); sigprocmask(SIG_BLOCK, &mask, NULL); sig_fd = __signalfd(-1, &mask, 0); if (sig_fd < 0) return 1; ret = tgt_event_add(sig_fd, EPOLLIN, bs_sig_request_done, NULL); if (ret < 0) { close (sig_fd); sig_fd = -1; return 1; } return 0; } static int bs_init_notify_thread(void) { int ret; pthread_cond_init(&finished_cond, NULL); pthread_mutex_init(&finished_lock, NULL); ret = pipe(command_fd); if (ret) { eprintf("failed to create command pipe, %m\n"); goto destroy_cond_mutex; } ret = pipe(done_fd); if (ret) { eprintf("failed to done command pipe, %m\n"); goto close_command_fd; } ret = tgt_event_add(done_fd[0], EPOLLIN, bs_thread_request_done, NULL); if (ret) { eprintf("failed to add epoll event\n"); goto close_done_fd; } ret = pthread_create(&ack_thread, NULL, bs_thread_ack_fn, NULL); if (ret) { eprintf("failed to create an ack thread, %s\n", strerror(ret)); goto event_del; } ret = write(command_fd[1], &ret, sizeof(ret)); if (ret <= 0) goto event_del; return 0; event_del: tgt_event_del(done_fd[0]); close_done_fd: close(done_fd[0]); close(done_fd[1]); close_command_fd: close(command_fd[0]); close(command_fd[1]); destroy_cond_mutex: pthread_cond_destroy(&finished_cond); pthread_mutex_destroy(&finished_lock); return 1; } int bs_init(void) { int ret; ret = bs_init_signalfd(); if (!ret) { eprintf("use signalfd notification\n"); return 0; } ret = bs_init_notify_thread(); if (!ret) { eprintf("use pthread notification\n"); return 0; } return 1; } tgtadm_err bs_thread_open(struct bs_thread_info *info, request_func_t *rfn, int nr_threads) { int i, ret; info->worker_thread = zalloc(sizeof(pthread_t) * nr_threads); if (!info->worker_thread) return TGTADM_NOMEM; eprintf("%d\n", nr_threads); info->request_fn = rfn; INIT_LIST_HEAD(&info->pending_list); pthread_cond_init(&info->pending_cond, NULL); pthread_mutex_init(&info->pending_lock, NULL); for (i = 0; i < nr_threads; i++) { ret = pthread_create(&info->worker_thread[i], NULL, bs_thread_worker_fn, info); if (ret) { eprintf("failed to create a worker thread, %d %s\n", i, strerror(ret)); if (ret) goto destroy_threads; } } info->nr_worker_threads = nr_threads; return TGTADM_SUCCESS; destroy_threads: for (; i > 0; i--) { if (info->worker_thread[i - 1]) { pthread_cancel(info->worker_thread[i - 1]); pthread_join(info->worker_thread[i - 1], NULL); eprintf("stopped the worker thread %d\n", i - 1); } } pthread_cond_destroy(&info->pending_cond); pthread_mutex_destroy(&info->pending_lock); free(info->worker_thread); return TGTADM_NOMEM; } void bs_thread_close(struct bs_thread_info *info) { int i; for (i = 0; i < info->nr_worker_threads && info->worker_thread[i]; i++) { pthread_cancel(info->worker_thread[i]); pthread_join(info->worker_thread[i], NULL); } pthread_cond_destroy(&info->pending_cond); pthread_mutex_destroy(&info->pending_lock); free(info->worker_thread); } int bs_thread_cmd_submit(struct scsi_cmd *cmd) { struct scsi_lu *lu = cmd->dev; struct bs_thread_info *info = BS_THREAD_I(lu); pthread_mutex_lock(&info->pending_lock); list_add_tail(&cmd->bs_list, &info->pending_list); pthread_mutex_unlock(&info->pending_lock); pthread_cond_signal(&info->pending_cond); set_cmd_async(cmd); return 0; } tgt-1.0.85/usr/bs_aio.c000066400000000000000000000263661435417276200146440ustar00rootroot00000000000000/* * AIO backing store * * Copyright (C) 2006-2007 FUJITA Tomonori * Copyright (C) 2006-2007 Mike Christie * Copyright (C) 2011 Alexander Nezhinsky * * 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, version 2 of the * License. * * 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 #include #include "list.h" #include "util.h" #include "tgtd.h" #include "target.h" #include "scsi.h" #ifndef O_DIRECT #define O_DIRECT 040000 #endif #define AIO_MAX_IODEPTH 128 struct bs_aio_info { struct list_head dev_list_entry; io_context_t ctx; struct list_head cmd_wait_list; unsigned int nwaiting; unsigned int npending; unsigned int iodepth; int resubmit; struct scsi_lu *lu; int evt_fd; struct iocb iocb_arr[AIO_MAX_IODEPTH]; struct iocb *piocb_arr[AIO_MAX_IODEPTH]; struct io_event io_evts[AIO_MAX_IODEPTH]; }; static struct list_head bs_aio_dev_list = LIST_HEAD_INIT(bs_aio_dev_list); static inline struct bs_aio_info *BS_AIO_I(struct scsi_lu *lu) { return (struct bs_aio_info *) ((char *)lu + sizeof(*lu)); } static void bs_aio_iocb_prep(struct bs_aio_info *info, int idx, struct scsi_cmd *cmd) { struct iocb *iocb = &info->iocb_arr[idx]; unsigned int scsi_op = (unsigned int)cmd->scb[0]; iocb->data = cmd; iocb->key = 0; iocb->aio_reqprio = 0; iocb->aio_fildes = info->lu->fd; switch (scsi_op) { case WRITE_6: case WRITE_10: case WRITE_12: case WRITE_16: iocb->aio_lio_opcode = IO_CMD_PWRITE; iocb->u.c.buf = scsi_get_out_buffer(cmd); iocb->u.c.nbytes = scsi_get_out_length(cmd); dprintf("prep WR cmd:%p op:%x buf:0x%p sz:%lx\n", cmd, scsi_op, iocb->u.c.buf, iocb->u.c.nbytes); break; case READ_6: case READ_10: case READ_12: case READ_16: iocb->aio_lio_opcode = IO_CMD_PREAD; iocb->u.c.buf = scsi_get_in_buffer(cmd); iocb->u.c.nbytes = scsi_get_in_length(cmd); dprintf("prep RD cmd:%p op:%x buf:0x%p sz:%lx\n", cmd, scsi_op, iocb->u.c.buf, iocb->u.c.nbytes); break; default: return; } iocb->u.c.offset = cmd->offset; iocb->u.c.flags |= (1 << 0); /* IOCB_FLAG_RESFD - use eventfd file desc. */ iocb->u.c.resfd = info->evt_fd; } static int bs_aio_submit_dev_batch(struct bs_aio_info *info) { int nsubmit, nsuccess; struct scsi_cmd *cmd, *next; int i = 0; nsubmit = info->iodepth - info->npending; /* max allowed to submit */ if (nsubmit > info->nwaiting) nsubmit = info->nwaiting; dprintf("nsubmit:%d waiting:%d pending:%d, tgt:%d lun:%"PRId64 "\n", nsubmit, info->nwaiting, info->npending, info->lu->tgt->tid, info->lu->lun); if (!nsubmit) return 0; list_for_each_entry_safe(cmd, next, &info->cmd_wait_list, bs_list) { bs_aio_iocb_prep(info, i, cmd); list_del(&cmd->bs_list); if (++i == nsubmit) break; } nsuccess = io_submit(info->ctx, nsubmit, info->piocb_arr); if (unlikely(nsuccess < 0)) { if (nsuccess == -EAGAIN) { eprintf("delayed submit %d cmds to tgt:%d lun:%"PRId64 "\n", nsubmit, info->lu->tgt->tid, info->lu->lun); nsuccess = 0; /* leave the dev pending with all cmds */ } else { eprintf("failed to submit %d cmds to tgt:%d lun:%"PRId64 ", err: %d\n", nsubmit, info->lu->tgt->tid, info->lu->lun, -nsuccess); for (i = nsubmit - 1; i >= 0; i--) { cmd = info->iocb_arr[i].data; clear_cmd_async(cmd); info->nwaiting--; if (!info->nwaiting) list_del(&info->dev_list_entry); } return nsuccess; } } if (unlikely(nsuccess < nsubmit)) { for (i=nsubmit-1; i >= nsuccess; i--) { cmd = info->iocb_arr[i].data; list_add(&cmd->bs_list, &info->cmd_wait_list); } } info->npending += nsuccess; info->nwaiting -= nsuccess; /* if no cmds remain, remove the dev from the pending list */ if (likely(!info->nwaiting)) list_del(&info->dev_list_entry); dprintf("submitted %d of %d cmds to tgt:%d lun:%"PRId64 ", waiting:%d pending:%d\n", nsuccess, nsubmit, info->lu->tgt->tid, info->lu->lun, info->nwaiting, info->npending); return 0; } static int bs_aio_submit_all_devs(void) { struct bs_aio_info *dev_info, *next_dev; int err; /* pass over all devices having some queued cmds and submit */ list_for_each_entry_safe(dev_info, next_dev, &bs_aio_dev_list, dev_list_entry) { err = bs_aio_submit_dev_batch(dev_info); if (unlikely(err)) return err; } return 0; } static int bs_aio_cmd_submit(struct scsi_cmd *cmd) { struct scsi_lu *lu = cmd->dev; struct bs_aio_info *info = BS_AIO_I(lu); unsigned int scsi_op = (unsigned int)cmd->scb[0]; switch (scsi_op) { case WRITE_6: case WRITE_10: case WRITE_12: case WRITE_16: case READ_6: case READ_10: case READ_12: case READ_16: break; case WRITE_SAME: case WRITE_SAME_16: eprintf("WRITE_SAME not yet supported for AIO backend.\n"); return -1; case SYNCHRONIZE_CACHE: case SYNCHRONIZE_CACHE_16: default: dprintf("skipped cmd:%p op:%x\n", cmd, scsi_op); return 0; } list_add_tail(&cmd->bs_list, &info->cmd_wait_list); if (!info->nwaiting) list_add_tail(&info->dev_list_entry, &bs_aio_dev_list); info->nwaiting++; set_cmd_async(cmd); if (!cmd_not_last(cmd)) /* last cmd in batch */ return bs_aio_submit_all_devs(); if (info->nwaiting == info->iodepth - info->npending) return bs_aio_submit_dev_batch(info); return 0; } static void bs_aio_complete_one(struct io_event *ep) { struct scsi_cmd *cmd = (void *)(unsigned long)ep->data; uint32_t length; int result; switch (cmd->scb[0]) { case WRITE_6: case WRITE_10: case WRITE_12: case WRITE_16: length = scsi_get_out_length(cmd); break; default: length = scsi_get_in_length(cmd); break; } if (likely(ep->res == length)) result = SAM_STAT_GOOD; else { sense_data_build(cmd, MEDIUM_ERROR, 0); result = SAM_STAT_CHECK_CONDITION; } dprintf("cmd: %p\n", cmd); target_cmd_io_done(cmd, result); } static void bs_aio_get_completions(int fd, int events, void *data) { struct bs_aio_info *info = data; int i, ret; /* read from eventfd returns 8-byte int, fails with the error EINVAL if the size of the supplied buffer is less than 8 bytes */ uint64_t evts_complete; unsigned int ncomplete, nevents; retry_read: ret = read(info->evt_fd, &evts_complete, sizeof(evts_complete)); if (unlikely(ret < 0)) { eprintf("failed to read AIO completions, %m\n"); if (errno == EAGAIN || errno == EINTR) goto retry_read; return; } ncomplete = (unsigned int) evts_complete; while (ncomplete) { nevents = min_t(unsigned int, ncomplete, ARRAY_SIZE(info->io_evts)); retry_getevts: ret = io_getevents(info->ctx, 1, nevents, info->io_evts, NULL); if (likely(ret > 0)) { nevents = ret; info->npending -= nevents; } else { if (ret == -EINTR) goto retry_getevts; eprintf("io_getevents failed, err:%d\n", -ret); return; } dprintf("got %d ioevents out of %d, pending %d\n", nevents, ncomplete, info->npending); for (i = 0; i < nevents; i++) bs_aio_complete_one(&info->io_evts[i]); ncomplete -= nevents; } if (info->nwaiting) { dprintf("submit waiting cmds to tgt:%d lun:%"PRId64 "\n", info->lu->tgt->tid, info->lu->lun); bs_aio_submit_dev_batch(info); } } static int bs_aio_open(struct scsi_lu *lu, char *path, int *fd, uint64_t *size) { struct bs_aio_info *info = BS_AIO_I(lu); int ret, afd; uint32_t blksize = 0; info->iodepth = AIO_MAX_IODEPTH; eprintf("create aio context for tgt:%d lun:%"PRId64 ", max iodepth:%d\n", info->lu->tgt->tid, info->lu->lun, info->iodepth); ret = io_setup(info->iodepth, &info->ctx); if (ret) { eprintf("failed to create aio context, %m\n"); return -1; } afd = eventfd(0, O_NONBLOCK); if (afd < 0) { eprintf("failed to create eventfd for tgt:%d lun:%"PRId64 ", %m\n", info->lu->tgt->tid, info->lu->lun); ret = afd; goto close_ctx; } dprintf("eventfd:%d for tgt:%d lun:%"PRId64 "\n", afd, info->lu->tgt->tid, info->lu->lun); ret = tgt_event_add(afd, EPOLLIN, bs_aio_get_completions, info); if (ret) goto close_eventfd; info->evt_fd = afd; eprintf("open %s, RW, O_DIRECT for tgt:%d lun:%"PRId64 "\n", path, info->lu->tgt->tid, info->lu->lun); *fd = backed_file_open(path, O_RDWR|O_LARGEFILE|O_DIRECT, size, &blksize); /* If we get access denied, try opening the file in readonly mode */ if (*fd == -1 && (errno == EACCES || errno == EROFS)) { eprintf("open %s, READONLY, O_DIRECT for tgt:%d lun:%"PRId64 "\n", path, info->lu->tgt->tid, info->lu->lun); *fd = backed_file_open(path, O_RDONLY|O_LARGEFILE|O_DIRECT, size, &blksize); lu->attrs.readonly = 1; } if (*fd < 0) { eprintf("failed to open %s, for tgt:%d lun:%"PRId64 ", %m\n", path, info->lu->tgt->tid, info->lu->lun); ret = *fd; goto remove_tgt_evt; } eprintf("%s opened successfully for tgt:%d lun:%"PRId64 "\n", path, info->lu->tgt->tid, info->lu->lun); if (!lu->attrs.no_auto_lbppbe) update_lbppbe(lu, blksize); return 0; remove_tgt_evt: tgt_event_del(afd); close_eventfd: close(afd); close_ctx: io_destroy(info->ctx); return ret; } static void bs_aio_close(struct scsi_lu *lu) { close(lu->fd); } static tgtadm_err bs_aio_init(struct scsi_lu *lu, char *bsopts) { struct bs_aio_info *info = BS_AIO_I(lu); int i; memset(info, 0, sizeof(*info)); INIT_LIST_HEAD(&info->dev_list_entry); INIT_LIST_HEAD(&info->cmd_wait_list); info->lu = lu; for (i=0; i < ARRAY_SIZE(info->iocb_arr); i++) info->piocb_arr[i] = &info->iocb_arr[i]; return TGTADM_SUCCESS; } static void bs_aio_exit(struct scsi_lu *lu) { struct bs_aio_info *info = BS_AIO_I(lu); tgt_event_del(info->evt_fd); close(info->evt_fd); io_destroy(info->ctx); } static struct backingstore_template aio_bst = { .bs_name = "aio", .bs_datasize = sizeof(struct bs_aio_info), .bs_init = bs_aio_init, .bs_exit = bs_aio_exit, .bs_open = bs_aio_open, .bs_close = bs_aio_close, .bs_cmd_submit = bs_aio_cmd_submit, }; __attribute__((constructor)) static void register_bs_module(void) { unsigned char opcodes[] = { ALLOW_MEDIUM_REMOVAL, COMPARE_AND_WRITE, FORMAT_UNIT, INQUIRY, MAINT_PROTOCOL_IN, MODE_SELECT, MODE_SELECT_10, MODE_SENSE, MODE_SENSE_10, ORWRITE_16, PERSISTENT_RESERVE_IN, PERSISTENT_RESERVE_OUT, PRE_FETCH_10, PRE_FETCH_16, READ_10, READ_12, READ_16, READ_6, READ_CAPACITY, RELEASE, REPORT_LUNS, REQUEST_SENSE, RESERVE, SEND_DIAGNOSTIC, SERVICE_ACTION_IN, START_STOP, SYNCHRONIZE_CACHE, SYNCHRONIZE_CACHE_16, TEST_UNIT_READY, UNMAP, VERIFY_10, VERIFY_12, VERIFY_16, WRITE_10, WRITE_12, WRITE_16, WRITE_6, WRITE_VERIFY, WRITE_VERIFY_12, WRITE_VERIFY_16 }; bs_create_opcode_map(&aio_bst, opcodes, ARRAY_SIZE(opcodes)); register_backingstore_template(&aio_bst); } tgt-1.0.85/usr/bs_aio.h000066400000000000000000000061131435417276200146350ustar00rootroot00000000000000#ifndef __BS_AIO_H #define __BS_AIO_H /* this file is a workaround */ /* * eventfd-aio-test by Davide Libenzi (test app for eventfd hooked into KAIO) * Copyright (C) 2007 Davide Libenzi * * 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 * * Davide Libenzi * */ enum { IOCB_CMD_PREAD = 0, IOCB_CMD_PWRITE = 1, IOCB_CMD_FSYNC = 2, IOCB_CMD_FDSYNC = 3, /* These two are experimental. * IOCB_CMD_PREADX = 4, * IOCB_CMD_POLL = 5, */ IOCB_CMD_NOOP = 6, IOCB_CMD_PREADV = 7, IOCB_CMD_PWRITEV = 8, }; #define IOCB_FLAG_RESFD (1 << 0) #if defined(__LITTLE_ENDIAN) #define PADDED(x, y) x, y #elif defined(__BIG_ENDIAN) #define PADDED(x, y) y, x #else #error edit for your odd byteorder. #endif typedef unsigned long io_context_t; struct io_event { uint64_t data; /* the data field from the iocb */ uint64_t obj; /* what iocb this event came from */ int64_t res; /* result code for this event */ int64_t res2; /* secondary result */ }; struct iocb { /* these are internal to the kernel/libc. */ uint64_t aio_data; /* data to be returned in event's data */ int32_t PADDED(aio_key, aio_reserved1); /* the kernel sets aio_key to the req # */ /* common fields */ uint16_t aio_lio_opcode; /* see IOCB_CMD_ above */ int16_t aio_reqprio; int32_t aio_fildes; uint64_t aio_buf; uint64_t aio_nbytes; int64_t aio_offset; /* extra parameters */ uint64_t aio_reserved2; /* TODO: use this for a (struct sigevent *) */ /* flags for the "struct iocb" */ int32_t aio_flags; /* * if the IOCB_FLAG_RESFD flag of "aio_flags" is set, this is an * eventfd to signal AIO readiness to */ int32_t aio_resfd; }; /* 64 bytes */ static inline int io_setup(unsigned nr_reqs, io_context_t *ctx) { return syscall(__NR_io_setup, nr_reqs, ctx); } static inline long io_destroy(io_context_t ctx) { return syscall(__NR_io_destroy, ctx); } static inline int io_submit(io_context_t ctx, long n, struct iocb **paiocb) { return syscall(__NR_io_submit, ctx, n, paiocb); } static inline long io_cancel(io_context_t ctx, struct iocb *aiocb, struct io_event *res) { return syscall(__NR_io_cancel, ctx, aiocb, res); } static inline long io_getevents(io_context_t ctx, long min_nr, long nr, struct io_event *events, struct timespec *tmo) { return syscall(__NR_io_getevents, ctx, min_nr, nr, events, tmo); } static inline int eventfd(int count) { return syscall(__NR_eventfd, count); } #endif tgt-1.0.85/usr/bs_glfs.c000066400000000000000000000271431435417276200150210ustar00rootroot00000000000000/* * Synchronous glfs backing store routines * * Modified from bs_rdb.c * Copyright (C) 2013 Dan Lambright * Copyright (C) 2006-2007 FUJITA Tomonori * Copyright (C) 2006-2007 Mike Christie * * 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, version 2 of the * License. * * 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 */ #define _XOPEN_SOURCE 600 #include #include #include #include #include #include #include #include #include #include #include #include "list.h" #include "util.h" #include "tgtd.h" #include "scsi.h" #include "spc.h" #include "bs_thread.h" #include "glfs.h" struct active_glfs { char *name; glfs_t *fs; glfs_fd_t *gfd; char *logfile; int loglevel; }; #define ALLOWED_BSOFLAGS (O_SYNC | O_DIRECT | O_RDWR | O_LARGEFILE) #define GLUSTER_PORT 24007 #define GFSP(lu) ((struct active_glfs *) \ ((char *)lu + \ sizeof(struct scsi_lu) + \ sizeof(struct bs_thread_info)) \ ) static void set_medium_error(int *result, uint8_t *key, uint16_t *asc) { *result = SAM_STAT_CHECK_CONDITION; *key = MEDIUM_ERROR; *asc = ASC_READ_ERROR; } static int bs_glfs_discard(glfs_fd_t *gfd, off_t offset, size_t len) { #ifdef BS_GLFS_DISCARD return glfs_discard(gfd, offset, len); #endif return 0; } static void bs_glfs_request(struct scsi_cmd *cmd) { glfs_fd_t *gfd = GFSP(cmd->dev)->gfd; struct scsi_lu *lu = cmd->dev; int ret; uint32_t length; int result = SAM_STAT_GOOD; uint8_t key; uint16_t asc; char *tmpbuf; size_t blocksize; uint64_t offset = cmd->offset; uint32_t tl = cmd->tl; int do_verify = 0; int i; char *ptr; const char *write_buf = NULL; ret = length = 0; key = asc = 0; switch (cmd->scb[0]) { case ORWRITE_16: length = scsi_get_out_length(cmd); tmpbuf = malloc(length); if (!tmpbuf) { result = SAM_STAT_CHECK_CONDITION; key = HARDWARE_ERROR; asc = ASC_INTERNAL_TGT_FAILURE; break; } ret = glfs_pread(gfd, tmpbuf, length, offset, lu->bsoflags); if (ret != length) { set_medium_error(&result, &key, &asc); free(tmpbuf); break; } ptr = scsi_get_out_buffer(cmd); for (i = 0; i < length; i++) ptr[i] |= tmpbuf[i]; free(tmpbuf); write_buf = scsi_get_out_buffer(cmd); goto write; case COMPARE_AND_WRITE: /* Blocks are transferred twice, first the set that * we compare to the existing data, and second the set * to write if the compare was successful. */ length = scsi_get_out_length(cmd) / 2; if (length != cmd->tl) { result = SAM_STAT_CHECK_CONDITION; key = ILLEGAL_REQUEST; asc = ASC_INVALID_FIELD_IN_CDB; break; } tmpbuf = malloc(length); if (!tmpbuf) { result = SAM_STAT_CHECK_CONDITION; key = HARDWARE_ERROR; asc = ASC_INTERNAL_TGT_FAILURE; break; } ret = glfs_pread(gfd, tmpbuf, length, offset, SEEK_SET); if (ret != length) { set_medium_error(&result, &key, &asc); free(tmpbuf); break; } if (memcmp(scsi_get_out_buffer(cmd), tmpbuf, length)) { uint32_t pos = 0; char *spos = scsi_get_out_buffer(cmd); char *dpos = tmpbuf; /* * Data differed, this is assumed to be 'rare' * so use a much more expensive byte-by-byte * comparasion to find out at which offset the * data differs. */ for (pos = 0; pos < length && *spos++ == *dpos++; pos++) ; result = SAM_STAT_CHECK_CONDITION; key = MISCOMPARE; asc = ASC_MISCOMPARE_DURING_VERIFY_OPERATION; free(tmpbuf); break; } free(tmpbuf); write_buf = scsi_get_out_buffer(cmd) + length; goto write; case SYNCHRONIZE_CACHE: case SYNCHRONIZE_CACHE_16: /* TODO */ length = (cmd->scb[0] == SYNCHRONIZE_CACHE) ? 0 : 0; if (cmd->scb[1] & 0x2) { result = SAM_STAT_CHECK_CONDITION; key = ILLEGAL_REQUEST; asc = ASC_INVALID_FIELD_IN_CDB; } else { glfs_fdatasync(gfd); } break; case WRITE_VERIFY: case WRITE_VERIFY_12: case WRITE_VERIFY_16: do_verify = 1; case WRITE_6: case WRITE_10: case WRITE_12: case WRITE_16: length = scsi_get_out_length(cmd); write_buf = scsi_get_out_buffer(cmd); write: ret = glfs_pwrite(gfd, write_buf, length, offset, lu->bsoflags); if (ret == length) { struct mode_pg *pg; /* * it would be better not to access to pg * directy. */ pg = find_mode_page(cmd->dev, 0x08, 0); if (pg == NULL) { result = SAM_STAT_CHECK_CONDITION; key = ILLEGAL_REQUEST; asc = ASC_INVALID_FIELD_IN_CDB; break; } if (((cmd->scb[0] != WRITE_6) && (cmd->scb[1] & 0x8)) || !(pg->mode_data[0] & 0x04)) glfs_fdatasync(gfd); } else set_medium_error(&result, &key, &asc); if (do_verify) goto verify; break; case WRITE_SAME: case WRITE_SAME_16: /* WRITE_SAME used to punch hole in file */ if (cmd->scb[1] & 0x08) { ret = bs_glfs_discard(gfd, offset, tl); if (ret != 0) { eprintf("Failed WRITE_SAME command\n"); result = SAM_STAT_CHECK_CONDITION; key = HARDWARE_ERROR; asc = ASC_INTERNAL_TGT_FAILURE; break; } break; } while (tl > 0) { blocksize = 1 << cmd->dev->blk_shift; tmpbuf = scsi_get_out_buffer(cmd); switch (cmd->scb[1] & 0x06) { case 0x02: /* PBDATA==0 LBDATA==1 */ put_unaligned_be32(offset, tmpbuf); break; case 0x04: /* PBDATA==1 LBDATA==0 */ /* physical sector format */ put_unaligned_be64(offset, tmpbuf); break; } ret = glfs_pwrite(gfd, tmpbuf, blocksize, offset, lu->bsoflags); if (ret != blocksize) set_medium_error(&result, &key, &asc); offset += blocksize; tl -= blocksize; } break; case READ_6: case READ_10: case READ_12: case READ_16: length = scsi_get_in_length(cmd); ret = glfs_pread(gfd, scsi_get_in_buffer(cmd), length, offset, SEEK_SET); if (ret != length) { eprintf("Error on read %x %x", ret, length); set_medium_error(&result, &key, &asc); } break; case PRE_FETCH_10: case PRE_FETCH_16: if (ret != 0) set_medium_error(&result, &key, &asc); break; case VERIFY_10: case VERIFY_12: case VERIFY_16: verify: length = scsi_get_out_length(cmd); tmpbuf = malloc(length); if (!tmpbuf) { result = SAM_STAT_CHECK_CONDITION; key = HARDWARE_ERROR; asc = ASC_INTERNAL_TGT_FAILURE; break; } ret = glfs_pread(gfd, tmpbuf, length, offset, lu->bsoflags); if (ret != length) set_medium_error(&result, &key, &asc); else if (memcmp(scsi_get_out_buffer(cmd), tmpbuf, length)) { result = SAM_STAT_CHECK_CONDITION; key = MISCOMPARE; asc = ASC_MISCOMPARE_DURING_VERIFY_OPERATION; } free(tmpbuf); break; case UNMAP: if (!cmd->dev->attrs.thinprovisioning) { result = SAM_STAT_CHECK_CONDITION; key = ILLEGAL_REQUEST; asc = ASC_INVALID_FIELD_IN_CDB; break; } length = scsi_get_out_length(cmd); tmpbuf = scsi_get_out_buffer(cmd); if (length < 8) break; length -= 8; tmpbuf += 8; while (length >= 16) { offset = get_unaligned_be64(&tmpbuf[0]); offset = offset << cmd->dev->blk_shift; tl = get_unaligned_be32(&tmpbuf[8]); tl = tl << cmd->dev->blk_shift; if (offset + tl > cmd->dev->size) { eprintf("UNMAP beyond EOF\n"); result = SAM_STAT_CHECK_CONDITION; key = ILLEGAL_REQUEST; asc = ASC_LBA_OUT_OF_RANGE; break; } if (tl > 0) { if (bs_glfs_discard(gfd, offset, tl) != 0) { eprintf("Failed UNMAP\n"); result = SAM_STAT_CHECK_CONDITION; key = HARDWARE_ERROR; asc = ASC_INTERNAL_TGT_FAILURE; break; } } length -= 16; tmpbuf += 16; } break; default: break; } dprintf("io done %p %x %d %u\n", cmd, cmd->scb[0], ret, length); scsi_set_result(cmd, result); if (result != SAM_STAT_GOOD) { eprintf("io error %p %x %x %d %d %" PRIu64 ", %m\n", cmd, result, cmd->scb[0], ret, length, offset); sense_data_build(cmd, key, asc); } } static void parse_imagepath(char *image, char **server, char **vol, char **path) { char *origp = strdup(image); char *p, *sep; p = origp; sep = strchr(p, '@'); if (sep == NULL) { *server = ""; } else { *sep = '\0'; *server = strdup(p); p = sep + 1; } sep = strchr(p, ':'); if (sep == NULL) { *vol = ""; } else { *vol = strdup(sep + 1); *sep = '\0'; } /* p points to path\0 */ *path = strdup(p); free(origp); } static int bs_glfs_open(struct scsi_lu *lu, char *image, int *fd, uint64_t *size) { int ret = 0; char *servername; char *volname; char *pathname; int bsoflags = ALLOWED_BSOFLAGS; glfs_t *fs = 0; parse_imagepath(image, &volname, &pathname, &servername); if (volname && servername && pathname) { glfs_fd_t *gfd = NULL; struct stat st; fs = glfs_new(volname); if (!fs) goto fail; ret = glfs_set_volfile_server(fs, "tcp", servername, GLUSTER_PORT); ret = glfs_init(fs); if (ret) goto fail; GFSP(lu)->fs = fs; if (lu->bsoflags) bsoflags = lu->bsoflags; gfd = glfs_open(fs, pathname, bsoflags); if (gfd == NULL) goto fail; ret = glfs_lstat(fs, pathname, &st); if (ret) goto fail; GFSP(lu)->gfd = gfd; *size = (long) st.st_size; if (GFSP(lu)->logfile) glfs_set_logging(fs, GFSP(lu)->logfile, GFSP(lu)->loglevel); return 0; } fail: if (fs) glfs_fini(fs); return -EIO; } static void bs_glfs_close(struct scsi_lu *lu) { if (GFSP(lu)->gfd) glfs_close(GFSP(lu)->gfd); if (GFSP(lu)->gfd) glfs_fini(GFSP(lu)->fs); } static char *slurp_to_semi(char **p) { char *end = index(*p, ';'); char *ret; int len; if (end == NULL) end = *p + strlen(*p); len = end - *p; ret = malloc(len + 1); strncpy(ret, *p, len); ret[len] = '\0'; *p = end; /* Jump past the semicolon, if we stopped at one */ if (**p == ';') *p = end + 1; return ret; } static char *slurp_value(char **p) { char *equal = index(*p, '='); if (equal) { *p = equal + 1; return slurp_to_semi(p); } else { return NULL; } } static int is_opt(const char *opt, char *p) { int ret = 0; if ((strncmp(p, opt, strlen(opt)) == 0) && (p[strlen(opt)] == '=')) { ret = 1; } return ret; } static tgtadm_err bs_glfs_init(struct scsi_lu *lu, char *bsopts) { struct bs_thread_info *info = BS_THREAD_I(lu); char *logfile = NULL; int loglevel = 0; char *sloglevel; while (bsopts && strlen(bsopts)) { if (is_opt("logfile", bsopts)) logfile = slurp_value(&bsopts); else if (is_opt("loglevel", bsopts)) { sloglevel = slurp_value(&bsopts); loglevel = atoi(sloglevel); } } GFSP(lu)->logfile = logfile; GFSP(lu)->loglevel = loglevel; return bs_thread_open(info, bs_glfs_request, nr_iothreads); } static void bs_glfs_exit(struct scsi_lu *lu) { struct bs_thread_info *info = BS_THREAD_I(lu); if (GFSP(lu)->gfd) glfs_close(GFSP(lu)->gfd); if (GFSP(lu)->fs) glfs_fini(GFSP(lu)->fs); bs_thread_close(info); } static struct backingstore_template glfs_bst = { .bs_name = "glfs", .bs_datasize = sizeof(struct active_glfs) + sizeof(struct bs_thread_info), .bs_open = bs_glfs_open, .bs_close = bs_glfs_close, .bs_init = bs_glfs_init, .bs_exit = bs_glfs_exit, .bs_cmd_submit = bs_thread_cmd_submit, .bs_oflags_supported = ALLOWED_BSOFLAGS }; void register_bs_module(void) { register_backingstore_template(&glfs_bst); } tgt-1.0.85/usr/bs_null.c000066400000000000000000000031101435417276200150240ustar00rootroot00000000000000/* * NULL I/O backing store routine * * Copyright (C) 2008 Alexander Nezhinsky * * 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, version 2 of the * License. * * 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 "list.h" #include "tgtd.h" #include "scsi.h" #define NULL_BS_DEV_SIZE (1ULL << 40) int bs_null_cmd_submit(struct scsi_cmd *cmd) { scsi_set_result(cmd, SAM_STAT_GOOD); return 0; } static int bs_null_open(struct scsi_lu *lu, char *path, int *fd, uint64_t *size) { *size = NULL_BS_DEV_SIZE; dprintf("NULL backing store open, size: %" PRIu64 "\n", *size); return 0; } static void bs_null_close(struct scsi_lu *lu) { } static struct backingstore_template null_bst = { .bs_name = "null", .bs_datasize = 0, .bs_open = bs_null_open, .bs_close = bs_null_close, .bs_cmd_submit = bs_null_cmd_submit, }; __attribute__((constructor)) static void bs_null_constructor(void) { register_backingstore_template(&null_bst); } tgt-1.0.85/usr/bs_rbd.c000066400000000000000000000375341435417276200146420ustar00rootroot00000000000000/* * Synchronous rbd image backing store routine * * modified from bs_rdrw.c: * Copyright (C) 2006-2007 FUJITA Tomonori * Copyright (C) 2006-2007 Mike Christie * * 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, version 2 of the * License. * * 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 */ #define _XOPEN_SOURCE 600 #include #include #include #include #include #include #include #include #include #include #include #include "list.h" #include "util.h" #include "tgtd.h" #include "scsi.h" #include "spc.h" #include "bs_thread.h" #include "rados/librados.h" #include "rbd/librbd.h" struct active_rbd { char *poolname; char *imagename; char *snapname; rados_t cluster; rados_ioctx_t ioctx; rbd_image_t rbd_image; }; /* active_rbd is allocated just after the bs_thread_info */ #define RBDP(lu) ((struct active_rbd *) \ ((char *)lu + \ sizeof(struct scsi_lu) + \ sizeof(struct bs_thread_info)) \ ) static void parse_imagepath(char *path, char **pool, char **image, char **snap) { char *origp = strdup(path); char *p, *sep; p = origp; sep = strchr(p, '/'); if (sep == NULL) { *pool = "rbd"; } else { *sep = '\0'; *pool = strdup(p); p = sep + 1; } /* p points to image[@snap] */ sep = strchr(p, '@'); if (sep == NULL) { *snap = ""; } else { *snap = strdup(sep + 1); *sep = '\0'; } /* p points to image\0 */ *image = strdup(p); free(origp); } static void set_medium_error(int *result, uint8_t *key, uint16_t *asc) { *result = SAM_STAT_CHECK_CONDITION; *key = MEDIUM_ERROR; *asc = ASC_READ_ERROR; } static void bs_sync_sync_range(struct scsi_cmd *cmd, uint32_t length, int *result, uint8_t *key, uint16_t *asc) { int ret; ret = rbd_flush(RBDP(cmd->dev)->rbd_image); if (ret) set_medium_error(result, key, asc); } static void bs_rbd_request(struct scsi_cmd *cmd) { int ret; uint32_t length; int result = SAM_STAT_GOOD; uint8_t key; uint16_t asc; #if 0 /* * This should go in the sense data on error for COMPARE_AND_WRITE, but * there doesn't seem to be any attempt to do so... */ uint32_t info = 0; #endif char *tmpbuf; size_t blocksize; uint64_t offset = cmd->offset; uint32_t tl = cmd->tl; int do_verify = 0; int i; char *ptr; const char *write_buf = NULL; ret = length = 0; key = asc = 0; struct active_rbd *rbd = RBDP(cmd->dev); switch (cmd->scb[0]) { case ORWRITE_16: length = scsi_get_out_length(cmd); tmpbuf = malloc(length); if (!tmpbuf) { result = SAM_STAT_CHECK_CONDITION; key = HARDWARE_ERROR; asc = ASC_INTERNAL_TGT_FAILURE; break; } ret = rbd_read(rbd->rbd_image, offset, length, tmpbuf); if (ret != length) { set_medium_error(&result, &key, &asc); free(tmpbuf); break; } ptr = scsi_get_out_buffer(cmd); for (i = 0; i < length; i++) ptr[i] |= tmpbuf[i]; free(tmpbuf); write_buf = scsi_get_out_buffer(cmd); goto write; case COMPARE_AND_WRITE: /* Blocks are transferred twice, first the set that * we compare to the existing data, and second the set * to write if the compare was successful. */ length = scsi_get_out_length(cmd) / 2; if (length != cmd->tl) { result = SAM_STAT_CHECK_CONDITION; key = ILLEGAL_REQUEST; asc = ASC_INVALID_FIELD_IN_CDB; break; } tmpbuf = malloc(length); if (!tmpbuf) { result = SAM_STAT_CHECK_CONDITION; key = HARDWARE_ERROR; asc = ASC_INTERNAL_TGT_FAILURE; break; } ret = rbd_read(rbd->rbd_image, offset, length, tmpbuf); if (ret != length) { set_medium_error(&result, &key, &asc); free(tmpbuf); break; } if (memcmp(scsi_get_out_buffer(cmd), tmpbuf, length)) { uint32_t pos = 0; char *spos = scsi_get_out_buffer(cmd); char *dpos = tmpbuf; /* * Data differed, this is assumed to be 'rare' * so use a much more expensive byte-by-byte * comparasion to find out at which offset the * data differs. */ for (pos = 0; pos < length && *spos++ == *dpos++; pos++) ; #if 0 /* See comment above at declaration */ info = pos; #endif result = SAM_STAT_CHECK_CONDITION; key = MISCOMPARE; asc = ASC_MISCOMPARE_DURING_VERIFY_OPERATION; free(tmpbuf); break; } /* no DPO bit (cache retention advice) support */ free(tmpbuf); write_buf = scsi_get_out_buffer(cmd) + length; goto write; case SYNCHRONIZE_CACHE: case SYNCHRONIZE_CACHE_16: /* TODO */ length = (cmd->scb[0] == SYNCHRONIZE_CACHE) ? 0 : 0; if (cmd->scb[1] & 0x2) { result = SAM_STAT_CHECK_CONDITION; key = ILLEGAL_REQUEST; asc = ASC_INVALID_FIELD_IN_CDB; } else bs_sync_sync_range(cmd, length, &result, &key, &asc); break; case WRITE_VERIFY: case WRITE_VERIFY_12: case WRITE_VERIFY_16: do_verify = 1; case WRITE_6: case WRITE_10: case WRITE_12: case WRITE_16: length = scsi_get_out_length(cmd); write_buf = scsi_get_out_buffer(cmd); write: ret = rbd_write(rbd->rbd_image, offset, length, write_buf); if (ret == length) { struct mode_pg *pg; /* * it would be better not to access to pg * directy. */ pg = find_mode_page(cmd->dev, 0x08, 0); if (pg == NULL) { result = SAM_STAT_CHECK_CONDITION; key = ILLEGAL_REQUEST; asc = ASC_INVALID_FIELD_IN_CDB; break; } if (((cmd->scb[0] != WRITE_6) && (cmd->scb[1] & 0x8)) || !(pg->mode_data[0] & 0x04)) bs_sync_sync_range(cmd, length, &result, &key, &asc); } else set_medium_error(&result, &key, &asc); if (do_verify) goto verify; break; case WRITE_SAME: case WRITE_SAME_16: /* WRITE_SAME used to punch hole in file */ if (cmd->scb[1] & 0x08) { ret = rbd_discard(rbd->rbd_image, offset, tl); if (ret != 0) { eprintf("Failed to punch hole for WRITE_SAME" " command\n"); result = SAM_STAT_CHECK_CONDITION; key = HARDWARE_ERROR; asc = ASC_INTERNAL_TGT_FAILURE; break; } break; } while (tl > 0) { blocksize = 1 << cmd->dev->blk_shift; tmpbuf = scsi_get_out_buffer(cmd); switch (cmd->scb[1] & 0x06) { case 0x02: /* PBDATA==0 LBDATA==1 */ put_unaligned_be32(offset, tmpbuf); break; case 0x04: /* PBDATA==1 LBDATA==0 */ /* physical sector format */ put_unaligned_be64(offset, tmpbuf); break; } ret = rbd_write(rbd->rbd_image, offset, blocksize, tmpbuf); if (ret != blocksize) set_medium_error(&result, &key, &asc); offset += blocksize; tl -= blocksize; } break; case READ_6: case READ_10: case READ_12: case READ_16: length = scsi_get_in_length(cmd); ret = rbd_read(rbd->rbd_image, offset, length, scsi_get_in_buffer(cmd)); if (ret != length) set_medium_error(&result, &key, &asc); break; case PRE_FETCH_10: case PRE_FETCH_16: break; case VERIFY_10: case VERIFY_12: case VERIFY_16: verify: length = scsi_get_out_length(cmd); tmpbuf = malloc(length); if (!tmpbuf) { result = SAM_STAT_CHECK_CONDITION; key = HARDWARE_ERROR; asc = ASC_INTERNAL_TGT_FAILURE; break; } ret = rbd_read(rbd->rbd_image, offset, length, tmpbuf); if (ret != length) set_medium_error(&result, &key, &asc); else if (memcmp(scsi_get_out_buffer(cmd), tmpbuf, length)) { result = SAM_STAT_CHECK_CONDITION; key = MISCOMPARE; asc = ASC_MISCOMPARE_DURING_VERIFY_OPERATION; } free(tmpbuf); break; case UNMAP: if (!cmd->dev->attrs.thinprovisioning) { result = SAM_STAT_CHECK_CONDITION; key = ILLEGAL_REQUEST; asc = ASC_INVALID_FIELD_IN_CDB; break; } length = scsi_get_out_length(cmd); tmpbuf = scsi_get_out_buffer(cmd); if (length < 8) break; length -= 8; tmpbuf += 8; while (length >= 16) { offset = get_unaligned_be64(&tmpbuf[0]); offset = offset << cmd->dev->blk_shift; tl = get_unaligned_be32(&tmpbuf[8]); tl = tl << cmd->dev->blk_shift; if (offset + tl > cmd->dev->size) { eprintf("UNMAP beyond EOF\n"); result = SAM_STAT_CHECK_CONDITION; key = ILLEGAL_REQUEST; asc = ASC_LBA_OUT_OF_RANGE; break; } if (tl > 0) { if (rbd_discard(rbd->rbd_image, offset, tl) != 0) { eprintf("Failed to punch hole for" " UNMAP at offset:%" PRIu64 " length:%d\n", offset, tl); result = SAM_STAT_CHECK_CONDITION; key = HARDWARE_ERROR; asc = ASC_INTERNAL_TGT_FAILURE; break; } } length -= 16; tmpbuf += 16; } break; default: break; } dprintf("io done %p %x %d %u\n", cmd, cmd->scb[0], ret, length); scsi_set_result(cmd, result); if (result != SAM_STAT_GOOD) { eprintf("io error %p %x %d %d %" PRIu64 ", %m\n", cmd, cmd->scb[0], ret, length, offset); sense_data_build(cmd, key, asc); } } static int bs_rbd_open(struct scsi_lu *lu, char *path, int *fd, uint64_t *size) { uint32_t blksize = 0; int ret; rbd_image_info_t inf; char *poolname; char *imagename; char *snapname; struct active_rbd *rbd = RBDP(lu); parse_imagepath(path, &poolname, &imagename, &snapname); rbd->poolname = poolname; rbd->imagename = imagename; rbd->snapname = snapname; eprintf("bs_rbd_open: pool: %s image: %s snap: %s\n", poolname, imagename, snapname); ret = rados_ioctx_create(rbd->cluster, poolname, &rbd->ioctx); if (ret < 0) { eprintf("bs_rbd_open: rados_ioctx_create: %d\n", ret); return -EIO; } ret = rbd_open(rbd->ioctx, imagename, &rbd->rbd_image, snapname); if (ret < 0) { eprintf("bs_rbd_open: rbd_open: %d\n", ret); return ret; } if (rbd_stat(rbd->rbd_image, &inf, sizeof(inf)) < 0) { eprintf("bs_rbd_open: rbd_stat: %d\n", ret); return ret; } *size = inf.size; blksize = inf.obj_size; if (!lu->attrs.no_auto_lbppbe) update_lbppbe(lu, blksize); return 0; } static void bs_rbd_close(struct scsi_lu *lu) { struct active_rbd *rbd = RBDP(lu); if (rbd->rbd_image) { rbd_close(rbd->rbd_image); rados_ioctx_destroy(rbd->ioctx); rbd->rbd_image = rbd->ioctx = NULL; } } // Slurp up and return a copy of everything to the next ';', and update p static char *slurp_to_semi(char **p) { char *end = index(*p, ';'); char *ret; int len; if (end == NULL) end = *p + strlen(*p); len = end - *p; ret = malloc(len + 1); strncpy(ret, *p, len); ret[len] = '\0'; *p = end; /* Jump past the semicolon, if we stopped at one */ if (**p == ';') *p = end + 1; return ret; } static char *slurp_value(char **p) { char *equal = index(*p, '='); if (equal) { *p = equal + 1; return slurp_to_semi(p); } else { // uh...no? return NULL; } } static int is_opt(const char *opt, char *p) { int ret = 0; if ((strncmp(p, opt, strlen(opt)) == 0) && (p[strlen(opt)] == '=')) { ret = 1; } return ret; } static tgtadm_err bs_rbd_init(struct scsi_lu *lu, char *bsopts) { struct bs_thread_info *info = BS_THREAD_I(lu); tgtadm_err ret = TGTADM_UNKNOWN_ERR; int rados_ret; struct active_rbd *rbd = RBDP(lu); char *confname = NULL; char *clientid = NULL; char *virsecretuuid = NULL; char *given_cephx_key = NULL; char disc_cephx_key[256]; char *clustername = NULL; char clientid_full[128]; char *ignore = NULL; dprintf("bs_rbd_init bsopts: \"%s\"\n", bsopts); // look for conf= or id= or cluster= while (bsopts && strlen(bsopts)) { if (is_opt("conf", bsopts)) confname = slurp_value(&bsopts); else if (is_opt("id", bsopts)) clientid = slurp_value(&bsopts); else if (is_opt("cluster", bsopts)) clustername = slurp_value(&bsopts); else if (is_opt("virsecretuuid", bsopts)) virsecretuuid = slurp_value(&bsopts); else if (is_opt("cephx_key", bsopts)) given_cephx_key = slurp_value(&bsopts); else { ignore = slurp_to_semi(&bsopts); eprintf("bs_rbd: ignoring unknown option \"%s\"\n", ignore); free(ignore); break; } } if (clientid) eprintf("bs_rbd_init: clientid %s\n", clientid); if (confname) eprintf("bs_rbd_init: confname %s\n", confname); if (clustername) eprintf("bs_rbd_init: clustername %s\n", clustername); if (virsecretuuid) eprintf("bs_rbd_init: virsecretuuid %s\n", virsecretuuid); if (given_cephx_key) eprintf("bs_rbd_init: given_cephx_key %s\n", given_cephx_key); /* virsecretuuid && given_cephx_key are conflicting options. */ if (virsecretuuid && given_cephx_key) { eprintf("Conflicting options virsecretuuid=[%s] cephx_key=[%s]", virsecretuuid, given_cephx_key); goto fail; } /* Get stored key from secret uuid. */ if (virsecretuuid) { char libvir_uuid_file_path_buf[256] = "/etc/libvirt/secrets/"; strcat(libvir_uuid_file_path_buf, virsecretuuid); strcat(libvir_uuid_file_path_buf, ".base64"); FILE *fp; fp = fopen(libvir_uuid_file_path_buf , "r"); if (fp == NULL) { eprintf("bs_rbd_init: Unable to read %s\n", libvir_uuid_file_path_buf); goto fail; } if (fgets(disc_cephx_key, 256, fp) == NULL) { eprintf("bs_rbd_init: Unable to read %s\n", libvir_uuid_file_path_buf); goto fail; } fclose(fp); strtok(disc_cephx_key, "\n"); eprintf("bs_rbd_init: disc_cephx_key %s\n", disc_cephx_key); } eprintf("bs_rbd_init bsopts=%s\n", bsopts); /* * clientid may be set by -i/--id. If clustername is set, then * we use rados_create2, else rados_create */ if (clustername) { /* rados_create2 wants the full client name */ if (clientid) snprintf(clientid_full, sizeof clientid_full, "client.%s", clientid); else /* if not specified, default to client.admin */ snprintf(clientid_full, sizeof clientid_full, "client.admin"); rados_ret = rados_create2(&rbd->cluster, clustername, clientid_full, 0); } else { rados_ret = rados_create(&rbd->cluster, clientid); } if (rados_ret < 0) { eprintf("bs_rbd_init: rados_create: %d\n", rados_ret); return ret; } /* * Read config from environment, then conf file(s) which may * be set by conf= */ rados_ret = rados_conf_parse_env(rbd->cluster, NULL); if (rados_ret < 0) { eprintf("bs_rbd_init: rados_conf_parse_env: %d\n", rados_ret); goto fail; } rados_ret = rados_conf_read_file(rbd->cluster, confname); if (rados_ret < 0) { eprintf("bs_rbd_init: rados_conf_read_file: %d\n", rados_ret); goto fail; } /* Set given key */ if (virsecretuuid) { if (rados_conf_set(rbd->cluster, "key", disc_cephx_key) < 0) { eprintf("bs_rbd_init: failed to set cephx_key: %s\n", disc_cephx_key); goto fail; } } if (given_cephx_key) { if (rados_conf_set(rbd->cluster, "key", given_cephx_key) < 0) { eprintf("bs_rbd_init: failed to set cephx_key: %s\n", given_cephx_key); goto fail; } } rados_ret = rados_connect(rbd->cluster); if (rados_ret < 0) { eprintf("bs_rbd_init: rados_connect: %d\n", rados_ret); goto fail; } ret = bs_thread_open(info, bs_rbd_request, nr_iothreads); fail: if (confname) free(confname); if (clientid) free(clientid); if (virsecretuuid) free(virsecretuuid); if (given_cephx_key) free(given_cephx_key); return ret; } static void bs_rbd_exit(struct scsi_lu *lu) { struct bs_thread_info *info = BS_THREAD_I(lu); struct active_rbd *rbd = RBDP(lu); /* do this first to try to be sure there's no outstanding I/O */ bs_thread_close(info); rados_shutdown(rbd->cluster); } static struct backingstore_template rbd_bst = { .bs_name = "rbd", .bs_datasize = sizeof(struct bs_thread_info) + sizeof(struct active_rbd), .bs_open = bs_rbd_open, .bs_close = bs_rbd_close, .bs_init = bs_rbd_init, .bs_exit = bs_rbd_exit, .bs_cmd_submit = bs_thread_cmd_submit, .bs_oflags_supported = O_SYNC | O_DIRECT, }; void register_bs_module(void) { register_backingstore_template(&rbd_bst); } tgt-1.0.85/usr/bs_rdwr.c000066400000000000000000000273261435417276200150470ustar00rootroot00000000000000/* * Synchronous I/O file backing store routine * * Copyright (C) 2006-2007 FUJITA Tomonori * Copyright (C) 2006-2007 Mike Christie * * 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, version 2 of the * License. * * 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 */ #define _XOPEN_SOURCE 600 #include #include #include #include #include #include #include #include #include #include #include #include "list.h" #include "util.h" #include "tgtd.h" #include "scsi.h" #include "spc.h" #include "bs_thread.h" static void cmd_error_sense(struct scsi_cmd *cmd, uint8_t key, uint16_t asc) { scsi_set_result(cmd, SAM_STAT_CHECK_CONDITION); sense_data_build(cmd, key, asc); } #define set_medium_error(cmd) cmd_error_sense(cmd, MEDIUM_ERROR, ASC_READ_ERROR) static void bs_rdwr_request(struct scsi_cmd *cmd) { int ret = 0; int fd = cmd->dev->fd; uint32_t length = 0; char *tmpbuf; size_t blocksize; uint64_t offset = cmd->offset; uint32_t tl = cmd->tl; int do_verify = 0; int i; char *ptr; const char *write_buf = NULL; /* overwritten on error */ scsi_set_result(cmd, SAM_STAT_GOOD); switch (cmd->scb[0]) { case ORWRITE_16: length = scsi_get_out_length(cmd); tmpbuf = malloc(length); if (!tmpbuf) { cmd_error_sense(cmd, HARDWARE_ERROR, ASC_INTERNAL_TGT_FAILURE); break; } ret = pread64(fd, tmpbuf, length, offset); if (ret != length) { set_medium_error(cmd); free(tmpbuf); break; } ptr = scsi_get_out_buffer(cmd); for (i = 0; i < length; i++) ptr[i] |= tmpbuf[i]; free(tmpbuf); write_buf = scsi_get_out_buffer(cmd); goto write; case COMPARE_AND_WRITE: /* Blocks are transferred twice, first the set that * we compare to the existing data, and second the set * to write if the compare was successful. */ length = scsi_get_out_length(cmd) / 2; if (length != cmd->tl) { cmd_error_sense(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB); break; } tmpbuf = malloc(length); if (!tmpbuf) { cmd_error_sense(cmd, HARDWARE_ERROR, ASC_INTERNAL_TGT_FAILURE); break; } ret = pread64(fd, tmpbuf, length, offset); if (ret != length) { set_medium_error(cmd); free(tmpbuf); break; } if (memcmp(scsi_get_out_buffer(cmd), tmpbuf, length)) { uint64_t pos = 0; char *spos = scsi_get_out_buffer(cmd); char *dpos = tmpbuf; /* * Data differed, this is assumed to be 'rare' * so use a much more expensive byte-by-byte * comparasion to find out at which offset the * data differs. */ for (pos = 0; pos < length && *spos++ == *dpos++; pos++) ; free(tmpbuf); scsi_set_result(cmd, SAM_STAT_CHECK_CONDITION); sense_data_build_with_info(cmd, MISCOMPARE, ASC_MISCOMPARE_DURING_VERIFY_OPERATION, pos); break; } if (cmd->scb[1] & 0x10) posix_fadvise(fd, offset, length, POSIX_FADV_NOREUSE); free(tmpbuf); write_buf = scsi_get_out_buffer(cmd) + length; goto write; case SYNCHRONIZE_CACHE: case SYNCHRONIZE_CACHE_16: /* TODO */ length = (cmd->scb[0] == SYNCHRONIZE_CACHE) ? 0 : 0; if (cmd->scb[1] & 0x2) { cmd_error_sense(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB); } else { ret = fdatasync(fd); if (ret) set_medium_error(cmd); } break; case WRITE_VERIFY: case WRITE_VERIFY_12: case WRITE_VERIFY_16: do_verify = 1; case WRITE_6: case WRITE_10: case WRITE_12: case WRITE_16: length = scsi_get_out_length(cmd); write_buf = scsi_get_out_buffer(cmd); write: ret = pwrite64(fd, write_buf, length, offset); if (ret == length) { struct mode_pg *pg; /* * it would be better not to access to pg * directy. */ pg = find_mode_page(cmd->dev, 0x08, 0); if (pg == NULL) { cmd_error_sense(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB); break; } if (((cmd->scb[0] != WRITE_6) && (cmd->scb[1] & 0x8)) || !(pg->mode_data[0] & 0x04)) { ret = fdatasync(fd); if (ret) set_medium_error(cmd); } } else set_medium_error(cmd); if ((cmd->scb[0] != WRITE_6) && (cmd->scb[1] & 0x10)) posix_fadvise(fd, offset, length, POSIX_FADV_NOREUSE); if (do_verify) goto verify; break; case WRITE_SAME: case WRITE_SAME_16: /* WRITE_SAME used to punch hole in file */ if (cmd->scb[1] & 0x08) { ret = unmap_file_region(fd, offset, tl); if (ret != 0) { eprintf("Failed to punch hole for WRITE_SAME" " command\n"); cmd_error_sense(cmd, HARDWARE_ERROR, ASC_INTERNAL_TGT_FAILURE); break; } break; } while (tl > 0) { blocksize = 1 << cmd->dev->blk_shift; tmpbuf = scsi_get_out_buffer(cmd); switch(cmd->scb[1] & 0x06) { case 0x02: /* PBDATA==0 LBDATA==1 */ put_unaligned_be32(offset, tmpbuf); break; case 0x04: /* PBDATA==1 LBDATA==0 */ /* physical sector format */ put_unaligned_be64(offset, tmpbuf); break; } ret = pwrite64(fd, tmpbuf, blocksize, offset); if (ret != blocksize) set_medium_error(cmd); offset += blocksize; tl -= blocksize; } break; case READ_6: case READ_10: case READ_12: case READ_16: length = scsi_get_in_length(cmd); ret = pread64(fd, scsi_get_in_buffer(cmd), length, offset); if (ret != length) set_medium_error(cmd); if ((cmd->scb[0] != READ_6) && (cmd->scb[1] & 0x10)) posix_fadvise(fd, offset, length, POSIX_FADV_NOREUSE); break; case PRE_FETCH_10: case PRE_FETCH_16: ret = posix_fadvise(fd, offset, cmd->tl, POSIX_FADV_WILLNEED); if (ret != 0) set_medium_error(cmd); break; case VERIFY_10: case VERIFY_12: case VERIFY_16: verify: length = scsi_get_out_length(cmd); tmpbuf = malloc(length); if (!tmpbuf) { cmd_error_sense(cmd, HARDWARE_ERROR, ASC_INTERNAL_TGT_FAILURE); break; } ret = pread64(fd, tmpbuf, length, offset); if (ret != length) set_medium_error(cmd); else if (memcmp(scsi_get_out_buffer(cmd), tmpbuf, length)) { cmd_error_sense(cmd, MISCOMPARE, ASC_MISCOMPARE_DURING_VERIFY_OPERATION); } if (cmd->scb[1] & 0x10) posix_fadvise(fd, offset, length, POSIX_FADV_NOREUSE); free(tmpbuf); break; case UNMAP: if (!cmd->dev->attrs.thinprovisioning) { cmd_error_sense(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB); break; } length = scsi_get_out_length(cmd); tmpbuf = scsi_get_out_buffer(cmd); if (length < 8) break; length -= 8; tmpbuf += 8; while (length >= 16) { offset = get_unaligned_be64(&tmpbuf[0]); offset = offset << cmd->dev->blk_shift; tl = get_unaligned_be32(&tmpbuf[8]); tl = tl << cmd->dev->blk_shift; if (offset + tl > cmd->dev->size) { eprintf("UNMAP beyond EOF\n"); cmd_error_sense(cmd, ILLEGAL_REQUEST, ASC_LBA_OUT_OF_RANGE); break; } if (tl > 0) { if (unmap_file_region(fd, offset, tl) != 0) { eprintf("Failed to punch hole for" " UNMAP at offset:%" PRIu64 " length:%d\n", offset, tl); cmd_error_sense(cmd, HARDWARE_ERROR, ASC_INTERNAL_TGT_FAILURE); break; } } length -= 16; tmpbuf += 16; } break; default: break; } dprintf("io done %p %x %d %u\n", cmd, cmd->scb[0], ret, length); if (scsi_get_result(cmd) != SAM_STAT_GOOD) { eprintf("io error %p %x %d %d %" PRIu64 ", %m\n", cmd, cmd->scb[0], ret, length, offset); } } static int bs_rdwr_open(struct scsi_lu *lu, char *path, int *fd, uint64_t *size) { uint32_t blksize = 0; *fd = backed_file_open(path, O_RDWR|O_LARGEFILE|lu->bsoflags, size, &blksize); /* If we get access denied, try opening the file in readonly mode */ if (*fd == -1 && (errno == EACCES || errno == EROFS)) { *fd = backed_file_open(path, O_RDONLY|O_LARGEFILE|lu->bsoflags, size, &blksize); lu->attrs.readonly = 1; } if (*fd < 0) return *fd; if (!lu->attrs.no_auto_lbppbe) update_lbppbe(lu, blksize); return 0; } static void bs_rdwr_close(struct scsi_lu *lu) { close(lu->fd); } static tgtadm_err bs_rdwr_init(struct scsi_lu *lu, char *bsopts) { struct bs_thread_info *info = BS_THREAD_I(lu); return bs_thread_open(info, bs_rdwr_request, nr_iothreads); } static void bs_rdwr_exit(struct scsi_lu *lu) { struct bs_thread_info *info = BS_THREAD_I(lu); bs_thread_close(info); } static struct backingstore_template rdwr_bst = { .bs_name = "rdwr", .bs_datasize = sizeof(struct bs_thread_info), .bs_open = bs_rdwr_open, .bs_close = bs_rdwr_close, .bs_init = bs_rdwr_init, .bs_exit = bs_rdwr_exit, .bs_cmd_submit = bs_thread_cmd_submit, .bs_oflags_supported = O_SYNC | O_DIRECT, }; static struct backingstore_template mmc_bst = { .bs_name = "mmc", .bs_datasize = sizeof(struct bs_thread_info), .bs_open = bs_rdwr_open, .bs_close = bs_rdwr_close, .bs_init = bs_rdwr_init, .bs_exit = bs_rdwr_exit, .bs_cmd_submit = bs_thread_cmd_submit, .bs_oflags_supported = O_SYNC | O_DIRECT, }; static struct backingstore_template smc_bst = { .bs_name = "smc", .bs_datasize = sizeof(struct bs_thread_info), .bs_open = bs_rdwr_open, .bs_close = bs_rdwr_close, .bs_init = bs_rdwr_init, .bs_exit = bs_rdwr_exit, .bs_cmd_submit = bs_thread_cmd_submit, .bs_oflags_supported = O_SYNC | O_DIRECT, }; __attribute__((constructor)) static void bs_rdwr_constructor(void) { unsigned char sbc_opcodes[] = { ALLOW_MEDIUM_REMOVAL, COMPARE_AND_WRITE, FORMAT_UNIT, INQUIRY, MAINT_PROTOCOL_IN, MODE_SELECT, MODE_SELECT_10, MODE_SENSE, MODE_SENSE_10, ORWRITE_16, PERSISTENT_RESERVE_IN, PERSISTENT_RESERVE_OUT, PRE_FETCH_10, PRE_FETCH_16, READ_10, READ_12, READ_16, READ_6, READ_CAPACITY, RELEASE, REPORT_LUNS, REQUEST_SENSE, RESERVE, SEND_DIAGNOSTIC, SERVICE_ACTION_IN, START_STOP, SYNCHRONIZE_CACHE, SYNCHRONIZE_CACHE_16, TEST_UNIT_READY, UNMAP, VERIFY_10, VERIFY_12, VERIFY_16, WRITE_10, WRITE_12, WRITE_16, WRITE_6, WRITE_SAME, WRITE_SAME_16, WRITE_VERIFY, WRITE_VERIFY_12, WRITE_VERIFY_16 }; bs_create_opcode_map(&rdwr_bst, sbc_opcodes, ARRAY_SIZE(sbc_opcodes)); register_backingstore_template(&rdwr_bst); unsigned char mmc_opcodes[] = { ALLOW_MEDIUM_REMOVAL, CLOSE_TRACK, GET_CONFIGURATION, GET_PERFORMACE, INQUIRY, MODE_SELECT, MODE_SELECT_10, MODE_SENSE, MODE_SENSE_10, PERSISTENT_RESERVE_IN, PERSISTENT_RESERVE_OUT, READ_10, READ_12, READ_BUFFER_CAP, READ_CAPACITY, READ_DISK_INFO, READ_DVD_STRUCTURE, READ_TOC, READ_TRACK_INFO, RELEASE, REPORT_LUNS, REQUEST_SENSE, RESERVE, SET_CD_SPEED, SET_STREAMING, START_STOP, SYNCHRONIZE_CACHE, TEST_UNIT_READY, VERIFY_10, WRITE_10, WRITE_12, WRITE_VERIFY, }; bs_create_opcode_map(&mmc_bst, mmc_opcodes, ARRAY_SIZE(mmc_opcodes)); register_backingstore_template(&mmc_bst); unsigned char smc_opcodes[] = { INITIALIZE_ELEMENT_STATUS, INITIALIZE_ELEMENT_STATUS_WITH_RANGE, INQUIRY, MAINT_PROTOCOL_IN, MODE_SELECT, MODE_SELECT_10, MODE_SENSE, MODE_SENSE_10, MOVE_MEDIUM, PERSISTENT_RESERVE_IN, PERSISTENT_RESERVE_OUT, REQUEST_SENSE, TEST_UNIT_READY, READ_ELEMENT_STATUS, RELEASE, REPORT_LUNS, RESERVE, }; bs_create_opcode_map(&smc_bst, smc_opcodes, ARRAY_SIZE(smc_opcodes)); register_backingstore_template(&smc_bst); } tgt-1.0.85/usr/bs_sg.c000066400000000000000000000273351435417276200145020ustar00rootroot00000000000000/* * SCSI Generic I/O backing store * * Copyright (C) 2008 Alexander Nezhinsky * * Added linux/block/bsg.c support using struct sg_io_v4. * Copyright (C) 2010 Nicholas A. Bellinger * * 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, version 2 of the * License. * * 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 #include #include #include #include #include "bsg.h" /* Copied from include/linux/bsg.h */ #include "list.h" #include "util.h" #include "tgtd.h" #include "scsi.h" #include "spc.h" #include "tgtadm_error.h" #define BS_SG_RESVD_SZ (512 * 1024) static unsigned int sg_timeout = 30 * 1000; /* 30 seconds */ static int graceful_read(int fd, void *p_read, int to_read) { int err; while (to_read > 0) { err = read(fd, p_read, to_read); if (err >= 0) { to_read -= err; p_read += err; } else if (errno == EINTR) continue; else { eprintf("sg device %d read failed, errno: %d\n", fd, errno); return errno; } } return 0; } static int graceful_write(int fd, void *p_write, int to_write) { int err; while (to_write > 0) { err = write(fd, p_write, to_write); if (err >= 0) { to_write -= err; p_write += err; } else if (errno == EINTR) continue; else { eprintf("sg device %d write failed, errno: %d\n", fd, errno); return errno; } } return 0; } static int bs_sg_rw(int host_no, struct scsi_cmd *cmd) { int ret; unsigned char key = ILLEGAL_REQUEST; uint16_t asc = ASC_LUN_NOT_SUPPORTED; ret = cmd->dev->bst->bs_cmd_submit(cmd); if (ret) { key = HARDWARE_ERROR; asc = ASC_INTERNAL_TGT_FAILURE; } else return SAM_STAT_GOOD; cmd->offset = 0; scsi_set_in_resid_by_actual(cmd, 0); scsi_set_out_resid_by_actual(cmd, 0); sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } static int set_cmd_failed(struct scsi_cmd *cmd) { int result = SAM_STAT_CHECK_CONDITION; uint16_t asc = ASC_READ_ERROR; uint8_t key = MEDIUM_ERROR; scsi_set_result(cmd, result); sense_data_build(cmd, key, asc); return result; } static int bs_bsg_cmd_submit(struct scsi_cmd *cmd) { struct scsi_lu *dev = cmd->dev; int fd = dev->fd; struct sg_io_v4 io_hdr; int err = 0; memset(&io_hdr, 0, sizeof(io_hdr)); /* * Following linux/include/linux/bsg.h */ /* [i] 'Q' to differentiate from v3 */ io_hdr.guard = 'Q'; io_hdr.protocol = BSG_PROTOCOL_SCSI; io_hdr.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD; io_hdr.request_len = cmd->scb_len; io_hdr.request = (unsigned long )cmd->scb; io_hdr.dout_xfer_len = scsi_get_out_length(cmd); io_hdr.dout_xferp = (unsigned long)scsi_get_out_buffer(cmd); io_hdr.din_xfer_len = scsi_get_in_length(cmd); io_hdr.din_xferp = (unsigned long)scsi_get_in_buffer(cmd); io_hdr.max_response_len = sizeof(cmd->sense_buffer); /* SCSI: (auto)sense data */ io_hdr.response = (unsigned long)cmd->sense_buffer; /* Using the same 2000 millisecond timeout.. */ io_hdr.timeout = sg_timeout; /* [i->o] unused internally */ io_hdr.usr_ptr = (unsigned long)cmd; dprintf("[%d] Set io_hdr->usr_ptr from cmd: %p\n", getpid(), cmd); /* Bsg does Q_AT_HEAD by default */ io_hdr.flags |= BSG_FLAG_Q_AT_TAIL; dprintf("[%d] Calling graceful_write for CDB: 0x%02x\n", getpid(), cmd->scb[0]); err = graceful_write(fd, &io_hdr, sizeof(io_hdr)); if (!err) set_cmd_async(cmd); else { eprintf("failed to start cmd 0x%p\n", cmd); return set_cmd_failed(cmd); } return 0; } static void bs_sg_cmd_setup(struct sg_io_hdr *hdr, unsigned char *cmd, int cmd_len, void *data, int data_len, int direction, void *sense, int sense_len, int timeout) { memset(hdr, 0, sizeof(*hdr)); hdr->interface_id = 'S'; hdr->cmdp = cmd; hdr->cmd_len = cmd_len; hdr->dxfer_direction = direction; hdr->dxfer_len = data_len; hdr->dxferp = data; hdr->mx_sb_len = sense_len; hdr->sbp = sense; hdr->timeout = timeout; hdr->pack_id = -1; hdr->usr_ptr = NULL; hdr->flags = 0; } static int bs_sg_cmd_submit(struct scsi_cmd *cmd) { struct scsi_lu *dev = cmd->dev; int fd = dev->fd; struct sg_io_hdr io_hdr; int err = 0; memset(&io_hdr, 0, sizeof(io_hdr)); io_hdr.interface_id = 'S'; io_hdr.cmd_len = cmd->scb_len; io_hdr.cmdp = cmd->scb; if (scsi_get_data_dir(cmd) == DATA_WRITE) { io_hdr.dxfer_direction = SG_DXFER_TO_DEV; io_hdr.dxfer_len = scsi_get_out_length(cmd); io_hdr.dxferp = (void *)scsi_get_out_buffer(cmd); } else { io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; io_hdr.dxfer_len = scsi_get_in_length(cmd); io_hdr.dxferp = (void *)scsi_get_in_buffer(cmd); } io_hdr.mx_sb_len = sizeof(cmd->sense_buffer); io_hdr.sbp = cmd->sense_buffer; io_hdr.timeout = sg_timeout; io_hdr.pack_id = -1; io_hdr.usr_ptr = cmd; io_hdr.flags |= SG_FLAG_DIRECT_IO; err = graceful_write(fd, &io_hdr, sizeof(io_hdr)); if (!err) set_cmd_async(cmd); else { eprintf("failed to start cmd 0x%p\n", cmd); return set_cmd_failed(cmd); } return 0; } static void bs_bsg_cmd_complete(int fd, int events, void *data) { struct sg_io_v4 io_hdr; struct scsi_cmd *cmd; int err; dprintf("[%d] bs_bsg_cmd_complete() called!\n", getpid()); memset(&io_hdr, 0, sizeof(io_hdr)); /* [i] 'Q' to differentiate from v3 */ io_hdr.guard = 'Q'; err = graceful_read(fd, &io_hdr, sizeof(io_hdr)); if (err) return; cmd = (struct scsi_cmd *)(unsigned long)io_hdr.usr_ptr; dprintf("BSG Using cmd: %p for io_hdr.usr_ptr\n", cmd); /* * Check SCSI: command completion status * */ if (!io_hdr.device_status) { uint32_t actual_len; if (io_hdr.dout_resid) { actual_len = scsi_get_out_length(cmd) - io_hdr.dout_resid; scsi_set_out_resid_by_actual(cmd, actual_len); } if (io_hdr.din_resid) { actual_len = scsi_get_in_length(cmd) - io_hdr.din_resid; scsi_set_in_resid_by_actual(cmd, actual_len); } } else { /* * NAB: Used by linux/block/bsg.c:bsg_ioctl(), is this * right..? */ cmd->sense_len = SCSI_SENSE_BUFFERSIZE; scsi_set_out_resid_by_actual(cmd, 0); scsi_set_in_resid_by_actual(cmd, 0); } target_cmd_io_done(cmd, io_hdr.device_status); } static void bs_sg_cmd_complete(int fd, int events, void *data) { struct sg_io_hdr io_hdr; struct scsi_cmd *cmd; int err; uint32_t actual_len; memset(&io_hdr, 0, sizeof(io_hdr)); io_hdr.interface_id = 'S'; io_hdr.pack_id = -1; err = graceful_read(fd, &io_hdr, sizeof(io_hdr)); if (err) return; cmd = (struct scsi_cmd *)io_hdr.usr_ptr; actual_len = io_hdr.dxfer_len - io_hdr.resid; cmd->sense_len = io_hdr.sb_len_wr; if (!actual_len || io_hdr.resid) { if (io_hdr.dxfer_direction == SG_DXFER_TO_DEV) scsi_set_out_resid_by_actual(cmd, actual_len); else scsi_set_in_resid_by_actual(cmd, actual_len); } target_cmd_io_done(cmd, io_hdr.status); } static int get_bsg_major(char *path) { FILE *devfd; int majorno, n; char dev[64]; char tmp[16]; sscanf(path, "/dev/bsg/%s", tmp); sprintf(dev, "/sys/class/bsg/%s/dev", tmp); devfd = fopen(dev, "r"); if (!devfd) { eprintf("%s open failed errno: %d\n", dev, errno); return -1; } n = fscanf(devfd, "%d:", &majorno); fclose(devfd); if (n != 1) { if (n < 0) eprintf("reading major from %s failed errno: %d\n", dev, errno); else eprintf("reading major from %s failed: invalid input\n", dev); return -1; } return majorno; } static int chk_sg_device(char *path) { struct stat st; if (stat(path, &st) < 0) { eprintf("stat() failed errno: %d\n", errno); return -1; } if (!S_ISCHR(st.st_mode)) { eprintf("Not a character device: %s\n", path); return -1; } /* Check for SG_IO major first.. */ if (major(st.st_rdev) == SCSI_GENERIC_MAJOR) return 0; if (!strncmp("/dev/bsg", path, 8)) { if (major(st.st_rdev) == get_bsg_major(path)) return 1; } return -1; } static int init_bsg_device(int fd) { int t, err; err = ioctl(fd, SG_GET_COMMAND_Q, &t); if (err < 0) { eprintf("SG_GET_COMMAND_Q for bsd failed: %d\n", err); return -1; } eprintf("bsg: Using max_queue: %d\n", t); t = BS_SG_RESVD_SZ; err = ioctl(fd, SG_SET_RESERVED_SIZE, &t); if (err < 0) { eprintf("SG_SET_RESERVED_SIZE errno: %d\n", errno); return -1; } return 0; } static int init_sg_device(int fd) { int t, err; struct sg_io_hdr hdr; unsigned char cmd[6]; unsigned char resp[36]; err = ioctl(fd, SG_GET_VERSION_NUM, &t); if ((err < 0) || (t < 30000)) { eprintf("sg driver prior to 3.x\n"); return -1; } t = BS_SG_RESVD_SZ; err = ioctl(fd, SG_SET_RESERVED_SIZE, &t); if (err < 0) { eprintf("SG_SET_RESERVED_SIZE errno: %d\n", errno); return -1; } memset(&cmd, 0, sizeof(cmd)); memset(&resp, 0, sizeof(resp)); cmd[0] = INQUIRY; cmd[4] = sizeof(resp); bs_sg_cmd_setup(&hdr, cmd, sizeof(cmd), resp, sizeof(resp), SG_DXFER_FROM_DEV, NULL, 0, 30000); err = ioctl(fd, SG_IO, &hdr); if (!err && (resp[0] & 0x1f) == TYPE_TAPE) sg_timeout = 14000 * 1000; return 0; } static tgtadm_err bs_sg_init(struct scsi_lu *lu, char *bsopts) { /* * Setup struct scsi_lu->cmd_perform() passthrough pointer * (if available) for the underlying device type. */ lu->cmd_perform = &target_cmd_perform_passthrough; /* * Setup struct scsi_lu->cmd_done() passthrough pointer using * usr/target.c:__cmd_done_passthrough(). */ lu->cmd_done = &__cmd_done_passthrough; return TGTADM_SUCCESS; } static int bs_sg_open(struct scsi_lu *lu, char *path, int *fd, uint64_t *size) { void (*cmd_complete)(int, int, void *) = NULL; int sg_fd, err, bsg = 0; bsg = chk_sg_device(path); if (bsg < 0) { eprintf("Not recognized %s as an SG device\n", path); return -EINVAL; } sg_fd = open(path, O_RDWR); if (sg_fd < 0) { eprintf("Could not open %s, %m\n", path); return sg_fd; } if (bsg) { cmd_complete = &bs_bsg_cmd_complete; err = init_bsg_device(sg_fd); } else { cmd_complete = &bs_sg_cmd_complete; err = init_sg_device(sg_fd); } if (err) { eprintf("Failed to initialize sg device %s\n", path); return err; } err = tgt_event_add(sg_fd, EPOLLIN, cmd_complete, NULL); if (err) { eprintf("Failed to add sg device event %s\n", path); return err; } *fd = sg_fd; *size = 0; return 0; } static void bs_sg_close(struct scsi_lu *lu) { close(lu->fd); } static tgtadm_err bs_sg_lu_init(struct scsi_lu *lu) { if (spc_lu_init(lu)) return TGTADM_NOMEM; return TGTADM_SUCCESS; } static struct backingstore_template sg_bst = { .bs_name = "sg", .bs_datasize = 0, .bs_init = bs_sg_init, .bs_open = bs_sg_open, .bs_close = bs_sg_close, .bs_cmd_submit = bs_sg_cmd_submit, }; static struct backingstore_template bsg_bst = { .bs_name = "bsg", .bs_datasize = 0, .bs_init = bs_sg_init, .bs_open = bs_sg_open, .bs_close = bs_sg_close, .bs_cmd_submit = bs_bsg_cmd_submit, }; static struct device_type_template sg_template = { .type = TYPE_PT, .lu_init = bs_sg_lu_init, .lu_config = spc_lu_config, .lu_online = spc_lu_online, .lu_offline = spc_lu_offline, .lu_exit = spc_lu_exit, .cmd_passthrough = bs_sg_rw, }; __attribute__((constructor)) static void bs_sg_constructor(void) { register_backingstore_template(&sg_bst); register_backingstore_template(&bsg_bst); device_type_register(&sg_template); } tgt-1.0.85/usr/bs_sheepdog.c000066400000000000000000001063201435417276200156570ustar00rootroot00000000000000/* * Copyright (C) 2010 FUJITA Tomonori * Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. * * 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, version 2 of the * License. * * 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 #include #include #include #include #include #include #include "list.h" #include "tgtd.h" #include "util.h" #include "log.h" #include "scsi.h" #include "bs_thread.h" #define SD_PROTO_VER 0x01 #define SD_DEFAULT_ADDR "localhost" #define SD_DEFAULT_PORT 7000 #define SD_OP_CREATE_AND_WRITE_OBJ 0x01 #define SD_OP_READ_OBJ 0x02 #define SD_OP_WRITE_OBJ 0x03 /* 0x04 is used internally by Sheepdog */ #define SD_OP_DISCARD_OBJ 0x05 #define SD_OP_NEW_VDI 0x11 #define SD_OP_LOCK_VDI 0x12 #define SD_OP_RELEASE_VDI 0x13 #define SD_OP_GET_VDI_INFO 0x14 #define SD_OP_READ_VDIS 0x15 #define SD_OP_FLUSH_VDI 0x16 #define SD_OP_DEL_VDI 0x17 #define SD_FLAG_CMD_WRITE 0x01 #define SD_FLAG_CMD_COW 0x02 #define SD_FLAG_CMD_CACHE 0x04 /* Writeback mode for cache */ #define SD_FLAG_CMD_DIRECT 0x08 /* Don't use cache */ /* return something back while sending something to sheep */ #define SD_FLAG_CMD_PIGGYBACK 0x10 #define SD_FLAG_CMD_TGT 0x20 #define SD_RES_SUCCESS 0x00 /* Success */ #define SD_RES_UNKNOWN 0x01 /* Unknown error */ #define SD_RES_NO_OBJ 0x02 /* No object found */ #define SD_RES_EIO 0x03 /* I/O error */ #define SD_RES_VDI_EXIST 0x04 /* Vdi exists already */ #define SD_RES_INVALID_PARMS 0x05 /* Invalid parameters */ #define SD_RES_SYSTEM_ERROR 0x06 /* System error */ #define SD_RES_VDI_LOCKED 0x07 /* Vdi is locked */ #define SD_RES_NO_VDI 0x08 /* No vdi found */ #define SD_RES_NO_BASE_VDI 0x09 /* No base vdi found */ #define SD_RES_VDI_READ 0x0A /* Cannot read requested vdi */ #define SD_RES_VDI_WRITE 0x0B /* Cannot write requested vdi */ #define SD_RES_BASE_VDI_READ 0x0C /* Cannot read base vdi */ #define SD_RES_BASE_VDI_WRITE 0x0D /* Cannot write base vdi */ #define SD_RES_NO_TAG 0x0E /* Requested tag is not found */ #define SD_RES_STARTUP 0x0F /* Sheepdog is on starting up */ #define SD_RES_VDI_NOT_LOCKED 0x10 /* Vdi is not locked */ #define SD_RES_SHUTDOWN 0x11 /* Sheepdog is shutting down */ #define SD_RES_NO_MEM 0x12 /* Cannot allocate memory */ #define SD_RES_FULL_VDI 0x13 /* we already have the maximum vdis */ #define SD_RES_VER_MISMATCH 0x14 /* Protocol version mismatch */ #define SD_RES_NO_SPACE 0x15 /* Server has no room for new objects */ #define SD_RES_WAIT_FOR_FORMAT 0x16 /* Waiting for a format operation */ #define SD_RES_WAIT_FOR_JOIN 0x17 /* Waiting for other nodes joining */ #define SD_RES_JOIN_FAILED 0x18 /* Target node had failed to join sheepdog */ #define SD_RES_HALT 0x19 /* Sheepdog is stopped serving IO request */ #define SD_RES_READONLY 0x1A /* Object is read-only */ #define SD_RES_INCOMPLETE 0x1B /* Object (in kv) is incomplete uploading */ /* sheep is collecting cluster wide status, not ready for operation */ #define SD_RES_COLLECTING_CINFO 0x1C /* inode object in client is invalidated, refreshing is required */ #define SD_RES_INODE_INVALIDATED 0x1D /* * Object ID rules * * 0 - 19 (20 bits): data object space * 20 - 31 (12 bits): reserved data object space * 32 - 55 (24 bits): vdi object space * 56 - 59 ( 4 bits): reserved vdi object space * 60 - 63 ( 4 bits): object type identifier space */ #define VDI_SPACE_SHIFT 32 #define VDI_BIT (UINT64_C(1) << 63) #define VMSTATE_BIT (UINT64_C(1) << 62) #define MAX_DATA_OBJS (UINT64_C(1) << 20) #define MAX_CHILDREN 1024 #define SD_MAX_VDI_LEN 256 #define SD_MAX_VDI_TAG_LEN 256 #define SD_NR_VDIS (1U << 24) #define SD_DATA_OBJ_SIZE (UINT64_C(1) << 22) #define SD_MAX_VDI_SIZE (SD_DATA_OBJ_SIZE * MAX_DATA_OBJS) #define SECTOR_SIZE 512 #define CURRENT_VDI_ID 0 struct sheepdog_req { uint8_t proto_ver; uint8_t opcode; uint16_t flags; uint32_t epoch; uint32_t id; uint32_t data_length; uint32_t opcode_specific[8]; }; struct sheepdog_rsp { uint8_t proto_ver; uint8_t opcode; uint16_t flags; uint32_t epoch; uint32_t id; uint32_t data_length; uint32_t result; uint32_t opcode_specific[7]; }; struct sheepdog_obj_req { uint8_t proto_ver; uint8_t opcode; uint16_t flags; uint32_t epoch; uint32_t id; uint32_t data_length; uint64_t oid; uint64_t cow_oid; uint8_t copies; uint8_t copy_policy; uint8_t ec_index; uint8_t reserved; uint32_t rsvd; uint32_t offset; uint32_t pad; }; struct sheepdog_obj_rsp { uint8_t proto_ver; uint8_t opcode; uint16_t flags; uint32_t epoch; uint32_t id; uint32_t data_length; uint32_t result; uint8_t copies; uint8_t reserved[3]; uint32_t pad[6]; }; #define LOCK_TYPE_NORMAL 0 #define LOCK_TYPE_SHARED 1 /* for iSCSI multipath */ struct sheepdog_vdi_req { uint8_t proto_ver; uint8_t opcode; uint16_t flags; uint32_t epoch; uint32_t id; uint32_t data_length; uint64_t vdi_size; uint32_t vdi_id; uint8_t copies; uint8_t copy_policy; uint8_t ec_index; uint8_t block_size_shift; uint32_t snapid; uint32_t type; uint32_t pad[2]; }; struct sheepdog_vdi_rsp { uint8_t proto_ver; uint8_t opcode; uint16_t flags; uint32_t epoch; uint32_t id; uint32_t data_length; uint32_t result; uint32_t rsvd; uint32_t vdi_id; uint32_t attr_id; uint8_t copies; uint8_t block_size_shift; uint8_t reserved[2]; uint32_t pad[3]; }; /* * Historical notes: previous version of sheepdog (< v0.9.0) has a limit of * maximum number of children which can be created from single VDI. So the inode * object has an array for storing the IDs of the child VDIs. The constant * OLD_MAX_CHILDREN represents it. Current sheepdog doesn't have the limitation, * so we are recycling the area (4 * OLD_MAX_CHILDREN = 4KB) for storing new * metadata. * * users of the released area: * - uint32_t btree_counter */ #define OLD_MAX_CHILDREN 1024U struct sheepdog_inode { char name[SD_MAX_VDI_LEN]; char tag[SD_MAX_VDI_TAG_LEN]; uint64_t create_time; uint64_t snap_ctime; uint64_t vm_clock_nsec; uint64_t vdi_size; uint64_t vm_state_size; uint8_t copy_policy; uint8_t store_policy; uint8_t nr_copies; uint8_t block_size_shift; uint32_t snap_id; uint32_t vdi_id; uint32_t parent_vdi_id; uint32_t btree_counter; uint32_t __unused[OLD_MAX_CHILDREN - 1]; uint32_t data_vdi_id[MAX_DATA_OBJS]; }; #define SD_INODE_SIZE (sizeof(struct sheepdog_inode)) struct sheepdog_fd_list { int fd; pthread_t id; struct list_head list; }; #define UNIX_PATH_MAX 108 struct sheepdog_access_info { int is_unix; /* tcp */ char hostname[HOST_NAME_MAX + 1]; int port; /* unix domain socket */ char uds_path[UNIX_PATH_MAX]; /* * maximum length of fd_list_head: nr_iothreads + 1 * (+ 1 is for main thread) * * TODO: more effective data structure for handling massive parallel * access */ struct list_head fd_list_head; pthread_rwlock_t fd_list_lock; struct sheepdog_inode inode; pthread_rwlock_t inode_lock; pthread_mutex_t inode_version_mutex; uint64_t inode_version; struct list_head inflight_list_head; pthread_mutex_t inflight_list_mutex; pthread_cond_t inflight_list_cond; }; static inline int is_data_obj_writeable(struct sheepdog_inode *inode, unsigned int idx) { return inode->vdi_id == inode->data_vdi_id[idx]; } static inline int is_data_obj(uint64_t oid) { return !(VDI_BIT & oid); } static inline uint64_t data_oid_to_idx(uint64_t oid) { return oid & (MAX_DATA_OBJS - 1); } static inline uint64_t vid_to_vdi_oid(uint32_t vid) { return VDI_BIT | ((uint64_t)vid << VDI_SPACE_SHIFT); } static inline uint64_t vid_to_vmstate_oid(uint32_t vid, uint32_t idx) { return VMSTATE_BIT | ((uint64_t)vid << VDI_SPACE_SHIFT) | idx; } static inline uint64_t vid_to_data_oid(uint32_t vid, uint32_t idx) { return ((uint64_t)vid << VDI_SPACE_SHIFT) | idx; } static const char *sd_strerror(int err) { int i; static const struct { int err; const char *desc; } errors[] = { {SD_RES_SUCCESS, "Success"}, {SD_RES_UNKNOWN, "Unknown error"}, {SD_RES_NO_OBJ, "No object found"}, {SD_RES_EIO, "I/O error"}, {SD_RES_VDI_EXIST, "VDI exists already"}, {SD_RES_INVALID_PARMS, "Invalid parameters"}, {SD_RES_SYSTEM_ERROR, "System error"}, {SD_RES_VDI_LOCKED, "VDI is already locked"}, {SD_RES_NO_VDI, "No vdi found"}, {SD_RES_NO_BASE_VDI, "No base VDI found"}, {SD_RES_VDI_READ, "Failed read the requested VDI"}, {SD_RES_VDI_WRITE, "Failed to write the requested VDI"}, {SD_RES_BASE_VDI_READ, "Failed to read the base VDI"}, {SD_RES_BASE_VDI_WRITE, "Failed to write the base VDI"}, {SD_RES_NO_TAG, "Failed to find the requested tag"}, {SD_RES_STARTUP, "The system is still booting"}, {SD_RES_VDI_NOT_LOCKED, "VDI isn't locked"}, {SD_RES_SHUTDOWN, "The system is shutting down"}, {SD_RES_NO_MEM, "Out of memory on the server"}, {SD_RES_FULL_VDI, "We already have the maximum vdis"}, {SD_RES_VER_MISMATCH, "Protocol version mismatch"}, {SD_RES_NO_SPACE, "Server has no space for new objects"}, {SD_RES_WAIT_FOR_FORMAT, "Sheepdog is waiting for a format operation"}, {SD_RES_WAIT_FOR_JOIN, "Sheepdog is waiting for other nodes joining"}, {SD_RES_JOIN_FAILED, "Target node had failed to join sheepdog"}, {SD_RES_HALT, "Sheepdog is stopped serving IO request"}, {SD_RES_READONLY, "Object is read-only"}, {SD_RES_INODE_INVALIDATED, "Inode object is invalidated"}, }; for (i = 0; i < ARRAY_SIZE(errors); ++i) { if (errors[i].err == err) return errors[i].desc; } return "Invalid error code"; } static int connect_to_sdog_tcp(const char *addr, int port) { char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; int fd, ret; struct addrinfo hints, *res, *res0; char port_s[6]; if (!addr) { addr = SD_DEFAULT_ADDR; port = SD_DEFAULT_PORT; } memset(port_s, 0, 6); snprintf(port_s, 5, "%d", port); memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; ret = getaddrinfo(addr, port_s, &hints, &res0); if (ret) { eprintf("unable to get address info %s, %s\n", addr, strerror(errno)); return -1; } for (res = res0; res; res = res->ai_next) { ret = getnameinfo(res->ai_addr, res->ai_addrlen, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV); if (ret) continue; fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (fd < 0) continue; reconnect: ret = connect(fd, res->ai_addr, res->ai_addrlen); if (ret < 0) { if (errno == EINTR) goto reconnect; close(fd); break; } dprintf("connected to %s:%d\n", addr, port); goto success; } fd = -1; eprintf("failed connect to %s:%d\n", addr, port); success: freeaddrinfo(res0); return fd; } static int connect_to_sdog_unix(const char *path) { int fd, ret; struct sockaddr_un un; fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { eprintf("socket() failed: %m\n"); return -1; } memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; strncpy(un.sun_path, path, sizeof(un.sun_path) - 1); ret = connect(fd, (const struct sockaddr *)&un, (socklen_t)sizeof(un)); if (ret < 0) { eprintf("connect() failed: %m\n"); close(fd); return -1; } return fd; } static int get_my_fd(struct sheepdog_access_info *ai) { pthread_t self_id = pthread_self(); struct sheepdog_fd_list *p; int fd; pthread_rwlock_rdlock(&ai->fd_list_lock); list_for_each_entry(p, &ai->fd_list_head, list) { if (p->id == self_id) { pthread_rwlock_unlock(&ai->fd_list_lock); return p->fd; } } pthread_rwlock_unlock(&ai->fd_list_lock); if (ai->is_unix) fd = connect_to_sdog_unix(ai->uds_path); else fd = connect_to_sdog_tcp(ai->hostname, ai->port); if (fd < 0) return -1; p = zalloc(sizeof(*p)); if (!p) { close(fd); return -1; } p->id = self_id; p->fd = fd; INIT_LIST_HEAD(&p->list); pthread_rwlock_wrlock(&ai->fd_list_lock); list_add_tail(&p->list, &ai->fd_list_head); pthread_rwlock_unlock(&ai->fd_list_lock); return p->fd; } static void close_my_fd(struct sheepdog_access_info *ai, int fd) { struct sheepdog_fd_list *p; int closed = 0; pthread_rwlock_wrlock(&ai->fd_list_lock); list_for_each_entry(p, &ai->fd_list_head, list) { if (p->fd == fd) { close(fd); list_del(&p->list); free(p); closed = 1; break; } } pthread_rwlock_unlock(&ai->fd_list_lock); if (!closed) eprintf("unknown fd to close: %d\n", fd); } static int do_read(int sockfd, void *buf, int len) { int ret; reread: ret = read(sockfd, buf, len); if (!ret) { eprintf("connection is closed (%d bytes left)\n", len); return 1; } if (ret < 0) { if (errno == EINTR || errno == EAGAIN) goto reread; eprintf("failed to read from socket: %d, %s\n", ret, strerror(errno)); return 1; } len -= ret; buf = (char *)buf + ret; if (len) goto reread; return 0; } static void forward_iov(struct msghdr *msg, int len) { while (msg->msg_iov->iov_len <= len) { len -= msg->msg_iov->iov_len; msg->msg_iov++; msg->msg_iovlen--; } msg->msg_iov->iov_base = (char *) msg->msg_iov->iov_base + len; msg->msg_iov->iov_len -= len; } static int do_write(int sockfd, struct msghdr *msg, int len) { int ret; rewrite: ret = sendmsg(sockfd, msg, 0); if (ret < 0) { if (errno == EINTR || errno == EAGAIN) goto rewrite; eprintf("failed to write to socket: %d, %s\n", ret, strerror(errno)); return 1; } len -= ret; if (len) { forward_iov(msg, ret); goto rewrite; } return 0; } static int send_req(int sockfd, struct sheepdog_req *hdr, void *data, unsigned int *wlen) { int ret; struct iovec iov[2]; struct msghdr msg; memset(&msg, 0, sizeof(msg)); msg.msg_iov = iov; msg.msg_iovlen = 1; iov[0].iov_base = hdr; iov[0].iov_len = sizeof(*hdr); if (*wlen) { msg.msg_iovlen++; iov[1].iov_base = data; iov[1].iov_len = *wlen; } ret = do_write(sockfd, &msg, sizeof(*hdr) + *wlen); if (ret) { eprintf("failed to send a req, %s\n", strerror(errno)); ret = -1; } return ret; } static int do_req(struct sheepdog_access_info *ai, struct sheepdog_req *hdr, void *data, unsigned int *wlen, unsigned int *rlen) { int ret, sockfd, count = 0; retry: if (count++) { eprintf("retrying to reconnect (%d)\n", count); if (0 <= sockfd) close_my_fd(ai, sockfd); sleep(1); } sockfd = get_my_fd(ai); if (sockfd < 0) goto retry; ret = send_req(sockfd, hdr, data, wlen); if (ret) goto retry; /* FIXME: retrying COW request should be handled in graceful way */ ret = do_read(sockfd, hdr, sizeof(*hdr)); if (ret) goto retry; if (hdr->data_length < *rlen) *rlen = hdr->data_length; if (*rlen) { ret = do_read(sockfd, data, *rlen); if (ret) goto retry; } return 0; } static int find_vdi_name(struct sheepdog_access_info *ai, char *filename, uint32_t snapid, char *tag, uint32_t *vid, int for_snapshot); static int read_object(struct sheepdog_access_info *ai, char *buf, uint64_t oid, int copies, unsigned int datalen, uint32_t offset, int *need_reload); static int reload_inode(struct sheepdog_access_info *ai, int is_snapshot) { int ret = 0, need_reload = 0; char tag[SD_MAX_VDI_TAG_LEN]; uint32_t vid; static __thread uint64_t inode_version; pthread_mutex_lock(&ai->inode_version_mutex); if (inode_version != ai->inode_version) { /* some other threads reloaded inode */ inode_version = ai->inode_version; goto ret; } if (is_snapshot) { memset(tag, 0, sizeof(tag)); ret = find_vdi_name(ai, ai->inode.name, CURRENT_VDI_ID, tag, &vid, 0); if (ret) { ret = -1; goto ret; } ret = read_object(ai, (char *)&ai->inode, vid_to_vdi_oid(vid), ai->inode.nr_copies, offsetof(struct sheepdog_inode, data_vdi_id), 0, &need_reload); if (ret) { ret = -1; goto ret; } } else { ret = read_object(ai, (char *)&ai->inode, vid_to_vdi_oid(ai->inode.vdi_id), ai->inode.nr_copies, SD_INODE_SIZE, 0, &need_reload); if (ret) { ret = -1; goto ret; } if (!!ai->inode.snap_ctime) { /* * This is a case like below: * take snapshot -> write something -> failover * * Because invalidated inode is readonly and latest * working VDI can have COWed objects, we need to * resolve VID and reload its entire inode object. */ memset(tag, 0, sizeof(tag)); ret = find_vdi_name(ai, ai->inode.name, CURRENT_VDI_ID, tag, &vid, 0); if (ret) { ret = -1; goto ret; } ret = read_object(ai, (char *)&ai->inode, vid_to_vdi_oid(vid), ai->inode.nr_copies, SD_INODE_SIZE, 0, &need_reload); if (ret) { ret = -1; goto ret; } } } inode_version++; ai->inode_version = inode_version; ret: pthread_mutex_unlock(&ai->inode_version_mutex); return ret; } static int read_write_object(struct sheepdog_access_info *ai, char *buf, uint64_t oid, int copies, unsigned int datalen, uint32_t offset, int write, int create, uint64_t old_oid, uint16_t flags, int *need_reload) { struct sheepdog_obj_req hdr; struct sheepdog_obj_rsp *rsp = (struct sheepdog_obj_rsp *)&hdr; unsigned int wlen, rlen; int ret; retry: memset(&hdr, 0, sizeof(hdr)); hdr.proto_ver = SD_PROTO_VER; hdr.flags = flags; if (write) { wlen = datalen; rlen = 0; hdr.flags |= SD_FLAG_CMD_WRITE; if (create) { hdr.opcode = SD_OP_CREATE_AND_WRITE_OBJ; hdr.cow_oid = old_oid; } else { hdr.opcode = SD_OP_WRITE_OBJ; } } else { wlen = 0; rlen = datalen; hdr.opcode = SD_OP_READ_OBJ; } hdr.oid = oid; hdr.data_length = datalen; hdr.offset = offset; hdr.copies = copies; hdr.flags |= SD_FLAG_CMD_TGT; ret = do_req(ai, (struct sheepdog_req *)&hdr, buf, &wlen, &rlen); if (ret) { eprintf("failed to send a request to the sheep\n"); return -1; } switch (rsp->result) { case SD_RES_SUCCESS: return 0; case SD_RES_INODE_INVALIDATED: dprintf("inode object is invalidated\n"); *need_reload = 2; return 0; case SD_RES_READONLY: *need_reload = 1; return 0; case SD_RES_NO_OBJ: if (!write && oid & (UINT64_C(1) << 63)) /* * sheepdog doesn't provide a mechanism of metadata * transaction, so tgt can see an inconsistent state * like this (old working VDI became snapshot already * but an inode object of new working VDI isn't * created yet). */ goto retry; return -1; default: eprintf("%s (oid: %" PRIx64 ", old_oid: %" PRIx64 ")\n", sd_strerror(rsp->result), oid, old_oid); return -1; } } static int read_object(struct sheepdog_access_info *ai, char *buf, uint64_t oid, int copies, unsigned int datalen, uint32_t offset, int *need_reload) { return read_write_object(ai, buf, oid, copies, datalen, offset, 0, 0, 0, 0, need_reload); } static int write_object(struct sheepdog_access_info *ai, char *buf, uint64_t oid, int copies, unsigned int datalen, uint32_t offset, int create, uint64_t old_oid, uint16_t flags, int *need_reload) { return read_write_object(ai, buf, oid, copies, datalen, offset, 1, create, old_oid, flags, need_reload); } static int sd_sync(struct sheepdog_access_info *ai) { int ret; struct sheepdog_obj_req hdr; struct sheepdog_obj_rsp *rsp = (struct sheepdog_obj_rsp *)&hdr; unsigned int wlen = 0, rlen = 0; memset(&hdr, 0, sizeof(hdr)); hdr.proto_ver = SD_PROTO_VER; hdr.opcode = SD_OP_FLUSH_VDI; hdr.oid = vid_to_vdi_oid(ai->inode.vdi_id); ret = do_req(ai, (struct sheepdog_req *)&hdr, NULL, &wlen, &rlen); if (ret) { eprintf("failed to send a request to the sheep\n"); return -1; } switch (rsp->result) { case SD_RES_SUCCESS: case SD_RES_INVALID_PARMS: /* * SD_RES_INVALID_PARMS means the sheep daemon doesn't use * object caches */ return 0; default: eprintf("%s\n", sd_strerror(rsp->result)); return -1; } } static int update_inode(struct sheepdog_access_info *ai, uint32_t min, uint32_t max) { int ret = 0, need_reload_inode = 0; uint64_t oid = vid_to_vdi_oid(ai->inode.vdi_id); uint32_t offset, data_len; if (max < min) goto end; goto update; reload: reload_inode(ai, 0); need_reload_inode = 0; update: offset = sizeof(ai->inode) - sizeof(ai->inode.data_vdi_id) + min * sizeof(ai->inode.data_vdi_id[0]); data_len = (max - min + 1) * sizeof(ai->inode.data_vdi_id[0]); ret = write_object(ai, (char *)&ai->inode + offset, oid, ai->inode.nr_copies, data_len, offset, 0, 0, 0, &need_reload_inode); if (ret < 0) eprintf("sync inode failed\n"); if (need_reload_inode) { dprintf("reloading inode is required in the path" " of update_inode()\n"); goto reload; } end: return ret; } static int is_refresh_required(struct sheepdog_access_info *ai) /* * 0: refresh isn't required * 1: refresh is required */ { uint64_t inode_oid = vid_to_vdi_oid(ai->inode.vdi_id); char dummy; int need_reload_inode = 0; /* * Check inode of this tgtd is invaldiated or not. * The inode object is the only one object which always exists. */ read_object(ai, &dummy, inode_oid, ai->inode.nr_copies, sizeof(dummy), 0, &need_reload_inode); return need_reload_inode; } static int sd_io(struct sheepdog_access_info *ai, int write, char *buf, int len, uint64_t offset) { uint32_t vid; uint32_t object_size = (UINT32_C(1) << ai->inode.block_size_shift); unsigned long idx = offset / object_size; unsigned long max = (offset + len + (object_size - 1)) / object_size; unsigned obj_offset = offset % object_size; size_t orig_size, size, rest = len; int ret = 0, create; uint64_t oid, old_oid; uint16_t flags = 0; int need_update_inode = 0, need_reload_inode; int nr_copies = ai->inode.nr_copies; int need_write_lock, check_idx; int read_reload_snap = 0; uint32_t min_dirty_data_idx = UINT32_MAX, max_dirty_data_idx = 0; goto do_req; reload_in_read_path: pthread_rwlock_unlock(&ai->inode_lock); /* unlock current read lock */ pthread_rwlock_wrlock(&ai->inode_lock); ret = reload_inode(ai, read_reload_snap); if (ret) { eprintf("failed to reload in read path\n"); goto out; } pthread_rwlock_unlock(&ai->inode_lock); do_req: need_write_lock = 0; vid = ai->inode.vdi_id; for (check_idx = idx; check_idx < max; check_idx++) { if (ai->inode.data_vdi_id[check_idx] == vid) continue; need_write_lock = 1; break; } if (need_write_lock) pthread_rwlock_wrlock(&ai->inode_lock); else pthread_rwlock_rdlock(&ai->inode_lock); for (; idx < max; idx++) { orig_size = size; size = object_size - obj_offset; size = min_t(size_t, size, rest); retry: vid = ai->inode.vdi_id; oid = vid_to_data_oid(ai->inode.data_vdi_id[idx], idx); old_oid = 0; if (write) { /* * tgt doesn't affect semantics of caching, so we can * always turn on cache of sheep layer */ flags = SD_FLAG_CMD_CACHE; create = 0; if (ai->inode.data_vdi_id[idx] != vid) { create = 1; if (ai->inode.data_vdi_id[idx]) { /* COW */ old_oid = oid; flags |= SD_FLAG_CMD_COW; } oid = vid_to_data_oid(ai->inode.vdi_id, idx); } need_reload_inode = 0; ret = write_object(ai, buf + (len - rest), oid, nr_copies, size, obj_offset, create, old_oid, flags, &need_reload_inode); if (!ret) { if (need_reload_inode) { /* If need_reload_inode is 1, * snapshot was created. * If it is 2, inode object is * invalidated */ ret = reload_inode(ai, need_reload_inode == 1); if (!ret) goto retry; } if (create) { min_dirty_data_idx = min_t(uint32_t, idx, min_dirty_data_idx); max_dirty_data_idx = max_t(uint32_t, idx, max_dirty_data_idx); ai->inode.data_vdi_id[idx] = vid; need_update_inode = 1; create = 0; } } } else { if (!ai->inode.data_vdi_id[idx]) { int check = is_refresh_required(ai); if (!check) { memset(buf, 0, size); goto done; } else { dprintf("reload in read path for not"\ " written area\n"); size = orig_size; read_reload_snap = need_reload_inode == 1; goto reload_in_read_path; } } need_reload_inode = 0; ret = read_object(ai, buf + (len - rest), oid, nr_copies, size, obj_offset, &need_reload_inode); if (need_reload_inode) { dprintf("reload in ordinal read path\n"); size = orig_size; read_reload_snap = need_reload_inode == 1; goto reload_in_read_path; } } if (ret) { eprintf("%lu %d\n", idx, ret); goto out; } done: rest -= size; obj_offset = 0; } if (need_update_inode) ret = update_inode(ai, min_dirty_data_idx, max_dirty_data_idx); out: pthread_rwlock_unlock(&ai->inode_lock); return ret; } static int find_vdi_name(struct sheepdog_access_info *ai, char *filename, uint32_t snapid, char *tag, uint32_t *vid, int for_snapshot) { int ret; struct sheepdog_vdi_req hdr; struct sheepdog_vdi_rsp *rsp = (struct sheepdog_vdi_rsp *)&hdr; unsigned int wlen, rlen = 0; char buf[SD_MAX_VDI_LEN + SD_MAX_VDI_TAG_LEN]; memset(buf, 0, sizeof(buf)); strncpy(buf, filename, SD_MAX_VDI_LEN - 1); strncpy(buf + SD_MAX_VDI_LEN, tag, SD_MAX_VDI_TAG_LEN - 1); memset(&hdr, 0, sizeof(hdr)); if (for_snapshot) hdr.opcode = SD_OP_GET_VDI_INFO; else hdr.opcode = SD_OP_LOCK_VDI; hdr.type = LOCK_TYPE_SHARED; wlen = SD_MAX_VDI_LEN + SD_MAX_VDI_TAG_LEN; hdr.proto_ver = SD_PROTO_VER; hdr.data_length = wlen; hdr.snapid = snapid; hdr.flags = SD_FLAG_CMD_WRITE; ret = do_req(ai, (struct sheepdog_req *)&hdr, buf, &wlen, &rlen); if (ret) { ret = -1; goto out; } if (rsp->result != SD_RES_SUCCESS) { eprintf("cannot get vdi info, %s, %s %d %s\n", sd_strerror(rsp->result), filename, snapid, tag); ret = -1; goto out; } *vid = rsp->vdi_id; ret = 0; out: return ret; } static int sd_open(struct sheepdog_access_info *ai, char *filename, int flags) { int ret = 0, i, len, fd, need_reload = 0; uint32_t vid = 0; char *orig_filename; char vdi_name[SD_MAX_VDI_LEN + 1]; char *saveptr = NULL, *result; enum { EXPECT_PROTO, EXPECT_PATH, EXPECT_HOST, EXPECT_PORT, EXPECT_VDI, EXPECT_NOTHING, } parse_state = EXPECT_PROTO; memset(vdi_name, 0, sizeof(vdi_name)); orig_filename = strdup(filename); if (!orig_filename) { eprintf("saving original filename failed\n"); return -1; } /* * expected form of filename: * * unix:: * tcp::: */ result = strtok_r(filename, ":", &saveptr); do { switch (parse_state) { case EXPECT_PROTO: if (!strcmp("unix", result)) { ai->is_unix = 1; parse_state = EXPECT_PATH; } else if (!strcmp("tcp", result)) { ai->is_unix = 0; parse_state = EXPECT_HOST; } else { eprintf("unknown protocol of sheepdog vdi:"\ " %s\n", result); ret = -1; goto out; } break; case EXPECT_PATH: strncpy(ai->uds_path, result, UNIX_PATH_MAX - 1); parse_state = EXPECT_VDI; break; case EXPECT_HOST: strncpy(ai->hostname, result, HOST_NAME_MAX); parse_state = EXPECT_PORT; break; case EXPECT_PORT: len = strlen(result); for (i = 0; i < len; i++) { if (!isdigit(result[i])) { eprintf("invalid tcp port number:"\ " %s\n", result); ret = -1; goto out; } } ai->port = atoi(result); parse_state = EXPECT_VDI; break; case EXPECT_VDI: strncpy(vdi_name, result, SD_MAX_VDI_LEN); parse_state = EXPECT_NOTHING; break; case EXPECT_NOTHING: eprintf("invalid VDI path of sheepdog, unexpected"\ " token: %s (entire: %s)\n", result, orig_filename); ret = -1; goto out; default: eprintf("BUG: invalid state of parser: %d\n", parse_state); exit(1); } } while ((result = strtok_r(NULL, ":", &saveptr)) != NULL); if (parse_state != EXPECT_NOTHING) { eprintf("invalid VDI path of sheepdog: %s (state: %d)\n", orig_filename, parse_state); ret = -1; goto out; } dprintf("protocol: %s\n", ai->is_unix ? "unix" : "tcp"); if (ai->is_unix) dprintf("path of unix domain socket: %s\n", ai->uds_path); else dprintf("hostname: %s, port: %d\n", ai->hostname, ai->port); /* * test connection for validating command line option * * if this step is skipped, the main thread of tgtd will try to * reconnect to sheep process forever */ fd = ai->is_unix ? connect_to_sdog_unix(ai->uds_path) : connect_to_sdog_tcp(ai->hostname, ai->port); if (fd < 0) { eprintf("connecting to sheep process failed, "\ "please verify the --backing-store option: %s", orig_filename); ret = -1; goto out; } close(fd); /* we don't need this connection */ dprintf("VDI name: %s\n", vdi_name); ret = find_vdi_name(ai, vdi_name, 0, "", &vid, 0); if (ret) goto out; ret = read_object(ai, (char *)&ai->inode, vid_to_vdi_oid(vid), 0, SD_INODE_SIZE, 0, &need_reload); if (ret) goto out; ret = 0; INIT_LIST_HEAD(&ai->inflight_list_head); pthread_mutex_init(&ai->inflight_list_mutex, NULL); pthread_cond_init(&ai->inflight_list_cond, NULL); out: strcpy(filename, orig_filename); free(orig_filename); return ret; } static void sd_close(struct sheepdog_access_info *ai) { struct sheepdog_vdi_req hdr; struct sheepdog_vdi_rsp *rsp = (struct sheepdog_vdi_rsp *)&hdr; unsigned int wlen = 0, rlen = 0; int ret; memset(&hdr, 0, sizeof(hdr)); hdr.opcode = SD_OP_RELEASE_VDI; hdr.type = LOCK_TYPE_SHARED; hdr.vdi_id = ai->inode.vdi_id; ret = do_req(ai, (struct sheepdog_req *)&hdr, NULL, &wlen, &rlen); if (!ret && rsp->result != SD_RES_SUCCESS && rsp->result != SD_RES_VDI_NOT_LOCKED) eprintf("%s, %s", sd_strerror(rsp->result), ai->inode.name); } static void set_medium_error(int *result, uint8_t *key, uint16_t *asc) { *result = SAM_STAT_CHECK_CONDITION; *key = MEDIUM_ERROR; *asc = ASC_READ_ERROR; } struct inflight_thread { unsigned long min_idx, max_idx; struct list_head list; }; static void inflight_block(struct sheepdog_access_info *ai, struct inflight_thread *myself) { struct inflight_thread *inflight; pthread_mutex_lock(&ai->inflight_list_mutex); retry: list_for_each_entry(inflight, &ai->inflight_list_head, list) { if (!(myself->max_idx < inflight->min_idx || inflight->max_idx < myself->min_idx)) { pthread_cond_wait(&ai->inflight_list_cond, &ai->inflight_list_mutex); goto retry; } } list_add_tail(&myself->list, &ai->inflight_list_head); pthread_mutex_unlock(&ai->inflight_list_mutex); } void inflight_release(struct sheepdog_access_info *ai, struct inflight_thread *myself) { pthread_mutex_lock(&ai->inflight_list_mutex); list_del(&myself->list); pthread_mutex_unlock(&ai->inflight_list_mutex); pthread_cond_signal(&ai->inflight_list_cond); } static void bs_sheepdog_request(struct scsi_cmd *cmd) { int ret = 0; uint32_t length = 0; int result = SAM_STAT_GOOD; uint8_t key = 0; uint16_t asc = 0; struct bs_thread_info *info = BS_THREAD_I(cmd->dev); struct sheepdog_access_info *ai = (struct sheepdog_access_info *)(info + 1); uint32_t object_size = (UINT32_C(1) << ai->inode.block_size_shift); struct inflight_thread myself; int inflight = 0; memset(&myself, 0, sizeof(myself)); INIT_LIST_HEAD(&myself.list); switch (cmd->scb[0]) { case SYNCHRONIZE_CACHE: case SYNCHRONIZE_CACHE_16: ret = sd_sync(ai); if (ret) set_medium_error(&result, &key, &asc); break; case WRITE_6: case WRITE_10: case WRITE_12: case WRITE_16: length = scsi_get_out_length(cmd); myself.min_idx = cmd->offset / object_size; myself.max_idx = (cmd->offset + length + (object_size - 1)) / object_size; inflight_block(ai, &myself); inflight = 1; ret = sd_io(ai, 1, scsi_get_out_buffer(cmd), length, cmd->offset); if (ret) set_medium_error(&result, &key, &asc); break; case READ_6: case READ_10: case READ_12: case READ_16: length = scsi_get_in_length(cmd); myself.min_idx = cmd->offset / object_size; myself.max_idx = (cmd->offset + length + (object_size - 1)) / object_size; inflight_block(ai, &myself); inflight = 1; ret = sd_io(ai, 0, scsi_get_in_buffer(cmd), length, cmd->offset); if (ret) set_medium_error(&result, &key, &asc); break; default: eprintf("cmd->scb[0]: %x\n", cmd->scb[0]); break; } dprintf("io done %p %x %d %u\n", cmd, cmd->scb[0], ret, length); scsi_set_result(cmd, result); if (result != SAM_STAT_GOOD) { eprintf("io error %p %x %d %d %" PRIu64 ", %m\n", cmd, cmd->scb[0], ret, length, cmd->offset); sense_data_build(cmd, key, asc); } if (inflight) inflight_release(ai, &myself); } static int bs_sheepdog_open(struct scsi_lu *lu, char *path, int *fd, uint64_t *size) { struct bs_thread_info *info = BS_THREAD_I(lu); struct sheepdog_access_info *ai = (struct sheepdog_access_info *)(info + 1); int ret; ret = sd_open(ai, path, 0); if (ret) return ret; *size = ai->inode.vdi_size; return 0; } static void bs_sheepdog_close(struct scsi_lu *lu) { struct bs_thread_info *info = BS_THREAD_I(lu); struct sheepdog_access_info *ai = (struct sheepdog_access_info *)(info + 1); sd_close(ai); } static tgtadm_err bs_sheepdog_init(struct scsi_lu *lu, char *bsopts) { struct bs_thread_info *info = BS_THREAD_I(lu); struct sheepdog_access_info *ai = (struct sheepdog_access_info *)(info + 1); INIT_LIST_HEAD(&ai->fd_list_head); pthread_rwlock_init(&ai->fd_list_lock, NULL); pthread_rwlock_init(&ai->inode_lock, NULL); pthread_mutex_init(&ai->inode_version_mutex, NULL); return bs_thread_open(info, bs_sheepdog_request, nr_iothreads); } static void bs_sheepdog_exit(struct scsi_lu *lu) { struct bs_thread_info *info = BS_THREAD_I(lu); struct sheepdog_access_info *ai = (struct sheepdog_access_info *)(info + 1); struct sheepdog_fd_list *p, *next; bs_thread_close(info); list_for_each_entry_safe(p, next, &ai->fd_list_head, list) { close(p->fd); list_del(&p->list); free(p); } pthread_rwlock_destroy(&ai->fd_list_lock); pthread_rwlock_destroy(&ai->inode_lock); dprintf("cleaned logical unit %p safely\n", lu); } static struct backingstore_template sheepdog_bst = { .bs_name = "sheepdog", .bs_datasize = sizeof(struct bs_thread_info) + sizeof(struct sheepdog_access_info), .bs_open = bs_sheepdog_open, .bs_close = bs_sheepdog_close, .bs_init = bs_sheepdog_init, .bs_exit = bs_sheepdog_exit, .bs_cmd_submit = bs_thread_cmd_submit, }; __attribute__((constructor)) static void __constructor(void) { unsigned char opcodes[] = { ALLOW_MEDIUM_REMOVAL, FORMAT_UNIT, INQUIRY, MAINT_PROTOCOL_IN, MODE_SELECT, MODE_SELECT_10, MODE_SENSE, MODE_SENSE_10, PERSISTENT_RESERVE_IN, PERSISTENT_RESERVE_OUT, READ_10, READ_12, READ_16, READ_6, READ_CAPACITY, RELEASE, REPORT_LUNS, REQUEST_SENSE, RESERVE, SEND_DIAGNOSTIC, SERVICE_ACTION_IN, START_STOP, SYNCHRONIZE_CACHE, SYNCHRONIZE_CACHE_16, TEST_UNIT_READY, WRITE_10, WRITE_12, WRITE_16, WRITE_6 }; bs_create_opcode_map(&sheepdog_bst, opcodes, ARRAY_SIZE(opcodes)); register_backingstore_template(&sheepdog_bst); } tgt-1.0.85/usr/bs_ssc.c000066400000000000000000000413341435417276200146540ustar00rootroot00000000000000/* * SCSI stream command processing backing store * * Copyright (C) 2008 Mark Harvey markh794@gmail.com * * 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, version 2 of the * License. * * 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 "list.h" #include "util.h" #include "tgtd.h" #include "scsi.h" #include "bs_thread.h" #include "media.h" #include "bs_ssc.h" #include "ssc.h" #include "libssc.h" #define SENSE_FILEMARK 0x80 #define SENSE_EOM 0x40 #define SENSE_ILI 0X20 static void ssc_sense_data_build(struct scsi_cmd *cmd, uint8_t key, uint16_t asc, uint8_t *info, int info_len) { /* TODO: support descriptor format */ sense_data_build(cmd, key, asc); if (info_len) { memcpy(cmd->sense_buffer + 3, info, 4); cmd->sense_buffer[0] |= 0x80; } } static inline uint32_t ssc_get_block_length(struct scsi_lu *lu) { return get_unaligned_be24(lu->mode_block_descriptor + 5); } /* I'm sure there is a more efficent method then this */ static int32_t be24_to_2comp(uint8_t *c) { int count; count = (c[0] << 16) | (c[1] << 8) | c[2]; if (c[0] & 0x80) count += (0xff << 24); return count; } static int skip_next_header(struct scsi_lu *lu) { struct ssc_info *ssc = dtype_priv(lu); struct blk_header_info *h = &ssc->c_blk; return ssc_read_blkhdr(lu->fd, h, h->next); } static int skip_prev_header(struct scsi_lu *lu) { ssize_t rd; struct ssc_info *ssc = dtype_priv(lu); struct blk_header_info *h = &ssc->c_blk; rd = ssc_read_blkhdr(lu->fd, h, h->prev); if (rd) return 1; if (h->blk_type == BLK_BOT) return skip_next_header(lu); return 0; } static int resp_rewind(struct scsi_lu *lu) { int fd; ssize_t rd; struct ssc_info *ssc = dtype_priv(lu); struct blk_header_info *h = &ssc->c_blk; fd = lu->fd; dprintf("*** Backing store fd: %s %d %d ***\n", lu->path, lu->fd, fd); rd = ssc_read_blkhdr(fd, h, 0); if (rd) { eprintf("fail to read the first block header\n"); return 1; } return skip_next_header(lu); } static uint64_t current_size(struct scsi_cmd *cmd) { struct ssc_info *ssc = dtype_priv(cmd->dev); return ssc->c_blk.curr; } static int append_blk(struct scsi_cmd *cmd, uint8_t *data, int size, int orig_sz, int type) { int fd; struct ssc_info *ssc = dtype_priv(cmd->dev); struct blk_header_info c, *curr = &c; struct blk_header_info e, *eod = &e; ssize_t ret; fd = cmd->dev->fd; *curr = ssc->c_blk; dprintf("B4 update : prev/curr/next" " <%" PRId64 "/%" PRId64 "/%" PRId64 "> type: %d," " num: %" PRIx64 ", ondisk sz: %d, about to write %d\n", curr->prev, curr->curr, curr->next, curr->blk_type, curr->blk_num, curr->ondisk_sz, size); curr->next = curr->curr + size + SSC_BLK_HDR_SIZE; curr->blk_type = type; curr->ondisk_sz = size; curr->blk_sz = orig_sz; eod->prev = curr->curr; eod->curr = curr->next; eod->next = curr->next; eod->ondisk_sz = 0; eod->blk_sz = 0; eod->blk_type = BLK_EOD; eod->blk_num = curr->blk_num + 1; memcpy(&ssc->c_blk, eod, sizeof(*eod)); dprintf("After update : prev/curr/next <%" PRId64 "/%" PRId64 "/%" PRId64 "> type: %d, num: %" PRIx64 ", ondisk sz: %d\n", curr->prev, curr->curr, curr->next, curr->blk_type, curr->blk_num, curr->ondisk_sz); dprintf("EOD blk header: prev/curr/next <%" PRId64 "/%" PRId64 "/%" PRId64 "> type: %d, num: %" PRIx64 ", ondisk sz: %d\n", eod->prev, eod->curr, eod->next, eod->blk_type, eod->blk_num, eod->ondisk_sz); /* Rewrite previous header with updated positioning info */ ret = ssc_write_blkhdr(fd, curr, curr->curr); if (ret) { eprintf("Rewrite of blk header failed: %m\n"); sense_data_build(cmd, MEDIUM_ERROR, ASC_WRITE_ERROR); return SAM_STAT_CHECK_CONDITION; } /* Write new EOD blk header */ ret = ssc_write_blkhdr(fd, eod, eod->curr); if (ret) { eprintf("Write of EOD blk header failed: %m\n"); sense_data_build(cmd, MEDIUM_ERROR, ASC_WRITE_ERROR); return SAM_STAT_CHECK_CONDITION; } /* Write any data */ if (size) { ret = pwrite64(fd, data, size, curr->curr + SSC_BLK_HDR_SIZE); if (ret != size) { eprintf("Write of data failed: %m\n"); sense_data_build(cmd, MEDIUM_ERROR, ASC_WRITE_ERROR); return SAM_STAT_CHECK_CONDITION; } } /* Write new EOD blk header */ return SAM_STAT_GOOD; } static int space_filemark_reverse(struct scsi_cmd *cmd, int32_t count) { struct ssc_info *ssc = dtype_priv(cmd->dev); struct blk_header_info *h = &ssc->c_blk; count *= -1; again: if (!h->prev) { sense_data_build(cmd, NO_SENSE, ASC_BOM); return SAM_STAT_CHECK_CONDITION; } if (h->blk_type == BLK_FILEMARK) count--; if (skip_prev_header(cmd->dev)) { sense_data_build(cmd, MEDIUM_ERROR, ASC_MEDIUM_FORMAT_CORRUPT); return SAM_STAT_CHECK_CONDITION; } if (count > 0) goto again; return SAM_STAT_GOOD; } static int space_filemark_forward(struct scsi_cmd *cmd, int32_t count) { struct ssc_info *ssc = dtype_priv(cmd->dev); struct blk_header_info *h = &ssc->c_blk; again: if (h->blk_type == BLK_EOD) { sense_data_build(cmd, NO_SENSE, ASC_END_OF_DATA); return SAM_STAT_CHECK_CONDITION; } if (h->blk_type == BLK_FILEMARK) count--; if (skip_next_header(cmd->dev)) { sense_data_build(cmd, MEDIUM_ERROR, ASC_MEDIUM_FORMAT_CORRUPT); return SAM_STAT_CHECK_CONDITION; } if (count > 0) goto again; return SAM_STAT_GOOD; } static int space_filemark(struct scsi_cmd *cmd, int32_t count) { struct ssc_info *ssc = dtype_priv(cmd->dev); struct blk_header_info *h = &ssc->c_blk; int result; dprintf("*** space %d filemarks, %" PRIu64 "\n", count, h->curr); if (count > 0) result = space_filemark_forward(cmd, count); else if (count < 0) result = space_filemark_reverse(cmd, count); else result = SAM_STAT_GOOD; dprintf("%" PRIu64 "\n", h->curr); return result; } static int space_blocks(struct scsi_cmd *cmd, int32_t count) { struct ssc_info *ssc = dtype_priv(cmd->dev); struct blk_header_info *h = &ssc->c_blk; dprintf("*** space %d blocks, %" PRIu64 "\n", count, h->curr); while (count != 0) { if (count > 0) { if (skip_next_header(cmd->dev)) { sense_data_build(cmd, MEDIUM_ERROR, ASC_MEDIUM_FORMAT_CORRUPT); return SAM_STAT_CHECK_CONDITION; } if (h->blk_type == BLK_EOD) { sense_data_build(cmd, NO_SENSE, ASC_END_OF_DATA); return SAM_STAT_CHECK_CONDITION; } count--; } else { if (skip_prev_header(cmd->dev)) { sense_data_build(cmd, MEDIUM_ERROR, ASC_MEDIUM_FORMAT_CORRUPT); return SAM_STAT_CHECK_CONDITION; } if (h->blk_type == BLK_BOT) { /* Can't leave at BOT */ skip_next_header(cmd->dev); sense_data_build(cmd, NO_SENSE, ASC_BOM); return SAM_STAT_CHECK_CONDITION; } count++; } } dprintf("%" PRIu64 "\n", h->curr); return SAM_STAT_GOOD; } static int resp_var_read(struct scsi_cmd *cmd, uint8_t *buf, uint32_t length, int *transferred) { struct ssc_info *ssc = dtype_priv(cmd->dev); struct blk_header_info *h = &ssc->c_blk; int ret = 0, result = SAM_STAT_GOOD; length = min(length, get_unaligned_be24(&cmd->scb[2])); *transferred = 0; if (length != h->blk_sz) { uint8_t info[4]; int val = length - h->blk_sz; put_unaligned_be32(val, info); if (h->blk_type == BLK_EOD) sense_data_build(cmd, 0x40 | BLANK_CHECK, NO_ADDITIONAL_SENSE); else if (h->blk_type == BLK_FILEMARK) ssc_sense_data_build(cmd, NO_SENSE | SENSE_FILEMARK, ASC_MARK, info, sizeof(info)); else ssc_sense_data_build(cmd, NO_SENSE | 0x20, NO_ADDITIONAL_SENSE, info, sizeof(info)); if (length > h->blk_sz) scsi_set_in_resid_by_actual(cmd, length - h->blk_sz); else scsi_set_in_resid_by_actual(cmd, 0); length = min(length, h->blk_sz); result = SAM_STAT_CHECK_CONDITION; if (!length) { if (h->blk_type == BLK_FILEMARK) goto skip_and_out; goto out; } } ret = pread64(cmd->dev->fd, buf, length, h->curr + SSC_BLK_HDR_SIZE); if (ret != length) { sense_data_build(cmd, MEDIUM_ERROR, ASC_READ_ERROR); result = SAM_STAT_CHECK_CONDITION; goto out; } *transferred = length; skip_and_out: ret = skip_next_header(cmd->dev); if (ret) { sense_data_build(cmd, MEDIUM_ERROR, ASC_MEDIUM_FORMAT_CORRUPT); result = SAM_STAT_CHECK_CONDITION; } out: return result; } static int resp_fixed_read(struct scsi_cmd *cmd, uint8_t *buf, uint32_t length, int *transferred) { struct ssc_info *ssc = dtype_priv(cmd->dev); struct blk_header_info *h = &ssc->c_blk; int i, ret, result = SAM_STAT_GOOD; int count; ssize_t residue; int fd; uint32_t block_length = ssc_get_block_length(cmd->dev); count = get_unaligned_be24(&cmd->scb[2]); fd = cmd->dev->fd; ret = 0; for (i = 0; i < count; i++) { if (h->blk_type == BLK_FILEMARK) { uint8_t info[4]; eprintf("Oops - found filemark\n"); put_unaligned_be32(count - i, info); ssc_sense_data_build(cmd, NO_SENSE | SENSE_FILEMARK, ASC_MARK, info, sizeof(info)); skip_next_header(cmd->dev); result = SAM_STAT_CHECK_CONDITION; goto out; } if (block_length != h->blk_sz) { eprintf("block size mismatch %d vs %d\n", block_length, h->blk_sz); sense_data_build(cmd, MEDIUM_ERROR, ASC_MEDIUM_FORMAT_CORRUPT); result = SAM_STAT_CHECK_CONDITION; goto out; } residue = pread64(fd, buf, block_length, h->curr + SSC_BLK_HDR_SIZE); if (block_length != residue) { eprintf("Could only read %d bytes, not %d\n", (int)residue, block_length); sense_data_build(cmd, MEDIUM_ERROR, ASC_READ_ERROR); result = SAM_STAT_CHECK_CONDITION; goto out; } ret += block_length; buf += block_length; if (skip_next_header(cmd->dev)) { eprintf("Could not read next header\n"); sense_data_build(cmd, MEDIUM_ERROR, ASC_MEDIUM_FORMAT_CORRUPT); result = SAM_STAT_CHECK_CONDITION; goto out; } } *transferred = ret; out: return result; } static void tape_rdwr_request(struct scsi_cmd *cmd) { struct ssc_info *ssc = dtype_priv(cmd->dev); struct blk_header_info *h = &ssc->c_blk; int ret, code; uint32_t length, i; int result = SAM_STAT_GOOD; uint8_t *buf; int32_t count; int8_t fixed; int8_t sti; uint32_t block_length = ssc_get_block_length(cmd->dev); ret = 0; length = 0; i = 0; code = 0; ssc = dtype_priv(cmd->dev); switch (cmd->scb[0]) { case REZERO_UNIT: dprintf("**** Rewind ****\n"); if (resp_rewind(cmd->dev)) { sense_data_build(cmd, MEDIUM_ERROR, ASC_SEQUENTIAL_POSITION_ERR); result = SAM_STAT_CHECK_CONDITION; } break; case WRITE_FILEMARKS: ret = get_unaligned_be24(&cmd->scb[2]); dprintf("*** Write %d filemark%s ***\n", ret, ((ret > 1) || (ret < 0)) ? "s" : ""); for (i = 0; i < ret; i++) append_blk(cmd, scsi_get_out_buffer(cmd), 0, 0, BLK_FILEMARK); fsync(cmd->dev->fd); break; case READ_6: fixed = cmd->scb[1] & 1; sti = cmd->scb[1] & 2; if (fixed && sti) { sense_data_build(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB); result = SAM_STAT_CHECK_CONDITION; break; } length = scsi_get_in_length(cmd); count = get_unaligned_be24(&cmd->scb[2]); buf = scsi_get_in_buffer(cmd); dprintf("*** READ_6: length %d, count %d, fixed block %s," " %" PRIu64 " %d\n", length, count, (fixed) ? "Yes" : "No", h->curr, sti); if (fixed) result = resp_fixed_read(cmd, buf, length, &ret); else result = resp_var_read(cmd, buf, length, &ret); eprintf("Executed READ_6, Read %d bytes, %" PRIu64 "\n", ret, h->curr); break; case WRITE_6: fixed = cmd->scb[1] & 1; buf = scsi_get_out_buffer(cmd); count = get_unaligned_be24(&cmd->scb[2]); length = scsi_get_out_length(cmd); if (!fixed) { block_length = length; count = 1; } for (i = 0, ret = 0; i < count; i++) { if (append_blk(cmd, buf, block_length, block_length, BLK_UNCOMPRESS_DATA)) { sense_data_build(cmd, MEDIUM_ERROR, ASC_WRITE_ERROR); result = SAM_STAT_CHECK_CONDITION; break; } buf += block_length; ret += block_length; } dprintf("*** WRITE_6 count: %d, length: %d, ret: %d, fixed: %s," " ssc->blk_sz: %d\n", count, length, ret, (fixed) ? "Yes" : "No", block_length); /* Check for end of media */ if (current_size(cmd) > ssc->mam.max_capacity) { sense_data_build(cmd, NO_SENSE|SENSE_EOM, NO_ADDITIONAL_SENSE); result = SAM_STAT_CHECK_CONDITION; break; } if (ret != length) { sense_data_build(cmd, MEDIUM_ERROR, ASC_WRITE_ERROR); result = SAM_STAT_CHECK_CONDITION; } break; case SPACE: code = cmd->scb[1] & 0xf; count = be24_to_2comp(&cmd->scb[2]); if (code == 0) { /* Logical Blocks */ result = space_blocks(cmd, count); break; } else if (code == 1) { /* Filemarks */ result = space_filemark(cmd, count); break; } else if (code == 3) { /* End of data */ while (h->blk_type != BLK_EOD) if (skip_next_header(cmd->dev)) { sense_data_build(cmd, MEDIUM_ERROR, ASC_MEDIUM_FORMAT_CORRUPT); result = SAM_STAT_CHECK_CONDITION; break; } } else { /* Unsupported */ sense_data_build(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB); result = SAM_STAT_CHECK_CONDITION; } break; case READ_POSITION: { int service_action = cmd->scb[1] & 0x1f; uint8_t *data = scsi_get_in_buffer(cmd); int len = scsi_get_in_length(cmd); dprintf("Size of in_buffer = %d\n", len); dprintf("Sizeof(buf): %zd\n", sizeof(buf)); dprintf("service action: 0x%02x\n", service_action); if (service_action == 0) { /* Short form - block ID */ memset(data, 0, 20); data[0] = 20; } else if (service_action == 1) { /* Short form - vendor uniq */ memset(data, 0, 20); data[0] = 20; } else if (service_action == 6) { /* Long form */ memset(data, 0, 32); data[0] = 32; } else { sense_data_build(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB); result = SAM_STAT_CHECK_CONDITION; } break; } default: eprintf("Unknown op code - should never see this\n"); sense_data_build(cmd, ILLEGAL_REQUEST, ASC_INVALID_OP_CODE); result = SAM_STAT_CHECK_CONDITION; break; } dprintf("io done %p %x %d %u\n", cmd, cmd->scb[0], ret, length); scsi_set_result(cmd, result); if (result != SAM_STAT_GOOD) eprintf("io error %p %x %d %d %" PRIu64 ", %m\n", cmd, cmd->scb[0], ret, length, cmd->offset); } static tgtadm_err bs_ssc_init(struct scsi_lu *lu, char *bsopts) { struct bs_thread_info *info = BS_THREAD_I(lu); return bs_thread_open(info, tape_rdwr_request, 1); } static int bs_ssc_open(struct scsi_lu *lu, char *path, int *fd, uint64_t *size) { struct ssc_info *ssc; char *cart = NULL; ssize_t rd; int ret; struct blk_header_info *h; ssc = dtype_priv(lu); *fd = backed_file_open(path, O_RDWR | O_LARGEFILE, size, NULL); if (*fd < 0) { eprintf("Could not open %s %m\n", path); return *fd; } eprintf("Backing store %s, %d\n", path, *fd); if (*size < SSC_BLK_HDR_SIZE + sizeof(struct MAM)) { eprintf("backing file too small - not correct media format\n"); return -1; } h = &ssc->c_blk; /* Can't call 'resp_rewind() at this point as lu data not * setup */ rd = ssc_read_blkhdr(*fd, h, 0); if (rd) { eprintf("Failed to read complete blk header: %d %m\n", (int)rd); return -1; } ret = ssc_read_mam_info(*fd, &ssc->mam); if (ret) { eprintf("Failed to read MAM: %d %m\n", (int)rd); return -1; } rd = ssc_read_blkhdr(*fd, h, h->next); if (rd) { eprintf("Failed to read complete blk header: %d %m\n", (int)rd); return -1; } switch (ssc->mam.medium_type) { case CART_CLEAN: cart = "Cleaning cartridge"; break; case CART_DATA: cart = "data cartridge"; break; case CART_WORM: cart = "WORM cartridge"; break; default: cart = "Unknown cartridge type"; break; } dprintf("Media size: %d, media type: %s\n", h->blk_sz, cart); return 0; } static void bs_ssc_exit(struct scsi_lu *lu) { struct bs_thread_info *info = BS_THREAD_I(lu); bs_thread_close(info); } static void bs_ssc_close(struct scsi_lu *lu) { dprintf("##### Close #####\n"); close(lu->fd); } static struct backingstore_template ssc_bst = { .bs_name = "ssc", .bs_datasize = sizeof(struct bs_thread_info), .bs_init = bs_ssc_init, .bs_exit = bs_ssc_exit, .bs_open = bs_ssc_open, .bs_close = bs_ssc_close, .bs_cmd_submit = bs_thread_cmd_submit, }; __attribute__((constructor)) static void bs_ssc_constructor(void) { register_backingstore_template(&ssc_bst); } tgt-1.0.85/usr/bs_ssc.h000066400000000000000000000045221435417276200146570ustar00rootroot00000000000000#ifndef __BS_SSC_H #define __BS_SSC_H /* * structure of a 'poor mans double linked list' on disk. */ /** * Block type definitations * * @BLK_NOOP: No Operation.. Dummy value * @BLK_UNCOMPRESS_DATA: If true, data block is uncompressed * @BLK_ENCRYPTED_DATA: If true, data block is encrypted * @BLK_FILEMARK: Represents a filemark * @BLK_SETMARK: Represents a setmark * @BLK_BOT: Represents a Beginning of Tape marker * @BLK_EOD: Represents an End of Data marker * * Defines for types of SSC data blocks */ #define BLK_NOOP 0x00000000 #define BLK_COMPRESSED_DATA 0x00000001 #define BLK_UNCOMPRESS_DATA 0x00000002 #define BLK_ENCRYPTED_DATA 0x00000004 #define BLK_BOT 0x00000010 #define BLK_EOD 0x00000020 #define BLK_FILEMARK 0x00000040 #define BLK_SETMARK 0x00000080 #define TGT_TAPE_VERSION 2 #define SSC_BLK_HDR_SIZE (sizeof(struct blk_header)) struct blk_header { uint8_t h_csum[4]; uint32_t ondisk_sz; uint32_t blk_sz; uint32_t blk_type; uint64_t blk_num; uint64_t prev; uint64_t curr; uint64_t next; }; /* * MAM (media access memory) structure based from IBM Ultrium SCSI * Reference WB1109-02 */ struct MAM { uint32_t tape_fmt_version; uint32_t __pad1; uint64_t remaining_capacity; uint64_t max_capacity; uint64_t TapeAlert; uint64_t load_count; uint64_t MAM_space_remaining; uint8_t assigning_organization_1[8]; uint8_t formatted_density_code; uint8_t __pad2[5]; uint8_t initialization_count[2]; uint8_t dev_make_serial_last_load[4][40]; uint64_t written_in_medium_life; uint64_t read_in_medium_life; uint64_t written_in_last_load; uint64_t read_in_last_load; uint8_t medium_manufacturer[8]; uint8_t medium_serial_number[32]; uint32_t medium_length; uint32_t medium_width; uint8_t assigning_organization_2[8]; uint8_t medium_density_code; uint8_t __pad3[7]; uint8_t medium_manufacture_date[8]; uint64_t MAM_capacity; uint8_t medium_type; uint8_t __pad4; uint16_t medium_type_information; uint8_t __pad5[4]; uint8_t application_vendor[8]; uint8_t application_name[32]; uint8_t application_version[8]; uint8_t user_medium_text_label[160]; uint8_t date_time_last_written[12]; uint8_t __pad6[3]; uint8_t localization_identifier; uint8_t barcode[32]; uint8_t owning_host_textual_name[80]; uint8_t media_pool[160]; uint8_t vendor_unique[256]; uint8_t dirty; uint8_t __reserved[7]; }; #endif tgt-1.0.85/usr/bs_thread.h000066400000000000000000000013561435417276200153400ustar00rootroot00000000000000typedef void (request_func_t) (struct scsi_cmd *); struct bs_thread_info { pthread_t *worker_thread; int nr_worker_threads; /* wokers sleep on this and signaled by tgtd */ pthread_cond_t pending_cond; /* locked by tgtd and workers */ pthread_mutex_t pending_lock; /* protected by pending_lock */ struct list_head pending_list; request_func_t *request_fn; }; static inline struct bs_thread_info *BS_THREAD_I(struct scsi_lu *lu) { return (struct bs_thread_info *) ((char *)lu + sizeof(*lu)); } extern tgtadm_err bs_thread_open(struct bs_thread_info *info, request_func_t *rfn, int nr_threads); extern void bs_thread_close(struct bs_thread_info *info); extern int bs_thread_cmd_submit(struct scsi_cmd *cmd); extern int nr_iothreads; tgt-1.0.85/usr/bsg.h000066400000000000000000000060301435417276200141520ustar00rootroot00000000000000#ifndef BSG_H #define BSG_H #include #define BSG_PROTOCOL_SCSI 0 #define BSG_SUB_PROTOCOL_SCSI_CMD 0 #define BSG_SUB_PROTOCOL_SCSI_TMF 1 #define BSG_SUB_PROTOCOL_SCSI_TRANSPORT 2 /* * For flags member below * sg.h sg_io_hdr also has bits defined for it's flags member. However * none of these bits are implemented/used by bsg. The bits below are * allocated to not conflict with sg.h ones anyway. */ #define BSG_FLAG_Q_AT_TAIL 0x10 /* default, == 0 at this bit, is Q_AT_HEAD */ struct sg_io_v4 { __s32 guard; /* [i] 'Q' to differentiate from v3 */ __u32 protocol; /* [i] 0 -> SCSI , .... */ __u32 subprotocol; /* [i] 0 -> SCSI command, 1 -> SCSI task management function, .... */ __u32 request_len; /* [i] in bytes */ __u64 request; /* [i], [*i] {SCSI: cdb} */ __u64 request_tag; /* [i] {SCSI: task tag (only if flagged)} */ __u32 request_attr; /* [i] {SCSI: task attribute} */ __u32 request_priority; /* [i] {SCSI: task priority} */ __u32 request_extra; /* [i] {spare, for padding} */ __u32 max_response_len; /* [i] in bytes */ __u64 response; /* [i], [*o] {SCSI: (auto)sense data} */ /* "dout_": data out (to device); "din_": data in (from device) */ __u32 dout_iovec_count; /* [i] 0 -> "flat" dout transfer else dout_xfer points to array of iovec */ __u32 dout_xfer_len; /* [i] bytes to be transferred to device */ __u32 din_iovec_count; /* [i] 0 -> "flat" din transfer */ __u32 din_xfer_len; /* [i] bytes to be transferred from device */ __u64 dout_xferp; /* [i], [*i] */ __u64 din_xferp; /* [i], [*o] */ __u32 timeout; /* [i] units: millisecond */ __u32 flags; /* [i] bit mask */ __u64 usr_ptr; /* [i->o] unused internally */ __u32 spare_in; /* [i] */ __u32 driver_status; /* [o] 0 -> ok */ __u32 transport_status; /* [o] 0 -> ok */ __u32 device_status; /* [o] {SCSI: command completion status} */ __u32 retry_delay; /* [o] {SCSI: status auxiliary information} */ __u32 info; /* [o] additional information */ __u32 duration; /* [o] time to complete, in milliseconds */ __u32 response_len; /* [o] bytes of response actually written */ __s32 din_resid; /* [o] din_xfer_len - actual_din_xfer_len */ __s32 dout_resid; /* [o] dout_xfer_len - actual_dout_xfer_len */ __u64 generated_tag; /* [o] {SCSI: transport generated task tag} */ __u32 spare_out; /* [o] */ __u32 padding; }; #ifdef __KERNEL__ #if defined(CONFIG_BLK_DEV_BSG) struct bsg_class_device { struct device *class_dev; struct device *parent; int minor; struct request_queue *queue; struct kref ref; void (*release)(struct device *); }; extern int bsg_register_queue(struct request_queue *q, struct device *parent, const char *name, void (*release)(struct device *)); extern void bsg_unregister_queue(struct request_queue *); #else static inline int bsg_register_queue(struct request_queue *q, struct device *parent, const char *name, void (*release)(struct device *)) { return 0; } static inline void bsg_unregister_queue(struct request_queue *q) { } #endif #endif /* __KERNEL__ */ #endif tgt-1.0.85/usr/concat_buf.c000066400000000000000000000041151435417276200154770ustar00rootroot00000000000000/* * concat_buf functions * appending formatted output to dynamically grown strings * * Copyright (C) 2011 Alexander Nezhinsky * * 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, version 2 of the * License. * * 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 "log.h" #include "util.h" void concat_buf_init(struct concat_buf *b) { b->streamf = open_memstream(&b->buf, &b->size); b->err = b->streamf ? 0 : errno; b->used = 0; } int concat_printf(struct concat_buf *b, const char *format, ...) { va_list args; int nprinted; if (!b->err) { va_start(args, format); nprinted = vfprintf(b->streamf, format, args); if (nprinted >= 0) b->used += nprinted; else { b->err = nprinted; fclose(b->streamf); b->streamf = NULL; } va_end(args); } return b->err; } const char *concat_delim(struct concat_buf *b, const char *delim) { return !b->used ? "" : delim; } int concat_buf_finish(struct concat_buf *b) { if (b->streamf) { fclose(b->streamf); b->streamf = NULL; if (b->size) b->size++; /* account for trailing NULL char */ } return b->err; } int concat_write(struct concat_buf *b, int fd, int offset) { concat_buf_finish(b); if (b->err) { errno = b->err; return -1; } if (b->size - offset > 0) return write(fd, b->buf + offset, b->size - offset); else { errno = EINVAL; return -1; } } void concat_buf_release(struct concat_buf *b) { concat_buf_finish(b); if (b->buf) { free(b->buf); memset(b, 0, sizeof(*b)); } } tgt-1.0.85/usr/crc32c.h000066400000000000000000000005761435417276200144670ustar00rootroot00000000000000#ifndef _LINUX_CRC32C_H #define _LINUX_CRC32C_H #include #include extern uint32_t crc32c_le(uint32_t crc, unsigned char const *address, size_t length); extern uint32_t crc32c_be(uint32_t crc, unsigned char const *address, size_t length); #define crc32c(seed, data, length) crc32c_le(seed, (unsigned char const *)data, length) #endif /* _LINUX_CRC32C_H */ tgt-1.0.85/usr/driver.c000066400000000000000000000032301435417276200146640ustar00rootroot00000000000000/* * driver routine * * Copyright (C) 2007 FUJITA Tomonori * Copyright (C) 2007 Mike Christie * * 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, version 2 of the * License. * * 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 "list.h" #include "util.h" #include "tgtd.h" #include "driver.h" #define MAX_NR_DRIVERS 32 struct tgt_driver *tgt_drivers[MAX_NR_DRIVERS] = { }; int get_driver_index(char *name) { int i; for (i = 0; tgt_drivers[i]; i++) { if (!strcmp(name, tgt_drivers[i]->name)) return i; } return -ENOENT; } int register_driver(struct tgt_driver *drv) { int i; for (i = 0; i < ARRAY_SIZE(tgt_drivers); i++) if (!tgt_drivers[i]) { drv->drv_state = DRIVER_REGD; tgt_drivers[i] = drv; return 0; } return -1; } const char *driver_state_name(struct tgt_driver *drv) { switch (drv->drv_state) { case DRIVER_REGD: return "uninitialized"; case DRIVER_INIT: return "ready"; case DRIVER_ERR: return "error"; case DRIVER_EXIT: return "stopped"; default: return "unsupported"; } } tgt-1.0.85/usr/driver.h000066400000000000000000000023671435417276200147030ustar00rootroot00000000000000#ifndef __DRIVER_H__ #define __DRIVER_H__ #include "tgtadm_error.h" enum tgt_driver_state { DRIVER_REGD = 0, /* just registered */ DRIVER_INIT, /* initialized ok */ DRIVER_ERR, /* failed to initialize */ DRIVER_EXIT /* exited */ }; struct tgt_driver { const char *name; enum tgt_driver_state drv_state; int (*init)(int, char *); void (*exit)(void); int (*target_create)(struct target *); void (*target_destroy)(int, int); int (*portal_create)(char *); int (*portal_destroy)(char *); int (*lu_create)(struct scsi_lu *); tgtadm_err (*update)(int, int, int ,uint64_t, uint64_t, uint32_t, char *); tgtadm_err (*show)(int, int, uint64_t, uint32_t, uint64_t, struct concat_buf *); tgtadm_err (*stat)(int, int, uint64_t, uint32_t, uint64_t, struct concat_buf *); uint64_t (*scsi_get_lun)(uint8_t *); int (*cmd_end_notify)(uint64_t nid, int result, struct scsi_cmd *); int (*mgmt_end_notify)(struct mgmt_req *); int (*transportid)(int, uint64_t, char *, int); const char *default_bst; struct list_head target_list; }; extern struct tgt_driver *tgt_drivers[]; extern int get_driver_index(char *name); extern int register_driver(struct tgt_driver *drv); extern const char *driver_state_name(struct tgt_driver *drv); #endif /* __DRIVER_H__ */ tgt-1.0.85/usr/iscsi/000077500000000000000000000000001435417276200143415ustar00rootroot00000000000000tgt-1.0.85/usr/iscsi/chap.c000066400000000000000000000405401435417276200154230ustar00rootroot00000000000000/* * chap.c - support for (mutual) CHAP authentication. * (C) 2004 Xiranet Communications GmbH * * heavily based on code from iscsid.c: * Copyright (C) 2002-2003 Ardis Technolgies , * * and code taken from UNH iSCSI software: * Copyright (C) 2001-2003 InterOperability Lab (IOL) * University of New Hampshire (UNH) * Durham, NH 03824 * * 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, version 2 of the * License. * * 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 "iscsid.h" #include "tgtd.h" #include "md5.h" #include "sha1.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, uint8_t *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 uint8_t 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, uint8_t *intnum, int int_len) { int len; int count; int intptr; uint8_t 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(uint8_t *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(uint8_t *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, uint8_t **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, uint8_t *decode_buf, int buf_len, int encoding_fmt) { if (encoding_fmt == HEX_FORMAT) { if ((strlen(encoded) - 2) > (2 * buf_len)) { eprintf("buf[%d] !sufficient to decode string[%d]\n", 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) { eprintf("buf[%d] !sufficient to decode string[%d]\n", 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(uint8_t *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, uint8_t *challenge, int challenge_len, uint8_t *digest) { struct MD5Context ctx; MD5Init(&ctx); MD5Update(&ctx, (unsigned char*)&chap_id, 1); MD5Update(&ctx, (unsigned char*)secret, secret_len); MD5Update(&ctx, challenge, challenge_len); MD5Final(digest, &ctx); } static inline void chap_calc_digest_sha1(char chap_id, char *secret, int secret_len, uint8_t *challenge, int challenge_len, uint8_t *digest) { struct sha1_ctx ctx; sha1_init(&ctx); sha1_update(&ctx, (unsigned char*)&chap_id, 1); sha1_update(&ctx, (unsigned char*)secret, secret_len); sha1_update(&ctx, challenge, challenge_len); sha1_final(&ctx, digest); } static int chap_initiator_auth_create_challenge(struct iscsi_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 iscsi_connection *conn) { char *value; uint8_t *his_digest = NULL, *our_digest = NULL; int digest_len = 0, retval = 0, encoding_format, err; char pass[ISCSI_NAME_LEN]; memset(pass, 0, sizeof(pass)); err = account_available(conn->tid, AUTH_DIR_INCOMING); if (!err) { eprintf("No CHAP credentials configured\n"); retval = CHAP_TARGET_ERROR; goto out; } if (!(value = text_key_find(conn, "CHAP_N"))) { retval = CHAP_INITIATOR_ERROR; goto out; } memset(pass, 0, sizeof(pass)); err = account_lookup(conn->tid, AUTH_DIR_INCOMING, value, 0, pass, ISCSI_NAME_LEN); if (err) { eprintf("No valid user/pass combination for initiator %s " "found\n", 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->auth_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 iscsi_connection *conn) { char chap_id, *value, *response = NULL; uint8_t *challenge = NULL, *digest = NULL; int encoding_format, response_len; int challenge_len = 0, digest_len = 0, retval = 0, err; 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)); err = account_lookup(conn->tid, AUTH_DIR_OUTGOING, name, sizeof(name), pass, sizeof(pass)); if (err) { 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 iscsi_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: eprintf("BUG. unknown conn->auth_state %d\n", conn->auth_state); res = CHAP_TARGET_ERROR; } return res; } tgt-1.0.85/usr/iscsi/conn.c000066400000000000000000000155741435417276200154560ustar00rootroot00000000000000/* * Copyright (C) 2002-2003 Ardis Technolgies * * 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, version 2 of the * License. * * 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 "iscsid.h" #include "tgtd.h" #include "util.h" #include "tgtadm_error.h" void conn_add_to_session(struct iscsi_connection *conn, struct iscsi_session *session) { if (!list_empty(&conn->clist)) { eprintf("%" PRIx64 " %u\n", sid64(session->isid, session->tsih), conn->cid); exit(0); } /* release in conn_free */ session_get(session); conn->session = session; list_add(&conn->clist, &session->conn_list); } int conn_init(struct iscsi_connection *conn) { conn->req_buffer = malloc(INCOMING_BUFSIZE); if (!conn->req_buffer) return -ENOMEM; conn->rsp_buffer = malloc(INCOMING_BUFSIZE); if (!conn->rsp_buffer) { free(conn->req_buffer); return -ENOMEM; } conn->rsp_buffer_size = INCOMING_BUFSIZE; conn->refcount = 1; conn->state = STATE_FREE; param_set_defaults(conn->session_param, session_keys); INIT_LIST_HEAD(&conn->clist); INIT_LIST_HEAD(&conn->tx_clist); INIT_LIST_HEAD(&conn->task_list); return 0; } void conn_exit(struct iscsi_connection *conn) { struct iscsi_session *session = conn->session; list_del(&conn->clist); free(conn->req_buffer); free(conn->rsp_buffer); free(conn->initiator); if (conn->initiator_alias) free(conn->initiator_alias); if (session) session_put(session); } void conn_close(struct iscsi_connection *conn) { struct iscsi_task *task, *tmp; int ret; if (conn->closed) { eprintf("already closed %p %u\n", conn, conn->refcount); return; } conn->closed = 1; ret = conn->tp->ep_close(conn); if (ret) eprintf("failed to close a connection, %p %u %s\n", conn, conn->refcount, strerror(errno)); else dprintf("connection closed, %p %u\n", conn, conn->refcount); /* may not have been in FFP yet */ if (!conn->session) goto done; dprintf("session %p %d\n", conn->session, conn->session->refcount); /* * We just closed the ep so we are not going to send/recv anything. * Just free these up since they are not going to complete. */ list_for_each_entry_safe(task, tmp, &conn->session->pending_cmd_list, c_list) { if (task->conn != conn) continue; eprintf("Forcing release of pending task %p %" PRIx64 "\n", task, task->tag); list_del(&task->c_list); iscsi_free_task(task); } if (conn->tx_task) { dprintf("Add current tx task to the tx list for removal " "%p %" PRIx64 "\n", conn->tx_task, conn->tx_task->tag); list_add(&conn->tx_task->c_list, &conn->tx_clist); conn->tx_task = NULL; } list_for_each_entry_safe(task, tmp, &conn->tx_clist, c_list) { uint8_t op; op = task->req.opcode & ISCSI_OPCODE_MASK; eprintf("Forcing release of tx task %p %" PRIx64 " %x\n", task, task->tag, op); switch (op) { case ISCSI_OP_SCSI_CMD: /* * We can't call iscsi_free_cmd_task for a * command waiting for SCSI_DATA_OUT. There * would be a better way to see * task->scmd.c_target though. */ if (task->scmd.c_target) iscsi_free_cmd_task(task); else iscsi_free_task(task); break; case ISCSI_OP_NOOP_IN: /* NOOP_IN req is allocated within iscsi_tcp * by a direct call to the transport * allocation routine, unaccounted in the * connection refcount and not added to * task_list, hence it should be freed when * it's done by a similar direct call. * * We're overprotective here by checking tp's * free_task pointer, avoiding interference * with iser (I'm unsure if it's relevant * though). */ if (task->conn->tp->free_task) task->conn->tp->free_task(task); break; case ISCSI_OP_NOOP_OUT: case ISCSI_OP_LOGOUT: case ISCSI_OP_SCSI_TMFUNC: iscsi_free_task(task); break; default: eprintf("%x\n", op); break; } } if (conn->rx_task) { eprintf("Forcing release of rx task %p %" PRIx64 "\n", conn->rx_task, conn->rx_task->tag); iscsi_free_task(conn->rx_task); } conn->rx_task = NULL; /* cleaning up commands waiting for SCSI_DATA_OUT */ list_for_each_entry_safe(task, tmp, &conn->task_list, c_siblings) { /* * This task is in SCSI. We need to wait for I/O * completion. */ if (task_in_scsi(task)) continue; iscsi_free_task(task); } done: conn_put(conn); } void conn_put(struct iscsi_connection *conn) { conn->refcount--; if (!conn->refcount) conn->tp->ep_release(conn); } int conn_get(struct iscsi_connection *conn) { /* TODO: check state */ conn->refcount++; return 0; } struct iscsi_connection *conn_find(struct iscsi_session *session, uint32_t cid) { struct iscsi_connection *conn; list_for_each_entry(conn, &session->conn_list, clist) { if (conn->cid == cid) return conn; } return NULL; } int conn_take_fd(struct iscsi_connection *conn) { dprintf("%u %u %u %" PRIx64 "\n", conn->cid, conn->stat_sn, conn->exp_stat_sn, sid64(conn->isid, conn->tsih)); conn->session->conn_cnt++; return 0; } /* called by tgtadm */ tgtadm_err conn_close_admin(uint32_t tid, uint64_t sid, uint32_t cid) { struct iscsi_target* target = NULL; struct iscsi_session *session; struct iscsi_connection *conn; int sess_found = 0; target = target_find_by_id(tid); if (!target) return TGTADM_NO_TARGET; list_for_each_entry(session, &target->sessions_list, slist) { if (session->tsih == sid) { sess_found = 1; list_for_each_entry(conn, &session->conn_list, clist) { if (conn->cid == cid) { eprintf("close %" PRIx64 " %u\n", sid, cid); conn->tp->ep_force_close(conn); return TGTADM_SUCCESS; } } } } return sess_found ? TGTADM_NO_CONNECTION : TGTADM_NO_SESSION; } void iscsi_update_conn_stats_rx(struct iscsi_connection *conn, int size, int opcode) { conn->stats.rxdata_octets += (uint64_t)size; if (unlikely(opcode < 0)) return; if (opcode == ISCSI_OP_SCSI_CMD) conn->stats.scsicmd_pdus++; else if (opcode == ISCSI_OP_SCSI_DATA_OUT) conn->stats.dataout_pdus++; } void iscsi_update_conn_stats_tx(struct iscsi_connection *conn, int size, int opcode) { conn->stats.txdata_octets += (uint64_t)size; if (unlikely(opcode < 0)) return; if (opcode == ISCSI_OP_SCSI_DATA_IN) conn->stats.datain_pdus++; else if (opcode == ISCSI_OP_SCSI_CMD_RSP) conn->stats.scsirsp_pdus++; } tgt-1.0.85/usr/iscsi/iscsi_if.h000066400000000000000000000177061435417276200163150ustar00rootroot00000000000000/* * iSCSI User/Kernel Shares (Defines, Constants, Protocol definitions, etc) * * Copyright (C) 2005 Dmitry Yusupov * Copyright (C) 2005 Alex Aizman * maintained by open-iscsi@googlegroups.com * * 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. * * See the file COPYING included with this distribution for more details. */ #ifndef ISCSI_IF_H #define ISCSI_IF_H #define UEVENT_BASE 10 #define KEVENT_BASE 100 #define ISCSI_ERR_BASE 1000 enum iscsi_uevent_e { ISCSI_UEVENT_UNKNOWN = 0, /* down events */ ISCSI_UEVENT_CREATE_SESSION = UEVENT_BASE + 1, ISCSI_UEVENT_DESTROY_SESSION = UEVENT_BASE + 2, ISCSI_UEVENT_CREATE_CONN = UEVENT_BASE + 3, ISCSI_UEVENT_DESTROY_CONN = UEVENT_BASE + 4, ISCSI_UEVENT_BIND_CONN = UEVENT_BASE + 5, ISCSI_UEVENT_SET_PARAM = UEVENT_BASE + 6, ISCSI_UEVENT_START_CONN = UEVENT_BASE + 7, ISCSI_UEVENT_STOP_CONN = UEVENT_BASE + 8, ISCSI_UEVENT_SEND_PDU = UEVENT_BASE + 9, ISCSI_UEVENT_GET_STATS = UEVENT_BASE + 10, ISCSI_UEVENT_GET_PARAM = UEVENT_BASE + 11, ISCSI_UEVENT_TRANSPORT_EP_CONNECT = UEVENT_BASE + 12, ISCSI_UEVENT_TRANSPORT_EP_POLL = UEVENT_BASE + 13, ISCSI_UEVENT_TRANSPORT_EP_DISCONNECT = UEVENT_BASE + 14, ISCSI_UEVENT_TGT_DSCVR = UEVENT_BASE + 15, /* up events */ ISCSI_KEVENT_RECV_PDU = KEVENT_BASE + 1, ISCSI_KEVENT_CONN_ERROR = KEVENT_BASE + 2, ISCSI_KEVENT_IF_ERROR = KEVENT_BASE + 3, ISCSI_KEVENT_DESTROY_SESSION = KEVENT_BASE + 4, }; enum iscsi_tgt_dscvr { ISCSI_TGT_DSCVR_SEND_TARGETS = 1, ISCSI_TGT_DSCVR_ISNS = 2, ISCSI_TGT_DSCVR_SLP = 3, }; struct iscsi_uevent { uint32_t type; /* k/u events type */ uint32_t iferror; /* carries interface or resource errors */ uint64_t transport_handle; union { /* messages u -> k */ struct msg_create_session { uint32_t initial_cmdsn; } c_session; struct msg_destroy_session { uint32_t sid; } d_session; struct msg_create_conn { uint32_t sid; uint32_t cid; } c_conn; struct msg_bind_conn { uint32_t sid; uint32_t cid; uint64_t transport_eph; uint32_t is_leading; } b_conn; struct msg_destroy_conn { uint32_t sid; uint32_t cid; } d_conn; struct msg_send_pdu { uint32_t sid; uint32_t cid; uint32_t hdr_size; uint32_t data_size; } send_pdu; struct msg_set_param { uint32_t sid; uint32_t cid; uint32_t param; /* enum iscsi_param */ uint32_t len; } set_param; struct msg_start_conn { uint32_t sid; uint32_t cid; } start_conn; struct msg_stop_conn { uint32_t sid; uint32_t cid; uint64_t conn_handle; uint32_t flag; } stop_conn; struct msg_get_stats { uint32_t sid; uint32_t cid; } get_stats; struct msg_transport_connect { uint32_t non_blocking; } ep_connect; struct msg_transport_poll { uint64_t ep_handle; uint32_t timeout_ms; } ep_poll; struct msg_transport_disconnect { uint64_t ep_handle; } ep_disconnect; struct msg_tgt_dscvr { enum iscsi_tgt_dscvr type; uint32_t host_no; /* * enable = 1 to establish a new connection * with the server. enable = 0 to disconnect * from the server. Used primarily to switch * from one iSNS server to another. */ uint32_t enable; } tgt_dscvr; } u; union { /* messages k -> u */ int retcode; struct msg_create_session_ret { uint32_t sid; uint32_t host_no; } c_session_ret; struct msg_create_conn_ret { uint32_t sid; uint32_t cid; } c_conn_ret; struct msg_recv_req { uint32_t sid; uint32_t cid; uint64_t recv_handle; } recv_req; struct msg_conn_error { uint32_t sid; uint32_t cid; uint32_t error; /* enum iscsi_err */ } connerror; struct msg_session_destroyed { uint32_t host_no; uint32_t sid; } d_session; struct msg_transport_connect_ret { uint64_t handle; } ep_connect_ret; } r; } __attribute__ ((aligned (sizeof(uint64_t)))); /* * Common error codes */ enum iscsi_err { ISCSI_OK = 0, ISCSI_ERR_DATASN = ISCSI_ERR_BASE + 1, ISCSI_ERR_DATA_OFFSET = ISCSI_ERR_BASE + 2, ISCSI_ERR_MAX_CMDSN = ISCSI_ERR_BASE + 3, ISCSI_ERR_EXP_CMDSN = ISCSI_ERR_BASE + 4, ISCSI_ERR_BAD_OPCODE = ISCSI_ERR_BASE + 5, ISCSI_ERR_DATALEN = ISCSI_ERR_BASE + 6, ISCSI_ERR_AHSLEN = ISCSI_ERR_BASE + 7, ISCSI_ERR_PROTO = ISCSI_ERR_BASE + 8, ISCSI_ERR_LUN = ISCSI_ERR_BASE + 9, ISCSI_ERR_BAD_ITT = ISCSI_ERR_BASE + 10, ISCSI_ERR_CONN_FAILED = ISCSI_ERR_BASE + 11, ISCSI_ERR_R2TSN = ISCSI_ERR_BASE + 12, ISCSI_ERR_SESSION_FAILED = ISCSI_ERR_BASE + 13, ISCSI_ERR_HDR_DGST = ISCSI_ERR_BASE + 14, ISCSI_ERR_DATA_DGST = ISCSI_ERR_BASE + 15, ISCSI_ERR_PARAM_NOT_FOUND = ISCSI_ERR_BASE + 16, ISCSI_ERR_NO_SCSI_CMD = ISCSI_ERR_BASE + 17, }; /* * iSCSI Parameters (RFC3720). Keep the session_keys and * default_tgt_session_param arrays consistent with this list. */ enum iscsi_param { ISCSI_PARAM_MAX_RECV_DLENGTH, ISCSI_PARAM_HDRDGST_EN, ISCSI_PARAM_DATADGST_EN, ISCSI_PARAM_INITIAL_R2T_EN, ISCSI_PARAM_MAX_R2T, ISCSI_PARAM_IMM_DATA_EN, ISCSI_PARAM_FIRST_BURST, ISCSI_PARAM_MAX_BURST, ISCSI_PARAM_PDU_INORDER_EN, ISCSI_PARAM_DATASEQ_INORDER_EN, ISCSI_PARAM_ERL, ISCSI_PARAM_IFMARKER_EN, ISCSI_PARAM_OFMARKER_EN, ISCSI_PARAM_DEFAULTTIME2WAIT, ISCSI_PARAM_DEFAULTTIME2RETAIN, ISCSI_PARAM_OFMARKINT, ISCSI_PARAM_IFMARKINT, ISCSI_PARAM_MAXCONNECTIONS, /* iSCSI Extensions for RDMA (RFC5046) */ ISCSI_PARAM_RDMA_EXTENSIONS, ISCSI_PARAM_TARGET_RDSL, ISCSI_PARAM_INITIATOR_RDSL, ISCSI_PARAM_MAX_OUTST_PDU, /* "local" parmas, never sent to the initiator */ ISCSI_PARAM_FIRST_LOCAL, ISCSI_PARAM_MAX_XMIT_DLENGTH = ISCSI_PARAM_FIRST_LOCAL, ISCSI_PARAM_MAX_QUEUE_CMD, /* must always be last */ ISCSI_PARAM_MAX, }; #define iscsi_ptr(_handle) ((void*)(unsigned long)_handle) #define iscsi_handle(_ptr) ((uint64_t)(unsigned long)_ptr) #define hostdata_session(_hostdata) (iscsi_ptr(*(unsigned long *)_hostdata)) /** * iscsi_hostdata - get LLD hostdata from scsi_host * @_hostdata: pointer to scsi host's hostdata **/ #define iscsi_hostdata(_hostdata) ((void*)_hostdata + sizeof(unsigned long)) /* * These flags presents iSCSI Data-Path capabilities. */ #define CAP_RECOVERY_L0 0x1 #define CAP_RECOVERY_L1 0x2 #define CAP_RECOVERY_L2 0x4 #define CAP_MULTI_R2T 0x8 #define CAP_HDRDGST 0x10 #define CAP_DATADGST 0x20 #define CAP_MULTI_CONN 0x40 #define CAP_TEXT_NEGO 0x80 #define CAP_MARKERS 0x100 /* * These flags describes reason of stop_conn() call */ #define STOP_CONN_TERM 0x1 #define STOP_CONN_SUSPEND 0x2 #define STOP_CONN_RECOVER 0x3 #define ISCSI_STATS_CUSTOM_MAX 32 #define ISCSI_STATS_CUSTOM_DESC_MAX 64 struct iscsi_stats_custom { char desc[ISCSI_STATS_CUSTOM_DESC_MAX]; uint64_t value; }; /* * struct iscsi_stats - iSCSI Statistics (iSCSI MIB) * * Note: this structure contains counters collected on per-connection basis. */ struct iscsi_stats { /* octets */ uint64_t txdata_octets; uint64_t rxdata_octets; /* xmit pdus */ uint32_t noptx_pdus; uint32_t scsicmd_pdus; uint32_t tmfcmd_pdus; uint32_t login_pdus; uint32_t text_pdus; uint32_t dataout_pdus; uint32_t logout_pdus; uint32_t snack_pdus; /* recv pdus */ uint32_t noprx_pdus; uint32_t scsirsp_pdus; uint32_t tmfrsp_pdus; uint32_t textrsp_pdus; uint32_t datain_pdus; uint32_t logoutrsp_pdus; uint32_t r2t_pdus; uint32_t async_pdus; uint32_t rjt_pdus; /* errors */ uint32_t digest_err; uint32_t timeout_err; /* * iSCSI Custom Statistics support, i.e. Transport could * extend existing MIB statistics with its own specific statistics * up to ISCSI_STATS_CUSTOM_MAX */ uint32_t custom_length; struct iscsi_stats_custom custom[0] __attribute__ ((aligned (sizeof(uint64_t)))); }; #endif tgt-1.0.85/usr/iscsi/iscsi_proto.h000066400000000000000000000346351435417276200170620ustar00rootroot00000000000000/* * RFC 3720 (iSCSI) protocol data types * * Copyright (C) 2005 Dmitry Yusupov * Copyright (C) 2005 Alex Aizman * maintained by open-iscsi@googlegroups.com * * 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. * * See the file COPYING included with this distribution for more details. */ #ifndef ISCSI_PROTO_H #define ISCSI_PROTO_H #define ISCSI_DRAFT20_VERSION 0x00 /* default iSCSI listen port for incoming connections */ #define ISCSI_LISTEN_PORT 3260 /* Padding word length */ #define PAD_WORD_LEN 4 /* * useful common(control and data pathes) macro */ #define ntoh24(p) (((p)[0] << 16) | ((p)[1] << 8) | ((p)[2])) #define hton24(p, v) { \ p[0] = (((v) >> 16) & 0xFF); \ p[1] = (((v) >> 8) & 0xFF); \ p[2] = ((v) & 0xFF); \ } #define zero_data(p) {p[0]=0;p[1]=0;p[2]=0;} /* * iSCSI Template Message Header */ struct iscsi_hdr { uint8_t opcode; uint8_t flags; /* Final bit */ uint8_t rsvd2[2]; uint8_t hlength; /* AHSs total length */ uint8_t dlength[3]; /* Data length */ uint8_t lun[8]; uint32_t itt; /* Initiator Task Tag */ uint32_t ttt; /* Target Task Tag */ uint32_t statsn; uint32_t exp_statsn; uint32_t max_statsn; uint8_t other[12]; }; /************************* RFC 3720 Begin *****************************/ #define ISCSI_RESERVED_TAG 0xffffffff /* Opcode encoding bits */ #define ISCSI_OP_RETRY 0x80 #define ISCSI_OP_IMMEDIATE 0x40 #define ISCSI_OPCODE_MASK 0x3F /* Initiator Opcode values */ #define ISCSI_OP_NOOP_OUT 0x00 #define ISCSI_OP_SCSI_CMD 0x01 #define ISCSI_OP_SCSI_TMFUNC 0x02 #define ISCSI_OP_LOGIN 0x03 #define ISCSI_OP_TEXT 0x04 #define ISCSI_OP_SCSI_DATA_OUT 0x05 #define ISCSI_OP_LOGOUT 0x06 #define ISCSI_OP_SNACK 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 /* Target Opcode values */ #define ISCSI_OP_NOOP_IN 0x20 #define ISCSI_OP_SCSI_CMD_RSP 0x21 #define ISCSI_OP_SCSI_TMFUNC_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_EVENT 0x32 #define ISCSI_OP_REJECT 0x3f struct iscsi_ahs_hdr { uint16_t ahslength; uint8_t ahstype; uint8_t ahspec[5]; }; #define ISCSI_AHSTYPE_CDB 1 #define ISCSI_AHSTYPE_RLENGTH 2 /* iSCSI PDU Header */ struct iscsi_cmd { uint8_t opcode; uint8_t flags; uint16_t rsvd2; uint8_t hlength; uint8_t dlength[3]; uint8_t lun[8]; uint32_t itt; /* Initiator Task Tag */ uint32_t data_length; uint32_t cmdsn; uint32_t exp_statsn; uint8_t cdb[16]; /* SCSI Command Block */ /* Additional Data (Command Dependent) */ }; /* Command PDU flags */ #define ISCSI_FLAG_CMD_FINAL 0x80 #define ISCSI_FLAG_CMD_READ 0x40 #define ISCSI_FLAG_CMD_WRITE 0x20 #define ISCSI_FLAG_CMD_ATTR_MASK 0x07 /* 3 bits */ /* SCSI Command Attribute values */ #define ISCSI_ATTR_UNTAGGED 0 #define ISCSI_ATTR_SIMPLE 1 #define ISCSI_ATTR_ORDERED 2 #define ISCSI_ATTR_HEAD_OF_QUEUE 3 #define ISCSI_ATTR_ACA 4 struct iscsi_rlength_ahdr { uint16_t ahslength; uint8_t ahstype; uint8_t reserved; uint32_t read_length; }; /* Extended CDB AHS */ struct iscsi_ecdb_ahdr { uint16_t ahslength; /* CDB length - 15, including reserved byte */ uint8_t ahstype; uint8_t reserved; uint8_t ecdb[260 - 16]; /* 4-byte aligned extended CDB spillover */ }; /* SCSI Response Header */ struct iscsi_cmd_rsp { uint8_t opcode; uint8_t flags; uint8_t response; uint8_t cmd_status; uint8_t hlength; uint8_t dlength[3]; uint8_t rsvd[8]; uint32_t itt; /* Initiator Task Tag */ uint32_t rsvd1; uint32_t statsn; uint32_t exp_cmdsn; uint32_t max_cmdsn; uint32_t exp_datasn; uint32_t bi_residual_count; uint32_t residual_count; /* Response or Sense Data (optional) */ }; /* Command Response PDU flags */ #define ISCSI_FLAG_CMD_BIDI_OVERFLOW 0x10 #define ISCSI_FLAG_CMD_BIDI_UNDERFLOW 0x08 #define ISCSI_FLAG_CMD_OVERFLOW 0x04 #define ISCSI_FLAG_CMD_UNDERFLOW 0x02 /* iSCSI Status values. Valid if Rsp Selector bit is not set */ #define ISCSI_STATUS_CMD_COMPLETED 0 #define ISCSI_STATUS_TARGET_FAILURE 1 #define ISCSI_STATUS_SUBSYS_FAILURE 2 /* Asynchronous Event Header */ struct iscsi_async { uint8_t opcode; uint8_t flags; uint8_t rsvd2[2]; uint8_t rsvd3; uint8_t dlength[3]; uint8_t lun[8]; uint8_t rsvd4[8]; uint32_t statsn; uint32_t exp_cmdsn; uint32_t max_cmdsn; uint8_t async_event; uint8_t async_vcode; uint16_t param1; uint16_t param2; uint16_t param3; uint8_t rsvd5[4]; }; /* iSCSI Event Codes */ #define ISCSI_ASYNC_MSG_SCSI_EVENT 0 #define ISCSI_ASYNC_MSG_REQUEST_LOGOUT 1 #define ISCSI_ASYNC_MSG_DROPPING_CONNECTION 2 #define ISCSI_ASYNC_MSG_DROPPING_ALL_CONNECTIONS 3 #define ISCSI_ASYNC_MSG_PARAM_NEGOTIATION 4 #define ISCSI_ASYNC_MSG_VENDOR_SPECIFIC 255 /* NOP-Out Message */ struct iscsi_nopout { uint8_t opcode; uint8_t flags; uint16_t rsvd2; uint8_t rsvd3; uint8_t dlength[3]; uint8_t lun[8]; uint32_t itt; /* Initiator Task Tag */ uint32_t ttt; /* Target Transfer Tag */ uint32_t cmdsn; uint32_t exp_statsn; uint8_t rsvd4[16]; }; /* NOP-In Message */ struct iscsi_nopin { uint8_t opcode; uint8_t flags; uint16_t rsvd2; uint8_t rsvd3; uint8_t dlength[3]; uint8_t lun[8]; uint32_t itt; /* Initiator Task Tag */ uint32_t ttt; /* Target Transfer Tag */ uint32_t statsn; uint32_t exp_cmdsn; uint32_t max_cmdsn; uint8_t rsvd4[12]; }; /* SCSI Task Management Message Header */ struct iscsi_tm { uint8_t opcode; uint8_t flags; uint8_t rsvd1[2]; uint8_t hlength; uint8_t dlength[3]; uint8_t lun[8]; uint32_t itt; /* Initiator Task Tag */ uint32_t rtt; /* Reference Task Tag */ uint32_t cmdsn; uint32_t exp_statsn; uint32_t refcmdsn; uint32_t exp_datasn; uint8_t rsvd2[8]; }; #define ISCSI_FLAG_TM_FUNC_MASK 0x7F /* Function values */ #define ISCSI_TM_FUNC_ABORT_TASK 1 #define ISCSI_TM_FUNC_ABORT_TASK_SET 2 #define ISCSI_TM_FUNC_CLEAR_ACA 3 #define ISCSI_TM_FUNC_CLEAR_TASK_SET 4 #define ISCSI_TM_FUNC_LOGICAL_UNIT_RESET 5 #define ISCSI_TM_FUNC_TARGET_WARM_RESET 6 #define ISCSI_TM_FUNC_TARGET_COLD_RESET 7 #define ISCSI_TM_FUNC_TASK_REASSIGN 8 /* SCSI Task Management Response Header */ struct iscsi_tm_rsp { uint8_t opcode; uint8_t flags; uint8_t response; /* see Response values below */ uint8_t qualifier; uint8_t hlength; uint8_t dlength[3]; uint8_t rsvd2[8]; uint32_t itt; /* Initiator Task Tag */ uint32_t rtt; /* Reference Task Tag */ uint32_t statsn; uint32_t exp_cmdsn; uint32_t max_cmdsn; uint8_t rsvd3[12]; }; /* Response values */ #define ISCSI_TMF_RSP_COMPLETE 0x00 #define ISCSI_TMF_RSP_NO_TASK 0x01 #define ISCSI_TMF_RSP_NO_LUN 0x02 #define ISCSI_TMF_RSP_TASK_ALLEGIANT 0x03 #define ISCSI_TMF_RSP_NO_FAILOVER 0x04 #define ISCSI_TMF_RSP_NOT_SUPPORTED 0x05 #define ISCSI_TMF_RSP_AUTH_FAILED 0x06 #define ISCSI_TMF_RSP_REJECTED 0xff /* Ready To Transfer Header */ struct iscsi_r2t_rsp { uint8_t opcode; uint8_t flags; uint8_t rsvd2[2]; uint8_t hlength; uint8_t dlength[3]; uint8_t lun[8]; uint32_t itt; /* Initiator Task Tag */ uint32_t ttt; /* Target Transfer Tag */ uint32_t statsn; uint32_t exp_cmdsn; uint32_t max_cmdsn; uint32_t r2tsn; uint32_t data_offset; uint32_t data_length; }; /* SCSI Data Hdr */ struct iscsi_data { uint8_t opcode; uint8_t flags; uint8_t rsvd2[2]; uint8_t rsvd3; uint8_t dlength[3]; uint8_t lun[8]; uint32_t itt; uint32_t ttt; uint32_t rsvd4; uint32_t exp_statsn; uint32_t rsvd5; uint32_t datasn; uint32_t offset; uint32_t rsvd6; /* Payload */ }; /* SCSI Data Response Hdr */ struct iscsi_data_rsp { uint8_t opcode; uint8_t flags; uint8_t rsvd2; uint8_t cmd_status; uint8_t hlength; uint8_t dlength[3]; uint8_t lun[8]; uint32_t itt; uint32_t ttt; uint32_t statsn; uint32_t exp_cmdsn; uint32_t max_cmdsn; uint32_t datasn; uint32_t offset; uint32_t residual_count; }; /* Data Response PDU flags */ #define ISCSI_FLAG_DATA_ACK 0x40 #define ISCSI_FLAG_DATA_OVERFLOW 0x04 #define ISCSI_FLAG_DATA_UNDERFLOW 0x02 #define ISCSI_FLAG_DATA_STATUS 0x01 /* Text Header */ struct iscsi_text { uint8_t opcode; uint8_t flags; uint8_t rsvd2[2]; uint8_t hlength; uint8_t dlength[3]; uint8_t rsvd4[8]; uint32_t itt; uint32_t ttt; uint32_t cmdsn; uint32_t exp_statsn; uint8_t rsvd5[16]; /* Text - key=value pairs */ }; #define ISCSI_FLAG_TEXT_CONTINUE 0x40 /* Text Response Header */ struct iscsi_text_rsp { uint8_t opcode; uint8_t flags; uint8_t rsvd2[2]; uint8_t hlength; uint8_t dlength[3]; uint8_t rsvd4[8]; uint32_t itt; uint32_t ttt; uint32_t statsn; uint32_t exp_cmdsn; uint32_t max_cmdsn; uint8_t rsvd5[12]; /* Text Response - key:value pairs */ }; /* Login Header */ struct iscsi_login { uint8_t opcode; uint8_t flags; uint8_t max_version; /* Max. version supported */ uint8_t min_version; /* Min. version supported */ uint8_t hlength; uint8_t dlength[3]; uint8_t isid[6]; /* Initiator Session ID */ uint16_t tsih; /* Target Session Handle */ uint32_t itt; /* Initiator Task Tag */ uint16_t cid; uint16_t rsvd3; uint32_t cmdsn; uint32_t exp_statsn; uint8_t rsvd5[16]; }; /* Login PDU flags */ #define ISCSI_FLAG_LOGIN_TRANSIT 0x80 #define ISCSI_FLAG_LOGIN_CONTINUE 0x40 #define ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK 0x0C /* 2 bits */ #define ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK 0x03 /* 2 bits */ #define ISCSI_LOGIN_CURRENT_STAGE(flags) \ ((flags & ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK) >> 2) #define ISCSI_LOGIN_NEXT_STAGE(flags) \ (flags & ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK) /* Login Response Header */ struct iscsi_login_rsp { uint8_t opcode; uint8_t flags; uint8_t max_version; /* Max. version supported */ uint8_t active_version; /* Active version */ uint8_t hlength; uint8_t dlength[3]; uint8_t isid[6]; /* Initiator Session ID */ uint16_t tsih; /* Target Session Handle */ uint32_t itt; /* Initiator Task Tag */ uint32_t rsvd3; uint32_t statsn; uint32_t exp_cmdsn; uint32_t max_cmdsn; uint8_t status_class; /* see Login RSP ststus classes below */ uint8_t status_detail; /* see Login RSP Status details below */ uint8_t rsvd4[10]; }; /* Login stage (phase) codes for CSG, NSG */ #define ISCSI_INITIAL_LOGIN_STAGE -1 #define ISCSI_SECURITY_NEGOTIATION_STAGE 0 #define ISCSI_OP_PARMS_NEGOTIATION_STAGE 1 #define ISCSI_FULL_FEATURE_PHASE 3 /* Login Status response classes */ #define ISCSI_STATUS_CLS_SUCCESS 0x00 #define ISCSI_STATUS_CLS_REDIRECT 0x01 #define ISCSI_STATUS_CLS_INITIATOR_ERR 0x02 #define ISCSI_STATUS_CLS_TARGET_ERR 0x03 /* Login Status response detail codes */ /* Class-0 (Success) */ #define ISCSI_LOGIN_STATUS_ACCEPT 0x00 /* Class-1 (Redirection) */ #define ISCSI_LOGIN_STATUS_TGT_MOVED_TEMP 0x01 #define ISCSI_LOGIN_STATUS_TGT_MOVED_PERM 0x02 /* Class-2 (Initiator Error) */ #define ISCSI_LOGIN_STATUS_INIT_ERR 0x00 #define ISCSI_LOGIN_STATUS_AUTH_FAILED 0x01 #define ISCSI_LOGIN_STATUS_TGT_FORBIDDEN 0x02 #define ISCSI_LOGIN_STATUS_TGT_NOT_FOUND 0x03 #define ISCSI_LOGIN_STATUS_TGT_REMOVED 0x04 #define ISCSI_LOGIN_STATUS_NO_VERSION 0x05 #define ISCSI_LOGIN_STATUS_ISID_ERROR 0x06 #define ISCSI_LOGIN_STATUS_MISSING_FIELDS 0x07 #define ISCSI_LOGIN_STATUS_CONN_ADD_FAILED 0x08 #define ISCSI_LOGIN_STATUS_NO_SESSION_TYPE 0x09 #define ISCSI_LOGIN_STATUS_NO_SESSION 0x0a #define ISCSI_LOGIN_STATUS_INVALID_REQUEST 0x0b /* Class-3 (Target Error) */ #define ISCSI_LOGIN_STATUS_TARGET_ERROR 0x00 #define ISCSI_LOGIN_STATUS_SVC_UNAVAILABLE 0x01 #define ISCSI_LOGIN_STATUS_NO_RESOURCES 0x02 /* Logout Header */ struct iscsi_logout { uint8_t opcode; uint8_t flags; uint8_t rsvd1[2]; uint8_t hlength; uint8_t dlength[3]; uint8_t rsvd2[8]; uint32_t itt; /* Initiator Task Tag */ uint16_t cid; uint8_t rsvd3[2]; uint32_t cmdsn; uint32_t exp_statsn; uint8_t rsvd4[16]; }; /* Logout PDU flags */ #define ISCSI_FLAG_LOGOUT_REASON_MASK 0x7F /* logout reason_code values */ #define ISCSI_LOGOUT_REASON_CLOSE_SESSION 0 #define ISCSI_LOGOUT_REASON_CLOSE_CONNECTION 1 #define ISCSI_LOGOUT_REASON_RECOVERY 2 #define ISCSI_LOGOUT_REASON_AEN_REQUEST 3 /* Logout Response Header */ struct iscsi_logout_rsp { uint8_t opcode; uint8_t flags; uint8_t response; /* see Logout response values below */ uint8_t rsvd2; uint8_t hlength; uint8_t dlength[3]; uint8_t rsvd3[8]; uint32_t itt; /* Initiator Task Tag */ uint32_t rsvd4; uint32_t statsn; uint32_t exp_cmdsn; uint32_t max_cmdsn; uint32_t rsvd5; uint16_t t2wait; uint16_t t2retain; uint32_t rsvd6; }; /* logout response status values */ #define ISCSI_LOGOUT_SUCCESS 0 #define ISCSI_LOGOUT_CID_NOT_FOUND 1 #define ISCSI_LOGOUT_RECOVERY_UNSUPPORTED 2 #define ISCSI_LOGOUT_CLEANUP_FAILED 3 /* SNACK Header */ struct iscsi_snack { uint8_t opcode; uint8_t flags; uint8_t rsvd2[14]; uint32_t itt; uint32_t begrun; uint32_t runlength; uint32_t exp_statsn; uint32_t rsvd3; uint32_t exp_datasn; uint8_t rsvd6[8]; }; /* SNACK PDU flags */ #define ISCSI_FLAG_SNACK_TYPE_MASK 0x0F /* 4 bits */ /* Reject Message Header */ struct iscsi_reject { uint8_t opcode; uint8_t flags; uint8_t reason; uint8_t rsvd2; uint8_t hlength; uint8_t dlength[3]; uint8_t rsvd3[8]; uint32_t ffffffff; uint8_t rsvd4[4]; uint32_t statsn; uint32_t exp_cmdsn; uint32_t max_cmdsn; uint32_t datasn; uint8_t rsvd5[8]; /* Text - Rejected hdr */ }; /* Reason for Reject */ #define ISCSI_REASON_CMD_BEFORE_LOGIN 1 #define ISCSI_REASON_DATA_DIGEST_ERROR 0x02 #define ISCSI_REASON_DATA_SNACK_REJECT 0x03 #define ISCSI_REASON_PROTOCOL_ERROR 0x04 #define ISCSI_REASON_CMD_NOT_SUPPORTED 0x05 #define ISCSI_REASON_IMM_CMD_REJECT 0x06 #define ISCSI_REASON_TASK_IN_PROGRESS 0x07 #define ISCSI_REASON_INVALID_SNACK 0x08 #define ISCSI_REASON_INVALID_PDU_FIELD 0x09 #define ISCSI_REASON_OUT_OF_RESOURCES 0x0a #define ISCSI_REASON_NEGOTIATION_RESET 0x0b #define ISCSI_REASON_WAINTING_FOR_LOGOUT 0x0c /* Max. number of Key=Value pairs in a text message */ #define MAX_KEY_VALUE_PAIRS 8192 /* maximum length for text keys/values */ #define KEY_MAXLEN 64 #define VALUE_MAXLEN 255 #define TARGET_NAME_MAXLEN VALUE_MAXLEN #define DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH 8192 /************************* RFC 3720 End *****************************/ #endif /* ISCSI_PROTO_H */ tgt-1.0.85/usr/iscsi/iscsi_tcp.c000066400000000000000000000364231435417276200164750ustar00rootroot00000000000000/* * Software iSCSI target over TCP/IP Data-Path * * Copyright (C) 2006-2007 FUJITA Tomonori * Copyright (C) 2006-2007 Mike Christie * * 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, version 2 of the * License. * * 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 #include #include "iscsid.h" #include "tgtd.h" #include "util.h" #include "work.h" static void iscsi_tcp_event_handler(int fd, int events, void *data); static void iscsi_tcp_release(struct iscsi_connection *conn); static struct iscsi_task *iscsi_tcp_alloc_task(struct iscsi_connection *conn, size_t ext_len); static void iscsi_tcp_free_task(struct iscsi_task *task); static long nop_ttt; static int listen_fds[8]; static struct iscsi_transport iscsi_tcp; struct iscsi_tcp_connection { int fd; struct list_head tcp_conn_siblings; int nop_inflight_count; int nop_interval; int nop_tick; int nop_count; long ttt; struct iscsi_connection iscsi_conn; }; static inline struct iscsi_tcp_connection *TCP_CONN(struct iscsi_connection *conn) { return container_of(conn, struct iscsi_tcp_connection, iscsi_conn); } static struct tgt_work nop_work; /* all iscsi connections */ static struct list_head iscsi_tcp_conn_list; static int iscsi_send_ping_nop_in(struct iscsi_tcp_connection *tcp_conn) { struct iscsi_connection *conn = &tcp_conn->iscsi_conn; struct iscsi_task *task = NULL; task = iscsi_tcp_alloc_task(&tcp_conn->iscsi_conn, 0); task->conn = conn; task->tag = ISCSI_RESERVED_TAG; task->req.opcode = ISCSI_OP_NOOP_IN; task->req.itt = cpu_to_be32(ISCSI_RESERVED_TAG); task->req.ttt = cpu_to_be32(tcp_conn->ttt); list_add_tail(&task->c_list, &task->conn->tx_clist); task->conn->tp->ep_event_modify(task->conn, EPOLLIN | EPOLLOUT); return 0; } static void iscsi_tcp_nop_work_handler(void *data) { struct iscsi_tcp_connection *tcp_conn; list_for_each_entry(tcp_conn, &iscsi_tcp_conn_list, tcp_conn_siblings) { if (tcp_conn->nop_interval == 0) continue; tcp_conn->nop_tick--; if (tcp_conn->nop_tick > 0) continue; tcp_conn->nop_tick = tcp_conn->nop_interval; tcp_conn->nop_inflight_count++; if (tcp_conn->nop_inflight_count > tcp_conn->nop_count) { eprintf("tcp connection timed out after %d failed " \ "NOP-OUT\n", tcp_conn->nop_count); conn_close(&tcp_conn->iscsi_conn); /* cant/shouldnt delete tcp_conn from within the loop */ break; } nop_ttt++; if (nop_ttt == ISCSI_RESERVED_TAG) nop_ttt = 1; tcp_conn->ttt = nop_ttt; iscsi_send_ping_nop_in(tcp_conn); } add_work(&nop_work, 1); } static void iscsi_tcp_nop_reply(long ttt) { struct iscsi_tcp_connection *tcp_conn; list_for_each_entry(tcp_conn, &iscsi_tcp_conn_list, tcp_conn_siblings) { if (tcp_conn->ttt != ttt) continue; tcp_conn->nop_inflight_count = 0; } } int iscsi_update_target_nop_count(int tid, int count) { struct iscsi_target *target; list_for_each_entry(target, &iscsi_targets_list, tlist) { if (target->tid != tid) continue; target->nop_count = count; return 0; } return -1; } int iscsi_update_target_nop_interval(int tid, int interval) { struct iscsi_target *target; list_for_each_entry(target, &iscsi_targets_list, tlist) { if (target->tid != tid) continue; target->nop_interval = interval; return 0; } return -1; } void iscsi_set_nop_interval(int interval) { default_nop_interval = interval; } void iscsi_set_nop_count(int count) { default_nop_count = count; } static int set_keepalive(int fd) { int ret, opt; opt = 1; ret = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt)); if (ret) return ret; opt = 1800; ret = setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &opt, sizeof(opt)); if (ret) return ret; opt = 6; ret = setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &opt, sizeof(opt)); if (ret) return ret; opt = 300; ret = setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &opt, sizeof(opt)); if (ret) return ret; return 0; } static int set_nodelay(int fd) { int ret, opt; opt = 1; ret = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); return ret; } static void accept_connection(int afd, int events, void *data) { struct sockaddr_storage from; socklen_t namesize; struct iscsi_connection *conn; struct iscsi_tcp_connection *tcp_conn; int fd, ret; dprintf("%d\n", afd); namesize = sizeof(from); fd = accept(afd, (struct sockaddr *) &from, &namesize); if (fd < 0) { eprintf("can't accept, %m\n"); return; } if (!is_system_available()) goto out; if (list_empty(&iscsi_targets_list)) goto out; ret = set_keepalive(fd); if (ret) goto out; ret = set_nodelay(fd); if (ret) goto out; tcp_conn = zalloc(sizeof(*tcp_conn)); if (!tcp_conn) goto out; conn = &tcp_conn->iscsi_conn; ret = conn_init(conn); if (ret) { free(tcp_conn); goto out; } tcp_conn->fd = fd; conn->tp = &iscsi_tcp; conn_read_pdu(conn); set_non_blocking(fd); ret = tgt_event_add(fd, EPOLLIN, iscsi_tcp_event_handler, conn); if (ret) { conn_exit(conn); free(tcp_conn); goto out; } list_add(&tcp_conn->tcp_conn_siblings, &iscsi_tcp_conn_list); return; out: close(fd); return; } static void iscsi_tcp_event_handler(int fd, int events, void *data) { struct iscsi_connection *conn = (struct iscsi_connection *) data; if (events & EPOLLIN) iscsi_rx_handler(conn); if (conn->state == STATE_CLOSE) dprintf("connection closed\n"); if (conn->state != STATE_CLOSE && events & EPOLLOUT) iscsi_tx_handler(conn); if (conn->state == STATE_CLOSE) { dprintf("connection closed %p\n", conn); conn_close(conn); } } int iscsi_tcp_init_portal(char *addr, int port, int tpgt) { struct addrinfo hints, *res, *res0; char servname[64]; int ret, fd, opt, nr_sock = 0; struct iscsi_portal *portal = NULL; char addrstr[64]; void *addrptr = NULL; if (port < 0 || port > 65535) { errno = EINVAL; eprintf("port out of range, %m\n"); return -errno; } memset(servname, 0, sizeof(servname)); snprintf(servname, sizeof(servname), "%d", port); memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; ret = getaddrinfo(addr, servname, &hints, &res0); if (ret) { eprintf("unable to get address info, %m\n"); return -errno; } for (res = res0; res; res = res->ai_next) { fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (fd < 0) { if (res->ai_family == AF_INET6) dprintf("IPv6 support is disabled.\n"); else eprintf("unable to create fdet %d %d %d, %m\n", res->ai_family, res->ai_socktype, res->ai_protocol); continue; } opt = 1; ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); if (ret) dprintf("unable to set SO_REUSEADDR, %m\n"); opt = 1; if (res->ai_family == AF_INET6) { ret = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)); if (ret) { close(fd); continue; } } ret = bind(fd, res->ai_addr, res->ai_addrlen); if (ret) { close(fd); eprintf("unable to bind server socket, %m\n"); continue; } ret = listen(fd, SOMAXCONN); if (ret) { eprintf("unable to listen to server socket, %m\n"); close(fd); continue; } ret = getsockname(fd, res->ai_addr, &res->ai_addrlen); if (ret) { close(fd); eprintf("unable to get socket address, %m\n"); continue; } set_non_blocking(fd); ret = tgt_event_add(fd, EPOLLIN, accept_connection, NULL); if (ret) close(fd); else { listen_fds[nr_sock] = fd; nr_sock++; } portal = zalloc(sizeof(struct iscsi_portal)); switch (res->ai_family) { case AF_INET: addrptr = &((struct sockaddr_in *) res->ai_addr)->sin_addr; port = ntohs(((struct sockaddr_in *) res->ai_addr)->sin_port); break; case AF_INET6: addrptr = &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr; port = ntohs(((struct sockaddr_in6 *) res->ai_addr)->sin6_port); break; } portal->addr = strdup(inet_ntop(res->ai_family, addrptr, addrstr, sizeof(addrstr))); portal->port = port; portal->tpgt = tpgt; portal->fd = fd; portal->af = res->ai_family; list_add(&portal->iscsi_portal_siblings, &iscsi_portals_list); } freeaddrinfo(res0); return !nr_sock; } int iscsi_add_portal(char *addr, int port, int tpgt) { const char *addr_str = ""; if (iscsi_tcp_init_portal(addr, port, tpgt)) { if (addr) { addr_str = addr; } eprintf("failed to create/bind to portal %s:%d\n", addr_str, port); return -1; } return 0; }; int iscsi_delete_portal(char *addr, int port) { struct iscsi_portal *portal; list_for_each_entry(portal, &iscsi_portals_list, iscsi_portal_siblings) { if (!strcmp(addr, portal->addr) && port == portal->port) { if (portal->fd != -1) tgt_event_del(portal->fd); close(portal->fd); list_del(&portal->iscsi_portal_siblings); free(portal->addr); free(portal); return 0; } } eprintf("delete_portal failed. No such portal found %s:%d\n", addr, port); return -1; } static int iscsi_tcp_init(void) { /* If we were passed any portals on the command line */ if (portal_arguments) iscsi_param_parse_portals(portal_arguments, 1, 0); /* if the user did not set a portal we default to wildcard for ipv4 and ipv6 */ if (list_empty(&iscsi_portals_list)) { iscsi_add_portal(NULL, ISCSI_LISTEN_PORT, 1); } INIT_LIST_HEAD(&iscsi_tcp_conn_list); nop_work.func = iscsi_tcp_nop_work_handler; nop_work.data = &nop_work; add_work(&nop_work, 1); return 0; } static void iscsi_tcp_exit(void) { struct iscsi_portal *portal, *ptmp; list_for_each_entry_safe(portal, ptmp, &iscsi_portals_list, iscsi_portal_siblings) { iscsi_delete_portal(portal->addr, portal->port); } } static int iscsi_tcp_conn_login_complete(struct iscsi_connection *conn) { struct iscsi_tcp_connection *tcp_conn; struct iscsi_target *target; list_for_each_entry(tcp_conn, &iscsi_tcp_conn_list, tcp_conn_siblings) if (&tcp_conn->iscsi_conn == conn) break; if (tcp_conn == NULL) return 0; list_for_each_entry(target, &iscsi_targets_list, tlist) { if (target->tid != conn->tid) continue; tcp_conn->nop_count = target->nop_count; tcp_conn->nop_interval = target->nop_interval; tcp_conn->nop_tick = target->nop_interval; break; } return 0; } static size_t iscsi_tcp_read(struct iscsi_connection *conn, void *buf, size_t nbytes) { struct iscsi_tcp_connection *tcp_conn = TCP_CONN(conn); return read(tcp_conn->fd, buf, nbytes); } static size_t iscsi_tcp_write_begin(struct iscsi_connection *conn, void *buf, size_t nbytes) { struct iscsi_tcp_connection *tcp_conn = TCP_CONN(conn); int opt = 1; setsockopt(tcp_conn->fd, SOL_TCP, TCP_CORK, &opt, sizeof(opt)); return write(tcp_conn->fd, buf, nbytes); } static void iscsi_tcp_write_end(struct iscsi_connection *conn) { struct iscsi_tcp_connection *tcp_conn = TCP_CONN(conn); int opt = 0; setsockopt(tcp_conn->fd, SOL_TCP, TCP_CORK, &opt, sizeof(opt)); } static size_t iscsi_tcp_close(struct iscsi_connection *conn) { struct iscsi_tcp_connection *tcp_conn = TCP_CONN(conn); tgt_event_del(tcp_conn->fd); conn->state = STATE_CLOSE; tcp_conn->nop_interval = 0; return 0; } static void iscsi_tcp_release(struct iscsi_connection *conn) { struct iscsi_tcp_connection *tcp_conn = TCP_CONN(conn); conn_exit(conn); close(tcp_conn->fd); list_del(&tcp_conn->tcp_conn_siblings); free(tcp_conn); } static int iscsi_tcp_show(struct iscsi_connection *conn, char *buf, int rest) { struct iscsi_tcp_connection *tcp_conn = TCP_CONN(conn); int err, total = 0; socklen_t slen; char dst[INET6_ADDRSTRLEN]; struct sockaddr_storage from; slen = sizeof(from); err = getpeername(tcp_conn->fd, (struct sockaddr *) &from, &slen); if (err < 0) { eprintf("%m\n"); return 0; } err = getnameinfo((struct sockaddr *)&from, sizeof(from), dst, sizeof(dst), NULL, 0, NI_NUMERICHOST); if (err < 0) { eprintf("%m\n"); return 0; } total = snprintf(buf, rest, "IP Address: %s", dst); return total > 0 ? total : 0; } static void iscsi_event_modify(struct iscsi_connection *conn, int events) { struct iscsi_tcp_connection *tcp_conn = TCP_CONN(conn); int ret; ret = tgt_event_modify(tcp_conn->fd, events); if (ret) eprintf("tgt_event_modify failed\n"); } static struct iscsi_task *iscsi_tcp_alloc_task(struct iscsi_connection *conn, size_t ext_len) { struct iscsi_task *task; task = malloc(sizeof(*task) + ext_len); if (task) memset(task, 0, sizeof(*task) + ext_len); return task; } static void iscsi_tcp_free_task(struct iscsi_task *task) { free(task); } static void *iscsi_tcp_alloc_data_buf(struct iscsi_connection *conn, size_t sz) { void *addr = NULL; if (posix_memalign(&addr, sysconf(_SC_PAGESIZE), sz) != 0) return addr; return addr; } static void iscsi_tcp_free_data_buf(struct iscsi_connection *conn, void *buf) { if (buf) free(buf); } static int iscsi_tcp_getsockname(struct iscsi_connection *conn, struct sockaddr *sa, socklen_t *len) { struct iscsi_tcp_connection *tcp_conn = TCP_CONN(conn); return getsockname(tcp_conn->fd, sa, len); } static int iscsi_tcp_getpeername(struct iscsi_connection *conn, struct sockaddr *sa, socklen_t *len) { struct iscsi_tcp_connection *tcp_conn = TCP_CONN(conn); return getpeername(tcp_conn->fd, sa, len); } static void iscsi_tcp_conn_force_close(struct iscsi_connection *conn) { conn->state = STATE_CLOSE; conn->tp->ep_event_modify(conn, EPOLLIN|EPOLLOUT|EPOLLERR); } void iscsi_print_nop_settings(struct concat_buf *b, int tid) { struct iscsi_target *target; list_for_each_entry(target, &iscsi_targets_list, tlist) { if (target->tid != tid) continue; if (target->nop_interval == 0) continue; concat_printf(b, _TAB2 "Nop interval: %d\n" _TAB2 "Nop count: %d\n", target->nop_interval, target->nop_count); break; } } static struct iscsi_transport iscsi_tcp = { .name = "iscsi", .rdma = 0, .data_padding = PAD_WORD_LEN, .ep_init = iscsi_tcp_init, .ep_exit = iscsi_tcp_exit, .ep_login_complete = iscsi_tcp_conn_login_complete, .alloc_task = iscsi_tcp_alloc_task, .free_task = iscsi_tcp_free_task, .ep_read = iscsi_tcp_read, .ep_write_begin = iscsi_tcp_write_begin, .ep_write_end = iscsi_tcp_write_end, .ep_close = iscsi_tcp_close, .ep_force_close = iscsi_tcp_conn_force_close, .ep_release = iscsi_tcp_release, .ep_show = iscsi_tcp_show, .ep_event_modify = iscsi_event_modify, .alloc_data_buf = iscsi_tcp_alloc_data_buf, .free_data_buf = iscsi_tcp_free_data_buf, .ep_getsockname = iscsi_tcp_getsockname, .ep_getpeername = iscsi_tcp_getpeername, .ep_nop_reply = iscsi_tcp_nop_reply, }; __attribute__((constructor)) static void iscsi_transport_init(void) { iscsi_transport_register(&iscsi_tcp); } tgt-1.0.85/usr/iscsi/iscsid.c000066400000000000000000001741641435417276200160000ustar00rootroot00000000000000/* * Software iSCSI target protocol routines * * Copyright (C) 2005-2007 FUJITA Tomonori * Copyright (C) 2005-2007 Mike Christie * Copyright (C) 2007 Pete Wyckoff * * This code is based on Ardis's iSCSI implementation. * http://www.ardistech.com/iscsi/ * Copyright (C) 2002-2003 Ardis Technolgies * * 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, version 2 of the * License. * * 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 "iscsid.h" #include "tgtd.h" #include "util.h" #include "driver.h" #include "scsi.h" #include "tgtadm.h" #include "crc32c.h" int default_nop_interval; int default_nop_count; LIST_HEAD(iscsi_portals_list); char *portal_arguments; enum { IOSTATE_FREE, IOSTATE_RX_BHS, IOSTATE_RX_INIT_AHS, IOSTATE_RX_AHS, IOSTATE_RX_INIT_HDIGEST, IOSTATE_RX_HDIGEST, IOSTATE_RX_CHECK_HDIGEST, IOSTATE_RX_INIT_DATA, IOSTATE_RX_DATA, IOSTATE_RX_INIT_DDIGEST, IOSTATE_RX_DDIGEST, IOSTATE_RX_CHECK_DDIGEST, IOSTATE_RX_END, IOSTATE_TX_BHS, IOSTATE_TX_INIT_AHS, IOSTATE_TX_AHS, IOSTATE_TX_INIT_HDIGEST, IOSTATE_TX_HDIGEST, IOSTATE_TX_INIT_DATA, IOSTATE_TX_DATA, IOSTATE_TX_INIT_DDIGEST, IOSTATE_TX_DDIGEST, IOSTATE_TX_END, }; void conn_read_pdu(struct iscsi_connection *conn) { conn->rx_iostate = IOSTATE_RX_BHS; conn->rx_buffer = (void *)&conn->req.bhs; conn->rx_size = BHS_SIZE; } static void conn_write_pdu(struct iscsi_connection *conn) { conn->tx_iostate = IOSTATE_TX_BHS; memset(&conn->rsp, 0, sizeof(conn->rsp)); conn->tx_buffer = (void *)&conn->rsp.bhs; conn->tx_size = BHS_SIZE; } static struct iscsi_key login_keys[] = { {"InitiatorName",}, {"InitiatorAlias",}, {"SessionType",}, {"TargetName",}, {NULL, 0, 0, 0, NULL}, }; char *text_key_find(struct iscsi_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 iscsi_connection *conn, char *key, char *value) { int keylen = strlen(key); int valuelen = strlen(value); int len = keylen + valuelen + 2; char *buffer; int max_len; if (conn->state == STATE_FULL) max_len = conn->session_param[ISCSI_PARAM_MAX_XMIT_DLENGTH].val; else max_len = conn->rsp_buffer_size; if (!conn->rsp.datasize) conn->rsp.data = conn->rsp_buffer; buffer = conn->rsp_buffer; if (conn->rsp.datasize + len > max_len && (conn->req.bhs.opcode & ISCSI_OPCODE_MASK) != ISCSI_OP_TEXT) goto drop; if (conn->rsp.datasize + len > conn->rsp_buffer_size) { buffer = realloc(buffer, conn->rsp.datasize + len); if (buffer) { conn->rsp_buffer = buffer; conn->rsp.data = conn->rsp_buffer; conn->rsp_buffer_size = conn->rsp.datasize + len; } else goto drop; } buffer += conn->rsp.datasize; conn->rsp.datasize += len; strcpy(buffer, key); buffer += keylen; *buffer++ = '='; strcpy(buffer, value); return; drop: log_warning("Dropping key (%s=%s)", key, value); return; } static void text_key_add_reject(struct iscsi_connection *conn, char *key) { text_key_add(conn, key, "Reject"); } static void text_scan_security(struct iscsi_connection *conn) { struct iscsi_login_rsp *rsp = (struct iscsi_login_rsp *)&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_available(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_available(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_CLS_INITIATOR_ERR; rsp->status_detail = ISCSI_LOGIN_STATUS_AUTH_FAILED; conn->state = STATE_EXIT; } } static void login_security_done(struct iscsi_connection *conn) { struct iscsi_login *req = (struct iscsi_login *)&conn->req.bhs; struct iscsi_login_rsp *rsp = (struct iscsi_login_rsp *) &conn->rsp.bhs; struct iscsi_session *session; if (!conn->tid) return; session = session_find_name(conn->tid, conn->initiator, req->isid); if (session) { if (!req->tsih) { struct iscsi_connection *ent, *next; /* do session reinstatement */ session_get(session); list_for_each_entry_safe(ent, next, &session->conn_list, clist) { conn_close(ent); } session_put(session); session = NULL; } else if (req->tsih != session->tsih) { /* fail the login */ rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp->status_detail = ISCSI_LOGIN_STATUS_TGT_NOT_FOUND; conn->state = STATE_EXIT; return; } else if (conn_find(session, conn->cid)) { /* do connection reinstatement */ } /* add a new connection to the session */ if (session) conn_add_to_session(conn, session); } else { if (req->tsih) { /* fail the login */ rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp->status_detail = ISCSI_LOGIN_STATUS_NO_SESSION; conn->state = STATE_EXIT; return; } /* * We do nothing here and instantiate a new session * later at login_finish(). */ } } static void text_scan_login(struct iscsi_connection *conn) { char *key, *value, *data; int datasize, idx, is_rdma = 0; struct iscsi_login_rsp *rsp = (struct iscsi_login_rsp *)&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)) { int err; unsigned int val; char buf[32]; if (idx == ISCSI_PARAM_MAX_RECV_DLENGTH) idx = ISCSI_PARAM_MAX_XMIT_DLENGTH; if (idx == ISCSI_PARAM_RDMA_EXTENSIONS) is_rdma = 1; 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_CLS_INITIATOR_ERR; rsp->status_detail = ISCSI_LOGIN_STATUS_INIT_ERR; conn->state = STATE_EXIT; goto out; } } err = param_check_val(session_keys, idx, &val); if (err) { text_key_add_reject(conn, key); continue; } if (idx >= ISCSI_PARAM_FIRST_LOCAL) conn->session_param[idx].val = val; else param_set_val(session_keys, conn->session_param, idx, &val); switch (conn->session_param[idx].state) { case KEY_STATE_START: if (idx >= ISCSI_PARAM_FIRST_LOCAL) 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].val) { rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp->status_detail = ISCSI_LOGIN_STATUS_INIT_ERR; conn->state = STATE_EXIT; log_warning("%s %u %u\n", key, val, conn->session_param[idx].val); goto out; } break; case KEY_STATE_DONE: break; } conn->session_param[idx].state = KEY_STATE_DONE; } else text_key_add(conn, key, "NotUnderstood"); } if (is_rdma) { /* do not try to do digests, not supported in iser */ conn->session_param[ISCSI_PARAM_HDRDGST_EN].val = DIGEST_NONE; conn->session_param[ISCSI_PARAM_DATADGST_EN].val = DIGEST_NONE; } else { /* do not offer RDMA, initiator must explicitly request */ conn->session_param[ISCSI_PARAM_RDMA_EXTENSIONS].val = 0; } out: return; } static int text_check_param(struct iscsi_connection *conn) { struct 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].val != session_keys[i].def) { if (conn->state == STATE_LOGIN) { if (i >= ISCSI_PARAM_FIRST_LOCAL) { if (p[i].val > session_keys[i].def) p[i].val = session_keys[i].def; p[i].state = KEY_STATE_DONE; continue; } if (p[ISCSI_PARAM_RDMA_EXTENSIONS].val == 1) { if (i == ISCSI_PARAM_MAX_RECV_DLENGTH) continue; } else { if (i >= ISCSI_PARAM_RDMA_EXTENSIONS) continue; } memset(buf, 0, sizeof(buf)); param_val_to_str(session_keys, i, p[i].val, buf); text_key_add(conn, session_keys[i].name, buf); p[i].state = KEY_STATE_REQUEST; } cnt++; } } return cnt; } static void login_start(struct iscsi_connection *conn) { struct iscsi_login *req = (struct iscsi_login *)&conn->req.bhs; struct iscsi_login_rsp *rsp = (struct iscsi_login_rsp *)&conn->rsp.bhs; char *name, *alias, *session_type, *target_name; struct iscsi_target *target; char buf[NI_MAXHOST + NI_MAXSERV + 4]; int reason, redir; conn->cid = be16_to_cpu(req->cid); memcpy(conn->isid, req->isid, sizeof(req->isid)); conn->tsih = req->tsih; if (!sid64(conn->isid, conn->tsih)) { rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp->status_detail = ISCSI_LOGIN_STATUS_MISSING_FIELDS; conn->state = STATE_EXIT; return; } name = text_key_find(conn, "InitiatorName"); if (!name) { rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp->status_detail = ISCSI_LOGIN_STATUS_MISSING_FIELDS; conn->state = STATE_EXIT; return; } conn->initiator = strdup(name); alias = text_key_find(conn, "InitiatorAlias"); if (alias) conn->initiator_alias = strdup(alias); 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_CLS_INITIATOR_ERR; rsp->status_detail = ISCSI_LOGIN_STATUS_NO_SESSION_TYPE; conn->state = STATE_EXIT; return; } } if (conn->session_type == SESSION_DISCOVERY) conn->tid = GLOBAL_TID; if (conn->session_type == SESSION_NORMAL) { if (!target_name) { rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp->status_detail = ISCSI_LOGIN_STATUS_MISSING_FIELDS; conn->state = STATE_EXIT; return; } target = target_find_by_name(target_name); if (!target) { rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp->status_detail = ISCSI_LOGIN_STATUS_TGT_NOT_FOUND; conn->state = STATE_EXIT; return; } if (target->rdma) { eprintf("Target %s is RDMA, but conn cid:%d from %s is TCP\n", target_name, conn->cid, conn->initiator); rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp->status_detail = ISCSI_LOGIN_STATUS_TGT_NOT_FOUND; conn->state = STATE_EXIT; return; } conn->tid = target->tid; redir = target_redirected(target, conn, buf, &reason); if (redir < 0) { rsp->status_class = ISCSI_STATUS_CLS_TARGET_ERR; rsp->status_detail = ISCSI_LOGIN_STATUS_TARGET_ERROR; conn->state = STATE_EXIT; return; } else if (redir) { text_key_add(conn, "TargetAddress", buf); rsp->status_class = ISCSI_STATUS_CLS_REDIRECT; rsp->status_detail = reason; conn->state = STATE_EXIT; return; } if (tgt_get_target_state(target->tid) != SCSI_TARGET_READY) { rsp->status_class = ISCSI_STATUS_CLS_TARGET_ERR; rsp->status_detail = ISCSI_LOGIN_STATUS_TARGET_ERROR; conn->state = STATE_EXIT; return; } if (ip_acl(conn->tid, conn) && iqn_acl(conn->tid, conn)) { rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp->status_detail = ISCSI_LOGIN_STATUS_TGT_NOT_FOUND; conn->state = STATE_EXIT; return; } if (isns_scn_access(conn->tid, name)) { rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp->status_detail = ISCSI_LOGIN_STATUS_TGT_NOT_FOUND; conn->state = STATE_EXIT; return; } /* if (conn->target->max_sessions && */ /* (++conn->target->session_cnt > conn->target->max_sessions)) { */ /* conn->target->session_cnt--; */ /* rsp->status_class = ISCSI_STATUS_INITIATOR_ERR; */ /* rsp->status_detail = ISCSI_STATUS_TOO_MANY_CONN; */ /* conn->state = STATE_EXIT; */ /* return; */ /* } */ memcpy(conn->session_param, target->session_param, sizeof(conn->session_param)); } conn->exp_cmd_sn = be32_to_cpu(req->cmdsn); conn->max_cmd_sn = conn->exp_cmd_sn + 1; dprintf("exp_cmd_sn: %d,%d\n", conn->exp_cmd_sn, req->cmdsn); text_key_add(conn, "TargetPortalGroupTag", "1"); } static void login_finish(struct iscsi_connection *conn) { struct iscsi_login_rsp *rsp = (struct iscsi_login_rsp *) &conn->rsp.bhs; int ret; uint8_t class, detail; switch (conn->session_type) { case SESSION_NORMAL: /* * Allocate transport resources for this connection. */ ret = conn->tp->ep_login_complete(conn); if (ret) { class = ISCSI_STATUS_CLS_TARGET_ERR; detail = ISCSI_LOGIN_STATUS_NO_RESOURCES; goto fail; } if (!conn->session) { ret = session_create(conn); if (ret) { class = ISCSI_STATUS_CLS_TARGET_ERR; detail = ISCSI_LOGIN_STATUS_TARGET_ERROR; goto fail; } } else { if (conn->tp->rdma ^ conn->session->rdma) { eprintf("new conn rdma %d, but session %d\n", conn->tp->rdma, conn->session->rdma); class = ISCSI_STATUS_CLS_INITIATOR_ERR; detail =ISCSI_LOGIN_STATUS_INVALID_REQUEST; goto fail; } } memcpy(conn->isid, conn->session->isid, sizeof(conn->isid)); conn->tsih = conn->session->tsih; break; case SESSION_DISCOVERY: /* set a dummy tsih value */ conn->tsih = 1; break; } return; fail: rsp->flags = 0; rsp->status_class = class; rsp->status_detail = detail; conn->state = STATE_EXIT; return; } static int cmnd_exec_auth(struct iscsi_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: eprintf("Unknown auth. method %d\n", conn->auth_method); res = -3; } return res; } static void cmnd_reject(struct iscsi_connection *conn, uint8_t reason) { struct iscsi_reject *rsp = (struct iscsi_reject *)&conn->rsp.bhs; memset(rsp, 0, BHS_SIZE); rsp->opcode = ISCSI_OP_REJECT; rsp->reason = reason; rsp->ffffffff = ISCSI_RESERVED_TAG; rsp->flags = ISCSI_FLAG_CMD_FINAL; rsp->statsn = cpu_to_be32(conn->stat_sn++); rsp->exp_cmdsn = cpu_to_be32(conn->exp_cmd_sn); rsp->max_cmdsn = cpu_to_be32(conn->max_cmd_sn); conn->rsp.data = conn->rsp_buffer; conn->rsp.datasize = BHS_SIZE; memcpy(conn->rsp.data, &conn->req.bhs, BHS_SIZE); } static void cmnd_exec_login(struct iscsi_connection *conn) { struct iscsi_login *req = (struct iscsi_login *)&conn->req.bhs; struct iscsi_login_rsp *rsp = (struct iscsi_login_rsp *)&conn->rsp.bhs; int stay = 0, nsg_disagree = 0; memset(rsp, 0, BHS_SIZE); if ((req->opcode & ISCSI_OPCODE_MASK) != ISCSI_OP_LOGIN || !(req->opcode & ISCSI_OP_IMMEDIATE)) { cmnd_reject(conn, ISCSI_REASON_PROTOCOL_ERROR); return; } rsp->opcode = ISCSI_OP_LOGIN_RSP; rsp->max_version = ISCSI_DRAFT20_VERSION; rsp->active_version = ISCSI_DRAFT20_VERSION; rsp->itt = req->itt; if (/* req->max_version < ISCSI_VERSION || */ req->min_version > ISCSI_DRAFT20_VERSION) { rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp->status_detail = ISCSI_LOGIN_STATUS_NO_VERSION; conn->state = STATE_EXIT; return; } switch (ISCSI_LOGIN_CURRENT_STAGE(req->flags)) { case ISCSI_SECURITY_NEGOTIATION_STAGE: dprintf("Login request (security negotiation): %d\n", conn->state); rsp->flags = ISCSI_SECURITY_NEGOTIATION_STAGE << 2; switch (conn->state) { case STATE_FREE: conn->state = STATE_SECURITY; login_start(conn); if (rsp->status_class) return; /* 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_OP_PARMS_NEGOTIATION_STAGE: dprintf("Login request (operational negotiation): %d\n", conn->state); rsp->flags = ISCSI_OP_PARMS_NEGOTIATION_STAGE << 2; switch (conn->state) { case STATE_FREE: conn->state = STATE_LOGIN; login_start(conn); if (account_available(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_FLAG_LOGIN_TRANSIT) { int nsg = ISCSI_LOGIN_NEXT_STAGE(req->flags); switch (nsg) { case ISCSI_OP_PARMS_NEGOTIATION_STAGE: 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_FULL_FEATURE_PHASE: switch (conn->state) { case STATE_SECURITY: case STATE_SECURITY_DONE: if ((nsg_disagree = text_check_param(conn))) { conn->state = STATE_LOGIN; nsg = ISCSI_OP_PARMS_NEGOTIATION_STAGE; break; } conn->state = STATE_SECURITY_FULL; login_security_done(conn); break; case STATE_LOGIN: if (stay) nsg = ISCSI_OP_PARMS_NEGOTIATION_STAGE; else conn->state = STATE_LOGIN_FULL; break; default: goto init_err; } if (!stay && !nsg_disagree) { login_finish(conn); if (rsp->status_class) return; } break; default: goto init_err; } rsp->flags |= nsg | (stay ? 0 : ISCSI_FLAG_LOGIN_TRANSIT); } memcpy(rsp->isid, conn->isid, sizeof(rsp->isid)); rsp->tsih = conn->tsih; rsp->statsn = cpu_to_be32(conn->stat_sn++); rsp->exp_cmdsn = cpu_to_be32(conn->exp_cmd_sn); rsp->max_cmdsn = cpu_to_be32(conn->max_cmd_sn); return; init_err: rsp->flags = 0; rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp->status_detail = ISCSI_LOGIN_STATUS_INIT_ERR; conn->state = STATE_EXIT; return; auth_err: rsp->flags = 0; rsp->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp->status_detail = ISCSI_LOGIN_STATUS_AUTH_FAILED; conn->state = STATE_EXIT; return; } static void text_scan_text(struct iscsi_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, buf[NI_MAXHOST + 128]; int ret, port; if (value[0] == 0) continue; p = buf; blen = sizeof(buf); slen = sizeof(ss); ret = conn->tp->ep_getsockname(conn, (struct sockaddr *)&ss, &slen); if (ret) { eprintf("getsockname failed\n"); continue; } if (ss.ss_family == AF_INET6) { *p++ = '['; blen--; } ret = getnameinfo((struct sockaddr *)&ss, slen, p, blen, NULL, 0, NI_NUMERICHOST); if (ret) { eprintf("getnameinfo failed, %m\n"); continue; } /* strip zone id */ if (ss.ss_family == AF_INET6) (void) strsep(&p, "%"); p = buf + strlen(buf); if (ss.ss_family == AF_INET6) *p++ = ']'; if (ss.ss_family == AF_INET6) port = ntohs(((struct sockaddr_in6 *) &ss)->sin6_port); else port = ntohs(((struct sockaddr_in *) &ss)->sin_port); sprintf(p, ":%d,1", port); target_list_build(conn, buf, strcmp(value, "All") ? value : NULL); } else text_key_add(conn, key, "NotUnderstood"); } } static void cmnd_exec_text(struct iscsi_connection *conn) { struct iscsi_text *req = (struct iscsi_text *)&conn->req.bhs; struct iscsi_text_rsp *rsp = (struct iscsi_text_rsp *)&conn->rsp.bhs; int max_len = conn->session_param[ISCSI_PARAM_MAX_XMIT_DLENGTH].val; memset(rsp, 0, BHS_SIZE); rsp->opcode = ISCSI_OP_TEXT_RSP; rsp->itt = req->itt; conn->exp_cmd_sn = be32_to_cpu(req->cmdsn); if (!(req->opcode & ISCSI_OP_IMMEDIATE)) conn->exp_cmd_sn++; if (be32_to_cpu(req->ttt) == ISCSI_RESERVED_TAG) { conn->text_datasize = 0; text_scan_text(conn); conn->text_rsp_buffer = conn->rsp_buffer; conn->text_datasize = conn->rsp.datasize; if (conn->text_datasize > max_len) { conn->ttt++; if (conn->ttt == ISCSI_RESERVED_TAG) conn->ttt++; } else conn->ttt = ISCSI_RESERVED_TAG; } else if (!conn->text_datasize || conn->ttt != be32_to_cpu(req->ttt)) { cmnd_reject(conn, ISCSI_REASON_INVALID_PDU_FIELD); return; } if (conn->text_datasize <= max_len) { rsp->flags = ISCSI_FLAG_CMD_FINAL; conn->ttt = ISCSI_RESERVED_TAG; } conn->rsp.datasize = min(max_len, conn->text_datasize); conn->rsp.data = conn->text_rsp_buffer; conn->text_rsp_buffer += conn->rsp.datasize; conn->text_datasize -= conn->rsp.datasize; rsp->ttt = cpu_to_be32(conn->ttt); rsp->statsn = cpu_to_be32(conn->stat_sn++); rsp->exp_cmdsn = cpu_to_be32(conn->exp_cmd_sn); rsp->max_cmdsn = cpu_to_be32(conn->max_cmd_sn); } static void cmnd_exec_logout(struct iscsi_connection *conn) { struct iscsi_logout *req = (struct iscsi_logout *)&conn->req.bhs; struct iscsi_logout_rsp *rsp = (struct iscsi_logout_rsp *)&conn->rsp.bhs; memset(rsp, 0, BHS_SIZE); rsp->opcode = ISCSI_OP_LOGOUT_RSP; rsp->flags = ISCSI_FLAG_CMD_FINAL; rsp->itt = req->itt; conn->exp_cmd_sn = be32_to_cpu(req->cmdsn); if (!(req->opcode & ISCSI_OP_IMMEDIATE)) conn->exp_cmd_sn++; rsp->statsn = cpu_to_be32(conn->stat_sn++); rsp->exp_cmdsn = cpu_to_be32(conn->exp_cmd_sn); rsp->max_cmdsn = cpu_to_be32(conn->max_cmd_sn); } static int cmnd_execute(struct iscsi_connection *conn) { int res = 0; switch (conn->req.bhs.opcode & ISCSI_OPCODE_MASK) { case ISCSI_OP_LOGIN: cmnd_exec_login(conn); conn->rsp.bhs.hlength = conn->rsp.ahssize / 4; hton24(conn->rsp.bhs.dlength, conn->rsp.datasize); break; case ISCSI_OP_TEXT: cmnd_exec_text(conn); conn->rsp.bhs.hlength = conn->rsp.ahssize / 4; hton24(conn->rsp.bhs.dlength, conn->rsp.datasize); break; case ISCSI_OP_LOGOUT: cmnd_exec_logout(conn); conn->rsp.bhs.hlength = conn->rsp.ahssize / 4; hton24(conn->rsp.bhs.dlength, conn->rsp.datasize); break; default: cmnd_reject(conn, ISCSI_REASON_CMD_NOT_SUPPORTED); res = 1; break; } return res; } static void cmnd_finish(struct iscsi_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; } } void iscsi_set_data_rsp_residual(struct iscsi_data_rsp *data_in, struct scsi_cmd *scmd) { int32_t resid = scsi_get_in_resid(scmd); if (likely(!resid)) data_in->residual_count = 0; else if (resid > 0) { data_in->flags |= ISCSI_FLAG_CMD_UNDERFLOW; data_in->residual_count = cpu_to_be32((uint32_t)resid); } else { data_in->flags |= ISCSI_FLAG_CMD_OVERFLOW; data_in->residual_count = cpu_to_be32((uint32_t)-resid); } } static inline void iscsi_rsp_set_resid(struct iscsi_cmd_rsp *rsp, int32_t resid) { if (likely(!resid)) rsp->residual_count = 0; else if (resid > 0) { rsp->flags |= ISCSI_FLAG_CMD_UNDERFLOW; rsp->residual_count = cpu_to_be32((uint32_t)resid); } else { rsp->flags |= ISCSI_FLAG_CMD_OVERFLOW; rsp->residual_count = cpu_to_be32((uint32_t)-resid); } } static inline void iscsi_rsp_set_bidir_resid(struct iscsi_cmd_rsp *rsp, int32_t resid) { if (likely(!resid)) rsp->bi_residual_count = 0; else if (resid > 0) { rsp->flags |= ISCSI_FLAG_CMD_BIDI_UNDERFLOW; rsp->bi_residual_count = cpu_to_be32((uint32_t)resid); } else { rsp->flags |= ISCSI_FLAG_CMD_BIDI_OVERFLOW; rsp->bi_residual_count = cpu_to_be32((uint32_t)-resid); } } void iscsi_rsp_set_residual(struct iscsi_cmd_rsp *rsp, struct scsi_cmd *scmd) { rsp->bi_residual_count = 0; if (scsi_get_data_dir(scmd) == DATA_READ) iscsi_rsp_set_resid(rsp, scsi_get_in_resid(scmd)); else if (scsi_get_data_dir(scmd) == DATA_WRITE) iscsi_rsp_set_resid(rsp, scsi_get_out_resid(scmd)); else if (scsi_get_data_dir(scmd) == DATA_BIDIRECTIONAL) { iscsi_rsp_set_bidir_resid(rsp, scsi_get_in_resid(scmd)); iscsi_rsp_set_resid(rsp, scsi_get_out_resid(scmd)); } else rsp->residual_count = 0; } struct iscsi_sense_data { uint16_t length; uint8_t data[0]; } __attribute__((__packed__)); static int iscsi_cmd_rsp_build(struct iscsi_task *task) { struct iscsi_connection *conn = task->conn; struct iscsi_cmd_rsp *rsp = (struct iscsi_cmd_rsp *) &conn->rsp.bhs; struct iscsi_sense_data *sense; unsigned char sense_len; memset(rsp, 0, sizeof(*rsp)); rsp->opcode = ISCSI_OP_SCSI_CMD_RSP; rsp->itt = task->tag; rsp->flags = ISCSI_FLAG_CMD_FINAL; rsp->response = ISCSI_STATUS_CMD_COMPLETED; rsp->cmd_status = scsi_get_result(&task->scmd); rsp->statsn = cpu_to_be32(conn->stat_sn++); rsp->exp_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn); rsp->max_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn + conn->session->max_queue_cmd); iscsi_rsp_set_residual(rsp, &task->scmd); sense_len = task->scmd.sense_len; if (sense_len) { sense = (struct iscsi_sense_data *)task->scmd.sense_buffer; memmove(sense->data, sense, sense_len); sense->length = cpu_to_be16(sense_len); conn->rsp.datasize = sense_len + sizeof(*sense); hton24(rsp->dlength, sense_len + sizeof(*sense)); conn->rsp.data = sense; } return 0; } static int iscsi_data_rsp_build(struct iscsi_task *task) { struct iscsi_connection *conn = task->conn; struct iscsi_data_rsp *rsp = (struct iscsi_data_rsp *) &conn->rsp.bhs; int datalen, maxdatalen; int result = scsi_get_result(&task->scmd); memset(rsp, 0, sizeof(*rsp)); rsp->opcode = ISCSI_OP_SCSI_DATA_IN; rsp->itt = task->tag; rsp->ttt = cpu_to_be32(ISCSI_RESERVED_TAG); rsp->offset = cpu_to_be32(task->offset); rsp->datasn = cpu_to_be32(task->exp_r2tsn++); datalen = scsi_get_in_transfer_len(&task->scmd) - task->offset; maxdatalen = conn->tp->rdma ? conn->session_param[ISCSI_PARAM_MAX_BURST].val : conn->session_param[ISCSI_PARAM_MAX_XMIT_DLENGTH].val; dprintf("%d %d %d %" PRIu32 "%x\n", datalen, scsi_get_in_transfer_len(&task->scmd), task->offset, maxdatalen, rsp->itt); if (datalen <= maxdatalen) { rsp->flags = ISCSI_FLAG_CMD_FINAL; /* collapse status into final packet if successful */ if (result == SAM_STAT_GOOD && scsi_get_data_dir(&task->scmd) != DATA_BIDIRECTIONAL && !conn->tp->rdma) { rsp->flags |= ISCSI_FLAG_DATA_STATUS; rsp->cmd_status = result; rsp->statsn = cpu_to_be32(conn->stat_sn++); iscsi_set_data_rsp_residual(rsp, &task->scmd); } } else datalen = maxdatalen; rsp->exp_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn); rsp->max_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn + conn->session->max_queue_cmd); conn->rsp.datasize = datalen; hton24(rsp->dlength, datalen); conn->rsp.data = scsi_get_in_buffer(&task->scmd); conn->rsp.data += task->offset; task->offset += datalen; return 0; } static int iscsi_r2t_build(struct iscsi_task *task) { struct iscsi_connection *conn = task->conn; struct iscsi_r2t_rsp *rsp = (struct iscsi_r2t_rsp *) &conn->rsp.bhs; uint32_t length; memset(rsp, 0, sizeof(*rsp)); rsp->opcode = ISCSI_OP_R2T; rsp->flags = ISCSI_FLAG_CMD_FINAL; memcpy(rsp->lun, task->req.lun, sizeof(rsp->lun)); rsp->itt = task->req.itt; rsp->r2tsn = cpu_to_be32(task->exp_r2tsn++); rsp->data_offset = cpu_to_be32(task->offset); /* return next statsn for this conn w/o advancing it */ rsp->statsn = cpu_to_be32(conn->stat_sn); rsp->exp_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn); rsp->max_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn + conn->session->max_queue_cmd); rsp->ttt = (unsigned long) task; length = min_t(uint32_t, task->r2t_count, conn->session_param[ISCSI_PARAM_MAX_BURST].val); rsp->data_length = cpu_to_be32(length); return 0; } static struct iscsi_task *iscsi_alloc_task(struct iscsi_connection *conn, int ext_len, int data_len) { struct iscsi_hdr *req = (struct iscsi_hdr *) &conn->req.bhs; struct iscsi_task *task; void *buf; task = conn->tp->alloc_task(conn, ext_len); if (!task) return NULL; if (data_len) { buf = conn->tp->alloc_data_buf(conn, data_len); if (!buf) { conn->tp->free_task(task); return NULL; } task->data = buf; } memcpy(&task->req, req, sizeof(*req)); task->conn = conn; INIT_LIST_HEAD(&task->c_hlist); INIT_LIST_HEAD(&task->c_list); list_add(&task->c_siblings, &conn->task_list); conn_get(conn); return task; } void iscsi_free_task(struct iscsi_task *task) { struct iscsi_connection *conn = task->conn; list_del(&task->c_siblings); if (task_opcode(task) == ISCSI_OP_SCSI_CMD) list_del(&task->c_hlist); conn->tp->free_data_buf(conn, scsi_get_in_buffer(&task->scmd)); conn->tp->free_data_buf(conn, scsi_get_out_buffer(&task->scmd)); /* * If freeing task before in/out buffers are set, make sure to free * task->data or it leaks. */ if ((task->data != scsi_get_in_buffer(&task->scmd)) && (task->data != scsi_get_out_buffer(&task->scmd))) conn->tp->free_data_buf(conn, task->data); conn->tp->free_task(task); conn_put(conn); } static inline struct iscsi_task *ITASK(struct scsi_cmd *scmd) { return container_of(scmd, struct iscsi_task, scmd); } void iscsi_free_cmd_task(struct iscsi_task *task) { target_cmd_done(&task->scmd); iscsi_free_task(task); } static int iscsi_scsi_cmd_done(uint64_t nid, int result, struct scsi_cmd *scmd) { struct iscsi_task *task = ITASK(scmd); /* * Since the connection is closed we just free the task. * We could delay the closing of the conn in some cases and send * the response with a little extra code or we can check if this * task got reassinged to another connection. */ if (task->conn->state == STATE_CLOSE) { iscsi_free_cmd_task(task); return 0; } list_add_tail(&task->c_list, &task->conn->tx_clist); task->conn->tp->ep_event_modify(task->conn, EPOLLIN | EPOLLOUT); return 0; } static int cmd_attr(struct iscsi_task *task) { int attr; struct iscsi_cmd *req = (struct iscsi_cmd *) &task->req; switch (req->flags & ISCSI_FLAG_CMD_ATTR_MASK) { case ISCSI_ATTR_UNTAGGED: case ISCSI_ATTR_SIMPLE: attr = MSG_SIMPLE_TAG; break; case ISCSI_ATTR_HEAD_OF_QUEUE: attr = MSG_HEAD_TAG; break; case ISCSI_ATTR_ORDERED: default: attr = MSG_ORDERED_TAG; } return attr; } static int iscsi_target_cmd_queue(struct iscsi_task *task) { struct scsi_cmd *scmd = &task->scmd; struct iscsi_connection *conn = task->conn; struct iscsi_cmd *req = (struct iscsi_cmd *) &task->req; uint32_t data_len; uint8_t *ahs; int ahslen; int err; enum data_direction dir = scsi_get_data_dir(scmd); scmd->cmd_itn_id = conn->session->tsih; scmd->scb = req->cdb; scmd->scb_len = sizeof(req->cdb); ahs = task->ahs; ahslen = req->hlength * 4; if (ahslen >= 4) { struct iscsi_ecdb_ahdr *ahs_extcdb = (void *) ahs; if (ahs_extcdb->ahstype == ISCSI_AHSTYPE_CDB) { int extcdb_len = ntohs(ahs_extcdb->ahslength) - 1; unsigned char *p = (void *)task->extdata; if (4 + extcdb_len > ahslen) { eprintf("AHS len %d too short for extcdb %d\n", ahslen, extcdb_len); return -EINVAL; } if (extcdb_len + sizeof(req->cdb) > 260) { eprintf("invalid extcdb len %d\n", extcdb_len); return -EINVAL; } memcpy(p, req->cdb, sizeof(req->cdb)); memmove(p + sizeof(req->cdb), ahs_extcdb->ecdb, extcdb_len); scmd->scb = p; scmd->scb_len = sizeof(req->cdb) + extcdb_len; ahs += 4 + extcdb_len; ahslen -= 4 + extcdb_len; } } data_len = ntohl(req->data_length); /* figure out incoming (write) and outgoing (read) sizes */ if (dir == DATA_WRITE || dir == DATA_BIDIRECTIONAL) { scsi_set_out_length(scmd, data_len); scsi_set_out_buffer(scmd, task->data); } else if (dir == DATA_READ) { scsi_set_in_length(scmd, data_len); scsi_set_in_buffer(scmd, task->data); } if (dir == DATA_BIDIRECTIONAL && ahslen >= 8) { struct iscsi_rlength_ahdr *ahs_bidi = (void *) ahs; if (ahs_bidi->ahstype == ISCSI_AHSTYPE_RLENGTH) { uint32_t in_length = ntohl(ahs_bidi->read_length); dprintf("bidi read len %u\n", in_length); if (in_length) { uint32_t len; void *buf; len = roundup(in_length, conn->tp->data_padding); buf = conn->tp->alloc_data_buf(conn, len); if (!buf) return -ENOMEM; scsi_set_in_buffer(scmd, buf); scsi_set_in_length(scmd, in_length); } } } memcpy(scmd->lun, task->req.lun, sizeof(scmd->lun)); scmd->attribute = cmd_attr(task); scmd->tag = req->itt; set_task_in_scsi(task); err = target_cmd_queue(conn->session->target->tid, scmd); if (err) clear_task_in_scsi(task); return err; } int iscsi_scsi_cmd_execute(struct iscsi_task *task) { struct iscsi_connection *conn = task->conn; struct iscsi_cmd *req = (struct iscsi_cmd *) &task->req; int ret = 0; if ((req->flags & ISCSI_FLAG_CMD_WRITE) && task->r2t_count) { if (!task->unsol_count) list_add_tail(&task->c_list, &task->conn->tx_clist); goto no_queuing; } task->offset = 0; /* for use as transmit pointer for data-ins */ ret = iscsi_target_cmd_queue(task); no_queuing: conn->tp->ep_event_modify(conn, EPOLLIN | EPOLLOUT); return ret; } static int iscsi_tm_done(struct mgmt_req *mreq) { struct iscsi_task *task; task = (struct iscsi_task *) (unsigned long) mreq->mid; switch (mreq->result) { case 0: task->result = ISCSI_TMF_RSP_COMPLETE; break; case -EINVAL: task->result = ISCSI_TMF_RSP_NOT_SUPPORTED; break; case -EEXIST: /* * the command completed or we could not find it so * we retrun no task here */ task->result = ISCSI_TMF_RSP_NO_TASK; break; default: task->result = ISCSI_TMF_RSP_REJECTED; break; } if (task->conn->state == STATE_CLOSE) { iscsi_free_task(task); return 0; } list_add_tail(&task->c_list, &task->conn->tx_clist); task->conn->tp->ep_event_modify(task->conn, EPOLLIN | EPOLLOUT); return 0; } static int iscsi_tm_execute(struct iscsi_task *task) { struct iscsi_connection *conn = task->conn; struct iscsi_tm *req = (struct iscsi_tm *) &task->req; int fn = 0, err = 0; switch (req->flags & ISCSI_FLAG_TM_FUNC_MASK) { case ISCSI_TM_FUNC_ABORT_TASK: fn = ABORT_TASK; break; case ISCSI_TM_FUNC_ABORT_TASK_SET: fn = ABORT_TASK_SET; break; case ISCSI_TM_FUNC_CLEAR_ACA: err = ISCSI_TMF_RSP_NOT_SUPPORTED; break; case ISCSI_TM_FUNC_CLEAR_TASK_SET: err = ISCSI_TMF_RSP_NOT_SUPPORTED; break; case ISCSI_TM_FUNC_LOGICAL_UNIT_RESET: fn = LOGICAL_UNIT_RESET; break; case ISCSI_TM_FUNC_TARGET_WARM_RESET: case ISCSI_TM_FUNC_TARGET_COLD_RESET: case ISCSI_TM_FUNC_TASK_REASSIGN: err = ISCSI_TMF_RSP_NOT_SUPPORTED; break; default: err = ISCSI_TMF_RSP_REJECTED; eprintf("unknown task management function %d\n", req->flags & ISCSI_FLAG_TM_FUNC_MASK); } if (err) task->result = err; else { int ret; ret = target_mgmt_request(conn->session->target->tid, conn->session->tsih, (unsigned long)task, fn, req->lun, req->rtt, 0); set_task_in_scsi(task); switch (ret) { case MGMT_REQ_QUEUED: break; case MGMT_REQ_FAILED: case MGMT_REQ_DONE: clear_task_in_scsi(task); break; } } return err; } static int iscsi_task_execute(struct iscsi_task *task) { struct iscsi_hdr *hdr = (struct iscsi_hdr *) &task->req; uint8_t op = hdr->opcode & ISCSI_OPCODE_MASK; int err; switch (op) { case ISCSI_OP_NOOP_OUT: case ISCSI_OP_LOGOUT: list_add_tail(&task->c_list, &task->conn->tx_clist); task->conn->tp->ep_event_modify(task->conn, EPOLLIN | EPOLLOUT); break; case ISCSI_OP_SCSI_CMD: /* convenient directionality for our internal use */ if (hdr->flags & ISCSI_FLAG_CMD_READ) { if (hdr->flags & ISCSI_FLAG_CMD_WRITE) scsi_set_data_dir(&task->scmd, DATA_BIDIRECTIONAL); else scsi_set_data_dir(&task->scmd, DATA_READ); } else if (hdr->flags & ISCSI_FLAG_CMD_WRITE) { scsi_set_data_dir(&task->scmd, DATA_WRITE); } else scsi_set_data_dir(&task->scmd, DATA_NONE); err = iscsi_scsi_cmd_execute(task); break; case ISCSI_OP_SCSI_TMFUNC: err = iscsi_tm_execute(task); if (err) { list_add_tail(&task->c_list, &task->conn->tx_clist); task->conn->tp->ep_event_modify(task->conn, EPOLLIN | EPOLLOUT); } break; case ISCSI_OP_TEXT: case ISCSI_OP_SNACK: break; default: break; } return 0; } static int iscsi_data_out_rx_done(struct iscsi_task *task) { struct iscsi_hdr *hdr = &task->conn->req.bhs; int err = 0; if (hdr->ttt == cpu_to_be32(ISCSI_RESERVED_TAG)) { if (hdr->flags & ISCSI_FLAG_CMD_FINAL) { task->unsol_count = 0; if (!task_pending(task)) err = iscsi_scsi_cmd_execute(task); } } else { if (!(hdr->flags & ISCSI_FLAG_CMD_FINAL)) return err; err = iscsi_scsi_cmd_execute(task); } return err; } static int iscsi_data_out_rx_start(struct iscsi_connection *conn) { struct iscsi_task *task; struct iscsi_data *req = (struct iscsi_data *) &conn->req.bhs; list_for_each_entry(task, &conn->session->cmd_list, c_hlist) { if (task->tag == req->itt) goto found; } return -EINVAL; found: dprintf("found a task %" PRIx64 " %u %u %u %u %u\n", task->tag, ntohl(((struct iscsi_cmd *) (&task->req))->data_length), task->offset, task->r2t_count, ntoh24(req->dlength), be32_to_cpu(req->offset)); conn->req.data = task->data + be32_to_cpu(req->offset); task->offset += ntoh24(req->dlength); task->r2t_count -= ntoh24(req->dlength); conn->rx_task = task; return 0; } static int iscsi_task_queue(struct iscsi_task *task) { struct iscsi_session *session = task->conn->session; struct iscsi_hdr *req = (struct iscsi_hdr *) &task->req; uint32_t cmd_sn; struct iscsi_task *ent; dprintf("%x %x %x\n", be32_to_cpu(req->statsn), session->exp_cmd_sn, req->opcode); if (req->opcode & ISCSI_OP_IMMEDIATE) return iscsi_task_execute(task); cmd_sn = be32_to_cpu(req->statsn); if (cmd_sn == session->exp_cmd_sn) { retry: session->exp_cmd_sn = ++cmd_sn; /* Should we close the connection... */ iscsi_task_execute(task); if (list_empty(&session->pending_cmd_list)) return 0; task = list_first_entry(&session->pending_cmd_list, struct iscsi_task, c_list); if (be32_to_cpu(task->req.statsn) != cmd_sn) return 0; list_del(&task->c_list); clear_task_pending(task); goto retry; } else { if (before(cmd_sn, session->exp_cmd_sn)) { eprintf("unexpected cmd_sn (%u,%u)\n", cmd_sn, session->exp_cmd_sn); return -EINVAL; } /* TODO: check max cmd_sn */ list_for_each_entry(ent, &session->pending_cmd_list, c_list) { if (before(cmd_sn, be32_to_cpu(ent->req.statsn))) break; } list_add_tail(&task->c_list, &ent->c_list); set_task_pending(task); } return 0; } static int iscsi_scsi_cmd_rx_start(struct iscsi_connection *conn) { struct iscsi_cmd *req = (struct iscsi_cmd *) &conn->req.bhs; struct iscsi_task *task; int ahs_len, imm_len, data_len, ext_len; ahs_len = req->hlength * 4; imm_len = roundup(ntoh24(req->dlength), conn->tp->data_padding); data_len = roundup(ntohl(req->data_length), conn->tp->data_padding); dprintf("%u %x %d %d %d %x %x\n", conn->session->tsih, req->cdb[0], ahs_len, imm_len, data_len, req->flags & ISCSI_FLAG_CMD_ATTR_MASK, req->itt); ext_len = ahs_len ? sizeof(req->cdb) + ahs_len : 0; task = iscsi_alloc_task(conn, ext_len, max(imm_len, data_len)); if (task) conn->rx_task = task; else return -ENOMEM; task->tag = req->itt; if (ahs_len) { task->ahs = (uint8_t *) task->extdata + sizeof(req->cdb); conn->req.ahs = task->ahs; conn->req.data = task->data; } else if (data_len) conn->req.data = task->data; if (req->flags & ISCSI_FLAG_CMD_WRITE) { task->offset = ntoh24(req->dlength); task->r2t_count = ntohl(req->data_length) - task->offset; task->unsol_count = !(req->flags & ISCSI_FLAG_CMD_FINAL); dprintf("%d %d %d %d\n", conn->rx_size, task->r2t_count, task->unsol_count, task->offset); } list_add(&task->c_hlist, &conn->session->cmd_list); return 0; } static int iscsi_noop_out_rx_start(struct iscsi_connection *conn) { struct iscsi_hdr *req = (struct iscsi_hdr *) &conn->req.bhs; struct iscsi_task *task; int len, err = 0; dprintf("%x %x %u\n", req->ttt, req->itt, ntoh24(req->dlength)); if (req->ttt != cpu_to_be32(ISCSI_RESERVED_TAG)) { if ((req->opcode & ISCSI_OPCODE_MASK) == ISCSI_OP_NOOP_OUT) { goto good; } } if (req->itt == cpu_to_be32(ISCSI_RESERVED_TAG)) { if (!(req->opcode & ISCSI_OP_IMMEDIATE)) { eprintf("initiator bug\n"); err = -ISCSI_REASON_PROTOCOL_ERROR; goto out; } } good: conn->exp_stat_sn = be32_to_cpu(req->exp_statsn); len = ntoh24(req->dlength); task = iscsi_alloc_task(conn, 0, len); if (task) conn->rx_task = task; else { err = -ENOMEM; goto out; } if (len) { task->len = len; conn->req.data = task->data; } out: return err; } static int iscsi_task_rx_done(struct iscsi_connection *conn) { struct iscsi_hdr *hdr = &conn->req.bhs; struct iscsi_task *task = conn->rx_task; uint8_t op; int err = 0; op = hdr->opcode & ISCSI_OPCODE_MASK; switch (op) { case ISCSI_OP_SCSI_CMD: case ISCSI_OP_NOOP_OUT: case ISCSI_OP_SCSI_TMFUNC: case ISCSI_OP_LOGOUT: err = iscsi_task_queue(task); break; case ISCSI_OP_SCSI_DATA_OUT: err = iscsi_data_out_rx_done(task); break; case ISCSI_OP_TEXT: case ISCSI_OP_SNACK: default: eprintf("Cannot handle yet %x\n", op); break; } conn->rx_task = NULL; return err; } static int iscsi_task_rx_start(struct iscsi_connection *conn) { struct iscsi_hdr *hdr = &conn->req.bhs; struct iscsi_task *task; uint8_t op; int err = 0; op = hdr->opcode & ISCSI_OPCODE_MASK; switch (op) { case ISCSI_OP_SCSI_CMD: err = iscsi_scsi_cmd_rx_start(conn); if (!err) conn->exp_stat_sn = be32_to_cpu(hdr->exp_statsn); break; case ISCSI_OP_SCSI_DATA_OUT: err = iscsi_data_out_rx_start(conn); if (!err) conn->exp_stat_sn = be32_to_cpu(hdr->exp_statsn); break; case ISCSI_OP_NOOP_OUT: err = iscsi_noop_out_rx_start(conn); break; case ISCSI_OP_SCSI_TMFUNC: case ISCSI_OP_LOGOUT: task = iscsi_alloc_task(conn, 0, 0); if (task) conn->rx_task = task; else err = -ENOMEM; break; case ISCSI_OP_TEXT: case ISCSI_OP_SNACK: eprintf("Cannot handle yet %x\n", op); err = -EINVAL; break; default: eprintf("Unknown op %x\n", op); err = -EINVAL; break; } return err; } static int iscsi_scsi_cmd_tx_start(struct iscsi_task *task) { enum data_direction data_dir = scsi_get_data_dir(&task->scmd); int err = 0; switch (data_dir) { case DATA_NONE: err = iscsi_cmd_rsp_build(task); break; case DATA_READ: if (task->offset < scsi_get_in_transfer_len(&task->scmd)) err = iscsi_data_rsp_build(task); else err = iscsi_cmd_rsp_build(task); break; case DATA_WRITE: if (task->r2t_count) err = iscsi_r2t_build(task); else err = iscsi_cmd_rsp_build(task); break; case DATA_BIDIRECTIONAL: if (task->r2t_count) err = iscsi_r2t_build(task); else if (task->offset < scsi_get_in_transfer_len(&task->scmd)) err = iscsi_data_rsp_build(task); else err = iscsi_cmd_rsp_build(task); break; default: eprintf("Unexpected data_dir %d task %p\n", data_dir, task); exit(-1); } return err; } static int iscsi_logout_tx_start(struct iscsi_task *task) { struct iscsi_connection *conn = task->conn; struct iscsi_logout_rsp *rsp = (struct iscsi_logout_rsp *) &conn->rsp.bhs; rsp->opcode = ISCSI_OP_LOGOUT_RSP; rsp->flags = ISCSI_FLAG_CMD_FINAL; rsp->itt = task->req.itt; rsp->statsn = cpu_to_be32(conn->stat_sn++); rsp->exp_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn); rsp->max_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn + conn->session->max_queue_cmd); return 0; } static int iscsi_noop_in_tx_start(struct iscsi_task *task) { struct iscsi_connection *conn = task->conn; struct iscsi_data_rsp *rsp = (struct iscsi_data_rsp *) &conn->rsp.bhs; memset(rsp, 0, sizeof(*rsp)); rsp->opcode = ISCSI_OP_NOOP_IN; rsp->flags = ISCSI_FLAG_CMD_FINAL; rsp->itt = task->req.itt; rsp->ttt = task->req.ttt; rsp->statsn = cpu_to_be32(conn->stat_sn); rsp->exp_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn); rsp->max_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn + conn->session->max_queue_cmd); /* TODO: honor max_burst */ conn->rsp.datasize = task->len; hton24(rsp->dlength, task->len); conn->rsp.data = task->data; return 0; } static int iscsi_noop_out_tx_start(struct iscsi_task *task, int *is_rsp) { struct iscsi_connection *conn = task->conn; struct iscsi_data_rsp *rsp = (struct iscsi_data_rsp *) &conn->rsp.bhs; if (task->req.itt == cpu_to_be32(ISCSI_RESERVED_TAG)) { *is_rsp = 0; if (conn->tp->ep_nop_reply) conn->tp->ep_nop_reply(be32_to_cpu(task->req.ttt)); iscsi_free_task(task); } else { *is_rsp = 1; memset(rsp, 0, sizeof(*rsp)); rsp->opcode = ISCSI_OP_NOOP_IN; rsp->flags = ISCSI_FLAG_CMD_FINAL; rsp->itt = task->req.itt; rsp->ttt = cpu_to_be32(ISCSI_RESERVED_TAG); rsp->statsn = cpu_to_be32(conn->stat_sn++); rsp->exp_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn); rsp->max_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn + conn->session->max_queue_cmd); /* TODO: honor max_burst */ conn->rsp.datasize = task->len; hton24(rsp->dlength, task->len); conn->rsp.data = task->data; } return 0; } static int iscsi_tm_tx_start(struct iscsi_task *task) { struct iscsi_connection *conn = task->conn; struct iscsi_tm_rsp *rsp = (struct iscsi_tm_rsp *) &conn->rsp.bhs; memset(rsp, 0, sizeof(*rsp)); rsp->opcode = ISCSI_OP_SCSI_TMFUNC_RSP; rsp->flags = ISCSI_FLAG_CMD_FINAL; rsp->itt = task->req.itt; rsp->response = task->result; rsp->statsn = cpu_to_be32(conn->stat_sn++); rsp->exp_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn); rsp->max_cmdsn = cpu_to_be32(conn->session->exp_cmd_sn + conn->session->max_queue_cmd); return 0; } static int iscsi_scsi_cmd_tx_done(struct iscsi_connection *conn) { struct iscsi_hdr *hdr = &conn->rsp.bhs; struct iscsi_task *task = conn->tx_task; switch (hdr->opcode & ISCSI_OPCODE_MASK) { case ISCSI_OP_R2T: break; case ISCSI_OP_SCSI_DATA_IN: if (task->offset < scsi_get_in_transfer_len(&task->scmd) || scsi_get_result(&task->scmd) != SAM_STAT_GOOD || scsi_get_data_dir(&task->scmd) == DATA_BIDIRECTIONAL) { dprintf("more data or sense or bidir %x\n", hdr->itt); list_add(&task->c_list, &task->conn->tx_clist); return 0; } case ISCSI_OP_SCSI_CMD_RSP: iscsi_free_cmd_task(task); break; default: eprintf("target bug %x\n", hdr->opcode & ISCSI_OPCODE_MASK); } return 0; } static int iscsi_task_tx_done(struct iscsi_connection *conn) { struct iscsi_task *task = conn->tx_task; uint8_t op; op = task->req.opcode & ISCSI_OPCODE_MASK; switch (op) { case ISCSI_OP_SCSI_CMD: iscsi_scsi_cmd_tx_done(conn); break; case ISCSI_OP_NOOP_IN: /* NOOP_IN req is allocated within iscsi_tcp * by a direct call to the transport * allocation routine, unaccounted in the * connection refcount and not added to * task_list, hence it should be freed when * it's done by a similar direct call. * * We're overprotective here by checking tp's * free_task pointer, avoiding interference * with iser (I'm unsure if it's relevant * though). */ if (task->conn->tp->free_task) task->conn->tp->free_task(task); break; case ISCSI_OP_NOOP_OUT: case ISCSI_OP_LOGOUT: case ISCSI_OP_SCSI_TMFUNC: iscsi_free_task(task); if (op == ISCSI_OP_LOGOUT) conn->state = STATE_CLOSE; } conn->tx_task = NULL; return 0; } static int iscsi_task_tx_start(struct iscsi_connection *conn) { struct iscsi_task *task; int is_rsp, err = 0; if (list_empty(&conn->tx_clist)) goto nodata; conn_write_pdu(conn); task = list_first_entry(&conn->tx_clist, struct iscsi_task, c_list); dprintf("found a task %" PRIx64 " %u %u %u\n", task->tag, ntohl(((struct iscsi_cmd *) (&task->req))->data_length), task->offset, task->r2t_count); list_del(&task->c_list); switch (task->req.opcode & ISCSI_OPCODE_MASK) { case ISCSI_OP_SCSI_CMD: err = iscsi_scsi_cmd_tx_start(task); break; case ISCSI_OP_NOOP_IN: err = iscsi_noop_in_tx_start(task); break; case ISCSI_OP_NOOP_OUT: err = iscsi_noop_out_tx_start(task, &is_rsp); if (!is_rsp) goto nodata; break; case ISCSI_OP_LOGOUT: err = iscsi_logout_tx_start(task); break; case ISCSI_OP_SCSI_TMFUNC: err = iscsi_tm_tx_start(task); break; } conn->tx_task = task; return err; nodata: dprintf("no more data\n"); conn->tp->ep_event_modify(conn, EPOLLIN); return -EAGAIN; } static int do_recv(struct iscsi_connection *conn, int next_state) { int ret, opcode; ret = conn->tp->ep_read(conn, conn->rx_buffer, conn->rx_size); if (!ret) { conn->state = STATE_CLOSE; return 0; } else if (ret < 0) { if (errno == EINTR || errno == EAGAIN) return 0; else return -EIO; } conn->rx_size -= ret; conn->rx_buffer += ret; opcode = (conn->rx_iostate == IOSTATE_RX_BHS) ? (conn->req.bhs.opcode & ISCSI_OPCODE_MASK) : -1; iscsi_update_conn_stats_rx(conn, ret, opcode); if (!conn->rx_size) conn->rx_iostate = next_state; return ret; } void iscsi_rx_handler(struct iscsi_connection *conn) { int ret = 0, hdigest, ddigest; uint32_t crc; if (conn->state == STATE_SCSI) { struct param *p = conn->session_param; hdigest = p[ISCSI_PARAM_HDRDGST_EN].val & DIGEST_CRC32C; ddigest = p[ISCSI_PARAM_DATADGST_EN].val & DIGEST_CRC32C; } else hdigest = ddigest = 0; again: switch (conn->rx_iostate) { case IOSTATE_RX_BHS: ret = do_recv(conn, IOSTATE_RX_INIT_AHS); if (ret <= 0 || conn->rx_iostate != IOSTATE_RX_INIT_AHS) break; case IOSTATE_RX_INIT_AHS: if (conn->state == STATE_SCSI) { ret = iscsi_task_rx_start(conn); if (ret) { conn->state = STATE_CLOSE; break; } } else { conn->rx_buffer = conn->req_buffer; conn->req.ahs = conn->rx_buffer; conn->req.data = conn->rx_buffer + conn->req.bhs.hlength * 4; } conn->req.ahssize = conn->req.bhs.hlength * 4; conn->req.datasize = ntoh24(conn->req.bhs.dlength); conn->rx_size = conn->req.ahssize; if (conn->state != STATE_SCSI && conn->req.ahssize > INCOMING_BUFSIZE) { conn->state = STATE_CLOSE; return; } if (conn->rx_size) { conn->rx_buffer = conn->req.ahs; conn->rx_iostate = IOSTATE_RX_AHS; } else conn->rx_iostate = hdigest ? IOSTATE_RX_INIT_HDIGEST : IOSTATE_RX_INIT_DATA; /* * if the datasize is zero, we must go to * IOSTATE_RX_END via IOSTATE_RX_INIT_DATA now. Note * iscsi_rx_handler will not called since tgtd doesn't * have data to read. */ if (conn->rx_iostate == IOSTATE_RX_INIT_DATA) goto again; else if (conn->rx_iostate != IOSTATE_RX_AHS) break; case IOSTATE_RX_AHS: ret = do_recv(conn, hdigest ? IOSTATE_RX_INIT_HDIGEST : IOSTATE_RX_INIT_DATA); if (ret <= 0) break; if (conn->rx_iostate == IOSTATE_RX_INIT_DATA) goto again; if (conn->rx_iostate != IOSTATE_RX_INIT_HDIGEST) break; case IOSTATE_RX_INIT_HDIGEST: conn->rx_buffer = conn->rx_digest; conn->rx_size = sizeof(conn->rx_digest); conn->rx_iostate = IOSTATE_RX_HDIGEST; case IOSTATE_RX_HDIGEST: ret = do_recv(conn, IOSTATE_RX_CHECK_HDIGEST); if (ret <= 0 || conn->rx_iostate != IOSTATE_RX_CHECK_HDIGEST) break; case IOSTATE_RX_CHECK_HDIGEST: crc = ~0; crc = crc32c(crc, &conn->req.bhs, BHS_SIZE); if (conn->req.ahssize) crc = crc32c(crc, conn->req.ahs, conn->req.ahssize); crc = ~crc; if (*((uint32_t *)conn->rx_digest) != crc) { eprintf("rx hdr digest error 0x%x calc 0x%x\n", *((uint32_t *)conn->rx_digest), crc); conn->state = STATE_CLOSE; } conn->rx_iostate = IOSTATE_RX_INIT_DATA; case IOSTATE_RX_INIT_DATA: conn->rx_size = roundup(conn->req.datasize, conn->tp->data_padding); if (conn->rx_size) { conn->rx_iostate = IOSTATE_RX_DATA; conn->rx_buffer = conn->req.data; if (conn->state != STATE_SCSI) { if (conn->req.ahssize + conn->rx_size > INCOMING_BUFSIZE) { conn->state = STATE_CLOSE; return; } } } else { conn->rx_iostate = IOSTATE_RX_END; break; } case IOSTATE_RX_DATA: ret = do_recv(conn, ddigest ? IOSTATE_RX_INIT_DDIGEST : IOSTATE_RX_END); if (ret <= 0 || conn->rx_iostate != IOSTATE_RX_INIT_DDIGEST) break; case IOSTATE_RX_INIT_DDIGEST: conn->rx_buffer = conn->rx_digest; conn->rx_size = sizeof(conn->rx_digest); conn->rx_iostate = IOSTATE_RX_DDIGEST; case IOSTATE_RX_DDIGEST: ret = do_recv(conn, IOSTATE_RX_CHECK_DDIGEST); if (ret <= 0 || conn->rx_iostate != IOSTATE_RX_CHECK_DDIGEST) break; case IOSTATE_RX_CHECK_DDIGEST: crc = ~0; crc = crc32c(crc, conn->req.data, roundup(conn->req.datasize, conn->tp->data_padding)); crc = ~crc; conn->rx_iostate = IOSTATE_RX_END; if (*((uint32_t *)conn->rx_digest) != crc) { eprintf("rx hdr digest error 0x%x calc 0x%x\n", *((uint32_t *)conn->rx_digest), crc); conn->state = STATE_CLOSE; } break; default: eprintf("error %d %d\n", conn->state, conn->rx_iostate); exit(1); } if (ret < 0 || conn->rx_iostate != IOSTATE_RX_END || conn->state == STATE_CLOSE) return; if (conn->rx_size) { eprintf("error %d %d %d\n", conn->state, conn->rx_iostate, conn->rx_size); exit(1); } if (conn->state == STATE_SCSI) { ret = iscsi_task_rx_done(conn); if (ret) conn->state = STATE_CLOSE; else conn_read_pdu(conn); } else { conn_write_pdu(conn); conn->tp->ep_event_modify(conn, EPOLLOUT); ret = cmnd_execute(conn); if (ret) conn->state = STATE_CLOSE; } } static int do_send(struct iscsi_connection *conn, int next_state) { int ret, opcode; again: ret = conn->tp->ep_write_begin(conn, conn->tx_buffer, conn->tx_size); if (ret < 0) { if (errno != EINTR && errno != EAGAIN) conn->state = STATE_CLOSE; else if (errno == EINTR || errno == EAGAIN) goto again; return -EIO; } conn->tx_size -= ret; conn->tx_buffer += ret; opcode = (conn->tx_iostate == IOSTATE_TX_BHS) ? (conn->req.bhs.opcode & ISCSI_OPCODE_MASK) : -1; iscsi_update_conn_stats_tx(conn, ret, opcode); if (conn->tx_size) goto again; conn->tx_iostate = next_state; return 0; } int iscsi_tx_handler(struct iscsi_connection *conn) { int ret = 0, hdigest, ddigest; uint32_t crc; if (conn->state == STATE_SCSI) { struct param *p = conn->session_param; hdigest = p[ISCSI_PARAM_HDRDGST_EN].val & DIGEST_CRC32C; ddigest = p[ISCSI_PARAM_DATADGST_EN].val & DIGEST_CRC32C; } else hdigest = ddigest = 0; if (conn->state == STATE_SCSI && !conn->tx_task) { ret = iscsi_task_tx_start(conn); if (ret) goto out; } /* * For rdma, grab the data-in or r2t packet and covert to * an RDMA operation. */ if (conn->tp->rdma && conn->state == STATE_SCSI) { switch (conn->rsp.bhs.opcode) { case ISCSI_OP_R2T: ret = conn->tp->ep_rdma_read(conn); if (ret < 0) /* wait for free slot */ goto out; goto finish; case ISCSI_OP_SCSI_DATA_IN: ret = conn->tp->ep_rdma_write(conn); if (ret < 0) goto out; goto finish; default: break; } } again: switch (conn->tx_iostate) { case IOSTATE_TX_BHS: ret = do_send(conn, IOSTATE_TX_INIT_AHS); if (ret < 0) break; case IOSTATE_TX_INIT_AHS: if (conn->rsp.ahssize) { conn->tx_iostate = IOSTATE_TX_AHS; conn->tx_buffer = conn->rsp.ahs; conn->tx_size = conn->rsp.ahssize; conn->tx_iostate = IOSTATE_TX_AHS; } else conn->tx_iostate = hdigest ? IOSTATE_TX_INIT_HDIGEST : IOSTATE_TX_INIT_DATA; if (conn->tx_iostate != IOSTATE_TX_AHS) break; case IOSTATE_TX_AHS: conn->tx_iostate = hdigest ? IOSTATE_TX_INIT_HDIGEST : IOSTATE_TX_INIT_DATA; if (conn->tx_iostate != IOSTATE_TX_INIT_HDIGEST) break; case IOSTATE_TX_INIT_HDIGEST: crc = ~0; crc = crc32c(crc, &conn->rsp.bhs, BHS_SIZE); *(uint32_t *)conn->tx_digest = ~crc; conn->tx_iostate = IOSTATE_TX_HDIGEST; conn->tx_buffer = conn->tx_digest; conn->tx_size = sizeof(conn->tx_digest); case IOSTATE_TX_HDIGEST: ret = do_send(conn, IOSTATE_TX_INIT_DATA); if (ret < 0) break; case IOSTATE_TX_INIT_DATA: if (conn->rsp.datasize) { int pad; conn->tx_iostate = IOSTATE_TX_DATA; conn->tx_buffer = conn->rsp.data; conn->tx_size = conn->rsp.datasize; pad = conn->tx_size & (conn->tp->data_padding - 1); if (pad) { pad = PAD_WORD_LEN - pad; memset(conn->tx_buffer + conn->tx_size, 0, pad); conn->tx_size += pad; } } else conn->tx_iostate = IOSTATE_TX_END; if (conn->tx_iostate != IOSTATE_TX_DATA) break; case IOSTATE_TX_DATA: ret = do_send(conn, ddigest ? IOSTATE_TX_INIT_DDIGEST : IOSTATE_TX_END); if (ret < 0) goto out; if (conn->tx_iostate != IOSTATE_TX_INIT_DDIGEST) break; case IOSTATE_TX_INIT_DDIGEST: crc = ~0; crc = crc32c(crc, conn->rsp.data, roundup(conn->rsp.datasize, conn->tp->data_padding)); *(uint32_t *)conn->tx_digest = ~crc; conn->tx_iostate = IOSTATE_TX_DDIGEST; conn->tx_buffer = conn->tx_digest; conn->tx_size = sizeof(conn->tx_digest); case IOSTATE_TX_DDIGEST: ret = do_send(conn, IOSTATE_TX_END); break; default: eprintf("error %d %d\n", conn->state, conn->tx_iostate); exit(1); } if (ret < 0 || conn->state == STATE_CLOSE) goto out; if (conn->tx_iostate != IOSTATE_TX_END) { if (conn->tp->rdma) goto again; /* avoid event loop, just push */ goto out; } if (conn->tx_size) { eprintf("error %d %d %d\n", conn->state, conn->tx_iostate, conn->tx_size); exit(1); } conn->tp->ep_write_end(conn); finish: cmnd_finish(conn); switch (conn->state) { case STATE_KERNEL: ret = conn_take_fd(conn); if (ret) conn->state = STATE_CLOSE; else { conn->state = STATE_SCSI; conn_read_pdu(conn); conn->tp->ep_event_modify(conn, EPOLLIN); } break; case STATE_EXIT: case STATE_CLOSE: break; case STATE_SCSI: iscsi_task_tx_done(conn); break; default: conn_read_pdu(conn); conn->tp->ep_event_modify(conn, EPOLLIN); break; } out: return ret; } int iscsi_transportid(int tid, uint64_t itn_id, char *buf, int size) { struct iscsi_session *session; char *p; uint16_t len; session = session_lookup_by_tsih(itn_id); if (!session) return 0; len = 4; len += strlen(session->initiator) + 1; len += 5; /* separator */ len += 7; /* isid + '\0' */ len = ALIGN(len, 4); if (len > size) return len; memset(buf, 0, size); buf[0] = 0x05; buf[0] |= 0x40; put_unaligned_be16(len - 4, buf + 2); sprintf(buf + 4, "%s", session->initiator); p = buf + (4 + strlen(session->initiator) + 1); p += sprintf(p, ",i,0x"); memcpy(p, session->isid, sizeof(session->isid)); return len; } int iscsi_param_parse_portals(char *p, int do_add, int do_delete) { while (*p) { if (!strncmp(p, "portal", 6)) { char *addr, *q; int len = 0, port = ISCSI_LISTEN_PORT; addr = p + 7; if (addr[0] == '[') { addr++; q = strchr(addr, ']'); if (!q) { eprintf("malformed string when parsing " "portal (%s). mismatched ipv6 " "'[' ']'\n", p); return -1; } q++; len = q - addr -1; if (*q != ':') q = NULL; } else q = strchr(addr, ':'); if (q) port = atoi(q + 1); else q = strchr(addr, ','); if (!len) { if (q) len = q - addr; else len = strlen(addr); } if (len) { char *tmp; tmp = zalloc(len + 1); memcpy(tmp, addr, len); if (do_add && iscsi_add_portal(tmp, port, 1)) { free(tmp); return -1; } if (do_delete && iscsi_delete_portal(tmp, port)) { free(tmp); return -1; } } } else if (!strncmp(p, "nop_interval", 12)) { iscsi_set_nop_interval(atoi(p+13)); } else if (!strncmp(p, "nop_count", 9)) { iscsi_set_nop_count(atoi(p+10)); } p += strcspn(p, ","); if (*p == ',') ++p; } return 0; } static int iscsi_param_parser(char *p) { portal_arguments = p; return 0; } static int iscsi_portal_create(char *p) { return iscsi_param_parse_portals(p, 1, 0); } static int iscsi_portal_destroy(char *p) { return iscsi_param_parse_portals(p, 0, 1); } static struct tgt_driver iscsi = { .name = "iscsi", .init = iscsi_init, .exit = iscsi_exit, .target_create = iscsi_target_create, .target_destroy = iscsi_target_destroy, .portal_create = iscsi_portal_create, .portal_destroy = iscsi_portal_destroy, .update = iscsi_target_update, .show = iscsi_target_show, .stat = iscsi_stat, .cmd_end_notify = iscsi_scsi_cmd_done, .mgmt_end_notify = iscsi_tm_done, .transportid = iscsi_transportid, .default_bst = "rdwr", }; __attribute__((constructor)) static void iscsi_driver_constructor(void) { register_driver(&iscsi); setup_param("iscsi", iscsi_param_parser); } tgt-1.0.85/usr/iscsi/iscsid.h000066400000000000000000000235431435417276200157770ustar00rootroot00000000000000/* * Copyright (C) 2002-2003 Ardis Technolgies * * 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, version 2 of the * License. * * 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 ISCSID_H #define ISCSID_H #include #include #include #include "transport.h" #include "list.h" #include "param.h" #include "log.h" #include "tgtd.h" #include "util.h" #include "iscsi_proto.h" #include "iscsi_if.h" #define cpu_to_be16(x) __cpu_to_be16(x) #define cpu_to_be32(x) __cpu_to_be32(x) #define cpu_to_be64(x) __cpu_to_be64(x) #define be16_to_cpu(x) __be16_to_cpu(x) #define be32_to_cpu(x) __be32_to_cpu(x) #define be64_to_cpu(x) __be64_to_cpu(x) #define MAX_QUEUE_CMD_MIN 1 #define MAX_QUEUE_CMD_DEF 128 #define MAX_QUEUE_CMD_MAX 512 #define ISCSI_NAME_LEN 256 #define DIGEST_ALL (DIGEST_NONE | DIGEST_CRC32C) #define DIGEST_NONE (1 << 0) #define DIGEST_CRC32C (1 << 1) #define sid64(isid, tsih) \ ({ \ (uint64_t) isid[0] << 0 | (uint64_t) isid[1] << 8 | \ (uint64_t) isid[2] << 16 | (uint64_t) isid[3] << 24 | \ (uint64_t) isid[4] << 32 | (uint64_t) isid[5] << 40 | \ (uint64_t) tsih << 48; \ }) #define sid_to_tsih(sid) ((sid) >> 48) #define task_opcode(task) ((task)->req.opcode & ISCSI_OPCODE_MASK) struct iscsi_pdu { struct iscsi_hdr bhs; void *ahs; unsigned int ahssize; void *data; unsigned int datasize; }; struct iscsi_session { int refcount; /* linked to target->sessions_list */ struct list_head slist; /* linked to sessions_list */ struct list_head hlist; char *initiator; char *initiator_alias; struct iscsi_target *target; uint8_t isid[6]; uint16_t tsih; /* links all connections (conn->clist) */ struct list_head conn_list; int conn_cnt; /* links all tasks (task->c_hlist) */ struct list_head cmd_list; /* links pending tasks (task->c_list) */ struct list_head pending_cmd_list; uint32_t exp_cmd_sn; uint32_t max_queue_cmd; struct param session_param[ISCSI_PARAM_MAX]; char *info; /* if this session uses rdma connections */ int rdma; }; struct iscsi_task { struct iscsi_hdr req; struct iscsi_hdr rsp; uint64_t tag; struct iscsi_connection *conn; /* linked to session->cmd_list */ struct list_head c_hlist; /* linked to conn->tx_clist or session->cmd_pending_list */ struct list_head c_list; /* linked to conn->tx_clist or conn->task_list */ struct list_head c_siblings; unsigned long flags; int result; int len; int offset; int r2t_count; int unsol_count; int exp_r2tsn; void *ahs; void *data; struct scsi_cmd scmd; unsigned long extdata[0]; }; struct iscsi_connection { int state; /* should be a new state */ int closed; int rx_iostate; int tx_iostate; int refcount; struct list_head clist; struct iscsi_session *session; int tid; struct param session_param[ISCSI_PARAM_MAX]; char *initiator; char *initiator_alias; uint8_t isid[6]; uint16_t tsih; uint16_t cid; int session_type; int auth_method; uint32_t stat_sn; uint32_t exp_stat_sn; uint32_t cmd_sn; uint32_t exp_cmd_sn; uint32_t max_cmd_sn; struct iscsi_pdu req; void *req_buffer; struct iscsi_pdu rsp; void *rsp_buffer; int rsp_buffer_size; unsigned char *rx_buffer; unsigned char *tx_buffer; int rx_size; int tx_size; uint32_t ttt; int text_datasize; void *text_rsp_buffer; struct iscsi_task *rx_task; struct iscsi_task *tx_task; struct list_head tx_clist; struct list_head task_list; unsigned char rx_digest[4]; unsigned char tx_digest[4]; int auth_state; union { struct { int digest_alg; int id; int challenge_size; unsigned char *challenge; } chap; } auth; struct iscsi_transport *tp; struct iscsi_stats stats; }; #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 STATE_SCSI 12 #define STATE_INIT 13 #define STATE_START 14 #define STATE_READY 15 #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 sizeof(struct iscsi_hdr) #define INCOMING_BUFSIZE 8192 extern int default_nop_interval; extern int default_nop_count; struct iscsi_target { struct list_head tlist; struct list_head sessions_list; struct param session_param[ISCSI_PARAM_MAX]; int tid; char *alias; int max_nr_sessions; int nr_sessions; struct redirect_info { char addr[NI_MAXHOST + 1]; char port[NI_MAXSERV + 1]; uint8_t reason; char *callback; } redirect_info; struct list_head isns_list; int rdma; int nop_interval; int nop_count; }; enum task_flags { TASK_pending, TASK_in_scsi, }; struct iscsi_portal { struct list_head iscsi_portal_siblings; char *addr; int port; int tpgt; int fd; int af; }; extern struct list_head iscsi_portals_list; extern char *portal_arguments; #define set_task_pending(t) ((t)->flags |= (1 << TASK_pending)) #define clear_task_pending(t) ((t)->flags &= ~(1 << TASK_pending)) #define task_pending(t) ((t)->flags & (1 << TASK_pending)) #define set_task_in_scsi(t) ((t)->flags |= (1 << TASK_in_scsi)) #define clear_task_in_scsi(t) ((t)->flags &= ~(1 << TASK_in_scsi)) #define task_in_scsi(t) ((t)->flags & (1 << TASK_in_scsi)) extern int lld_index; extern struct list_head iscsi_targets_list; /* chap.c */ extern int cmnd_exec_auth_chap(struct iscsi_connection *conn); /* conn.c */ extern int conn_init(struct iscsi_connection *conn); extern void conn_exit(struct iscsi_connection *conn); extern void conn_close(struct iscsi_connection *conn); extern void conn_put(struct iscsi_connection *conn); extern int conn_get(struct iscsi_connection *conn); extern struct iscsi_connection * conn_find(struct iscsi_session *session, uint32_t cid); extern int conn_take_fd(struct iscsi_connection *conn); extern void conn_add_to_session(struct iscsi_connection *conn, struct iscsi_session *session); extern tgtadm_err conn_close_admin(uint32_t tid, uint64_t sid, uint32_t cid); /* iscsid.c */ extern char *text_key_find(struct iscsi_connection *conn, char *searchKey); extern void text_key_add(struct iscsi_connection *conn, char *key, char *value); extern void conn_read_pdu(struct iscsi_connection *conn); extern int iscsi_tx_handler(struct iscsi_connection *conn); extern void iscsi_rx_handler(struct iscsi_connection *conn); extern int iscsi_scsi_cmd_execute(struct iscsi_task *task); extern int iscsi_transportid(int tid, uint64_t itn_id, char *buf, int size); extern int iscsi_add_portal(char *addr, int port, int tpgt); extern void iscsi_print_nop_settings(struct concat_buf *b, int tid); extern int iscsi_update_target_nop_count(int tid, int count); extern int iscsi_update_target_nop_interval(int tid, int interval); extern void iscsi_set_nop_interval(int interval); extern void iscsi_set_nop_count(int count); extern int iscsi_delete_portal(char *addr, int port); extern int iscsi_param_parse_portals(char *p, int do_add, int do_delete); extern void iscsi_update_conn_stats_rx(struct iscsi_connection *conn, int size, int opcode); extern void iscsi_update_conn_stats_tx(struct iscsi_connection *conn, int size, int opcode); extern void iscsi_rsp_set_residual(struct iscsi_cmd_rsp *rsp, struct scsi_cmd *scmd); /* iscsid.c iscsi_task */ extern void iscsi_free_task(struct iscsi_task *task); extern void iscsi_free_cmd_task(struct iscsi_task *task); /* session.c */ extern struct iscsi_session *session_find_name(int tid, const char *iname, uint8_t *isid); extern struct iscsi_session *session_lookup_by_tsih(uint16_t tsih); extern int session_create(struct iscsi_connection *conn); extern void session_get(struct iscsi_session *session); extern void session_put(struct iscsi_session *session); /* target.c */ extern struct iscsi_target * target_find_by_name(const char *name); extern struct iscsi_target * target_find_by_id(int tid); extern void target_list_build(struct iscsi_connection *, char *, char *); extern int ip_acl(int tid, struct iscsi_connection *conn); extern int iqn_acl(int tid, struct iscsi_connection *conn); extern int iscsi_target_create(struct target *); extern void iscsi_target_destroy(int tid, int force); extern tgtadm_err iscsi_target_show(int mode, int tid, uint64_t sid, uint32_t cid, uint64_t lun, struct concat_buf *b); extern tgtadm_err iscsi_stat(int mode, int tid, uint64_t sid, uint32_t cid, uint64_t lun, struct concat_buf *b); extern tgtadm_err iscsi_target_update(int mode, int op, int tid, uint64_t sid, uint64_t lun, uint32_t cid, char *name); extern int target_redirected(struct iscsi_target *target, struct iscsi_connection *conn, char *buf, int *reason); /* param.c */ extern int param_index_by_name(char *name, struct iscsi_key *keys); /* transport.c */ extern int iscsi_init(int, char *); extern void iscsi_exit(void); /* isns.c */ extern int isns_init(void); extern void isns_exit(void); extern tgtadm_err isns_show(struct concat_buf *b); extern tgtadm_err isns_update(char *params); extern int isns_scn_access(int tid, char *name); extern int isns_target_register(char *name); extern int isns_target_deregister(char *name); #endif /* ISCSID_H */ tgt-1.0.85/usr/iscsi/iser.c000066400000000000000000003014631435417276200154560ustar00rootroot00000000000000/* * iSCSI extensions for RDMA (iSER) data path * * Copyright (C) 2007 Dennis Dalessandro (dennis@osc.edu) * Copyright (C) 2007 Ananth Devulapalli (ananth@osc.edu) * Copyright (C) 2007 Pete Wyckoff (pw@osc.edu) * Copyright (C) 2010 Voltaire, Inc. All rights reserved. * Copyright (C) 2010 Alexander Nezhinsky (alexandern@voltaire.com) * * 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, version 2 of the * License. * * 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 "util.h" #include "iscsid.h" #include "target.h" #include "driver.h" #include "scsi.h" #include "work.h" #include "iser.h" #if defined(HAVE_VALGRIND) && !defined(NDEBUG) #include #else #define VALGRIND_MAKE_MEM_DEFINED(addr, len) #endif static struct iscsi_transport iscsi_iser; /* global, across all devices */ static struct rdma_event_channel *rdma_evt_channel; LIST_HEAD(iser_portals_list); /* accepted at RDMA layer, but not yet established */ static LIST_HEAD(temp_conn); /* all devices */ static LIST_HEAD(iser_dev_list); /* all iser connections */ static LIST_HEAD(iser_conn_list); #define uint64_from_ptr(p) (uint64_t)(uintptr_t)(p) #define ptr_from_int64(p) (void *)(unsigned long)(p) #define ISER_LISTEN_PORT 3260 short int iser_listen_port = ISER_LISTEN_PORT; char *iser_portal_addr; /* * Crazy hard-coded linux iser settings need 128 * 8 slots + slop, plus * room for our rdmas and send requests. */ #define MAX_WQE 1800 #define DEFAULT_RDMA_TRANSFER_SIZE (512 * 1024) #define MAX_POLL_WC 32 #define DEFAULT_POOL_SIZE_MB 1024 #define MAX_CQ_ENTRIES (MAX_QUEUE_CMD_MAX * DEFAULT_POOL_SIZE_MB) #define MASK_BY_BIT(b) ((1UL << b) - 1) #define ALIGN_TO_BIT(x, b) ((((unsigned long)x) + MASK_BY_BIT(b)) & \ ~MASK_BY_BIT(b)) #define ALIGN_TO_32(x) ALIGN_TO_BIT(x, 5) struct iscsi_sense_data { uint16_t length; uint8_t data[0]; } __attribute__((__packed__)); static size_t buf_pool_sz_mb = DEFAULT_POOL_SIZE_MB; static int cq_vector = -1; static int membuf_num; static size_t membuf_size = DEFAULT_RDMA_TRANSFER_SIZE; static int iser_conn_get(struct iser_conn *conn); static int iser_conn_getn(struct iser_conn *conn, int n); static void iser_conn_put(struct iser_conn *conn); static void iser_free_login_resources(struct iser_conn *conn); static void iser_free_ff_resources(struct iser_conn *conn); static void iser_login_rx(struct iser_task *task); static int iser_device_init(struct iser_device *dev); static void iser_handle_cq_event(int fd __attribute__ ((unused)), int events __attribute__ ((unused)), void *data); static void iser_handle_async_event(int fd __attribute__ ((unused)), int events __attribute__ ((unused)), void *data); static void iser_sched_poll_cq(struct event_data *tev); static void iser_sched_consume_cq(struct event_data *tev); static void iser_sched_tx(struct event_data *evt); static void iser_sched_post_recv(struct event_data *evt); static void iser_sched_buf_alloc(struct event_data *evt); static void iser_sched_iosubmit(struct event_data *evt); static void iser_sched_rdma_rd(struct event_data *evt); static inline void schedule_task_iosubmit(struct iser_task *task, struct iser_conn *conn) { list_add_tail(&task->exec_list, &conn->iosubmit_list); tgt_add_sched_event(&conn->sched_iosubmit); dprintf("task:%p tag:0x%04"PRIx64 " cmdsn:0x%x\n", task, task->tag, task->cmd_sn); } static inline void schedule_rdma_read(struct iser_task *task, struct iser_conn *conn) { list_add_tail(&task->rdma_list, &conn->rdma_rd_list); tgt_add_sched_event(&conn->sched_rdma_rd); dprintf("task:%p tag:0x%04"PRIx64 " cmdsn:0x%x\n", task, task->tag, task->cmd_sn); } static inline void schedule_resp_tx(struct iser_task *task, struct iser_conn *conn) { list_add_tail(&task->tx_list, &conn->resp_tx_list); tgt_add_sched_event(&conn->sched_tx); dprintf("task:%p tag:0x%04"PRIx64 " cmdsn:0x%x\n", task, task->tag, task->cmd_sn); } static inline void schedule_post_recv(struct iser_task *task, struct iser_conn *conn) { list_add_tail(&task->recv_list, &conn->post_recv_list); tgt_add_sched_event(&conn->sched_post_recv); dprintf("task:%p tag:0x%04"PRIx64 " cmdsn:0x%x\n", task, task->tag, task->cmd_sn); } static char *iser_ib_op_to_str(enum iser_ib_op_code iser_ib_op) { char *op_str; switch (iser_ib_op) { case ISER_IB_RECV: op_str = "recv"; break; case ISER_IB_SEND: op_str = "send"; break; case ISER_IB_RDMA_WRITE: op_str = "rdma_wr"; break; case ISER_IB_RDMA_READ: op_str = "rdma_rd"; break; default: op_str = "Unknown"; break; } return op_str; } static void iser_pdu_init(struct iser_pdu *pdu, void *buf) { pdu->iser_hdr = (struct iser_hdr *) buf; buf += sizeof(struct iser_hdr); pdu->bhs = (struct iscsi_hdr *) buf; buf += sizeof(struct iscsi_hdr); pdu->ahs = buf; pdu->ahssize = 0; pdu->membuf.addr = buf; pdu->membuf.size = 0; } static void iser_rxd_init(struct iser_work_req *rxd, struct iser_task *task, void *buf, unsigned size, struct ibv_mr *srmr) { rxd->task = task; rxd->iser_ib_op = ISER_IB_RECV; rxd->sge.addr = uint64_from_ptr(buf); rxd->sge.length = size; rxd->sge.lkey = srmr->lkey; rxd->recv_wr.wr_id = uint64_from_ptr(rxd); rxd->recv_wr.sg_list = &rxd->sge; rxd->recv_wr.num_sge = 1; rxd->recv_wr.next = NULL; } static void iser_txd_init(struct iser_work_req *txd, struct iser_task *task, void *buf, unsigned size, struct ibv_mr *srmr) { txd->task = task; txd->iser_ib_op = ISER_IB_SEND; txd->sge.addr = uint64_from_ptr(buf); txd->sge.length = size; txd->sge.lkey = srmr->lkey; txd->send_wr.wr_id = uint64_from_ptr(txd); txd->send_wr.next = NULL; txd->send_wr.sg_list = &txd->sge; txd->send_wr.num_sge = 1; txd->send_wr.opcode = IBV_WR_SEND; txd->send_wr.send_flags = IBV_SEND_SIGNALED; INIT_LIST_HEAD(&txd->wr_list); } static void iser_rdmad_init(struct iser_work_req *rdmad, struct iser_task *task, struct ibv_mr *srmr) { rdmad->task = task; rdmad->sge.lkey = srmr->lkey; rdmad->send_wr.wr_id = uint64_from_ptr(rdmad); rdmad->send_wr.sg_list = &rdmad->sge; rdmad->send_wr.num_sge = 1; rdmad->send_wr.next = NULL; rdmad->send_wr.send_flags = IBV_SEND_SIGNALED; /* to be set before posting: rdmad->iser_ib_op, rdmad->send_wr.opcode rdmad->sge.addr, rdmad->sge.length rdmad->send_wr.wr.rdma.(remote_addr,rkey) */ INIT_LIST_HEAD(&rdmad->wr_list); } static void iser_task_init(struct iser_task *task, struct iser_conn *conn, void *pdu_buf, unsigned long buf_size, struct ibv_mr *srmr) { task->conn = conn; task->unsolicited = 0; iser_pdu_init(&task->pdu, pdu_buf); iser_rxd_init(&task->rxd, task, pdu_buf, buf_size, srmr); iser_txd_init(&task->txd, task, pdu_buf, buf_size, srmr); iser_rdmad_init(&task->rdmad, task, conn->dev->membuf_mr); INIT_LIST_HEAD(&task->in_buf_list); INIT_LIST_HEAD(&task->out_buf_list); INIT_LIST_HEAD(&task->exec_list); INIT_LIST_HEAD(&task->rdma_list); INIT_LIST_HEAD(&task->tx_list); INIT_LIST_HEAD(&task->recv_list); INIT_LIST_HEAD(&task->session_list); INIT_LIST_HEAD(&task->dout_task_list); } static void iser_unsolicited_task_init(struct iser_task *task, struct iser_conn *conn, void *pdu_data_buf, unsigned long buf_size, struct ibv_mr *srmr) { task->conn = conn; task->unsolicited = 1; iser_txd_init(&task->txd, task, pdu_data_buf, buf_size, srmr); iser_pdu_init(&task->pdu, pdu_data_buf); } static void iser_task_add_out_pdu_buf(struct iser_task *task, struct iser_membuf *data_buf, unsigned int offset) { data_buf->offset = offset; task->out_buf_num++; if (!list_empty(&task->out_buf_list)) { struct iser_membuf *cur_buf; list_for_each_entry(cur_buf, &task->out_buf_list, task_list) { if (offset < cur_buf->offset) { dprintf("task:%p offset:%d size:%d data_buf:%p add before:%p\n", task, offset, data_buf->size, data_buf->addr, cur_buf->addr); list_add_tail(&data_buf->task_list, &cur_buf->task_list); return; } } } dprintf("task:%p offset:%d size:%d data_buf:%p add last\n", task, offset, data_buf->size, data_buf->addr); list_add_tail(&data_buf->task_list, &task->out_buf_list); } static void iser_task_add_out_rdma_buf(struct iser_task *task, struct iser_membuf *data_buf, unsigned int offset) { data_buf->offset = offset; task->out_buf_num++; dprintf("task:%p offset:%d size:%d data_buf:%p add last\n", task, offset, data_buf->size, data_buf->addr); list_add_tail(&data_buf->task_list, &task->out_buf_list); } static void iser_task_del_out_buf(struct iser_task *task, struct iser_membuf *data_buf) { dprintf("task:%p offset:%d size:%d data_buf:%p\n", task, data_buf->offset, data_buf->size, data_buf->addr); list_del(&data_buf->task_list); task->out_buf_num--; } static void iser_task_add_in_rdma_buf(struct iser_task *task, struct iser_membuf *data_buf, unsigned int offset) { dprintf("task:%p offset:0x%d size:%d data_buf:%p add last\n", task, offset, data_buf->size, data_buf->addr); data_buf->offset = offset; task->in_buf_num++; list_add_tail(&data_buf->task_list, &task->in_buf_list); } static void iser_task_del_in_buf(struct iser_task *task, struct iser_membuf *data_buf) { dprintf("task:%p offset:%d size:%d data_buf:%p\n", task, data_buf->offset, data_buf->size, data_buf->addr); list_del(&data_buf->task_list); task->in_buf_num--; } static void iser_prep_resp_send_req(struct iser_task *task, struct iser_work_req *next_wr, int signaled) { struct iser_pdu *pdu = &task->pdu; struct iser_hdr *iser_hdr = pdu->iser_hdr; struct iscsi_hdr *bhs = pdu->bhs; struct iser_work_req *txd = &task->txd; bhs->hlength = pdu->ahssize / 4; hton24(bhs->dlength, pdu->membuf.size); txd->sge.length = ISER_HDRS_SZ; txd->sge.length += pdu->ahssize; txd->sge.length += pdu->membuf.size; memset(iser_hdr, 0, sizeof(*iser_hdr)); iser_hdr->flags = ISCSI_CTRL; txd->send_wr.next = (next_wr ? &next_wr->send_wr : NULL); txd->send_wr.send_flags = (signaled ? IBV_SEND_SIGNALED : 0); dprintf("task:%p wr_id:0x%"PRIx64 " tag:0x%04"PRIx64 " dtbuf:%p " "dtsz:%u ahs_sz:%u stat:0x%x statsn:0x%x expcmdsn:0x%x\n", task, txd->send_wr.wr_id, task->tag, pdu->membuf.addr, pdu->membuf.size, pdu->ahssize, bhs->rsvd2[1], ntohl(bhs->statsn), ntohl(bhs->exp_statsn)); } static void iser_prep_rdma_wr_send_req(struct iser_task *task, struct iser_work_req *next_wr, int signaled) { struct iser_work_req *rdmad = &task->rdmad; struct iser_membuf *rdma_buf; uint64_t offset = 0; /* ToDo: multiple RDMA-Write buffers */ rdmad->iser_ib_op = ISER_IB_RDMA_WRITE; /* RDMA-Write buffer is the only one on the list */ /* ToDo: multiple RDMA-Write buffers, use rdma_buf->offset */ rdma_buf = list_first_entry(&task->in_buf_list, struct iser_membuf, task_list); rdmad->sge.addr = uint64_from_ptr(rdma_buf->addr); if (likely(task->rdma_wr_remains <= rdma_buf->size)) { rdmad->sge.length = task->rdma_wr_remains; task->rdma_wr_remains = 0; } else { rdmad->sge.length = rdma_buf->size; task->rdma_wr_remains -= rdmad->sge.length; } rdmad->send_wr.next = (next_wr ? &next_wr->send_wr : NULL); rdmad->send_wr.opcode = IBV_WR_RDMA_WRITE; rdmad->send_wr.send_flags = (signaled ? IBV_SEND_SIGNALED : 0); rdmad->send_wr.wr.rdma.remote_addr = task->rem_read_va + offset; /* offset += rdmad->sge.length // ToDo: multiple RDMA-Write buffers */ rdmad->send_wr.wr.rdma.rkey = task->rem_read_stag; dprintf(" task:%p tag:0x%04"PRIx64 " wr_id:0x%"PRIx64 " daddr:0x%"PRIx64 " dsz:%u " "bufsz:%u rdma:%d lkey:%x raddr:%"PRIx64 " rkey:%x rems:%u\n", task, task->tag, rdmad->send_wr.wr_id, rdmad->sge.addr, rdmad->sge.length, rdma_buf->size, rdma_buf->rdma, rdmad->sge.lkey, rdmad->send_wr.wr.rdma.remote_addr, rdmad->send_wr.wr.rdma.rkey, task->rdma_wr_remains); } static void iser_prep_rdma_rd_send_req(struct iser_task *task, struct iser_work_req *next_wr, int signaled) { struct iser_work_req *rdmad = &task->rdmad; struct iser_membuf *rdma_buf; uint64_t offset; int cur_req_sz; rdmad->iser_ib_op = ISER_IB_RDMA_READ; /* RDMA-Read buffer is always put at the list's tail */ /* ToDo: multiple RDMA-Write buffers */ rdma_buf = container_of(task->out_buf_list.prev, struct iser_membuf, task_list); offset = rdma_buf->offset; rdmad->sge.addr = uint64_from_ptr(rdma_buf->addr) + offset; if (task->rdma_rd_sz <= rdma_buf->size) cur_req_sz = task->rdma_rd_sz; else cur_req_sz = rdma_buf->size; /* task->rdma_rd_offset += cur_req_sz; // ToDo: multiple RDMA-Write buffers */ rdmad->sge.length = cur_req_sz; rdmad->send_wr.next = (next_wr ? &next_wr->send_wr : NULL); rdmad->send_wr.opcode = IBV_WR_RDMA_READ; rdmad->send_wr.send_flags = (signaled ? IBV_SEND_SIGNALED : 0); rdmad->send_wr.wr.rdma.remote_addr = (uint64_t) task->rem_write_va; /* + (offset - task->unsol_sz); // ToDo: multiple RDMA-Write buffers */ rdmad->send_wr.wr.rdma.rkey = (uint32_t) task->rem_write_stag; dprintf("task:%p wr_id:0x%"PRIx64 " tag:0x%04"PRIx64 " daddr:0x%"PRIx64 " dsz:%u " "bufsz:%u rdma:%d lkey:%x raddr:%"PRIx64 " rkey:%x rems:%u\n", task, rdmad->send_wr.wr_id, task->tag, rdmad->sge.addr, rdmad->sge.length, rdma_buf->size, rdma_buf->rdma, rdmad->sge.lkey, rdmad->send_wr.wr.rdma.remote_addr, rdmad->send_wr.wr.rdma.rkey, task->rdma_rd_sz); } static int iser_post_send(struct iser_conn *conn, struct iser_work_req *iser_send, int num_send_reqs) { struct ibv_send_wr *bad_wr; int err, nr_posted; err = ibv_post_send(conn->qp_hndl, &iser_send->send_wr, &bad_wr); if (likely(!err)) { nr_posted = num_send_reqs; dprintf("conn:%p posted:%d 1st wr:%p wr_id:0x%lx sge_sz:%u\n", &conn->h, nr_posted, iser_send, (unsigned long)iser_send->send_wr.wr_id, iser_send->sge.length); } else { struct ibv_send_wr *wr; nr_posted = 0; for (wr = &iser_send->send_wr; wr != bad_wr; wr = wr->next) nr_posted++; eprintf("%m, conn:%p posted:%d/%d 1st wr:%p wr_id:0x%lx sge_sz:%u\n", &conn->h, nr_posted, num_send_reqs, iser_send, (unsigned long)iser_send->send_wr.wr_id, iser_send->sge.length); } iser_conn_getn(conn, nr_posted); return err; } static int iser_post_recv(struct iser_conn *conn, struct iser_task *task, int num_recv_bufs) { struct ibv_recv_wr *bad_wr; int err, nr_posted; err = ibv_post_recv(conn->qp_hndl, &task->rxd.recv_wr, &bad_wr); if (likely(!err)) { nr_posted = num_recv_bufs; dprintf("conn:%p posted:%d 1st task:%p " "wr_id:0x%lx sge_sz:%u\n", &conn->h, nr_posted, task, (unsigned long)task->rxd.recv_wr.wr_id, task->rxd.sge.length); } else { struct ibv_recv_wr *wr; nr_posted = 0; for (wr = &task->rxd.recv_wr; wr != bad_wr; wr = wr->next) nr_posted++; eprintf("%m, conn:%p posted:%d/%d 1st task:%p " "wr_id:0x%lx sge_sz:%u\n", &conn->h, nr_posted, num_recv_bufs, task, (unsigned long)task->rxd.recv_wr.wr_id, task->rxd.sge.length); } iser_conn_getn(conn, nr_posted); return err; } static inline void iser_set_rsp_stat_sn(struct iscsi_session *session, struct iscsi_hdr *rsp) { if (session) { rsp->exp_statsn = cpu_to_be32(session->exp_cmd_sn); rsp->max_statsn = cpu_to_be32(session->exp_cmd_sn + session->max_queue_cmd); } } static uint8_t* iser_alloc_pool(size_t pool_size, int *shmid) { int shmemid; uint8_t *buf; /* allocate memory */ shmemid = shmget(IPC_PRIVATE, pool_size, SHM_HUGETLB | IPC_CREAT | SHM_R | SHM_W); if (shmemid < 0) { eprintf("shmget rdma pool sz:%zu failed\n", pool_size); goto failed_huge_page; } /* get pointer to allocated memory */ buf = shmat(shmemid, NULL, 0); if (buf == (void*)-1) { eprintf("Shared memory attach failure (errno=%d %m)", errno); shmctl(shmemid, IPC_RMID, NULL); goto failed_huge_page; } /* mark 'to be destroyed' when process detaches from shmem segment this will clear the HugePage resources even if process if killed not nicely. From checking shmctl man page it is unlikely that it will fail here. */ if (shmctl(shmemid, IPC_RMID, NULL)) { eprintf("Shared memory contrl mark 'to be destroyed' failed (errno=%d %m)", errno); } dprintf("Allocated huge page sz:%zu\n", pool_size); *shmid = shmemid; return buf; failed_huge_page: *shmid = -1; return valloc(pool_size); } static void iser_free_pool(uint8_t *pool_buf, int shmid) { if (shmid >= 0) { if (shmdt(pool_buf) != 0) { eprintf("shmem detach failure (errno=%d %m)", errno); } } else { free(pool_buf); } } static int iser_init_rdma_buf_pool(struct iser_device *dev) { uint8_t *pool_buf, *list_buf; size_t pool_size, list_size; struct iser_membuf *rdma_buf; int shmid; int i; /* The membuf size is rounded up at initialization time to the hardware page size so that allocations for direct IO devices are aligned. */ membuf_size = roundup(membuf_size, pagesize); pool_size = buf_pool_sz_mb * 1024 * 1024; membuf_num = pool_size / membuf_size; pool_size = membuf_num * membuf_size; /* reflect possible round-down */ pool_buf = iser_alloc_pool(pool_size, &shmid); if (!pool_buf) { eprintf("malloc rdma pool sz:%zu failed\n", pool_size); return -ENOMEM; } list_size = membuf_num * sizeof(*rdma_buf); list_buf = malloc(list_size); if (!list_buf) { eprintf("malloc list_buf sz:%zu failed\n", list_size); iser_free_pool(pool_buf, shmid); return -ENOMEM; } /* One pool of registered memory per PD */ dev->membuf_mr = ibv_reg_mr(dev->pd, pool_buf, pool_size, IBV_ACCESS_LOCAL_WRITE); if (!dev->membuf_mr) { eprintf("ibv_reg_mr failed, %m\n"); iser_free_pool(pool_buf, shmid); free(list_buf); return -1; } dprintf("pool buf:%p list:%p mr:%p lkey:0x%x\n", pool_buf, list_buf, dev->membuf_mr, dev->membuf_mr->lkey); dev->rdma_hugetbl_shmid = shmid; dev->membuf_regbuf = pool_buf; dev->membuf_listbuf = list_buf; INIT_LIST_HEAD(&dev->membuf_free); INIT_LIST_HEAD(&dev->membuf_alloc); for (i = 0; i < membuf_num; i++) { rdma_buf = (void *) list_buf; list_buf += sizeof(*rdma_buf); list_add_tail(&rdma_buf->pool_list, &dev->membuf_free); INIT_LIST_HEAD(&rdma_buf->task_list); rdma_buf->addr = pool_buf; rdma_buf->size = membuf_size; rdma_buf->rdma = 1; pool_buf += membuf_size; } return 0; } static void iser_destroy_rdma_buf_pool(struct iser_device *dev) { int err; assert(list_empty(&dev->membuf_alloc)); err = ibv_dereg_mr(dev->membuf_mr); if (err) eprintf("ibv_dereg_mr failed: (errno=%d %m)\n", errno); iser_free_pool(dev->membuf_regbuf, dev->rdma_hugetbl_shmid); free(dev->membuf_listbuf); dev->membuf_mr = NULL; dev->membuf_regbuf = NULL; dev->membuf_listbuf = NULL; } static struct iser_membuf *iser_dev_alloc_rdma_buf(struct iser_device *dev) { struct iser_membuf *rdma_buf; if (unlikely(list_empty(&dev->membuf_free))) return NULL; rdma_buf = list_first_entry(&dev->membuf_free, struct iser_membuf, pool_list); list_del(&rdma_buf->pool_list); list_add_tail(&rdma_buf->pool_list, &dev->membuf_alloc); dprintf("alloc:%p\n", rdma_buf); return rdma_buf; } static void iser_dev_free_rdma_buf(struct iser_device *dev, struct iser_membuf *rdma_buf) { if (unlikely(!rdma_buf || !rdma_buf->rdma)) return; dprintf("free %p\n", rdma_buf); /* add to the free list head to reuse recently used buffers first */ list_del(&rdma_buf->pool_list); list_add(&rdma_buf->pool_list, &dev->membuf_free); } static int iser_task_alloc_rdma_bufs(struct iser_task *task) { struct iser_conn *conn = task->conn; struct iser_membuf *rdma_wr_buf = NULL, *rdma_rd_buf = NULL; if (task->is_write && task->rdma_rd_sz > 0) { /* ToDo: multiple RDMA-Read buffers */ if (unlikely(task->rdma_rd_sz > membuf_size)) { eprintf("conn:%p task:%p tag:0x%04"PRIx64 ", " "rdma-rd size:%u too big\n", &conn->h, task, task->tag, task->rdma_rd_sz); return -E2BIG; } rdma_rd_buf = iser_dev_alloc_rdma_buf(conn->dev); if (unlikely(rdma_rd_buf == NULL)) goto no_mem_err; /* if this is a bidir task, allocation of the rdma_wr buffer may still fail, thus don't add the buffer to the task yet */ } if (task->is_read && task->rdma_wr_sz > 0) { /* ToDo: multiple RDMA-Read buffers */ if (unlikely(task->rdma_wr_sz > membuf_size)) { eprintf("conn:%p task:%p tag:0x%04"PRIx64 ", " "rdma-wr size:%u too big\n", &conn->h, task, task->tag, task->rdma_wr_sz); return -E2BIG; } rdma_wr_buf = iser_dev_alloc_rdma_buf(conn->dev); if (unlikely(rdma_wr_buf == NULL)) goto no_mem_err; iser_task_add_in_rdma_buf(task, rdma_wr_buf, 0); dprintf("conn:%p task:%p tag:0x%04"PRIx64 ", " "rdma-wr buf:%p sz:%u\n", &conn->h, task, task->tag, rdma_wr_buf->addr, rdma_wr_buf->size); } /* With a write or bidir task there may be rdma portion and data-outs, independently. If data-outs remain, wait until they all arrive */ if (task->is_write) { if (rdma_rd_buf) { /* ToDo: multiple RDMA-Read buffers */ iser_task_add_out_rdma_buf(task, rdma_rd_buf, task->unsol_sz); dprintf("conn:%p task:%p tag:0x%04"PRIx64 ", " "rdma-rd buf:%p sz:%u\n", &conn->h, task, task->tag, rdma_rd_buf->addr, rdma_rd_buf->size); schedule_rdma_read(task, conn); return 0; } if (task->unsol_remains > 0) return 0; } schedule_task_iosubmit(task, conn); return 0; no_mem_err: if (rdma_rd_buf) iser_dev_free_rdma_buf(conn->dev, rdma_rd_buf); conn->dev->waiting_for_mem = 1; eprintf("conn:%p task:%p tag:0x%04"PRIx64 ", free list empty\n", &conn->h, task, task->tag); return -ENOMEM; } static void iser_task_free_rdma_buf(struct iser_task *task, struct iser_membuf *rdma_buf) { struct iser_conn *conn = task->conn; struct iser_device *dev = conn->dev; iser_dev_free_rdma_buf(dev, rdma_buf); if (unlikely(dev->waiting_for_mem)) { dev->waiting_for_mem = 0; tgt_add_sched_event(&conn->sched_buf_alloc); } } static void iser_task_free_out_bufs(struct iser_task *task) { struct iser_membuf *membuf, *mbnext; list_for_each_entry_safe(membuf, mbnext, &task->out_buf_list, task_list) { iser_task_del_out_buf(task, membuf); if (membuf->rdma) iser_task_free_rdma_buf(task, membuf); } assert(task->out_buf_num == 0); } static void iser_task_free_in_bufs(struct iser_task *task) { struct iser_membuf *membuf, *mbnext; list_for_each_entry_safe(membuf, mbnext, &task->in_buf_list, task_list) { iser_task_del_in_buf(task, membuf); iser_task_free_rdma_buf(task, membuf); } assert(task->in_buf_num == 0); } static void iser_complete_task(struct iser_task *task) { struct iser_conn *conn = task->conn; if (unlikely(task->opcode != ISCSI_OP_SCSI_CMD)) { dprintf("task:%p, non-cmd\n", task); return; } list_del(&task->session_list); if (task->is_read) iser_task_free_in_bufs(task); if (task->is_write) { iser_task_free_out_bufs(task); /* iser_task_free_dout_tasks(task); // ToDo: multiple out buffers */ } /* we are completing scsi cmd task, returning from target */ if (likely(task_in_scsi(task))) { target_cmd_done(&task->scmd); clear_task_in_scsi(task); iser_conn_put(conn); } if (task->extdata) { free(task->extdata); task->extdata = NULL; } } static char *iser_conn_login_phase_name(enum iser_login_phase phase) { switch (phase) { case LOGIN_PHASE_INIT: return "INIT"; case LOGIN_PHASE_START: return "START"; case LOGIN_PHASE_LAST_SEND: return "LAST_SEND"; case LOGIN_PHASE_FF: return "FF"; default: return "Illegal"; } } static void iser_conn_login_phase_set(struct iser_conn *conn, enum iser_login_phase phase) { dprintf("conn:%p from:%s to:%s\n", &conn->h, iser_conn_login_phase_name(conn->login_phase), iser_conn_login_phase_name(phase)); conn->login_phase = phase; } /* * Called at accept time, builds resources just for login phase. */ #define NUM_LOGIN_TASKS 2 /* one posted for req rx, one for reply tx */ static int iser_alloc_login_resources(struct iser_conn *conn) { unsigned long buf_size = ALIGN_TO_32(conn->rsize); unsigned long pool_size = NUM_LOGIN_TASKS * buf_size; struct iser_task *login_task[NUM_LOGIN_TASKS]; uint8_t *pdu_data_buf, *task_buf; unsigned int i; int err = 0; dprintf("conn:%p login tasks num:%u, buf_sz:%lu (rx_sz:%u tx_sz:%u)\n", &conn->h, NUM_LOGIN_TASKS, buf_size, conn->rsize, conn->ssize); conn->login_data_pool = malloc(pool_size); if (!conn->login_data_pool) { eprintf("conn:%p malloc login_data_pool sz:%lu failed\n", &conn->h, pool_size); err = -1; goto out; } conn->login_data_mr = ibv_reg_mr(conn->dev->pd, conn->login_data_pool, pool_size, IBV_ACCESS_LOCAL_WRITE); if (!conn->login_data_mr) { eprintf("conn:%p ibv_reg_mr login pool failed, %m\n", &conn->h); free(conn->login_data_pool); conn->login_data_pool = NULL; err = -1; goto out; } pool_size = NUM_LOGIN_TASKS * sizeof(struct iser_task); conn->login_task_pool = malloc(pool_size); if (!conn->login_task_pool) { eprintf("conn:%p malloc login_task_pool sz:%lu failed\n", &conn->h, pool_size); ibv_dereg_mr(conn->login_data_mr); conn->login_data_mr = NULL; free(conn->login_data_pool); conn->login_data_pool = NULL; goto out; } memset(conn->login_task_pool, 0, pool_size); conn->login_res_alloc = 1; pdu_data_buf = conn->login_data_pool; task_buf = conn->login_task_pool; for (i = 0; i < NUM_LOGIN_TASKS; i++) { login_task[i] = (struct iser_task *) task_buf; iser_task_init(login_task[i], conn, pdu_data_buf, buf_size, conn->login_data_mr); task_buf += sizeof(struct iser_task); pdu_data_buf += buf_size; } dprintf("post_recv login rx task:%p\n", login_task[0]); err = iser_post_recv(conn, login_task[0], 1); if (err) { eprintf("conn:%p post_recv login rx-task failed\n", &conn->h); iser_free_login_resources(conn); goto out; } dprintf("saved login tx-task:%p\n", login_task[1]); conn->login_tx_task = login_task[1]; out: return err; } /* * When ready for full-feature mode, free login-phase resources. */ static void iser_free_login_resources(struct iser_conn *conn) { int err; if (!conn->login_res_alloc) return; dprintf("conn:%p, login phase:%s\n", &conn->h, iser_conn_login_phase_name(conn->login_phase)); /* release mr and free the lists */ if (conn->login_data_mr) { err = ibv_dereg_mr(conn->login_data_mr); if (err) eprintf("conn:%p ibv_dereg_mr failed, %m\n", &conn->h); } if (conn->login_data_pool) free(conn->login_data_pool); if (conn->login_task_pool) free(conn->login_task_pool); conn->login_tx_task = NULL; conn->login_res_alloc = 0; } /* * Ready for full feature, allocate resources. */ static int iser_alloc_ff_resources(struct iser_conn *conn) { /* ToDo: need to fix the ISCSI_PARAM_INITIATOR_RDSL bug in initiator */ /* buf_size = ALIGN_TO_32(MAX(conn->rsize, conn->ssize)); */ unsigned long buf_size = ALIGN_TO_32(conn->rsize); /* includes headers */ unsigned long num_tasks = conn->max_outst_pdu + 2; /* all rx, text tx, nop-in*/ unsigned long alloc_sz; uint8_t *pdu_data_buf, *task_buf; struct iser_task *task; unsigned int i; int err = 0; dprintf("conn:%p max_outst:%u buf_sz:%lu (ssize:%u rsize:%u)\n", &conn->h, conn->max_outst_pdu, buf_size, conn->ssize, conn->rsize); alloc_sz = num_tasks * buf_size; conn->pdu_data_pool = malloc(alloc_sz); if (!conn->pdu_data_pool) { eprintf("conn:%p malloc pdu_data_buf sz:%lu failed\n", &conn->h, alloc_sz); err = -1; goto out; } conn->pdu_data_mr = ibv_reg_mr(conn->dev->pd, conn->pdu_data_pool, alloc_sz, IBV_ACCESS_LOCAL_WRITE); if (!conn->pdu_data_mr) { eprintf("conn:%p ibv_reg_mr pdu_data_pool failed, %m\n", &conn->h); free(conn->pdu_data_pool); conn->pdu_data_pool = NULL; err = -1; goto out; } alloc_sz = num_tasks * sizeof(struct iser_task); conn->task_pool = malloc(alloc_sz); if (!conn->task_pool) { eprintf("conn:%p malloc task_pool sz:%lu failed\n", &conn->h, alloc_sz); ibv_dereg_mr(conn->pdu_data_mr); conn->pdu_data_mr = NULL; free(conn->pdu_data_pool); conn->pdu_data_pool = NULL; err = -1; goto out; } memset(conn->task_pool, 0, alloc_sz); conn->ff_res_alloc = 1; pdu_data_buf = conn->pdu_data_pool; task_buf = conn->task_pool; for (i = 0; i < conn->max_outst_pdu; i++) { task = (void *) task_buf; iser_task_init(task, conn, pdu_data_buf, buf_size, conn->pdu_data_mr); task_buf += sizeof(*task); /* ToDo: need to fix the ISCSI_PARAM_INITIATOR_RDSL bug in initiator */ /* pdu_data_buf += conn->rsize + conn->ssize; */ pdu_data_buf += buf_size; err = iser_post_recv(conn, task, 1); if (err) { eprintf("conn:%p post_recv (%d/%d) failed\n", &conn->h, i, conn->max_outst_pdu); iser_free_ff_resources(conn); err = -1; goto out; } } /* initialize unsolicited tx task: nop-in */ task = (void *) task_buf; iser_unsolicited_task_init(task, conn, pdu_data_buf, buf_size, conn->pdu_data_mr); conn->nop_in_task = task; task_buf += sizeof(*task); pdu_data_buf += buf_size; /* initialize tx task: text/login */ task = (void *) task_buf; iser_unsolicited_task_init(task, conn, pdu_data_buf, buf_size, conn->pdu_data_mr); conn->text_tx_task = task; out: return err; } /* * On connection shutdown. */ static void iser_free_ff_resources(struct iser_conn *conn) { int err; if (!conn->ff_res_alloc) return; dprintf("conn:%p pdu_mr:%p pdu_pool:%p task_pool:%p\n", &conn->h, conn->pdu_data_mr, conn->pdu_data_pool, conn->task_pool); /* release mr and free the lists */ if (conn->pdu_data_mr) { err = ibv_dereg_mr(conn->pdu_data_mr); if (err) eprintf("conn:%p ibv_dereg_mr failed, %m\n", &conn->h); } if (conn->pdu_data_pool) free(conn->pdu_data_pool); if (conn->task_pool) free(conn->task_pool); conn->nop_in_task = NULL; conn->text_tx_task = NULL; conn->ff_res_alloc = 0; } /* * Allocate resources for this new connection. Called after login, when * final negotiated transfer parameters are known. */ int iser_login_complete(struct iscsi_connection *iscsi_conn) { struct iser_conn *conn = ISER_CONN(iscsi_conn); unsigned int trdsl; /* unsigned int irdsl; */ unsigned int outst_pdu, hdrsz; uint32_t max_q_cmd; int err = -1; dprintf("entry\n"); /* one more send, then done; login resources are left until then */ iser_conn_login_phase_set(conn, LOGIN_PHASE_LAST_SEND); /* irdsl = iscsi_conn->session_param[ISCSI_PARAM_INITIATOR_RDSL].val; */ trdsl = iscsi_conn->session_param[ISCSI_PARAM_TARGET_RDSL].val; /* ToDo: outstanding pdus num */ /* outst_pdu = iscsi_conn->session_param[ISCSI_PARAM_MAX_OUTST_PDU].val; */ /* hack, ib/ulp/iser does not have this param, but reading the code * shows their formula for max tx dtos outstanding * = cmds_max * (1 + dataouts) + rx_misc + tx_misc */ #define ISER_INFLIGHT_DATAOUTS 0 #define ISER_MAX_RX_MISC_PDUS 4 #define ISER_MAX_TX_MISC_PDUS 6 max_q_cmd = iscsi_conn->session_param[ISCSI_PARAM_MAX_QUEUE_CMD].val; /* if (outst_pdu == 0) // ToDo: outstanding pdus num */ outst_pdu = 3 * max_q_cmd * (1 + ISER_INFLIGHT_DATAOUTS) + ISER_MAX_RX_MISC_PDUS + ISER_MAX_TX_MISC_PDUS; /* RDSLs do not include headers. */ hdrsz = sizeof(struct iser_hdr) + sizeof(struct iscsi_hdr) + sizeof(struct iscsi_ecdb_ahdr) + sizeof(struct iscsi_rlength_ahdr); if (trdsl < 1024) trdsl = 1024; conn->rsize = hdrsz + trdsl; /* ToDo: need to fix the ISCSI_PARAM_INITIATOR_RDSL bug in initiator */ /* conn->ssize = hdrsz + irdsl; */ conn->ssize = conn->rsize; conn->max_outst_pdu = outst_pdu; err = iser_alloc_ff_resources(conn); if (err) goto out; /* How much data to grab in an RDMA operation, read or write */ /* ToDo: fix iscsi login code, broken handling of MAX_XMIT_DL */ iscsi_conn->session_param[ISCSI_PARAM_MAX_XMIT_DLENGTH].val = membuf_size; out: return err; } static int iser_ib_clear_iosubmit_list(struct iser_conn *conn) { struct iser_task *task; dprintf("start\n"); while (!list_empty(&conn->iosubmit_list)) { task = list_first_entry(&conn->iosubmit_list, struct iser_task, exec_list); list_del(&task->exec_list); iser_complete_task(task); /* must free, task keeps rdma buffer */ } return 0; } static int iser_ib_clear_rdma_rd_list(struct iser_conn *conn) { struct iser_task *task; dprintf("start\n"); while (!list_empty(&conn->rdma_rd_list)) { task = list_first_entry(&conn->rdma_rd_list, struct iser_task, rdma_list); list_del(&task->rdma_list); iser_complete_task(task); /* must free, task keeps rdma buffer */ } return 0; } static int iser_ib_clear_tx_list(struct iser_conn *conn) { struct iser_task *task; dprintf("start\n"); while (!list_empty(&conn->resp_tx_list)) { task = list_first_entry(&conn->resp_tx_list, struct iser_task, tx_list); list_del(&task->tx_list); iser_complete_task(task); /* must free, task keeps rdma buffer */ } return 0; } static int iser_ib_clear_sent_list(struct iser_conn *conn) { struct iser_task *task; dprintf("start\n"); while (!list_empty(&conn->sent_list)) { task = list_first_entry(&conn->sent_list, struct iser_task, tx_list); list_del(&task->tx_list); /* don't free, future completion guaranteed */ } return 0; } static int iser_ib_clear_post_recv_list(struct iser_conn *conn) { struct iser_task *task; dprintf("start\n"); while (!list_empty(&conn->post_recv_list)) { task = list_first_entry(&conn->post_recv_list, struct iser_task, recv_list); list_del(&task->recv_list); /* don't free, future completion guaranteed */ } return 0; } int iser_conn_init(struct iser_conn *conn) { conn->h.refcount = 0; conn->h.state = STATE_INIT; param_set_defaults(conn->h.session_param, session_keys); INIT_LIST_HEAD(&conn->h.clist); INIT_LIST_HEAD(&conn->buf_alloc_list); INIT_LIST_HEAD(&conn->rdma_rd_list); /* INIT_LIST_HEAD(&conn->rdma_wr_list); */ INIT_LIST_HEAD(&conn->iosubmit_list); INIT_LIST_HEAD(&conn->resp_tx_list); INIT_LIST_HEAD(&conn->sent_list); INIT_LIST_HEAD(&conn->post_recv_list); return 0; } /* * Start closing connection. Transfer IB QP to error state. * This will be followed by WC error and buffers flush events. * We also should expect DISCONNECTED and TIMEWAIT_EXIT events. * Only after the draining is over we are sure to have reclaimed * all buffers (and tasks). After the RDMA CM events are collected, * the connection QP may be destroyed, and its number may be recycled. */ void iser_conn_close(struct iser_conn *conn) { int err; if (conn->h.state == STATE_CLOSE) return; dprintf("rdma_disconnect conn:%p\n", &conn->h); err = rdma_disconnect(conn->cm_id); if (err) eprintf("conn:%p rdma_disconnect failed, %m\n", &conn->h); iser_ib_clear_tx_list(conn); list_del(&conn->conn_list); tgt_remove_sched_event(&conn->sched_buf_alloc); tgt_remove_sched_event(&conn->sched_rdma_rd); tgt_remove_sched_event(&conn->sched_iosubmit); tgt_remove_sched_event(&conn->sched_tx); tgt_remove_sched_event(&conn->sched_post_recv); conn->h.state = STATE_CLOSE; eprintf("conn:%p cm_id:0x%p state: CLOSE, refcnt:%d\n", &conn->h, conn->cm_id, conn->h.refcount); } static void iser_conn_force_close(struct iscsi_connection *iscsi_conn) { struct iser_conn *conn = ISER_CONN(iscsi_conn); eprintf("conn:%p\n", &conn->h); conn->h.closed = 1; iser_conn_close(conn); iser_conn_put(conn); } /* * Called when the connection is freed, from iscsi, but won't do anything until * all posted WRs have gone away. So also called again from RX progress when * it notices this happens. */ void iser_conn_free(struct iser_conn *conn) { int err; dprintf("conn:%p refcnt:%d qp:%p cm_id:%p\n", &conn->h, conn->h.refcount, conn->qp_hndl, conn->cm_id); assert(conn->h.refcount == 0); iser_ib_clear_iosubmit_list(conn); iser_ib_clear_rdma_rd_list(conn); iser_ib_clear_tx_list(conn); iser_ib_clear_sent_list(conn); iser_ib_clear_post_recv_list(conn); /* try to free unconditionally, resources freed only if necessary */ iser_free_login_resources(conn); iser_free_ff_resources(conn); if (conn->qp_hndl) { err = ibv_destroy_qp(conn->qp_hndl); if (err) eprintf("conn:%p ibv_destroy_qp failed, %m\n", &conn->h); } if (conn->cm_id) { err = rdma_destroy_id(conn->cm_id); if (err) eprintf("conn:%p rdma_destroy_id failed, %m\n", &conn->h); } /* delete from session; was put there by conn_add_to_session() */ list_del(&conn->h.clist); if (conn->h.initiator) free(conn->h.initiator); if (conn->h.session) session_put(conn->h.session); if (conn->peer_name) free(conn->peer_name); if (conn->self_name) free(conn->self_name); conn->h.state = STATE_INIT; dprintf("conn:%p freed\n", &conn->h); free(conn); } static void iser_sched_conn_free(struct event_data *evt) { struct iser_conn *conn = (struct iser_conn *) evt->data; iser_conn_free(conn); } int iser_conn_get(struct iser_conn *conn) { /* TODO: check state */ conn->h.refcount++; dprintf("refcnt:%d\n", conn->h.refcount); return 0; } int iser_conn_getn(struct iser_conn *conn, int n) { int new_count = conn->h.refcount + n; dprintf("refcnt:%d + %d = %d\n", conn->h.refcount, n, new_count); conn->h.refcount = new_count; return 0; } void iser_conn_put(struct iser_conn *conn) { conn->h.refcount--; dprintf("refcnt:%d\n", conn->h.refcount); if (unlikely(conn->h.refcount == 0)) { assert(conn->h.state == STATE_CLOSE); tgt_add_sched_event(&conn->sched_conn_free); } } static int iser_get_host_name(struct sockaddr_storage *addr, char **name) { int err; char host[NI_MAXHOST]; if (name == NULL) return -EINVAL; err = getnameinfo((struct sockaddr *) addr, sizeof(*addr), host, sizeof(host), NULL, 0, NI_NUMERICHOST); if (!err) { *name = strdup(host); if (*name == NULL) err = -ENOMEM; } else eprintf("getnameinfo failed, %m\n"); return err; } static int iser_show(struct iscsi_connection *iscsi_conn, char *buf, int rest) { struct iser_conn *conn = ISER_CONN(iscsi_conn); int len; len = snprintf(buf, rest, "RDMA IP Address: %s", conn->peer_name); return len; } static int iser_getsockname(struct iscsi_connection *iscsi_conn, struct sockaddr *sa, socklen_t *len) { struct iser_conn *conn = ISER_CONN(iscsi_conn); if (*len > sizeof(conn->self_addr)) *len = sizeof(conn->self_addr); memcpy(sa, &conn->self_addr, *len); return 0; } static int iser_getpeername(struct iscsi_connection *iscsi_conn, struct sockaddr *sa, socklen_t *len) { struct iser_conn *conn = ISER_CONN(iscsi_conn); if (*len > sizeof(conn->peer_addr)) *len = sizeof(conn->peer_addr); memcpy(sa, &conn->peer_addr, *len); return 0; } static void iser_cm_connect_request(struct rdma_cm_event *ev) { struct rdma_cm_id *cm_id = ev->id; struct ibv_qp_init_attr qp_init_attr; struct iser_conn *conn; struct iser_device *dev; int err, dev_found; struct rdma_conn_param conn_param = { .responder_resources = 1, .initiator_depth = 1, .retry_count = 5, }; /* find device */ dev_found = 0; list_for_each_entry(dev, &iser_dev_list, list) { if (dev->ibv_ctxt == cm_id->verbs) { dev_found = 1; break; } } if (!dev_found) { dev = malloc(sizeof(*dev)); if (dev == NULL) { eprintf("cm_id:%p malloc dev failed\n", cm_id); goto reject; } dev->ibv_ctxt = cm_id->verbs; err = iser_device_init(dev); if (err) { free(dev); goto reject; } } /* build a new connection structure */ conn = zalloc(sizeof(*conn)); if (!conn) { eprintf("cm_id:%p malloc conn failed\n", cm_id); goto reject; } err = iser_conn_init(conn); if (err) { free(conn); goto reject; } dprintf("alloc conn:%p cm_id:%p\n", &conn->h, cm_id); list_add(&conn->conn_list, &iser_conn_list); /* relate iser and rdma connections */ conn->cm_id = cm_id; cm_id->context = conn; conn->dev = dev; iser_conn_login_phase_set(conn, LOGIN_PHASE_START); tgt_init_sched_event(&conn->sched_buf_alloc, iser_sched_buf_alloc, conn); tgt_init_sched_event(&conn->sched_iosubmit, iser_sched_iosubmit, conn); tgt_init_sched_event(&conn->sched_rdma_rd, iser_sched_rdma_rd, conn); tgt_init_sched_event(&conn->sched_tx, iser_sched_tx, conn); tgt_init_sched_event(&conn->sched_post_recv, iser_sched_post_recv, conn); tgt_init_sched_event(&conn->sched_conn_free, iser_sched_conn_free, conn); /* initiator is dst, target is src */ memcpy(&conn->peer_addr, &cm_id->route.addr.dst_addr, sizeof(conn->peer_addr)); err = iser_get_host_name(&conn->peer_addr, &conn->peer_name); if (err) conn->peer_name = strdup("Unresolved"); memcpy(&conn->self_addr, &cm_id->route.addr.src_addr, sizeof(conn->self_addr)); err = iser_get_host_name(&conn->self_addr, &conn->self_name); if (err) conn->self_name = strdup("Unresolved"); /* create qp */ memset(&qp_init_attr, 0, sizeof(qp_init_attr)); qp_init_attr.qp_context = conn; /* both send and recv to the same CQ */ qp_init_attr.send_cq = dev->cq; qp_init_attr.recv_cq = dev->cq; qp_init_attr.cap.max_send_wr = MAX_WQE; qp_init_attr.cap.max_recv_wr = MAX_WQE; qp_init_attr.cap.max_send_sge = 1; /* scatter/gather entries */ qp_init_attr.cap.max_recv_sge = 1; qp_init_attr.qp_type = IBV_QPT_RC; /* only generate completion queue entries if requested */ qp_init_attr.sq_sig_all = 0; err = rdma_create_qp(cm_id, dev->pd, &qp_init_attr); if (err) { eprintf("conn:%p cm_id:%p rdma_create_qp failed, %m\n", &conn->h, cm_id); goto free_conn; } conn->qp_hndl = cm_id->qp; VALGRIND_MAKE_MEM_DEFINED(conn->qp_hndl, sizeof(*conn->qp_hndl)); dprintf("conn:%p cm_id:%p, created qp:%p\n", &conn->h, cm_id, conn->qp_hndl); /* * Post buffers for the login phase, only. */ conn->rsize = sizeof(struct iser_hdr) + sizeof(struct iscsi_hdr) + sizeof(struct iscsi_ecdb_ahdr) + sizeof(struct iscsi_rlength_ahdr) + 8192; conn->ssize = conn->rsize; err = iser_alloc_login_resources(conn); if (err) goto free_conn; conn_param.initiator_depth = dev->device_attr.max_qp_init_rd_atom; if (conn_param.initiator_depth > ev->param.conn.initiator_depth) conn_param.initiator_depth = ev->param.conn.initiator_depth; /* Increment reference count to be able to wait for TIMEWAIT_EXIT when finalizing the disconnect process */ iser_conn_get(conn); /* now we can actually accept the connection */ err = rdma_accept(conn->cm_id, &conn_param); if (err) { eprintf("conn:%p cm_id:%p rdma_accept failed, %m\n", &conn->h, cm_id); goto free_conn; } conn->h.tp = &iscsi_iser; conn->h.state = STATE_START; dprintf("conn:%p cm_id:%p, %s -> %s, accepted\n", &conn->h, cm_id, conn->peer_name, conn->self_name); return; free_conn: iser_conn_free(conn); reject: err = rdma_reject(cm_id, NULL, 0); if (err) eprintf("cm_id:%p rdma_reject failed, %m\n", cm_id); } /* * Finish putting the connection together, now that the other side * has ACKed our acceptance. Moves it from the temp_conn to the * iser_conn_list. * * Release the temporary conn_info and glue it into iser_conn_list. */ static void iser_cm_conn_established(struct rdma_cm_event *ev) { struct rdma_cm_id *cm_id = ev->id; struct iser_conn *conn = cm_id->context; if (conn->h.state == STATE_START) { conn->h.state = STATE_READY; eprintf("conn:%p cm_id:%p, %s -> %s, established\n", &conn->h, cm_id, conn->peer_name, conn->self_name); } else if (conn->h.state == STATE_READY) { eprintf("conn:%p cm_id:%p, %s -> %s, " "execute delayed login_rx now\n", &conn->h, cm_id, conn->peer_name, conn->self_name); iser_login_rx(conn->login_rx_task); } } /* * Handle RDMA_CM_EVENT_DISCONNECTED or an equivalent event. * Start closing the target's side connection. */ static void iser_cm_disconnected(struct rdma_cm_event *ev) { struct rdma_cm_id *cm_id = ev->id; struct iser_conn *conn = cm_id->context; enum rdma_cm_event_type ev_type = ev->event; eprintf("conn:%p cm_id:%p event:%d, %s\n", &conn->h, cm_id, ev_type, rdma_event_str(ev_type)); iser_conn_close(conn); } /* * Handle RDMA_CM_EVENT_TIMEWAIT_EXIT which is expected to be the last * event during the lifecycle of a connection, when it had been shut down * and the network has cleared from the remaining in-flight messages. */ static void iser_cm_timewait_exit(struct rdma_cm_event *ev) { struct rdma_cm_id *cm_id = ev->id; struct iser_conn *conn = cm_id->context; eprintf("conn:%p refcnt:%d cm_id:%p\n", &conn->h, conn->h.refcount, cm_id); if (conn->h.state == STATE_CLOSE) { /* * Tasks sitting in the conn->tx_list are stuck there after we * close the conn and since each holds a reference on the conn * we need to clean them up explicitly now. Otherwise they will * prevent the conn from being cleaned up (since the refcount * won't reach zero). If the conn doesn't get cleaned up, then * along with leaking the conn itself (and all its resources), * we'll also leak any rdma_buf's associated with the tasks. * Since the rdma_buf pool is associated with the iser_device * and so gets reused when a new iser_conn connection is * established, leaking too many of those bufs will eventually * clear the pool. */ iser_ib_clear_tx_list(conn); eprintf("conn:%p refcnt:%d cm_id:%p (after cleanup)\n", &conn->h, conn->h.refcount, cm_id); } /* Refcount was incremented just before accepting the connection, typically this is the last decrement and the connection will be released instantly */ iser_conn_put(conn); } /* * Handle RDMA CM events. */ static void iser_handle_rdmacm(int fd __attribute__ ((unused)), int events __attribute__ ((unused)), void *data __attribute__ ((unused))) { struct rdma_cm_event *ev; enum rdma_cm_event_type ev_type; int err; err = rdma_get_cm_event(rdma_evt_channel, &ev); if (err) { eprintf("rdma_get_cm_event failed, %m\n"); return; } VALGRIND_MAKE_MEM_DEFINED(ev, sizeof(*ev)); ev_type = ev->event; switch (ev_type) { case RDMA_CM_EVENT_CONNECT_REQUEST: iser_cm_connect_request(ev); break; case RDMA_CM_EVENT_ESTABLISHED: iser_cm_conn_established(ev); break; case RDMA_CM_EVENT_CONNECT_ERROR: case RDMA_CM_EVENT_REJECTED: case RDMA_CM_EVENT_ADDR_CHANGE: case RDMA_CM_EVENT_DISCONNECTED: iser_cm_disconnected(ev); break; case RDMA_CM_EVENT_TIMEWAIT_EXIT: iser_cm_timewait_exit(ev); break; case RDMA_CM_EVENT_MULTICAST_JOIN: case RDMA_CM_EVENT_MULTICAST_ERROR: eprintf("UD-related event:%d, %s - ignored\n", ev_type, rdma_event_str(ev_type)); break; case RDMA_CM_EVENT_DEVICE_REMOVAL: eprintf("Unsupported event:%d, %s - ignored\n", ev_type, rdma_event_str(ev_type)); break; case RDMA_CM_EVENT_ADDR_RESOLVED: case RDMA_CM_EVENT_ADDR_ERROR: case RDMA_CM_EVENT_ROUTE_RESOLVED: case RDMA_CM_EVENT_ROUTE_ERROR: case RDMA_CM_EVENT_CONNECT_RESPONSE: case RDMA_CM_EVENT_UNREACHABLE: eprintf("Active side event:%d, %s - ignored\n", ev_type, rdma_event_str(ev_type)); break; default: eprintf("Illegal event:%d - ignored\n", ev_type); break; } err = rdma_ack_cm_event(ev); if (err) eprintf("ack cm event failed, %s\n", rdma_event_str(ev_type)); } static int iser_logout_exec(struct iser_task *task) { struct iser_conn *conn = task->conn; struct iscsi_logout_rsp *rsp_bhs = (struct iscsi_logout_rsp *) task->pdu.bhs; memset(rsp_bhs, 0, BHS_SIZE); rsp_bhs->opcode = ISCSI_OP_LOGOUT_RSP; rsp_bhs->flags = ISCSI_FLAG_CMD_FINAL; rsp_bhs->response = ISCSI_LOGOUT_SUCCESS; rsp_bhs->itt = task->tag; rsp_bhs->statsn = cpu_to_be32(conn->h.stat_sn++); rsp_bhs->exp_cmdsn = cpu_to_be32(conn->h.exp_cmd_sn); rsp_bhs->max_cmdsn = cpu_to_be32(conn->h.max_cmd_sn); task->pdu.ahssize = 0; task->pdu.membuf.size = 0; dprintf("rsp task:%p op:%0x itt:%0x statsn:%0x\n", task, (unsigned int)rsp_bhs->opcode, (unsigned int)rsp_bhs->itt, (unsigned int)rsp_bhs->statsn); /* add the logout resp to tx list, and force all scheduled tx now */ list_add_tail(&task->tx_list, &conn->resp_tx_list); dprintf("add to tx list, logout resp task:%p tag:0x%04"PRIx64 " cmdsn:0x%x\n", task, task->tag, task->cmd_sn); tgt_remove_sched_event(&conn->sched_tx); iser_sched_tx(&conn->sched_tx); return 0; } static int iser_nop_out_exec(struct iser_task *task) { struct iscsi_nopin *rsp_bhs = (struct iscsi_nopin *) task->pdu.bhs; struct iser_conn *conn = task->conn; rsp_bhs->opcode = ISCSI_OP_NOOP_IN; rsp_bhs->flags = ISCSI_FLAG_CMD_FINAL; rsp_bhs->rsvd2 = 0; rsp_bhs->rsvd3 = 0; memset(rsp_bhs->lun, 0, sizeof(rsp_bhs->lun)); rsp_bhs->itt = task->tag; rsp_bhs->ttt = ISCSI_RESERVED_TAG; rsp_bhs->statsn = cpu_to_be32(conn->h.stat_sn); if (task->tag != ISCSI_RESERVED_TAG) conn->h.stat_sn++; iser_set_rsp_stat_sn(conn->h.session, task->pdu.bhs); memset(rsp_bhs->rsvd4, 0, sizeof(rsp_bhs->rsvd4)); task->pdu.ahssize = 0; task->pdu.membuf.size = task->out_len; /* ping back nop-out data */ schedule_resp_tx(task, conn); return 0; } static int iser_send_ping_nop_in(struct iser_task *task) { struct iscsi_nopin *rsp_bhs = (struct iscsi_nopin *) task->pdu.bhs; struct iser_conn *conn = task->conn; task->opcode = ISCSI_OP_NOOP_IN; task->tag = ISCSI_RESERVED_TAG; rsp_bhs->opcode = ISCSI_OP_NOOP_IN; rsp_bhs->flags = ISCSI_FLAG_CMD_FINAL; rsp_bhs->rsvd2 = 0; rsp_bhs->rsvd3 = 0; memset(rsp_bhs->lun, 0, sizeof(rsp_bhs->lun)); rsp_bhs->itt = ISCSI_RESERVED_TAG; rsp_bhs->ttt = ISCSI_RESERVED_TAG; rsp_bhs->statsn = cpu_to_be32(conn->h.stat_sn); iser_set_rsp_stat_sn(conn->h.session, task->pdu.bhs); memset(rsp_bhs->rsvd4, 0, sizeof(rsp_bhs->rsvd4)); task->pdu.ahssize = 0; task->pdu.membuf.size = 0; dprintf("task:%p conn:%p\n", task, &conn->h); schedule_resp_tx(task, conn); return 0; } /* static int iser_send_reject(struct iser_task *task, uint8 reason) { struct iser_conn *conn = task->conn; struct iscsi_session *session = conn->h.session; struct iscsi_hdr *req = (struct iscsi_hdr *) task->req.bhs; struct iscsi_reject *rsp = (struct iscsi_reject *) task->rsp.bhs; memset(rsp, 0, BHS_SIZE); rsp->opcode = ISCSI_OP_REJECT; rsp->flags = ISCSI_FLAG_CMD_FINAL; rsp->reason = reason; hton24(rsp->dlength, sizeof(struct iser_hdr)); rsp->ffffffff = 0xffffffff; rsp->itt = req->itt; rsp->statsn = cpu_to_be32(conn->h.stat_sn++); iser_set_rsp_stat_sn(session, task->rsp.bhs); task->rsp.ahssize = 0; task->rsp.membuf.addr = task->req.bhs; task->rsp.membuf.size = sizeof(struct iser_hdr); schedule_resp_tx(task, conn); return 0; } */ static int iser_tm_exec(struct iser_task *task) { struct iser_conn *conn = task->conn; struct iscsi_session *session = conn->h.session; struct iscsi_tm *req_bhs = (struct iscsi_tm *) task->pdu.bhs; int fn = 0; int err = 0; eprintf("conn:%p TM itt:0x%x cmdsn:0x%x " "ref.itt:0x%x ref.cmdsn:0x%x " "exp.cmdsn:0x%x " "lun:0x%02x%02x%02x%02x%02x%02x%02x%02x\n", &conn->h, be32_to_cpu(req_bhs->itt), be32_to_cpu(req_bhs->cmdsn), be32_to_cpu(req_bhs->rtt), be32_to_cpu(req_bhs->refcmdsn), be32_to_cpu(req_bhs->exp_statsn), req_bhs->lun[0], req_bhs->lun[1], req_bhs->lun[2], req_bhs->lun[3], req_bhs->lun[4], req_bhs->lun[5], req_bhs->lun[6], req_bhs->lun[7]); switch (req_bhs->flags & ISCSI_FLAG_TM_FUNC_MASK) { case ISCSI_TM_FUNC_ABORT_TASK: fn = ABORT_TASK; break; case ISCSI_TM_FUNC_ABORT_TASK_SET: fn = ABORT_TASK_SET; break; case ISCSI_TM_FUNC_CLEAR_ACA: fn = CLEAR_TASK_SET; break; case ISCSI_TM_FUNC_CLEAR_TASK_SET: fn = CLEAR_ACA; break; case ISCSI_TM_FUNC_LOGICAL_UNIT_RESET: fn = LOGICAL_UNIT_RESET; break; case ISCSI_TM_FUNC_TARGET_WARM_RESET: case ISCSI_TM_FUNC_TARGET_COLD_RESET: case ISCSI_TM_FUNC_TASK_REASSIGN: err = ISCSI_TMF_RSP_NOT_SUPPORTED; eprintf("unsupported TMF %d\n", req_bhs->flags & ISCSI_FLAG_TM_FUNC_MASK); break; default: err = ISCSI_TMF_RSP_REJECTED; eprintf("unknown TMF %d\n", req_bhs->flags & ISCSI_FLAG_TM_FUNC_MASK); } if (err) task->result = err; else { int ret; ret = target_mgmt_request(session->target->tid, session->tsih, (unsigned long) task, fn, req_bhs->lun, req_bhs->rtt, 0); set_task_in_scsi(task); iser_conn_get(conn); switch (ret) { case MGMT_REQ_QUEUED: break; case MGMT_REQ_FAILED: case MGMT_REQ_DONE: clear_task_in_scsi(task); iser_conn_put(conn); break; } } return err; } static int iser_tm_done(struct mgmt_req *mreq) { struct iser_task *task = (struct iser_task *) (unsigned long) mreq->mid; struct iser_conn *conn = task->conn; struct iscsi_session *session = conn->h.session; struct iscsi_tm_rsp *rsp_bhs = (struct iscsi_tm_rsp *) task->pdu.bhs; memset(rsp_bhs, 0, sizeof(*rsp_bhs)); rsp_bhs->opcode = ISCSI_OP_SCSI_TMFUNC_RSP; rsp_bhs->flags = ISCSI_FLAG_CMD_FINAL; rsp_bhs->itt = task->tag; switch (mreq->result) { case 0: rsp_bhs->response = ISCSI_TMF_RSP_COMPLETE; break; case -EINVAL: rsp_bhs->response = ISCSI_TMF_RSP_NOT_SUPPORTED; break; case -EEXIST: /* * the command completed or we could not find it so * we retrun no task here */ rsp_bhs->response = ISCSI_TMF_RSP_NO_TASK; break; default: rsp_bhs->response = ISCSI_TMF_RSP_REJECTED; break; } rsp_bhs->statsn = cpu_to_be32(conn->h.stat_sn++); iser_set_rsp_stat_sn(session, task->pdu.bhs); /* Must be zero according to 10.6.3 */ task->pdu.ahssize = 0; task->pdu.membuf.size = 0; schedule_resp_tx(task, conn); return 0; } static int scsi_cmd_attr(unsigned int flags) { int attr; switch (flags & ISCSI_FLAG_CMD_ATTR_MASK) { case ISCSI_ATTR_UNTAGGED: case ISCSI_ATTR_SIMPLE: attr = MSG_SIMPLE_TAG; break; case ISCSI_ATTR_HEAD_OF_QUEUE: attr = MSG_HEAD_TAG; break; case ISCSI_ATTR_ORDERED: default: attr = MSG_ORDERED_TAG; } return attr; } static void iser_task_free_dout_tasks(struct iser_task *task) { struct iser_conn *conn = task->conn; struct iser_task *dout_task, *tnext; list_for_each_entry_safe(dout_task, tnext, &task->dout_task_list, dout_task_list) { list_del(&dout_task->dout_task_list); iser_task_del_out_buf(task, &dout_task->pdu.membuf); schedule_post_recv(dout_task, conn); } } static void iser_scsi_cmd_iosubmit(struct iser_task *task, int not_last) { struct iscsi_cmd *req_bhs = (struct iscsi_cmd *) task->pdu.bhs; struct scsi_cmd *scmd = &task->scmd; struct iser_conn *conn = task->conn; struct iscsi_session *session = conn->h.session; struct iser_membuf *data_buf; scmd->state = 0; if (not_last) set_cmd_not_last(scmd); scsi_set_in_length(scmd, 0); scsi_set_out_length(scmd, 0); if (task->is_write) { /* It's either the last buffer, which is RDMA, or the only buffer */ data_buf = list_entry(task->out_buf_list.prev, struct iser_membuf, task_list); /* ToDo: multiple RDMA-Write buffers */ if (task->out_buf_num > 1) { unsigned char *ptr = data_buf->addr; struct iser_membuf *cur_buf; list_for_each_entry(cur_buf, &task->out_buf_list, task_list) { if (cur_buf->rdma) break; memcpy(ptr, cur_buf->addr, cur_buf->size); ptr += cur_buf->size; } iser_task_free_dout_tasks(task); } scsi_set_out_buffer(scmd, data_buf->addr); scsi_set_out_length(scmd, task->out_len); } if (task->is_read) { /* ToDo: multiple RDMA-Read buffers */ data_buf = list_entry(task->in_buf_list.next, struct iser_membuf, task_list); scsi_set_in_buffer(scmd, data_buf->addr); scsi_set_in_length(scmd, task->in_len); } scmd->cmd_itn_id = session->tsih; scmd->scb = req_bhs->cdb; scmd->scb_len = sizeof(req_bhs->cdb); memcpy(scmd->lun, req_bhs->lun, sizeof(scmd->lun)); scmd->attribute = scsi_cmd_attr(req_bhs->flags); scmd->tag = task->tag; scmd->result = 0; scmd->mreq = NULL; scmd->sense_len = 0; dprintf("task:%p tag:0x%04"PRIx64 "\n", task, task->tag); set_task_in_scsi(task); iser_conn_get(conn); target_cmd_queue(session->target->tid, scmd); } static int iser_scsi_cmd_done(uint64_t nid, int result, struct scsi_cmd *scmd) { struct iser_task *task = container_of(scmd, struct iser_task, scmd); struct iscsi_cmd_rsp *rsp_bhs = (struct iscsi_cmd_rsp *) task->pdu.bhs; struct iser_conn *conn = task->conn; struct iscsi_session *session = conn->h.session; unsigned char sense_len = scmd->sense_len; assert(nid == scmd->cmd_itn_id); if (unlikely(conn->h.state != STATE_FULL)) { /* Connection is closed, but its resources are not released to allow receiving completion of such late tasks. When all tasks are released, and connection refcnt drops to zero, then all the resources can be freed. */ iser_complete_task(task); return 0; } rsp_bhs->opcode = ISCSI_OP_SCSI_CMD_RSP; rsp_bhs->flags = ISCSI_FLAG_CMD_FINAL; rsp_bhs->response = ISCSI_STATUS_CMD_COMPLETED; rsp_bhs->cmd_status = scsi_get_result(scmd); *((uint64_t *) rsp_bhs->rsvd) = (uint64_t) 0; rsp_bhs->itt = task->tag; rsp_bhs->rsvd1 = 0; rsp_bhs->statsn = cpu_to_be32(conn->h.stat_sn++); iser_set_rsp_stat_sn(session, task->pdu.bhs); rsp_bhs->exp_datasn = 0; iscsi_rsp_set_residual(rsp_bhs, scmd); if (task->is_read) { task->rdma_wr_remains = scsi_get_in_transfer_len(scmd); task->rdma_wr_sz = scsi_get_in_transfer_len(scmd); } task->pdu.ahssize = 0; task->pdu.membuf.size = 0; if (unlikely(sense_len > 0)) { struct iscsi_sense_data *sense = (struct iscsi_sense_data *) task->pdu.membuf.addr; sense->length = cpu_to_be16(sense_len); /* ToDo: need to fix the ISCSI_PARAM_INITIATOR_RDSL bug in initiator */ assert(sense_len + 2 <= conn->rsize); memcpy(sense->data, scmd->sense_buffer, sense_len); task->pdu.membuf.size = sense_len + sizeof(*sense); } dprintf("task:%p tag:0x%04"PRIx64 " status:%x statsn:%d flags:0x%x " "in_len:%d out_len:%d rsd:%d birsd:%d\n", task, task->tag, rsp_bhs->cmd_status, conn->h.stat_sn-1, rsp_bhs->flags, scsi_get_in_length(scmd), scsi_get_out_length(scmd), ntohl(rsp_bhs->residual_count), ntohl(rsp_bhs->bi_residual_count)); schedule_resp_tx(task, conn); return 0; } static void iser_sched_buf_alloc(struct event_data *evt) { struct iser_conn *conn = (struct iser_conn *) evt->data; struct iser_task *task; int err; while (!list_empty(&conn->buf_alloc_list)) { task = list_first_entry(&conn->buf_alloc_list, struct iser_task, exec_list); err = iser_task_alloc_rdma_bufs(task); if (likely(!err)) list_del(&task->exec_list); else { if (err != -ENOMEM) iser_conn_close(conn); break; } } } static inline int task_can_batch(struct iser_task *task) { struct iscsi_cmd *req_bhs = (struct iscsi_cmd *) task->pdu.bhs; switch (req_bhs->cdb[0]) { case SYNCHRONIZE_CACHE: case SYNCHRONIZE_CACHE_16: case WRITE_6: case WRITE_10: case WRITE_12: case WRITE_16: case READ_6: case READ_10: case READ_12: case READ_16: return 1; default: return 0; } } static void iser_sched_iosubmit(struct event_data *evt) { struct iser_conn *conn = (struct iser_conn *) evt->data; struct iser_task *task, *next_task; int last_in_batch; list_for_each_entry_safe(task, next_task, &conn->iosubmit_list, exec_list) { if (&next_task->exec_list == &conn->iosubmit_list) last_in_batch = 1; /* end of list */ else last_in_batch = (task_can_batch(task) && task_can_batch(next_task)) ? 0 : 1; list_del(&task->exec_list); iser_scsi_cmd_iosubmit(task, !last_in_batch); } } static void iser_sched_rdma_rd(struct event_data *evt) { struct iser_conn *conn = (struct iser_conn *) evt->data; struct iser_work_req *first_wr = NULL; struct iser_task *prev_task = NULL; struct iser_task *task = NULL; int num_reqs = 0; if (unlikely(conn->h.state != STATE_FULL)) { dprintf("conn:%p closing, ignoring rdma_rd\n", conn); /* ToDo: free all tasks and buffers */ return; } while (!list_empty(&conn->rdma_rd_list)) { task = list_first_entry(&conn->rdma_rd_list, struct iser_task, rdma_list); list_del(&task->rdma_list); iser_prep_rdma_rd_send_req(task, NULL, 1); if (first_wr == NULL) first_wr = &task->rdmad; else prev_task->rdmad.send_wr.next = &task->rdmad.send_wr; prev_task = task; num_reqs++; } if (prev_task) { prev_task->rdmad.send_wr.next = NULL; /* submit the chain of rdma-rd requests, start from the first */ iser_post_send(conn, first_wr, num_reqs); /* ToDo: error handling */ } } static void iser_sched_tx(struct event_data *evt) { struct iser_conn *conn = (struct iser_conn *) evt->data; struct iser_work_req *first_wr = NULL; struct iser_task *prev_task = NULL; struct iser_task *task; struct iser_work_req *cur_send_wr; int num_reqs = 0; if (unlikely(conn->h.state == STATE_CLOSE)) { dprintf("conn:%p closing, ignoring tx\n", conn); return; } while (!list_empty(&conn->resp_tx_list)) { task = list_first_entry(&conn->resp_tx_list, struct iser_task, tx_list); list_del(&task->tx_list); list_add_tail(&task->tx_list, &conn->sent_list); if (task->is_read && task->rdma_wr_remains) { iser_prep_rdma_wr_send_req(task, &task->txd, 1); cur_send_wr = &task->rdmad; num_reqs++; } else cur_send_wr = &task->txd; if (prev_task == NULL) first_wr = cur_send_wr; else prev_task->txd.send_wr.next = &cur_send_wr->send_wr; iser_prep_resp_send_req(task, NULL, 1); prev_task = task; num_reqs++; } if (prev_task) { prev_task->txd.send_wr.next = NULL; /* submit the chain of rdma-wr & tx reqs, start from the first */ iser_post_send(conn, first_wr, num_reqs); /* ToDo: error handling */ } } static void iser_sched_post_recv(struct event_data *evt) { struct iser_conn *conn = (struct iser_conn *) evt->data; struct iser_task *first_task = NULL; struct iser_task *prev_task = NULL; struct iser_task *task; int num_recv_bufs = 0; if (unlikely(conn->h.state != STATE_FULL)) { dprintf("conn:%p closing, ignoring post recv\n", conn); return; } while (!list_empty(&conn->post_recv_list)) { task = list_first_entry(&conn->post_recv_list, struct iser_task, recv_list); list_del(&task->recv_list); if (prev_task == NULL) first_task = task; else prev_task->rxd.recv_wr.next = &task->rxd.recv_wr; prev_task = task; num_recv_bufs++; } if (prev_task) { prev_task->rxd.recv_wr.next = NULL; /* post the chain of recv buffers, start from the first */ iser_post_recv(conn, first_task, num_recv_bufs); /* ToDo: error handling */ } } static int iser_task_handle_ahs(struct iser_task *task) { struct iscsi_connection *conn = &task->conn->h; struct iscsi_cmd *req_bhs = (struct iscsi_cmd *) task->pdu.bhs; int ahslen = task->pdu.ahssize; void *ahs = task->pdu.ahs; struct scsi_cmd *scmd = &task->scmd; enum data_direction dir = scsi_get_data_dir(scmd); if (ahslen >= 4) { struct iscsi_ecdb_ahdr *ahs_extcdb = ahs; if (ahs_extcdb->ahstype == ISCSI_AHSTYPE_CDB) { int extcdb_len = ntohs(ahs_extcdb->ahslength) - 1; int total_cdb_len = sizeof(req_bhs->cdb) + extcdb_len; unsigned char *p; if (4 + extcdb_len > ahslen) { eprintf("AHS len:%d too short for extcdb %d\n", ahslen, extcdb_len); return -EINVAL; } if (total_cdb_len > 260) { eprintf("invalid extcdb len:%d\n", extcdb_len); return -EINVAL; } p = malloc(total_cdb_len); if (!p) { eprintf("failed to allocate extdata len:%d\n", total_cdb_len); return -ENOMEM; } memcpy(p, req_bhs->cdb, sizeof(req_bhs->cdb)); p += sizeof(req_bhs->cdb); memcpy(p, ahs_extcdb->ecdb, extcdb_len); task->extdata = p; scmd->scb = ahs; scmd->scb_len = total_cdb_len; ahs += 4 + extcdb_len; ahslen -= 4 + extcdb_len; } } if (dir == DATA_BIDIRECTIONAL && ahslen >= 8) { struct iscsi_rlength_ahdr *ahs_bidi = ahs; if (ahs_bidi->ahstype == ISCSI_AHSTYPE_RLENGTH) { uint32_t in_length = ntohl(ahs_bidi->read_length); if (in_length) task->in_len = roundup(in_length, conn->tp->data_padding); dprintf("bidi read len:%u, padded:%u\n", in_length, task->in_len); } } return 0; } static int iser_scsi_cmd_rx(struct iser_task *task) { struct iser_conn *conn = task->conn; struct iscsi_session *session = conn->h.session; struct iscsi_cmd *req_bhs = (struct iscsi_cmd *) task->pdu.bhs; unsigned int flags = req_bhs->flags; uint32_t imm_data_sz = ntoh24(req_bhs->dlength); uint32_t xfer_sz = ntohl(req_bhs->data_length); int err = 0; task->is_read = flags & ISCSI_FLAG_CMD_READ; task->is_write = flags & ISCSI_FLAG_CMD_WRITE; if (task->is_write) { task->out_len = xfer_sz; if (!task->is_read) { scsi_set_data_dir(&task->scmd, DATA_WRITE); /* reset irrelevant fields */ task->in_len = 0; task->rdma_wr_sz = 0; task->rdma_wr_remains = 0; } else scsi_set_data_dir(&task->scmd, DATA_BIDIRECTIONAL); } else { if (likely(task->is_read)) { task->in_len = xfer_sz; scsi_set_data_dir(&task->scmd, DATA_READ); /* reset irrelevant fields */ task->out_len = 0; task->unsol_sz = 0; task->unsol_remains = 0; task->rdma_rd_sz = 0; task->rdma_rd_remains = 0; } else { scsi_set_data_dir(&task->scmd, DATA_NONE); task->out_len = 0; task->in_len = 0; } } if (task->pdu.ahssize) { err = iser_task_handle_ahs(task); /* may set task->in_len */ if (err) goto out; } if (task->is_write) { /* add immediate data to the task */ if (!conn->h.session_param[ISCSI_PARAM_INITIAL_R2T_EN].val) { task->unsol_sz = conn->h.session_param[ISCSI_PARAM_FIRST_BURST].val; if (task->out_len < task->unsol_sz) task->unsol_sz = task->out_len; } else task->unsol_sz = 0; if (conn->h.session_param[ISCSI_PARAM_IMM_DATA_EN].val) { if (imm_data_sz > 0) { if (task->unsol_sz == 0) task->unsol_sz = imm_data_sz; iser_task_add_out_pdu_buf(task, &task->pdu.membuf, 0); } } else if (imm_data_sz > 0) { eprintf("ImmediateData disabled but received\n"); err = -EINVAL; goto out; } /* immediate data is the first chunk of the unsolicited data */ task->unsol_remains = task->unsol_sz - imm_data_sz; /* rdma-reads cover the entire solicited data */ task->rdma_rd_sz = task->out_len - task->unsol_sz; task->rdma_rd_remains = task->rdma_rd_sz; /* ToDo: multiple RDMA-Write buffers */ /* task->rdma_rd_offset = task->unsol_sz; */ } if (task->is_read) { task->rdma_wr_sz = task->in_len; task->rdma_wr_remains = task->in_len; } list_add_tail(&task->session_list, &session->cmd_list); out: dprintf("task:%p tag:0x%04"PRIx64 " scsi_op:0x%x %s%s in_len:%d out_len:%d " "imm_sz:%d unsol_sz:%d cmdsn:0x%x expcmdsn:0x%x\n", task, task->tag, req_bhs->cdb[0], task->is_read ? "rd" : "", task->is_write ? "wr" : "", task->in_len, task->out_len, imm_data_sz, task->unsol_sz, task->cmd_sn, session->exp_cmd_sn); return err; } static int iser_data_out_rx(struct iser_task *dout_task) { struct iser_conn *conn = dout_task->conn; struct iscsi_session *session = conn->h.session; struct iscsi_data *req_bhs = (struct iscsi_data *) dout_task->pdu.bhs; struct iser_task *task; int err = 0; list_for_each_entry(task, &session->cmd_list, session_list) { if (task->tag == req_bhs->itt) goto found; } return -EINVAL; found: iser_task_add_out_pdu_buf(task, &dout_task->pdu.membuf, be32_to_cpu(req_bhs->offset)); list_add_tail(&dout_task->dout_task_list, &task->dout_task_list); /* ToDo: BUG!!! add an indication that it's data-out task so that it can be released when the buffer is released */ task->unsol_remains -= ntoh24(req_bhs->dlength); dprintf("task:%p tag:0x%04"PRIx64 ", dout taskptr:%p out_len:%d " "uns_rem:%d rdma_rd_rem:%d expcmdsn:0x%x\n", task, task->tag, dout_task, task->out_len, task->unsol_remains, task->rdma_rd_remains, session->exp_cmd_sn); /* ToDo: look at the task counters !!! */ if (req_bhs->ttt == cpu_to_be32(ISCSI_RESERVED_TAG)) { if (req_bhs->flags & ISCSI_FLAG_CMD_FINAL) { if (!task_pending(task)) { /* ToDo: this condition is weird ... */ if (task->rdma_rd_remains == 0 && task->unsol_remains == 0) schedule_task_iosubmit(task, conn); } } } else { if (!(req_bhs->flags & ISCSI_FLAG_CMD_FINAL)) return err; if (task->rdma_rd_remains == 0 && task->unsol_remains == 0) schedule_task_iosubmit(task, conn); } return err; } /* usually rx_task is passed directly from the rx handler, but due to the connection establishment event-data race the first login task may be saved and injected afterwards. */ static void iser_login_rx(struct iser_task *rx_task) { struct iser_conn *conn = rx_task->conn; struct iser_task *tx_task = conn->login_tx_task; struct iscsi_login_rsp *rsp_bhs = (struct iscsi_login_rsp *)tx_task->pdu.bhs; if (conn->h.state == STATE_START) { eprintf("conn:%p, not established yet, delaying login_rx\n", &conn->h); conn->h.state = STATE_READY; conn->login_rx_task = rx_task; return; } iser_login_exec(&conn->h, &rx_task->pdu, &tx_task->pdu); if (rsp_bhs->status_class) { eprintf("conn:%p, login failed, class:%0x detail:%0x\n", &conn->h, rsp_bhs->status_class, rsp_bhs->status_detail); } if (conn->login_phase == LOGIN_PHASE_LAST_SEND) dprintf("transitioning to full-feature, no repost\n"); else iser_post_recv(conn, rx_task, 1); schedule_resp_tx(tx_task, conn); } static int iser_nop_out_rx(struct iser_task *task) { struct iser_conn *conn = task->conn; struct iscsi_nopout *req_bhs = (struct iscsi_nopout *) task->pdu.bhs; int err = 0; if (req_bhs->ttt != cpu_to_be32(ISCSI_RESERVED_TAG)) { /* * When sending NOP-In, we don't request a NOP-Out . * by sending a valid Target Tranfer Tag. * See iser_send_ping_nop_in() and 10.18.2 in the draft 20. */ eprintf("conn:%p task:%p initiator bug, ttt not reserved\n", &conn->h, task); err = -ISCSI_REASON_PROTOCOL_ERROR; goto reject; } if (req_bhs->itt == cpu_to_be32(ISCSI_RESERVED_TAG)) { if (req_bhs->opcode & ISCSI_OP_IMMEDIATE) { dprintf("No response to Nop-Out\n"); iser_post_recv(conn, task, 1); return -EAGAIN; /* ToDo: fix the ret codes */ } else { eprintf("conn:%p task:%p initiator bug, " "itt reserved with no imm. flag\n", &conn->h, task); err = -ISCSI_REASON_PROTOCOL_ERROR; goto reject; } } task->out_len = ntoh24(req_bhs->dlength); dprintf("conn:%p nop-out task:%p cmdsn:0x%x data_sz:%d\n", &conn->h, task, task->cmd_sn, task->out_len); return 0; reject: /* ToDo: prepare and send reject pdu */ /* iser_send_reject(task, ISCSI_REASON_INVALID_PDU_FIELD); */ return err; } static int iser_task_delivery(struct iser_task *task) { struct iser_conn *conn = task->conn; int err; switch (task->opcode) { case ISCSI_OP_SCSI_CMD: err = iser_task_alloc_rdma_bufs(task); if (unlikely(err == -ENOMEM)) list_add_tail(&task->exec_list, &task->conn->buf_alloc_list); break; case ISCSI_OP_NOOP_OUT: err = iser_nop_out_exec(task); break; case ISCSI_OP_LOGOUT: err = iser_logout_exec(task); break; case ISCSI_OP_SCSI_TMFUNC: err = iser_tm_exec(task); break; case ISCSI_OP_TEXT: err = iser_text_exec(&conn->h, &task->pdu, &conn->text_tx_task->pdu); schedule_resp_tx(conn->text_tx_task, conn); break; default: eprintf("Internal error: Unexpected op:0x%x\n", task->opcode); err = -EINVAL; break; } return err; } static inline int cmdsn_cmp(uint32_t sn1, uint32_t sn2) { if (sn1 == sn2) return 0; return ((int32_t)sn1 - (int32_t)sn2) > 0 ? 1 : -1; } /* queues the task according to cmd-sn, no exec here */ static int iser_queue_task(struct iscsi_session *session, struct iser_task *task) { uint32_t cmd_sn = task->cmd_sn; struct list_head *cmp_entry; int err; if (unlikely(task->is_immediate)) { dprintf("exec imm task task:%p tag:0x%0"PRIx64 " cmd_sn:0x%x\n", task, task->tag, cmd_sn); err = iser_task_delivery(task); if (likely(!err || err == -ENOMEM)) return 0; else return err; } /* if the current command is the expected one, exec it and all others possibly acumulated on the queue */ while (session->exp_cmd_sn == cmd_sn) { session->exp_cmd_sn++; dprintf("exec task:%p cmd_sn:0x%x\n", task, cmd_sn); err = iser_task_delivery(task); if (unlikely(err && err != -ENOMEM)) { /* when no free buffers remains, the task will wait on queue, so it is not a real error, but we should not attempt to start other tasks until more memory becomes available */ return err; /* ToDo: what if there are more tasks in case of error */ } if (list_empty(&session->pending_cmd_list)) return 0; task = list_first_entry(&session->pending_cmd_list, struct iser_task, exec_list); list_del(&task->exec_list); clear_task_pending(task); cmd_sn = be32_to_cpu(task->pdu.bhs->statsn); } /* cmd_sn > (exp_cmd_sn+max_queue_cmd), i.e. beyond allowed window */ if (cmdsn_cmp(cmd_sn, session->exp_cmd_sn+session->max_queue_cmd) == 1) { eprintf("unexpected cmd_sn:0x%x, max:0x%x\n", cmd_sn, session->exp_cmd_sn+session->max_queue_cmd); return -EINVAL; } /* insert the current task, ordered by cmd_sn */ list_for_each_prev(cmp_entry, &session->pending_cmd_list) { struct iser_task *cmp_task; uint32_t cmp_cmd_sn; int cmp_res; cmp_task = list_entry(cmp_entry, struct iser_task, exec_list); cmp_cmd_sn = cmp_task->cmd_sn; cmp_res = cmdsn_cmp(cmd_sn, cmp_cmd_sn); if (cmp_res == 1) { /* cmd_sn > cmp_cmd_sn */ dprintf("inserted cmdsn:0x%x after cmdsn:0x%x\n", cmd_sn, cmp_cmd_sn); break; } else if (cmp_res == -1) { /* cmd_sn < cmp_cmd_sn */ dprintf("inserting cmdsn:0x%x skip cmdsn:0x%x\n", cmd_sn, cmp_cmd_sn); continue; } else { /* cmd_sn == cmp_cmd_sn */ eprintf("duplicate cmd_sn:0x%x, exp:%u\n", cmd_sn, session->exp_cmd_sn); return -EINVAL; } } list_add(&task->exec_list, cmp_entry); set_task_pending(task); return 0; } static int iser_parse_req_headers(struct iser_task *task) { struct iser_conn *conn = task->conn; struct iser_hdr *iser_hdr = task->pdu.iser_hdr; struct iscsi_hdr *iscsi_hdr = task->pdu.bhs; unsigned pdu_dlength = ntoh24(iscsi_hdr->dlength); unsigned pdu_len = pdu_dlength + sizeof(struct iscsi_hdr); int err = -1; switch (iser_hdr->flags & 0xF0) { case ISCSI_CTRL: if (iser_hdr->flags & ISER_RSV) { task->rem_read_stag = be32_to_cpu(iser_hdr->read_stag); task->rem_read_va = be64_to_cpu(iser_hdr->read_va); dprintf("task:%p rstag:0x%x va:0x%"PRIx64 "\n", task, task->rem_read_stag, task->rem_read_va); } if (iser_hdr->flags & ISER_WSV) { task->rem_write_stag = be32_to_cpu(iser_hdr->write_stag); task->rem_write_va = be64_to_cpu(iser_hdr->write_va); dprintf("task:%p wstag:0x%x va:0x%"PRIx64 "\n", task, task->rem_write_stag, task->rem_write_va); } err = 0; break; case ISER_HELLO: dprintf("iSER Hello message??\n"); break; default: eprintf("malformed iser iser_hdr, flags 0x%02x\n", iser_hdr->flags); break; } task->opcode = iscsi_hdr->opcode & ISCSI_OPCODE_MASK; task->is_immediate = iscsi_hdr->opcode & ISCSI_OP_IMMEDIATE ? 1 : 0; task->is_read = 0; /* valid for cmds only */ task->is_write = 0; /* valid for cmds only */ task->pdu.ahssize = iscsi_hdr->hlength * 4; task->pdu.membuf.addr += task->pdu.ahssize; pdu_len += task->pdu.ahssize; task->pdu.membuf.size = pdu_dlength; task->pdu.membuf.rdma = 0; task->tag = iscsi_hdr->itt; task->cmd_sn = be32_to_cpu(iscsi_hdr->statsn); conn->h.exp_stat_sn = be32_to_cpu(iscsi_hdr->exp_statsn); iscsi_update_conn_stats_rx(&conn->h, pdu_len, task->opcode); return err; } static int iser_rx_handler_non_ff(struct iser_task *task) { struct iser_conn *conn = task->conn; int err = 0; switch (conn->h.state) { case STATE_START: case STATE_READY: case STATE_SECURITY: case STATE_SECURITY_AUTH: case STATE_SECURITY_DONE: case STATE_SECURITY_LOGIN: case STATE_SECURITY_FULL: case STATE_LOGIN: case STATE_LOGIN_FULL: if (task->opcode == ISCSI_OP_LOGIN) { dprintf("login rx, conn:%p\n", conn); iser_login_rx(task); } else { eprintf("non-login pdu during login phase, " "conn:%p opcode:0x%0x\n", conn, task->opcode); err = -EINVAL; } break; case STATE_EXIT: case STATE_CLOSE: dprintf("ignored rx, while conn:%p closing\n", conn); break; default: dprintf("ignored rx, conn:%p unexpected state:%d\n", conn, conn->h.state); break; } return err; } static void iser_rx_handler(struct iser_work_req *rxd) { struct iser_task *task = rxd->task; struct iser_conn *conn = task->conn; int queue_task = 1; int err = 0; iser_conn_put(conn); err = iser_parse_req_headers(task); if (unlikely(err)) goto out; if (unlikely(conn->h.state != STATE_FULL)) { err = iser_rx_handler_non_ff(task); goto out; } INIT_LIST_HEAD(&task->in_buf_list); task->in_buf_num = 0; INIT_LIST_HEAD(&task->out_buf_list); task->out_buf_num = 0; if (likely(task->opcode == ISCSI_OP_SCSI_CMD)) err = iser_scsi_cmd_rx(task); else { switch (task->opcode) { case ISCSI_OP_SCSI_DATA_OUT: err = iser_data_out_rx(task); queue_task = 0; break; case ISCSI_OP_NOOP_OUT: err = iser_nop_out_rx(task); break; case ISCSI_OP_LOGOUT: dprintf("logout rx\n"); break; case ISCSI_OP_SCSI_TMFUNC: dprintf("tmfunc rx\n"); break; case ISCSI_OP_TEXT: dprintf("text rx\n"); err = iser_task_delivery(task); queue_task = 0; break; case ISCSI_OP_SNACK: eprintf("Cannot handle SNACK yet\n"); err = -EINVAL; break; default: eprintf("Unknown op 0x%x\n", task->opcode); err = -EINVAL; break; } } if (likely(!err && queue_task)) err = iser_queue_task(conn->h.session, task); out: if (unlikely(err)) { eprintf("conn:%p task:%p err:%d, closing\n", &conn->h, task, err); iser_conn_close(conn); } } static void iser_login_tx_complete(struct iser_conn *conn) { switch (conn->h.state) { case STATE_SECURITY_LOGIN: conn->h.state = STATE_LOGIN; break; case STATE_SECURITY_FULL: /* fall through */ case STATE_LOGIN_FULL: dprintf("conn:%p, last login send completed\n", conn); conn->h.state = STATE_FULL; iser_conn_login_phase_set(conn, LOGIN_PHASE_FF); iser_free_login_resources(conn); break; } } static void iser_tx_complete_handler(struct iser_work_req *txd) { struct iser_task *task = txd->task; struct iser_conn *conn = task->conn; int opcode = task->pdu.bhs->opcode & ISCSI_OPCODE_MASK; iscsi_update_conn_stats_tx(&conn->h, txd->sge.length, opcode); dprintf("conn:%p task:%p tag:0x%04"PRIx64 " opcode:0x%x\n", &conn->h, task, task->tag, opcode); iser_conn_put(conn); list_del(&task->tx_list); /* remove from conn->sent_list */ if (unlikely(task->unsolicited)) { conn->nop_in_task = task; return; } iser_complete_task(task); if (unlikely(conn->h.state != STATE_FULL)) iser_login_tx_complete(conn); else schedule_post_recv(task, conn); } static void iser_rdma_wr_complete_handler(struct iser_work_req *rdmad) { struct iser_task *task = rdmad->task; struct iser_conn *conn = task->conn; iscsi_update_conn_stats_tx(&conn->h, rdmad->sge.length, ISCSI_OP_SCSI_DATA_IN); dprintf("conn:%p task:%p tag:0x%04"PRIx64 "\n", &conn->h, task, task->tag); iser_conn_put(conn); /* no need to remove from conn->sent_list, it is done in iser_tx_complete_handler(), as rdma-wr is followed by tx */ } static void iser_rdma_rd_complete_handler(struct iser_work_req *rdmad) { struct iser_task *task = rdmad->task; struct iser_conn *conn = task->conn; iscsi_update_conn_stats_rx(&conn->h, rdmad->sge.length, ISCSI_OP_SCSI_DATA_OUT); task->rdma_rd_remains -= rdmad->sge.length; dprintf("conn:%p task:%p tag:0x%04"PRIx64 ", rems rdma:%d unsol:%d\n", &conn->h, task, task->tag, task->rdma_rd_remains, task->unsol_remains); iser_conn_put(conn); /* no need to remove from a list, was removed before rdma-rd */ if (unlikely(conn->h.state != STATE_FULL)) return; if (task->rdma_rd_remains == 0 && task->unsol_remains == 0) schedule_task_iosubmit(task, conn); } /* * Deal with just one work completion. */ static void handle_wc(struct ibv_wc *wc) { struct iser_work_req *req = ptr_from_int64(wc->wr_id); struct iser_task *task; struct iser_conn *conn; dprintf("%s complete, wr_id:%p len:%d\n", iser_ib_op_to_str(req->iser_ib_op), req, wc->byte_len); switch (req->iser_ib_op) { case ISER_IB_RECV: iser_rx_handler(req); break; case ISER_IB_SEND: iser_tx_complete_handler(req); break; case ISER_IB_RDMA_WRITE: iser_rdma_wr_complete_handler(req); break; case ISER_IB_RDMA_READ: iser_rdma_rd_complete_handler(req); break; default: task = req->task; conn = (task ? task->conn : NULL); eprintf("unexpected req op:%d, wc op%d, wc::%p " "wr_id:%p task:%p conn:%p\n", req->iser_ib_op, wc->opcode, wc, req, task, conn); if (conn) iser_conn_close(conn); break; } } static void handle_wc_error(struct ibv_wc *wc) { struct iser_work_req *req = ptr_from_int64(wc->wr_id); struct iser_task *task = req->task; struct iser_conn *conn = task ? task->conn : NULL; if (wc->status != IBV_WC_WR_FLUSH_ERR) eprintf("conn:%p task:%p tag:0x%04"PRIx64 " wr_id:0x%p op:%s " "err:%s vendor_err:0x%0x\n", &conn->h, task, task->tag, req, iser_ib_op_to_str(req->iser_ib_op), ibv_wc_status_str(wc->status), wc->vendor_err); else dprintf("conn:%p task:%p tag:0x%04"PRIx64 " wr_id:0x%p op:%s " "err:%s vendor_err:0x%0x\n", &conn->h, task, task->tag, req, iser_ib_op_to_str(req->iser_ib_op), ibv_wc_status_str(wc->status), wc->vendor_err); switch (req->iser_ib_op) { case ISER_IB_SEND: /* in both read and write tasks SEND is last, the task should be completed now */ case ISER_IB_RDMA_READ: /* RDMA-RD is sent separately, and Response is to be SENT after its completion, so if RDMA-RD fails, task to be completed now */ iser_complete_task(task); break; case ISER_IB_RECV: /* this should be the Flush, no task has been created yet */ case ISER_IB_RDMA_WRITE: /* RDMA-WR and SEND response of a READ task are sent together, so when receiving RDMA-WR error, wait until SEND error arrives to complete the task */ break; default: eprintf("unexpected opcode %d, " "wc:%p wr_id:%p task:%p conn:%p\n", wc->opcode, wc, req, task, conn); break; } if (conn) { iser_conn_put(conn); iser_conn_close(conn); } } /* * Could read as many entries as possible without blocking, but * that just fills up a list of tasks. Instead pop out of here * so that tx progress, like issuing rdma reads and writes, can * happen periodically. */ static int iser_poll_cq(struct iser_device *dev, int max_wc) { int err = 0, numwc = 0; struct ibv_wc wc; for (;;) { err = ibv_poll_cq(dev->cq, 1, &wc); if (err == 0) /* no completions retrieved */ break; if (unlikely(err < 0)) { eprintf("ibv_poll_cq failed\n"); break; } VALGRIND_MAKE_MEM_DEFINED(&wc, sizeof(wc)); if (likely(wc.status == IBV_WC_SUCCESS)) handle_wc(&wc); else handle_wc_error(&wc); if (++numwc == max_wc) { err = 1; break; } } return err; } static int num_delayed_arm; #define MAX_NUM_DELAYED_ARM 16 static void iser_rearm_completions(struct iser_device *dev) { int err; err = ibv_req_notify_cq(dev->cq, 0); if (unlikely(err)) eprintf("ibv_req_notify_cq failed\n"); dev->poll_sched.sched_handler = iser_sched_consume_cq; tgt_add_sched_event(&dev->poll_sched); num_delayed_arm = 0; } static void iser_poll_cq_armable(struct iser_device *dev) { int err; err = iser_poll_cq(dev, MAX_POLL_WC); if (unlikely(err < 0)) { iser_rearm_completions(dev); return; } if (err == 0 && (++num_delayed_arm == MAX_NUM_DELAYED_ARM)) /* no more completions on cq, give up and arm the interrupts */ iser_rearm_completions(dev); else { dev->poll_sched.sched_handler = iser_sched_poll_cq; tgt_add_sched_event(&dev->poll_sched); } } /* iser_sched_consume_cq() is scheduled to consume completion events that could arrive after the cq had been seen empty, but just before the interrupts were re-armed. Intended to consume those remaining completions only, the function does not re-arm interrupts, but polls the cq until it's empty. As we always limit the number of completions polled at a time, we may need to schedule this functions few times. It may happen that during this process new completions occur, and we get an interrupt about that. Some of the "new" completions may be processed by the self-scheduling iser_sched_consume_cq(), which is a good thing, because we don't need to wait for the interrupt event. When the interrupt notification arrives, its handler will remove the scheduled event, and call iser_poll_cq_armable(), so that the polling cycle resumes normally. */ static void iser_sched_consume_cq(struct event_data *tev) { struct iser_device *dev = tev->data; int err; err = iser_poll_cq(dev, MAX_POLL_WC); if (err > 0) { dev->poll_sched.sched_handler = iser_sched_consume_cq; tgt_add_sched_event(&dev->poll_sched); } } /* Scheduled to poll cq after a completion event has been received and acknowledged, if no more completions are found the interrupts are re-armed */ static void iser_sched_poll_cq(struct event_data *tev) { struct iser_device *dev = tev->data; iser_poll_cq_armable(dev); } /* * Called from main event loop when a CQ notification is available. */ static void iser_handle_cq_event(int fd __attribute__ ((unused)), int events __attribute__ ((unused)), void *data) { struct iser_device *dev = data; void *cq_context; int err; err = ibv_get_cq_event(dev->cq_channel, &dev->cq, &cq_context); if (unlikely(err != 0)) { /* Just print the log message, if that was a serious problem, it will express itself elsewhere */ eprintf("failed to retrieve CQ event, cq:%p\n", dev->cq); return; } ibv_ack_cq_events(dev->cq, 1); /* if a poll was previosuly scheduled, remove it, as it will be scheduled when necessary */ if (dev->poll_sched.scheduled) tgt_remove_sched_event(&dev->poll_sched); iser_poll_cq_armable(dev); } /* * Called from main event loop when async event is available. */ static void iser_handle_async_event(int fd __attribute__ ((unused)), int events __attribute__ ((unused)), void *data) { struct iser_device *dev = data; char *dev_name = dev->ibv_ctxt->device->name; struct ibv_async_event async_event; struct iser_conn *conn; if (ibv_get_async_event(dev->ibv_ctxt, &async_event)) { eprintf("ibv_get_async_event failed\n"); return; } switch (async_event.event_type) { case IBV_EVENT_COMM_EST: conn = async_event.element.qp->qp_context; eprintf("conn:0x%p cm_id:0x%p dev:%s, QP evt: %s\n", &conn->h, conn->cm_id, dev_name, ibv_event_type_str(IBV_EVENT_COMM_EST)); /* force "connection established" event */ rdma_notify(conn->cm_id, IBV_EVENT_COMM_EST); break; /* rest of QP-related events */ case IBV_EVENT_QP_FATAL: case IBV_EVENT_QP_REQ_ERR: case IBV_EVENT_QP_ACCESS_ERR: case IBV_EVENT_SQ_DRAINED: case IBV_EVENT_PATH_MIG: case IBV_EVENT_PATH_MIG_ERR: case IBV_EVENT_QP_LAST_WQE_REACHED: conn = async_event.element.qp->qp_context; eprintf("conn:0x%p cm_id:0x%p dev:%s, QP evt: %s\n", &conn->h, conn->cm_id, dev_name, ibv_event_type_str(async_event.event_type)); break; /* CQ-related events */ case IBV_EVENT_CQ_ERR: eprintf("dev:%s CQ evt: %s\n", dev_name, ibv_event_type_str(async_event.event_type)); break; /* SRQ events */ case IBV_EVENT_SRQ_ERR: case IBV_EVENT_SRQ_LIMIT_REACHED: eprintf("dev:%s SRQ evt: %s\n", dev_name, ibv_event_type_str(async_event.event_type)); break; /* Port events */ case IBV_EVENT_PORT_ACTIVE: case IBV_EVENT_PORT_ERR: case IBV_EVENT_LID_CHANGE: case IBV_EVENT_PKEY_CHANGE: case IBV_EVENT_SM_CHANGE: case IBV_EVENT_CLIENT_REREGISTER: eprintf("dev:%s port:%d evt: %s\n", dev_name, async_event.element.port_num, ibv_event_type_str(async_event.event_type)); break; /* HCA events */ case IBV_EVENT_DEVICE_FATAL: eprintf("dev:%s HCA evt: %s\n", dev_name, ibv_event_type_str(async_event.event_type)); break; default: eprintf("dev:%s evt: %s\n", dev_name, ibv_event_type_str(async_event.event_type)); break; } ibv_ack_async_event(&async_event); } /* * First time a new connection is received on an RDMA device, record * it and build a PD and static memory. */ static int iser_device_init(struct iser_device *dev) { int cqe_num; int err = -1; dprintf("dev %p\n", dev); dev->pd = ibv_alloc_pd(dev->ibv_ctxt); if (dev->pd == NULL) { eprintf("ibv_alloc_pd failed\n"); goto out; } err = iser_init_rdma_buf_pool(dev); if (err) { eprintf("iser_init_rdma_buf_pool failed\n"); goto out; } err = ibv_query_device(dev->ibv_ctxt, &dev->device_attr); if (err < 0) { eprintf("ibv_query_device failed, %m\n"); goto out; } cqe_num = min(dev->device_attr.max_cqe, MAX_CQ_ENTRIES); dprintf("max %d CQEs\n", cqe_num); err = -1; dev->cq_channel = ibv_create_comp_channel(dev->ibv_ctxt); if (dev->cq_channel == NULL) { eprintf("ibv_create_comp_channel failed\n"); goto out; } /* verify cq_vector */ if (cq_vector < 0) cq_vector = control_port % dev->ibv_ctxt->num_comp_vectors; else if (cq_vector >= dev->ibv_ctxt->num_comp_vectors) { eprintf("Bad CQ vector. max: %d\n", dev->ibv_ctxt->num_comp_vectors); goto out; } dprintf("CQ vector: %d\n", cq_vector); dev->cq = ibv_create_cq(dev->ibv_ctxt, cqe_num, NULL, dev->cq_channel, cq_vector); if (dev->cq == NULL) { eprintf("ibv_create_cq failed\n"); goto out; } dprintf("dev->cq:%p\n", dev->cq); tgt_init_sched_event(&dev->poll_sched, iser_sched_poll_cq, dev); err = ibv_req_notify_cq(dev->cq, 0); if (err) { eprintf("ibv_req_notify failed, %m\n"); goto out; } err = tgt_event_add(dev->cq_channel->fd, EPOLLIN, iser_handle_cq_event, dev); if (err) { eprintf("tgt_event_add failed, %m\n"); goto out; } err = tgt_event_add(dev->ibv_ctxt->async_fd, EPOLLIN, iser_handle_async_event, dev); if (err) return err; list_add_tail(&dev->list, &iser_dev_list); out: return err; } static void iser_device_release(struct iser_device *dev) { int err; list_del(&dev->list); tgt_event_del(dev->ibv_ctxt->async_fd); tgt_event_del(dev->cq_channel->fd); tgt_remove_sched_event(&dev->poll_sched); err = ibv_destroy_cq(dev->cq); if (err) eprintf("ibv_destroy_cq failed: (errno=%d %m)\n", errno); err = ibv_destroy_comp_channel(dev->cq_channel); if (err) eprintf("ibv_destroy_comp_channel failed: (errno=%d %m)\n", errno); iser_destroy_rdma_buf_pool(dev); err = ibv_dealloc_pd(dev->pd); if (err) eprintf("ibv_dealloc_pd failed: (errno=%d %m)\n", errno); } static int iser_add_portal(struct addrinfo *res, short int port) { int err; int afonly = 1; struct sockaddr_storage sock_addr; struct rdma_cm_id *cma_listen_id; struct iser_portal *portal = NULL; portal = zalloc(sizeof(struct iser_portal)); if (!portal) { eprintf("zalloc failed.\n"); return -1; } portal->af = res->ai_family; portal->port = port; memset(&sock_addr, 0, sizeof(sock_addr)); if (res->ai_family == AF_INET6) { ((struct sockaddr_in6 *) &sock_addr)->sin6_family = AF_INET6; ((struct sockaddr_in6 *) &sock_addr)->sin6_port = htons(port); ((struct sockaddr_in6 *) &sock_addr)->sin6_addr = in6addr_any; } else { ((struct sockaddr_in *) &sock_addr)->sin_family = AF_INET; ((struct sockaddr_in *) &sock_addr)->sin_port = htons(port); ((struct sockaddr_in *) &sock_addr)->sin_addr.s_addr = INADDR_ANY; } err = rdma_create_id(rdma_evt_channel, &cma_listen_id, NULL, RDMA_PS_TCP); if (err) { eprintf("rdma_create_id failed, %m\n"); return -1; } portal->cma_listen_id = cma_listen_id; rdma_set_option(cma_listen_id, RDMA_OPTION_ID, RDMA_OPTION_ID_AFONLY, &afonly, sizeof(afonly)); err = rdma_bind_addr(cma_listen_id, (struct sockaddr *) &sock_addr); if (err) { if (err == -1) eprintf("rdma_bind_addr -1: %m\n"); else eprintf("rdma_bind_addr: %s\n", strerror(-err)); return -1; } /* 0 == maximum backlog */ err = rdma_listen(cma_listen_id, 0); if (err) { if (err == -1) eprintf("rdma_listen -1: %m\n"); else eprintf("rdma_listen: %s\n", strerror(-err)); return -1; } list_add(&portal->iser_portal_siblings, &iser_portals_list); return err; } /* * Init entire iscsi transport. Begin listening for connections. */ static int iser_ib_init(void) { int err; short int port = iser_listen_port; struct addrinfo hints, *res, *res0; char servname[64]; rdma_evt_channel = rdma_create_event_channel(); if (!rdma_evt_channel) { eprintf("Failed to initialize RDMA; load kernel modules?\n"); return -1; } memset(servname, 0, sizeof(servname)); snprintf(servname, sizeof(servname), "%d", port); memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; err = getaddrinfo(NULL, servname, &hints, &res0); if (err) { eprintf("unable to get address info, %m\n"); return -errno; } for (res = res0; res; res = res->ai_next) { err = iser_add_portal(res, port); if (err) { freeaddrinfo(res0); return err; } } freeaddrinfo(res0); dprintf("listening for iser connections on port %d\n", port); err = tgt_event_add(rdma_evt_channel->fd, EPOLLIN, iser_handle_rdmacm, NULL); if (err) return err; return err; } void iser_delete_portals(void) { int err; struct iser_portal *portal, *ptmp; list_for_each_entry_safe(portal, ptmp, &iser_portals_list, iser_portal_siblings) { err = rdma_destroy_id(portal->cma_listen_id); if (err) eprintf("rdma_destroy_id failed: (errno=%d %m)\n", errno); list_del(&portal->iser_portal_siblings); free(portal); } } static void iser_ib_release(void) { struct iser_device *dev, *tdev; assert(list_empty(&iser_conn_list)); list_for_each_entry_safe(dev, tdev, &iser_dev_list, list) { iser_device_release(dev); free(dev); } if (!list_empty(&iser_portals_list)) { tgt_event_del(rdma_evt_channel->fd); iser_delete_portals(); rdma_destroy_event_channel(rdma_evt_channel); } } static int iser_send_nop = 1; static struct tgt_work nop_work; #define ISER_TIMER_INT_SEC 5 static void iser_nop_work_handler(void *data) { struct iser_conn *conn; struct iser_task *task; list_for_each_entry(conn, &iser_conn_list, conn_list) { if (conn->h.state != STATE_FULL) continue; task = conn->nop_in_task; if (!task) continue; conn->nop_in_task = NULL; iser_send_ping_nop_in(task); } add_work(&nop_work, ISER_TIMER_INT_SEC); } static int iser_init(int index, char *args) { int err; err = iser_ib_init(); if (err) { iser_send_nop = 0; return err; } if (iser_send_nop) { nop_work.func = iser_nop_work_handler; nop_work.data = &nop_work; add_work(&nop_work, ISER_TIMER_INT_SEC); } return 0; } static void iser_exit(void) { if (iser_send_nop) del_work(&nop_work); iser_ib_release(); } static int iser_target_create(struct target *t) { struct iscsi_target *target; int err; err = iscsi_target_create(t); if (err) return err; target = target_find_by_id(t->tid); assert(target != NULL); target->rdma = 1; target->session_param[ISCSI_PARAM_INITIAL_R2T_EN].val = 1; target->session_param[ISCSI_PARAM_IMM_DATA_EN].val = 0; return 0; } static const char *lld_param_port = "port"; static const char *lld_param_nop = "nop"; static const char *lld_param_on = "on"; static const char *lld_param_off = "off"; static const char *lld_param_pool_sz_mb = "pool_sz_mb"; static const char *lld_param_buf_sz_kb = "buf_sz_kb"; static const char *lld_param_cq_vector = "cq_vector"; static int iser_param_parser(char *p) { int err = 0; char *q; if (!p) return 0; while (*p) { if (!strncmp(p, lld_param_port, strlen(lld_param_port))) { short int port_val; q = p + strlen(lld_param_port) + 1; port_val = strtol(q, NULL, 10); if (!errno) iser_listen_port = port_val; else { eprintf("invalid port supplied\n"); err = -EINVAL; break; } } else if (!strncmp(p, lld_param_nop, strlen(lld_param_nop))) { q = p + strlen(lld_param_nop) + 1; if (!strncmp(q, lld_param_on, strlen(lld_param_on))) iser_send_nop = 1; else if (!strncmp(q, lld_param_off, strlen(lld_param_off))) iser_send_nop = 0; else { eprintf("unsupported value for param:%s\n", lld_param_nop); err = -EINVAL; break; } } else if (!strncmp(p, lld_param_pool_sz_mb, strlen(lld_param_pool_sz_mb))) { q = p + strlen(lld_param_pool_sz_mb) + 1; buf_pool_sz_mb = atoi(q); if (buf_pool_sz_mb < 128) buf_pool_sz_mb = 128; } else if (!strncmp(p, lld_param_buf_sz_kb, strlen(lld_param_buf_sz_kb))) { q = p + strlen(lld_param_buf_sz_kb) + 1; membuf_size = atoi(q); if (membuf_size < 1) membuf_size = 1; membuf_size = membuf_size * 1024; } else if (!strncmp(p, lld_param_cq_vector, strlen(lld_param_cq_vector))) { q = p + strlen(lld_param_cq_vector) + 1; cq_vector = atoi(q); if (cq_vector < 0) { eprintf("unsupported value for param: %s\n", lld_param_cq_vector); err = -EINVAL; break; } } else { dprintf("unsupported param:%s\n", p); err = -EINVAL; break; } p += strcspn(p, ","); if (*p == ',') ++p; } return err; } static struct iscsi_transport iscsi_iser = { .name = "iser", .rdma = 1, .data_padding = 1, .ep_show = iser_show, .ep_force_close = iser_conn_force_close, .ep_getsockname = iser_getsockname, .ep_getpeername = iser_getpeername, }; static struct tgt_driver iser = { .name = "iser", .init = iser_init, .exit = iser_exit, .target_create = iser_target_create, .target_destroy = iscsi_target_destroy, .update = iscsi_target_update, .show = iscsi_target_show, .stat = iscsi_stat, .cmd_end_notify = iser_scsi_cmd_done, .mgmt_end_notify = iser_tm_done, .transportid = iscsi_transportid, .default_bst = "rdwr", }; __attribute__ ((constructor)) static void iser_driver_constructor(void) { register_driver(&iser); setup_param("iser", iser_param_parser); } tgt-1.0.85/usr/iscsi/iser.h000066400000000000000000000154661435417276200154700ustar00rootroot00000000000000/* * Copyright (C) 2007 Dennis Dalessandro (dennis@osc.edu) * Copyright (C) 2007 Ananth Devulapalli (ananth@osc.edu) * Copyright (C) 2007 Pete Wyckoff (pw@osc.edu) * Copyright (C) 2010 Voltaire, Inc. All rights reserved. * Copyright (C) 2010 Alexander Nezhinsky (alexandern@voltaire.com) * * 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, version 2 of the * License. * * 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 ISER_H #define ISER_H #include "iscsid.h" #include #include extern short control_port; struct iser_portal { struct list_head iser_portal_siblings; int port; int af; struct rdma_cm_id *cma_listen_id; }; /* * The IB-extended version from the kernel. Stags and VAs are in * big-endian format. */ struct iser_hdr { uint8_t flags; uint8_t rsvd[3]; uint32_t write_stag; /* write rkey */ uint64_t write_va; uint32_t read_stag; /* read rkey */ uint64_t read_va; } __attribute__((packed)); #define ISER_WSV (0x08) #define ISER_RSV (0x04) #define ISCSI_CTRL (0x10) #define ISER_HELLO (0x20) #define ISER_HELLORPLY (0x30) struct iser_conn; enum iser_ib_op_code { ISER_IB_RECV, ISER_IB_SEND, ISER_IB_RDMA_WRITE, ISER_IB_RDMA_READ, }; #define ISER_HDRS_SZ (sizeof(struct iser_hdr) + sizeof(struct iscsi_hdr)) /* * Work requests are either posted Receives for control messages, * or Send and RDMA ops (also considered "send" by IB) * They have different work request descriptors. * During a flush, we need to know the type of op and the * task to which it is related. */ struct iser_work_req { struct list_head wr_list; struct iser_task *task; enum iser_ib_op_code iser_ib_op; struct ibv_sge sge; union { struct ibv_recv_wr recv_wr; struct ibv_send_wr send_wr; }; }; /* * Pre-registered memory. Buffers are allocated by iscsi from us, handed * to device to fill, then iser can send them directly without registration. * Also for write path. */ struct iser_membuf { void *addr; unsigned size; unsigned offset; /* offset within task data */ struct list_head task_list; int rdma; struct list_head pool_list; }; struct iser_pdu { struct iser_hdr *iser_hdr; struct iscsi_hdr *bhs; unsigned int ahssize; void *ahs; /* pdu data only, original buffer is reflected in ibv_sge */ struct iser_membuf membuf; }; /* * Each SCSI command may have its own RDMA parameters. These appear on * the connection then later are assigned to the particular task to be * used when the target responds. */ struct iser_task { struct iser_conn *conn; struct iser_pdu pdu; struct iser_work_req rxd; struct iser_work_req txd; struct iser_work_req rdmad; int opcode; int is_immediate; int is_read; int is_write; int unsolicited; uint64_t tag; uint32_t cmd_sn; unsigned long flags; int in_len; int out_len; int unsol_sz; int unsol_remains; int rdma_rd_sz; int rdma_rd_remains; /* int rdma_rd_offset; // ToDo: multiple RDMA-Write buffers */ int rdma_wr_sz; int rdma_wr_remains; /* read and write from the initiator's point of view */ uint32_t rem_read_stag, rem_write_stag; uint64_t rem_read_va, rem_write_va; struct list_head in_buf_list; int in_buf_num; struct list_head out_buf_list; int out_buf_num; struct list_head exec_list; struct list_head rdma_list; struct list_head tx_list; struct list_head recv_list; /* linked to session->cmd_list */ struct list_head session_list; struct list_head dout_task_list; int result; struct scsi_cmd scmd; unsigned char *extdata; }; struct iser_device; enum iser_login_phase { LOGIN_PHASE_INIT, LOGIN_PHASE_START, /* keep 1 send spot and 1 recv posted */ LOGIN_PHASE_LAST_SEND, /* need 1 more send before ff */ LOGIN_PHASE_FF, /* full feature */ NUM_LOGIN_PHASE_VALS }; /* * Parallels iscsi_connection. Adds more fields for iser. */ struct iser_conn { struct iscsi_connection h; struct ibv_qp *qp_hndl; struct rdma_cm_id *cm_id; struct iser_device *dev; struct event_data sched_buf_alloc; struct list_head buf_alloc_list; struct event_data sched_rdma_rd; struct list_head rdma_rd_list; struct event_data sched_iosubmit; struct list_head iosubmit_list; struct event_data sched_tx; struct list_head resp_tx_list; struct list_head sent_list; struct event_data sched_post_recv; struct list_head post_recv_list; struct event_data sched_conn_free; struct sockaddr_storage peer_addr; /* initiator address */ char *peer_name; struct sockaddr_storage self_addr; /* target address */ char *self_name; unsigned int ssize, rsize, max_outst_pdu; /* FF resources */ int ff_res_alloc; void *task_pool; /* iser_task structures */ void *pdu_data_pool; /* memory for pdu, non-rdma send/recv */ struct ibv_mr *pdu_data_mr; /* memory registration for pdu data */ struct iser_task *nop_in_task; struct iser_task *text_tx_task; enum iser_login_phase login_phase; /* login phase resources, freed at full-feature */ int login_res_alloc; void *login_task_pool; void *login_data_pool; struct ibv_mr *login_data_mr; struct iser_task *login_rx_task; struct iser_task *login_tx_task; /* list of all iser conns */ struct list_head conn_list; }; static inline struct iser_conn *ISER_CONN(struct iscsi_connection *iscsi_conn) { return container_of(iscsi_conn, struct iser_conn, h); } /* * Shared variables for a particular device. The conn[] array will * have to be broken out when multiple device support is added, maybe with * a pointer into this "device" struct. */ struct iser_device { struct list_head list; struct ibv_context *ibv_ctxt; struct ibv_pd *pd; struct ibv_cq *cq; struct ibv_comp_channel *cq_channel; struct ibv_device_attr device_attr; /* membuf registered buffer, list area, handle */ void *membuf_regbuf; void *membuf_listbuf; struct ibv_mr *membuf_mr; int waiting_for_mem; /* shared memory identifier */ int rdma_hugetbl_shmid; struct event_data poll_sched; /* free and allocated membuf entries */ struct list_head membuf_free, membuf_alloc; }; void iser_login_exec(struct iscsi_connection *iscsi_conn, struct iser_pdu *rx_pdu, struct iser_pdu *tx_pdu); int iser_login_complete(struct iscsi_connection *iscsi_conn); int iser_text_exec(struct iscsi_connection *iscsi_conn, struct iser_pdu *rx_pdu, struct iser_pdu *tx_pdu); void iser_conn_close(struct iser_conn *conn); #endif /* ISER_H */ tgt-1.0.85/usr/iscsi/iser_text.c000066400000000000000000000613051435417276200165200ustar00rootroot00000000000000/* * iSCSI extensions for RDMA (iSER) * LOGIN and TEXT related code * * Copyright (C) 2007 Dennis Dalessandro (dennis@osc.edu) * Copyright (C) 2007 Ananth Devulapalli (ananth@osc.edu) * Copyright (C) 2007 Pete Wyckoff (pw@osc.edu) * Copyright (C) 2010 Voltaire, Inc. All rights reserved. * Copyright (C) 2010 Alexander Nezhinsky (alexandern@voltaire.com) * * 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, version 2 of the * License. * * 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 "util.h" #include "iscsid.h" #include "iser.h" #if defined(HAVE_VALGRIND) && !defined(NDEBUG) #include #else #define VALGRIND_MAKE_MEM_DEFINED(addr, len) #endif static struct iscsi_key login_keys[] = { {"InitiatorName",}, {"InitiatorAlias",}, {"SessionType",}, {"TargetName",}, {NULL, 0, 0, 0, NULL}, }; static char *iser_text_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; } static char *iser_text_key_find(char *data, int datasize, char *searchKey) { int keylen = strlen(searchKey); char *key, *value; 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 void iser_text_key_add(struct iscsi_connection *iscsi_conn, struct iser_pdu *pdu, char *key, char *value) { struct iser_conn *iser_conn = ISER_CONN(iscsi_conn); int keylen = strlen(key); int valuelen = strlen(value); int len = keylen + valuelen + 2; char *buffer; if (pdu->membuf.size + len > iser_conn->ssize) { log_warning("Dropping key: %s=%s, pdu_sz:%d, key_sz:%d, ssize:%d\n", key, value, pdu->membuf.size, len, iser_conn->ssize); return; } dprintf("%s=%s, offset:%d\n", key, value, pdu->membuf.size); buffer = pdu->membuf.addr + pdu->membuf.size; pdu->membuf.size += len; strcpy(buffer, key); buffer += keylen; *buffer++ = '='; strcpy(buffer, value); } static void iser_text_key_add_reject(struct iscsi_connection *iscsi_conn, struct iser_pdu *pdu, char *key) { iser_text_key_add(iscsi_conn, pdu, key, "Reject"); } static void iser_login_security_scan(struct iscsi_connection *iscsi_conn, struct iser_pdu *rx_pdu, struct iser_pdu *tx_pdu) { struct iscsi_login_rsp *rsp_bhs = (struct iscsi_login_rsp *)tx_pdu->bhs; char *key, *value, *data, *nextValue; int datasize; data = rx_pdu->membuf.addr; datasize = rx_pdu->membuf.size; while ((key = iser_text_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_available(iscsi_conn->tid, AUTH_DIR_INCOMING)) continue; iscsi_conn->auth_method = AUTH_NONE; iser_text_key_add(iscsi_conn, tx_pdu, key, "None"); break; } else if (!strcmp(value, "CHAP")) { if (!account_available(iscsi_conn->tid, AUTH_DIR_INCOMING)) continue; iscsi_conn->auth_method = AUTH_CHAP; iser_text_key_add(iscsi_conn, tx_pdu, key, "CHAP"); break; } } while ((value = nextValue)); if (iscsi_conn->auth_method == AUTH_UNKNOWN) iser_text_key_add_reject(iscsi_conn, tx_pdu, key); } else iser_text_key_add(iscsi_conn, tx_pdu, key, "NotUnderstood"); } if (iscsi_conn->auth_method == AUTH_UNKNOWN) { rsp_bhs->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp_bhs->status_detail = ISCSI_LOGIN_STATUS_AUTH_FAILED; iscsi_conn->state = STATE_EXIT; } } static void iser_login_security_done(struct iscsi_connection *iscsi_conn, struct iser_pdu *rx_pdu, struct iser_pdu *tx_pdu) { struct iscsi_login *req_bhs = (struct iscsi_login *)rx_pdu->bhs; struct iscsi_login_rsp *rsp_bhs = (struct iscsi_login_rsp *)tx_pdu->bhs; struct iscsi_session *session; if (!iscsi_conn->tid) return; session = session_find_name(iscsi_conn->tid, iscsi_conn->initiator, req_bhs->isid); if (session) { if (!req_bhs->tsih) { struct iscsi_connection *ent, *next; struct iser_conn *c; /* do session reinstatement */ list_for_each_entry_safe(ent, next, &session->conn_list, clist) { c = container_of(ent, struct iser_conn, h); iser_conn_close(c); } session = NULL; } else if (req_bhs->tsih != session->tsih) { /* fail the login */ rsp_bhs->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp_bhs->status_detail = ISCSI_LOGIN_STATUS_TGT_NOT_FOUND; iscsi_conn->state = STATE_EXIT; return; } else if (conn_find(session, iscsi_conn->cid)) { /* do connection reinstatement */ } /* add a new connection to the session */ if (session) conn_add_to_session(iscsi_conn, session); } else { if (req_bhs->tsih) { /* fail the login */ rsp_bhs->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp_bhs->status_detail = ISCSI_LOGIN_STATUS_NO_SESSION; iscsi_conn->state = STATE_EXIT; return; } /* * We do nothing here and instantiate a new session * later at login_finish(). */ } } static void iser_login_oper_scan(struct iscsi_connection *iscsi_conn, struct iser_pdu *rx_pdu, struct iser_pdu *tx_pdu) { struct iscsi_login_rsp *rsp_bhs = (struct iscsi_login_rsp *)tx_pdu->bhs; char *key, *value, *data; int datasize, idx, is_rdma = 0; data = rx_pdu->membuf.addr; datasize = rx_pdu->membuf.size; while ((key = iser_text_next_key(&data, &datasize, &value))) { dprintf("%s=%s\n", key, 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)) { int err; unsigned int val; char buf[32]; if (idx == ISCSI_PARAM_MAX_RECV_DLENGTH) idx = ISCSI_PARAM_MAX_XMIT_DLENGTH; if (idx == ISCSI_PARAM_RDMA_EXTENSIONS) is_rdma = 1; if (param_str_to_val(session_keys, idx, value, &val) < 0) { if (iscsi_conn->session_param[idx].state == KEY_STATE_START) { iser_text_key_add_reject(iscsi_conn, tx_pdu, key); continue; } else { rsp_bhs->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp_bhs->status_detail = ISCSI_LOGIN_STATUS_INIT_ERR; iscsi_conn->state = STATE_EXIT; goto out; } } err = param_check_val(session_keys, idx, &val); if (err) { iser_text_key_add_reject(iscsi_conn, tx_pdu, key); continue; } param_set_val(session_keys, iscsi_conn->session_param, idx, &val); switch (iscsi_conn->session_param[idx].state) { case KEY_STATE_START: if (idx >= ISCSI_PARAM_FIRST_LOCAL) break; memset(buf, 0, sizeof(buf)); param_val_to_str(session_keys, idx, val, buf); iser_text_key_add(iscsi_conn, tx_pdu, key, buf); break; case KEY_STATE_REQUEST: if (val != iscsi_conn->session_param[idx].val) { rsp_bhs->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp_bhs->status_detail = ISCSI_LOGIN_STATUS_INIT_ERR; iscsi_conn->state = STATE_EXIT; log_warning("%s %u %u\n", key, val, iscsi_conn->session_param[idx].val); goto out; } break; case KEY_STATE_DONE: break; } iscsi_conn->session_param[idx].state = KEY_STATE_DONE; } else iser_text_key_add(iscsi_conn, tx_pdu, key, "NotUnderstood"); } if (is_rdma) { /* do not try to do digests, not supported in iser */ iscsi_conn->session_param[ISCSI_PARAM_HDRDGST_EN].val = DIGEST_NONE; iscsi_conn->session_param[ISCSI_PARAM_DATADGST_EN].val = DIGEST_NONE; } else { /* do not offer RDMA, initiator must explicitly request */ iscsi_conn->session_param[ISCSI_PARAM_RDMA_EXTENSIONS].val = 0; } out: return; } static int iser_login_check_params(struct iscsi_connection *iscsi_conn, struct iser_pdu *pdu) { struct param *p = iscsi_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].val != session_keys[i].def) { if (iscsi_conn->state == STATE_LOGIN) { if (i >= ISCSI_PARAM_FIRST_LOCAL) { if (p[i].val > session_keys[i].max) p[i].val = session_keys[i].max; p[i].state = KEY_STATE_DONE; continue; } if (p[ISCSI_PARAM_RDMA_EXTENSIONS].val == 1) { if (i == ISCSI_PARAM_MAX_RECV_DLENGTH) continue; } else { if (i >= ISCSI_PARAM_RDMA_EXTENSIONS) continue; } memset(buf, 0, sizeof(buf)); param_val_to_str(session_keys, i, p[i].val, buf); iser_text_key_add(iscsi_conn, pdu, session_keys[i].name, buf); p[i].state = KEY_STATE_REQUEST; } cnt++; } } return cnt; } static int iser_login_auth_exec(struct iscsi_connection *iscsi_conn, struct iser_pdu *rx_pdu, struct iser_pdu *tx_pdu) { struct iscsi_connection fake_iscsi_conn; int res; switch (iscsi_conn->auth_method) { case AUTH_CHAP: /* ToDo: need to implement buffer-based chap functions */ memset(&fake_iscsi_conn, 0, sizeof(fake_iscsi_conn)); memcpy(&fake_iscsi_conn, iscsi_conn, sizeof(*iscsi_conn)); fake_iscsi_conn.req_buffer = rx_pdu->membuf.addr; fake_iscsi_conn.req.data = rx_pdu->membuf.addr; fake_iscsi_conn.req.datasize = rx_pdu->membuf.size; fake_iscsi_conn.rsp.datasize = 0; // here the new size will be returned fake_iscsi_conn.rsp.data = 0; fake_iscsi_conn.rsp_buffer = tx_pdu->membuf.addr; fake_iscsi_conn.rsp_buffer_size = 8192; // max buffer capacity res = cmnd_exec_auth_chap(&fake_iscsi_conn); if (!res) { memcpy(iscsi_conn, &fake_iscsi_conn, sizeof(*iscsi_conn)); tx_pdu->membuf.size = fake_iscsi_conn.rsp.datasize; } break; case AUTH_NONE: res = 0; break; default: eprintf("Unknown auth. method %d\n", iscsi_conn->auth_method); res = -3; } return res; } static void iser_login_start(struct iscsi_connection *iscsi_conn, struct iser_pdu *rx_pdu, struct iser_pdu *tx_pdu) { struct iscsi_login *req_bhs = (struct iscsi_login *)rx_pdu->bhs; struct iscsi_login_rsp *rsp_bhs = (struct iscsi_login_rsp *)tx_pdu->bhs; char *req_data = rx_pdu->membuf.addr; int req_datasize = rx_pdu->membuf.size; char *name, *alias, *session_type, *target_name; struct iscsi_target *target; char buf[NI_MAXHOST + NI_MAXSERV + 4]; int reason; iscsi_conn->cid = be16_to_cpu(req_bhs->cid); memcpy(iscsi_conn->isid, req_bhs->isid, sizeof(req_bhs->isid)); iscsi_conn->tsih = req_bhs->tsih; if (!sid64(iscsi_conn->isid, iscsi_conn->tsih)) { rsp_bhs->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp_bhs->status_detail = ISCSI_LOGIN_STATUS_MISSING_FIELDS; iscsi_conn->state = STATE_EXIT; return; } name = iser_text_key_find(req_data, req_datasize, "InitiatorName"); if (!name) { rsp_bhs->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp_bhs->status_detail = ISCSI_LOGIN_STATUS_MISSING_FIELDS; iscsi_conn->state = STATE_EXIT; return; } iscsi_conn->initiator = strdup(name); alias = iser_text_key_find(req_data, req_datasize, "InitiatorAlias"); if (alias) iscsi_conn->initiator_alias = strdup(alias); session_type = iser_text_key_find(req_data, req_datasize, "SessionType"); target_name = iser_text_key_find(req_data, req_datasize, "TargetName"); iscsi_conn->auth_method = -1; iscsi_conn->session_type = SESSION_NORMAL; if (session_type) { if (!strcmp(session_type, "Discovery")) iscsi_conn->session_type = SESSION_DISCOVERY; else if (strcmp(session_type, "Normal")) { rsp_bhs->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp_bhs->status_detail = ISCSI_LOGIN_STATUS_NO_SESSION_TYPE; iscsi_conn->state = STATE_EXIT; return; } } if (iscsi_conn->session_type == SESSION_NORMAL) { if (!target_name) { rsp_bhs->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp_bhs->status_detail = ISCSI_LOGIN_STATUS_MISSING_FIELDS; iscsi_conn->state = STATE_EXIT; return; } target = target_find_by_name(target_name); if (!target) { rsp_bhs->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp_bhs->status_detail = ISCSI_LOGIN_STATUS_TGT_NOT_FOUND; iscsi_conn->state = STATE_EXIT; return; } if (!target->rdma) { eprintf("Target %s is TCP, but conn cid:%d from %s is RDMA\n", target_name, iscsi_conn->cid, iscsi_conn->initiator); rsp_bhs->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp_bhs->status_detail = ISCSI_LOGIN_STATUS_TGT_NOT_FOUND; iscsi_conn->state = STATE_EXIT; return; } iscsi_conn->tid = target->tid; if (target_redirected(target, iscsi_conn, buf, &reason)) { iser_text_key_add(iscsi_conn, tx_pdu, "TargetAddress", buf); rsp_bhs->status_class = ISCSI_STATUS_CLS_REDIRECT; rsp_bhs->status_detail = reason; iscsi_conn->state = STATE_EXIT; return; } if (tgt_get_target_state(target->tid) != SCSI_TARGET_READY) { rsp_bhs->status_class = ISCSI_STATUS_CLS_TARGET_ERR; rsp_bhs->status_detail = ISCSI_LOGIN_STATUS_TARGET_ERROR; iscsi_conn->state = STATE_EXIT; return; } if (ip_acl(iscsi_conn->tid, iscsi_conn)) { rsp_bhs->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp_bhs->status_detail = ISCSI_LOGIN_STATUS_TGT_NOT_FOUND; iscsi_conn->state = STATE_EXIT; return; } if (iqn_acl(iscsi_conn->tid, iscsi_conn)) { rsp_bhs->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp_bhs->status_detail = ISCSI_LOGIN_STATUS_TGT_NOT_FOUND; iscsi_conn->state = STATE_EXIT; return; } if (isns_scn_access(iscsi_conn->tid, name)) { rsp_bhs->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp_bhs->status_detail = ISCSI_LOGIN_STATUS_TGT_NOT_FOUND; iscsi_conn->state = STATE_EXIT; return; } /* if (iscsi_conn->target->max_sessions && */ /* (++iscsi_conn->target->session_cnt > iscsi_conn->target->max_sessions)) { */ /* iscsi_conn->target->session_cnt--; */ /* rsp_bhs->status_class = ISCSI_STATUS_INITIATOR_ERR; */ /* rsp_bhs->status_detail = ISCSI_STATUS_TOO_MANY_CONN; */ /* iscsi_conn->state = STATE_EXIT; */ /* return; */ /* } */ memcpy(iscsi_conn->session_param, target->session_param, sizeof(iscsi_conn->session_param)); iscsi_conn->exp_cmd_sn = be32_to_cpu(req_bhs->cmdsn); iscsi_conn->max_cmd_sn = iscsi_conn->exp_cmd_sn; dprintf("set exp_cmdsn:0x%0x\n", iscsi_conn->exp_cmd_sn); } iser_text_key_add(iscsi_conn, tx_pdu, "TargetPortalGroupTag", "1"); } static void iser_login_finish(struct iscsi_connection *iscsi_conn, struct iser_pdu *tx_pdu) { struct iscsi_login_rsp *rsp_bhs = (struct iscsi_login_rsp *)tx_pdu->bhs; int err; uint8_t class, detail; switch (iscsi_conn->session_type) { case SESSION_NORMAL: /* * update based on negotiations (but ep_login_complete * could override) */ //iscsi_conn->data_inout_max_length = //iscsi_conn->session_param[ISCSI_PARAM_MAX_XMIT_DLENGTH].val; /* * Allocate transport resources for this connection. */ err = iser_login_complete(iscsi_conn); if (err) { class = ISCSI_STATUS_CLS_TARGET_ERR; detail = ISCSI_LOGIN_STATUS_NO_RESOURCES; goto fail; } if (!iscsi_conn->session) { err = session_create(iscsi_conn); if (err) { class = ISCSI_STATUS_CLS_TARGET_ERR; detail = ISCSI_LOGIN_STATUS_TARGET_ERROR; goto fail; } } else { /* if (iscsi_conn->rdma ^ iscsi_conn->session->rdma) { eprintf("new iscsi_conn rdma %d, but session %d\n", iscsi_conn->rdma, iscsi_conn->session->rdma); class = ISCSI_STATUS_CLS_INITIATOR_ERR; detail =ISCSI_LOGIN_STATUS_INVALID_REQUEST; goto fail; } */ } memcpy(iscsi_conn->isid, iscsi_conn->session->isid, sizeof(iscsi_conn->isid)); iscsi_conn->tsih = iscsi_conn->session->tsih; break; case SESSION_DISCOVERY: err = iser_login_complete(iscsi_conn); if (err) { class = ISCSI_STATUS_CLS_TARGET_ERR; detail = ISCSI_LOGIN_STATUS_NO_RESOURCES; goto fail; } /* set a dummy tsih value */ iscsi_conn->tsih = 1; break; } return; fail: rsp_bhs->flags = 0; rsp_bhs->status_class = class; rsp_bhs->status_detail = detail; iscsi_conn->state = STATE_EXIT; return; } void iser_login_exec(struct iscsi_connection *iscsi_conn, struct iser_pdu *rx_pdu, struct iser_pdu *tx_pdu) { struct iscsi_login *req_bhs = (struct iscsi_login *)rx_pdu->bhs; struct iscsi_login_rsp *rsp_bhs = (struct iscsi_login_rsp *)tx_pdu->bhs; int stay = 0, nsg_disagree = 0; tx_pdu->membuf.size = 0; memset(rsp_bhs, 0, BHS_SIZE); if ((req_bhs->opcode & ISCSI_OPCODE_MASK) != ISCSI_OP_LOGIN || !(req_bhs->opcode & ISCSI_OP_IMMEDIATE)) { /* reject */ } rsp_bhs->opcode = ISCSI_OP_LOGIN_RSP; rsp_bhs->max_version = ISCSI_DRAFT20_VERSION; rsp_bhs->active_version = ISCSI_DRAFT20_VERSION; rsp_bhs->itt = req_bhs->itt; if (/* req_bhs->max_version < ISCSI_VERSION || */ req_bhs->min_version > ISCSI_DRAFT20_VERSION) { rsp_bhs->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp_bhs->status_detail = ISCSI_LOGIN_STATUS_NO_VERSION; iscsi_conn->state = STATE_EXIT; return; } iscsi_conn->exp_cmd_sn = iscsi_conn->max_cmd_sn = ntohl(req_bhs->cmdsn); switch (ISCSI_LOGIN_CURRENT_STAGE(req_bhs->flags)) { case ISCSI_SECURITY_NEGOTIATION_STAGE: dprintf("conn:%p Login request (security negotiation): %d\n", iscsi_conn, iscsi_conn->state); rsp_bhs->flags = ISCSI_SECURITY_NEGOTIATION_STAGE << 2; switch (iscsi_conn->state) { case STATE_READY: iscsi_conn->state = STATE_SECURITY; iser_login_start(iscsi_conn, rx_pdu, tx_pdu); if (rsp_bhs->status_class) return; /* fall through */ case STATE_SECURITY: iser_login_security_scan(iscsi_conn, rx_pdu, tx_pdu); if (rsp_bhs->status_class) return; if (iscsi_conn->auth_method != AUTH_NONE) { iscsi_conn->state = STATE_SECURITY_AUTH; iscsi_conn->auth_state = AUTH_STATE_START; } break; case STATE_SECURITY_AUTH: switch (iser_login_auth_exec(iscsi_conn, rx_pdu, tx_pdu)) { case 0: break; default: case -1: goto init_err; case -2: goto auth_err; } break; default: goto init_err; } break; case ISCSI_OP_PARMS_NEGOTIATION_STAGE: dprintf("conn:%p Login request (operational negotiation): %d\n", iscsi_conn, iscsi_conn->state); rsp_bhs->flags = ISCSI_OP_PARMS_NEGOTIATION_STAGE << 2; switch (iscsi_conn->state) { case STATE_READY: iscsi_conn->state = STATE_LOGIN; iser_login_start(iscsi_conn, rx_pdu, tx_pdu); if (account_available(iscsi_conn->tid, AUTH_DIR_INCOMING)) goto auth_err; if (rsp_bhs->status_class) return; iser_login_oper_scan(iscsi_conn, rx_pdu, tx_pdu); if (rsp_bhs->status_class) return; stay = iser_login_check_params(iscsi_conn, tx_pdu); break; case STATE_LOGIN: iser_login_oper_scan(iscsi_conn, rx_pdu, tx_pdu); if (rsp_bhs->status_class) return; stay = iser_login_check_params(iscsi_conn, tx_pdu); break; default: goto init_err; } break; default: goto init_err; } if (rsp_bhs->status_class) return; if (iscsi_conn->state != STATE_SECURITY_AUTH && req_bhs->flags & ISCSI_FLAG_LOGIN_TRANSIT) { int nsg = ISCSI_LOGIN_NEXT_STAGE(req_bhs->flags); switch (nsg) { case ISCSI_OP_PARMS_NEGOTIATION_STAGE: switch (iscsi_conn->state) { case STATE_SECURITY: case STATE_SECURITY_DONE: iscsi_conn->state = STATE_SECURITY_LOGIN; iser_login_security_done(iscsi_conn, rx_pdu, tx_pdu); break; default: goto init_err; } break; case ISCSI_FULL_FEATURE_PHASE: switch (iscsi_conn->state) { case STATE_SECURITY: case STATE_SECURITY_DONE: if ((nsg_disagree = iser_login_check_params(iscsi_conn, tx_pdu))) { iscsi_conn->state = STATE_LOGIN; nsg = ISCSI_OP_PARMS_NEGOTIATION_STAGE; break; } iscsi_conn->state = STATE_SECURITY_FULL; iser_login_security_done(iscsi_conn, rx_pdu, tx_pdu); break; case STATE_LOGIN: if (stay) nsg = ISCSI_OP_PARMS_NEGOTIATION_STAGE; else iscsi_conn->state = STATE_LOGIN_FULL; break; default: goto init_err; } if (!stay && !nsg_disagree) { iser_login_finish(iscsi_conn, tx_pdu); if (rsp_bhs->status_class) return; } break; default: goto init_err; } rsp_bhs->flags |= nsg | (stay ? 0 : ISCSI_FLAG_LOGIN_TRANSIT); } if (iscsi_conn->exp_cmd_sn == ntohl(req_bhs->cmdsn)) iscsi_conn->exp_cmd_sn++; memcpy(rsp_bhs->isid, iscsi_conn->isid, sizeof(rsp_bhs->isid)); rsp_bhs->tsih = iscsi_conn->tsih; rsp_bhs->statsn = cpu_to_be32(iscsi_conn->stat_sn++); rsp_bhs->exp_cmdsn = cpu_to_be32(iscsi_conn->exp_cmd_sn); rsp_bhs->max_cmdsn = cpu_to_be32(iscsi_conn->max_cmd_sn); return; init_err: eprintf("conn:%p, Initiator error\n", iscsi_conn); rsp_bhs->flags = 0; rsp_bhs->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp_bhs->status_detail = ISCSI_LOGIN_STATUS_INIT_ERR; iscsi_conn->state = STATE_EXIT; return; auth_err: eprintf("conn:%p, Authentication error\n", iscsi_conn); rsp_bhs->flags = 0; rsp_bhs->status_class = ISCSI_STATUS_CLS_INITIATOR_ERR; rsp_bhs->status_detail = ISCSI_LOGIN_STATUS_AUTH_FAILED; iscsi_conn->state = STATE_EXIT; return; } void iser_target_list_build(struct iscsi_connection *conn, struct iser_pdu *tx_pdu, char *addr, char *name) { struct iscsi_target *target; list_for_each_entry(target, &iscsi_targets_list, tlist) { if (name && strcmp(tgt_targetname(target->tid), name)) continue; if (!target->rdma) continue; if (ip_acl(target->tid, conn)) continue; if (iqn_acl(target->tid, conn)) continue; if (isns_scn_access(target->tid, conn->initiator)) continue; iser_text_key_add(conn, tx_pdu, "TargetName", tgt_targetname(target->tid)); iser_text_key_add(conn, tx_pdu, "TargetAddress", addr); } } static void iser_text_scan(struct iscsi_connection *iscsi_conn, struct iser_pdu *rx_pdu, struct iser_pdu *tx_pdu) { char *key, *value, *data; int datasize; data = rx_pdu->membuf.addr; datasize = rx_pdu->membuf.size; while ((key = iser_text_next_key(&data, &datasize, &value))) { if (!strcmp(key, "SendTargets")) { struct sockaddr_storage ss; socklen_t slen, blen; char *p, buf[NI_MAXHOST + 128]; int port; if (value[0] == 0) continue; p = buf; blen = sizeof(buf); slen = sizeof(ss); iscsi_conn->tp->ep_getsockname(iscsi_conn, (struct sockaddr *) &ss, &slen); if (ss.ss_family == AF_INET6) { *p++ = '['; blen--; } slen = sizeof(ss); getnameinfo((struct sockaddr *) &ss, slen, p, blen, NULL, 0, NI_NUMERICHOST); p = buf + strlen(buf); if (ss.ss_family == AF_INET6) *p++ = ']'; if (ss.ss_family == AF_INET6) port = ntohs(((struct sockaddr_in6 *) &ss)->sin6_port); else port = ntohs(((struct sockaddr_in *) &ss)->sin_port); sprintf(p, ":%d,1", port); iser_target_list_build(iscsi_conn, tx_pdu, buf, strcmp(value, "All") ? value : NULL); } else iser_text_key_add(iscsi_conn, tx_pdu, key, "NotUnderstood"); } } int iser_text_exec(struct iscsi_connection *iscsi_conn, struct iser_pdu *rx_pdu, struct iser_pdu *tx_pdu) { struct iscsi_text *req = (struct iscsi_text *)rx_pdu->bhs; struct iscsi_text_rsp *rsp = (struct iscsi_text_rsp *)tx_pdu->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; iscsi_conn->exp_cmd_sn = be32_to_cpu(req->cmdsn); if (!(req->opcode & ISCSI_OP_IMMEDIATE)) iscsi_conn->exp_cmd_sn++; dprintf("Text request: %d\n", iscsi_conn->state); iser_text_scan(iscsi_conn, rx_pdu, tx_pdu); if (tx_pdu->membuf.size > MAX_KEY_VALUE_PAIRS) { eprintf("Text pdu size %d too big, can't send at once\n", tx_pdu->membuf.size); tx_pdu->membuf.size = 0; } if (req->flags & ISCSI_FLAG_CMD_FINAL) rsp->flags = ISCSI_FLAG_CMD_FINAL; rsp->statsn = cpu_to_be32(iscsi_conn->stat_sn++); rsp->exp_cmdsn = cpu_to_be32(iscsi_conn->exp_cmd_sn); rsp->max_cmdsn = cpu_to_be32(iscsi_conn->max_cmd_sn); return 0; } tgt-1.0.85/usr/iscsi/isns.c000066400000000000000000000663451435417276200154770ustar00rootroot00000000000000/* * 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 #include #include "iscsid.h" #include "parser.h" #include "tgtd.h" #include "util.h" #include "work.h" #include "list.h" #include "isns_proto.h" #include "tgtadm.h" #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) #define BUFSIZE (1 << 18) #define EID_NAME_KEY "eid" struct isns_io { char *buf; int offset; }; struct isns_qry_mgmt { char name[ISCSI_NAME_LEN]; uint16_t transaction; struct list_head qlist; }; struct isns_initiator { char name[ISCSI_NAME_LEN]; struct list_head ilist; }; struct tgt_work timeout_work; 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 char eid[ISCSI_NAME_LEN]; static struct sockaddr_storage ss; /* * Section 6.2.6 * The registration SHALL be removed from the iSNS database if an iSNS * Protocol message is not received from the iSNS client before the * registration period has expired. Receipt of any iSNS Protocol * message from the iSNS client automatically refreshes the Entity * Registration Period and Entity Registration Timestamp. To prevent a * registration from expiring, the iSNS client should send an iSNS * Protocol message to the iSNS server at intervals shorter than the * registration period. * * Implementor's Note: * We send a DevAttrQry message to the iSNS server every isns_timeout * seconds to keep our entries alive at the iSNS server. * To start with, we assume that the registration period is greater * than 30 seconds. After we register an entity we query for the * registration period and change the isns_timeout to be 10 seconds less * than the regiistration period. If the iSNS server doesn't respond * with a registration period we stick with 30 seconds. */ #define DEFAULT_ISNS_TIMEOUT 30 /* seconds */ static uint32_t isns_timeout = DEFAULT_ISNS_TIMEOUT; static char isns_addr[NI_MAXHOST]; static int isns_port = ISNS_PORT; static int num_targets = 0; int isns_scn_access(int tid, char *name) { struct isns_initiator *ini; struct iscsi_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_list, ilist) { if (!strcmp(ini->name, name)) return 0; } return -EPERM; } static int isns_get_ip(int fd) { int err; struct sockaddr_storage lss; socklen_t slen = sizeof(lss); err = getsockname(fd, (struct sockaddr *) &lss, &slen); if (err) { eprintf("getsockname error %s!\n", gai_strerror(err)); return err; } err = getnameinfo((struct sockaddr *) &lss, sizeof(lss), eid, sizeof(eid), NULL, 0, NI_NUMERICHOST); if (err) { eprintf("getnameinfo error %s!\n", gai_strerror(err)); return err; } return 0; } static void isns_handle(int fd, int events, void *data); static int isns_connect(void) { int fd, err; fd = socket(ss.ss_family, SOCK_STREAM, IPPROTO_TCP); if (fd < 0) { eprintf("unable to create (%s) %d!\n", strerror(errno), ss.ss_family); return -1; } err = connect(fd, (struct sockaddr *) &ss, sizeof(ss)); if (err < 0) { eprintf("unable to connect (%s) %d!\n", strerror(errno), ss.ss_family); close(fd); return -1; } if (!strlen(eid)) { err = isns_get_ip(fd); if (err) { close(fd); return -1; } } isns_fd = fd; tgt_event_add(fd, EPOLLIN, isns_handle, NULL); 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_tlv_set_string(struct isns_tlv **tlv, uint32_t tag, char *str) { return isns_tlv_set(tlv, tag, strlen(str) + 1, str); } 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_string(&tlv, ISNS_ATTR_ISCSI_NAME, name); length += isns_tlv_set_string(&tlv, ISNS_ATTR_ISCSI_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) eprintf("%d %m\n", length); 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) #endif static int isns_scn_register(char *name) { 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; if (list_empty(&iscsi_targets_list)) return 0; if (!isns_fd) if (isns_connect() < 0) return 0; memset(buf, 0, sizeof(buf)); tlv = (struct isns_tlv *) hdr->pdu; length += isns_tlv_set_string(&tlv, ISNS_ATTR_ISCSI_NAME, name); length += isns_tlv_set_string(&tlv, ISNS_ATTR_ISCSI_NAME, 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; set_scn_flag(scn_flags); scn_flags = htonl(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) eprintf("%d %m\n", length); return 0; } static int isns_eid_attr_query(void) { int err; uint16_t flags, length = 0; char buf[4096]; struct isns_hdr *hdr = (struct isns_hdr *) buf; struct isns_tlv *tlv; struct iscsi_target *target; struct isns_qry_mgmt *mgmt; char *name; eprintf("\n"); if (!isns_fd) if (isns_connect() < 0) return 0; mgmt = malloc(sizeof(*mgmt)); if (!mgmt) return 0; list_add(&mgmt->qlist, &qry_list); memset(buf, 0, sizeof(buf)); tlv = (struct isns_tlv *) hdr->pdu; strcpy(mgmt->name, EID_NAME_KEY); target = list_first_entry(&iscsi_targets_list, struct iscsi_target, tlist); name = tgt_targetname(target->tid); length += isns_tlv_set_string(&tlv, ISNS_ATTR_ISCSI_NAME, name); length += isns_tlv_set_string(&tlv, ISNS_ATTR_ENTITY_IDENTIFIER, eid); length += isns_tlv_set(&tlv, 0, 0, 0); length += isns_tlv_set(&tlv, ISNS_ATTR_REGISTRATION_PERIOD, 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) eprintf("%d %m\n", length); 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 iscsi_target *target; uint32_t node = htonl(ISNS_NODE_INITIATOR); struct isns_qry_mgmt *mgmt; if (list_empty(&iscsi_targets_list)) return 0; if (!isns_fd) if (isns_connect() < 0) return 0; mgmt = malloc(sizeof(*mgmt)); if (!mgmt) return 0; list_add(&mgmt->qlist, &qry_list); memset(buf, 0, sizeof(buf)); tlv = (struct isns_tlv *) hdr->pdu; if (name) snprintf(mgmt->name, sizeof(mgmt->name), "%s", name); else { mgmt->name[0] = '\0'; target = list_first_entry(&iscsi_targets_list, struct iscsi_target, tlist); name = tgt_targetname(target->tid); } length += isns_tlv_set_string(&tlv, ISNS_ATTR_ISCSI_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) eprintf("%d %m\n", length); return 0; } int isns_target_register(char *name) { unsigned char buf[4096]; uint16_t flags = 0, length = 0; struct isns_hdr *hdr = (struct isns_hdr *) buf; struct isns_tlv *tlv; struct iscsi_target *target; uint32_t node = htonl(ISNS_NODE_TARGET); uint32_t type = htonl(2); int err; struct iscsi_portal *portal; 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_first_entry(&iscsi_targets_list, struct iscsi_target, tlist); length += isns_tlv_set_string(&tlv, ISNS_ATTR_ISCSI_NAME, tgt_targetname(target->tid)); length += isns_tlv_set_string(&tlv, ISNS_ATTR_ENTITY_IDENTIFIER, eid); length += isns_tlv_set(&tlv, 0, 0, 0); length += isns_tlv_set_string(&tlv, ISNS_ATTR_ENTITY_IDENTIFIER, eid); if (!num_targets) { list_for_each_entry(portal, &iscsi_portals_list, iscsi_portal_siblings) { uint8_t t_ip[16]; uint32_t t_port = htonl(portal->port); memset(t_ip, 0, 16); /* * If listening on all ports, iSNS listings will * reflect local IP we connected to iSNS server with. */ if (portal->af == AF_INET) { uint32_t addr; if (!strcmp("0.0.0.0", portal->addr)) { if (ss.ss_family != AF_INET) continue; addr = ((struct sockaddr_in *) &ss)->sin_addr.s_addr; } else { inet_pton(AF_INET, portal->addr, &addr); } /* RFC 4171 6.3.1: convert v4 to mapped v6 */ t_ip[10] = t_ip[11] = 0xff; t_ip[15] = 0xff & (addr >> 24); t_ip[14] = 0xff & (addr >> 16); t_ip[13] = 0xff & (addr >> 8); t_ip[12] = 0xff & addr; } else { if (!strcmp("::", portal->addr)) { int i; if (ss.ss_family != AF_INET6) continue; for (i = 0; i < ARRAY_SIZE(t_ip); i++) t_ip[i] = ((struct sockaddr_in6 *) &ss)->sin6_addr.s6_addr[i]; } else { inet_pton(AF_INET6, portal->addr, t_ip); } } length += isns_tlv_set(&tlv, ISNS_ATTR_ENTITY_PROTOCOL, sizeof(type), &type); length += isns_tlv_set(&tlv, ISNS_ATTR_PORTAL_IP_ADDRESS, sizeof(t_ip), &t_ip); length += isns_tlv_set(&tlv, ISNS_ATTR_PORTAL_PORT, sizeof(t_port), &t_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); } add_work(&timeout_work, DEFAULT_ISNS_TIMEOUT); } length += isns_tlv_set_string(&tlv, ISNS_ATTR_ISCSI_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) eprintf("%d %m\n", length); if (scn_listen_port) isns_scn_register(name); if (!num_targets) isns_eid_attr_query(); isns_attr_query(name); num_targets++; return 0; } static void free_all_acl(struct iscsi_target *target) { struct isns_initiator *ini; while (!list_empty(&target->isns_list)) { ini = list_first_entry(&target->isns_list, typeof(*ini), ilist); list_del(&ini->ilist); free(ini); } } 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; struct iscsi_target *target; target = target_find_by_name(name); if (target) free_all_acl(target); if (!use_isns) return 0; if (!isns_fd) if (isns_connect() < 0) return 0; num_targets--; isns_scn_deregister(name); memset(buf, 0, sizeof(buf)); tlv = (struct isns_tlv *) hdr->pdu; length += isns_tlv_set_string(&tlv, ISNS_ATTR_ISCSI_NAME, name); length += isns_tlv_set(&tlv, 0, 0, 0); if (!num_targets) { del_work(&timeout_work); length += isns_tlv_set_string(&tlv, ISNS_ATTR_ENTITY_IDENTIFIER, eid); } else length += isns_tlv_set_string(&tlv, ISNS_ATTR_ISCSI_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) eprintf("%d %m\n", length); 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; eprintf("header read error %d %d %d %d\n", fd, err, errno, rx->offset); return -1; } else if (err == 0) return -1; dprintf("header %d %d bytes!\n", fd, err); rx->offset += err; if (rx->offset < sizeof(*hdr)) { dprintf("header wait %d %d\n", 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); \ dprintf("got a header %x %u %x %u %u\n", function, length, flags, \ transaction, 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); if (length + sizeof(*hdr) > BUFSIZE) { eprintf("FIXME we cannot handle this yet %u!\n", 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; eprintf("pdu read error %d %d %d %d\n", fd, err, errno, rx->offset); return -1; } else if (err == 0) return -1; dprintf("pdu %u %u\n", fd, err); rx->offset += err; if (rx->offset < length + sizeof(*hdr)) { eprintf("pdu wait %d %d\n", 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) \ eprintf("unknown function %x %u %x %u %u\n", \ 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; static char iscsi_name[224]; get_hdr_param(hdr, function, length, flags, transaction, sequence); while (length) { uint32_t vlen = ntohl(tlv->length); if (vlen + sizeof(*tlv) > length) vlen = length - sizeof(*tlv); switch (ntohl(tlv->tag)) { case ISNS_ATTR_ISCSI_NAME: eprintf("scn name: %u, %s\n", vlen, (char *) tlv->value); if (!name) { snprintf(iscsi_name, sizeof(iscsi_name), "%s", (char *)tlv->value); name = iscsi_name; } 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: eprintf("scn bitmap : %x\n", *((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 iscsi_target *target = NULL; struct isns_initiator *ini; char *name = NULL; int reg_period = 0; get_hdr_param(hdr, function, length, flags, transaction, sequence); list_for_each_entry_safe(mgmt, n, &qry_list, qlist) { if (mgmt->transaction == transaction) { list_del(&mgmt->qlist); goto found; } } eprintf("transaction not found %u\n", transaction); return; found: if (status) { eprintf("error response %u\n", status); goto free_qry_mgmt; } if (!strlen(mgmt->name)) { dprintf("skip %u\n", transaction); goto free_qry_mgmt; } if (strcmp(mgmt->name, EID_NAME_KEY)) { target = target_find_by_name(mgmt->name); if (!target) { eprintf("invalid tid %s\n", mgmt->name); goto free_qry_mgmt; } free_all_acl(target); } /* skip status */ tlv = (struct isns_tlv *) ((char *) hdr->pdu + 4); if (length < 4) goto free_qry_mgmt; length -= 4; while (length) { uint32_t vlen = ntohl(tlv->length); if (vlen + sizeof(*tlv) > length) vlen = length - sizeof(*tlv); 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) { eprintf("%s\n", (char *) name); ini = malloc(sizeof(*ini)); if (!ini) goto free_qry_mgmt; snprintf(ini->name, sizeof(ini->name), "%s", name); list_add(&ini->ilist, &target->isns_list); } else name = NULL; break; case ISNS_ATTR_REGISTRATION_PERIOD: reg_period = ntohl(*(tlv->value)); default: name = NULL; break; } length -= (sizeof(*tlv) + vlen); tlv = (struct isns_tlv *) ((char *) tlv->value + vlen); } /* * If the iSNS server provided a registration period, change * isns_timeout to be slighly less than the period, to make * sure our registrations are not kicked out */ if (reg_period) isns_timeout = reg_period - 10; free_qry_mgmt: free(mgmt); } static void isns_handle(int fd, int events, void *data) { int err; struct isns_io *rx = &isns_rx; struct isns_hdr *hdr = (struct isns_hdr *) rx->buf; uint16_t function, length, flags, transaction, sequence; char *name = NULL; err = recv_pdu(isns_fd, rx, hdr); if (err) { if (err == -EAGAIN) return; dprintf("close connection %d\n", isns_fd); tgt_event_del(isns_fd); close(isns_fd); isns_fd = 0; return; } get_hdr_param(hdr, function, length, flags, transaction, sequence); 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: case ISNS_FUNC_SCN_DEREG_RSP: break; case ISNS_FUNC_SCN: name = print_scn_pdu(hdr); if (name) { eprintf("%s\n", name); isns_attr_query(name); } break; default: print_unknown_pdu(hdr); } return; } 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_string(&tlv, ISNS_ATTR_ISCSI_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) eprintf("%d %m\n", length); } static void isns_scn_handle(int fd, int events, void *data) { 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; err = recv_pdu(scn_fd, rx, hdr); if (err) { if (err == -EAGAIN) return; dprintf("close connection %d\n", scn_fd); tgt_event_del(scn_fd); close(scn_fd); scn_fd = 0; return; } 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; } static void scn_accept_connection(int dummy, int events, void *data) { 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) { eprintf("accept error %m\n"); return; } eprintf("Accept scn connection %d\n", fd); err = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); if (err) eprintf("%m\n"); /* not critical, so ignore. */ scn_fd = fd; tgt_event_add(scn_fd, EPOLLIN, isns_scn_handle, NULL); return; } 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) { eprintf("%m\n"); return -errno; } opt = 1; if (ss.ss_family == AF_INET6) { err = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)); if (err) eprintf("%m\n"); goto out; } err = listen(fd, 5); if (err) { eprintf("%m\n"); goto out; } slen = sizeof(lss); err = getsockname(fd, (struct sockaddr *) &lss, &slen); if (err) { eprintf("%m\n"); 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); eprintf("scn listen port %u %d %d\n", scn_listen_port, fd, err); out: if (err) close(fd); else { scn_listen_fd = fd; tgt_event_add(fd, EPOLLIN, scn_accept_connection, NULL); } return err; } static void isns_timeout_fn(void *data) { struct tgt_work *w = data; isns_attr_query(NULL); add_work(w, isns_timeout); } int isns_init(void) { int err; char p[8]; struct addrinfo hints, *res; struct iscsi_target *target; snprintf(p, sizeof(p), "%d", isns_port); memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; err = getaddrinfo(isns_addr, (char *) &p, &hints, &res); if (err) { eprintf("getaddrinfo error %s\n", isns_addr); return -1; } memcpy(&ss, res->ai_addr, sizeof(*res->ai_addr)); freeaddrinfo(res); rxbuf = calloc(2, BUFSIZE); if (!rxbuf) { eprintf("oom\n"); return -1; } scn_init(isns_addr); isns_rx.buf = rxbuf; isns_rx.offset = 0; scn_rx.buf = rxbuf + BUFSIZE; scn_rx.offset = 0; use_isns = 1; if (!num_targets) list_for_each_entry(target, &iscsi_targets_list, tlist) isns_target_register(tgt_targetname(target->tid)); timeout_work.func = isns_timeout_fn; timeout_work.data = &timeout_work; return 0; } int isns_eid_deregister(void) { char buf[4096]; uint16_t flags, length = 0; struct isns_hdr *hdr = (struct isns_hdr *) buf; struct isns_tlv *tlv; struct iscsi_target *target; int err; if (!isns_fd) if (isns_connect() < 0) return 0; memset(buf, 0, sizeof(buf)); tlv = (struct isns_tlv *) hdr->pdu; target = list_first_entry(&iscsi_targets_list, struct iscsi_target, tlist); length += isns_tlv_set_string(&tlv, ISNS_ATTR_ISCSI_NAME, tgt_targetname(target->tid)); length += isns_tlv_set(&tlv, 0, 0, 0); length += isns_tlv_set_string(&tlv, ISNS_ATTR_ENTITY_IDENTIFIER, 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) eprintf("%d %m\n", length); return 0; } void isns_exit(void) { struct iscsi_target *target; if (!use_isns) return; if (num_targets) { del_work(&timeout_work); list_for_each_entry(target, &iscsi_targets_list, tlist) free_all_acl(target); isns_eid_deregister(); } if (isns_fd) { tgt_event_del(isns_fd); close(isns_fd); } if (scn_listen_fd) { tgt_event_del(scn_listen_fd); close(scn_listen_fd); } if (scn_fd) { tgt_event_del(scn_fd); close(scn_fd); } num_targets = use_isns = isns_fd = scn_listen_fd = scn_fd = 0; free(rxbuf); } tgtadm_err isns_show(struct concat_buf *b) { concat_printf(b, "iSNS:\n"); concat_printf(b, _TAB1 "iSNS=%s\n", use_isns ? "On" : "Off"); concat_printf(b, _TAB1 "iSNSServerIP=%s\n", isns_addr); concat_printf(b, _TAB1 "iSNSServerPort=%d\n", isns_port); concat_printf(b, _TAB1 "iSNSAccessControl=%s\n", use_isns_ac ? "On" : "Off"); return TGTADM_SUCCESS; } enum { Opt_isns, Opt_ip, Opt_port, Opt_ac, Opt_state, Opt_err, }; static match_table_t tokens = { {Opt_isns, "iSNS=%s"}, {Opt_ip, "iSNSServerIP=%s"}, {Opt_port, "iSNSServerPort=%d"}, {Opt_ac, "iSNSAccessControl=%s"}, {Opt_state, "State=%s"}, {Opt_err, NULL}, }; tgtadm_err isns_update(char *params) { tgtadm_err adm_err = TGTADM_SUCCESS; char *p; while ((p = strsep(¶ms, ",")) != NULL) { substring_t args[MAX_OPT_ARGS]; int token, isns; char tmp[16]; if (!*p) continue; token = match_token(p, tokens, args); switch (token) { case Opt_isns: match_strncpy(tmp, &args[0], sizeof(tmp)); if (!strcmp(tmp, "On")) isns = 1; else if (!strcmp(tmp, "Off")) isns = 0; else { adm_err = TGTADM_INVALID_REQUEST; break; } if (use_isns == isns) break; if (isns) isns_init(); else isns_exit(); break; case Opt_ip: match_strncpy(isns_addr, &args[0], sizeof(isns_addr)); break; case Opt_port: if (match_int(&args[0], &isns_port)) adm_err = TGTADM_INVALID_REQUEST; break; case Opt_ac: match_strncpy(tmp, &args[0], sizeof(tmp)); use_isns_ac = !strcmp(tmp, "On"); break; case Opt_state: match_strncpy(tmp, &args[0], sizeof(tmp)); adm_err = system_set_state(tmp); break; default: adm_err = TGTADM_INVALID_REQUEST; } } return adm_err; } tgt-1.0.85/usr/iscsi/isns_proto.h000066400000000000000000000155331435417276200167200ustar00rootroot00000000000000/* * 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 tgt-1.0.85/usr/iscsi/md5.c000066400000000000000000000163051435417276200151770ustar00rootroot00000000000000/* * This code implements the MD5 message-digest algorithm. * The algorithm is due to Ron Rivest. This code was * written by Colin Plumb in 1993, no copyright is claimed. * This code is in the public domain; do with it what you wish. * * Equivalent code is available from RSA Data Security, Inc. * This code has been tested against that, and is equivalent, * except that you don't need to include two pages of legalese * with every copy. * * To compute the message digest of a chunk of bytes, declare an * MD5Context structure, pass it to MD5Init, call MD5Update as * needed on buffers full of bytes, and then call MD5Final, which * will fill a supplied 16-byte array with the digest. * * Changed so as no longer to depend on Colin Plumb's `usual.h' header * definitions; now uses stuff from dpkg's config.h. * - Ian Jackson . * Still in the public domain. */ #include "md5.h" #ifdef WORDS_BIGENDIAN void byteSwap(UWORD32 *buf, unsigned words) { md5byte *p = (md5byte *)buf; do { *buf++ = (UWORD32)((unsigned)p[3] << 8 | p[2]) << 16 | ((unsigned)p[1] << 8 | p[0]); p += 4; } while (--words); } #else #define byteSwap(buf,words) #endif /* * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious * initialization constants. */ void MD5Init(struct MD5Context *ctx) { ctx->buf[0] = 0x67452301; ctx->buf[1] = 0xefcdab89; ctx->buf[2] = 0x98badcfe; ctx->buf[3] = 0x10325476; ctx->bytes[0] = 0; ctx->bytes[1] = 0; } /* * Update context to reflect the concatenation of another buffer full * of bytes. */ void MD5Update(struct MD5Context *ctx, md5byte const *buf, unsigned len) { UWORD32 t; /* Update byte count */ t = ctx->bytes[0]; if ((ctx->bytes[0] = t + len) < t) ctx->bytes[1]++; /* Carry from low to high */ t = 64 - (t & 0x3f); /* Space available in ctx->in (at least 1) */ if (t > len) { memcpy((md5byte *)ctx->in + 64 - t, buf, len); return; } /* First chunk is an odd size */ memcpy((md5byte *)ctx->in + 64 - t, buf, t); byteSwap(ctx->in, 16); MD5Transform(ctx->buf, ctx->in); buf += t; len -= t; /* Process data in 64-byte chunks */ while (len >= 64) { memcpy(ctx->in, buf, 64); byteSwap(ctx->in, 16); MD5Transform(ctx->buf, ctx->in); buf += 64; len -= 64; } /* Handle any remaining bytes of data. */ memcpy(ctx->in, buf, len); } /* * Final wrapup - pad to 64-byte boundary with the bit pattern * 1 0* (64-bit count of bits processed, MSB-first) */ void MD5Final(md5byte digest[16], struct MD5Context *ctx) { int count = ctx->bytes[0] & 0x3f; /* Number of bytes in ctx->in */ md5byte *p = (md5byte *)ctx->in + count; /* Set the first char of padding to 0x80. There is always room. */ *p++ = 0x80; /* Bytes of padding needed to make 56 bytes (-8..55) */ count = 56 - 1 - count; if (count < 0) { /* Padding forces an extra block */ memset(p, 0, count + 8); byteSwap(ctx->in, 16); MD5Transform(ctx->buf, ctx->in); p = (md5byte *)ctx->in; count = 56; } memset(p, 0, count); byteSwap(ctx->in, 14); /* Append length in bits and transform */ ctx->in[14] = ctx->bytes[0] << 3; ctx->in[15] = ctx->bytes[1] << 3 | ctx->bytes[0] >> 29; MD5Transform(ctx->buf, ctx->in); byteSwap(ctx->buf, 4); memcpy(digest, ctx->buf, 16); memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */ } #ifndef ASM_MD5 /* The four core functions - F1 is optimized somewhat */ /* #define F1(x, y, z) (x & y | ~x & z) */ #define F1(x, y, z) (z ^ (x & (y ^ z))) #define F2(x, y, z) F1(z, x, y) #define F3(x, y, z) (x ^ y ^ z) #define F4(x, y, z) (y ^ (x | ~z)) /* This is the central step in the MD5 algorithm. */ #define MD5STEP(f,w,x,y,z,in,s) \ (w += f(x,y,z) + in, w = (w<>(32-s)) + x) /* * The core of the MD5 algorithm, this alters an existing MD5 hash to * reflect the addition of 16 longwords of new data. MD5Update blocks * the data and converts bytes into longwords for this routine. */ void MD5Transform(UWORD32 buf[4], UWORD32 const in[16]) { register UWORD32 a, b, c, d; a = buf[0]; b = buf[1]; c = buf[2]; d = buf[3]; MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); buf[0] += a; buf[1] += b; buf[2] += c; buf[3] += d; } #endif tgt-1.0.85/usr/iscsi/md5.h000066400000000000000000000030461435417276200152020ustar00rootroot00000000000000/* * This is the header file for the MD5 message-digest algorithm. * The algorithm is due to Ron Rivest. This code was * written by Colin Plumb in 1993, no copyright is claimed. * This code is in the public domain; do with it what you wish. * * Equivalent code is available from RSA Data Security, Inc. * This code has been tested against that, and is equivalent, * except that you don't need to include two pages of legalese * with every copy. * * To compute the message digest of a chunk of bytes, declare an * MD5Context structure, pass it to MD5Init, call MD5Update as * needed on buffers full of bytes, and then call MD5Final, which * will fill a supplied 16-byte array with the digest. * * Changed so as no longer to depend on Colin Plumb's `usual.h' * header definitions; now uses stuff from dpkg's config.h * - Ian Jackson . * Still in the public domain. */ #ifndef MD5_H #define MD5_H #include #include #include #include #if (__BYTE_ORDER == __BIG_ENDIAN) # define WORDS_BIGENDIAN 1 #endif typedef uint32_t UWORD32; #ifdef __cplusplus extern "C" { #endif #define md5byte unsigned char struct MD5Context { UWORD32 buf[4]; UWORD32 bytes[2]; UWORD32 in[16]; }; void MD5Init(struct MD5Context *context); void MD5Update(struct MD5Context *context, md5byte const *buf, unsigned len); void MD5Final(unsigned char digest[16], struct MD5Context *context); void MD5Transform(UWORD32 buf[4], UWORD32 const in[16]); #ifdef __cplusplus } #endif #endif /* !MD5_H */ tgt-1.0.85/usr/iscsi/param.c000066400000000000000000000212571435417276200156140ustar00rootroot00000000000000/* * Copyright (C) 2005-2007 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, version 2 of the * License. * * 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 "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 param *params, struct iscsi_key *keys) { int i; for (i = 0; keys[i].name; i++) params[i].val = keys[i].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 void or_set_val(struct param *param, int idx, unsigned int *val) { *val |= param[idx].val; param[idx].val = *val; } static void and_set_val(struct param *param, int idx, unsigned int *val) { *val &= param[idx].val; param[idx].val = *val; } static int minimum_check_val(struct iscsi_key *key, unsigned int *val) { int err = 0; if (*val < key->min || key->max < *val) { *val = key->min; err = -EINVAL; } return err; } static int min_or_zero_check_val(struct iscsi_key *key, unsigned int *val) { int err = 0; if (*val != 0 && (*val < key->min || key->max < *val)) { *val = key->min; err = -EINVAL; } return err; } static int maximum_check_val(struct iscsi_key *key, unsigned int *val) { int err = 0; if (*val < key->min || key->max < *val) { *val = key->max; err = -EINVAL; } return err; } static void minimum_set_val(struct param *param, int idx, unsigned int *val) { if (*val > param[idx].val) *val = param[idx].val; else param[idx].val = *val; } static void min_or_zero_set_val(struct param *param, int idx, unsigned int *val) { if (*val > param[idx].val || *val == 0) *val = param[idx].val; else param[idx].val = *val; } static void maximum_set_val(struct param *param, int idx, unsigned int *val) { if (param[idx].val > *val) *val = param[idx].val; else param[idx].val = *val; } 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 void digest_set_val(struct param *param, int idx, unsigned int *val) { if (*val & DIGEST_CRC32C && param[idx].val & DIGEST_CRC32C) *val = DIGEST_CRC32C; else *val = DIGEST_NONE; param[idx].val = *val; } static int marker_val_to_str(unsigned int val, char *str) { if (val == 0) strcpy(str, "Irrelevant"); else strcpy(str, "Reject"); return 0; } static void marker_set_val(struct param *param, int idx, unsigned int *val) { if ((idx == ISCSI_PARAM_OFMARKER_EN && param[ISCSI_PARAM_OFMARKER_EN].state == KEY_STATE_DONE) || (idx == ISCSI_PARAM_IFMARKER_EN && param[ISCSI_PARAM_IFMARKER_EN].state == KEY_STATE_DONE)) *val = 0; else *val = 1; param[idx].val = *val; } 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; } void param_set_val(struct iscsi_key *keys, struct param *param, int idx, unsigned int *val2) { if (keys[idx].ops->set_val) keys[idx].ops->set_val(param, idx, val2); } static struct iscsi_key_ops minimum_ops = { .val_to_str = range_val_to_str, .str_to_val = range_str_to_val, .check_val = minimum_check_val, .set_val = minimum_set_val, }; static struct iscsi_key_ops min_or_zero_ops = { .val_to_str = range_val_to_str, .str_to_val = range_str_to_val, .check_val = min_or_zero_check_val, .set_val = min_or_zero_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 = maximum_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,MIN_NR_##x, MAX_NR_##x /* * The defaults here are according to the spec and must not be changed, * otherwise the initiator may make the wrong assumption. If you want * to change a value, edit the value in iscsi_target_create. * * The param MaxXmitDataSegmentLength doesn't really exist. It's a way * to remember the RDSL of the initiator, which defaults to 8k if he has * not told us otherwise. */ struct iscsi_key session_keys[] = { [ISCSI_PARAM_MAX_RECV_DLENGTH] = {"MaxRecvDataSegmentLength", 8192, 512, 16777215, &minimum_ops}, [ISCSI_PARAM_HDRDGST_EN] = {"HeaderDigest", DIGEST_NONE, DIGEST_NONE, DIGEST_ALL, &digest_ops}, [ISCSI_PARAM_DATADGST_EN] = {"DataDigest", DIGEST_NONE, DIGEST_NONE, DIGEST_ALL, &digest_ops}, [ISCSI_PARAM_INITIAL_R2T_EN] = {"InitialR2T", 1, 0, 1, &or_ops}, [ISCSI_PARAM_MAX_R2T] = {"MaxOutstandingR2T", 1, 1, 65535, &minimum_ops}, [ISCSI_PARAM_IMM_DATA_EN] = {"ImmediateData", 1, 0, 1, &and_ops}, [ISCSI_PARAM_FIRST_BURST] = {"FirstBurstLength", 65536, 512, 16777215, &minimum_ops}, [ISCSI_PARAM_MAX_BURST] = {"MaxBurstLength", 262144, 512, 16777215, &minimum_ops}, [ISCSI_PARAM_PDU_INORDER_EN] = {"DataPDUInOrder", 1, 0, 1, &or_ops}, [ISCSI_PARAM_DATASEQ_INORDER_EN] = {"DataSequenceInOrder", 1, 0, 1, &or_ops}, [ISCSI_PARAM_ERL] = {"ErrorRecoveryLevel", 0, 0, 2, &minimum_ops}, [ISCSI_PARAM_IFMARKER_EN] = {"IFMarker", 0, 0, 1, &and_ops}, [ISCSI_PARAM_OFMARKER_EN] = {"OFMarker", 0, 0, 1, &and_ops}, [ISCSI_PARAM_DEFAULTTIME2WAIT] = {"DefaultTime2Wait", 2, 0, 3600, &maximum_ops}, [ISCSI_PARAM_DEFAULTTIME2RETAIN] = {"DefaultTime2Retain", 20, 0, 3600, &minimum_ops}, [ISCSI_PARAM_OFMARKINT] = {"OFMarkInt", 2048, 1, 65535, &marker_ops}, [ISCSI_PARAM_IFMARKINT] = {"IFMarkInt", 2048, 1, 65535, &marker_ops}, [ISCSI_PARAM_MAXCONNECTIONS] = {"MaxConnections", 1, 1, 65535, &minimum_ops}, /* iSER draft */ [ISCSI_PARAM_RDMA_EXTENSIONS] = {"RDMAExtensions", 0, 0, 1, &and_ops}, [ISCSI_PARAM_TARGET_RDSL] = {"TargetRecvDataSegmentLength", 8192, 512, 16777215, &minimum_ops}, [ISCSI_PARAM_INITIATOR_RDSL] = {"InitiatorRecvDataSegmentLength", 8192, 512, 16777215, &minimum_ops}, [ISCSI_PARAM_MAX_OUTST_PDU] = {"MaxOutstandingUnexpectedPDUs", 0, 2, 4294967295U, &min_or_zero_ops}, /* "local" parmas, never sent to the initiator */ [ISCSI_PARAM_MAX_XMIT_DLENGTH] = {"MaxXmitDataSegmentLength", 8192, 512, 16777215, &minimum_ops}, [ISCSI_PARAM_MAX_QUEUE_CMD] = {"MaxQueueCmd", MAX_QUEUE_CMD_DEF, MAX_QUEUE_CMD_MIN, MAX_QUEUE_CMD_MAX, &minimum_ops}, [ISCSI_PARAM_MAX] = {NULL,}, }; tgt-1.0.85/usr/iscsi/param.h000066400000000000000000000033271435417276200156170ustar00rootroot00000000000000/* * Copyright (C) 2005-2007 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, version 2 of the * License. * * 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 PARAMS_H #define PARAMS_H struct iscsi_key; struct param { int state; unsigned int val; }; #define KEY_STATE_START 0 #define KEY_STATE_REQUEST 1 #define KEY_STATE_DONE 2 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 *); void (*set_val)(struct param *, int, unsigned int *); }; struct iscsi_key { char *name; unsigned int def; unsigned int min; unsigned int max; struct iscsi_key_ops *ops; }; extern struct iscsi_key session_keys[]; extern void param_set_defaults(struct 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 void param_set_val(struct iscsi_key *, struct param *, int, unsigned int *); #endif tgt-1.0.85/usr/iscsi/session.c000066400000000000000000000110201435417276200161620ustar00rootroot00000000000000/* * Copyright (C) 2002-2003 Ardis Technolgies * * 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, version 2 of the * License. * * 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 "iscsid.h" #include "tgtd.h" #include "util.h" static LIST_HEAD(sessions_list); struct iscsi_session *session_find_name(int tid, const char *iname, uint8_t *isid) { struct iscsi_session *session; struct iscsi_target *target; target = target_find_by_id(tid); if (!target) return NULL; dprintf("session_find_name: %s %x %x %x %x %x %x\n", iname, isid[0], isid[1], isid[2], isid[3], isid[4], isid[5]); list_for_each_entry(session, &target->sessions_list, slist) { if (!memcmp(isid, session->isid, sizeof(session->isid)) && !strcmp(iname, session->initiator)) return session; } return NULL; } struct iscsi_session *session_lookup_by_tsih(uint16_t tsih) { struct iscsi_session *session; list_for_each_entry(session, &sessions_list, hlist) { if (session->tsih == tsih) return session; } return NULL; } int session_create(struct iscsi_connection *conn) { int err; struct iscsi_session *session = NULL; static uint16_t tsih, last_tsih = 0; struct iscsi_target *target; char addr[128]; target = target_find_by_id(conn->tid); if (!target) return -EINVAL; for (tsih = last_tsih + 1; tsih != last_tsih; tsih++) { if (!tsih) continue; session = session_lookup_by_tsih(tsih); if (!session) break; } if (session) return -EINVAL; session = zalloc(sizeof(*session)); if (!session) return -ENOMEM; session->initiator = strdup(conn->initiator); if (!session->initiator) { free(session); return -ENOMEM; } if (conn->initiator_alias) { session->initiator_alias = strdup(conn->initiator_alias); if (!session->initiator_alias) { free(session->initiator); free(session); return -ENOMEM; } } session->info = zalloc(1024); if (!session->info) { free(session->initiator); free(session->initiator_alias); free(session); return -ENOMEM; } memset(addr, 0, sizeof(addr)); conn->tp->ep_show(conn, addr, sizeof(addr)); snprintf(session->info, 1024, _TAB3 "Initiator: %s alias: %s\n" _TAB3 "Connection: %u\n" _TAB4 "%s\n", session->initiator, session->initiator_alias ? session->initiator_alias : "none", conn->cid, addr); err = it_nexus_create(target->tid, tsih, 0, session->info); if (err) { free(session->initiator); free(session->initiator_alias); free(session->info); free(session); return err; } session->target = target; INIT_LIST_HEAD(&session->slist); list_add(&session->slist, &target->sessions_list); INIT_LIST_HEAD(&session->conn_list); INIT_LIST_HEAD(&session->cmd_list); INIT_LIST_HEAD(&session->pending_cmd_list); memcpy(session->isid, conn->isid, sizeof(session->isid)); session->tsih = last_tsih = tsih; session->rdma = conn->tp->rdma; conn_add_to_session(conn, session); dprintf("session_create: %#" PRIx64 "\n", sid64(conn->isid, session->tsih)); list_add(&session->hlist, &sessions_list); session->exp_cmd_sn = conn->exp_cmd_sn; memcpy(session->session_param, conn->session_param, sizeof(session->session_param)); session->max_queue_cmd = session->session_param[ISCSI_PARAM_MAX_QUEUE_CMD].val; return 0; } static void session_destroy(struct iscsi_session *session) { if (!list_empty(&session->conn_list)) { eprintf("%d conn_list is not null\n", session->tsih); return; } if (session->target) { list_del(&session->slist); /* session->target->nr_sessions--; */ it_nexus_destroy(session->target->tid, session->tsih); } list_del(&session->hlist); free(session->initiator); free(session->initiator_alias); free(session->info); free(session); } void session_get(struct iscsi_session *session) { session->refcount++; } void session_put(struct iscsi_session *session) { if (!--session->refcount) session_destroy(session); } tgt-1.0.85/usr/iscsi/sha1.c000066400000000000000000000126061435417276200153460ustar00rootroot00000000000000/* * Cryptographic API. * * SHA1 Secure Hash Algorithm. * * Derived from cryptoapi implementation, adapted for in-place * scatterlist interface. Originally based on the public domain * implementation written by Steve Reid. * * Copyright (c) Alan Smithee. * Copyright (c) Andrew McDonald * Copyright (c) Jean-Francois Dive * * 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. * */ #include #include "sha1.h" #define SHA1_DIGEST_SIZE 20 #define SHA1_HMAC_BLOCK_SIZE 64 static inline uint32_t rol(uint32_t value, uint32_t bits) { return (((value) << (bits)) | ((value) >> (32 - (bits)))); } /* blk0() and blk() perform the initial expand. */ /* I got the idea of expanding during the round function from SSLeay */ # define blk0(i) block32[i] #define blk(i) (block32[i&15] = rol(block32[(i+13)&15]^block32[(i+8)&15] \ ^block32[(i+2)&15]^block32[i&15],1)) /* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ #define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5); \ w=rol(w,30); #define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5); \ w=rol(w,30); #define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); #define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5); \ w=rol(w,30); #define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); /* Hash a single 512-bit block. This is the core of the algorithm. */ static void sha1_transform(uint32_t *state, const uint8_t *in) { uint32_t a, b, c, d, e; uint32_t block32[16]; /* convert/copy data to workspace */ for (a = 0; a < sizeof(block32)/sizeof(uint32_t); a++) block32[a] = ntohl (((const uint32_t *)in)[a]); /* Copy context->state[] to working vars */ a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4]; /* 4 rounds of 20 operations each. Loop unrolled. */ R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); /* Add the working vars back into context.state[] */ state[0] += a; state[1] += b; state[2] += c; state[3] += d; state[4] += e; /* Wipe variables */ a = b = c = d = e = 0; memset (block32, 0x00, sizeof block32); } void sha1_init(void *ctx) { struct sha1_ctx *sctx = ctx; static const struct sha1_ctx initstate = { 0, { 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0 }, { 0, } }; *sctx = initstate; } void sha1_update(void *ctx, const uint8_t *data, unsigned int len) { struct sha1_ctx *sctx = ctx; unsigned int i, j; j = (sctx->count >> 3) & 0x3f; sctx->count += len << 3; if ((j + len) > 63) { memcpy(&sctx->buffer[j], data, (i = 64-j)); sha1_transform(sctx->state, sctx->buffer); for ( ; i + 63 < len; i += 64) { sha1_transform(sctx->state, &data[i]); } j = 0; } else i = 0; memcpy(&sctx->buffer[j], &data[i], len - i); } /* Add padding and return the message digest. */ void sha1_final(void* ctx, uint8_t *out) { struct sha1_ctx *sctx = ctx; uint32_t i, j, index, padlen; uint64_t t; uint8_t bits[8] = { 0, }; static const uint8_t padding[64] = { 0x80, }; t = sctx->count; bits[7] = 0xff & t; t>>=8; bits[6] = 0xff & t; t>>=8; bits[5] = 0xff & t; t>>=8; bits[4] = 0xff & t; t>>=8; bits[3] = 0xff & t; t>>=8; bits[2] = 0xff & t; t>>=8; bits[1] = 0xff & t; t>>=8; bits[0] = 0xff & t; /* Pad out to 56 mod 64 */ index = (sctx->count >> 3) & 0x3f; padlen = (index < 56) ? (56 - index) : ((64+56) - index); sha1_update(sctx, padding, padlen); /* Append length */ sha1_update(sctx, bits, sizeof bits); /* Store state in digest */ for (i = j = 0; i < 5; i++, j += 4) { uint32_t t2 = sctx->state[i]; out[j+3] = t2 & 0xff; t2>>=8; out[j+2] = t2 & 0xff; t2>>=8; out[j+1] = t2 & 0xff; t2>>=8; out[j ] = t2 & 0xff; } /* Wipe context */ memset(sctx, 0, sizeof *sctx); } tgt-1.0.85/usr/iscsi/sha1.h000066400000000000000000000011661435417276200153520ustar00rootroot00000000000000/* * sha1.h - SHA1 Secure Hash Algorithm used for CHAP authentication. * copied from the Linux kernel's Cryptographic API and slightly adjusted to * fit IET's needs * * This file is (c) 2004 Xiranet Communications GmbH * and licensed under the GPL. */ #ifndef SHA1_H #define SHA1_H #include #include #include struct sha1_ctx { uint64_t count; uint32_t state[5]; uint8_t buffer[64]; }; void sha1_init(void *ctx); void sha1_update(void *ctx, const uint8_t *data, unsigned int len); void sha1_final(void* ctx, uint8_t *out); #endif tgt-1.0.85/usr/iscsi/target.c000066400000000000000000000507511435417276200160030ustar00rootroot00000000000000/* * Copyright (C) 2002-2003 Ardis Technolgies * * 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, version 2 of the * License. * * 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 #include #include #include #include #include #include "iscsid.h" #include "tgtadm.h" #include "tgtd.h" #include "target.h" #include "util.h" LIST_HEAD(iscsi_targets_list); 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 ((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 ip_match(struct iscsi_connection *conn, char *address) { struct sockaddr_storage from; struct addrinfo hints, *res; socklen_t len; char *str, *p, *q; int err; len = sizeof(from); err = conn->tp->ep_getpeername(conn, (struct sockaddr *) &from, &len); if (err < 0) return -EPERM; str = p = strdup(address); if (!p) return -EPERM; if (!strcmp(p, "ALL")) { err = 0; goto out; } if (*p == '[') { p++; if (!(q = strchr(p, ']'))) { err = -EPERM; goto out; } *(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; err = getaddrinfo(p, NULL, &hints, &res); if (err < 0) { err = -EPERM; goto out; } if (q) err = netmask_match(res->ai_addr, (struct sockaddr *) &from, q); else err = address_match(res->ai_addr, (struct sockaddr *) &from); err = !err; freeaddrinfo(res); out: free(str); return err; } int ip_acl(int tid, struct iscsi_connection *conn) { int idx, err; char *addr; for (idx = 0;; idx++) { addr = acl_get(tid, idx); if (!addr) break; err = ip_match(conn, addr); if (!err) return 0; } return -EPERM; } static int iqn_match(struct iscsi_connection *conn, char *name) { return strcmp(conn->initiator, name); } int iqn_acl(int tid, struct iscsi_connection *conn) { int idx, err; char *name; for (idx = 0;; idx++) { name = iqn_acl_get(tid, idx); if (!name) break; err = iqn_match(conn, name); if (!err) return 0; } return -EPERM; } static int get_redirect_address(char *callback, char *buffer, int buflen, char **address, char **ip_port, int *rsn) { char *p, *addr, *port; memset(buffer, 0, buflen); if (call_program(callback, NULL, NULL, buffer, buflen, 0)) return -1; /* syntax is string_addr:string_port:string_reason */ addr = p = buffer; if (*p == '[') { while (*p != ']' && *p != '\0') p++; if (*p == ']') { p++; if (*p != ':') return -1; } } else { while (*p != ':' && *p != '\0') p++; } if (!*p) return -1; *p = '\0'; port = ++p; while (*p != ':' && *p != '\0') p++; if (!*p) return -1; *p = '\0'; p++; if (!strncmp(p, "Temporary", 9)) *rsn = ISCSI_LOGIN_STATUS_TGT_MOVED_TEMP; else if (!strncmp(p, "Permanent", 9)) *rsn = ISCSI_LOGIN_STATUS_TGT_MOVED_PERM; else return -1; *address = addr; *ip_port = port; return 0; } int target_redirected(struct iscsi_target *target, struct iscsi_connection *conn, char *buf, int *reason) { struct sockaddr_storage from; struct addrinfo hints, *res; socklen_t len; int ret, rsn = 0; char *p, *q, *str, *port = NULL, *addr; char buffer[NI_MAXHOST + NI_MAXSERV + 4]; char dst[INET6_ADDRSTRLEN], in_buf[1024]; len = sizeof(from); ret = conn->tp->ep_getpeername(conn, (struct sockaddr *)&from, &len); if (ret < 0) return 0; ret = 1; if (target->redirect_info.callback) { p = in_buf; p += sprintf(p, "%s ", target->redirect_info.callback); p += sprintf(p, "%s ", tgt_targetname(target->tid)); ret = getnameinfo((struct sockaddr *)&from, sizeof(from), dst, sizeof(dst), NULL, 0, NI_NUMERICHOST); if (ret) goto predefined; sprintf(p, "%s", dst); ret = get_redirect_address(in_buf, buffer, sizeof(buffer), &addr, &port, &rsn); if (ret) return -1; } predefined: if (ret) { if (!strlen(target->redirect_info.addr)) return 0; addr = target->redirect_info.addr; port = target->redirect_info.port; rsn = target->redirect_info.reason; } if (rsn != ISCSI_LOGIN_STATUS_TGT_MOVED_TEMP && rsn != ISCSI_LOGIN_STATUS_TGT_MOVED_PERM) return 0; p = strdup(addr); if (!p) return 0; str = p; if (*p == '[') { p++; if (!(q = strchr(p, ']'))) { free(str); return 0; } *(q++) = '\0'; } memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_NUMERICHOST; ret = getaddrinfo(p, NULL, &hints, &res); if (ret < 0) { free(str); return 0; } ret = address_match(res->ai_addr, (struct sockaddr *)&from); freeaddrinfo(res); free(str); if (!ret) { sprintf(buf, "%s:%s", addr, port); *reason = rsn; } return !ret; } void target_list_build(struct iscsi_connection *conn, char *addr, char *name) { struct iscsi_target *target; list_for_each_entry(target, &iscsi_targets_list, tlist) { if (name && strcmp(tgt_targetname(target->tid), name)) continue; if (ip_acl(target->tid, conn) && iqn_acl(target->tid, conn)) continue; if (isns_scn_access(target->tid, conn->initiator)) continue; text_key_add(conn, "TargetName", tgt_targetname(target->tid)); text_key_add(conn, "TargetAddress", addr); } } struct iscsi_target *target_find_by_name(const char *name) { struct iscsi_target *target; char *tname; list_for_each_entry(target, &iscsi_targets_list, tlist) { tname = tgt_targetname(target->tid); if (tname && !strcmp(tname, name)) return target; } return NULL; } struct iscsi_target* target_find_by_id(int tid) { struct iscsi_target *target; list_for_each_entry(target, &iscsi_targets_list, tlist) { if (target->tid == tid) return target; } return NULL; } void iscsi_target_destroy(int tid, int force) { struct iscsi_target* target; struct iscsi_session *session, *stmp; struct iscsi_connection *conn, *ctmp; target = target_find_by_id(tid); if (!target) { eprintf("can't find the target %d\n", tid); return; } if (!force && target->nr_sessions) { eprintf("the target %d still has sessions\n", tid); return; } list_for_each_entry_safe(session, stmp, &target->sessions_list, slist) { session_get(session); list_for_each_entry_safe(conn, ctmp, &session->conn_list, clist) { conn_close(conn); } session_put(session); } if (!list_empty(&target->sessions_list)) { eprintf("bug still have sessions %d\n", tid); exit(-1); } list_del(&target->tlist); if (target->redirect_info.callback) free(target->redirect_info.callback); free(target); isns_target_deregister(tgt_targetname(tid)); return; } int iscsi_target_create(struct target *t) { int tid = t->tid; struct iscsi_target *target; struct param default_tgt_session_param[] = { [ISCSI_PARAM_MAX_RECV_DLENGTH] = {0, 8192}, [ISCSI_PARAM_HDRDGST_EN] = {0, DIGEST_NONE}, [ISCSI_PARAM_DATADGST_EN] = {0, DIGEST_NONE}, [ISCSI_PARAM_INITIAL_R2T_EN] = {0, 1}, [ISCSI_PARAM_MAX_R2T] = {0, 1}, [ISCSI_PARAM_IMM_DATA_EN] = {0, 1}, [ISCSI_PARAM_FIRST_BURST] = {0, 65536}, [ISCSI_PARAM_MAX_BURST] = {0, 262144}, [ISCSI_PARAM_PDU_INORDER_EN] = {0, 1}, [ISCSI_PARAM_DATASEQ_INORDER_EN] = {0, 1}, [ISCSI_PARAM_ERL] = {0, 0}, [ISCSI_PARAM_IFMARKER_EN] = {0, 0}, [ISCSI_PARAM_OFMARKER_EN] = {0, 0}, [ISCSI_PARAM_DEFAULTTIME2WAIT] = {0, 2}, [ISCSI_PARAM_DEFAULTTIME2RETAIN] = {0, 20}, [ISCSI_PARAM_OFMARKINT] = {0, 2048}, [ISCSI_PARAM_IFMARKINT] = {0, 2048}, [ISCSI_PARAM_MAXCONNECTIONS] = {0, 1}, [ISCSI_PARAM_RDMA_EXTENSIONS] = {0, 1}, [ISCSI_PARAM_TARGET_RDSL] = {0, 262144}, [ISCSI_PARAM_INITIATOR_RDSL] = {0, 262144}, [ISCSI_PARAM_MAX_OUTST_PDU] = {0, 0}, /* not in open-iscsi */ /* "local" parmas, never sent to the initiator */ [ISCSI_PARAM_MAX_XMIT_DLENGTH] = {0, 8192}, /* do not edit */ [ISCSI_PARAM_MAX_QUEUE_CMD] = {0, MAX_QUEUE_CMD_DEF}, }; target = malloc(sizeof(*target)); if (!target) return -ENOMEM; memset(target, 0, sizeof(*target)); memcpy(target->session_param, default_tgt_session_param, sizeof(target->session_param)); INIT_LIST_HEAD(&target->tlist); INIT_LIST_HEAD(&target->sessions_list); INIT_LIST_HEAD(&target->isns_list); target->tid = tid; target->nop_interval = default_nop_interval; target->nop_count = default_nop_count; list_add_tail(&target->tlist, &iscsi_targets_list); isns_target_register(tgt_targetname(tid)); return 0; } static int iscsi_session_param_update(struct iscsi_target* target, int idx, char *str) { int err; unsigned int val; err = param_str_to_val(session_keys, idx, str, &val); if (err) return err; err = param_check_val(session_keys, idx, &val); if (err < 0) return err; target->session_param[idx].val = val; dprintf("%s %s %u\n", session_keys[idx].name, str, val); return 0; } tgtadm_err iscsi_target_update(int mode, int op, int tid, uint64_t sid, uint64_t lun, uint32_t cid, char *name) { tgtadm_err adm_err = TGTADM_INVALID_REQUEST; int idx, err; char *str; struct iscsi_target* target; switch (mode) { case MODE_SYSTEM: adm_err = isns_update(name); break; case MODE_TARGET: target = target_find_by_id(tid); if (!target) return TGTADM_NO_TARGET; str = name + strlen(name) + 1; dprintf("%s:%s\n", name, str); if (!strncmp(name, "RedirectAddress", 15)) { snprintf(target->redirect_info.addr, sizeof(target->redirect_info.addr), "%s", str); adm_err = TGTADM_SUCCESS; break; } else if (!strncmp(name, "RedirectPort", 12)) { snprintf(target->redirect_info.port, sizeof(target->redirect_info.port), "%s", str); adm_err = TGTADM_SUCCESS; break; } else if (!strncmp(name, "RedirectReason", 14)) { if (!strncmp(str, "Temporary", 9)) { target->redirect_info.reason = ISCSI_LOGIN_STATUS_TGT_MOVED_TEMP; adm_err = TGTADM_SUCCESS; } else if (!strncmp(str, "Permanent", 9)) { target->redirect_info.reason = ISCSI_LOGIN_STATUS_TGT_MOVED_PERM; adm_err = TGTADM_SUCCESS; } else break; } else if (!strncmp(name, "RedirectCallback", 16)) { target->redirect_info.callback = strdup(str); if (!target->redirect_info.callback) { adm_err = TGTADM_NOMEM; break; } adm_err = TGTADM_SUCCESS; } else if (!strncmp(name, "nop_count", 9)) { err = iscsi_update_target_nop_count(tid, atoi(&name[10])); adm_err = !err ? TGTADM_SUCCESS : TGTADM_INVALID_REQUEST; break; } else if (!strncmp(name, "nop_interval", 12)) { err = iscsi_update_target_nop_interval(tid, atoi(&name[13])); adm_err = !err ? TGTADM_SUCCESS : TGTADM_INVALID_REQUEST; break; } idx = param_index_by_name(name, session_keys); if (idx >= 0) { err = iscsi_session_param_update(target, idx, str); adm_err = !err ? TGTADM_SUCCESS : TGTADM_INVALID_REQUEST; } break; case MODE_CONNECTION: if (op == OP_DELETE) adm_err = conn_close_admin(tid, sid, cid); break; default: break; } return adm_err; } static tgtadm_err show_iscsi_param(struct param *param, struct concat_buf *b) { struct iscsi_key *keys = session_keys; int i; char value[64]; for (i = 0; session_keys[i].name; i++) { param_val_to_str(keys, i, param[i].val, value); concat_printf(b, "%s=%s\n", keys[i].name, value); } return TGTADM_SUCCESS; } static struct iscsi_session *iscsi_target_find_session( struct iscsi_target *target, uint64_t sid) { struct iscsi_session *session; list_for_each_entry(session, &target->sessions_list, slist) { if (session->tsih == sid) return session; } return NULL; } static tgtadm_err iscsi_target_show_session(struct iscsi_target *target, uint64_t sid, struct concat_buf *b) { tgtadm_err adm_err = TGTADM_SUCCESS; struct iscsi_session *session; session = iscsi_target_find_session(target, sid); if (session) adm_err = show_iscsi_param(session->session_param, b); return adm_err; } static tgtadm_err iscsi_target_show_connections(struct iscsi_target *target, uint64_t sid, struct concat_buf *b) { tgtadm_err adm_err = TGTADM_SUCCESS; struct iscsi_session *session; struct iscsi_connection *conn; char addr[128]; list_for_each_entry(session, &target->sessions_list, slist) { list_for_each_entry(conn, &session->conn_list, clist) { memset(addr, 0, sizeof(addr)); conn->tp->ep_show(conn, addr, sizeof(addr)); concat_printf(b, "Session: %u\n" _TAB1 "Connection: %u\n" _TAB2 "Initiator: %s\n" _TAB2 "%s\n", session->tsih, conn->cid, session->initiator, addr); } } return adm_err; } static tgtadm_err iscsi_target_show_portals(struct iscsi_target *target, uint64_t sid, struct concat_buf *b) { tgtadm_err adm_err = TGTADM_SUCCESS; struct iscsi_portal *portal; list_for_each_entry(portal, &iscsi_portals_list, iscsi_portal_siblings) { int is_ipv6; is_ipv6 = strchr(portal->addr, ':') != NULL; concat_printf(b, "Portal: %s%s%s:%d,%d\n", is_ipv6 ? "[" : "", portal->addr, is_ipv6 ? "]" : "", portal->port ? portal->port : ISCSI_LISTEN_PORT, portal->tpgt); } return adm_err; } static tgtadm_err show_nop_info(struct iscsi_target *target, struct concat_buf *b) { concat_printf(b, "nop_interval=%d\n", target->nop_interval); concat_printf(b, "nop_count=%d\n", target->nop_count); return TGTADM_SUCCESS; } static tgtadm_err show_redirect_info(struct iscsi_target *target, struct concat_buf *b) { tgtadm_err adm_err = TGTADM_SUCCESS; concat_printf(b, "RedirectAddress=%s\n", target->redirect_info.addr); concat_printf(b, "RedirectPort=%s\n", target->redirect_info.port); if (target->redirect_info.reason == ISCSI_LOGIN_STATUS_TGT_MOVED_TEMP) concat_printf(b, "RedirectReason=Temporary\n"); else if (target->redirect_info.reason == ISCSI_LOGIN_STATUS_TGT_MOVED_PERM) concat_printf(b, "RedirectReason=Permanent\n"); else concat_printf(b, "RedirectReason=Unknown\n"); return adm_err; } static tgtadm_err show_redirect_callback(struct iscsi_target *target, struct concat_buf *b) { concat_printf(b, "RedirectCallback=%s\n", target->redirect_info.callback); return TGTADM_SUCCESS; } tgtadm_err iscsi_target_show(int mode, int tid, uint64_t sid, uint32_t cid, uint64_t lun, struct concat_buf *b) { struct iscsi_target* target = NULL; tgtadm_err adm_err = TGTADM_INVALID_REQUEST; if (mode != MODE_SYSTEM && mode != MODE_PORTAL) { target = target_find_by_id(tid); if (!target) return TGTADM_NO_TARGET; } switch (mode) { case MODE_SYSTEM: adm_err = isns_show(b); break; case MODE_TARGET: if (target->redirect_info.callback) adm_err = show_redirect_callback(target, b); else if (strlen(target->redirect_info.addr)) adm_err = show_redirect_info(target, b); else { adm_err = show_nop_info(target, b); adm_err = show_iscsi_param(target->session_param, b); } break; case MODE_SESSION: adm_err = iscsi_target_show_session(target, sid, b); break; case MODE_PORTAL: adm_err = iscsi_target_show_portals(target, sid, b); break; case MODE_CONNECTION: adm_err = iscsi_target_show_connections(target, sid, b); break; default: break; } return adm_err; } static void _stat_iscsi_conn_hdr(struct concat_buf *b) { concat_printf(b, "sid cid rxdata_octets txdata_octets dataout_pdus datain_pdus cmd_pdus rsp_pdus\n"); } static void _stat_iscsi_conn(struct iscsi_connection *conn, struct concat_buf *b) { concat_printf(b, "%3d %3d" " %13" PRIu64 " %13" PRIu64 " %12" PRIu32 " %11" PRIu32 " %8" PRIu32 " %8" PRIu32 "\n", (unsigned int)conn->session->tsih, (unsigned int)conn->cid, conn->stats.rxdata_octets, conn->stats.txdata_octets, conn->stats.dataout_pdus, conn->stats.datain_pdus, conn->stats.scsicmd_pdus, conn->stats.scsirsp_pdus); } static tgtadm_err _stat_iscsi_session(struct iscsi_session *session, uint64_t lun, int filter_lun, struct concat_buf *b) { struct iscsi_target *target = session->target; uint64_t itn_id = session->tsih; int tid = target->tid; /* global target id */ struct it_nexus *itn; struct it_nexus_lu_info *itn_lu; struct scsi_lu *lu; struct iscsi_connection *conn; dprintf("tsih:%d lun:%" PRIu64 " filter_lun:%d\n", (unsigned int)session->tsih, lun, filter_lun); itn = it_nexus_lookup(tid, itn_id); if (!itn) { eprintf("invalid nexus %d %" PRIx64 "\n", tid, itn_id); return TGTADM_NO_SESSION; } tgt_stat_header(b); list_for_each_entry(itn_lu, &itn->itn_itl_info_list, itn_itl_info_siblings) { lu = itn_lu->lu; tgt_stat_line(tid, lu->lun, session->tsih, &itn_lu->stat, b); } if (!list_empty(&session->conn_list)) { concat_printf(b, "\n"); _stat_iscsi_conn_hdr(b); } list_for_each_entry(conn, &session->conn_list, clist) { _stat_iscsi_conn(conn, b); } return TGTADM_SUCCESS; } static tgtadm_err iscsi_stat_connection(uint64_t sid, uint32_t cid, struct concat_buf *b) { struct iscsi_session *session; struct iscsi_connection *conn; dprintf("sid:%" PRIu64 "cid:%" PRIu32 "\n", sid, cid); session = session_lookup_by_tsih((uint16_t)sid); if (!session) return TGTADM_NO_SESSION; conn = conn_find(session, cid); if (!conn) return TGTADM_NO_CONNECTION; _stat_iscsi_conn_hdr(b); _stat_iscsi_conn(conn, b); return TGTADM_SUCCESS; } static tgtadm_err iscsi_stat_session_by_sid(uint64_t sid, struct concat_buf *b) { struct iscsi_session *session; dprintf("sid:%" PRIu64 "\n", sid); session = session_lookup_by_tsih((uint16_t)sid); if (session) return _stat_iscsi_session(session, 0, 0, b); else return TGTADM_NO_SESSION; } static tgtadm_err iscsi_stat_device_by_id(uint64_t lun, uint64_t sid, struct concat_buf *b) { struct iscsi_session *session; dprintf("lun:%" PRIu64 " sid:%" PRIu64 "\n", lun, sid); session = session_lookup_by_tsih((uint16_t)sid); if (session) return _stat_iscsi_session(session, lun, 1, b); else return TGTADM_NO_SESSION; } tgtadm_err iscsi_stat(int mode, int tid, uint64_t sid, uint32_t cid, uint64_t lun, struct concat_buf *b) { tgtadm_err adm_err = TGTADM_INVALID_REQUEST; dprintf("mode:%d tid:%d sid:%" PRIu64 " cid:%" PRIu32 " lun:%" PRIx64 "\n", mode, tid, sid, cid, lun); switch (mode) { case MODE_DEVICE: adm_err = iscsi_stat_device_by_id(lun, sid, b); break; case MODE_SESSION: adm_err = iscsi_stat_session_by_sid(sid, b); break; case MODE_CONNECTION: adm_err = iscsi_stat_connection(sid, cid, b); break; default: break; } return adm_err; } tgt-1.0.85/usr/iscsi/transport.c000066400000000000000000000030451435417276200165430ustar00rootroot00000000000000/* * iSCSI transport functions * * Copyright (C) 2007 FUJITA Tomonori * Copyright (C) 2007 Mike Christie * * 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, version 2 of the * License. * * 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 "iscsid.h" #include "transport.h" static LIST_HEAD(iscsi_transport_list); int lld_index; int iscsi_init(int index, char *args) { int err, nr = 0; struct iscsi_transport *t; lld_index = index; list_for_each_entry(t, &iscsi_transport_list, iscsi_transport_siblings) { err = t->ep_init(); if (!err) nr++; } return !nr; } void iscsi_exit(void) { struct iscsi_transport *t; list_for_each_entry(t, &iscsi_transport_list, iscsi_transport_siblings) if (t->ep_exit) t->ep_exit(); } int iscsi_transport_register(struct iscsi_transport *t) { list_add_tail(&t->iscsi_transport_siblings, &iscsi_transport_list); return 0; } tgt-1.0.85/usr/iscsi/transport.h000066400000000000000000000030411435417276200165440ustar00rootroot00000000000000#ifndef __TRANSPORT_H #define __TRANSPORT_H #include #include "list.h" struct iscsi_connection; struct iscsi_task; struct iscsi_transport { struct list_head iscsi_transport_siblings; const char *name; int rdma; int data_padding; int (*ep_init) (void); void (*ep_exit) (void); int (*ep_login_complete)(struct iscsi_connection *conn); struct iscsi_task *(*alloc_task)(struct iscsi_connection *conn, size_t ext_len); void (*free_task)(struct iscsi_task *task); size_t (*ep_read)(struct iscsi_connection *conn, void *buf, size_t nbytes); size_t (*ep_write_begin)(struct iscsi_connection *conn, void *buf, size_t nbytes); void (*ep_write_end)(struct iscsi_connection *conn); int (*ep_rdma_read)(struct iscsi_connection *conn); int (*ep_rdma_write)(struct iscsi_connection *conn); size_t (*ep_close)(struct iscsi_connection *conn); void (*ep_force_close)(struct iscsi_connection *conn); void (*ep_release)(struct iscsi_connection *conn); int (*ep_show)(struct iscsi_connection *conn, char *buf, int rest); void (*ep_event_modify)(struct iscsi_connection *conn, int events); void *(*alloc_data_buf)(struct iscsi_connection *conn, size_t sz); void (*free_data_buf)(struct iscsi_connection *conn, void *buf); int (*ep_getsockname)(struct iscsi_connection *conn, struct sockaddr *sa, socklen_t *len); int (*ep_getpeername)(struct iscsi_connection *conn, struct sockaddr *sa, socklen_t *len); void (*ep_nop_reply) (long ttt); }; extern int iscsi_transport_register(struct iscsi_transport *); #endif tgt-1.0.85/usr/libcrc32c.c000066400000000000000000000145241435417276200151470ustar00rootroot00000000000000/* * CRC32C *@Article{castagnoli-crc, * author = { Guy Castagnoli and Stefan Braeuer and Martin Herrman}, * title = {{Optimization of Cyclic Redundancy-Check Codes with 24 * and 32 Parity Bits}}, * journal = IEEE Transactions on Communication, * year = {1993}, * volume = {41}, * number = {6}, * pages = {}, * month = {June}, *} * Used by the iSCSI driver, possibly others, and derived from the * the iscsi-crc.c module of the linux-iscsi driver at * http://linux-iscsi.sourceforge.net. * * Following the example of lib/crc32, this function is intended to be * flexible and useful for all users. Modules that currently have their * own crc32c, but hopefully may be able to use this one are: * net/sctp (please add all your doco to here if you change to * use this one!) * * * Copyright (c) 2004 Cisco Systems, Inc. * * 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. * */ #include "crc32c.h" #include /* * MODULE_AUTHOR("Clay Haapala "); * MODULE_DESCRIPTION("CRC32c (Castagnoli) calculations"); * MODULE_LICENSE("GPL"); */ #define CRC32C_POLY_BE 0x1EDC6F41 #define CRC32C_POLY_LE 0x82F63B78 #ifndef CRC_LE_BITS # define CRC_LE_BITS 8 #endif /* * Haven't generated a big-endian table yet, but the bit-wise version * should at least work. */ #if defined CRC_BE_BITS && CRC_BE_BITS != 1 #undef CRC_BE_BITS #endif #ifndef CRC_BE_BITS # define CRC_BE_BITS 1 #endif #if CRC_LE_BITS == 1 /* * Compute things bit-wise, as done in crc32.c. We could share the tight * loop below with crc32 and vary the POLY if we don't find value in terms * of space and maintainability in keeping the two modules separate. */ uint32_t __attribute__((pure)) crc32c_le(uint32_t crc, unsigned char const *p, size_t len) { int i; while (len--) { crc ^= *p++; for (i = 0; i < 8; i++) crc = (crc >> 1) ^ ((crc & 1) ? CRC32C_POLY_LE : 0); } return crc; } #else /* * This is the CRC-32C table * Generated with: * width = 32 bits * poly = 0x1EDC6F41 * reflect input bytes = true * reflect output bytes = true */ static const uint32_t crc32c_table[256] = { 0x00000000L, 0xF26B8303L, 0xE13B70F7L, 0x1350F3F4L, 0xC79A971FL, 0x35F1141CL, 0x26A1E7E8L, 0xD4CA64EBL, 0x8AD958CFL, 0x78B2DBCCL, 0x6BE22838L, 0x9989AB3BL, 0x4D43CFD0L, 0xBF284CD3L, 0xAC78BF27L, 0x5E133C24L, 0x105EC76FL, 0xE235446CL, 0xF165B798L, 0x030E349BL, 0xD7C45070L, 0x25AFD373L, 0x36FF2087L, 0xC494A384L, 0x9A879FA0L, 0x68EC1CA3L, 0x7BBCEF57L, 0x89D76C54L, 0x5D1D08BFL, 0xAF768BBCL, 0xBC267848L, 0x4E4DFB4BL, 0x20BD8EDEL, 0xD2D60DDDL, 0xC186FE29L, 0x33ED7D2AL, 0xE72719C1L, 0x154C9AC2L, 0x061C6936L, 0xF477EA35L, 0xAA64D611L, 0x580F5512L, 0x4B5FA6E6L, 0xB93425E5L, 0x6DFE410EL, 0x9F95C20DL, 0x8CC531F9L, 0x7EAEB2FAL, 0x30E349B1L, 0xC288CAB2L, 0xD1D83946L, 0x23B3BA45L, 0xF779DEAEL, 0x05125DADL, 0x1642AE59L, 0xE4292D5AL, 0xBA3A117EL, 0x4851927DL, 0x5B016189L, 0xA96AE28AL, 0x7DA08661L, 0x8FCB0562L, 0x9C9BF696L, 0x6EF07595L, 0x417B1DBCL, 0xB3109EBFL, 0xA0406D4BL, 0x522BEE48L, 0x86E18AA3L, 0x748A09A0L, 0x67DAFA54L, 0x95B17957L, 0xCBA24573L, 0x39C9C670L, 0x2A993584L, 0xD8F2B687L, 0x0C38D26CL, 0xFE53516FL, 0xED03A29BL, 0x1F682198L, 0x5125DAD3L, 0xA34E59D0L, 0xB01EAA24L, 0x42752927L, 0x96BF4DCCL, 0x64D4CECFL, 0x77843D3BL, 0x85EFBE38L, 0xDBFC821CL, 0x2997011FL, 0x3AC7F2EBL, 0xC8AC71E8L, 0x1C661503L, 0xEE0D9600L, 0xFD5D65F4L, 0x0F36E6F7L, 0x61C69362L, 0x93AD1061L, 0x80FDE395L, 0x72966096L, 0xA65C047DL, 0x5437877EL, 0x4767748AL, 0xB50CF789L, 0xEB1FCBADL, 0x197448AEL, 0x0A24BB5AL, 0xF84F3859L, 0x2C855CB2L, 0xDEEEDFB1L, 0xCDBE2C45L, 0x3FD5AF46L, 0x7198540DL, 0x83F3D70EL, 0x90A324FAL, 0x62C8A7F9L, 0xB602C312L, 0x44694011L, 0x5739B3E5L, 0xA55230E6L, 0xFB410CC2L, 0x092A8FC1L, 0x1A7A7C35L, 0xE811FF36L, 0x3CDB9BDDL, 0xCEB018DEL, 0xDDE0EB2AL, 0x2F8B6829L, 0x82F63B78L, 0x709DB87BL, 0x63CD4B8FL, 0x91A6C88CL, 0x456CAC67L, 0xB7072F64L, 0xA457DC90L, 0x563C5F93L, 0x082F63B7L, 0xFA44E0B4L, 0xE9141340L, 0x1B7F9043L, 0xCFB5F4A8L, 0x3DDE77ABL, 0x2E8E845FL, 0xDCE5075CL, 0x92A8FC17L, 0x60C37F14L, 0x73938CE0L, 0x81F80FE3L, 0x55326B08L, 0xA759E80BL, 0xB4091BFFL, 0x466298FCL, 0x1871A4D8L, 0xEA1A27DBL, 0xF94AD42FL, 0x0B21572CL, 0xDFEB33C7L, 0x2D80B0C4L, 0x3ED04330L, 0xCCBBC033L, 0xA24BB5A6L, 0x502036A5L, 0x4370C551L, 0xB11B4652L, 0x65D122B9L, 0x97BAA1BAL, 0x84EA524EL, 0x7681D14DL, 0x2892ED69L, 0xDAF96E6AL, 0xC9A99D9EL, 0x3BC21E9DL, 0xEF087A76L, 0x1D63F975L, 0x0E330A81L, 0xFC588982L, 0xB21572C9L, 0x407EF1CAL, 0x532E023EL, 0xA145813DL, 0x758FE5D6L, 0x87E466D5L, 0x94B49521L, 0x66DF1622L, 0x38CC2A06L, 0xCAA7A905L, 0xD9F75AF1L, 0x2B9CD9F2L, 0xFF56BD19L, 0x0D3D3E1AL, 0x1E6DCDEEL, 0xEC064EEDL, 0xC38D26C4L, 0x31E6A5C7L, 0x22B65633L, 0xD0DDD530L, 0x0417B1DBL, 0xF67C32D8L, 0xE52CC12CL, 0x1747422FL, 0x49547E0BL, 0xBB3FFD08L, 0xA86F0EFCL, 0x5A048DFFL, 0x8ECEE914L, 0x7CA56A17L, 0x6FF599E3L, 0x9D9E1AE0L, 0xD3D3E1ABL, 0x21B862A8L, 0x32E8915CL, 0xC083125FL, 0x144976B4L, 0xE622F5B7L, 0xF5720643L, 0x07198540L, 0x590AB964L, 0xAB613A67L, 0xB831C993L, 0x4A5A4A90L, 0x9E902E7BL, 0x6CFBAD78L, 0x7FAB5E8CL, 0x8DC0DD8FL, 0xE330A81AL, 0x115B2B19L, 0x020BD8EDL, 0xF0605BEEL, 0x24AA3F05L, 0xD6C1BC06L, 0xC5914FF2L, 0x37FACCF1L, 0x69E9F0D5L, 0x9B8273D6L, 0x88D28022L, 0x7AB90321L, 0xAE7367CAL, 0x5C18E4C9L, 0x4F48173DL, 0xBD23943EL, 0xF36E6F75L, 0x0105EC76L, 0x12551F82L, 0xE03E9C81L, 0x34F4F86AL, 0xC69F7B69L, 0xD5CF889DL, 0x27A40B9EL, 0x79B737BAL, 0x8BDCB4B9L, 0x988C474DL, 0x6AE7C44EL, 0xBE2DA0A5L, 0x4C4623A6L, 0x5F16D052L, 0xAD7D5351L }; /* * Steps through buffer one byte at at time, calculates reflected * crc using table. */ uint32_t __attribute__((pure)) crc32c_le(uint32_t seed, unsigned char const *data, size_t length) { uint32_t crc = __cpu_to_le32(seed); while (length--) crc = crc32c_table[(crc ^ *data++) & 0xFFL] ^ (crc >> 8); return __le32_to_cpu(crc); } #endif /* CRC_LE_BITS == 8 */ #if CRC_BE_BITS == 1 uint32_t __attribute__((pure)) crc32c_be(uint32_t crc, unsigned char const *p, size_t len) { int i; while (len--) { crc ^= *p++ << 24; for (i = 0; i < 8; i++) crc = (crc << 1) ^ ((crc & 0x80000000) ? CRC32C_POLY_BE : 0); } return crc; } #endif tgt-1.0.85/usr/libssc.c000066400000000000000000000150201435417276200146500ustar00rootroot00000000000000/* * helpers for assessing to ssc on-disk structures * * Copyright (C) 2008 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, version 2 of the * License. * * 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 "bs_ssc.h" #include "ssc.h" #include "be_byteshift.h" #include "crc32c.h" #define SSC_1ST_HDR_OFFSET (sizeof(struct MAM) + SSC_BLK_HDR_SIZE) #define SSC_GET_MAM_INFO_VAL(member, bits)\ {\ (i)->member = get_unaligned_be##bits(&((m)->member));\ } #define SSC_GET_MAM_INFO_ARRAY(member)\ {\ memcpy((i)->member, (m)->member, sizeof((m)->member));\ } static inline uint8_t get_unaligned_be8(const uint8_t *p) { return p[0]; } int ssc_read_mam_info(int fd, struct MAM_info *i) { struct MAM mam, *m; int ret; m = &mam; ret = pread(fd, m, sizeof(struct MAM), SSC_BLK_HDR_SIZE); if (ret != sizeof(struct MAM)) return 1; if (lseek64(fd, SSC_1ST_HDR_OFFSET, SEEK_SET) != SSC_1ST_HDR_OFFSET) return 1; SSC_GET_MAM_INFO_VAL(tape_fmt_version, 32); SSC_GET_MAM_INFO_VAL(remaining_capacity, 64); SSC_GET_MAM_INFO_VAL(max_capacity, 64); SSC_GET_MAM_INFO_VAL(TapeAlert, 64); SSC_GET_MAM_INFO_VAL(load_count, 64); SSC_GET_MAM_INFO_VAL(MAM_space_remaining, 64); SSC_GET_MAM_INFO_ARRAY(assigning_organization_1); SSC_GET_MAM_INFO_VAL(formatted_density_code, 8); SSC_GET_MAM_INFO_ARRAY(initialization_count); SSC_GET_MAM_INFO_ARRAY(dev_make_serial_last_load); SSC_GET_MAM_INFO_VAL(written_in_medium_life, 64); SSC_GET_MAM_INFO_VAL(read_in_medium_life, 64); SSC_GET_MAM_INFO_VAL(written_in_last_load, 64); SSC_GET_MAM_INFO_VAL(read_in_last_load, 64); SSC_GET_MAM_INFO_ARRAY(medium_manufacturer); SSC_GET_MAM_INFO_ARRAY(medium_serial_number); SSC_GET_MAM_INFO_VAL(medium_length, 32); SSC_GET_MAM_INFO_VAL(medium_width, 32); SSC_GET_MAM_INFO_ARRAY(assigning_organization_2); SSC_GET_MAM_INFO_VAL(medium_density_code, 8); SSC_GET_MAM_INFO_ARRAY(medium_manufacture_date); SSC_GET_MAM_INFO_VAL(MAM_capacity, 64); SSC_GET_MAM_INFO_VAL(medium_type, 8); SSC_GET_MAM_INFO_VAL(medium_type_information, 16); SSC_GET_MAM_INFO_ARRAY(application_vendor); SSC_GET_MAM_INFO_ARRAY(application_name); SSC_GET_MAM_INFO_ARRAY(application_version); SSC_GET_MAM_INFO_ARRAY(user_medium_text_label); SSC_GET_MAM_INFO_ARRAY(date_time_last_written); SSC_GET_MAM_INFO_VAL(localization_identifier, 8); SSC_GET_MAM_INFO_ARRAY(barcode); SSC_GET_MAM_INFO_ARRAY(owning_host_textual_name); SSC_GET_MAM_INFO_ARRAY(media_pool); SSC_GET_MAM_INFO_ARRAY(vendor_unique); SSC_GET_MAM_INFO_VAL(dirty, 8); return 0; } #define SSC_PUT_MAM_INFO_VAL(member, bits)\ {\ put_unaligned_be##bits((i)->member, &((m)->member));\ } #define SSC_PUT_MAM_INFO_ARRAY(member)\ {\ memcpy((m)->member, (i)->member, sizeof((m)->member));\ } static inline void put_unaligned_be8(uint8_t val, uint8_t *p) { *p = val; } int ssc_write_mam_info(int fd, struct MAM_info *i) { struct MAM mam, *m; int ret; m = &mam; memset(m, 0, sizeof(struct MAM)); SSC_PUT_MAM_INFO_VAL(tape_fmt_version, 32); SSC_PUT_MAM_INFO_VAL(remaining_capacity, 64); SSC_PUT_MAM_INFO_VAL(max_capacity, 64); SSC_PUT_MAM_INFO_VAL(TapeAlert, 64); SSC_PUT_MAM_INFO_VAL(load_count, 64); SSC_PUT_MAM_INFO_VAL(MAM_space_remaining, 64); SSC_PUT_MAM_INFO_ARRAY(assigning_organization_1); SSC_PUT_MAM_INFO_VAL(formatted_density_code, 8); SSC_PUT_MAM_INFO_ARRAY(initialization_count); SSC_PUT_MAM_INFO_ARRAY(dev_make_serial_last_load); SSC_PUT_MAM_INFO_VAL(written_in_medium_life, 64); SSC_PUT_MAM_INFO_VAL(read_in_medium_life, 64); SSC_PUT_MAM_INFO_VAL(written_in_last_load, 64); SSC_PUT_MAM_INFO_VAL(read_in_last_load, 64); SSC_PUT_MAM_INFO_ARRAY(medium_manufacturer); SSC_PUT_MAM_INFO_ARRAY(medium_serial_number); SSC_PUT_MAM_INFO_VAL(medium_length, 32); SSC_PUT_MAM_INFO_VAL(medium_width, 32); SSC_PUT_MAM_INFO_ARRAY(assigning_organization_2); SSC_PUT_MAM_INFO_VAL(medium_density_code, 8); SSC_PUT_MAM_INFO_ARRAY(medium_manufacture_date); SSC_PUT_MAM_INFO_VAL(MAM_capacity, 64); SSC_PUT_MAM_INFO_VAL(medium_type, 8); SSC_PUT_MAM_INFO_VAL(medium_type_information, 16); SSC_PUT_MAM_INFO_ARRAY(application_vendor); SSC_PUT_MAM_INFO_ARRAY(application_name); SSC_PUT_MAM_INFO_ARRAY(application_version); SSC_PUT_MAM_INFO_ARRAY(user_medium_text_label); SSC_PUT_MAM_INFO_ARRAY(date_time_last_written); SSC_PUT_MAM_INFO_VAL(localization_identifier, 8); SSC_PUT_MAM_INFO_ARRAY(barcode); SSC_PUT_MAM_INFO_ARRAY(owning_host_textual_name); SSC_PUT_MAM_INFO_ARRAY(media_pool); SSC_PUT_MAM_INFO_ARRAY(vendor_unique); SSC_PUT_MAM_INFO_VAL(dirty, 8); ret = pwrite(fd, m, sizeof(struct MAM), SSC_BLK_HDR_SIZE); if (ret != sizeof(struct MAM)) return 1; if (lseek64(fd, SSC_1ST_HDR_OFFSET, SEEK_SET) != SSC_1ST_HDR_OFFSET) return 1; return 0; } int ssc_read_blkhdr(int fd, struct blk_header_info *i, loff_t offset) { size_t count; struct blk_header h, *m = &h; uint32_t crc = ~0; count = pread64(fd, m, SSC_BLK_HDR_SIZE, offset); if (count != SSC_BLK_HDR_SIZE) return 1; crc = crc32c(crc, &m->ondisk_sz, SSC_BLK_HDR_SIZE - sizeof(m->h_csum)); if (*(uint32_t *)m->h_csum != ~crc) fprintf(stderr, "crc error\n"); SSC_GET_MAM_INFO_VAL(ondisk_sz, 32); SSC_GET_MAM_INFO_VAL(blk_sz, 32); SSC_GET_MAM_INFO_VAL(blk_type, 32); SSC_GET_MAM_INFO_VAL(blk_num, 64); SSC_GET_MAM_INFO_VAL(prev, 64); SSC_GET_MAM_INFO_VAL(curr, 64); SSC_GET_MAM_INFO_VAL(next, 64); return 0; } int ssc_write_blkhdr(int fd, struct blk_header_info *i, loff_t offset) { size_t count; struct blk_header h, *m = &h; uint32_t crc = ~0; SSC_PUT_MAM_INFO_VAL(ondisk_sz, 32); SSC_PUT_MAM_INFO_VAL(blk_sz, 32); SSC_PUT_MAM_INFO_VAL(blk_type, 32); SSC_PUT_MAM_INFO_VAL(blk_num, 64); SSC_PUT_MAM_INFO_VAL(prev, 64); SSC_PUT_MAM_INFO_VAL(curr, 64); SSC_PUT_MAM_INFO_VAL(next, 64); crc = crc32c(crc, &m->ondisk_sz, SSC_BLK_HDR_SIZE - sizeof(m->h_csum)); *(uint32_t *)m->h_csum = ~crc; count = pwrite64(fd, m, SSC_BLK_HDR_SIZE, offset); if (count != SSC_BLK_HDR_SIZE) return 1; return 0; } tgt-1.0.85/usr/libssc.h000066400000000000000000000005011435417276200146530ustar00rootroot00000000000000#ifndef __LIBSSC_H #define __LIBSSC_H extern int ssc_read_mam_info(int fd, struct MAM_info *i); extern int ssc_write_mam_info(int fd, struct MAM_info *i); extern int ssc_read_blkhdr(int fd, struct blk_header_info *h, loff_t offset); extern int ssc_write_blkhdr(int fd, struct blk_header_info *h, loff_t offset); #endif tgt-1.0.85/usr/list.h000066400000000000000000000056201435417276200143560ustar00rootroot00000000000000#ifndef __LIST_H__ #define __LIST_H__ /* taken 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 container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) struct list_head { struct list_head *next, *prev; }; #define LIST_HEAD_INIT(name) { &(name), &(name) } #define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name) static inline void INIT_LIST_HEAD(struct list_head *list) { list->next = list; list->prev = list; } #define list_first_entry(ptr, type, member) \ list_entry((ptr)->next, type, member) static inline int list_empty(const struct list_head *head) { return head->next == head; } #define list_entry(ptr, type, member) \ container_of(ptr, type, member) #define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); pos = pos->next) #define list_for_each_prev(pos, head) \ for (pos = (head)->prev; pos != (head); pos = pos->prev) #define list_for_each_entry(pos, head, member) \ for (pos = list_entry((head)->next, typeof(*pos), member); \ &pos->member != (head); \ pos = list_entry(pos->member.next, typeof(*pos), member)) #define list_for_each_entry_safe(pos, n, head, member) \ for (pos = list_entry((head)->next, typeof(*pos), member), \ n = list_entry(pos->member.next, typeof(*pos), member); \ &pos->member != (head); \ pos = n, n = list_entry(n->member.next, typeof(*n), member)) static inline void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next) { next->prev = new; new->next = next; new->prev = prev; prev->next = new; } static inline void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); } static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); } static inline void __list_del(struct list_head * prev, struct list_head * next) { next->prev = prev; prev->next = next; } static inline void list_del(struct list_head *entry) { __list_del(entry->prev, entry->next); entry->next = entry->prev = NULL; } static inline void list_del_init(struct list_head *entry) { __list_del(entry->prev, entry->next); INIT_LIST_HEAD(entry); } static inline void __list_splice(const struct list_head *list, struct list_head *prev, struct list_head *next) { struct list_head *first = list->next; struct list_head *last = list->prev; first->prev = prev; prev->next = first; last->next = next; next->prev = last; } static inline void list_splice_init(struct list_head *list, struct list_head *head) { if (!list_empty(list)) { __list_splice(list, head, head->next); INIT_LIST_HEAD(list); } } #endif tgt-1.0.85/usr/log.c000066400000000000000000000210721435417276200141560ustar00rootroot00000000000000/* * Copyright (C) 2002-2003 Ardis Technolgies * * 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, version 2 of the * License. * * 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 #include #include #include #include "log.h" #define LOGDBG 0 #if LOGDBG #define logdbg(file, fmt, args...) fprintf(file, fmt, ##args) #else #define logdbg(file, fmt, args...) do {} while (0) #endif static struct logarea *la; static char *log_name; int is_debug = 0; static pid_t pid; static int logarea_init (int size) { int shmid; extern char mgmt_path[]; key_t semkey; logdbg(stderr,"enter logarea_init\n"); if ((shmid = shmget(IPC_PRIVATE, sizeof(struct logarea), 0644 | IPC_CREAT | IPC_EXCL)) == -1) { syslog(LOG_ERR, "shmget logarea failed %d", errno); return 1; } la = shmat(shmid, NULL, 0); if (!la) { syslog(LOG_ERR, "shmat logarea failed %d", errno); return 1; } shmctl(shmid, IPC_RMID, NULL); if (size < MAX_MSG_SIZE) size = LOG_SPACE_SIZE; if ((shmid = shmget(IPC_PRIVATE, size, 0644 | IPC_CREAT | IPC_EXCL)) == -1) { syslog(LOG_ERR, "shmget msg failed %d", errno); shmdt(la); return 1; } la->start = shmat(shmid, NULL, 0); if (!la->start) { syslog(LOG_ERR, "shmat msg failed %d", errno); shmdt(la); return 1; } memset(la->start, 0, size); shmctl(shmid, IPC_RMID, NULL); la->empty = 1; la->end = la->start + size; la->head = la->start; la->tail = la->start; if ((shmid = shmget(IPC_PRIVATE, MAX_MSG_SIZE + sizeof(struct logmsg), 0644 | IPC_CREAT | IPC_EXCL)) == -1) { syslog(LOG_ERR, "shmget logmsg failed %d", errno); shmdt(la->start); shmdt(la); return 1; } la->buff = shmat(shmid, NULL, 0); if (!la->buff) { syslog(LOG_ERR, "shmat logmsgfailed %d", errno); shmdt(la->start); shmdt(la); return 1; } shmctl(shmid, IPC_RMID, NULL); if ((semkey = ftok(mgmt_path, 'a')) < 0) { syslog(LOG_ERR, "semkey failed %d", errno); return 1; } if ((semget(semkey, 0, IPC_EXCL)) > 0) { /* Semkey may exists after SIGKILL tgtd */ syslog(LOG_WARNING, "semkey 0x%x already exists", semkey); } if ((la->semid = semget(semkey, 1, 0666 | IPC_CREAT)) < 0) { syslog(LOG_ERR, "semget failed %d", errno); shmdt(la->buff); shmdt(la->start); shmdt(la); return 1; } syslog(LOG_INFO, "semkey 0x%x", semkey); la->semarg.val=1; if (semctl(la->semid, 0, SETVAL, la->semarg) < 0) { syslog(LOG_ERR, "semctl failed %d", errno); shmdt(la->buff); shmdt(la->start); shmdt(la); return 1; } return 0; } static void free_logarea (void) { if (!la) return; semctl(la->semid, 0, IPC_RMID, la->semarg); shmdt(la->buff); shmdt(la->start); shmdt(la); la = NULL; } #if LOGDBG static void dump_logarea (void) { struct logmsg * msg; logdbg(stderr, "\n==== area: start addr = %p, end addr = %p ====\n", la->start, la->end); logdbg(stderr, "|addr |next |prio|msg\n"); for (msg = (struct logmsg *)la->head; (void *)msg != la->tail; msg = msg->next) logdbg(stderr, "|%p |%p |%i |%s\n", (void *)msg, msg->next, msg->prio, (char *)&msg->str); logdbg(stderr, "|%p |%p |%i |%s\n", (void *)msg, msg->next, msg->prio, (char *)&msg->str); logdbg(stderr, "\n\n"); } #endif static int log_enqueue(int prio, const char *fmt, va_list ap) { int len, fwd; char buff[MAX_MSG_SIZE]; struct logmsg * msg; struct logmsg * lastmsg; lastmsg = (struct logmsg *)la->tail; if (!la->empty) { fwd = sizeof(struct logmsg) + strlen((char *)&lastmsg->str) * sizeof(char) + 1; la->tail += fwd; } vsnprintf(buff, MAX_MSG_SIZE, fmt, ap); len = strlen(buff) * sizeof(char) + 1; /* not enough space on tail : rewind */ if (la->head <= la->tail && (len + sizeof(struct logmsg)) > (la->end - la->tail)) { logdbg(stderr, "enqueue: rewind tail to %p\n", la->tail); la->tail = la->start; } /* not enough space on head : drop msg */ if (la->head >= la->tail && (len + sizeof(struct logmsg)) > (la->head - la->tail)) { logdbg(stderr, "enqueue: log area overrun, drop msg\n"); if (!la->empty) la->tail = lastmsg; return 1; } /* ok, we can stage the msg in the area */ la->empty = 0; msg = (struct logmsg *)la->tail; msg->prio = prio; memcpy((void *)&msg->str, buff, len); lastmsg->next = la->tail; msg->next = la->head; logdbg(stderr, "enqueue: %p, %p, %i, %s\n", (void *)msg, msg->next, msg->prio, (char *)&msg->str); #if LOGDBG dump_logarea(); #endif return 0; } static int log_dequeue(void *buff) { struct logmsg * src = (struct logmsg *)la->head; struct logmsg * dst = (struct logmsg *)buff; struct logmsg * lst = (struct logmsg *)la->tail; if (la->empty) return 1; int len = strlen((char *)&src->str) * sizeof(char) + sizeof(struct logmsg) + 1; dst->prio = src->prio; memcpy(dst, src, len); if (la->tail == la->head) la->empty = 1; /* we purge the last logmsg */ else { la->head = src->next; lst->next = la->head; } logdbg(stderr, "dequeue: %p, %p, %i, %s\n", (void *)src, src->next, src->prio, (char *)&src->str); memset((void *)src, 0, len); return la->empty; } /* * this one can block under memory pressure */ static void log_syslog (void * buff) { struct logmsg * msg = (struct logmsg *)buff; syslog(msg->prio, "%s", (char *)&msg->str); } static void dolog(int prio, const char *fmt, va_list ap) { struct timespec ts; struct sembuf ops; if (la) { ts.tv_sec = 0; ts.tv_nsec = 10000000; ops.sem_num = 0; ops.sem_flg = 0; ops.sem_op = -1; if (semtimedop(la->semid, &ops, 1, &ts) < 0) { syslog(LOG_ERR, "semop up failed"); return; } log_enqueue(prio, fmt, ap); ops.sem_op = 1; if (semop(la->semid, &ops, 1) < 0) { syslog(LOG_ERR, "semop down failed"); return; } } else { fprintf(stderr, "%s: ", log_name); vfprintf(stderr, fmt, ap); 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(const char *fmt, ...) { va_list ap; va_start(ap, fmt); dolog(LOG_DEBUG, fmt, ap); va_end(ap); } static void log_flush(void) { struct sembuf ops; if (!la) return; while (!la->empty) { ops.sem_num = 0; ops.sem_flg = 0; ops.sem_op = -1; if (semop(la->semid, &ops, 1) < 0) { syslog(LOG_ERR, "semop up failed"); exit(1); } log_dequeue(la->buff); ops.sem_op = 1; if (semop(la->semid, &ops, 1) < 0) { syslog(LOG_ERR, "semop down failed"); exit(1); } log_syslog(la->buff); } } static void log_sigsegv(void) { log_error("tgtd logger exits abnormally, pid:%d\n", getpid()); log_flush(); closelog(); free_logarea(); exit(1); } int log_init(char *program_name, int size, int daemon, int debug) { is_debug = debug; logdbg(stderr,"enter log_init\n"); log_name = program_name; if (daemon) { struct sigaction sa_old; struct sigaction sa_new; openlog(log_name, 0, LOG_DAEMON); setlogmask (LOG_UPTO (LOG_DEBUG)); if (logarea_init(size)) { syslog(LOG_ERR, "failed to initialize the logger\n"); return 1; } la->active = 1; pid = fork(); if (pid < 0) { syslog(LOG_ERR, "fail to fork the logger\n"); return 1; } else if (pid) { syslog(LOG_WARNING, "tgtd daemon started, pid:%d\n", getpid()); syslog(LOG_WARNING, "tgtd logger started, pid:%d debug:%d\n", pid, is_debug); return 0; } /* flush on daemon's crash */ sa_new.sa_handler = (void*)log_sigsegv; sigemptyset(&sa_new.sa_mask); sa_new.sa_flags = 0; sigaction(SIGSEGV, &sa_new, &sa_old ); prctl(PR_SET_PDEATHSIG, SIGSEGV); while (la->active) { log_flush(); sleep(1); } exit(0); } return 0; } void log_close(void) { if (la) { la->active = 0; waitpid(pid, NULL, 0); log_warning("tgtd logger stopped, pid:%d\n", pid); log_flush(); closelog(); free_logarea(); } } tgt-1.0.85/usr/log.h000066400000000000000000000047421435417276200141700ustar00rootroot00000000000000/* * iSCSI Safe Logging and Tracing Library * * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman * maintained by open-iscsi@googlegroups.com * * circular buffer code based on log.c from dm-multipath project * * heavily based on code from log.c: * Copyright (C) 2002-2003 Ardis Technolgies , * licensed under the terms of the GNU GPL v2.0, * * 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. * * See the file COPYING included with this distribution for more details. */ #ifndef LOG_H #define LOG_H #include #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) union semun { int val; struct semid_ds *buf; unsigned short int *array; struct seminfo *__buf; }; #define LOG_SPACE_SIZE 16384 #define MAX_MSG_SIZE 256 extern int log_daemon; extern int log_level; struct logmsg { short int prio; void *next; char *str; }; struct logarea { int empty; int active; void *head; void *tail; void *start; void *end; char *buff; int semid; union semun semarg; }; extern int log_init(char *progname, int size, int daemon, int debug); extern void log_close(void); extern void dump_logmsg(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(const char *fmt, ...) __attribute__ ((format (printf, 1, 2))); #ifdef NO_LOGGING #define eprintf(fmt, args...) \ do { \ fprintf(stderr, "%s: " fmt, program_name, ##args); \ } while (0) #define dprintf(fmt, args...) \ do { \ if (debug) \ fprintf(stderr, "%s %d: " fmt, \ __FUNCTION__, __LINE__, ##args); \ } while (0) #else #define eprintf(fmt, args...) \ do { \ log_error("%s(%d) " fmt, __FUNCTION__, __LINE__, ##args); \ } while (0) #define dprintf(fmt, args...) \ do { \ if (unlikely(is_debug)) \ log_debug("%s(%d) " fmt, __FUNCTION__, __LINE__, ##args); \ } while (0) #endif #endif /* LOG_H */ tgt-1.0.85/usr/media.h000066400000000000000000000015631435417276200144640ustar00rootroot00000000000000/* * 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; version 2 of the License. * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef _MEDIA_H_ #define _MEDIA_H_ enum c_type { /* Cartridge Types - Ref: smc3r06 - Table 20, page 37 */ CART_UNSPECIFIED, CART_DATA, CART_CLEAN, CART_DIAGNOSTICS, CART_WORM, CART_MICROCODE, }; #endif /* _MEDIA_H_ */ tgt-1.0.85/usr/mgmt.c000066400000000000000000000462031435417276200143440ustar00rootroot00000000000000/* * SCSI target management functions * * Copyright (C) 2005-2007 FUJITA Tomonori * Copyright (C) 2005-2007 Mike Christie * * 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, version 2 of the * License. * * 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 #include #include #include #include "list.h" #include "tgtd.h" #include "log.h" #include "tgtadm.h" #include "driver.h" #include "util.h" enum mgmt_task_state { MTASK_STATE_HDR_RECV, MTASK_STATE_PDU_RECV, MTASK_STATE_HDR_SEND, MTASK_STATE_PDU_SEND, }; struct mgmt_task { enum mgmt_task_state mtask_state; int retry; int done; struct tgtadm_req req; char *req_buf; int req_bsize; struct tgtadm_rsp rsp; struct concat_buf rsp_concat; /* struct tgt_work work; */ }; #define MAX_MGT_BUFSIZE (8*1024) /* limit incoming mgmt request data size */ static int ipc_fd, ipc_lock_fd; char mgmt_path[256]; char mgmt_lock_path[256]; static struct mgmt_task *mtask_alloc(void); static void mtask_free(struct mgmt_task *mtask); static tgtadm_err errno2tgtadm(int err) { if (err >= 0) return TGTADM_SUCCESS; else if (err == -ENOMEM) return TGTADM_NOMEM; else return TGTADM_UNKNOWN_ERR; } static void set_mtask_result(struct mgmt_task *mtask, tgtadm_err adm_err) { if (adm_err == TGTADM_SUCCESS && mtask->rsp_concat.err) adm_err = errno2tgtadm(mtask->rsp_concat.err); mtask->rsp.len = sizeof(mtask->rsp); if (adm_err == TGTADM_SUCCESS) { mtask->rsp.len += mtask->rsp_concat.size; mtask->rsp.err = 0; } else mtask->rsp.err = (uint32_t)adm_err; } static tgtadm_err target_mgmt(int lld_no, struct mgmt_task *mtask) { struct tgtadm_req *req = &mtask->req; tgtadm_err adm_err = TGTADM_INVALID_REQUEST; switch (req->op) { case OP_NEW: adm_err = tgt_target_create(lld_no, req->tid, mtask->req_buf); break; case OP_DELETE: adm_err = tgt_target_destroy(lld_no, req->tid, req->force); break; case OP_BIND: /* FIXME */ if (req->len == sizeof(*req)) adm_err = tgt_bind_host_to_target(req->tid, req->host_no); else { char *p; p = strstr(mtask->req_buf, "initiator-address="); if (p) { p += strlen("initiator-address="); adm_err = acl_add(req->tid, p); if (adm_err != TGTADM_SUCCESS) { eprintf("Failed to bind by address: %s\n", p); break; } } p = strstr(mtask->req_buf, "initiator-name="); if (p) { p += strlen("initiator-name="); adm_err = iqn_acl_add(req->tid, p); if (adm_err != TGTADM_SUCCESS) { eprintf("Failed to bind by name: %s\n", p); break; } } } break; case OP_UNBIND: if (req->len == sizeof(*req)) adm_err = tgt_unbind_host_to_target(req->tid, req->host_no); else { char *p; p = strstr(mtask->req_buf, "initiator-address="); if (p) { p += strlen("initiator-address="); adm_err = acl_del(req->tid, p); if (adm_err != TGTADM_SUCCESS) { eprintf("Failed to unbind by address: %s\n", p); break; } } p = strstr(mtask->req_buf, "initiator-name="); if (p) { p += strlen("initiator-name="); adm_err = iqn_acl_del(req->tid, p); if (adm_err != TGTADM_SUCCESS) { eprintf("Failed to unbind by name: %s\n", p); break; } } } break; case OP_UPDATE: { char *p; adm_err = TGTADM_UNSUPPORTED_OPERATION; p = strchr(mtask->req_buf, '='); if (!p) break; *p++ = '\0'; if (!strcmp(mtask->req_buf, "state")) { adm_err = tgt_set_target_state(req->tid, p); } else if (tgt_drivers[lld_no]->update) adm_err = tgt_drivers[lld_no]->update(req->mode, req->op, req->tid, req->sid, req->lun, req->cid, mtask->req_buf); break; } case OP_SHOW: { concat_buf_init(&mtask->rsp_concat); if (req->tid < 0) adm_err = tgt_target_show_all(&mtask->rsp_concat); else if (tgt_drivers[lld_no]->show) adm_err = tgt_drivers[lld_no]->show(req->mode, req->tid, req->sid, req->cid, req->lun, &mtask->rsp_concat); concat_buf_finish(&mtask->rsp_concat); break; } case OP_STATS: { concat_buf_init(&mtask->rsp_concat); adm_err = tgt_stat_target_by_id(req->tid, &mtask->rsp_concat); concat_buf_finish(&mtask->rsp_concat); break; } default: break; } return adm_err; } static tgtadm_err portal_mgmt(int lld_no, struct mgmt_task *mtask) { struct tgtadm_req *req = &mtask->req; tgtadm_err adm_err = TGTADM_INVALID_REQUEST; switch (req->op) { case OP_SHOW: if (tgt_drivers[lld_no]->show) { concat_buf_init(&mtask->rsp_concat); adm_err = tgt_drivers[lld_no]->show(req->mode, req->tid, req->sid, req->cid, req->lun, &mtask->rsp_concat); concat_buf_finish(&mtask->rsp_concat); } break; case OP_NEW: adm_err = tgt_portal_create(lld_no, mtask->req_buf); break; case OP_DELETE: adm_err = tgt_portal_destroy(lld_no, mtask->req_buf); break; default: break; } return adm_err; } static tgtadm_err device_mgmt(int lld_no, struct mgmt_task *mtask) { struct tgtadm_req *req = &mtask->req; char *params = mtask->req_buf; tgtadm_err adm_err = TGTADM_UNSUPPORTED_OPERATION; switch (req->op) { case OP_NEW: eprintf("sz:%d params:%s\n",mtask->req_bsize,params); adm_err = tgt_device_create(req->tid, req->device_type, req->lun, params, 1); break; case OP_DELETE: adm_err = tgt_device_destroy(req->tid, req->lun, 0); break; case OP_UPDATE: adm_err = tgt_device_update(req->tid, req->lun, params); break; case OP_STATS: concat_buf_init(&mtask->rsp_concat); if (!req->sid) adm_err = tgt_stat_device_by_id(req->tid, req->lun, &mtask->rsp_concat); else if (tgt_drivers[lld_no]->stat) adm_err = tgt_drivers[lld_no]->stat(req->mode, req->tid, req->sid, req->cid, req->lun, &mtask->rsp_concat); concat_buf_finish(&mtask->rsp_concat); break; default: break; } return adm_err; } static tgtadm_err account_mgmt(int lld_no, struct mgmt_task *mtask) { struct tgtadm_req *req = &mtask->req; char *user, *password; tgtadm_err adm_err = TGTADM_UNSUPPORTED_OPERATION; switch (req->op) { case OP_NEW: case OP_DELETE: case OP_BIND: case OP_UNBIND: user = strstr(mtask->req_buf, "user="); if (!user) return TGTADM_INVALID_REQUEST; user += 5; if (req->op == OP_NEW) { password = strchr(user, ','); if (!password) return TGTADM_INVALID_REQUEST; *password++ = '\0'; password += strlen("password="); adm_err = account_add(user, password); } else { if (req->op == OP_DELETE) { adm_err = account_del(user); } else adm_err = account_ctl(req->tid, req->ac_dir, user, req->op == OP_BIND); } break; case OP_SHOW: concat_buf_init(&mtask->rsp_concat); adm_err = account_show(&mtask->rsp_concat); concat_buf_finish(&mtask->rsp_concat); break; default: break; } return adm_err; } static tgtadm_err sys_mgmt(int lld_no, struct mgmt_task *mtask) { struct tgtadm_req *req = &mtask->req; tgtadm_err adm_err = TGTADM_INVALID_REQUEST; switch (req->op) { case OP_UPDATE: if (!strncmp(mtask->req_buf, "debug=", 6)) { if (!strncmp(mtask->req_buf+6, "on", 2)) { is_debug = 1; adm_err = TGTADM_SUCCESS; } else if (!strncmp(mtask->req_buf+6, "off", 3)) { is_debug = 0; adm_err = TGTADM_SUCCESS; } if (adm_err == TGTADM_SUCCESS) eprintf("set debug to: %d\n", is_debug); } else if (tgt_drivers[lld_no]->update) adm_err = tgt_drivers[lld_no]->update(req->mode, req->op, req->tid, req->sid, req->lun, req->cid, mtask->req_buf); break; case OP_SHOW: concat_buf_init(&mtask->rsp_concat); adm_err = system_show(req->mode, &mtask->rsp_concat); if (tgt_drivers[lld_no]->show) adm_err = tgt_drivers[lld_no]->show(req->mode, req->tid, req->sid, req->cid, req->lun, &mtask->rsp_concat); concat_buf_finish(&mtask->rsp_concat); break; case OP_STATS: concat_buf_init(&mtask->rsp_concat); adm_err = tgt_stat_system(&mtask->rsp_concat); concat_buf_finish(&mtask->rsp_concat); break; case OP_DELETE: if (is_system_inactive()) adm_err = TGTADM_SUCCESS; break; default: break; } return adm_err; } static tgtadm_err session_mgmt(int lld_no, struct mgmt_task *mtask) { struct tgtadm_req *req = &mtask->req; int adm_err = TGTADM_INVALID_REQUEST; switch (req->op) { case OP_STATS: if (tgt_drivers[lld_no]->stat) { concat_buf_init(&mtask->rsp_concat); adm_err = tgt_drivers[lld_no]->stat(req->mode, req->tid, req->sid, req->cid, req->lun, &mtask->rsp_concat); concat_buf_finish(&mtask->rsp_concat); } break; default: if (tgt_drivers[lld_no]->update) adm_err = tgt_drivers[lld_no]->update(req->mode, req->op, req->tid, req->sid, req->lun, req->cid, mtask->req_buf); break; } return adm_err; } static tgtadm_err connection_mgmt(int lld_no, struct mgmt_task *mtask) { struct tgtadm_req *req = &mtask->req; tgtadm_err adm_err = TGTADM_INVALID_REQUEST; switch (req->op) { case OP_SHOW: if (tgt_drivers[lld_no]->show) { concat_buf_init(&mtask->rsp_concat); adm_err = tgt_drivers[lld_no]->show(req->mode, req->tid, req->sid, req->cid, req->lun, &mtask->rsp_concat); concat_buf_finish(&mtask->rsp_concat); break; } break; case OP_STATS: if (tgt_drivers[lld_no]->stat) { concat_buf_init(&mtask->rsp_concat); adm_err = tgt_drivers[lld_no]->stat(req->mode, req->tid, req->sid, req->cid, req->lun, &mtask->rsp_concat); concat_buf_finish(&mtask->rsp_concat); } break; default: if (tgt_drivers[lld_no]->update) adm_err = tgt_drivers[lld_no]->update(req->mode, req->op, req->tid, req->sid, req->lun, req->cid, mtask->req_buf); break; } return adm_err; } static tgtadm_err lld_mgmt(int lld_no, struct mgmt_task *mtask) { struct tgtadm_req *req = &mtask->req; tgtadm_err adm_err = TGTADM_INVALID_REQUEST; switch (req->op) { case OP_START: if (tgt_drivers[lld_no]->drv_state != DRIVER_INIT) { if (!lld_init_one(lld_no)) adm_err = TGTADM_SUCCESS; else adm_err = TGTADM_UNKNOWN_ERR; } else adm_err = TGTADM_SUCCESS; break; case OP_STOP: if (tgt_drivers[lld_no]->drv_state == DRIVER_INIT) { if (list_empty(&tgt_drivers[lld_no]->target_list)) { if (tgt_drivers[lld_no]->exit) { tgt_drivers[lld_no]->exit(); tgt_drivers[lld_no]->drv_state = DRIVER_EXIT; } adm_err = TGTADM_SUCCESS; } else adm_err = TGTADM_DRIVER_ACTIVE; } else adm_err = TGTADM_SUCCESS; break; case OP_SHOW: concat_buf_init(&mtask->rsp_concat); adm_err = lld_show(&mtask->rsp_concat); concat_buf_finish(&mtask->rsp_concat); break; default: break; } return adm_err; } static tgtadm_err mtask_execute(struct mgmt_task *mtask) { struct tgtadm_req *req = &mtask->req; int lld_no; tgtadm_err adm_err = TGTADM_INVALID_REQUEST; req->lld[TGT_LLD_NAME_LEN - 1] = '\0'; if (!strlen(req->lld)) lld_no = 0; else { lld_no = get_driver_index(req->lld); if (lld_no < 0 || (tgt_drivers[lld_no]->drv_state != DRIVER_INIT && req->mode != MODE_LLD)) { if (lld_no < 0) eprintf("can't find the driver %s\n", req->lld); else eprintf("driver %s is in state: %s\n", req->lld, driver_state_name(tgt_drivers[lld_no])); return TGTADM_NO_DRIVER; } } dprintf("%d %d %d %d %d %" PRIx64 " %" PRIx64 " %s %d\n", req->len, lld_no, req->mode, req->op, req->tid, req->sid, req->lun, mtask->req_buf, getpid()); switch (req->mode) { case MODE_SYSTEM: adm_err = sys_mgmt(lld_no, mtask); break; case MODE_TARGET: adm_err = target_mgmt(lld_no, mtask); break; case MODE_PORTAL: adm_err = portal_mgmt(lld_no, mtask); break; case MODE_DEVICE: adm_err = device_mgmt(lld_no, mtask); break; case MODE_ACCOUNT: adm_err = account_mgmt(lld_no, mtask); break; case MODE_SESSION: adm_err = session_mgmt(lld_no, mtask); break; case MODE_CONNECTION: adm_err = connection_mgmt(lld_no, mtask); break; case MODE_LLD: adm_err = lld_mgmt(lld_no, mtask); break; default: if (req->op == OP_SHOW && tgt_drivers[lld_no]->show) { concat_buf_init(&mtask->rsp_concat); adm_err = tgt_drivers[lld_no]->show(req->mode, req->tid, req->sid, req->cid, req->lun, &mtask->rsp_concat); concat_buf_finish(&mtask->rsp_concat); } else eprintf("unsupported mode: %d\n", req->mode); break; } return adm_err; } static int ipc_accept(int accept_fd) { struct sockaddr addr; socklen_t len; int fd; len = sizeof(addr); fd = accept(accept_fd, (struct sockaddr *) &addr, &len); if (fd < 0) eprintf("can't accept a new connection, %m\n"); return fd; } static int ipc_perm(int fd) { struct ucred cred; socklen_t len; int err; len = sizeof(cred); err = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, (void *) &cred, &len); if (err) { eprintf("can't get sockopt, %m\n"); return -1; } if (cred.uid != getuid() || cred.gid != getgid()) return -EPERM; return 0; } static struct mgmt_task *mtask_alloc(void) { struct mgmt_task *mtask; mtask = zalloc(sizeof(*mtask)); if (!mtask) { eprintf("can't allocate mtask\n"); return NULL; } mtask->mtask_state = MTASK_STATE_HDR_RECV; dprintf("mtask:%p\n", mtask); return mtask; } static void mtask_free(struct mgmt_task *mtask) { dprintf("mtask:%p\n", mtask); if (mtask->req_buf) free(mtask->req_buf); concat_buf_release(&mtask->rsp_concat); free(mtask); } static int mtask_received(struct mgmt_task *mtask, int fd) { tgtadm_err adm_err; int err; adm_err = mtask_execute(mtask); set_mtask_result(mtask, adm_err); /* whatever the result of mtask execution, a response is sent */ mtask->mtask_state = MTASK_STATE_HDR_SEND; mtask->done = 0; err = tgt_event_modify(fd, EPOLLOUT); if (err) eprintf("failed to modify mgmt task event out\n"); return err; } static void mtask_recv_send_handler(int fd, int events, void *data) { int err, len; char *p; struct mgmt_task *mtask = data; struct tgtadm_req *req = &mtask->req; struct tgtadm_rsp *rsp = &mtask->rsp; switch (mtask->mtask_state) { case MTASK_STATE_HDR_RECV: len = sizeof(*req) - mtask->done; err = read(fd, (char *)req + mtask->done, len); if (err > 0) { mtask->done += err; if (mtask->done == sizeof(*req)) { mtask->req_bsize = req->len - sizeof(*req); if (!mtask->req_bsize) { err = mtask_received(mtask, fd); if (err) goto out; } else { /* the pdu exists */ if (mtask->req_bsize > MAX_MGT_BUFSIZE) { eprintf("mtask buffer len: %d too large\n", mtask->req_bsize); mtask->req_bsize = 0; goto out; } mtask->req_buf = zalloc(mtask->req_bsize); if (!mtask->req_buf) { eprintf("can't allocate mtask buffer len: %d\n", mtask->req_bsize); mtask->req_bsize = 0; goto out; } mtask->mtask_state = MTASK_STATE_PDU_RECV; mtask->done = 0; } } } else if (errno != EAGAIN) goto out; break; case MTASK_STATE_PDU_RECV: len = mtask->req_bsize - mtask->done; err = read(fd, mtask->req_buf + mtask->done, len); if (err > 0) { mtask->done += err; if (mtask->done == mtask->req_bsize) { err = mtask_received(mtask, fd); if (err) goto out; } } else if (errno != EAGAIN) goto out; break; case MTASK_STATE_HDR_SEND: p = (char *)rsp + mtask->done; len = sizeof(*rsp) - mtask->done; err = write(fd, p, len); if (err > 0) { mtask->done += err; if (mtask->done == sizeof(*rsp)) { if (rsp->len == sizeof(*rsp)) goto out; mtask->done = 0; mtask->mtask_state = MTASK_STATE_PDU_SEND; } } else if (errno != EAGAIN) goto out; break; case MTASK_STATE_PDU_SEND: err = concat_write(&mtask->rsp_concat, fd, mtask->done); if (err >= 0) { mtask->done += err; if (mtask->done == (rsp->len - sizeof(*rsp))) goto out; } else if (errno != EAGAIN) goto out; break; default: eprintf("unknown state %d\n", mtask->mtask_state); } return; out: if (req->mode == MODE_SYSTEM && req->op == OP_DELETE && !rsp->err) system_active = 0; tgt_event_del(fd); close(fd); mtask_free(mtask); } static void mgmt_event_handler(int accept_fd, int events, void *data) { int fd, err; struct mgmt_task *mtask; fd = ipc_accept(accept_fd); if (fd < 0) { eprintf("failed to accept a socket\n"); return; } err = ipc_perm(fd); if (err < 0) { eprintf("permission error\n"); goto out; } err = set_non_blocking(fd); if (err) { eprintf("failed to set a socket non-blocking\n"); goto out; } mtask = mtask_alloc(); if (!mtask) goto out; err = tgt_event_add(fd, EPOLLIN, mtask_recv_send_handler, mtask); if (err) { eprintf("failed to add a socket to epoll %d\n", fd); mtask_free(mtask); goto out; } return; out: if (fd > 0) close(fd); return; } int ipc_init(void) { extern short control_port; int fd = 0, err; struct sockaddr_un addr; struct stat st = {0}; char *path; if ((path = getenv("TGT_IPC_SOCKET")) == NULL) { path = TGT_IPC_NAMESPACE; if (stat(TGT_IPC_DIR, &st) == -1) mkdir(TGT_IPC_DIR, 0755); } sprintf(mgmt_lock_path, "%s.%d.lock", path, control_port); ipc_lock_fd = open(mgmt_lock_path, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (ipc_lock_fd < 0) { eprintf("failed to open lock file for management IPC\n"); return -1; } if (lockf(ipc_lock_fd, F_TLOCK, 1) < 0) { if (errno == EACCES || errno == EAGAIN) eprintf("another tgtd is using %s\n", mgmt_lock_path); else eprintf("unable to get lock of management IPC: %s"\ " (errno: %m)\n", mgmt_lock_path); goto close_lock_fd; } fd = socket(AF_LOCAL, SOCK_STREAM, 0); if (fd < 0) { eprintf("can't open a socket, %m\n"); goto close_lock_fd; } snprintf(mgmt_path, sizeof(mgmt_path) - 1, "%s.%d", path, control_port); if (strlen(mgmt_path) > (sizeof(addr.sun_path) - 1)) { eprintf("managment path too long: %s\n", mgmt_path); goto close_ipc_fd; } unlink(mgmt_path); memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_LOCAL; /* no need for strncpy because we already checked length */ strcpy(addr.sun_path, mgmt_path); err = bind(fd, (struct sockaddr *) &addr, sizeof(addr)); if (err) { eprintf("can't bind a socket, %m\n"); goto close_ipc_fd; } err = listen(fd, 32); if (err) { eprintf("can't listen a socket, %m\n"); goto close_ipc_fd; } err = tgt_event_add(fd, EPOLLIN, mgmt_event_handler, NULL); if (err) goto close_ipc_fd; ipc_fd = fd; return 0; close_ipc_fd: close(fd); close_lock_fd: close(ipc_lock_fd); return -1; } void ipc_exit(void) { tgt_event_del(ipc_fd); close(ipc_fd); close(ipc_lock_fd); } tgt-1.0.85/usr/mmc.c000066400000000000000000001534251435417276200141610ustar00rootroot00000000000000/* * SCSI multimedia command processing * * Copyright (C) 2007 FUJITA Tomonori * Copyright (C) 2007 Mike Christie * Copyright (C) 2008 Ronnie Sahlberg * * This code is based on Ardis's iSCSI implementation. * http://www.ardistech.com/iscsi/ * Copyright (C) 2002-2003 Ardis Technolgies * * This code is also based on Ming's mmc work for IET. * Copyright (C) 2005-2007 Ming Zhang * * 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, version 2 of the * License. * * 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 "list.h" #include "util.h" #include "tgtd.h" #include "target.h" #include "tgtadm_error.h" #include "driver.h" #include "scsi.h" #include "spc.h" #include "tgtadm_error.h" #define MMC_BLK_SHIFT 11 #define PROFILE_NO_PROFILE 0x0000 #define PROFILE_DVD_ROM 0x0010 #define PROFILE_DVD_PLUS_R 0x001b struct mmc_info { int current_profile; uint64_t reserve_track_len; }; static int mmc_rw(int host_no, struct scsi_cmd *cmd) { struct mmc_info *mmc = dtype_priv(cmd->dev); int ret; uint64_t end_offset; uint64_t offset, length; if (mmc->current_profile == PROFILE_NO_PROFILE) { scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_MEDIUM_NOT_PRESENT); return SAM_STAT_CHECK_CONDITION; } switch (mmc->current_profile) { case PROFILE_DVD_ROM: switch (cmd->scb[0]) { case WRITE_6: case WRITE_10: case WRITE_12: case WRITE_16: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, ILLEGAL_REQUEST, ASC_INCOMPATIBLE_FORMAT); return SAM_STAT_CHECK_CONDITION; } break; case PROFILE_DVD_PLUS_R: switch (cmd->scb[0]) { case READ_6: case READ_10: case READ_12: case READ_16: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, ILLEGAL_REQUEST, ASC_LBA_OUT_OF_RANGE); return SAM_STAT_CHECK_CONDITION; } break; } offset = scsi_rw_offset(cmd->scb); cmd->offset = (offset << MMC_BLK_SHIFT); /* update the size of the device */ length = scsi_rw_count(cmd->scb); end_offset = cmd->offset + (length << MMC_BLK_SHIFT); if (end_offset > cmd->dev->size) cmd->dev->size = end_offset; ret = cmd->dev->bst->bs_cmd_submit(cmd); if (ret) { cmd->offset = 0; if (scsi_get_data_dir(cmd) == DATA_WRITE) scsi_set_out_resid_by_actual(cmd, 0); else scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, ILLEGAL_REQUEST, ASC_LUN_NOT_SUPPORTED); return SAM_STAT_CHECK_CONDITION; } else { if ((mmc->current_profile == PROFILE_DVD_PLUS_R) && (mmc->reserve_track_len == (offset + length))) { /* once we close the track it becomes a DVD_ROM */ mmc->current_profile = PROFILE_DVD_ROM; } return SAM_STAT_GOOD; } return 0; } static int mmc_read_capacity(int host_no, struct scsi_cmd *cmd) { struct mmc_info *mmc = dtype_priv(cmd->dev); uint64_t size; uint32_t *data; if (mmc->current_profile == PROFILE_NO_PROFILE) { scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_MEDIUM_NOT_PRESENT); return SAM_STAT_CHECK_CONDITION; } if (scsi_get_in_length(cmd) < 8) goto overflow; data = scsi_get_in_buffer(cmd); size = cmd->dev->size >> MMC_BLK_SHIFT; if (size) data[0] = (size >> 32) ? __cpu_to_be32(0xffffffff) : __cpu_to_be32(size - 1); else data[0] = 0; /* A blank DVD */ data[1] = __cpu_to_be32(1U << MMC_BLK_SHIFT); overflow: scsi_set_in_resid_by_actual(cmd, 8); return SAM_STAT_GOOD; } static int mmc_read_toc(int host_no, struct scsi_cmd *cmd) { struct mmc_info *mmc = dtype_priv(cmd->dev); uint8_t *data; uint8_t buf[32]; int toc_time, toc_format, toc_track; unsigned long tsa; if (!cmd->dev->attrs.online) { scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_MEDIUM_NOT_PRESENT); return SAM_STAT_CHECK_CONDITION; } if (mmc->current_profile == PROFILE_NO_PROFILE) { /* a blank disk has no tracks */ scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_INVALID_FIELD_IN_CDB); return SAM_STAT_CHECK_CONDITION; } toc_time = cmd->scb[1] & 0x02; toc_format = cmd->scb[2] & 0x0f; toc_track = cmd->scb[6]; memset(buf, 0, sizeof(buf)); data = buf; switch (toc_format) { case 0: /* formatted toc */ if (toc_track != 0 && toc_track != 1) { /* we only do single session data disks so only the first track (track 1) exists. Since this command returns the data for all tracks equal to toc_track or higher, we must allow either track 0 or track 1 as valid and both will return the data for track 1. */ scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_INVALID_FIELD_IN_CDB); return SAM_STAT_CHECK_CONDITION; } if (toc_time) tsa = 0x00ff3b4a; else tsa = cmd->dev->size >> MMC_BLK_SHIFT; /* lba */ /* size of return data */ data[0] = 0; data[1] = 0x12; data[2] = 1; /* first track */ data[3] = 1; /* last track */ /* data track */ data[4] = 0; /* reserved */ data[5] = 0x14; data[6] = 1; /* track number */ data[7] = 0; /* reserved */ data[8] = 0; /* track start address : 0 */ data[9] = 0; if (toc_time) data[10] = 2; /* time 00:00:02:00 */ else data[10] = 0; data[11] = 0; /* leadout track */ data[12] = 0; /* reserved */ data[13] = 0x14; data[14] = 0xaa; /* track number */ data[15] = 0; /* reserved */ data[16] = (tsa >> 24) & 0xff;/* track start address */ data[17] = (tsa >> 16) & 0xff; data[18] = (tsa >> 8) & 0xff; data[19] = tsa & 0xff; break; case 1: /* multi session info */ /* size of return data */ data[0] = 0; data[1] = 0x0a; data[2] = 1; /* first session */ data[3] = 1; /* last session */ /* data track */ data[4] = 0; /* reserved */ data[5] = 0x14; data[6] = 1; /* track number */ data[7] = 0; /* reserved */ data[8] = 0; /* track start address : 0 */ data[9] = 0; if (toc_time) data[10] = 2; /* time 00:00:02:00 */ else data[10] = 0; data[11] = 0; break; case 2: /* raw toc */ case 3: /* pma */ case 4: /* atip */ case 5: /* cd-text */ /* not implemented yet */ default: eprintf("read_toc: format %x not implemented\n", toc_format); scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_INVALID_FIELD_IN_CDB); return SAM_STAT_CHECK_CONDITION; } memcpy(scsi_get_in_buffer(cmd), data, min(scsi_get_in_length(cmd), (uint32_t) sizeof(buf))); scsi_set_in_resid_by_actual(cmd, data[1]); return SAM_STAT_GOOD; } static int mmc_close_track(int host_no, struct scsi_cmd *cmd) { struct mmc_info *mmc = dtype_priv(cmd->dev); /* once we close the track it becomes a DVD_ROM */ mmc->current_profile = PROFILE_DVD_ROM; return SAM_STAT_GOOD; } static int mmc_read_disc_information(int host_no, struct scsi_cmd *cmd) { struct mmc_info *mmc = dtype_priv(cmd->dev); unsigned char buf[34]; if (mmc->current_profile == PROFILE_NO_PROFILE) { scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_MEDIUM_NOT_PRESENT); return SAM_STAT_CHECK_CONDITION; } memset(buf, 0, sizeof(buf)); switch (mmc->current_profile) { case PROFILE_DVD_ROM: /* disk information length */ buf[0] = 0x00; buf[1] = 0x20; /* * erasable:0 state of last session:complete disc * status:finalized */ buf[2] = 0x0e; /* number of first track on disk */ buf[3] = 1; /* number of sessions LSB */ buf[4] = 1; /* first track in last session LSB */ buf[5] = 1; /* last track in last session LSB */ buf[6] = 1; /* did_v:0 dbc_v:0 uru:0 dac_v:0 dbit:0 bg format:0 */ buf[7] = 0x00; /* disc type */ buf[8] = 0; /* number of sessions MSB */ buf[9] = 0; /* first track in last session MSB */ buf[10] = 0; /* last track in last session MSB */ buf[11] = 0; /* disc identification */ buf[12] = 0; buf[13] = 0; buf[14] = 0; buf[15] = 0; /* last session lead-in start address */ buf[16] = 0; buf[17] = 0; buf[18] = 0; buf[19] = 0; /* last possible lead-out start address */ buf[20] = 0; buf[21] = 0; buf[22] = 0; buf[23] = 0; /* disc bar code */ buf[24] = 0; buf[25] = 0; buf[26] = 0; buf[27] = 0; buf[28] = 0; buf[29] = 0; buf[30] = 0; buf[31] = 0; /* disc application code */ buf[32] = 0; /* number of opc tables */ buf[33] = 0; break; case PROFILE_DVD_PLUS_R: /* disk information length */ buf[0] = 0x00; buf[1] = 0x20; /* erasable:0 state of last session:empty disc status:empty */ buf[2] = 0; /* number of first track on disk */ buf[3] = 1; /* number of sessions LSB */ buf[4] = 1; /* first track in last session LSB */ buf[5] = 1; /* last track in last session LSB */ buf[6] = 1; /* did_v:0 dbc_v:0 uru:0 dac_v:1 dbit:0 bg format:0 */ buf[7] = 0x10; /* disc type */ buf[8] = 0; /* number of sessions MSB */ buf[9] = 0; /* first track in last session MSB */ buf[10] = 0; /* last track in last session MSB */ buf[11] = 0; /* disc identification */ buf[12] = 0; buf[13] = 0; buf[14] = 0; buf[15] = 0; /* last session lead-in start address */ buf[16] = 0; buf[17] = 0; buf[18] = 0; buf[19] = 0; /* last possible lead-out start address */ buf[20] = 0x00; buf[21] = 0x23; buf[22] = 0x05; buf[23] = 0x40; /* disc bar code */ buf[24] = 0; buf[25] = 0; buf[26] = 0; buf[27] = 0; buf[28] = 0; buf[29] = 0; buf[30] = 0; buf[31] = 0; /* disc application code */ buf[32] = 0; /* number of opc tables */ buf[33] = 0; break; default: /* we do not understand/support this command for this profile */ scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_INVALID_FIELD_IN_CDB); return SAM_STAT_CHECK_CONDITION; } memcpy(scsi_get_in_buffer(cmd), buf, min_t(uint32_t, scsi_get_in_length(cmd), sizeof(buf))); return SAM_STAT_GOOD; } static void profile_dvd_rom(struct scsi_cmd *cmd, char *data) { struct mmc_info *mmc = dtype_priv(cmd->dev); /* profile number */ *data++ = 0; *data++ = 0x10; /* current ? */ if (mmc->current_profile == PROFILE_DVD_ROM) *data++ = 0x01; else *data++ = 0; /* reserved */ *data++ = 0; } static void profile_dvd_plus_r(struct scsi_cmd *cmd, char *data) { struct mmc_info *mmc = dtype_priv(cmd->dev); /* profile number */ *data++ = 0; *data++ = 0x1b; /* current ? */ if (mmc->current_profile == PROFILE_DVD_PLUS_R) *data++ = 0x01; else *data++ = 0; /* reserved */ *data++ = 0; } struct profile_descriptor { int profile; void (*func)(struct scsi_cmd *cmd, char *data); }; struct profile_descriptor profiles[] = { {PROFILE_DVD_ROM, profile_dvd_rom}, {PROFILE_DVD_PLUS_R, profile_dvd_plus_r}, {0, NULL} }; /* * these features are mandatory for profile DVD_ROM * FEATURE_PROFILE_LIST * FEATURE_CORE * FEATURE_MORHPING * FEATURE_REMOVABLE_MEDIUM * FEATURE_RANDOM_READABLE * FEATURE_DVD_READ * FEATURE_POWER_MANAGEMENT * FEATURE_TIMEOUT * FEATURE_REAL_TIME_STREAMING * * these features are mandatory for profile DVD+R * FEATURE_PROFILE_LIST * FEATURE_CORE * FEATURE_MORHPING * FEATURE_REMOVABLE_MEDIUM * FEATURE_RANDOM_READABLE * FEATURE_DVD_READ * FEATURE_DVD_PLUS_R * FEATURE_POWER_MANAGEMENT * FEATURE_TIMEOUT * FEATURE_REAL_TIME_STREAMING * FEATURE_DCBS * * additional features * FEATURE_MULIT_READ * FEATURE_LUN_SERIAL_NO */ static char *feature_profile_list(struct scsi_cmd *cmd, char *data, int only_current) { struct profile_descriptor *p; char *additional; /* feature code */ *data++ = 0; *data++ = 0; /* version 0 always persistent, always current */ *data++ = 0x03; /* additional length start at 0*/ additional = data++; *additional = 0; /* all all profiles we support */ for (p = profiles; p->func; p++) { p->func(cmd, data); *additional = (*additional) + 4; data += 4; } return data; } static char *feature_core(struct scsi_cmd *cmd, char *data, int only_current) { /* feature code */ *data++ = 0; *data++ = 0x01; /* version 0 always persistent, always current */ *data++ = 0x03; /* additional length */ *data++ = 4; /* physical interface standard : atapi*/ *data++ = 0; *data++ = 0; *data++ = 0; *data++ = 2; return data; } static char *feature_morphing(struct scsi_cmd *cmd, char *data, int only_current) { /* feature code */ *data++ = 0; *data++ = 0x02; /* version 0 always persistent, always current */ *data++ = 0x03; /* additional length */ *data++ = 4; /* dont support ocevent or async */ *data++ = 0; *data++ = 0; *data++ = 0; *data++ = 0; return data; } static char *feature_removable_medium(struct scsi_cmd *cmd, char *data, int only_current) { /* feature code */ *data++ = 0; *data++ = 0x03; /* version 0 always persistent, always current */ *data++ = 0x03; /* additional length */ *data++ = 4; /* loading mechanism:tray * ejectable through STARTSTOPUNIT loej * vnt : PREVENTALLOWMEDIUMREMOVAL supported * lock : medium can be locked */ *data++ = 0x29; /* reserved */ *data++ = 0; *data++ = 0; *data++ = 0; return data; } static char *feature_random_readable(struct scsi_cmd *cmd, char *data, int only_current) { struct mmc_info *mmc = dtype_priv(cmd->dev); int is_current; /* this feature is only current in DVD_ROM */ switch (mmc->current_profile) { case PROFILE_DVD_ROM: is_current = 1; break; default: is_current = 0; } if (only_current && !is_current) return data; /* feature code */ *data++ = 0; *data++ = 0x10; /* version 0 , never persistent */ *data = 0; if (is_current) *data |= 0x01; data++; /* additional length */ *data++ = 8; /* logical block size */ *data++ = 0; *data++ = 0; *data++ = 8; *data++ = 0; /* blocking is always 0x10 for dvd devices */ *data++ = 0; *data++ = 0x10; /* pp is supported */ *data++ = 0x01; /* reserved */ *data++ = 0; return data; } static char *feature_dvd_read(struct scsi_cmd *cmd, char *data, int only_current) { struct mmc_info *mmc = dtype_priv(cmd->dev); int is_current; /* this feature is only current in DVD_ROM */ switch (mmc->current_profile) { case PROFILE_DVD_ROM: is_current = 1; break; default: is_current = 0; } if (only_current && !is_current) return data; /* feature code */ *data++ = 0; *data++ = 0x1f; /* version 0, never persistent */ *data = 0; if (is_current) *data |= 0x01; data++; /* additional length */ *data++ = 0; return data; } static char *feature_power_management(struct scsi_cmd *cmd, char *data, int only_current) { /* feature code */ *data++ = 0x01; *data++ = 0x00; /* version 0 always persistent always current */ *data++ = 0x03; /* additional length */ *data++ = 0; return data; } static char *feature_timeout(struct scsi_cmd *cmd, char *data, int only_current) { /* feature code */ *data++ = 0x01; *data++ = 0x05; /* version 0 always persistent always current */ *data++ = 0x03; /* additional length */ *data++ = 0; return data; } static char *feature_real_time_streaming(struct scsi_cmd *cmd, char *data, int only_current) { struct mmc_info *mmc = dtype_priv(cmd->dev); int is_current; /* this feature is only current in DVD_ROM */ switch (mmc->current_profile) { case PROFILE_DVD_ROM: is_current = 1; break; default: is_current = 0; } if (only_current && !is_current) return data; /* feature code */ *data++ = 0x01; *data++ = 0x07; /* version 3 */ *data = 0x0c; if (is_current) *data |= 0x01; data++; /* additional length */ *data++ = 4; /* flags */ *data++ = 0x1f; /* reserved */ *data++ = 0; *data++ = 0; *data++ = 0; return data; } static char *feature_dvd_plus_r(struct scsi_cmd *cmd, char *data, int only_current) { struct mmc_info *mmc = dtype_priv(cmd->dev); int is_current; /* this feature is only current in DVD+R */ switch (mmc->current_profile) { case PROFILE_DVD_PLUS_R: is_current = 1; break; default: is_current = 0; } if (only_current && !is_current) return data; /* feature code */ *data++ = 0; *data++ = 0x2b; /* version 0 */ *data = 0; if (is_current) *data |= 0x01; data++; /* additional length */ *data++ = 4; /* we support WRITE of DVD+R when profile is DVD+R*/ *data++ = 0x01; /* reserved */ *data++ = 0; *data++ = 0; *data++ = 0; return data; } static char *feature_lun_serial_no(struct scsi_cmd *cmd, char *data, int only_current) { struct lu_phy_attr *attrs; struct vpd *vpd_pg; /* feature code */ *data++ = 0x01; *data++ = 0x08; /* version 0 */ *data++ = 0x03; /* additional length */ *data++ = 8; /* serial number */ attrs = &cmd->dev->attrs; vpd_pg = attrs->lu_vpd[PCODE_OFFSET(0x80)]; if (vpd_pg->size == 8) memcpy(data, vpd_pg->data, 8); data += 8; return data; } static char *feature_multi_read(struct scsi_cmd *cmd, char *data, int only_current) { /* feature code */ *data++ = 0; *data++ = 0x1d; /* version 0 */ *data++ = 0x00; /* additional length */ *data++ = 0; return data; } static char *feature_dcbs(struct scsi_cmd *cmd, char *data, int only_current) { struct mmc_info *mmc = dtype_priv(cmd->dev); int is_current; /* this feature is only current in DVD+R */ switch (mmc->current_profile) { case PROFILE_DVD_PLUS_R: is_current = 1; break; default: is_current = 0; } if (only_current && !is_current) return data; /* feature code */ *data++ = 0x01; *data++ = 0x0a; /* version 0 */ *data = 0; if (is_current) *data |= 0x01; data++; /* additional length : 12 */ *data++ = 0x0c; /* DCB entry 0 */ *data++ = 0x46; *data++ = 0x44; *data++ = 0x43; *data++ = 0x00; /* DCB entry 1 */ *data++ = 0x53; *data++ = 0x44; *data++ = 0x43; *data++ = 0x00; /* DCB entry 2 */ *data++ = 0x54; *data++ = 0x4f; *data++ = 0x43; *data++ = 0x00; return data; } #define FEATURE_PROFILE_LIST 0x0000 #define FEATURE_CORE 0x0001 #define FEATURE_MORHPING 0x0002 #define FEATURE_REMOVABLE_MEDIUM 0x0003 #define FEATURE_RANDOM_READABLE 0x0010 #define FEATURE_MULTI_READ 0x001d #define FEATURE_DVD_READ 0x001f #define FEATURE_DVD_PLUS_R 0x002b #define FEATURE_POWER_MANAGEMENT 0x0100 #define FEATURE_TIMEOUT 0x0105 #define FEATURE_REAL_TIME_STREAMING 0x0107 #define FEATURE_LUN_SERIAL_NO 0x0108 #define FEATURE_DCBS 0x010a struct feature_descriptor { int feature; char *(*func)(struct scsi_cmd *cmd, char *data, int only_current); }; /* this array MUST list the features in numerical order */ struct feature_descriptor features[] = { {FEATURE_PROFILE_LIST, feature_profile_list}, {FEATURE_CORE, feature_core}, {FEATURE_MORHPING, feature_morphing}, {FEATURE_REMOVABLE_MEDIUM, feature_removable_medium}, {FEATURE_RANDOM_READABLE, feature_random_readable}, {FEATURE_MULTI_READ, feature_multi_read}, {FEATURE_DVD_READ, feature_dvd_read}, {FEATURE_DVD_PLUS_R, feature_dvd_plus_r}, {FEATURE_POWER_MANAGEMENT, feature_power_management}, {FEATURE_TIMEOUT, feature_timeout}, {FEATURE_REAL_TIME_STREAMING, feature_real_time_streaming}, {FEATURE_LUN_SERIAL_NO, feature_lun_serial_no}, {FEATURE_DCBS, feature_dcbs}, {0, NULL}, }; static int mmc_get_configuration(int host_no, struct scsi_cmd *cmd) { struct mmc_info *mmc = dtype_priv(cmd->dev); char *data; char buf[1024]; int rt, start; struct feature_descriptor *f; int tmp; rt = cmd->scb[1] & 0x03; start = cmd->scb[2]; start = (start << 8) | cmd->scb[3]; memset(buf, 0, sizeof(buf)); data = buf; /* skip past size */ data += 4; /* reserved */ *data++ = 0; *data++ = 0; /* current profile */ *data++ = (mmc->current_profile >> 8) & 0xff; *data++ = mmc->current_profile & 0xff; /* add the features */ for (f = features; f->func; f++) { /* only return features >= the start feature */ if (f->feature < start) continue; /* if rt==2 we skip all other features except start */ if (rt == 2 && f->feature != start) continue; data = f->func(cmd, data, rt == 1); } tmp = data-buf; tmp -= 4; buf[0] = (tmp >> 24) & 0xff; buf[1] = (tmp >> 16) & 0xff; buf[2] = (tmp >> 8) & 0xff; buf[3] = tmp & 0xff; tmp = data-buf; memcpy(scsi_get_in_buffer(cmd), buf, min_t(uint32_t, scsi_get_in_length(cmd), sizeof(buf))); /* dont report overflow/underflow for GET CONFIGURATION */ return SAM_STAT_GOOD; } static unsigned char *track_type_lba(struct scsi_cmd *cmd, unsigned char *data, unsigned int lba) { struct mmc_info *mmc = dtype_priv(cmd->dev); unsigned long tmp; switch (mmc->current_profile) { case PROFILE_DVD_PLUS_R: /* track number LSB */ *data++ = 1; /* session number LSB */ *data++ = 1; /* reserved */ *data++ = 0; /* damage:0 copy:0 track_mode:DVD+R */ *data++ = 0x07; /* rt:0 blank:1 packet/inc:0 fp:0 data mode:1 */ *data++ = 0x41; /* lra_v:0 nwa_v:1 */ *data++ = 0x01; /* track start address */ *data++ = 0; *data++ = 0; *data++ = 0; *data++ = 0; /* next writeable address */ *data++ = 0; *data++ = 0; *data++ = 0; *data++ = 0; /* free blocks */ *data++ = 0x00; *data++ = 0x23; *data++ = 0x05; *data++ = 0x40; /* blocking factor */ *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x10; /* track size */ *data++ = 0x00; *data++ = 0x23; *data++ = 0x05; *data++ = 0x40; /* last recorded address */ *data++ = 0; *data++ = 0; *data++ = 0; *data++ = 0; /* track number MSB */ *data++ = 0; /* session number MSB */ *data++ = 0; /* reserved */ data += 2; /* read compat lba */ *data++ = 0x00; *data++ = 0x04; *data++ = 0x0d; *data++ = 0x0e; return data; case PROFILE_DVD_ROM: /* track number LSB */ *data++ = 1; /* session number LSB */ *data++ = 1; /* reserved */ *data++ = 0; /* damage:0 copy:0 track_mode:other media */ *data++ = 0x04; /* rt:0 blank:0 packet/inc:0 fp:0 data mode:1 */ *data++ = 0x01; /* lra_v:1 nwa_v:0 */ *data++ = 0x02; /* track start address */ *data++ = 0; *data++ = 0; *data++ = 0; *data++ = 0; /* next writeable address */ *data++ = 0; *data++ = 0; *data++ = 0; *data++ = 0; /* free blocks */ *data++ = 0; *data++ = 0; *data++ = 0; *data++ = 0; /* blocking factor */ *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x10; /* track size */ tmp = cmd->dev->size >> MMC_BLK_SHIFT; *data++ = (tmp >> 24) & 0xff; *data++ = (tmp >> 16) & 0xff; *data++ = (tmp >> 8) & 0xff; *data++ = tmp & 0xff; /* last recorded address */ tmp--; /* one less */ *data++ = (tmp >> 24) & 0xff; *data++ = (tmp >> 16) & 0xff; *data++ = (tmp >> 8) & 0xff; *data++ = tmp & 0xff; /* track number MSB */ *data++ = 0; /* session number MSB */ *data++ = 0; /* reserved */ data += 2; return data; } /* we do not understand/support this profile */ scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_INVALID_FIELD_IN_CDB); return NULL; } static unsigned char *track_type_track(struct scsi_cmd *cmd, unsigned char *data, unsigned int lba) { struct mmc_info *mmc = dtype_priv(cmd->dev); unsigned long tmp; switch (mmc->current_profile) { case PROFILE_DVD_PLUS_R: if (!lba) { scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_INVALID_FIELD_IN_CDB); return NULL; } /* track number LSB */ *data++ = 1; /* session number LSB */ *data++ = 1; /* reserved */ *data++ = 0; /* damage:0 copy:0 track_mode:DVD+R */ *data++ = 0x07; /* rt:0 blank:1 packet/inc:0 fp:0 data mode:1 */ *data++ = 0x41; /* lra_v:0 nwa_v:1 */ *data++ = 0x01; /* track start address */ *data++ = 0; *data++ = 0; *data++ = 0; *data++ = 0; /* next writeable address */ *data++ = 0; *data++ = 0; *data++ = 0; *data++ = 0; /* free blocks */ *data++ = 0x00; *data++ = 0x23; *data++ = 0x05; *data++ = 0x40; /* blocking factor */ *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x10; /* track size */ *data++ = 0x00; *data++ = 0x23; *data++ = 0x05; *data++ = 0x40; /* last recorded address */ *data++ = 0; *data++ = 0; *data++ = 0; *data++ = 0; /* track number MSB */ *data++ = 0; /* session number MSB */ *data++ = 0; /* reserved */ data += 2; /* read compat lba */ *data++ = 0x00; *data++ = 0x04; *data++ = 0x0d; *data++ = 0x0e; return data; case PROFILE_DVD_ROM: /* we only have one track */ if (lba != 1) { scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_INVALID_FIELD_IN_CDB); return NULL; } /* track number LSB */ *data++ = 1; /* session number LSB */ *data++ = 1; /* reserved */ *data++ = 0; /* damage:0 copy:0 track_mode:other media */ *data++ = 0x04; /* rt:0 blank:0 packet/inc:0 fp:0 data mode:1 */ *data++ = 0x01; /* lra_v:1 nwa_v:0 */ *data++ = 0x02; /* track start address */ *data++ = 0; *data++ = 0; *data++ = 0; *data++ = 0; /* next writeable address */ *data++ = 0; *data++ = 0; *data++ = 0; *data++ = 0; /* free blocks */ *data++ = 0; *data++ = 0; *data++ = 0; *data++ = 0; /* blocking factor */ *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x10; /* track size */ tmp = cmd->dev->size >> MMC_BLK_SHIFT; *data++ = (tmp >> 24) & 0xff; *data++ = (tmp >> 16) & 0xff; *data++ = (tmp >> 8) & 0xff; *data++ = tmp & 0xff; /* last recorded address */ tmp--; /* one less */ *data++ = (tmp >> 24) & 0xff; *data++ = (tmp >> 16) & 0xff; *data++ = (tmp >> 8) & 0xff; *data++ = tmp & 0xff; /* track number MSB */ *data++ = 0; /* session number MSB */ *data++ = 0; /* reserved */ data += 2; return data; } /* we do not understand/support this profile */ scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_INVALID_FIELD_IN_CDB); return NULL; } #define TRACK_INFO_LBA 0 #define TRACK_INFO_TRACK 1 struct track_type { int type; unsigned char *(*func)(struct scsi_cmd *cmd, unsigned char *data, unsigned int lba); }; struct track_type track_types[] = { {TRACK_INFO_LBA, track_type_lba}, {TRACK_INFO_TRACK, track_type_track}, {0, NULL} }; static int mmc_read_track_information(int host_no, struct scsi_cmd *cmd) { struct mmc_info *mmc = dtype_priv(cmd->dev); struct track_type *t; unsigned char *data; unsigned char buf[4096]; int type; int lba; if (mmc->current_profile == PROFILE_NO_PROFILE) { scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_MEDIUM_NOT_PRESENT); return SAM_STAT_CHECK_CONDITION; } type = cmd->scb[1]&0x03; lba = cmd->scb[2]; lba = (lba<<8) | cmd->scb[3]; lba = (lba<<8) | cmd->scb[4]; lba = (lba<<8) | cmd->scb[5]; memset(buf, 0, sizeof(buf)); data = &buf[2]; for (t = track_types; t->func; t++) { int tmp; if (t->type != type) continue; data = t->func(cmd, data, lba); if (!data) return SAM_STAT_CHECK_CONDITION; tmp = data-&buf[2]; buf[0] = (tmp >> 8) & 0xff; buf[1] = tmp & 0xff; memcpy(scsi_get_in_buffer(cmd), buf, min_t(uint32_t, scsi_get_in_length(cmd), sizeof(buf))); return SAM_STAT_GOOD; } /* we do not understand this track type */ scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_INVALID_FIELD_IN_CDB); return SAM_STAT_CHECK_CONDITION; } static int mmc_read_buffer_capacity(int host_no, struct scsi_cmd *cmd) { struct mmc_info *mmc = dtype_priv(cmd->dev); int blocks; unsigned char buf[12]; long tmp; memset(buf, 0, sizeof(buf)); blocks = cmd->scb[1]&0x01; switch (mmc->current_profile) { case PROFILE_NO_PROFILE: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_MEDIUM_NOT_PRESENT); return SAM_STAT_CHECK_CONDITION; case PROFILE_DVD_ROM: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, ILLEGAL_REQUEST, ASC_IMCOMPATIBLE_FORMAT); return SAM_STAT_CHECK_CONDITION; case PROFILE_DVD_PLUS_R: /* data length */ buf[0] = 0x00; buf[1] = 0x0a; /* 4096 blocks */ tmp = 0x1000; if (!blocks) { /* convert to bytes */ tmp <<= MMC_BLK_SHIFT; } /* length of buffer */ buf[4] = (tmp >> 24) & 0xff; buf[5] = (tmp >> 16) & 0xff; buf[6] = (tmp >> 8) & 0xff; buf[7] = tmp & 0xff; /* available length of buffer (always half) */ tmp = tmp >> 1; buf[8] = (tmp >> 24) & 0xff; buf[9] = (tmp >> 16) & 0xff; buf[10] = (tmp >> 8) & 0xff; buf[11] = tmp & 0xff; memcpy(scsi_get_in_buffer(cmd), buf, min(scsi_get_in_length(cmd), (uint32_t) sizeof(buf))); scsi_set_in_resid_by_actual(cmd, buf[1] + 2); return SAM_STAT_GOOD; } scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, MEDIUM_ERROR, ASC_INVALID_FIELD_IN_CDB); return SAM_STAT_CHECK_CONDITION; } static int mmc_synchronize_cache(int host_no, struct scsi_cmd *cmd) { struct mmc_info *mmc = dtype_priv(cmd->dev); if (mmc->current_profile == PROFILE_NO_PROFILE) { scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_MEDIUM_NOT_PRESENT); return SAM_STAT_CHECK_CONDITION; } return SAM_STAT_GOOD; } static unsigned char *perf_type_write_speed(struct scsi_cmd *cmd, unsigned char *data, unsigned int type, unsigned int data_type) { struct mmc_info *mmc = dtype_priv(cmd->dev); /* write/except */ *data++ = 0x00; data += 3; switch (mmc->current_profile) { case PROFILE_NO_PROFILE: /* descriptor 0 */ /* wrcc:CLV rdd:0 exact:0 mrw:0 */ *data++ = 0x00; /* reserved */ data += 3; /* end lba */ *data++ = 0x00; *data++ = 0x25; *data++ = 0x99; *data++ = 0x99; /* read speed */ *data++ = 0x00; *data++ = 0x00; *data++ = 0x0a; *data++ = 0xd2; /* write speed */ *data++ = 0x00; *data++ = 0x00; *data++ = 0x0a; *data++ = 0xd2; /* descriptor 1 */ /* wrcc:CLV rdd:0 exact:0 mrw:0 */ *data++ = 0x00; /* reserved */ data += 3; /* end lba */ *data++ = 0x00; *data++ = 0x25; *data++ = 0x99; *data++ = 0x99; /* read speed */ *data++ = 0x00; *data++ = 0x00; *data++ = 0x05; *data++ = 0x69; /* write speed */ *data++ = 0x00; *data++ = 0x00; *data++ = 0x05; *data++ = 0x69; return data; break; case PROFILE_DVD_PLUS_R: /* descriptor 0 */ /* wrcc:CLV rdd:0 exact:0 mrw:1 */ *data++ = 0x01; /* reserved */ data += 3; /* end lba */ *data++ = 0x00; *data++ = 0x23; *data++ = 0x05; *data++ = 0x3f; /* read speed */ *data++ = 0x00; *data++ = 0x00; *data++ = 0x0c; *data++ = 0xfc; /* write speed */ *data++ = 0x00; *data++ = 0x00; *data++ = 0x0c; *data++ = 0xfc; return data; break; case PROFILE_DVD_ROM: /* descriptor 0 */ /* wrcc:CLV rdd:0 exact:0 mrw:0 */ *data++ = 0x00; /* reserved */ data += 3; /* end lba */ *data++ = 0x00; *data++ = 0x25; *data++ = 0x99; *data++ = 0x99; /* read speed */ *data++ = 0x00; *data++ = 0x00; *data++ = 0x0a; *data++ = 0xd2; /* write speed */ *data++ = 0x00; *data++ = 0x00; *data++ = 0x0a; *data++ = 0xd2; /* descriptor 1 */ /* wrcc:CLV rdd:0 exact:0 mrw:0 */ *data++ = 0x00; /* reserved */ data += 3; /* end lba */ *data++ = 0x00; *data++ = 0x25; *data++ = 0x99; *data++ = 0x99; /* read speed */ *data++ = 0x00; *data++ = 0x00; *data++ = 0x05; *data++ = 0x69; /* write speed */ *data++ = 0x00; *data++ = 0x00; *data++ = 0x05; *data++ = 0x69; return data; break; } /* we do not understand/support this command */ scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_INVALID_FIELD_IN_CDB); return NULL; } static unsigned char *perf_type_perf_data(struct scsi_cmd *cmd, unsigned char *data, unsigned int type, unsigned int data_type) { struct mmc_info *mmc = dtype_priv(cmd->dev); int tolerance; int write_flag; int except; long tmp; tolerance = (data_type >> 3) & 0x03; write_flag = (data_type >> 2) & 0x01; except = data_type & 0x03; /* all other values for tolerance are reserved */ if (tolerance != 0x02) { scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_INVALID_FIELD_IN_CDB); return NULL; } switch (except) { case 1: case 2: /* write/except */ *data++ = 0x01; /* reserved */ data += 3; /* no actual descriptor returned here */ return data; case 3: /* reserved */ scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_INVALID_FIELD_IN_CDB); return NULL; } /* write/except */ if (write_flag) *data++ = 0x02; else *data++ = 0x00; /* reserved */ data += 3; /* start lba */ *data++ = 0; *data++ = 0; *data++ = 0; *data++ = 0; /* start performance */ *data++ = 0x00; *data++ = 0x00; *data++ = 0x15; *data++ = 0xa4; /* end lba */ switch (mmc->current_profile) { case PROFILE_DVD_ROM: tmp = (cmd->dev->size >> MMC_BLK_SHIFT) - 1; break; default: tmp = 0x23053f; } *data++ = (tmp >> 24) & 0xff; *data++ = (tmp >> 16) & 0xff; *data++ = (tmp >> 8) & 0xff; *data++ = tmp & 0xff; /* end performance */ *data++ = 0x00; *data++ = 0x00; *data++ = 0x15; *data++ = 0xa4; return data; } #define PERF_TYPE_PERF_DATA 0x00 #define PERF_TYPE_WRITE_SPEED 0x03 struct perf_type { int type; unsigned char *(*func)(struct scsi_cmd *cmd, unsigned char *data, unsigned int type, unsigned int data_type); }; struct perf_type perf_types[] = { {PERF_TYPE_PERF_DATA, perf_type_perf_data}, {PERF_TYPE_WRITE_SPEED, perf_type_write_speed}, {0, NULL} }; static int mmc_get_performance(int host_no, struct scsi_cmd *cmd) { unsigned int type; unsigned int num_desc; unsigned long lba; unsigned int data_type; struct perf_type *p; unsigned char *data; unsigned char buf[256]; memset(buf, 0, sizeof(buf)); data = &buf[4]; data_type = cmd->scb[1]&0x1f; lba = cmd->scb[2]; lba = (lba<<8)|cmd->scb[3]; lba = (lba<<8)|cmd->scb[4]; lba = (lba<<8)|cmd->scb[5]; num_desc = cmd->scb[8]; num_desc = (num_desc<<8)|cmd->scb[9]; type = cmd->scb[10]; for (p = perf_types; p->func; p++) { int tmp; if (p->type != type) continue; data = p->func(cmd, data, type, data_type); if (!data) return SAM_STAT_CHECK_CONDITION; tmp = data-&buf[4]; buf[0] = (tmp >> 24) & 0xff; buf[1] = (tmp >> 16) & 0xff; buf[2] = (tmp >> 8) & 0xff; buf[3] = tmp & 0xff; memcpy(scsi_get_in_buffer(cmd), buf, min_t(uint32_t, scsi_get_in_length(cmd), sizeof(buf))); return SAM_STAT_GOOD; } /* we do not understand/support this command */ scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_INVALID_FIELD_IN_CDB); return SAM_STAT_CHECK_CONDITION; } static int mmc_set_streaming(int host_no, struct scsi_cmd *cmd) { return SAM_STAT_GOOD; } #define DVD_FORMAT_PHYS_INFO 0x00 #define DVD_FORMAT_DVD_COPYRIGHT_INFO 0x01 #define DVD_FORMAT_ADIP_INFO 0x11 #define DVD_FORMAT_DVD_STRUCTURE_LIST 0xff static unsigned char *dvd_format_phys_info(struct scsi_cmd *cmd, unsigned char *data, int format, int layer, int write_header) { struct mmc_info *mmc = dtype_priv(cmd->dev); unsigned char *old_data; if (write_header) { *data++ = DVD_FORMAT_PHYS_INFO; *data++ = 0x40; *data++ = 0x08; *data++ = 0x02; /* 0x800 bytes data, 2 reserved bytes */ return data; } if (layer) { /* we only support single layer disks */ scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_INVALID_FIELD_IN_CDB); return NULL; } switch (mmc->current_profile) { case PROFILE_DVD_ROM: /* book type DVD-ROM, part version */ *data++ = 0x01; /* disk size 120mm, maximum rate 10mbit/s */ *data++ = 0x02; /* num layers:1 layer type: embossed*/ *data++ = 0x01; /* linear density: track density: */ *data++ = 0x10; *data++ = 0; /* starting physical sector number of data area */ *data++ = 3; *data++ = 0; *data++ = 0; /* end physical sector number of data area */ *data++ = 0x1b; *data++ = 0xc1; *data++ = 0x7f; *data++ = 0; /* end physical sector number in layer 0 */ *data++ = 0; *data++ = 0; *data++ = 0; /* bca */ *data++ = 0; /* just leave the media specific area as 0 */ data += 2031; break; case PROFILE_DVD_PLUS_R: /* book type DVD+R, part version */ *data++ = 0xa1; /* disk size 120mm, maximum rate nof specified */ *data++ = 0x0f; /* num layers:1 layer type:recordable */ *data++ = 0x02; /* linear density: track density: */ *data++ = 0x00; *data++ = 0; /* starting physical sector number of data area */ *data++ = 3; *data++ = 0; *data++ = 0; *data++ = 0; /* end physical sector number of data area */ *data++ = 0x26; *data++ = 0x05; *data++ = 0x3f; *data++ = 0; /* end physical sector number in layer 0 */ *data++ = 0; *data++ = 0; *data++ = 0; /* bca */ *data++ = 0; /* data, copied from a blank disk */ /* not in t.10 spec */ old_data = data; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; data = old_data + 2031; break; default: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_MEDIUM_NOT_PRESENT); return NULL; } return data; } static unsigned char *dvd_format_adip_info(struct scsi_cmd *cmd, unsigned char *data, int format, int layer, int write_header) { struct mmc_info *mmc = dtype_priv(cmd->dev); if (write_header) { *data++ = DVD_FORMAT_ADIP_INFO; switch (mmc->current_profile) { case PROFILE_DVD_PLUS_R: *data++ = 0x40; /* readable */ break; default: *data++ = 0; } *data++ = 0x01; *data++ = 0x02; /* 0x100 bytes data, 2 reserved bytes */ return data; } switch (mmc->current_profile) { case PROFILE_DVD_PLUS_R: break; default: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_MEDIUM_NOT_PRESENT); return NULL; } /* adip information */ /* adip for DVD+R not in t.10 mmc spec, */ *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; *data++ = 0x00; return data; } static unsigned char *dvd_format_copyright_info(struct scsi_cmd *cmd, unsigned char *data, int format, int layer, int write_header) { if (write_header) { *data++ = DVD_FORMAT_DVD_COPYRIGHT_INFO; *data++ = 0x40; /* readable */ *data++ = 0; *data++ = 6; /* 4 bytes data, 2 reserved bytes */ return data; } if (layer) { /* we only support single layer disks */ scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_INVALID_FIELD_IN_CDB); return NULL; } /* copyright protection system type : no */ *data++ = 0; /* region management info : no regions blocked */ *data++ = 0; /* reserved */ *data++ = 0; *data++ = 0; return data; } static unsigned char *dvd_format_dvd_structure_list(struct scsi_cmd *cmd, unsigned char *data, int format, int layer, int write_header); struct dvd_format { int format; unsigned char *(*func)(struct scsi_cmd *cmd, unsigned char *data, int format, int layer, int write_header); }; struct dvd_format dvd_formats[] = { {DVD_FORMAT_PHYS_INFO, dvd_format_phys_info}, {DVD_FORMAT_DVD_COPYRIGHT_INFO, dvd_format_copyright_info}, {DVD_FORMAT_ADIP_INFO, dvd_format_adip_info}, {DVD_FORMAT_DVD_STRUCTURE_LIST, dvd_format_dvd_structure_list}, {0, NULL} }; static unsigned char *dvd_format_dvd_structure_list(struct scsi_cmd *cmd, unsigned char *data, int format, int layer, int write_header) { struct dvd_format *f; /* list all format headers */ for (f = dvd_formats; f->func; f++) { /* we dont report ourself back in the format list */ if (f->format == 0xff) continue; data = f->func(cmd, data, format, layer, 1); if (!data) return NULL; } return data; } static int mmc_read_dvd_structure(int host_no, struct scsi_cmd *cmd) { struct mmc_info *mmc = dtype_priv(cmd->dev); long address; int format, layer; unsigned char *data; unsigned char buf[4096]; struct dvd_format *f; if (mmc->current_profile == PROFILE_NO_PROFILE) { scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_MEDIUM_NOT_PRESENT); return SAM_STAT_CHECK_CONDITION; } address = cmd->scb[2]; address = (address<<8) | cmd->scb[3]; address = (address<<8) | cmd->scb[4]; address = (address<<8) | cmd->scb[5]; layer = cmd->scb[6]; format = cmd->scb[7]; memset(buf, 0, sizeof(buf)); data = &buf[4]; for (f = dvd_formats; f->func; f++) { if (f->format == format) { int tmp; data = f->func(cmd, data, format, layer, 0); if (!data) return SAM_STAT_CHECK_CONDITION; tmp = data - buf; tmp -= 2; buf[0] = (tmp >> 8) & 0xff; buf[1] = tmp & 0xff; buf[2] = 0; buf[3] = 0; memcpy(scsi_get_in_buffer(cmd), buf, min_t(uint32_t, scsi_get_in_length(cmd), sizeof(buf))); return SAM_STAT_GOOD; } } /* we do not understand this format */ scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, NOT_READY, ASC_INVALID_FIELD_IN_CDB); return SAM_STAT_CHECK_CONDITION; } static int mmc_reserve_track(int host_no, struct scsi_cmd *cmd) { struct mmc_info *mmc = dtype_priv(cmd->dev); uint64_t tmp; tmp = cmd->scb[5]; tmp = (tmp << 8) | cmd->scb[6]; tmp = (tmp << 8) | cmd->scb[7]; tmp = (tmp << 8) | cmd->scb[8]; mmc->reserve_track_len = tmp; return SAM_STAT_GOOD; } static int mmc_mode_select(int host_no, struct scsi_cmd *cmd) { return SAM_STAT_GOOD; } static int mmc_set_cd_speed(int host_no, struct scsi_cmd *cmd) { return SAM_STAT_GOOD; } static int mmc_mode_sense(int host_no, struct scsi_cmd *cmd) { uint8_t *scb = cmd->scb; /* MMC devices always return descriptor block */ scb[1] |= 8; return spc_mode_sense(host_no, cmd); } static tgtadm_err mmc_lu_init(struct scsi_lu *lu) { struct backingstore_template *bst; struct mmc_info *mmc; mmc = zalloc(sizeof(struct mmc_info)); if (!mmc) return TGTADM_NOMEM; lu->xxc_p = mmc; if (spc_lu_init(lu)) return TGTADM_NOMEM; /* MMC devices always use mmc backingstore */ bst = get_backingstore_template("mmc"); if (!bst) { eprintf("failed to find bstype, mmc\n"); return TGTADM_INVALID_REQUEST; } lu->bst = bst; strncpy(lu->attrs.product_id, "VIRTUAL-CDROM", sizeof(lu->attrs.product_id)); lu->attrs.sense_format = 0; lu->attrs.version_desc[0] = 0x02A0; /* MMC3, no version claimed */ lu->attrs.version_desc[1] = 0x0960; /* iSCSI */ lu->attrs.version_desc[2] = 0x0300; /* SPC-3 */ lu->attrs.removable = 1; /* * Set up default mode pages * Ref: mmc6r00.pdf 7.2.2 (Table 649) */ /* Vendor uniq - However most apps seem to call for mode page 0*/ add_mode_page(lu, "0:0:0"); /* Read/Write Error Recovery */ add_mode_page(lu, "1:0:10:0:8:0:0:0:0:8:0:0:0"); /* MRW */ add_mode_page(lu, "3:0:6:0:0:0:0:0:0"); /* Write Parameter * Somebody who knows more about this mode page should be setting * defaults. add_mode_page(lu, "5:0:0"); */ /* Caching Page */ add_mode_page(lu, "8:0:10:0:0:0:0:0:0:0:0:0:0"); /* Control page */ add_mode_page(lu, "0x0a:0:10:2:0:0:0:0:0:0:0:2:0"); /* Control Extensions mode page: TCMOS:1 */ add_mode_page(lu, "0x0a:1:0x1c:0x04:0x00:0x00"); /* Power Condition */ add_mode_page(lu, "0x1a:0:10:8:0:0:0:0:0:0:0:0:0"); /* Informational Exceptions Control page */ add_mode_page(lu, "0x1c:0:10:8:0:0:0:0:0:0:0:0:0"); /* Timeout & Protect */ add_mode_page(lu, "0x1d:0:10:0:0:7:0:0:2:0:2:0:20"); /* MM capabilities */ add_mode_page(lu, "0x2a:0x00:0x3e:0x3f:0x37:0xf3:0xf3:0x29:" "0x23:0x10:0x8a:0x01:0x00:0x08:0x00:0x10:" "0x8a:0x00:0x00:0x10:0x8a:0x10:0x8a:0x00:" "0x01:0x00:0x00:0x00:0x00:0x10:0x8a:0x00:" "0x05:0x00:0x00:0x10:0x8a:0x00:0x00:0x0b:" "0x06:0x00:0x00:0x08:0x45:0x00:0x00:0x05:" "0x83:0x00:0x00:0x02:0xc2:0x00:0x00:0x00:" "0x00:0x00:0x00:0x00:0x00:0x00:0x00:0x00:" "0x00"); /* Write parameters */ add_mode_page(lu, "0x05:0:0x32:0x62:5:8:0x10:0:0:0:0:0:0:0:" "0x10:0:0x96:0:0:0:0:0:0:0:0:0:0:0:0:0:0:" "0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:" "0:0"); return TGTADM_SUCCESS; } static tgtadm_err mmc_lu_online(struct scsi_lu *lu) { struct mmc_info *mmc = dtype_priv(lu); struct stat st; mmc->current_profile = PROFILE_NO_PROFILE; mmc->reserve_track_len = 0; if (lu->fd == -1) return TGTADM_INVALID_REQUEST; lu->attrs.online = 1; if (stat(lu->path, &st)) { mmc->current_profile = PROFILE_NO_PROFILE; lu->attrs.online = 0; } else { if (!st.st_size) mmc->current_profile = PROFILE_DVD_PLUS_R; else mmc->current_profile = PROFILE_DVD_ROM; } return TGTADM_SUCCESS; } static struct device_type_template mmc_template = { .type = TYPE_MMC, .lu_init = mmc_lu_init, .lu_config = spc_lu_config, .lu_online = mmc_lu_online, .lu_offline = spc_lu_offline, .lu_exit = spc_lu_exit, .ops = { {spc_test_unit,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_request_sense,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, /* 0x10 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_inquiry,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_start_stop,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_prevent_allow_media_removal,}, {spc_illegal_op,}, /* 0x20 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {mmc_read_capacity,}, {spc_illegal_op,}, {spc_illegal_op,}, {mmc_rw}, {spc_illegal_op,}, {mmc_rw}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_test_unit}, /* 0x30 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {mmc_synchronize_cache,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, /* 0x40 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {mmc_read_toc,}, {spc_illegal_op,}, {spc_illegal_op,}, {mmc_get_configuration,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, /* 0x50 */ {spc_illegal_op,}, {mmc_read_disc_information,}, {mmc_read_track_information,}, {mmc_reserve_track,}, {spc_illegal_op,}, {mmc_mode_select,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {mmc_mode_sense,}, {mmc_close_track,}, {mmc_read_buffer_capacity,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, [0x60 ... 0x9f] = {spc_illegal_op,}, /* 0xA0 */ {spc_report_luns,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {mmc_rw}, {spc_illegal_op,}, {mmc_rw}, {spc_illegal_op,}, {mmc_get_performance,}, {mmc_read_dvd_structure,}, {spc_illegal_op,}, {spc_illegal_op,}, /* 0xB0 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {mmc_set_streaming,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {mmc_set_cd_speed,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, [0xc0 ... 0xff] = {spc_illegal_op}, } }; __attribute__((constructor)) static void mmc_init(void) { device_type_register(&mmc_template); } tgt-1.0.85/usr/osd.c000066400000000000000000000066571435417276200141760ustar00rootroot00000000000000/* * SCSI object storage device command processing * * Copyright (C) 2006-2007 Pete Wyckoff * Copyright (C) 2007 FUJITA Tomonori * Copyright (C) 2007 Mike Christie * * 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, version 2 of the * License. * * 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 "list.h" #include "tgtd.h" #include "scsi.h" #include "spc.h" #include "tgtadm_error.h" static int osd_varlen_cdb(int host_no, struct scsi_cmd *cmd) { return cmd->dev->bst->bs_cmd_submit(cmd); } /* * XXX: missing support for b0 and b1, in page 0 and in inquiry code. * Figure out how to make spc_inquiry handle extra mode pages. */ static tgtadm_err osd_lu_init(struct scsi_lu *lu) { if (spc_lu_init(lu)) return TGTADM_NOMEM; strncpy(lu->attrs.product_id, "OSD", sizeof(lu->attrs.product_id)); lu->attrs.sense_format = 1; lu->attrs.version_desc[0] = 0x0340; /* OSD */ lu->attrs.version_desc[1] = 0x0960; /* iSCSI */ lu->attrs.version_desc[2] = 0x0300; /* SPC-3 */ return TGTADM_SUCCESS; } static struct device_type_template osd_template = { .type = TYPE_OSD, .lu_init = osd_lu_init, .lu_config = spc_lu_config, .lu_online = spc_lu_online, .lu_offline = spc_lu_offline, .lu_exit = spc_lu_exit, .ops = { /* 0x00 */ {spc_test_unit,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_request_sense,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, /* 0x10 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_inquiry,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, [0x20 ... 0x6f] = {spc_illegal_op}, /* 0x70 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {osd_varlen_cdb,}, [0x80 ... 0x9f] = {spc_illegal_op}, /* 0xA0 */ {spc_report_luns,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, [0xb0 ... 0xff] = {spc_illegal_op}, } }; __attribute__((constructor)) static void osd_init(void) { device_type_register(&osd_template); } tgt-1.0.85/usr/parser.c000066400000000000000000000132361435417276200146740ustar00rootroot00000000000000/* * lib/parser.c - simple parser for mount, etc. options. * * This source code is licensed under the GNU General Public License, * Version 2. See the file COPYING for more details. */ #include #include #include #include #include #include #include "parser.h" #include "util.h" /** * match_one: - Determines if a string matches a simple pattern * @s: the string to examine for presense of the pattern * @p: the string containing the pattern * @args: array of %MAX_OPT_ARGS &substring_t elements. Used to return match * locations. * * Description: Determines if the pattern @p is present in string @s. Can only * match extremely simple token=arg style patterns. If the pattern is found, * the location(s) of the arguments will be returned in the @args array. */ static int match_one(char *s, char *p, substring_t args[]) { char *meta; int argc = 0; if (!p) return 1; while(1) { int len = -1; meta = strchr(p, '%'); if (!meta) return strcmp(p, s) == 0; if (strncmp(p, s, meta-p)) return 0; s += meta - p; p = meta + 1; if (isdigit(*p)) len = strtoul(p, &p, 10); else if (*p == '%') { if (*s++ != '%') return 0; p++; continue; } if (argc >= MAX_OPT_ARGS) return 0; args[argc].from = s; switch (*p++) { case 's': if (strlen(s) == 0) return 0; else if (len == -1 || len > strlen(s)) len = strlen(s); args[argc].to = s + len; break; case 'd': strtol(s, &args[argc].to, 0); goto num; case 'u': strtoul(s, &args[argc].to, 0); goto num; case 'o': strtoul(s, &args[argc].to, 8); goto num; case 'x': strtoul(s, &args[argc].to, 16); num: if (args[argc].to == args[argc].from) return 0; break; default: return 0; } s = args[argc].to; argc++; } } /** * match_token: - Find a token (and optional args) in a string * @s: the string to examine for token/argument pairs * @table: match_table_t describing the set of allowed option tokens and the * arguments that may be associated with them. Must be terminated with a * &struct match_token whose pattern is set to the NULL pointer. * @args: array of %MAX_OPT_ARGS &substring_t elements. Used to return match * locations. * * Description: Detects which if any of a set of token strings has been passed * to it. Tokens can include up to MAX_OPT_ARGS instances of basic c-style * format identifiers which will be taken into account when matching the * tokens, and whose locations will be returned in the @args array. */ int match_token(char *s, match_table_t table, substring_t args[]) { struct match_token *p; for (p = table; !match_one(s, p->pattern, args) ; p++) ; return p->token; } /** * match_number: scan a number in the given base from a substring_t * @s: substring to be scanned * @result: resulting integer on success * @base: base to use when converting string * * Description: Given a &substring_t and a base, attempts to parse the substring * as a number in that base. On success, sets @result to the integer represented * by the string and returns 0. Returns either -ENOMEM or -EINVAL on failure. */ static int match_number(substring_t *s, int *result, int base) { char *endp; char *buf; int ret; buf = malloc(s->to - s->from + 1); if (!buf) return -ENOMEM; memcpy(buf, s->from, s->to - s->from); buf[s->to - s->from] = '\0'; *result = strtol(buf, &endp, base); ret = 0; if (endp == buf) ret = -EINVAL; free(buf); return ret; } /** * match_int: - scan a decimal representation of an integer from a substring_t * @s: substring_t to be scanned * @result: resulting integer on success * * Description: Attempts to parse the &substring_t @s as a decimal integer. On * success, sets @result to the integer represented by the string and returns 0. * Returns either -ENOMEM or -EINVAL on failure. */ int match_int(substring_t *s, int *result) { return match_number(s, result, 0); } /** * match_octal: - scan an octal representation of an integer from a substring_t * @s: substring_t to be scanned * @result: resulting integer on success * * Description: Attempts to parse the &substring_t @s as an octal integer. On * success, sets @result to the integer represented by the string and returns * 0. Returns either -ENOMEM or -EINVAL on failure. */ int match_octal(substring_t *s, int *result) { return match_number(s, result, 8); } /** * match_hex: - scan a hex representation of an integer from a substring_t * @s: substring_t to be scanned * @result: resulting integer on success * * Description: Attempts to parse the &substring_t @s as a hexadecimal integer. * On success, sets @result to the integer represented by the string and * returns 0. Returns either -ENOMEM or -EINVAL on failure. */ int match_hex(substring_t *s, int *result) { return match_number(s, result, 16); } /** * match_strcpy: - copies the characters from a substring_t to a string * @to: string to copy characters to. * @s: &substring_t to copy * * Description: Copies the set of characters represented by the given * &substring_t @s to the c-style string @to. Caller guarantees that @to is * large enough to hold the characters of @s. */ char *match_strncpy(char *to, substring_t *s, size_t n) { snprintf(to, n, "%s", s->from); return to; } /** * match_strdup: - allocate a new string with the contents of a substring_t * @s: &substring_t to copy * * Description: Allocates and returns a string filled with the contents of * the &substring_t @s. The caller is responsible for freeing the returned * string with kfree(). */ char *match_strdup(substring_t *s) { size_t n = s->to - s->from + 1; char *p = malloc(n); if (p) match_strncpy(p, s, n); return p; } tgt-1.0.85/usr/parser.h000066400000000000000000000016021435417276200146730ustar00rootroot00000000000000/* * linux/include/linux/parser.h * * Header for lib/parser.c * Intended use of these functions is parsing filesystem argument lists, * but could potentially be used anywhere else that simple option=arg * parsing is required. */ /* associates an integer enumerator with a pattern string. */ struct match_token { int token; char *pattern; }; typedef struct match_token match_table_t[]; /* Maximum number of arguments that match_token will find in a pattern */ enum {MAX_OPT_ARGS = 3}; /* Describe the location within a string of a substring */ typedef struct { char *from; char *to; } substring_t; int match_token(char *, match_table_t table, substring_t args[]); int match_int(substring_t *, int *result); int match_octal(substring_t *, int *result); int match_hex(substring_t *, int *result); char *match_strncpy(char *, substring_t *, size_t); char *match_strdup(substring_t *); tgt-1.0.85/usr/sbc.c000066400000000000000000000535261435417276200141550ustar00rootroot00000000000000/* * SCSI block command processing * * Copyright (C) 2004-2007 FUJITA Tomonori * Copyright (C) 2005-2007 Mike Christie * * This code is based on Ardis's iSCSI implementation. * http://www.ardistech.com/iscsi/ * Copyright (C) 2002-2003 Ardis Technolgies , * * 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, version 2 of the * License. * * 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 */ #define _FILE_OFFSET_BITS 64 #define __USE_GNU #include #include #include #include #include #include #include #include #include "list.h" #include "util.h" #include "tgtd.h" #include "tgtadm_error.h" #include "target.h" #include "driver.h" #include "scsi.h" #include "spc.h" #include "tgtadm_error.h" #define DEFAULT_BLK_SHIFT 9 static unsigned int blk_shift = DEFAULT_BLK_SHIFT; static off_t find_next_data(struct scsi_lu *dev, off_t offset) { #ifdef SEEK_DATA return lseek64(dev->fd, offset, SEEK_DATA); #else return offset; #endif } static off_t find_next_hole(struct scsi_lu *dev, off_t offset) { #ifdef SEEK_HOLE return lseek64(dev->fd, offset, SEEK_HOLE); #else return dev->size; #endif } static int sbc_mode_page_update(struct scsi_cmd *cmd, uint8_t *data, int *changed) { uint8_t pcode = data[0] & 0x3f; uint8_t subpcode = 0; struct mode_pg *pg; uint8_t old; if (data[0] & 0x40) subpcode = data[1]; pg = find_mode_page(cmd->dev, pcode, subpcode); if (pg == NULL) return 1; eprintf("%x %x\n", pg->mode_data[0], data[2]); switch (pcode) { case 0x08: /* Cachning mode page */ old = pg->mode_data[0]; if (0x4 & data[2]) pg->mode_data[0] |= 0x4; else pg->mode_data[0] &= ~0x4; if (old != pg->mode_data[0]) *changed = 1; return 0; case 0x0a: /* Control mode page */ old = pg->mode_data[2]; if (0x8 & data[4]) pg->mode_data[2] |= 0x8; else pg->mode_data[2] &= ~0x8; if (old != pg->mode_data[2]) *changed = 1; cmd->dev->attrs.swp = (0x8 & data[4]) ? 1 : 0; return 0; } return 1; } static int sbc_mode_select(int host_no, struct scsi_cmd *cmd) { return spc_mode_select(host_no, cmd, sbc_mode_page_update); } static int sbc_mode_sense(int host_no, struct scsi_cmd *cmd) { int ret; uint8_t device_specific = 0x10; /* DPOFUA */ uint8_t mode6, *data; ret = spc_mode_sense(host_no, cmd); if (ret != SAM_STAT_GOOD) return ret; /* * If this is a read-only lun, we must set the write protect bit */ if (cmd->dev->attrs.readonly || cmd->dev->attrs.swp) device_specific |= 0x80; data = scsi_get_in_buffer(cmd); mode6 = (cmd->scb[0] == 0x1a); if (mode6) data[2] = device_specific; else data[3] = device_specific; return SAM_STAT_GOOD; } static int sbc_format_unit(int host_no, struct scsi_cmd *cmd) { unsigned char key = ILLEGAL_REQUEST; uint16_t asc = ASC_INVALID_FIELD_IN_CDB; int ret; ret = device_reserved(cmd); if (ret) return SAM_STAT_RESERVATION_CONFLICT; if (!cmd->dev->attrs.online) { key = NOT_READY; asc = ASC_MEDIUM_NOT_PRESENT; goto sense; } if (cmd->dev->attrs.readonly || cmd->dev->attrs.swp) { key = DATA_PROTECT; asc = ASC_WRITE_PROTECT; goto sense; } if (cmd->scb[1] & 0x80) { /* we dont support format protection information */ goto sense; } if (cmd->scb[1] & 0x10) { /* we dont support format data */ goto sense; } if (cmd->scb[1] & 0x07) { /* defect list format must be 0 */ goto sense; } return SAM_STAT_GOOD; sense: sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } static int sbc_unmap(int host_no, struct scsi_cmd *cmd) { int ret; unsigned char key = ILLEGAL_REQUEST; uint16_t asc = ASC_LUN_NOT_SUPPORTED; struct scsi_lu *lu = cmd->dev; int anchor; ret = device_reserved(cmd); if (ret) return SAM_STAT_RESERVATION_CONFLICT; /* We dont support anchored blocks */ anchor = cmd->scb[1] & 0x01; if (anchor) { key = ILLEGAL_REQUEST; asc = ASC_INVALID_FIELD_IN_CDB; goto sense; } if (lu->attrs.removable && !lu->attrs.online) { key = NOT_READY; asc = ASC_MEDIUM_NOT_PRESENT; goto sense; } if (!lu->attrs.thinprovisioning) { key = ILLEGAL_REQUEST; asc = ASC_INVALID_OP_CODE; goto sense; } if (lu->attrs.readonly || cmd->dev->attrs.swp) { key = DATA_PROTECT; asc = ASC_WRITE_PROTECT; goto sense; } ret = cmd->dev->bst->bs_cmd_submit(cmd); if (ret) { key = HARDWARE_ERROR; asc = ASC_INTERNAL_TGT_FAILURE; goto sense; } sense: cmd->offset = 0; scsi_set_in_resid_by_actual(cmd, 0); scsi_set_out_resid_by_actual(cmd, 0); sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } static int sbc_rw(int host_no, struct scsi_cmd *cmd) { int ret; uint64_t lba; uint32_t tl; size_t blocksize = 1 << cmd->dev->blk_shift; unsigned char key = ILLEGAL_REQUEST; uint16_t asc = ASC_LUN_NOT_SUPPORTED; struct scsi_lu *lu = cmd->dev; ret = device_reserved(cmd); if (ret) return SAM_STAT_RESERVATION_CONFLICT; if (cmd->dev->attrs.removable && !cmd->dev->attrs.online) { key = NOT_READY; asc = ASC_MEDIUM_NOT_PRESENT; goto sense; } lba = scsi_rw_offset(cmd->scb); tl = scsi_rw_count(cmd->scb); switch (cmd->scb[0]) { case READ_10: case READ_12: case READ_16: case WRITE_10: case WRITE_12: case WRITE_16: case ORWRITE_16: case WRITE_VERIFY: case WRITE_VERIFY_12: case WRITE_VERIFY_16: case COMPARE_AND_WRITE: /* We only support protection information type 0 */ if (cmd->scb[1] & 0xe0) { key = ILLEGAL_REQUEST; asc = ASC_INVALID_FIELD_IN_CDB; goto sense; } break; case WRITE_SAME: case WRITE_SAME_16: /* We dont support resource-provisioning so * ANCHOR bit == 1 is an error. */ if (cmd->scb[1] & 0x10) { key = ILLEGAL_REQUEST; asc = ASC_INVALID_FIELD_IN_CDB; goto sense; } /* We only support unmap for thin provisioned LUNS */ if (cmd->scb[1] & 0x08 && !lu->attrs.thinprovisioning) { key = ILLEGAL_REQUEST; asc = ASC_INVALID_FIELD_IN_CDB; goto sense; } /* We only support protection information type 0 */ if (cmd->scb[1] & 0xe0) { key = ILLEGAL_REQUEST; asc = ASC_INVALID_FIELD_IN_CDB; goto sense; } /* LBDATA and PBDATA can not both be set */ if ((cmd->scb[1] & 0x06) == 0x06) { key = ILLEGAL_REQUEST; asc = ASC_INVALID_FIELD_IN_CDB; goto sense; } /* Fail of DataOut is neither ==0 or ==blocksize */ if (scsi_get_out_length(cmd) && blocksize != scsi_get_out_length(cmd)) { key = ILLEGAL_REQUEST; asc = ASC_PARAMETER_LIST_LENGTH_ERR; goto sense; } /* TL == 0 means all LBAs until end of device */ if (tl == 0) tl = (lu->size >> cmd->dev->blk_shift) - lba; break; } if (lu->attrs.readonly || cmd->dev->attrs.swp) { switch (cmd->scb[0]) { case WRITE_6: case WRITE_10: case WRITE_12: case WRITE_16: case ORWRITE_16: case WRITE_VERIFY: case WRITE_VERIFY_12: case WRITE_VERIFY_16: case WRITE_SAME: case WRITE_SAME_16: case PRE_FETCH_10: case PRE_FETCH_16: case COMPARE_AND_WRITE: key = DATA_PROTECT; asc = ASC_WRITE_PROTECT; goto sense; break; } } /* Verify that we are not doing i/o beyond the end-of-lun */ if (tl) { if (lba + tl < lba || lba + tl > lu->size >> cmd->dev->blk_shift) { key = ILLEGAL_REQUEST; asc = ASC_LBA_OUT_OF_RANGE; goto sense; } } else { if (lba >= lu->size >> cmd->dev->blk_shift) { key = ILLEGAL_REQUEST; asc = ASC_LBA_OUT_OF_RANGE; goto sense; } } cmd->offset = lba << cmd->dev->blk_shift; cmd->tl = tl << cmd->dev->blk_shift; /* Handle residuals */ switch (cmd->scb[0]) { case READ_6: case READ_10: case READ_12: case READ_16: if (cmd->tl != scsi_get_in_length(cmd)) scsi_set_in_resid_by_actual(cmd, cmd->tl); break; case WRITE_6: case WRITE_10: case WRITE_12: case WRITE_16: case WRITE_VERIFY: case WRITE_VERIFY_12: case WRITE_VERIFY_16: if (cmd->tl != scsi_get_out_length(cmd)) { scsi_set_out_resid_by_actual(cmd, cmd->tl); /* We need to clamp the size of the in-buffer * so that we dont try to write > cmd->tl in the * backend store. */ if (cmd->tl < scsi_get_out_length(cmd)) { scsi_set_out_length(cmd, cmd->tl); } } break; } ret = cmd->dev->bst->bs_cmd_submit(cmd); if (ret) { key = HARDWARE_ERROR; asc = ASC_INTERNAL_TGT_FAILURE; } else return SAM_STAT_GOOD; sense: cmd->offset = 0; scsi_set_in_resid_by_actual(cmd, 0); scsi_set_out_resid_by_actual(cmd, 0); sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } static int sbc_reserve(int host_no, struct scsi_cmd *cmd) { if (device_reserve(cmd)) return SAM_STAT_RESERVATION_CONFLICT ; else return SAM_STAT_GOOD; } static int sbc_release(int host_no, struct scsi_cmd *cmd) { int ret; ret = device_release(cmd->c_target->tid, cmd->cmd_itn_id, cmd->dev->lun, 0); return ret ? SAM_STAT_RESERVATION_CONFLICT : SAM_STAT_GOOD; } static int sbc_read_capacity(int host_no, struct scsi_cmd *cmd) { uint32_t *data; unsigned int bshift; uint64_t size; uint8_t *scb = cmd->scb; unsigned char key = ILLEGAL_REQUEST; uint16_t asc = ASC_LUN_NOT_SUPPORTED; if (cmd->dev->attrs.removable && !cmd->dev->attrs.online) { key = NOT_READY; asc = ASC_MEDIUM_NOT_PRESENT; goto sense; } if (!(scb[8] & 0x1) && (scb[2] | scb[3] | scb[4] | scb[5])) { asc = ASC_INVALID_FIELD_IN_CDB; goto sense; } if (scsi_get_in_length(cmd) < 8) goto overflow; data = scsi_get_in_buffer(cmd); bshift = cmd->dev->blk_shift; size = cmd->dev->size >> bshift; data[0] = (size >> 32) ? __cpu_to_be32(0xffffffff) : __cpu_to_be32(size - 1); data[1] = __cpu_to_be32(1U << bshift); overflow: scsi_set_in_resid_by_actual(cmd, 8); return SAM_STAT_GOOD; sense: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } static int sbc_verify(int host_no, struct scsi_cmd *cmd) { struct scsi_lu *lu = cmd->dev; unsigned char key; uint16_t asc; int vprotect, bytchk, ret; uint64_t lba; uint32_t tl; if (cmd->dev->attrs.removable && !cmd->dev->attrs.online) { key = NOT_READY; asc = ASC_MEDIUM_NOT_PRESENT; goto sense; } vprotect = cmd->scb[1] & 0xe0; if (vprotect) { /* We only support protection information type 0 */ key = ILLEGAL_REQUEST; asc = ASC_INVALID_FIELD_IN_CDB; goto sense; } bytchk = cmd->scb[1] & 0x02; if (!bytchk) { /* no data compare with the media */ return SAM_STAT_GOOD; } lba = scsi_rw_offset(cmd->scb); tl = scsi_rw_count(cmd->scb); /* Verify that we are not doing i/o beyond the end-of-lun */ if (tl) { if (lba + tl < lba || lba + tl > lu->size >> cmd->dev->blk_shift) { key = ILLEGAL_REQUEST; asc = ASC_LBA_OUT_OF_RANGE; goto sense; } } else { if (lba >= lu->size >> cmd->dev->blk_shift) { key = ILLEGAL_REQUEST; asc = ASC_LBA_OUT_OF_RANGE; goto sense; } } cmd->offset = lba << cmd->dev->blk_shift; ret = cmd->dev->bst->bs_cmd_submit(cmd); if (ret) { key = HARDWARE_ERROR; asc = ASC_INTERNAL_TGT_FAILURE; goto sense; } return SAM_STAT_GOOD; sense: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } static int sbc_readcapacity16(int host_no, struct scsi_cmd *cmd) { uint32_t alloc_len, avail_len, actual_len; uint8_t *data; uint8_t buf[32]; unsigned int bshift; uint64_t size; uint32_t val; uint16_t asc = ASC_INVALID_FIELD_IN_CDB; unsigned char key = ILLEGAL_REQUEST; if (cmd->dev->attrs.removable && !cmd->dev->attrs.online) { key = NOT_READY; asc = ASC_MEDIUM_NOT_PRESENT; goto sense; } alloc_len = get_unaligned_be32(&cmd->scb[10]); if (scsi_get_in_length(cmd) < alloc_len) goto sense; data = buf; memset(data, 0, 32); avail_len = 32; bshift = cmd->dev->blk_shift; size = cmd->dev->size >> bshift; put_unaligned_be64(size - 1, &data[0]); put_unaligned_be32(1UL << bshift, &data[8]); val = (cmd->dev->attrs.lbppbe << 16) | cmd->dev->attrs.la_lba; if (cmd->dev->attrs.thinprovisioning) val |= (3 << 14); /* set LBPME and LBPRZ */ put_unaligned_be32(val, &data[12]); actual_len = spc_memcpy(scsi_get_in_buffer(cmd), &alloc_len, data, avail_len); scsi_set_in_resid_by_actual(cmd, actual_len); return SAM_STAT_GOOD; sense: sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } static int sbc_getlbastatus(int host_no, struct scsi_cmd *cmd) { uint64_t offset; uint32_t alloc_len, avail_len, actual_len, remain_len; unsigned char *buf; uint8_t data[16]; int mapped; uint16_t asc; unsigned char key; if (cmd->dev->attrs.removable && !cmd->dev->attrs.online) { key = NOT_READY; asc = ASC_MEDIUM_NOT_PRESENT; goto sense; } offset = get_unaligned_be64(&cmd->scb[2]) << cmd->dev->blk_shift; if (offset >= cmd->dev->size) { key = ILLEGAL_REQUEST; asc = ASC_LBA_OUT_OF_RANGE; goto sense; } alloc_len = get_unaligned_be32(&cmd->scb[10]); if (alloc_len < 4 || scsi_get_in_length(cmd) < alloc_len) { key = ILLEGAL_REQUEST; asc = ASC_INVALID_FIELD_IN_CDB; goto sense; } avail_len = 0; remain_len = alloc_len; buf = scsi_get_in_buffer(cmd); memset(data, 0, 16); /* Copy zeros now - Parameter Data Length to be set later */ actual_len = spc_memcpy(&buf[0], &remain_len, data, 8); avail_len += 8; mapped = 1; do { off_t next_offset; uint64_t start_lba; uint32_t num_blocks; next_offset = (!mapped) ? find_next_data(cmd->dev, offset) : find_next_hole(cmd->dev, offset); if (next_offset == offset) { mapped = 1 - mapped; continue; } if (next_offset > cmd->dev->size) next_offset = cmd->dev->size; start_lba = offset >> cmd->dev->blk_shift; num_blocks = (next_offset - offset) >> cmd->dev->blk_shift; put_unaligned_be64(start_lba, &data[0]); put_unaligned_be32(num_blocks, &data[8]); data[12] = (!mapped) ? 1 : 0; /* 0:mapped 1:deallocated */ actual_len += spc_memcpy(&buf[avail_len], &remain_len, data, 16); avail_len += 16; mapped = 1 - mapped; offset = next_offset; } while (offset < cmd->dev->size); put_unaligned_be32(avail_len - 4, &buf[0]); /* Parameter Data Len */ scsi_set_in_resid_by_actual(cmd, actual_len); return SAM_STAT_GOOD; sense: sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } struct service_action sbc_service_actions[] = { {SAI_READ_CAPACITY_16, sbc_readcapacity16}, {SAI_GET_LBA_STATUS, sbc_getlbastatus}, {0, NULL} }; static int sbc_service_action(int host_no, struct scsi_cmd *cmd) { uint8_t action; unsigned char op = cmd->scb[0]; struct service_action *service_action, *actions; action = cmd->scb[1] & 0x1f; actions = cmd->dev->dev_type_template.ops[op].service_actions; service_action = find_service_action(actions, action); if (!service_action) { scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB); return SAM_STAT_CHECK_CONDITION; } return service_action->cmd_perform(host_no, cmd); } static int sbc_sync_cache(int host_no, struct scsi_cmd *cmd) { int ret; uint8_t key = ILLEGAL_REQUEST; uint16_t asc = ASC_LUN_NOT_SUPPORTED; if (device_reserved(cmd)) return SAM_STAT_RESERVATION_CONFLICT; scsi_set_in_resid_by_actual(cmd, 0); if (cmd->dev->attrs.removable && !cmd->dev->attrs.online) { key = NOT_READY; asc = ASC_MEDIUM_NOT_PRESENT; goto sense; } ret = cmd->dev->bst->bs_cmd_submit(cmd); switch (ret) { case EROFS: case EINVAL: case EBADF: case EIO: /* * is this the right sense code? * what should I put for the asc/ascq? */ key = HARDWARE_ERROR; asc = ASC_INTERNAL_TGT_FAILURE; goto sense; default: return SAM_STAT_GOOD; } sense: sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } static tgtadm_err sbc_lu_init(struct scsi_lu *lu) { uint64_t size; uint8_t *data; if (spc_lu_init(lu)) return TGTADM_NOMEM; strncpy(lu->attrs.product_id, "VIRTUAL-DISK", sizeof(lu->attrs.product_id)); lu->attrs.version_desc[0] = 0x04C0; /* SBC-3 no version claimed */ lu->attrs.version_desc[1] = 0x0960; /* iSCSI */ lu->attrs.version_desc[2] = 0x0300; /* SPC-3 */ data = lu->mode_block_descriptor; if (!lu->blk_shift) lu->blk_shift = blk_shift; /* if unset, use default shift */ size = lu->size >> lu->blk_shift; /* calculate size in blocks */ *(uint32_t *)(data) = (size >> 32) ? __cpu_to_be32(0xffffffff) : __cpu_to_be32(size); *(uint32_t *)(data + 4) = __cpu_to_be32(1 << lu->blk_shift); /* Vendor uniq - However most apps seem to call for mode page 0*/ add_mode_page(lu, "0:0:0"); /* Disconnect page */ add_mode_page(lu, "2:0:14:0x80:0x80:0:0xa:0:0:0:0:0:0:0:0:0:0"); /* Caching Page */ add_mode_page(lu, "8:0:18:0x14:0:0xff:0xff:0:0:" "0xff:0xff:0xff:0xff:0x80:0x14:0:0:0:0:0:0"); { uint8_t mask[18]; memset(mask, 0, sizeof(mask)); mask[0] = 0x4; set_mode_page_changeable_mask(lu, 8, 0, mask); } /* Control page */ add_mode_page(lu, "0x0a:0:10:2:0x10:0:0:0:0:0:0:2:0"); { uint8_t mask[10]; memset(mask, 0, sizeof(mask)); mask[2] = 0x08; /* allow changing SWP */ set_mode_page_changeable_mask(lu, 0x0a, 0, mask); } /* Control Extensions mode page: TCMOS:1 */ add_mode_page(lu, "0x0a:1:0x1c:0x04:0x00:0x00"); /* Informational Exceptions Control page */ add_mode_page(lu, "0x1c:0:10:8:0:0:0:0:0:0:0:0:0"); return TGTADM_SUCCESS; } static struct device_type_template sbc_template = { .type = TYPE_DISK, .lu_init = sbc_lu_init, .lu_config = spc_lu_config, .lu_online = spc_lu_online, .lu_offline = spc_lu_offline, .lu_exit = spc_lu_exit, .ops = { {spc_test_unit,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_request_sense,}, {sbc_format_unit,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {sbc_rw, NULL, PR_EA_FA|PR_EA_FN}, {spc_illegal_op,}, {sbc_rw, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, /* 0x10 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_inquiry,}, {spc_illegal_op,}, {spc_illegal_op,}, {sbc_mode_select, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN}, {sbc_reserve,}, {sbc_release,}, {spc_illegal_op,}, {spc_illegal_op,}, {sbc_mode_sense, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN}, {spc_start_stop, NULL, PR_SPECIAL}, {spc_illegal_op,}, {spc_send_diagnostics,}, {spc_prevent_allow_media_removal,}, {spc_illegal_op,}, /* 0x20 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {sbc_read_capacity,}, {spc_illegal_op,}, {spc_illegal_op,}, {sbc_rw, NULL, PR_EA_FA|PR_EA_FN}, {spc_illegal_op,}, {sbc_rw, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {sbc_rw, NULL, PR_EA_FA|PR_EA_FN}, {sbc_verify, NULL, PR_EA_FA|PR_EA_FN}, /* 0x30 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {sbc_rw, NULL, PR_EA_FA|PR_EA_FN}, /*PRE_FETCH_10 */ {sbc_sync_cache, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, /* 0x40 */ {spc_illegal_op,}, {sbc_rw,}, /* WRITE_SAME10 */ {sbc_unmap,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, /* 0x50 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {sbc_mode_select, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {sbc_mode_sense, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_service_action, persistent_reserve_in_actions,}, {spc_service_action, persistent_reserve_out_actions,}, [0x60 ... 0x7f] = {spc_illegal_op,}, /* 0x80 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {sbc_rw, NULL, PR_EA_FA|PR_EA_FN}, /* {sbc_rw, NULL, PR_EA_FA|PR_EA_FN}, */ {spc_illegal_op,}, {sbc_rw, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN}, {sbc_rw, NULL, PR_EA_FA|PR_EA_FN}, {spc_illegal_op,}, {spc_illegal_op,}, {sbc_rw, NULL, PR_EA_FA|PR_EA_FN}, {sbc_verify, NULL, PR_EA_FA|PR_EA_FN}, /* 0x90 */ {sbc_rw, NULL, PR_EA_FA|PR_EA_FN}, /*PRE_FETCH_16 */ {sbc_sync_cache, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN}, {spc_illegal_op,}, {sbc_rw,}, /* WRITE_SAME_16 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {sbc_service_action, sbc_service_actions,}, {spc_illegal_op,}, /* 0xA0 */ {spc_report_luns,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_service_action, maint_in_service_actions,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {sbc_rw, NULL, PR_EA_FA|PR_EA_FN}, {spc_illegal_op,}, {sbc_rw, NULL, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {sbc_rw, NULL, PR_EA_FA|PR_EA_FN}, {sbc_verify, NULL, PR_EA_FA|PR_EA_FN}, [0xb0 ... 0xff] = {spc_illegal_op}, } }; __attribute__((constructor)) static void sbc_init(void) { device_type_register(&sbc_template); } tgt-1.0.85/usr/scc.c000066400000000000000000000072471435417276200141550ustar00rootroot00000000000000/* * SCSI Controller command processing * * Copyright (C) 2007 FUJITA Tomonori * Copyright (C) 2007 Mike Christie * * 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, version 2 of the * License. * * 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 "list.h" #include "util.h" #include "tgtd.h" #include "target.h" #include "driver.h" #include "scsi.h" #include "tgtadm_error.h" #include "spc.h" static tgtadm_err scc_lu_init(struct scsi_lu *lu) { if (spc_lu_init(lu)) return TGTADM_NOMEM; strncpy(lu->attrs.product_id, "Controller", sizeof(lu->attrs.product_id)); lu->attrs.version_desc[0] = 0x04C0; /* SBC-3 no version claimed */ lu->attrs.version_desc[1] = 0x0960; /* iSCSI */ lu->attrs.version_desc[2] = 0x01fb; /* SCC-2 */ lu->dev_type_template.lu_online(lu); return TGTADM_SUCCESS; } static struct device_type_template scc_template = { .type = TYPE_RAID, .lu_init = scc_lu_init, .lu_config = spc_lu_config, .lu_online = spc_lu_online, .lu_offline = spc_lu_offline, .lu_exit = spc_lu_exit, .ops = { {spc_test_unit,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_request_sense,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, /* 0x10 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_inquiry,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, /* 0x20 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_test_unit}, [0x30 ... 0x7f] = {spc_illegal_op,}, /* 0x80 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_test_unit}, [0x90 ... 0x9f] = {spc_illegal_op,}, /* 0xA0 */ {spc_report_luns,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_service_action, maint_in_service_actions,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_test_unit,}, [0xb0 ... 0xff] = {spc_illegal_op}, } }; __attribute__((constructor)) static void scc_init(void) { device_type_register(&scc_template); } tgt-1.0.85/usr/scsi.c000066400000000000000000000364161435417276200143460ustar00rootroot00000000000000/* * SCSI lib functions * * Copyright (C) 2005-2007 FUJITA Tomonori * Copyright (C) 2005-2007 Mike Christie * * 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, version 2 of the * License. * * 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 #include "list.h" #include "util.h" #include "tgtd.h" #include "target.h" #include "driver.h" #include "scsi.h" #include "spc.h" static unsigned char scsi_command_size[8] = {6, 10, 10, 12, 16, 12, 10, 10}; #define COMMAND_SIZE(opcode) scsi_command_size[((opcode) >> 5) & 7] #define CDB_SIZE(cmd) (((((cmd)->scb[0] >> 5) & 7) < 6) ? \ COMMAND_SIZE((cmd)->scb[0]) : (cmd)->scb_len) #define CDB_CONTROL(cmd) (((cmd)->scb[0] == 0x7f) ? (cmd)->scb[1] \ : (cmd)->scb[CDB_SIZE((cmd))-1]) int get_scsi_command_size(unsigned char op) { return COMMAND_SIZE(op); } int get_scsi_cdb_size(struct scsi_cmd *cmd) { return CDB_SIZE(cmd); } const unsigned char *get_scsi_cdb_usage_data(unsigned char op, unsigned char sa) { static const unsigned char usage[16]; unsigned char *buf = NULL; static unsigned char allow_medium_removal[] = { 0xff, 0x00, 0x00, 0x00, 0x03, 0x07}; static unsigned char send_diagnostics[] = { 0xff, 0xff, 0x00, 0xff, 0xff, 0x07}; static unsigned char start_stop[] = { 0xff, 0x01, 0x00, 0x0f, 0xf7, 0x07}; static unsigned char mode_sense[] = { 0xff, 0x08, 0xff, 0xff, 0xff, 0x07}; static unsigned char mode_select[] = { 0xff, 0x11, 0x00, 0x00, 0xff, 0x07}; static unsigned char reserve_release[] = { 0xff, 0x00, 0x00, 0x00, 0x00, 0x07}; static unsigned char inquiry[] = { 0xff, 0x01, 0xff, 0xff, 0xff, 0x07}; static unsigned char read_write_6[] = { 0xff, 0x1f, 0xff, 0xff, 0xff, 0x07}; static unsigned char format_unit[] = { 0xff, 0xff, 0x00, 0x00, 0x00, 0x07}; static unsigned char request_sense[] = { 0xff, 0x01, 0x00, 0x00, 0xff, 0x07}; static unsigned char test_unit_ready[] = { 0xff, 0x00, 0x00, 0x00, 0x00, 0x07}; static unsigned char persistent_reserve_in[] = { 0xff, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x07}; static unsigned char persistent_reserve_out[] = { 0xff, 0x1f, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x07}; static unsigned char mode_sense_10[] = { 0xff, 0x18, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x07}; static unsigned char mode_select_10[] = { 0xff, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x07}; static unsigned char unmap[] = { 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x07}; static unsigned char write_same_10[] = { 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0x07}; static unsigned char pre_fetch_10[] = { 0xff, 0x02, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0x07}; static unsigned char synchronize_cache_10[] = { 0xff, 0x06, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0x07}; static unsigned char verify_10[] = { 0xff, 0xf2, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0x07}; static unsigned char write_10[] = { 0xff, 0xfa, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0x07}; static unsigned char read_10[] = { 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0x07}; static unsigned char read_capacity[] = { 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07}; static unsigned char verify_12[] = { 0xff, 0xf2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x07}; static unsigned char write_12[] = { 0xff, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x07}; static unsigned char read_12[] = { 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x07}; static unsigned char rep_sup_opcodes[] = { 0xff, 0x1f, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x07}; static unsigned char report_luns[] = { 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x07}; static unsigned char get_lba_status[] = { 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x07}; static unsigned char read_capacity_16[] = { 0xff, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x07}; static unsigned char write_same_16[] = { 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x07}; static unsigned char synchronize_cache_16[] = { 0xff, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x07}; static unsigned char pre_fetch_16[] = { 0xff, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x07}; static unsigned char verify_16[] = { 0xff, 0xf2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x07}; static unsigned char orwrite_16[] = { 0xff, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x07}; static unsigned char compare_and_write[] = { 0xff, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x07}; static unsigned char read_16[] = { 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x07}; switch (op) { case TEST_UNIT_READY: buf = test_unit_ready; break; case REQUEST_SENSE: buf = request_sense; break; case FORMAT_UNIT: buf = format_unit; break; case READ_6: case WRITE_6: buf = read_write_6; break; case INQUIRY: buf = inquiry; break; case MODE_SELECT: buf = mode_select; break; case RELEASE: case RESERVE: buf = reserve_release; break; case MODE_SENSE: buf = mode_sense; break; case START_STOP: buf = start_stop; break; case SEND_DIAGNOSTIC: buf = send_diagnostics; break; case ALLOW_MEDIUM_REMOVAL: buf = allow_medium_removal; break; case READ_CAPACITY: buf = read_capacity; break; case READ_10: buf = read_10; break; case WRITE_10: buf = write_10; break; case WRITE_VERIFY: case VERIFY_10: buf = verify_10; break; case PRE_FETCH_10: buf = pre_fetch_10; break; case SYNCHRONIZE_CACHE: buf = synchronize_cache_10; break; case WRITE_SAME: buf = write_same_10; break; case UNMAP: buf = unmap; break; case MODE_SELECT_10: buf = mode_select_10; break; case MODE_SENSE_10: buf = mode_sense_10; break; case PERSISTENT_RESERVE_IN: switch (sa) { case PR_IN_READ_KEYS: case PR_IN_READ_RESERVATION: case PR_IN_REPORT_CAPABILITIES: case PR_IN_READ_FULL_STATUS: buf = persistent_reserve_in; break; } break; case PERSISTENT_RESERVE_OUT: switch (sa) { case PR_OUT_REGISTER: case PR_OUT_RESERVE: case PR_OUT_RELEASE: case PR_OUT_CLEAR: case PR_OUT_PREEMPT: case PR_OUT_PREEMPT_AND_ABORT: case PR_OUT_REGISTER_AND_IGNORE_EXISTING_KEY: case PR_OUT_REGISTER_AND_MOVE: buf = persistent_reserve_out; break; } break; case READ_16: buf = read_16; break; case COMPARE_AND_WRITE: buf = compare_and_write; break; case WRITE_16: case ORWRITE_16: buf = orwrite_16; break; case WRITE_VERIFY_16: case VERIFY_16: buf = verify_16; break; case PRE_FETCH_16: buf = pre_fetch_16; break; case SYNCHRONIZE_CACHE_16: buf = synchronize_cache_16; break; case WRITE_SAME_16: buf = write_same_16; break; case SERVICE_ACTION_IN: switch (sa) { case SAI_READ_CAPACITY_16: buf = read_capacity_16; break; case SAI_GET_LBA_STATUS: buf = get_lba_status; break; } break; case REPORT_LUNS: buf = report_luns; break; case MAINT_PROTOCOL_IN: switch (sa) { case MPI_REPORT_SUPPORTED_OPCODES: buf = rep_sup_opcodes; break; } break; case READ_12: buf = read_12; break; case VERIFY_12: case WRITE_VERIFY_12: buf = verify_12; break; case WRITE_12: buf = write_12; break; } if (buf) { buf[0] = op; return buf; } return usage; } void sense_data_build(struct scsi_cmd *cmd, uint8_t key, uint16_t asc) { if (cmd->dev->attrs.sense_format) { /* descriptor format */ cmd->sense_buffer[0] = 0x72; /* current, not deferred */ cmd->sense_buffer[1] = key; cmd->sense_buffer[2] = (asc >> 8) & 0xff; cmd->sense_buffer[3] = asc & 0xff; cmd->sense_len = 8; } else { /* fixed format */ int len = 0xa; cmd->sense_buffer[0] = 0x70; /* current, not deferred */ cmd->sense_buffer[2] = key; cmd->sense_buffer[7] = len; cmd->sense_buffer[12] = (asc >> 8) & 0xff; cmd->sense_buffer[13] = asc & 0xff; cmd->sense_len = len + 8; } } void sense_data_build_with_info(struct scsi_cmd *cmd, uint8_t key, uint16_t asc, uint64_t info) { sense_data_build(cmd, key, asc); if (cmd->dev->attrs.sense_format) { /* descriptor format, append as first sense data descriptor */ assert(cmd->sense_buffer[7] == 0); cmd->sense_buffer[7] = 12; /* ADDITIONAL SENSE LENGTH */ uint8_t *sdd = &cmd->sense_buffer[8]; sdd[0] = 0x00; /* DESCRIPTOR TYPE */ sdd[1] = 0x0a; /* ADDITIONAL LENGTH */ sdd[2] = 0x80; /* VALID */ put_unaligned_be64(info, &sdd[4]); /* INFORMATION */ } else if (info <= UINT32_MAX) { /* fixed format. info field is only 32-bit */ cmd->sense_buffer[0] |= 0x80; /* VALID */ put_unaligned_be32((uint32_t)info, &cmd->sense_buffer[3]); /* INFORMATION */ } } #define TGT_INVALID_DEV_ID ~0ULL static uint64_t __scsi_get_devid(uint8_t *p) { uint64_t lun = TGT_INVALID_DEV_ID; switch (*p >> 6) { case 0: lun = p[1]; break; case 1: lun = (0x3f & p[0]) << 8 | p[1]; break; case 2: case 3: default: break; } return lun; } uint64_t scsi_get_devid(int lid, uint8_t *p) { typeof(__scsi_get_devid) *fn; fn = tgt_drivers[lid]->scsi_get_lun ? : __scsi_get_devid; return fn(p); } uint64_t scsi_rw_offset(uint8_t *scb) { uint64_t off; switch (scb[0]) { case READ_6: case WRITE_6: off = ((scb[1] & 0x1f) << 16) + (scb[2] << 8) + scb[3]; break; case READ_10: case PRE_FETCH_10: case WRITE_10: case VERIFY_10: case WRITE_VERIFY: case WRITE_SAME: case SYNCHRONIZE_CACHE: case READ_12: case WRITE_12: case VERIFY_12: case WRITE_VERIFY_12: off = (uint32_t)scb[2] << 24 | (uint32_t)scb[3] << 16 | (uint32_t)scb[4] << 8 | (uint32_t)scb[5]; break; case READ_16: case PRE_FETCH_16: case WRITE_16: case ORWRITE_16: case VERIFY_16: case WRITE_VERIFY_16: case WRITE_SAME_16: case SYNCHRONIZE_CACHE_16: case COMPARE_AND_WRITE: off = (uint64_t)scb[2] << 56 | (uint64_t)scb[3] << 48 | (uint64_t)scb[4] << 40 | (uint64_t)scb[5] << 32 | (uint64_t)scb[6] << 24 | (uint64_t)scb[7] << 16 | (uint64_t)scb[8] << 8 | (uint64_t)scb[9]; break; default: off = 0; break; } return off; } uint32_t scsi_rw_count(uint8_t *scb) { uint32_t cnt; switch (scb[0]) { case READ_6: case WRITE_6: cnt = scb[4]; if (!cnt) cnt = 256; break; case READ_10: case PRE_FETCH_10: case WRITE_10: case VERIFY_10: case WRITE_VERIFY: case WRITE_SAME: case SYNCHRONIZE_CACHE: cnt = (uint16_t)scb[7] << 8 | (uint16_t)scb[8]; break; case READ_12: case WRITE_12: case VERIFY_12: case WRITE_VERIFY_12: cnt = (uint32_t)scb[6] << 24 | (uint32_t)scb[7] << 16 | (uint32_t)scb[8] << 8 | (uint32_t)scb[9]; break; case READ_16: case PRE_FETCH_16: case WRITE_16: case ORWRITE_16: case VERIFY_16: case WRITE_VERIFY_16: case WRITE_SAME_16: case SYNCHRONIZE_CACHE_16: cnt = (uint32_t)scb[10] << 24 | (uint32_t)scb[11] << 16 | (uint32_t)scb[12] << 8 | (uint32_t)scb[13]; break; case COMPARE_AND_WRITE: cnt = (uint32_t)scb[13]; break; default: cnt = 0; break; } return cnt; } int scsi_cmd_perform(int host_no, struct scsi_cmd *cmd) { int ret; unsigned char op = cmd->scb[0]; struct it_nexus_lu_info *itn_lu; if (scsi_get_data_dir(cmd) == DATA_WRITE) { cmd->itn_lu_info->stat.wr_subm_bytes += scsi_get_out_length(cmd); cmd->itn_lu_info->stat.wr_subm_cmds++; } else if (scsi_get_data_dir(cmd) == DATA_READ) { cmd->itn_lu_info->stat.rd_subm_bytes += scsi_get_in_length(cmd); cmd->itn_lu_info->stat.rd_subm_cmds++; } else if (scsi_get_data_dir(cmd) == DATA_BIDIRECTIONAL) { cmd->itn_lu_info->stat.wr_subm_bytes += scsi_get_out_length(cmd); cmd->itn_lu_info->stat.rd_subm_bytes += scsi_get_in_length(cmd); cmd->itn_lu_info->stat.bidir_subm_cmds++; } if (CDB_CONTROL(cmd) & ((1U << 0) | (1U << 2))) { /* * We don't support a linked command. SAM-3 say that * it's optional. It's obsolete in SAM-4. */ /* * We don't support ACA. SAM-3 and SAM-4 say that a * logical unit MAY support ACA. */ sense_data_build(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB); return SAM_STAT_CHECK_CONDITION; } if (cmd->dev->lun != cmd->dev_id) { switch (op) { case INQUIRY: break; case REQUEST_SENSE: sense_data_build(cmd, ILLEGAL_REQUEST, ASC_LUN_NOT_SUPPORTED); return SAM_STAT_GOOD; default: sense_data_build(cmd, ILLEGAL_REQUEST, ASC_LUN_NOT_SUPPORTED); return SAM_STAT_CHECK_CONDITION; } } /* check out Unit Attention condition */ switch (op) { case INQUIRY: break; case REPORT_LUNS: list_for_each_entry(itn_lu, &cmd->it_nexus->itn_itl_info_list, itn_itl_info_siblings) ua_sense_clear(itn_lu, ASC_REPORTED_LUNS_DATA_HAS_CHANGED); break; case REQUEST_SENSE: ret = ua_sense_del(cmd, 0); if (!ret) return SAM_STAT_CHECK_CONDITION; break; default: /* FIXME: use UA_INTLCK_CTRL field. */ ret = ua_sense_del(cmd, 1); if (!ret) return SAM_STAT_CHECK_CONDITION; } if (spc_access_check(cmd)) return SAM_STAT_RESERVATION_CONFLICT; if (!is_bs_support_opcode(cmd->dev->bst, op)) { sense_data_build(cmd, ILLEGAL_REQUEST, ASC_INVALID_OP_CODE); return SAM_STAT_CHECK_CONDITION; } return cmd->dev->dev_type_template.ops[op].cmd_perform(host_no, cmd); } int scsi_is_io_opcode(unsigned char op) { int ret = 0; switch (op) { case READ_6: case WRITE_6: case READ_10: case WRITE_10: case VERIFY_10: case WRITE_VERIFY: case READ_12: case WRITE_12: case VERIFY_12: case WRITE_VERIFY_12: case READ_16: case WRITE_16: case ORWRITE_16: case VERIFY_16: case WRITE_VERIFY_16: case COMPARE_AND_WRITE: ret = 1; break; default: break; } return ret; } /* this isn't complete but good enough for what kernel drivers need */ enum data_direction scsi_data_dir_opcode(unsigned char op) { enum data_direction dir; switch (op) { case WRITE_6: case WRITE_10: case WRITE_12: case WRITE_16: case ORWRITE_16: case WRITE_VERIFY: case WRITE_VERIFY_12: case WRITE_VERIFY_16: case COMPARE_AND_WRITE: dir = DATA_WRITE; break; default: dir = DATA_READ; break; } return dir; } tgt-1.0.85/usr/scsi.h000066400000000000000000000220211435417276200143360ustar00rootroot00000000000000/* * taken from kernel.h * * better if we include kernel's one directly. */ #ifndef __SCSI_H #define __SCSI_H #define TEST_UNIT_READY 0x00 #define REZERO_UNIT 0x01 #define REQUEST_SENSE 0x03 #define FORMAT_UNIT 0x04 #define READ_BLOCK_LIMITS 0x05 #define REASSIGN_BLOCKS 0x07 #define INITIALIZE_ELEMENT_STATUS 0x07 #define READ_6 0x08 #define WRITE_6 0x0a #define SEEK_6 0x0b #define READ_REVERSE 0x0f #define WRITE_FILEMARKS 0x10 #define SPACE 0x11 #define INQUIRY 0x12 #define RECOVER_BUFFERED_DATA 0x14 #define MODE_SELECT 0x15 #define RESERVE 0x16 #define RELEASE 0x17 #define COPY 0x18 #define ERASE 0x19 #define MODE_SENSE 0x1a #define START_STOP 0x1b #define RECEIVE_DIAGNOSTIC 0x1c #define SEND_DIAGNOSTIC 0x1d #define ALLOW_MEDIUM_REMOVAL 0x1e #define SET_WINDOW 0x24 #define READ_CAPACITY 0x25 #define READ_10 0x28 #define WRITE_10 0x2a #define SEEK_10 0x2b #define POSITION_TO_ELEMENT 0x2b #define WRITE_VERIFY 0x2e #define VERIFY_10 0x2f #define SEARCH_HIGH 0x30 #define SEARCH_EQUAL 0x31 #define SEARCH_LOW 0x32 #define SET_LIMITS 0x33 #define PRE_FETCH_10 0x34 #define READ_POSITION 0x34 #define SYNCHRONIZE_CACHE 0x35 #define LOCK_UNLOCK_CACHE 0x36 #define READ_DEFECT_DATA 0x37 #define INITIALIZE_ELEMENT_STATUS_WITH_RANGE 0x37 #define MEDIUM_SCAN 0x38 #define COMPARE 0x39 #define COPY_VERIFY 0x3a #define WRITE_BUFFER 0x3b #define READ_BUFFER 0x3c #define UPDATE_BLOCK 0x3d #define READ_LONG 0x3e #define WRITE_LONG 0x3f #define CHANGE_DEFINITION 0x40 #define WRITE_SAME 0x41 #define UNMAP 0x42 #define READ_TOC 0x43 #define GET_CONFIGURATION 0x46 #define LOG_SELECT 0x4c #define LOG_SENSE 0x4d #define READ_DISK_INFO 0x51 #define READ_TRACK_INFO 0x52 #define MODE_SELECT_10 0x55 #define RESERVE_10 0x56 #define RELEASE_10 0x57 #define MODE_SENSE_10 0x5a #define CLOSE_TRACK 0x5b #define READ_BUFFER_CAP 0x5c #define PERSISTENT_RESERVE_IN 0x5e #define PERSISTENT_RESERVE_OUT 0x5f #define VARLEN_CDB 0x7f #define READ_16 0x88 #define COMPARE_AND_WRITE 0x89 #define WRITE_16 0x8a #define ORWRITE_16 0x8b #define WRITE_VERIFY_16 0x8e #define VERIFY_16 0x8f #define PRE_FETCH_16 0x90 #define SYNCHRONIZE_CACHE_16 0x91 #define WRITE_SAME_16 0x93 #define SERVICE_ACTION_IN 0x9e #define SAI_READ_CAPACITY_16 0x10 #define SAI_GET_LBA_STATUS 0x12 #define REPORT_LUNS 0xa0 #define MAINT_PROTOCOL_IN 0xa3 #define MOVE_MEDIUM 0xa5 #define EXCHANGE_MEDIUM 0xa6 #define READ_12 0xa8 #define WRITE_12 0xaa #define GET_PERFORMACE 0xac #define READ_DVD_STRUCTURE 0xad #define WRITE_VERIFY_12 0xae #define VERIFY_12 0xaf #define SEARCH_HIGH_12 0xb0 #define SEARCH_EQUAL_12 0xb1 #define SEARCH_LOW_12 0xb2 #define READ_ELEMENT_STATUS 0xb8 #define SEND_VOLUME_TAG 0xb6 #define SET_STREAMING 0xb6 #define SET_CD_SPEED 0xbb #define WRITE_LONG_2 0xea /* Service actions for opcode 0xa3 */ #define MPI_REPORT_SUPPORTED_OPCODES 0x0c #define SAM_STAT_GOOD 0x00 #define SAM_STAT_CHECK_CONDITION 0x02 #define SAM_STAT_CONDITION_MET 0x04 #define SAM_STAT_BUSY 0x08 #define SAM_STAT_INTERMEDIATE 0x10 #define SAM_STAT_INTERMEDIATE_CONDITION_MET 0x14 #define SAM_STAT_RESERVATION_CONFLICT 0x18 #define SAM_STAT_COMMAND_TERMINATED 0x22 #define SAM_STAT_TASK_SET_FULL 0x28 #define SAM_STAT_ACA_ACTIVE 0x30 #define SAM_STAT_TASK_ABORTED 0x40 #define NO_SENSE 0x00 #define RECOVERED_ERROR 0x01 #define NOT_READY 0x02 #define MEDIUM_ERROR 0x03 #define HARDWARE_ERROR 0x04 #define ILLEGAL_REQUEST 0x05 #define UNIT_ATTENTION 0x06 #define DATA_PROTECT 0x07 #define BLANK_CHECK 0x08 #define COPY_ABORTED 0x0a #define ABORTED_COMMAND 0x0b #define VOLUME_OVERFLOW 0x0d #define MISCOMPARE 0x0e #define TYPE_DISK 0x00 #define TYPE_TAPE 0x01 #define TYPE_PRINTER 0x02 #define TYPE_PROCESSOR 0x03 #define TYPE_WORM 0x04 #define TYPE_MMC 0x05 #define TYPE_SCANNER 0x06 #define TYPE_MOD 0x07 #define TYPE_MEDIUM_CHANGER 0x08 #define TYPE_COMM 0x09 #define TYPE_RAID 0x0c #define TYPE_ENCLOSURE 0x0d #define TYPE_RBC 0x0e #define TYPE_OSD 0x11 #define TYPE_NO_LUN 0x7f #define TYPE_PT 0xff #define MSG_SIMPLE_TAG 0x20 #define MSG_HEAD_TAG 0x21 #define MSG_ORDERED_TAG 0x22 #define ABORT_TASK 0x0d #define ABORT_TASK_SET 0x06 #define CLEAR_ACA 0x16 #define CLEAR_TASK_SET 0x0e #define LOGICAL_UNIT_RESET 0x17 #define TASK_ABORTED 0x20 #define SAM_STAT_TASK_ABORTED 0x40 /* Key 0: No Sense Errors */ #define NO_ADDITIONAL_SENSE 0x0000 #define ASC_MARK 0x0001 #define ASC_EOM 0x0002 #define ASC_BOM 0x0004 #define ASC_END_OF_DATA 0x0005 #define ASC_OP_IN_PROGRESS 0x0016 #define ASC_DRIVE_REQUIRES_CLEANING 0x8282 /* Key 1: Recovered Errors */ #define ASC_WRITE_ERROR 0x0c00 #define ASC_READ_ERROR 0x1100 #define ASC_RECOVERED_WITH_RETRYS 0x1701 #define ASC_MEDIA_LOAD_EJECT_ERROR 0x5300 #define ASC_FAILURE_PREDICTION 0x5d00 /* Key 2: Not ready */ #define ASC_CAUSE_NOT_REPORTABLE 0x0400 #define ASC_BECOMING_READY 0x0401 #define ASC_INITIALIZING_REQUIRED 0x0402 #define ASC_CLEANING_CART_INSTALLED 0x3003 #define ASC_CLEANING_FAILURE 0x3007 #define ASC_MEDIUM_NOT_PRESENT 0x3a00 #define ASC_LOGICAL_UNIT_NOT_CONFIG 0x3e00 /* Key 3: Medium Errors */ #define ASC_WRITE_ERROR 0x0c00 #define ASC_UNRECOVERED_READ 0x1100 #define ASC_RECORDED_ENTITY_NOT_FOUND 0x1400 #define ASC_UNKNOWN_FORMAT 0x3001 #define ASC_IMCOMPATIBLE_FORMAT 0x3002 #define ASC_MEDIUM_FORMAT_CORRUPT 0x3100 #define ASC_SEQUENTIAL_POSITION_ERR 0x3b00 #define ASC_WRITE_APPEND_ERR 0x5000 #define ASC_CARTRIDGE_FAULT 0x5200 #define ASC_MEDIA_LOAD_OR_EJECT_FAILED 0x5300 /* Key 4: Hardware Failure */ #define ASC_COMPRESSION_CHECK 0x0c04 #define ASC_DECOMPRESSION_CRC 0x110d #define ASC_MECHANICAL_POSITIONING_ERROR 0x1501 #define ASC_MANUAL_INTERVENTION_REQ 0x0403 #define ASC_HARDWARE_FAILURE 0x4000 #define ASC_INTERNAL_TGT_FAILURE 0x4400 #define ASC_ERASE_FAILURE 0x5100 /* Key 5: Illegal Request */ #define ASC_PARAMETER_LIST_LENGTH_ERR 0x1a00 #define ASC_INVALID_OP_CODE 0x2000 #define ASC_LBA_OUT_OF_RANGE 0x2100 #define ASC_INVALID_FIELD_IN_CDB 0x2400 #define ASC_LUN_NOT_SUPPORTED 0x2500 #define ASC_INVALID_FIELD_IN_PARMS 0x2600 #define ASC_INVALID_RELEASE_OF_PERSISTENT_RESERVATION 0x2604 #define ASC_INCOMPATIBLE_FORMAT 0x3005 #define ASC_SAVING_PARMS_UNSUP 0x3900 #define ASC_MEDIUM_DEST_FULL 0x3b0d #define ASC_MEDIUM_SRC_EMPTY 0x3b0e #define ASC_POSITION_PAST_BOM 0x3b0c #define ASC_MEDIUM_REMOVAL_PREVENTED 0x5302 #define ASC_INSUFFICENT_REGISTRATION_RESOURCES 0x5504 #define ASC_BAD_MICROCODE_DETECTED 0x8283 /* Key 6: Unit Attention */ #define ASC_NOT_READY_TO_TRANSITION 0x2800 #define ASC_POWERON_RESET 0x2900 #define ASC_I_T_NEXUS_LOSS_OCCURRED 0x2907 #define ASC_MODE_PARAMETERS_CHANGED 0x2a01 #define ASC_RESERVATIONS_PREEMPTED 0x2a03 #define ASC_RESERVATIONS_RELEASED 0x2a04 #define ASC_INSUFFICIENT_TIME_FOR_OPERATION 0x2e00 #define ASC_CMDS_CLEARED_BY_ANOTHER_INI 0x2f00 #define ASC_MICROCODE_DOWNLOADED 0x3f01 #define ASC_INQUIRY_DATA_HAS_CHANGED 0x3f03 #define ASC_REPORTED_LUNS_DATA_HAS_CHANGED 0x3f0e #define ASC_FAILURE_PREDICTION_FALSE 0x5dff /* Data Protect */ #define ASC_WRITE_PROTECT 0x2700 #define ASC_MEDIUM_OVERWRITE_ATTEMPTED 0x300c /* Miscompare */ #define ASC_MISCOMPARE_DURING_VERIFY_OPERATION 0x1d00 /* PERSISTENT_RESERVE_IN service action codes */ #define PR_IN_READ_KEYS 0x00 #define PR_IN_READ_RESERVATION 0x01 #define PR_IN_REPORT_CAPABILITIES 0x02 #define PR_IN_READ_FULL_STATUS 0x03 /* PERSISTENT_RESERVE_OUT service action codes */ #define PR_OUT_REGISTER 0x00 #define PR_OUT_RESERVE 0x01 #define PR_OUT_RELEASE 0x02 #define PR_OUT_CLEAR 0x03 #define PR_OUT_PREEMPT 0x04 #define PR_OUT_PREEMPT_AND_ABORT 0x05 #define PR_OUT_REGISTER_AND_IGNORE_EXISTING_KEY 0x06 #define PR_OUT_REGISTER_AND_MOVE 0x07 /* Persistent Reservation scope */ #define PR_LU_SCOPE 0x00 /* Persistent Reservation Type Mask format */ #define PR_TYPE_WRITE_EXCLUSIVE 0x01 #define PR_TYPE_EXCLUSIVE_ACCESS 0x03 #define PR_TYPE_WRITE_EXCLUSIVE_REGONLY 0x05 #define PR_TYPE_EXCLUSIVE_ACCESS_REGONLY 0x06 #define PR_TYPE_WRITE_EXCLUSIVE_ALLREG 0x07 #define PR_TYPE_EXCLUSIVE_ACCESS_ALLREG 0x08 #endif tgt-1.0.85/usr/scsi_cmnd.h000066400000000000000000000067451435417276200153560ustar00rootroot00000000000000struct target; struct mgmt_req; /* needs to move somewhere else */ #define SCSI_SENSE_BUFFERSIZE 252 enum data_direction { DATA_NONE = 0, DATA_WRITE = 1, DATA_READ = 2, DATA_BIDIRECTIONAL = 3, }; struct scsi_data_buffer { uint64_t buffer; uint32_t length; uint32_t transfer_len; int32_t resid; }; struct scsi_cmd { struct target *c_target; /* linked it_nexus->cmd_hash_list */ struct list_head c_hlist; struct list_head qlist; uint64_t dev_id; struct scsi_lu *dev; unsigned long state; enum data_direction data_dir; struct scsi_data_buffer in_sdb; struct scsi_data_buffer out_sdb; uint64_t cmd_itn_id; uint64_t offset; uint32_t tl; uint8_t *scb; int scb_len; uint8_t lun[8]; int attribute; uint64_t tag; int result; struct mgmt_req *mreq; unsigned char sense_buffer[SCSI_SENSE_BUFFERSIZE]; int sense_len; struct list_head bs_list; struct it_nexus *it_nexus; struct it_nexus_lu_info *itn_lu_info; }; #define scsi_cmnd_accessor(field, type) \ static inline void scsi_set_##field(struct scsi_cmd *scmd, type val) \ { \ scmd->field = val; \ } \ static inline type scsi_get_##field(struct scsi_cmd *scmd) \ { \ return scmd->field; \ } scsi_cmnd_accessor(result, int); scsi_cmnd_accessor(data_dir, enum data_direction); #define scsi_data_buffer_accessor(field, type, set_cast, get_cast) \ scsi_data_buffer_function(in, field, type, set_cast, get_cast) \ scsi_data_buffer_function(out, field, type, set_cast, get_cast) #define scsi_data_buffer_function(dir, field, type, set_cast, get_cast) \ static inline void scsi_set_##dir##_##field(struct scsi_cmd *scmd, type val) \ { \ scmd->dir##_sdb.field = set_cast (val); \ } \ static inline type scsi_get_##dir##_##field(struct scsi_cmd *scmd) \ { \ return get_cast (scmd->dir##_sdb.field); \ } scsi_data_buffer_accessor(length, uint32_t, ,); scsi_data_buffer_accessor(transfer_len, uint32_t, ,); scsi_data_buffer_accessor(resid, int32_t, ,); scsi_data_buffer_accessor(buffer, void *, (unsigned long), (void *)(unsigned long)); static inline void scsi_set_in_resid_by_actual(struct scsi_cmd *scmd, uint32_t transfer_len) { uint32_t expected_len = scsi_get_in_length(scmd); int32_t resid; if (transfer_len <= expected_len) resid = expected_len - transfer_len; else { resid = -(int32_t)(transfer_len - expected_len); transfer_len = expected_len; } scsi_set_in_transfer_len(scmd, transfer_len); scsi_set_in_resid(scmd, resid); } static inline void scsi_set_out_resid_by_actual(struct scsi_cmd *scmd, uint32_t transfer_len) { uint32_t expected_len = scsi_get_out_length(scmd); int32_t resid; if (transfer_len <= expected_len) resid = expected_len - transfer_len; else { resid = -(int32_t)(transfer_len - expected_len); transfer_len = expected_len; } scsi_set_out_transfer_len(scmd, transfer_len); scsi_set_out_resid(scmd, resid); } enum { TGT_CMD_QUEUED, TGT_CMD_PROCESSED, TGT_CMD_ASYNC, TGT_CMD_NOT_LAST, }; #define CMD_FNS(bit, name) \ static inline void set_cmd_##name(struct scsi_cmd *c) \ { \ (c)->state |= (1UL << TGT_CMD_##bit); \ } \ static inline void clear_cmd_##name(struct scsi_cmd *c) \ { \ (c)->state &= ~(1UL << TGT_CMD_##bit); \ } \ static inline int cmd_##name(const struct scsi_cmd *c) \ { \ return ((c)->state & (1UL << TGT_CMD_##bit)); \ } CMD_FNS(QUEUED, queued) CMD_FNS(PROCESSED, processed) CMD_FNS(ASYNC, async) CMD_FNS(NOT_LAST, not_last) tgt-1.0.85/usr/smc.c000066400000000000000000000607501435417276200141650ustar00rootroot00000000000000/* * SCSI Medium Changer command processing * Based on smc3r06.pdf document from t10.org * * (C) 2004-2007 FUJITA Tomonori * (C) 2005-2007 Mike Christie * (C) 2007 Mark Harvey * (C) 2012 Ronnie Sahlberg * * SCSI target emulation code is based on Ardis's iSCSI implementation. * http://www.ardistech.com/iscsi/ * Copyright (C) 2002-2003 Ardis Technolgies , * licensed under the terms of the GNU GPL v2.0, * * 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, version 2 of the * License. * * 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 "list.h" #include "util.h" #include "tgtd.h" #include "target.h" #include "driver.h" #include "tgtadm_error.h" #include "scsi.h" #include "spc.h" #include "parser.h" #include "smc.h" #include "media.h" static int check_slot_removable(struct slot *s) { tgtadm_err adm_err = dtd_check_removable(s->drive_tid, s->drive_lun); return (adm_err == TGTADM_SUCCESS ? 0 : -EINVAL); } static int set_slot_full(struct slot *s, uint16_t src, char *path) { int err = 0; if (path) err = dtd_load_unload(s->drive_tid, s->drive_lun, LOAD, path); if (err) return err; s->status |= 1; s->last_addr = src; return err; } static void set_slot_empty(struct slot *s) { s->status &= 0xfe; s->last_addr = 0; memset(s->barcode, ' ', sizeof(s->barcode)); memset(s->volume_tag, ' ', sizeof(s->volume_tag)); if (s->element_type == ELEMENT_DATA_TRANSFER) dtd_load_unload(s->drive_tid, s->drive_lun, UNLOAD, NULL); } static int test_slot_full(struct slot *s) { return s->status & 1; } /** * determine_element_sz -- read element status * @dvcid Device ID - true, return device ID information * @voltag true, return Volume Tag (volume_tag) * * Ref: Working Draft SCSI Media Changer-3 (smc3r06.pdf), chapter 6.10 * * The READ ELEMENT STATUS command request that the device server * report the status of its internal elements to the application * client. Support for READ ELEMENT STATUS command is mandatory. */ static int determine_element_sz(uint8_t dvcid, uint8_t voltag) { if (voltag) return (dvcid) ? 86 : 52; else return (dvcid) ? 50 : 16; } /** * element_status_data_hdr -- Fill in Element Status Header * @data uint8_t * - data pointer * @dvcid Device ID - true, return device ID information * @voltag true, return Volume Tag (volume_tag) * @start Start searching from slot 'start' * @count and return 'count' elements * * This builds the ELEMENT STATUS DATA header information built * from the params passed. * * DATA header is always 8 bytes long */ static int element_status_data_hdr(uint8_t *data, uint8_t dvcid, uint8_t voltag, int start, int count) { int element_sz; int size; element_sz = determine_element_sz(dvcid, voltag); /* First Element address reported */ *(uint16_t *)(data) = __cpu_to_be16(start); /* Number of elements available */ *(uint16_t *)(data + 2) = __cpu_to_be16(count); /* Byte count is the length required to return all valid data. * Allocated length is how much data the initiator will accept */ size = ((8 + (count * element_sz)) & 0xffffff); *(uint32_t *)(data + 4) = __cpu_to_be32(size); return size; } static int add_element_descriptor(uint8_t *data, struct slot *s, uint8_t element_type, uint8_t dvcid, uint8_t voltag) { struct lu_phy_attr *attr = NULL; int i; /* data[] index */ *(uint16_t *)(data) = __cpu_to_be16(s->slot_addr); data[2] = s->status; data[3] = 0; /* Reserved */ data[4] = (s->asc >> 8) & 0xff; /* Additional Sense Code */ data[5] = s->asc & 0xff; /* Additional Sense Code Qualifier */ /* [6], [7] & [8] reserved */ data[9] = (s->cart_type & 0xf); if (s->last_addr) { /* Source address is valid ? */ data[9] |= 0x80; *(uint16_t *)(data + 10) = __cpu_to_be16(s->last_addr); } i = 12; if (voltag) { if (s->volume_tag[0] != ' ' && s->volume_tag[0] != '\0') scsi_sprintf((char *)&data[i], 32, "%-32s", s->volume_tag); else if (s->barcode[0] != ' ' && s->barcode[0] != '\0') scsi_sprintf((char *)&data[i], 32, "%-32s", s->barcode); else memset(&data[i], 0x20, 32); /* Reserve additional 4 bytes if dvcid is set */ i += (dvcid) ? 36 : 32; } if (element_type == ELEMENT_DATA_TRANSFER) attr = lu_attr_lookup(s->drive_tid, s->drive_lun); if (dvcid && attr) { data[i] = 2; /* ASCII code set */ data[i + 1] = attr->device_type; data[i + 2] = 0; /* reserved */ data[i + 3] = 34; /* Length */ snprintf((char *)&data[i + 4], 9, "%-8s", attr->vendor_id); snprintf((char *)&data[i + 12], 17, "%-16s", attr->product_id); { char buf[sizeof(attr->scsi_sn) + 1]; memset(buf, 0, sizeof(buf)); snprintf(buf, sizeof(buf), "%-s", attr->scsi_sn); memcpy((char *)&data[i + 28], buf, 10); } } return determine_element_sz(dvcid, voltag); } /** * build_element_descriptor -- Fill in Element details * @data; pointer * @head; Slot struct head * @element_type; Slot type we are interested in. * @first: Return address of first slot found * @start; Start processing from this element # * @dvcid; Device ID * @voltag; Volume tag (volume_tag) * * Fill each Element Descriptor for slot *s * Return number of elements */ static int build_element_descriptors(uint8_t *data, struct list_head *head, uint8_t elem_type, int *first, uint16_t start, uint8_t dvcid, uint8_t voltag) { struct slot *s; int count = 0; int len = 8; int elem_sz = determine_element_sz(dvcid, voltag); list_for_each_entry(s, head, slot_siblings) { if (s->element_type == elem_type) { if (s->slot_addr >= start) { count++; len += add_element_descriptor(&data[len], s, elem_type, dvcid, voltag); } } if (count == 1) /* Record first found slot Address */ *first = s->slot_addr; } /* Fill in Element Status Page Header */ data[0] = elem_type; data[1] = (voltag) ? 0x80 : 0; *(uint16_t *)(data + 2) = __cpu_to_be16(elem_sz); /* Total number of bytes in all element descriptors */ *(uint32_t *)(data + 4) = __cpu_to_be32((elem_sz * count) & 0xffffff); return count; } /** * smc_initialize_element_status with range * - INITIALIZE ELEMENT STATUS WITH RANGE op code * * Support the SCSI op code INITIALIZE_ELEMENT_STATUS_WITH_RANGE * Ref: smc3r11, 6.5 */ static int smc_initialize_element_status_range(int host_no, struct scsi_cmd *cmd) { scsi_set_in_resid_by_actual(cmd, 0); if (device_reserved(cmd)) return SAM_STAT_RESERVATION_CONFLICT; else return SAM_STAT_GOOD; } /** * smc_initialize_element_status - INITIALIZE ELEMENT STATUS op code * * Some backup libraries seem to require this. * * Support the SCSI op code INITIALIZE_ELEMENT_STATUS * Ref: smc3r10a, 6.2 */ static int smc_initialize_element_status(int host_no, struct scsi_cmd *cmd) { scsi_set_in_resid_by_actual(cmd, 0); if (device_reserved(cmd)) return SAM_STAT_RESERVATION_CONFLICT; else return SAM_STAT_GOOD; } static int nr_slots(struct smc_info *smc) { int count = 0; struct slot *s; list_for_each_entry(s, &smc->slots, slot_siblings) count++; return count; } /** * smc_read_element_status - READ ELEMENT STATUS op code * * Support the SCSI op code READ ELEMENT STATUS * Ref: smc3r06, 6.10 */ static int smc_read_element_status(int host_no, struct scsi_cmd *cmd) { struct smc_info *smc = dtype_priv(cmd->dev); uint8_t *data = NULL; uint8_t *scb; uint8_t element_type; uint8_t voltag; uint16_t req_start_elem; uint8_t dvcid; int alloc_len; uint16_t count = 0; int first = 0; /* First valid slot location */ int len = 8; int elementSize; int ret; unsigned char key = ILLEGAL_REQUEST; uint16_t asc = ASC_INVALID_FIELD_IN_CDB; scb = cmd->scb; element_type = scb[1] & 0x0f; voltag = (scb[1] & 0x10) >> 4; dvcid = scb[6] & 0x01; req_start_elem = __be16_to_cpu(*(uint16_t *)(scb + 2)); alloc_len = 0xffffff & __be32_to_cpu(*(uint32_t *)(scb + 6)); if (scsi_get_in_length(cmd) < alloc_len) goto sense; elementSize = determine_element_sz(dvcid, voltag); scsi_set_in_resid_by_actual(cmd, 0); if (cmd->dev) { ret = device_reserved(cmd); if (ret) { dprintf("Reservation Conflict\n"); return SAM_STAT_RESERVATION_CONFLICT; } } /* we allocate possible maximum data length */ data = zalloc(8 + elementSize * nr_slots(smc)); if (!data) { dprintf("Can't allocate enough memory for cmd\n"); key = HARDWARE_ERROR; asc = ASC_INTERNAL_TGT_FAILURE; goto sense; } if (scb[11]) /* Reserved byte */ goto sense; switch(element_type) { case ELEMENT_ANY: /* Return element in type order */ count = build_element_descriptors(&data[len], &smc->slots, ELEMENT_MEDIUM_TRANSPORT, &first, req_start_elem, dvcid, voltag); len = count * elementSize; count += build_element_descriptors(&data[len], &smc->slots, ELEMENT_STORAGE, &first, req_start_elem, dvcid, voltag); len += count * elementSize; count += build_element_descriptors(&data[len], &smc->slots, ELEMENT_MAP, &first, req_start_elem, dvcid, voltag); len += count * elementSize; count += build_element_descriptors(&data[len], &smc->slots, ELEMENT_DATA_TRANSFER, &first, req_start_elem, dvcid, voltag); break; case ELEMENT_MEDIUM_TRANSPORT: count = build_element_descriptors(&data[len], &smc->slots, ELEMENT_MEDIUM_TRANSPORT, &first, req_start_elem, dvcid, voltag); break; case ELEMENT_STORAGE: count = build_element_descriptors(&data[len], &smc->slots, ELEMENT_STORAGE, &first, req_start_elem, dvcid, voltag); break; case ELEMENT_MAP: count = build_element_descriptors(&data[len], &smc->slots, ELEMENT_MAP, &first, req_start_elem, dvcid, voltag); break; case ELEMENT_DATA_TRANSFER: count = build_element_descriptors(&data[len], &smc->slots, ELEMENT_DATA_TRANSFER, &first, req_start_elem, dvcid, voltag); break; default: goto sense; break; } /* Lastly, fill in data header */ len = element_status_data_hdr(data, dvcid, voltag, first, count); memcpy(scsi_get_in_buffer(cmd), data, min(len, alloc_len)); scsi_set_in_resid_by_actual(cmd, len); free(data); return SAM_STAT_GOOD; sense: if (data) free(data); scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } /** * smc_move_medium - MOVE MEDIUM op code * * Support the SCSI op code MOVE MEDIUM * Ref: smc3r06, 6.6 */ static int smc_move_medium(int host_no, struct scsi_cmd *cmd) { struct smc_info *smc = dtype_priv(cmd->dev); uint8_t *scb; uint16_t src; uint16_t dest; uint8_t invert; struct slot *src_slot = NULL; struct slot *dest_slot = NULL; struct slot *s; int key = ILLEGAL_REQUEST; uint16_t asc = ASC_INVALID_FIELD_IN_CDB; scb = cmd->scb; src = __be16_to_cpu(*(uint16_t *)(scb + 4)); dest = __be16_to_cpu(*(uint16_t *)(scb + 6)); invert = scb[10] & 1; list_for_each_entry(s, &smc->slots, slot_siblings) { if (s->slot_addr == src) src_slot = s; if (s->slot_addr == dest) dest_slot = s; } if (src_slot) { if (!test_slot_full(src_slot)) { asc = ASC_MEDIUM_SRC_EMPTY; goto sense; } } else /* Could not find src slot - Error */ goto sense; if (dest_slot) { if (test_slot_full(dest_slot)) { asc = ASC_MEDIUM_DEST_FULL; goto sense; } } else /* Could not find dest slot - Error */ goto sense; if (invert && (s->sides == 1)) /* Use default INVALID FIELD IN CDB */ goto sense; if (src_slot->element_type == ELEMENT_DATA_TRANSFER) { if (check_slot_removable(src_slot)) { key = ILLEGAL_REQUEST; asc = ASC_MEDIUM_REMOVAL_PREVENTED; goto sense; } } memcpy(&dest_slot->barcode, &src_slot->barcode, sizeof(s->barcode)); memcpy(&dest_slot->volume_tag, &src_slot->volume_tag, sizeof(s->volume_tag)); if (dest_slot->element_type == ELEMENT_DATA_TRANSFER) { char path[128]; int sz; sz = snprintf(path, sizeof(path), "%s/%s", smc->media_home, dest_slot->barcode); if (sz >= sizeof(path)) { dprintf("Path too long: %s\n", path); key = ILLEGAL_REQUEST; asc = ASC_INTERNAL_TGT_FAILURE; memset(&dest_slot->barcode, ' ', sizeof(s->barcode)); memset(&dest_slot->volume_tag, ' ', sizeof(s->volume_tag)); goto sense; } if (set_slot_full(dest_slot, src, path)) { key = HARDWARE_ERROR; asc = ASC_MECHANICAL_POSITIONING_ERROR; goto sense; } } else set_slot_full(dest_slot, src, NULL); set_slot_empty(src_slot); scsi_set_in_resid_by_actual(cmd, 0); return SAM_STAT_GOOD; sense: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } static tgtadm_err smc_lu_init(struct scsi_lu *lu) { struct backingstore_template *bst; struct smc_info *smc; tgtadm_err adm_err; smc = zalloc(sizeof(struct smc_info)); if (smc) dtype_priv(lu) = smc; else return TGTADM_NOMEM; if (spc_lu_init(lu)) return TGTADM_NOMEM; /* SMC devices always use smc backingstore */ bst = get_backingstore_template("smc"); if (!bst) { eprintf("failed to find bstype, smc\n"); return TGTADM_INVALID_REQUEST; } lu->bst = bst; strncpy(lu->attrs.product_id, "VIRTUAL-CHANGER", sizeof(lu->attrs.product_id)); lu->attrs.version_desc[0] = 0x0480; /* SMC-3 no version claimed */ lu->attrs.version_desc[1] = 0x0960; /* iSCSI */ lu->attrs.version_desc[2] = 0x0300; /* SPC-3 */ /* Vendor uniq - However most apps seem to call for mode page 0*/ add_mode_page(lu, "0:0:0"); /* Control page */ add_mode_page(lu, "0x0a:0:10:2:0:0:0:0:0:0:0:2:0"); /* Control Extensions mode page: TCMOS:1 */ add_mode_page(lu, "0x0a:1:0x1c:0x04:0x00:0x00"); /* Power Condition */ add_mode_page(lu, "0x1a:0:10:8:0:0:0:0:0:0:0:0:0"); /* Informational Exceptions Control page */ add_mode_page(lu, "0x1c:0:10:8:0:0:0:0:0:0:0:0:0"); /* Address Assignment */ add_mode_page(lu, "0x1d:0:0x12:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0"); /* Transport Geometry Params */ add_mode_page(lu, "0x1e:0:2:0:0"); /* Device Capabilities */ add_mode_page(lu, "0x1f:0:0x12:15:7:15:15:15:15:0:0:0:0:15:15:15:15:0:0:0:0"); INIT_LIST_HEAD(&smc->slots); adm_err = lu->dev_type_template.lu_online(lu); /* Library will now report as Online */ lu->attrs.removable = 1; /* Default to removable media */ return adm_err; } static void smc_lu_exit(struct scsi_lu *lu) { struct smc_info *smc = dtype_priv(lu); dprintf("Medium Changer shutdown() called\n"); free(smc); } static tgtadm_err slot_insert(struct list_head *head, int element_type, int address) { struct slot *s; s = zalloc(sizeof(struct slot)); if (!s) return TGTADM_NOMEM; s->slot_addr = address; s->element_type = element_type; s->sides = 1; if (element_type == ELEMENT_DATA_TRANSFER) /* Drive */ s->asc = ASC_INITIALIZING_REQUIRED; list_add_tail(&s->slot_siblings, head); return TGTADM_SUCCESS; } static void slot_remove(struct slot *s) { list_del(&s->slot_siblings); free(s); } /** * slot_lookup -- Find slot of type 'element_type' & address 'address' * @element_type; Type of slot, 0 == Any type * @address; Slot address, 0 == First found slot * * Return NULL if no match */ static struct slot *slot_lookup(struct list_head *head, int element_type, int address) { struct slot *s; list_for_each_entry(s, head, slot_siblings) { if (element_type && address) { if ((s->slot_addr == address) && (s->element_type == element_type)) return s; } else if (element_type) { if (s->element_type == element_type) return s; } else if (address) { if (s->slot_addr == address) return s; } } return NULL; } static void slot_dump(struct list_head *head) { struct slot *s; list_for_each_entry(s, head, slot_siblings) if (s) { dprintf("Slot %d Information\n", s->slot_addr); dprintf(" Last Addr: %d\n", s->last_addr); dprintf(" Type: %d\n", s->element_type); dprintf(" Barcode: %s\n", s->barcode); dprintf(" Volume Tag: %s\n", s->volume_tag); if (s->drive_tid) { dprintf(" TID : %d\n", s->drive_tid); dprintf(" LUN : %" PRIu64 "\n", s->drive_lun); } dprintf(" ASC/ASCQ : %d\n\n", s->asc); } } static tgtadm_err add_slt(struct scsi_lu *lu, struct tmp_param *tmp) { struct smc_info *smc = dtype_priv(lu); tgtadm_err adm_err = TGTADM_INVALID_REQUEST; struct mode_pg *pg; struct slot *s; uint16_t *element; int sv_addr; int qnty_save; int i; pg = find_mode_page(lu, 0x1d, 0); if (!pg) { dprintf("Failed to find Element Address Assignment mode pg\n"); return TGTADM_UNKNOWN_ERR; } element = (uint16_t *)pg->mode_data; if (tmp->element_type && tmp->start_addr && tmp->quantity) { switch(tmp->element_type) { case ELEMENT_MEDIUM_TRANSPORT: break; case ELEMENT_MAP: element += 4; break; case ELEMENT_STORAGE: element += 2; break; case ELEMENT_DATA_TRANSFER: element += 6; break; default: goto dont_do_slots; break; } sv_addr = __be16_to_cpu(element[0]); qnty_save = __be16_to_cpu(element[1]); if (sv_addr) element[0] = __cpu_to_be16(min_t(int, tmp->start_addr, sv_addr)); else element[0] = __cpu_to_be16(tmp->start_addr); element[1] = __cpu_to_be16(tmp->quantity + qnty_save); s = slot_lookup(&smc->slots, tmp->element_type, tmp->start_addr); if (s) // Opps... Found a slot at this address.. goto dont_do_slots; adm_err = TGTADM_SUCCESS; for (i = tmp->start_addr; i < (tmp->start_addr + tmp->quantity); i++) { adm_err = slot_insert(&smc->slots, tmp->element_type, i); if (adm_err != TGTADM_SUCCESS) { int j; /* remove all slots added before error */ for (j = tmp->start_addr; j < i; j++) { s = slot_lookup(&smc->slots, tmp->element_type, j); slot_remove(s); } break; } } } dont_do_slots: return adm_err; } static tgtadm_err config_slot(struct scsi_lu *lu, struct tmp_param *tmp) { struct smc_info *smc = dtype_priv(lu); struct mode_pg *m = NULL; struct slot *s = NULL; int adm_err = TGTADM_INVALID_REQUEST; switch(tmp->element_type) { case ELEMENT_MEDIUM_TRANSPORT: /* If medium has more than one side, set the 'rotate' bit */ m = find_mode_page(lu, 0x1e, 0); if (m) { m->mode_data[0] = (tmp->sides > 1) ? 1 : 0; adm_err = TGTADM_SUCCESS; } break; case ELEMENT_STORAGE: case ELEMENT_MAP: s = slot_lookup(&smc->slots, tmp->element_type, tmp->address); if (!s) break; // Slot not found.. if (tmp->clear_slot) { set_slot_empty(s); adm_err = TGTADM_SUCCESS; break; } if (strlen(tmp->barcode) > sizeof(s->barcode) || strlen(tmp->volume_tag) > sizeof(s->volume_tag)) { eprintf("barcode or volume tag too large?"); break; } strcpy(s->barcode, tmp->barcode); strcpy(s->volume_tag, tmp->volume_tag); set_slot_full(s, 0, NULL); adm_err = TGTADM_SUCCESS; break; case ELEMENT_DATA_TRANSFER: if (!tmp->tid) break; /* Fail if no TID specified */ s = slot_lookup(&smc->slots, tmp->element_type, tmp->address); if (!s) break; // Slot not found.. s->asc = NO_ADDITIONAL_SENSE; s->drive_tid = tmp->tid; s->drive_lun = tmp->lun; adm_err = TGTADM_SUCCESS; break; } return adm_err; } #define ADD 1 #define CONFIGURE 2 static tgtadm_err __smc_lu_config(struct scsi_lu *lu, char *params) { struct smc_info *smc = dtype_priv(lu); tgtadm_err adm_err = TGTADM_SUCCESS; char *p; char buf[256]; while (adm_err == TGTADM_SUCCESS && (p = strsep(¶ms, ",")) != NULL) { substring_t args[MAX_OPT_ARGS]; int token; if (!*p) continue; token = match_token(p, tokens, args); switch (token) { case Opt_element_type: match_strncpy(buf, &args[0], sizeof(buf)); sv_param.element_type = atoi(buf); break; case Opt_start_address: match_strncpy(buf, &args[0], sizeof(buf)); sv_param.start_addr = atoi(buf); sv_param.operation = ADD; break; case Opt_quantity: match_strncpy(buf, &args[0], sizeof(buf)); sv_param.quantity = atoi(buf); break; case Opt_sides: match_strncpy(buf, &args[0], sizeof(buf)); sv_param.sides = atoi(buf); break; case Opt_clear_slot: match_strncpy(buf, &args[0], sizeof(buf)); sv_param.clear_slot = atoi(buf); break; case Opt_address: match_strncpy(buf, &args[0], sizeof(buf)); sv_param.address = atoi(buf); sv_param.operation = CONFIGURE; break; case Opt_barcode: match_strncpy(sv_param.barcode, &args[0], sizeof(sv_param.barcode)); break; case Opt_volumetag: match_strncpy(sv_param.volume_tag, &args[0], sizeof(sv_param.volume_tag)); break; case Opt_tid: match_strncpy(buf, &args[0], sizeof(buf)); sv_param.tid = atoi(buf); break; case Opt_lun: match_strncpy(buf, &args[0], sizeof(buf)); sv_param.lun = atoi(buf); break; case Opt_dump: slot_dump(&smc->slots); break; case Opt_media_home: if (smc->media_home) free(smc->media_home); match_strncpy(buf, &args[0], sizeof(buf)); smc->media_home = strdup(buf); if (!smc->media_home) adm_err = TGTADM_NOMEM; break; default: adm_err = TGTADM_UNKNOWN_PARAM; break; } } return adm_err; } static tgtadm_err smc_lu_config(struct scsi_lu *lu, char *params) { tgtadm_err adm_err = TGTADM_SUCCESS; memset(&sv_param, 0, sizeof(struct tmp_param)); adm_err = lu_config(lu, params, __smc_lu_config); if (adm_err != TGTADM_SUCCESS) return adm_err; switch(sv_param.operation) { case ADD: adm_err = add_slt(lu, &sv_param); break; case CONFIGURE: adm_err = config_slot(lu, &sv_param); break; } return adm_err; } struct device_type_template smc_template = { .type = TYPE_MEDIUM_CHANGER, .lu_init = smc_lu_init, .lu_exit = smc_lu_exit, .lu_online = spc_lu_online, .lu_offline = spc_lu_offline, .lu_config = smc_lu_config, .ops = { {spc_test_unit,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_request_sense,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {smc_initialize_element_status,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, /* 0x10 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_inquiry,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_mode_sense,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, [0x20 ... 0x2f] = {spc_illegal_op,}, /* 0x30 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {smc_initialize_element_status_range,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, [0x40 ... 0x4f] = {spc_illegal_op,}, /* 0x50 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_mode_sense,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, [0x60 ... 0x9f] = {spc_illegal_op,}, /* 0xA0 */ {spc_report_luns,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_service_action, maint_in_service_actions,}, {spc_illegal_op,}, {smc_move_medium,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_test_unit,}, /* 0xB0 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {smc_read_element_status,}, // Mandatory {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, [0xc0 ... 0xff] = {spc_illegal_op}, } }; __attribute__((constructor)) static void smc_init(void) { device_type_register(&smc_template); } tgt-1.0.85/usr/smc.h000066400000000000000000000064651435417276200141750ustar00rootroot00000000000000/* * SCSI Medium Changer Command * * Copyright (C) 2007 Mark Harvey * * 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; version 2 of the License. * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef _SMC_H_ #define _SMC_H_ #define LOAD 1 #define UNLOAD 0 /** * Define for ELEMENT TYPE */ #define ELEMENT_ANY 0 #define ELEMENT_MEDIUM_TRANSPORT 1 #define ELEMENT_STORAGE 2 #define ELEMENT_MAP 3 #define ELEMENT_DATA_TRANSFER 4 /** * struct slot - Virtual Library element * * @slot_addr: Slot address - number between 0 & 65535 * @last_addr: Last slot address where media came from * @asc: ASC/ASCQ for this slot * @element_type: 1 = Medium Transport, 2 = Storage, 3 = MAP, 4 = drive * @cart_type: Type of media in this slot * @status: Different bits used depending on element_type * @sides: If media is single or double sided * @barcode: Barcode of media ID * @drive_tid: TID of DATA TRANSFER DEVICE configured at this slot address. * @drive_lun: LUN of DATA TRANSFER DEVICE configured at this slot address. * * A linked list of slots describing each element within the virtual library */ struct slot { struct list_head slot_siblings; uint16_t slot_addr; uint16_t last_addr; uint16_t asc; uint8_t element_type; uint8_t cart_type; uint8_t status; uint8_t sides; char barcode[11]; uint8_t drive_tid; uint64_t drive_lun; char volume_tag[32]; }; /** * struct smc_info - Data structure for SMC device * @media_home: Home directory for virtual media * @slots: Linked list of 'slots' within the virtual library * */ struct smc_info { char *media_home; struct list_head slots; }; enum { Opt_element_type, Opt_start_address, Opt_quantity, Opt_sides, Opt_clear_slot, Opt_address, Opt_barcode, Opt_tid, Opt_lun, Opt_type, Opt_dump, Opt_media_home, Opt_err, Opt_volumetag, }; static match_table_t tokens = { {Opt_element_type, "element_type=%s"}, {Opt_start_address, "start_address=%s"}, {Opt_quantity, "quantity=%s"}, {Opt_sides, "sides=%s"}, {Opt_clear_slot, "clear_slot=%s"}, {Opt_address, "address=%s"}, {Opt_barcode, "barcode=%s"}, {Opt_tid, "tid=%s"}, {Opt_lun, "lun=%s"}, {Opt_type, "type=%s"}, {Opt_dump, "dump=%s"}, {Opt_media_home, "media_home=%s"}, {Opt_volumetag, "volume_tag=%s"}, {Opt_err, NULL}, }; /** * struct tmp_param{} -- temporary storage of param values from user * * As param parsing is stateless, several params need to be collected * before we know if we are attempting to configure a slot or adding * a number of new slots. This is just a temporary holder for all * possible valid params before processing. */ struct tmp_param { int operation; int element_type; int start_addr; int quantity; int address; int tid; uint64_t lun; char barcode[20]; int sides; int clear_slot; char volume_tag[32]; } sv_param; #endif // _SMC_H_ tgt-1.0.85/usr/spc.c000066400000000000000000001450331435417276200141660ustar00rootroot00000000000000/* * SCSI primary command processing * * Copyright (C) 2005-2007 FUJITA Tomonori * Copyright (C) 2005-2007 Mike Christie * * 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, version 2 of the * License. * * 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 "list.h" #include "util.h" #include "tgtd.h" #include "parser.h" #include "target.h" #include "driver.h" #include "tgtadm_error.h" #include "scsi.h" #include "spc.h" #define INQUIRY_EVPD 0x01 #define PRODUCT_REV "0" #define DESG_HDR_LEN 4 /* * Protocol Identifier Values * * 0 Fibre Channel (FCP-2) * 1 Parallel SCSI (SPI-5) * 2 SSA (SSA-S3P) * 3 IEEE 1394 (SBP-3) * 4 SCSI Remote Direct Memory Access (SRP) * 5 iSCSI * 6 SAS Serial SCSI Protocol (SAS) * 7 Automation/Drive Interface (ADT) * 8 AT Attachment Interface (ATA/ATAPI-7) */ #define PIV_FCP 0 #define PIV_SPI 1 #define PIV_S3P 2 #define PIV_SBP 3 #define PIV_SRP 4 #define PIV_ISCSI 5 #define PIV_SAS 6 #define PIV_ADT 7 #define PIV_ATA 8 #define PIV_VALID 0x80 /* * Code Set * * 1 - Designator fild contains binary values * 2 - Designator field contains ASCII printable chars * 3 - Designaotor field contains UTF-8 */ #define INQ_CODE_BIN 1 #define INQ_CODE_ASCII 2 #define INQ_CODE_UTF8 3 /* * Association field * * 00b - Associated with Logical Unit * 01b - Associated with target port * 10b - Associated with SCSI Target device * 11b - Reserved */ #define ASS_LU 0 #define ASS_TGT_PORT 0x10 #define ASS_TGT_DEV 0x20 /* * Designator type - SPC-4 Reference * * 0 - Vendor specific - 7.6.3.3 * 1 - T10 vendor ID - 7.6.3.4 * 2 - EUI-64 - 7.6.3.5 * 3 - NAA - 7.6.3.6 * 4 - Relative Target port identifier - 7.6.3.7 * 5 - Target Port group - 7.6.3.8 * 6 - Logical Unit group - 7.6.3.9 * 7 - MD5 logical unit identifier - 7.6.3.10 * 8 - SCSI name string - 7.6.3.11 */ #define DESG_VENDOR 0 #define DESG_T10 1 #define DESG_EUI64 2 #define DESG_NAA 3 #define DESG_REL_TGT_PORT 4 #define DESG_TGT_PORT_GRP 5 #define DESG_LU_GRP 6 #define DESG_MD5 7 #define DESG_SCSI 8 #define NAA_IEEE_EXTD 0x2 #define NAA_LOCAL 0x3 #define NAA_IEEE_REGD 0x5 #define NAA_IEEE_REGD_EXTD 0x6 #define NAA_DESG_LEN 0x8 #define NAA_DESG_LEN_EXTD 0x10 static void update_vpd_80(struct scsi_lu *lu, void *sn) { struct vpd *vpd_pg = lu->attrs.lu_vpd[PCODE_OFFSET(0x80)]; char *data = (char *)vpd_pg->data; memset(data, 0x20, vpd_pg->size); if (strlen(sn)) { int tmp = strlen(sn); char *p, *q; p = data + vpd_pg->size - 1; q = sn + tmp - 1; for (; tmp > 0; tmp--) *(p--) = *(q--); } } static void update_vpd_83(struct scsi_lu *lu, void *id) { struct vpd *vpd_pg = lu->attrs.lu_vpd[PCODE_OFFSET(0x83)]; uint8_t *data = vpd_pg->data; char *id_str = id; char substring[] = "0"; uint64_t a = 0; uint64_t b = 0; uint64_t c; data[0] = INQ_CODE_ASCII; data[1] = DESG_T10; data[3] = SCSI_ID_LEN; data += DESG_HDR_LEN; strncpy((char *)data, id, SCSI_ID_LEN); data += SCSI_ID_LEN; data[0] = INQ_CODE_BIN; data[1] = DESG_NAA; data[3] = NAA_DESG_LEN; data += DESG_HDR_LEN; put_unaligned_be64(lu->attrs.numeric_id, data); data[0] |= NAA_LOCAL << 4; data += NAA_DESG_LEN; data[0] = INQ_CODE_BIN; data[1] = DESG_NAA; data[3] = NAA_DESG_LEN_EXTD; data += DESG_HDR_LEN; /* * The NAA_DESG_LEN_EXTD field is a 128 bit field * which contains a numeric value. This loop converts * the string pointed to by 'id_str' into a right * adjusted numeric value. */ while (*id_str) { substring[0] = *id_str++; c = a >> 60; a <<= 4; b <<= 4; b |= c; a |= strtoul(substring, NULL, 16); } put_unaligned_be64(b, data); put_unaligned_be64(a, data + 8); data[0] &= 0x0F; data[0] |= NAA_IEEE_REGD_EXTD << 4; } static void update_vpd_b1(struct scsi_lu *lu, void *id) { struct vpd *vpd_pg = lu->attrs.lu_vpd[PCODE_OFFSET(0xb1)]; uint8_t *data = vpd_pg->data; if (lu->attrs.rotation_rate) { data[0] = (lu->attrs.rotation_rate >> 8) & 0xff; data[1] = lu->attrs.rotation_rate & 0xff; data[2] = 0; /* PRODUCT TYPE */ data[3] = 0; /* WABEREQ | WACEREQ | NOMINAL FORM FACTOR */ data[4] = 0; /* VBULS */ } else { data[0] = 0; data[1] = 0; data[2] = 0; data[3] = 0; data[4] = 0; } } static void update_vpd_b2(struct scsi_lu *lu, void *id) { struct vpd *vpd_pg = lu->attrs.lu_vpd[PCODE_OFFSET(0xb2)]; uint8_t *data = vpd_pg->data; if (lu->attrs.thinprovisioning) { data[0] = 0; /* threshold exponent */ data[1] = 0xe4; /* LBPU LBPWS(10) LBPRZ */ data[2] = 0x02; /* provisioning type */ data[3] = 0; } else { data[0] = 0; data[1] = 0; data[2] = 0; data[3] = 0; } } static void update_vpd_b0(struct scsi_lu *lu, void *id) { struct vpd *vpd_pg = lu->attrs.lu_vpd[PCODE_OFFSET(0xb0)]; /* maximum compare and write length : 64kb */ vpd_pg->data[1] = 128; if (lu->attrs.thinprovisioning) { /* maximum unmap lba count : maximum*/ put_unaligned_be32(0xffffffff, vpd_pg->data + 16); /* maximum unmap block descriptor count : maximum*/ put_unaligned_be32(0xffffffff, vpd_pg->data + 20); } else { put_unaligned_be32(0, vpd_pg->data + 16); put_unaligned_be32(0, vpd_pg->data + 20); } } static void update_b0_opt_xfer_gran(struct scsi_lu *lu, int opt_xfer_gran) { struct vpd *vpd_pg = lu->attrs.lu_vpd[PCODE_OFFSET(0xb0)]; /* 4 byte VPD header omitted from data buff */ put_unaligned_be16(opt_xfer_gran, vpd_pg->data + 2); } static void update_b0_opt_xfer_len(struct scsi_lu *lu, int opt_xfer_len) { struct vpd *vpd_pg = lu->attrs.lu_vpd[PCODE_OFFSET(0xb0)]; /* 4 byte VPD header omitted from data buff */ put_unaligned_be32(opt_xfer_len, vpd_pg->data + 8); } int spc_inquiry(int host_no, struct scsi_cmd *cmd) { int ret = SAM_STAT_CHECK_CONDITION; uint8_t *data; uint8_t *scb = cmd->scb; int evpd = (scb[1] & INQUIRY_EVPD) ? 1 : 0; int pcode = scb[2]; uint32_t alloc_len, avail_len, actual_len; unsigned char key = ILLEGAL_REQUEST; uint16_t asc = ASC_INVALID_FIELD_IN_CDB; uint8_t devtype = 0; struct lu_phy_attr *attrs; struct vpd *vpd_pg; uint8_t buf[256]; if (!evpd && pcode) goto sense; alloc_len = (uint32_t)get_unaligned_be16(&scb[3]); if (scsi_get_in_length(cmd) < alloc_len) goto sense; memset(buf, 0, sizeof(buf)); data = buf; avail_len = 0; dprintf("%x %x\n", (int)scb[1], pcode); attrs = &cmd->dev->attrs; devtype = (attrs->qualifier & 0x7) << 5; devtype |= (attrs->device_type & 0x1f); if (!evpd) { /* no VPD, return standard Inquiry data */ int i; uint16_t *desc; data[0] = devtype; data[1] = (attrs->removable) ? 0x80 : 0; data[2] = 5; /* SPC-3 */ data[3] = 0x12; data[7] = 0x02; memset(data + 8, 0x20, 28); scsi_sprintf((char *)data + 8, VENDOR_ID_LEN, "%-*s", VENDOR_ID_LEN, attrs->vendor_id); scsi_sprintf((char *)data + 16, PRODUCT_ID_LEN, "%-*s", PRODUCT_ID_LEN, attrs->product_id); scsi_sprintf((char *)data + 32, PRODUCT_REV_LEN, "%-*s", PRODUCT_REV_LEN, attrs->product_rev); desc = (uint16_t *)(data + 58); for (i = 0; i < ARRAY_SIZE(attrs->version_desc); i++) *desc++ = __cpu_to_be16(attrs->version_desc[i]); avail_len = 66; data[4] = avail_len - 5; /* Additional Length */ ret = SAM_STAT_GOOD; } else { /* return requested page of VPD */ if (pcode == 0x0) { uint8_t *p; int i, cnt; data[0] = devtype; data[1] = 0; data[2] = 0; cnt = 1; p = data + 5; for (i = 0; i < ARRAY_SIZE(attrs->lu_vpd); i++) { if (attrs->lu_vpd[i]) { *p++ = i | 0x80; cnt++; } } data[3] = cnt; data[4] = 0x0; avail_len = cnt + 4; ret = SAM_STAT_GOOD; } else if (attrs->lu_vpd[PCODE_OFFSET(pcode)]) { vpd_pg = attrs->lu_vpd[PCODE_OFFSET(pcode)]; data[0] = devtype; data[1] = (uint8_t)pcode; data[2] = (vpd_pg->size >> 8); data[3] = vpd_pg->size & 0xff; memcpy(&data[4], vpd_pg->data, vpd_pg->size); avail_len = vpd_pg->size + 4; ret = SAM_STAT_GOOD; } } if (ret != SAM_STAT_GOOD) goto sense; if (cmd->dev->lun != cmd->dev_id) data[0] = TYPE_NO_LUN; actual_len = spc_memcpy(scsi_get_in_buffer(cmd), &alloc_len, data, avail_len); scsi_set_in_resid_by_actual(cmd, actual_len); return SAM_STAT_GOOD; sense: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } int spc_report_luns(int host_no, struct scsi_cmd *cmd) { struct scsi_lu *lu; struct list_head *dev_list = &cmd->c_target->device_list; uint32_t alloc_len, avail_len, remain_len, actual_len; uint64_t lun, *data, *plun; unsigned char key = ILLEGAL_REQUEST; uint16_t asc = ASC_INVALID_FIELD_IN_CDB; uint8_t *scb = cmd->scb; alloc_len = get_unaligned_be32(&scb[6]); if (alloc_len < 16) goto sense; if (scsi_get_in_length(cmd) < alloc_len) goto sense; data = scsi_get_in_buffer(cmd); plun = data + 1; remain_len = alloc_len - 8; actual_len = 8; avail_len = 0; /* accumulate LUN list length */ list_for_each_entry(lu, dev_list, device_siblings) { if (remain_len) { lun = lu->lun; lun = ((lun > 0xff) ? (0x1 << 30) : 0) | ((0x3fff & lun) << 16); lun = __cpu_to_be64(lun << 32); } actual_len += spc_memcpy((uint8_t *)plun, &remain_len, (uint8_t *)&lun, 8); avail_len += 8; plun++; } *data = 0; *((uint32_t *) data) = __cpu_to_be32(avail_len); scsi_set_in_resid_by_actual(cmd, actual_len); return SAM_STAT_GOOD; sense: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } int spc_start_stop(int host_no, struct scsi_cmd *cmd) { uint8_t *scb = cmd->scb; int start, loej, pwrcnd; scsi_set_in_resid_by_actual(cmd, 0); if (device_reserved(cmd)) return SAM_STAT_RESERVATION_CONFLICT; pwrcnd = scb[4] & 0xf0; if (pwrcnd) return SAM_STAT_GOOD; loej = scb[4] & 0x02; start = scb[4] & 0x01; if (loej && !start && cmd->dev->attrs.removable) { if (lu_prevent_removal(cmd->dev)) { if (cmd->dev->attrs.online) { /* online == media is present */ sense_data_build(cmd, ILLEGAL_REQUEST, ASC_MEDIUM_REMOVAL_PREVENTED); } else { /* !online == media is not present */ sense_data_build(cmd, NOT_READY, ASC_MEDIUM_REMOVAL_PREVENTED); } return SAM_STAT_CHECK_CONDITION; } spc_lu_offline(cmd->dev); } if (loej && start && cmd->dev->attrs.removable) spc_lu_online(cmd->dev); return SAM_STAT_GOOD; } int spc_test_unit(int host_no, struct scsi_cmd *cmd) { /* how should we test a backing-storage file? */ if (device_reserved(cmd)) return SAM_STAT_RESERVATION_CONFLICT; if (cmd->dev->attrs.online) return SAM_STAT_GOOD; if (cmd->dev->attrs.removable) sense_data_build(cmd, NOT_READY, ASC_MEDIUM_NOT_PRESENT); else sense_data_build(cmd, NOT_READY, ASC_BECOMING_READY); return SAM_STAT_CHECK_CONDITION; } int spc_prevent_allow_media_removal(int host_no, struct scsi_cmd *cmd) { uint8_t *scb = cmd->scb; struct it_nexus_lu_info *itn_lu_info = cmd->itn_lu_info; if (device_reserved(cmd)) return SAM_STAT_RESERVATION_CONFLICT; itn_lu_info->prevent = scb[4] & PREVENT_MASK; return SAM_STAT_GOOD; } int spc_mode_select(int host_no, struct scsi_cmd *cmd, int (*update)(struct scsi_cmd *, uint8_t *, int *)) { uint8_t *scb = cmd->scb; uint8_t *data = NULL; uint8_t pf, sp, pcode; uint32_t in_len; unsigned char key = ILLEGAL_REQUEST; uint16_t asc = ASC_INVALID_FIELD_IN_PARMS; uint32_t offset = 0; uint8_t parameter_header_len; uint16_t block_descriptor_len; int add_ua = 0; if (device_reserved(cmd)) return SAM_STAT_RESERVATION_CONFLICT; pf = scb[1] & 0x10; sp = scb[1] & 0x01; if (!pf || sp) { asc = ASC_INVALID_FIELD_IN_CDB; goto sense; } in_len = scsi_get_out_length(cmd); data = scsi_get_out_buffer(cmd); if (scb[0] == MODE_SELECT) { in_len = min_t(uint32_t, scb[4], in_len); parameter_header_len = 4; } else if (scb[0] == MODE_SELECT_10) { in_len = min_t(uint32_t, (scb[7] << 8) + scb[8], in_len); parameter_header_len = 8; } else { eprintf("bug %u\n", scb[0]); exit(1); } if (in_len < parameter_header_len) { asc = ASC_INVALID_FIELD_IN_CDB; goto sense; } offset = parameter_header_len; if (scb[0] == MODE_SELECT) block_descriptor_len = data[3]; else block_descriptor_len = (data[6] << 8) + data[7]; if (block_descriptor_len) { if (block_descriptor_len != BLOCK_DESCRIPTOR_LEN) goto sense; memcpy(cmd->dev->mode_block_descriptor, data + offset, BLOCK_DESCRIPTOR_LEN); offset += 8; } while (in_len > offset + 2) { struct mode_pg *pg; uint8_t *mask; int i, ret, changed; if (0x80 & data[offset]) goto sense; pcode = data[offset] & 0x3f; pg = find_mode_page(cmd->dev, pcode, 0); if (!pg) goto sense; if (in_len - (offset + 2) < pg->pcode_size) goto sense; mask = pg->mode_data + pg->pcode_size; for (i = 0; i < pg->pcode_size; i++) { uint8_t *p, v; p = data + (offset + 2); v = p[i] ^ pg->mode_data[i]; if (v && (v & ~mask[i])) goto sense; } changed = 0; ret = update(cmd, data + offset, &changed); if (ret) goto sense; if (changed) add_ua = 1; offset += (data[offset + 1] + 2); } if (add_ua) ua_sense_add_other_it_nexus(cmd->cmd_itn_id, cmd->dev, ASC_MODE_PARAMETERS_CHANGED); if (in_len != offset) { asc = ASC_PARAMETER_LIST_LENGTH_ERR; goto sense; } return SAM_STAT_GOOD; sense: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } struct mode_pg *find_mode_page(struct scsi_lu *lu, uint8_t pcode, uint8_t subpcode) { struct mode_pg *pg; list_for_each_entry(pg, &lu->mode_pages, mode_pg_siblings) { if (pg->pcode == pcode && pg->subpcode == subpcode) return pg; } return NULL; } int set_mode_page_changeable_mask(struct scsi_lu *lu, uint8_t pcode, uint8_t subpcode, uint8_t *mask) { struct mode_pg *pg = find_mode_page(lu, pcode, subpcode); if (pg) { memcpy(pg->mode_data + pg->pcode_size, mask, pg->pcode_size); return 0; } return 1; } /** * build_mode_page - static routine used by spc_mode_sense() * @data: destination pointer * @m: struct mode pointer (src of data) * * Description: Copy mode page data from list into SCSI data so it can * be returned to the initiator * * Returns number of bytes copied. * Returns remaining alloc length in out-param remain_len */ static int build_mode_page(uint8_t *data, struct mode_pg *pg, uint32_t *avail_len, uint32_t *remain_len, uint8_t pc) { uint8_t hdr[4]; uint32_t hdr_size, actual_len; uint8_t *p, *mode_data; if (!pg->subpcode) { hdr[0] = pg->pcode; hdr[1] = pg->pcode_size; hdr_size = 2; } else { hdr[0] = pg->pcode | 0x40; hdr[1] = pg->subpcode; hdr[2] = (pg->pcode_size >> 8) & 0xff; hdr[3] = pg->pcode_size & 0xff; hdr_size = 4; } actual_len = spc_memcpy(data, remain_len, hdr, hdr_size); *avail_len += hdr_size; p = &data[hdr_size]; mode_data = pg->mode_data; if (pc == 1) mode_data += pg->pcode_size; actual_len += spc_memcpy(p, remain_len, mode_data, pg->pcode_size); *avail_len += pg->pcode_size; return actual_len; } /* * Set a byte at the given index within dst buffer to val, * not exceeding dst_len bytes available at dst. */ void set_byte_safe(uint8_t *dst, uint32_t index, uint32_t dst_len, int val) { if (index < dst_len) dst[index] = (uint8_t)val; } /** * spc_mode_sense - Implement SCSI op MODE SENSE(6) and MODE SENSE(10) * * Reference : SPC4r11 * 6.11 - MODE SENSE(6) * 6.12 - MODE SENSE(10) */ int spc_mode_sense(int host_no, struct scsi_cmd *cmd) { uint8_t *data = NULL, *scb; uint8_t mode6, dbd, blk_desc_len, pcode, pctrl, subpcode; unsigned char key = ILLEGAL_REQUEST; uint16_t asc = ASC_INVALID_FIELD_IN_CDB; uint32_t alloc_len, avail_len, remain_len, actual_len; uint32_t hdr_len, mod_data_len; struct mode_pg *pg; scb = cmd->scb; mode6 = (scb[0] == 0x1a); dbd = scb[1] & 0x8; /* Disable Block Descriptors */ blk_desc_len = dbd ? 0 : BLOCK_DESCRIPTOR_LEN; pcode = scb[2] & 0x3f; pctrl = (scb[2] & 0xc0) >> 6; subpcode = scb[3]; if (pctrl == 3) { asc = ASC_SAVING_PARMS_UNSUP; goto sense; } data = scsi_get_in_buffer(cmd); if (mode6) { alloc_len = (uint32_t)scb[4]; hdr_len = 4; } else { alloc_len = (uint32_t)get_unaligned_be16(&scb[7]); hdr_len = 8; } if (scsi_get_in_length(cmd) < alloc_len) goto sense; memset(data, 0, alloc_len); avail_len = hdr_len; actual_len = min_t(uint32_t, alloc_len, hdr_len); remain_len = alloc_len - actual_len; if (!dbd) { actual_len += spc_memcpy(data + actual_len, &remain_len, cmd->dev->mode_block_descriptor, BLOCK_DESCRIPTOR_LEN); avail_len += BLOCK_DESCRIPTOR_LEN; } if (pcode == 0x3f) { list_for_each_entry(pg, &cmd->dev->mode_pages, mode_pg_siblings) { actual_len += build_mode_page(data + actual_len, pg, &avail_len, &remain_len, pctrl); } } else { pg = find_mode_page(cmd->dev, pcode, subpcode); if (!pg) goto sense; actual_len += build_mode_page(data + actual_len, pg, &avail_len, &remain_len, pctrl); } if (mode6) { mod_data_len = avail_len - 1; set_byte_safe(data, 0, alloc_len, mod_data_len & 0xff); set_byte_safe(data, 3, alloc_len, blk_desc_len & 0xff); } else { mod_data_len = avail_len - 2; set_byte_safe(data, 0, alloc_len, (mod_data_len >> 8) & 0xff); set_byte_safe(data, 1, alloc_len, mod_data_len & 0xff); set_byte_safe(data, 6, alloc_len, (blk_desc_len >> 8) & 0xff); set_byte_safe(data, 7, alloc_len, blk_desc_len & 0xff); } scsi_set_in_resid_by_actual(cmd, actual_len); return SAM_STAT_GOOD; sense: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } static int report_opcodes_all(struct scsi_cmd *cmd, int rctd, uint32_t alloc_len) { uint8_t buf[2048], *data; struct device_type_operations *ops; struct service_action *service_action; int i; uint32_t avail_len, actual_len; int cdb_length; memset(buf, 0, sizeof(buf)); data = &buf[4]; ops = cmd->dev->dev_type_template.ops; for (i = 0; i < NR_SCSI_OPCODES; i++) { if (ops[i].cmd_perform == spc_illegal_op) continue; if (!is_bs_support_opcode(cmd->dev->bst, i)) continue; /* this command does not take a service action, so just report the opcode */ if (!ops[i].service_actions) { *data++ = i; /* reserved */ data++; /* service action */ data += 2; /* reserved */ data++; /* flags : no service action, possibly timeout desc */ *data++ = rctd ? 0x02 : 0x00; /* cdb length */ cdb_length = get_scsi_command_size(i); *data++ = (cdb_length >> 8) & 0xff; *data++ = cdb_length & 0xff; /* timeout descriptor */ if (rctd) { /* length == 0x0a */ data[1] = 0x0a; data += 12; } continue; } for (service_action = ops[i].service_actions; service_action->cmd_perform; service_action++) { /* opcode */ *data++ = i; /* reserved */ data++; /* service action */ *data++ = (service_action->service_action >> 8) & 0xff; *data++ = service_action->service_action & 0xff; /* reserved */ data++; /* flags : service action, possibly timeout desc */ *data++ = rctd ? 0x03 : 0x01; /* cdb length */ cdb_length = get_scsi_command_size(i); *data++ = (cdb_length >> 8) & 0xff; *data++ = cdb_length & 0xff; /* timeout descriptor */ if (rctd) { /* length == 0x0a */ data[1] = 0x0a; data += 12; } } } avail_len = data - &buf[0]; put_unaligned_be32(avail_len-4, &buf[0]); actual_len = spc_memcpy(scsi_get_in_buffer(cmd), &alloc_len, buf, avail_len); scsi_set_in_resid_by_actual(cmd, actual_len); return SAM_STAT_GOOD; } static int report_opcode_one(struct scsi_cmd *cmd, int rctd, uint8_t opcode, uint16_t sa, int is_service_action, uint32_t alloc_len) { struct device_type_operations *ops; uint8_t buf[256], *data; uint32_t avail_len, actual_len; int cdb_length; ops = cmd->dev->dev_type_template.ops; if (!is_bs_support_opcode(cmd->dev->bst, opcode)) return SAM_STAT_CHECK_CONDITION; if ((is_service_action && !ops[opcode].service_actions) || (!is_service_action && ops[opcode].service_actions)) { return SAM_STAT_CHECK_CONDITION; } memset(buf, 0, sizeof(buf)); data = &buf[0]; /* reserved */ data++; /* ctdp and support */ *data++ = rctd ? 0x83 : 0x03; /* cdb length */ cdb_length = get_scsi_command_size(opcode); *data++ = (cdb_length >> 8) & 0xff; *data++ = cdb_length & 0xff; /* cdb usage data */ memcpy(data, get_scsi_cdb_usage_data(opcode, sa), cdb_length); data += cdb_length; /* timeout descriptor */ if (rctd) { /* length == 0x0a */ data[1] = 0x0a; data += 12; } avail_len = data - &buf[0]; actual_len = spc_memcpy(scsi_get_in_buffer(cmd), &alloc_len, buf, avail_len); scsi_set_in_resid_by_actual(cmd, actual_len); return SAM_STAT_GOOD; } int spc_report_supported_opcodes(int host_no, struct scsi_cmd *cmd) { uint8_t reporting_options; uint8_t opcode; uint16_t requested_service_action; uint32_t alloc_len; int rctd; int ret; unsigned char key = ILLEGAL_REQUEST; uint16_t asc = ASC_INVALID_FIELD_IN_CDB; reporting_options = cmd->scb[2] & 0x07; opcode = cmd->scb[3]; requested_service_action = get_unaligned_be16(&cmd->scb[4]); alloc_len = get_unaligned_be32(&cmd->scb[6]); if (scsi_get_in_length(cmd) < alloc_len) goto sense; rctd = cmd->scb[2] & 0x80; switch (reporting_options) { case 0x00: /* report all */ ret = report_opcodes_all(cmd, rctd, alloc_len); break; case 0x01: /* report one no service action*/ ret = report_opcode_one(cmd, rctd, opcode, requested_service_action, 0, alloc_len); if (ret) goto sense; break; case 0x02: /* report one service action */ ret = report_opcode_one(cmd, rctd, opcode, requested_service_action, 1, alloc_len); if (ret) goto sense; break; default: goto sense; } return ret; sense: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } struct service_action maint_in_service_actions[] = { {0x0c, spc_report_supported_opcodes}, {0, NULL} }; struct service_action * find_service_action(struct service_action *service_action, uint32_t action) { while (service_action->cmd_perform) { if (service_action->service_action == action) return service_action; service_action++; } return NULL; } int spc_send_diagnostics(int host_no, struct scsi_cmd *cmd) { uint16_t asc = ASC_INVALID_FIELD_IN_CDB; uint8_t key = ILLEGAL_REQUEST; /* we only support SELF-TEST==1 */ if (!(cmd->scb[1] & 0x04)) goto sense; return SAM_STAT_GOOD; sense: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } /* * This is useful for the various commands using the SERVICE ACTION * format. */ int spc_service_action(int host_no, struct scsi_cmd *cmd) { uint8_t action; unsigned char op = cmd->scb[0]; struct service_action *service_action, *actions; action = cmd->scb[1] & 0x1f; actions = cmd->dev->dev_type_template.ops[op].service_actions; service_action = find_service_action(actions, action); if (!service_action) { scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB); return SAM_STAT_CHECK_CONDITION; } return service_action->cmd_perform(host_no, cmd); } static int is_pr_holder(struct scsi_lu *lu, struct registration *reg) { if (lu->pr_holder->pr_type == PR_TYPE_WRITE_EXCLUSIVE_ALLREG || lu->pr_holder->pr_type == PR_TYPE_EXCLUSIVE_ACCESS_ALLREG) return 1; if (lu->pr_holder == reg) return 1; return 0; } static int spc_pr_read_keys(int host_no, struct scsi_cmd *cmd) { uint32_t alloc_len, avail_len, actual_len, remain_len; uint8_t *buf; struct registration *reg; uint64_t reg_key; uint16_t asc = ASC_INVALID_FIELD_IN_CDB; uint8_t key = ILLEGAL_REQUEST; alloc_len = (uint32_t)get_unaligned_be16(&cmd->scb[7]); if (alloc_len < 8) goto sense; if (scsi_get_in_length(cmd) < alloc_len) goto sense; buf = scsi_get_in_buffer(cmd); put_unaligned_be32(cmd->dev->prgeneration, &buf[0]); actual_len = 8; avail_len = 8; remain_len = alloc_len - 8; list_for_each_entry(reg, &cmd->dev->registration_list, registration_siblings) { reg_key = __cpu_to_be64(reg->key); actual_len += spc_memcpy(&buf[actual_len], &remain_len, (uint8_t *)®_key, 8); avail_len += 8; } put_unaligned_be32(avail_len - 8, &buf[4]); /* additional length */ scsi_set_in_resid_by_actual(cmd, actual_len); return SAM_STAT_GOOD; sense: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } static int spc_pr_read_reservation(int host_no, struct scsi_cmd *cmd) { uint32_t alloc_len, add_len, avail_len, actual_len; struct registration *reg; uint64_t res_key; uint8_t buf[32]; uint8_t *data; uint16_t asc = ASC_INVALID_FIELD_IN_CDB; uint8_t key = ILLEGAL_REQUEST; alloc_len = (uint32_t)get_unaligned_be16(&cmd->scb[7]); if (alloc_len < 8) goto sense; if (scsi_get_in_length(cmd) < alloc_len) goto sense; reg = cmd->dev->pr_holder; add_len = (reg ? 16 : 0); avail_len = 8 + add_len; memset(buf, 0, avail_len); put_unaligned_be32(cmd->dev->prgeneration, &buf[0]); put_unaligned_be32(add_len, &buf[4]); /* additional length */ if (reg) { if (reg->pr_type == PR_TYPE_WRITE_EXCLUSIVE_ALLREG || reg->pr_type == PR_TYPE_EXCLUSIVE_ACCESS_ALLREG) res_key = 0; else res_key = reg->key; put_unaligned_be64(res_key, &buf[8]); buf[21] = (reg->pr_scope << 4) & 0xf0; buf[21] |= reg->pr_type & 0x0f; } data = scsi_get_in_buffer(cmd); actual_len = spc_memcpy(data, &alloc_len, buf, avail_len); scsi_set_in_resid_by_actual(cmd, actual_len); return SAM_STAT_GOOD; sense: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } static int spc_pr_report_capabilities(int host_no, struct scsi_cmd *cmd) { uint32_t alloc_len, avail_len, actual_len; uint8_t *data, buf[8]; uint16_t asc = ASC_INVALID_FIELD_IN_CDB; uint8_t key = ILLEGAL_REQUEST; alloc_len = (uint32_t)get_unaligned_be16(&cmd->scb[7]); if (alloc_len < 8) goto sense; if (scsi_get_in_length(cmd) < alloc_len) goto sense; memset(buf, 0, 8); avail_len = 8; put_unaligned_be16(avail_len, &buf[0]); /* length */ /* we don't set any capability for now */ /* Persistent Reservation Type Mask format */ buf[3] |= 0x80; /* Type Mask Valid (TMV) */ buf[4] |= 0x80; /* PR_TYPE_EXCLUSIVE_ACCESS_ALLREG */ buf[4] |= 0x40; /* PR_TYPE_EXCLUSIVE_ACCESS_REGONLY */ buf[4] |= 0x20; /* PR_TYPE_WRITE_EXCLUSIVE_REGONLY */ buf[4] |= 0x08; /* PR_TYPE_EXCLUSIVE_ACCESS */ buf[4] |= 0x02; /* PR_TYPE_WRITE_EXCLUSIVE */ buf[5] |= 0x01; /* PR_TYPE_EXCLUSIVE_ACCESS_ALLREG */ data = scsi_get_in_buffer(cmd); actual_len = spc_memcpy(data, &alloc_len, buf, avail_len); scsi_set_in_resid_by_actual(cmd, actual_len); return SAM_STAT_GOOD; sense: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } struct service_action persistent_reserve_in_actions[] = { {PR_IN_READ_KEYS, spc_pr_read_keys}, {PR_IN_READ_RESERVATION, spc_pr_read_reservation}, {PR_IN_REPORT_CAPABILITIES, spc_pr_report_capabilities}, {0, NULL}, }; static struct registration *lookup_registration_by_nexus(struct scsi_lu *lu, struct it_nexus *itn) { struct registration *reg; list_for_each_entry(reg, &lu->registration_list, registration_siblings) { if (reg->nexus_id == itn->itn_id && reg->ctime == itn->ctime) return reg; } return NULL; } static int check_registration_key_exists(struct scsi_lu *lu, uint64_t key) { struct registration *reg; list_for_each_entry(reg, &lu->registration_list, registration_siblings) { if (reg->key == key) return 0; } return 1; } static void __unregister(struct scsi_lu *lu, struct registration *reg) { list_del(®->registration_siblings); free(reg); } static void __unregister_and_clean(struct scsi_cmd *cmd, struct registration *reg) { struct registration *holder, *registrant; /* if reservation owner goes away then so does reservation */ list_del(®->registration_siblings); holder = cmd->dev->pr_holder; if (!holder) { free(reg); return; } if (!is_pr_holder(cmd->dev, reg)) { free(reg); return; } if (((holder->pr_type != PR_TYPE_WRITE_EXCLUSIVE_ALLREG) && (holder->pr_type != PR_TYPE_EXCLUSIVE_ACCESS_ALLREG)) || list_empty(&cmd->dev->registration_list)) { /* not all-registrants or no more registrants */ holder->pr_scope = 0; holder->pr_type = 0; cmd->dev->pr_holder = NULL; /* tell other registrants the reservation went away */ list_for_each_entry(registrant, &cmd->dev->registration_list, registration_siblings) { /* do not send UA to self */ if (registrant == reg) continue; ua_sense_add_other_it_nexus(registrant->nexus_id, cmd->dev, ASC_RESERVATIONS_RELEASED); } cmd->dev->prgeneration++; } else { /* all-registrants and there are more registrants */ list_for_each_entry(registrant, &cmd->dev->registration_list, registration_siblings) { if (registrant != reg) { /* give resvn to any sibling */ cmd->dev->pr_holder = registrant; registrant->pr_scope = holder->pr_scope; registrant->pr_type = holder->pr_type; break; } } } free(reg); } static int check_pr_out_basic_parameter(struct scsi_cmd *cmd) { uint32_t param_list_len; uint8_t *buf; uint8_t spec_i_pt, all_tg_pt, aptpl; param_list_len = get_unaligned_be32(&cmd->scb[5]); if (param_list_len != 24) return 1; if (scsi_get_out_length(cmd) < 24) return 1; buf = scsi_get_out_buffer(cmd); spec_i_pt = buf[20] & (1U << 3); all_tg_pt = buf[20] & (1U << 2); aptpl = buf[20] & (1U << 0); if (spec_i_pt | all_tg_pt | aptpl) { /* * for now, we say that we don't support these bits * via REPORT CAPABILITIES. */ return 1; } return 0; } static int spc_pr_register(int host_no, struct scsi_cmd *cmd) { uint8_t force, key = ILLEGAL_REQUEST; uint16_t asc = ASC_INVALID_FIELD_IN_CDB; uint64_t res_key, sa_res_key; int ret; uint8_t *buf; struct registration *reg; force = ((cmd->scb[1] & 0x1f) == PR_OUT_REGISTER_AND_IGNORE_EXISTING_KEY); ret = check_pr_out_basic_parameter(cmd); if (ret) goto sense; buf = scsi_get_out_buffer(cmd); res_key = get_unaligned_be64(buf); sa_res_key = get_unaligned_be64(buf + 8); reg = lookup_registration_by_nexus(cmd->dev, cmd->it_nexus); if (reg) { if (force || reg->key == res_key) { if (sa_res_key) reg->key = sa_res_key; else __unregister_and_clean(cmd, reg); } else return SAM_STAT_RESERVATION_CONFLICT; } else { if (force || !res_key) { if (sa_res_key) { reg = zalloc(sizeof(*reg)); if (!reg) { key = ILLEGAL_REQUEST; asc = ASC_INSUFFICENT_REGISTRATION_RESOURCES; goto sense; } reg->key = sa_res_key; reg->nexus_id = cmd->cmd_itn_id; reg->ctime = cmd->it_nexus->ctime; list_add_tail(®->registration_siblings, &cmd->dev->registration_list); } else ; /* do nothing */ } else return SAM_STAT_RESERVATION_CONFLICT; } cmd->dev->prgeneration++; return SAM_STAT_GOOD; sense: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } static int spc_pr_reserve(int host_no, struct scsi_cmd *cmd) { uint16_t asc = ASC_INVALID_FIELD_IN_CDB; uint8_t key = ILLEGAL_REQUEST; uint8_t pr_scope, pr_type; int ret; struct registration *reg, *holder; ret = check_pr_out_basic_parameter(cmd); if (ret) goto sense; pr_scope = (cmd->scb[2] & 0xf0) >> 4; pr_type = cmd->scb[2] & 0x0f; switch (pr_type) { case PR_TYPE_WRITE_EXCLUSIVE: case PR_TYPE_EXCLUSIVE_ACCESS: case PR_TYPE_WRITE_EXCLUSIVE_REGONLY: case PR_TYPE_EXCLUSIVE_ACCESS_REGONLY: case PR_TYPE_WRITE_EXCLUSIVE_ALLREG: case PR_TYPE_EXCLUSIVE_ACCESS_ALLREG: break; default: goto sense; } if (pr_scope != PR_LU_SCOPE) goto sense; reg = lookup_registration_by_nexus(cmd->dev, cmd->it_nexus); if (!reg) return SAM_STAT_RESERVATION_CONFLICT; holder = cmd->dev->pr_holder; if (holder) { if (!is_pr_holder(cmd->dev, reg)) return SAM_STAT_RESERVATION_CONFLICT; if (holder->pr_type != pr_type || holder->pr_scope != pr_scope) return SAM_STAT_RESERVATION_CONFLICT; return SAM_STAT_GOOD; } reg->pr_scope = pr_scope; reg->pr_type = pr_type; cmd->dev->pr_holder = reg; return SAM_STAT_GOOD; sense: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } static int spc_pr_release(int host_no, struct scsi_cmd *cmd) { uint16_t asc = ASC_INVALID_FIELD_IN_CDB; uint8_t key = ILLEGAL_REQUEST; uint8_t pr_scope, pr_type; uint8_t *buf; uint64_t res_key; int ret; struct registration *reg, *holder, *sibling; ret = check_pr_out_basic_parameter(cmd); if (ret) goto sense; pr_scope = (cmd->scb[2] & 0xf0) >> 4; pr_type = cmd->scb[2] & 0x0f; buf = scsi_get_out_buffer(cmd); res_key = get_unaligned_be64(buf); reg = lookup_registration_by_nexus(cmd->dev, cmd->it_nexus); if (!reg) return SAM_STAT_RESERVATION_CONFLICT; holder = cmd->dev->pr_holder; if (!holder) return SAM_STAT_GOOD; if (!is_pr_holder(cmd->dev, reg)) return SAM_STAT_GOOD; if (res_key != reg->key) return SAM_STAT_RESERVATION_CONFLICT; if (holder->pr_scope != pr_scope || holder->pr_type != pr_type) { asc = ASC_INVALID_RELEASE_OF_PERSISTENT_RESERVATION; goto sense; } cmd->dev->pr_holder = NULL; reg->pr_scope = 0; reg->pr_type = 0; switch (pr_type) { case PR_TYPE_WRITE_EXCLUSIVE_REGONLY: case PR_TYPE_EXCLUSIVE_ACCESS_REGONLY: case PR_TYPE_WRITE_EXCLUSIVE_ALLREG: case PR_TYPE_EXCLUSIVE_ACCESS_ALLREG: return SAM_STAT_GOOD; default: ; } list_for_each_entry(sibling, &cmd->dev->registration_list, registration_siblings) { /* we don't send myself */ if (sibling == reg) continue; ua_sense_add_other_it_nexus(sibling->nexus_id, cmd->dev, ASC_RESERVATIONS_RELEASED); } return SAM_STAT_GOOD; sense: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } static int spc_pr_clear(int host_no, struct scsi_cmd *cmd) { uint16_t asc = ASC_INVALID_FIELD_IN_CDB; uint8_t key = ILLEGAL_REQUEST; uint8_t *buf; uint64_t res_key; int ret; struct registration *reg, *holder, *sibling, *n; ret = check_pr_out_basic_parameter(cmd); if (ret) goto sense; buf = scsi_get_out_buffer(cmd); res_key = get_unaligned_be64(buf); reg = lookup_registration_by_nexus(cmd->dev, cmd->it_nexus); if (!reg) return SAM_STAT_RESERVATION_CONFLICT; if (reg->key != res_key) return SAM_STAT_RESERVATION_CONFLICT; holder = cmd->dev->pr_holder; if (holder) { holder->pr_scope = 0; holder->pr_type = 0; cmd->dev->pr_holder = NULL; } list_for_each_entry_safe(sibling, n, &cmd->dev->registration_list, registration_siblings) { /* we don't send myself */ if (sibling != reg) ua_sense_add_it_nexus(sibling->nexus_id, cmd->dev, ASC_RESERVATIONS_PREEMPTED); list_del(&sibling->registration_siblings); free(sibling); } cmd->dev->prgeneration++; return SAM_STAT_GOOD; sense: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } static int spc_pr_preempt(int host_no, struct scsi_cmd *cmd) { uint16_t asc = ASC_INVALID_FIELD_IN_CDB; uint8_t key = ILLEGAL_REQUEST; int ret; int res_released = 0, remove_all_reg = 0; uint64_t res_key, sa_res_key; uint8_t pr_scope, pr_type; uint8_t *buf; struct registration *holder, *reg, *sibling, *n; ret = check_pr_out_basic_parameter(cmd); if (ret) goto sense; pr_scope = (cmd->scb[2] & 0xf0) >> 4; pr_type = cmd->scb[2] & 0x0f; buf = scsi_get_out_buffer(cmd); res_key = get_unaligned_be64(buf); sa_res_key = get_unaligned_be64(buf + 8); reg = lookup_registration_by_nexus(cmd->dev, cmd->it_nexus); if (!reg) return SAM_STAT_RESERVATION_CONFLICT; if (reg->key != res_key) return SAM_STAT_RESERVATION_CONFLICT; if (sa_res_key) { ret = check_registration_key_exists(cmd->dev, sa_res_key); if (ret) return SAM_STAT_RESERVATION_CONFLICT; } holder = cmd->dev->pr_holder; if (holder) { if (holder->pr_type == PR_TYPE_WRITE_EXCLUSIVE_ALLREG || holder->pr_type == PR_TYPE_EXCLUSIVE_ACCESS_ALLREG) { if (!sa_res_key) { if (pr_type != holder->pr_type || pr_scope != holder->pr_scope) res_released = 1; reg->pr_type = pr_type; reg->pr_scope = pr_scope; cmd->dev->pr_holder = reg; remove_all_reg = 1; } } else { if (holder->key == sa_res_key) { if ((pr_type != holder->pr_type) || (pr_scope != holder->pr_scope)) res_released = 1; reg->pr_type = pr_type; reg->pr_scope = pr_scope; cmd->dev->pr_holder = reg; } else { if (!sa_res_key) goto sense; } } } list_for_each_entry_safe(sibling, n, &cmd->dev->registration_list, registration_siblings) { if (sibling == reg) continue; if (sibling->key == sa_res_key || remove_all_reg) { ua_sense_add_it_nexus(sibling->nexus_id, cmd->dev, ASC_RESERVATIONS_PREEMPTED); __unregister(cmd->dev, sibling); } else { if (res_released) ua_sense_add_it_nexus(sibling->nexus_id, cmd->dev, ASC_RESERVATIONS_RELEASED); } } cmd->dev->prgeneration++; return SAM_STAT_GOOD; sense: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } static int spc_pr_register_and_move(int host_no, struct scsi_cmd *cmd) { uint16_t asc = ASC_INVALID_FIELD_IN_CDB; uint8_t key = ILLEGAL_REQUEST; uint32_t param_list_len; char *buf; uint8_t unreg, aptpl; uint64_t res_key, sa_res_key; uint32_t tpid_data_len, idlen; struct registration *reg, *dst; int (*id)(int, uint64_t, char *, int); char tpid[300]; /* large enough? */ param_list_len = get_unaligned_be32(&cmd->scb[5]); if (param_list_len < 24) goto sense; if (scsi_get_out_length(cmd) < param_list_len) goto sense; buf = scsi_get_out_buffer(cmd); aptpl = buf[17] & 0x01; if (aptpl) /* not reported in capabilities */ goto sense; unreg = buf[17] & 0x02; res_key = get_unaligned_be64(buf); sa_res_key = get_unaligned_be64(buf + 8); tpid_data_len = get_unaligned_be32(&buf[20]); if (tpid_data_len < 24 || tpid_data_len % 4 != 0) goto sense; if (param_list_len - 24 < tpid_data_len) goto sense; reg = lookup_registration_by_nexus(cmd->dev, cmd->it_nexus); if (!reg) { if (cmd->dev->pr_holder) return SAM_STAT_RESERVATION_CONFLICT; else goto sense; } if (!is_pr_holder(cmd->dev, reg)) return SAM_STAT_GOOD; if (reg->key != res_key) return SAM_STAT_RESERVATION_CONFLICT; if (!sa_res_key) return SAM_STAT_RESERVATION_CONFLICT; list_for_each_entry(dst, &cmd->dev->registration_list, registration_siblings) if (dst->key == sa_res_key) goto found; /* we can't find the destination */ goto sense; found: id = tgt_drivers[cmd->c_target->lid]->transportid; if (id) { memset(tpid, 0, sizeof(tpid)); idlen = id(cmd->dev->tgt->tid, dst->nexus_id, tpid, sizeof(tpid)); if (tpid_data_len != idlen || memcmp(tpid, &buf[24], idlen)) goto sense; } cmd->dev->pr_holder = dst; if (unreg) __unregister(cmd->dev, reg); cmd->dev->prgeneration++; return SAM_STAT_GOOD; sense: scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } struct service_action persistent_reserve_out_actions[] = { {PR_OUT_REGISTER, spc_pr_register}, {PR_OUT_RESERVE, spc_pr_reserve}, {PR_OUT_RELEASE, spc_pr_release}, {PR_OUT_CLEAR, spc_pr_clear}, {PR_OUT_PREEMPT, spc_pr_preempt}, /* {PR_OUT_PREEMPT_AND_ABORT, spc_pr_preempt}, */ {PR_OUT_REGISTER_AND_IGNORE_EXISTING_KEY, spc_pr_register}, {PR_OUT_REGISTER_AND_MOVE, spc_pr_register_and_move}, {0, NULL}, }; int spc_access_check(struct scsi_cmd *cmd) { unsigned char op = cmd->scb[0]; uint8_t bits; uint8_t pr_type; struct registration *reg; int conflict = 0; if (!cmd->dev->pr_holder) return 0; reg = lookup_registration_by_nexus(cmd->dev, cmd->it_nexus); if (reg && is_pr_holder(cmd->dev, reg)) return 0; pr_type = cmd->dev->pr_holder->pr_type; bits = cmd->dev->dev_type_template.ops[op].pr_conflict_bits; if (pr_type == PR_TYPE_EXCLUSIVE_ACCESS) conflict = bits & PR_EA_FA; else if (pr_type == PR_TYPE_WRITE_EXCLUSIVE) conflict = bits & PR_WE_FA; else { if (reg) conflict = bits & PR_RR_FR; else { if (pr_type == PR_TYPE_WRITE_EXCLUSIVE_REGONLY || pr_type == PR_TYPE_WRITE_EXCLUSIVE_ALLREG) conflict = bits & PR_WE_FN; else conflict = bits & PR_EA_FN; } } return conflict; } int spc_request_sense(int host_no, struct scsi_cmd *cmd) { uint32_t alloc_len, actual_len; alloc_len = (uint32_t)cmd->scb[4]; alloc_len = min_t(uint32_t, alloc_len, scsi_get_in_length(cmd)); sense_data_build(cmd, NO_SENSE, NO_ADDITIONAL_SENSE); actual_len = spc_memcpy(scsi_get_in_buffer(cmd), &alloc_len, cmd->sense_buffer, cmd->sense_len); scsi_set_in_resid_by_actual(cmd, actual_len); /* reset sense buffer in cmnd */ memset(cmd->sense_buffer, 0, sizeof(cmd->sense_buffer)); cmd->sense_len = 0; return SAM_STAT_GOOD; } struct vpd *alloc_vpd(uint16_t size) { struct vpd *vpd_pg; vpd_pg = zalloc(sizeof(struct vpd) + size); if (!vpd_pg) return NULL; vpd_pg->size = size; return vpd_pg; } static struct mode_pg *alloc_mode_pg(uint8_t pcode, uint8_t subpcode, uint16_t size) { struct mode_pg *pg; pg = zalloc(sizeof(*pg) + size * 2); if (!pg) return NULL; pg->pcode = pcode; pg->subpcode = subpcode; pg->pcode_size = size; return pg; } tgtadm_err add_mode_page(struct scsi_lu *lu, char *p) { tgtadm_err adm_err = TGTADM_SUCCESS; int i, tmp; uint8_t pcode, subpcode, *data; uint16_t size; struct mode_pg *pg; pcode = subpcode = i = size = 0; data = NULL; for (i = 0; p; i++) { switch (i) { case 0: pcode = strtol(p, NULL, 0); break; case 1: subpcode = strtol(p, NULL, 0); break; case 2: size = strtol(p, NULL, 0); pg = find_mode_page(lu, pcode, subpcode); if (pg) { list_del(&pg->mode_pg_siblings); free(pg); } pg = alloc_mode_pg(pcode, subpcode, size); if (!pg) { adm_err = TGTADM_NOMEM; goto exit; } list_add_tail(&pg->mode_pg_siblings, &lu->mode_pages); data = pg->mode_data; break; default: if (i < (size + 3)) { tmp = strtol(p, NULL, 0); if (tmp > UINT8_MAX) eprintf("Incorrect value %d " "Mode page %d (0x%02x), index: %d\n", tmp, pcode, subpcode, i - 3); data[i - 3] = (uint8_t)tmp; } break; } p = strchr(p, ':'); if (p) p++; } if (i > size + 3) { adm_err = TGTADM_INVALID_REQUEST; eprintf("Mode Page %d (0x%02x): param_count %d > " "MODE PAGE size : %d\n", pcode, subpcode, i, size + 3); } exit: return adm_err; } void dump_cdb(struct scsi_cmd *cmd) { uint8_t *cdb = cmd->scb; switch(cmd->scb_len) { case 6: dprintf("SCSI CMD: %02x %02x %02x %02x %02d %02x\n", cdb[0], cdb[1], cdb[2], cdb[3], cdb[4], cdb[5]); break; case 10: dprintf("SCSI CMD: %02x %02x %02x %02x %02d %02x\n" " %02x %02x %02x %02x", cdb[0], cdb[1], cdb[2], cdb[3], cdb[4], cdb[5], cdb[6], cdb[7], cdb[8], cdb[9]); break; case 12: dprintf("SCSI CMD: %02x %02x %02x %02x %02d %02x" " %02x %02x %02x %02x %02x %02x\n", cdb[0], cdb[1], cdb[2], cdb[3], cdb[4], cdb[5], cdb[6], cdb[7], cdb[8], cdb[9], cdb[10], cdb[11]); break; case 16: dprintf("SCSI CMD: %02x %02x %02x %02x %02d %02x" " %02x %02x %02x %02x %02x %02x" " %02x %02x %02x %02x\n", cdb[0], cdb[1], cdb[2], cdb[3], cdb[4], cdb[5], cdb[6], cdb[7], cdb[8], cdb[9], cdb[10], cdb[11], cdb[12], cdb[13], cdb[14], cdb[15]); break; } } int spc_illegal_op(int host_no, struct scsi_cmd *cmd) { dump_cdb(cmd); scsi_set_in_resid_by_actual(cmd, 0); sense_data_build(cmd, ILLEGAL_REQUEST, ASC_INVALID_OP_CODE); return SAM_STAT_CHECK_CONDITION; } enum { Opt_scsi_id, Opt_scsi_sn, Opt_vendor_id, Opt_product_id, Opt_product_rev, Opt_sense_format, Opt_lbppbe, Opt_la_lba, Opt_optimal_xfer_gran, Opt_optimal_xfer_len, Opt_removable, Opt_readonly, Opt_online, Opt_mode_page, Opt_path, Opt_bsopts, Opt_bsoflags, Opt_thinprovisioning, Opt_rotation_rate, Opt_err, }; static match_table_t tokens = { {Opt_scsi_id, "scsi_id=%s"}, {Opt_scsi_sn, "scsi_sn=%s"}, {Opt_vendor_id, "vendor_id=%s"}, {Opt_product_id, "product_id=%s"}, {Opt_product_rev, "product_rev=%s"}, {Opt_sense_format, "sense_format=%s"}, {Opt_lbppbe, "lbppbe=%s"}, {Opt_la_lba, "la_lba=%s"}, {Opt_optimal_xfer_gran, "optimal_xfer_gran=%s"}, {Opt_optimal_xfer_len, "optimal_xfer_len=%s"}, {Opt_removable, "removable=%s"}, {Opt_readonly, "readonly=%s"}, {Opt_online, "online=%s"}, {Opt_mode_page, "mode_page=%s"}, {Opt_path, "path=%s"}, {Opt_bsopts, "bsopts=%s"}, {Opt_bsoflags, "bsoflags=%s"}, {Opt_thinprovisioning, "thin_provisioning=%s"}, {Opt_rotation_rate, "rotation_rate=%s"}, {Opt_err, NULL}, }; tgtadm_err spc_lu_online(struct scsi_lu *lu) { lu->attrs.online = 1; return TGTADM_SUCCESS; } tgtadm_err spc_lu_offline(struct scsi_lu *lu) { if (lu_prevent_removal(lu)) return TGTADM_PREVENT_REMOVAL; lu->attrs.online = 0; return TGTADM_SUCCESS; } tgtadm_err lu_config(struct scsi_lu *lu, char *params, match_fn_t *fn) { tgtadm_err adm_err = TGTADM_SUCCESS; char *p; char buf[1024]; struct lu_phy_attr *attrs; struct vpd **lu_vpd; attrs = &lu->attrs; lu_vpd = attrs->lu_vpd; if (params && !strncmp("targetOps", params, 9)) params = params + 10; while ((p = strsep(¶ms, ",")) != NULL) { substring_t args[MAX_OPT_ARGS]; int token; if (!*p) continue; token = match_token(p, tokens, args); switch (token) { case Opt_scsi_id: match_strncpy(attrs->scsi_id, &args[0], sizeof(attrs->scsi_id)); lu_vpd[PCODE_OFFSET(0x83)]->vpd_update(lu, attrs->scsi_id); break; case Opt_scsi_sn: match_strncpy(attrs->scsi_sn, &args[0], sizeof(attrs->scsi_sn)); lu_vpd[PCODE_OFFSET(0x80)]->vpd_update(lu, attrs->scsi_sn); break; case Opt_vendor_id: match_strncpy(attrs->vendor_id, &args[0], sizeof(attrs->vendor_id)); break; case Opt_product_id: match_strncpy(attrs->product_id, &args[0], sizeof(attrs->product_id)); break; case Opt_product_rev: match_strncpy(attrs->product_rev, &args[0], sizeof(attrs->product_rev)); break; case Opt_sense_format: match_strncpy(buf, &args[0], sizeof(buf)); attrs->sense_format = atoi(buf); break; case Opt_lbppbe: match_strncpy(buf, &args[0], sizeof(buf)); attrs->lbppbe = atoi(buf); attrs->no_auto_lbppbe = 1; break; case Opt_la_lba: match_strncpy(buf, &args[0], sizeof(buf)); attrs->la_lba = atoi(buf); break; case Opt_optimal_xfer_gran: match_strncpy(buf, &args[0], sizeof(buf)); update_b0_opt_xfer_gran(lu, atoi(buf)); break; case Opt_optimal_xfer_len: match_strncpy(buf, &args[0], sizeof(buf)); update_b0_opt_xfer_len(lu, atoi(buf)); break; case Opt_removable: match_strncpy(buf, &args[0], sizeof(buf)); attrs->removable = atoi(buf); break; case Opt_readonly: match_strncpy(buf, &args[0], sizeof(buf)); attrs->readonly = atoi(buf); break; case Opt_thinprovisioning: match_strncpy(buf, &args[0], sizeof(buf)); attrs->thinprovisioning = atoi(buf); /* update the provisioning vpd page */ lu_vpd[PCODE_OFFSET(0xb0)]->vpd_update(lu, NULL); lu_vpd[PCODE_OFFSET(0xb2)]->vpd_update(lu, NULL); break; case Opt_rotation_rate: match_strncpy(buf, &args[0], sizeof(buf)); attrs->rotation_rate = atoi(buf); /* update the characteristics vpd page */ lu_vpd[PCODE_OFFSET(0xb1)]->vpd_update(lu, NULL); break; case Opt_online: match_strncpy(buf, &args[0], sizeof(buf)); if (atoi(buf)) adm_err = lu->dev_type_template.lu_online(lu); else adm_err = lu->dev_type_template.lu_offline(lu); break; case Opt_mode_page: match_strncpy(buf, &args[0], sizeof(buf)); adm_err = add_mode_page(lu, buf); break; case Opt_path: match_strncpy(buf, &args[0], sizeof(buf)); adm_err = tgt_device_path_update(lu->tgt, lu, buf); break; default: adm_err = fn ? fn(lu, p) : TGTADM_INVALID_REQUEST; } } return adm_err; } tgtadm_err spc_lu_config(struct scsi_lu *lu, char *params) { return lu_config(lu, params, NULL); } int spc_lu_init(struct scsi_lu *lu) { struct vpd **lu_vpd = lu->attrs.lu_vpd; struct target *tgt = lu->tgt; int pg; lu->attrs.device_type = lu->dev_type_template.type; lu->attrs.qualifier = 0x0; lu->attrs.thinprovisioning = 0; lu->attrs.removable = 0; lu->attrs.readonly = 0; lu->attrs.swp = 0; lu->attrs.sense_format = 0; snprintf(lu->attrs.vendor_id, sizeof(lu->attrs.vendor_id), "%-s", VENDOR_ID); snprintf(lu->attrs.product_rev, sizeof(lu->attrs.product_rev), "%s", "0001"); snprintf(lu->attrs.scsi_id, sizeof(lu->attrs.scsi_id), "IET %04x%04" PRIx64, tgt->tid, lu->lun); snprintf(lu->attrs.scsi_sn, sizeof(lu->attrs.scsi_sn), "beaf%d%" PRIu64, tgt->tid, lu->lun); lu->attrs.numeric_id = tgt->tid; lu->attrs.numeric_id <<= 32; lu->attrs.numeric_id |= lu->lun; /* VPD page 0x80 */ pg = PCODE_OFFSET(0x80); lu_vpd[pg] = alloc_vpd(SCSI_SN_LEN); if (!lu_vpd[pg]) return -ENOMEM; lu_vpd[pg]->vpd_update = update_vpd_80; lu_vpd[pg]->vpd_update(lu, lu->attrs.scsi_sn); /* VPD page 0x83 */ pg = PCODE_OFFSET(0x83); lu_vpd[pg] = alloc_vpd(3*DESG_HDR_LEN + NAA_DESG_LEN + SCSI_ID_LEN + NAA_DESG_LEN_EXTD); if (!lu_vpd[pg]) return -ENOMEM; lu_vpd[pg]->vpd_update = update_vpd_83; lu_vpd[pg]->vpd_update(lu, lu->attrs.scsi_id); /* VPD page 0xb0 */ pg = PCODE_OFFSET(0xb0); lu_vpd[pg] = alloc_vpd(BLOCK_LIMITS_VPD_LEN); if (!lu_vpd[pg]) return -ENOMEM; lu_vpd[pg]->vpd_update = update_vpd_b0; lu_vpd[pg]->vpd_update(lu, NULL); /* VPD page 0xb1 BLOCK DEVICE CHARACTERISTICS*/ pg = PCODE_OFFSET(0xb1); lu_vpd[pg] = alloc_vpd(BDC_VPD_LEN); if (!lu_vpd[pg]) return -ENOMEM; lu_vpd[pg]->vpd_update = update_vpd_b1; lu_vpd[pg]->vpd_update(lu, NULL); /* VPD page 0xb2 LOGICAL BLOCK PROVISIONING*/ pg = PCODE_OFFSET(0xb2); lu_vpd[pg] = alloc_vpd(LBP_VPD_LEN); if (!lu_vpd[pg]) return -ENOMEM; lu_vpd[pg]->vpd_update = update_vpd_b2; lu_vpd[pg]->vpd_update(lu, NULL); lu->dev_type_template.lu_offline(lu); return 0; } void spc_lu_exit(struct scsi_lu *lu) { int i; struct vpd **lu_vpd = lu->attrs.lu_vpd; for (i = 0; i < ARRAY_SIZE(lu->attrs.lu_vpd); i++) if (lu_vpd[i]) free(lu_vpd[i]); while (!list_empty(&lu->mode_pages)) { struct mode_pg *pg; pg = list_first_entry(&lu->mode_pages, struct mode_pg, mode_pg_siblings); list_del(&pg->mode_pg_siblings); free(pg); } } tgt-1.0.85/usr/spc.h000066400000000000000000000033501435417276200141660ustar00rootroot00000000000000#ifndef __SPC_H #define __SPC_H extern struct service_action maint_in_service_actions[], persistent_reserve_in_actions[], persistent_reserve_out_actions[]; extern int spc_service_action(int host_no, struct scsi_cmd *cmd); extern int spc_inquiry(int host_no, struct scsi_cmd *cmd); extern int spc_report_luns(int host_no, struct scsi_cmd *cmd); extern int spc_start_stop(int host_no, struct scsi_cmd *cmd); extern int spc_test_unit(int host_no, struct scsi_cmd *cmd); extern int spc_request_sense(int host_no, struct scsi_cmd *cmd); extern int spc_prevent_allow_media_removal(int host_no, struct scsi_cmd *cmd); extern int spc_illegal_op(int host_no, struct scsi_cmd *cmd); extern int spc_lu_init(struct scsi_lu *lu); extern int spc_send_diagnostics(int host_no, struct scsi_cmd *cmd); typedef tgtadm_err (match_fn_t)(struct scsi_lu *lu, char *params); extern tgtadm_err lu_config(struct scsi_lu *lu, char *params, match_fn_t *); extern tgtadm_err spc_lu_config(struct scsi_lu *lu, char *params); extern void spc_lu_exit(struct scsi_lu *lu); extern void dump_cdb(struct scsi_cmd *cmd); extern int spc_mode_sense(int host_no, struct scsi_cmd *cmd); extern tgtadm_err add_mode_page(struct scsi_lu *lu, char *params); extern int set_mode_page_changeable_mask(struct scsi_lu *lu, uint8_t pcode, uint8_t subpcode, uint8_t *mask); extern struct mode_pg *find_mode_page(struct scsi_lu *lu, uint8_t pcode, uint8_t subpcode); extern int spc_mode_select(int host_no, struct scsi_cmd *cmd, int (*update)(struct scsi_cmd *, uint8_t *, int *)); extern struct vpd *alloc_vpd(uint16_t size); extern tgtadm_err spc_lu_online(struct scsi_lu *lu); extern tgtadm_err spc_lu_offline(struct scsi_lu *lu); extern int spc_access_check(struct scsi_cmd *cmd); #endif tgt-1.0.85/usr/ssc.c000066400000000000000000000205611435417276200141670ustar00rootroot00000000000000/* * SCSI stream command processing * * 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, version 2 of the * License. * * 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 "list.h" #include "util.h" #include "tgtd.h" #include "tgtadm_error.h" #include "target.h" #include "driver.h" #include "scsi.h" #include "spc.h" #include "ssc.h" #include "tgtadm_error.h" #define GRANULARITY 9 #define MAX_BLK_SIZE 1048576 #define MIN_BLK_SIZE 4 static inline uint32_t ssc_get_block_length(struct scsi_lu *lu) { return get_unaligned_be24(lu->mode_block_descriptor + 5); } static int ssc_mode_page_update(struct scsi_cmd *cmd, uint8_t *data, int *changed) { return 1; } static int ssc_mode_sense(int host_no, struct scsi_cmd *cmd) { int ret; uint8_t *data, mode6; ret = spc_mode_sense(host_no, cmd); if (ret != SAM_STAT_GOOD) return ret; mode6 = (cmd->scb[0] == 0x1a); data = scsi_get_in_buffer(cmd); /* set write protect bit to 1 for readonly devices */ if (cmd->dev->attrs.readonly) { if (mode6) data[2] |= 0x80; else data[3] |= 0x80; } /* set the device to report BUFFERED MODE for writes */ if (mode6) data[2] |= 0x10; else data[3] |= 0x10; return ret; } static int ssc_mode_select(int host_no, struct scsi_cmd *cmd) { return spc_mode_select(host_no, cmd, ssc_mode_page_update); } static int ssc_rw(int host_no, struct scsi_cmd *cmd) { int ret; unsigned char key = ILLEGAL_REQUEST; uint16_t asc = ASC_LUN_NOT_SUPPORTED; ret = device_reserved(cmd); if (ret) return SAM_STAT_RESERVATION_CONFLICT; if (cmd->dev->attrs.removable && !cmd->dev->attrs.online) { key = NOT_READY; asc = ASC_MEDIUM_NOT_PRESENT; goto sense; } if (cmd->dev->attrs.readonly) { switch (cmd->scb[0]) { case ERASE: case SPACE: case WRITE_6: case WRITE_FILEMARKS: key = DATA_PROTECT; asc = ASC_WRITE_PROTECT; goto sense; break; } } ret = cmd->dev->bst->bs_cmd_submit(cmd); if (ret) { key = HARDWARE_ERROR; asc = ASC_INTERNAL_TGT_FAILURE; } else return SAM_STAT_GOOD; sense: cmd->offset = 0; scsi_set_in_resid_by_actual(cmd, 0); scsi_set_out_resid_by_actual(cmd, 0); sense_data_build(cmd, key, asc); return SAM_STAT_CHECK_CONDITION; } #define READ_BLK_LIMITS_SZ 6 static int ssc_read_block_limit(int host_no, struct scsi_cmd *cmd) { uint8_t buf[READ_BLK_LIMITS_SZ]; uint8_t block_length = ssc_get_block_length(cmd->dev); memset(buf, 0, sizeof(buf)); buf[0] = GRANULARITY; if (block_length) { /* Fixed block size */ put_unaligned_be24(block_length, buf + 1); put_unaligned_be16(block_length, buf + 4); } else { /* Variable block size */ put_unaligned_be24(MAX_BLK_SIZE, buf + 1); put_unaligned_be16(MIN_BLK_SIZE, buf + 4); } memcpy(scsi_get_in_buffer(cmd), buf, READ_BLK_LIMITS_SZ); eprintf("In ssc_read_block_limit \n"); return SAM_STAT_GOOD; } static tgtadm_err ssc_lu_init(struct scsi_lu *lu) { uint8_t *data; struct ssc_info *ssc; ssc = zalloc(sizeof(struct ssc_info)); if (!ssc) return TGTADM_NOMEM; dtype_priv(lu) = ssc; if (spc_lu_init(lu)) return TGTADM_NOMEM; strncpy(lu->attrs.product_id, "VIRTUAL-TAPE", sizeof(lu->attrs.product_id)); /* use only fixed for now */ lu->attrs.sense_format = 0; lu->attrs.version_desc[0] = 0x0200; /* SSC no version claimed */ lu->attrs.version_desc[1] = 0x0960; /* iSCSI */ lu->attrs.version_desc[2] = 0x0300; /* SPC-3 */ lu->attrs.removable = 1; data = lu->mode_block_descriptor; /* SSC devices do not need to set number of blks */ put_unaligned_be24(0, data + 1); /* Set default blk size */ put_unaligned_be24(0, data + 5); /* Vendor uniq - However most apps seem to call for mode page 0*/ add_mode_page(lu, "0:0:0"); /* Read-Write Error Recovery - Mandatory - SSC3 8.3.5 */ add_mode_page(lu, "1:0:10:0:8:0:0:0:0:8:0:0:0"); /* Disconnect page - Mandatory - SPC-4 */ add_mode_page(lu, "2:0:14:0x80:0x80:0:0xa:0:0:0:0:0:0:0:0:0:0"); /* Control page - Mandatory - SPC-4 */ add_mode_page(lu, "0x0a:0:10:2:0:0:0:0:0:0:0:2:0"); /* Control Extensions mode page: TCMOS:1 */ add_mode_page(lu, "0x0a:1:0x1c:0x04:0x00:0x00"); /* Data Compression - Mandatory - SSC3 8.3.2 */ add_mode_page(lu, "15:0:14:0:0:0:0:0:0:0:0:0:0:0:0:0:0"); /* Device Configuration - Mandatory - SSC3 8.3.3 */ add_mode_page(lu, "16:0:14:0:0:0:128:128:0:0:0:0:0:0:0:0:0"); /* Informational Exceptions Control page - Mandatory - SSC3 8.3.6 */ add_mode_page(lu, "0x1c:0:10:8:0:0:0:0:0:0:0:0:0"); /* Medium Configuration - Mandatory - SSC3 8.3.7 */ add_mode_page(lu, "0x1d:0:0x1e:1:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0" ":0:0:0:0:0:0:0:0:0:0:0:0:0"); return TGTADM_SUCCESS; } static struct device_type_template ssc_template = { .type = TYPE_TAPE, .lu_init = ssc_lu_init, .lu_config = spc_lu_config, .lu_online = spc_lu_online, .lu_offline = spc_lu_offline, .lu_exit = spc_lu_exit, .ops = { {spc_test_unit,}, {ssc_rw,}, {spc_illegal_op,}, {spc_request_sense,}, {spc_illegal_op,}, {ssc_read_block_limit,}, {spc_illegal_op,}, {spc_illegal_op,}, {ssc_rw,}, {spc_illegal_op,}, {ssc_rw,}, {ssc_rw,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, /* 0x10 */ {ssc_rw,}, {ssc_rw,}, {spc_inquiry,}, {spc_illegal_op,}, {spc_illegal_op,}, {ssc_mode_select,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {ssc_mode_sense,}, {spc_start_stop,}, {spc_illegal_op,}, {spc_send_diagnostics,}, {spc_prevent_allow_media_removal,}, {spc_illegal_op,}, /* 0x20 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, /* 0x30 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {ssc_rw,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, [0x40 ... 0x4f] = {spc_illegal_op,}, /* 0x50 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {ssc_mode_sense,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, [0x60 ... 0x7f] = {spc_illegal_op,}, /* 0x80 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, /* 0x90 */ {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, /* 0xA0 */ {spc_report_luns,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_service_action, maint_in_service_actions,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, {spc_illegal_op,}, [0xb0 ... 0xff] = {spc_illegal_op}, } }; __attribute__((constructor)) static void ssc_init(void) { device_type_register(&ssc_template); } tgt-1.0.85/usr/ssc.h000066400000000000000000000031211435417276200141650ustar00rootroot00000000000000/* * SCSI Streaming specific header */ #ifndef _SSC_H_ #define _SSC_H_ struct blk_header_info { uint32_t ondisk_sz; uint32_t blk_sz; uint32_t blk_type; uint64_t blk_num; uint64_t prev; uint64_t curr; uint64_t next; }; /* * MAM structure based from IBM Ultrium SCSI Reference WB1109-02 */ struct MAM_info { uint32_t tape_fmt_version; uint64_t remaining_capacity; uint64_t max_capacity; uint64_t TapeAlert; uint64_t load_count; uint64_t MAM_space_remaining; uint8_t assigning_organization_1[8]; uint8_t formatted_density_code; uint8_t initialization_count[2]; uint8_t dev_make_serial_last_load[4][40]; uint64_t written_in_medium_life; uint64_t read_in_medium_life; uint64_t written_in_last_load; uint64_t read_in_last_load; uint8_t medium_manufacturer[8]; uint8_t medium_serial_number[32]; uint32_t medium_length; uint32_t medium_width; uint8_t assigning_organization_2[8]; uint8_t medium_density_code; uint8_t medium_manufacture_date[8]; uint64_t MAM_capacity; uint8_t medium_type; uint16_t medium_type_information; uint8_t application_vendor[8]; uint8_t application_name[32]; uint8_t application_version[8]; uint8_t user_medium_text_label[160]; uint8_t date_time_last_written[12]; uint8_t localization_identifier; uint8_t barcode[32]; uint8_t owning_host_textual_name[80]; uint8_t media_pool[160]; uint8_t vendor_unique[256]; uint8_t dirty; }; struct ssc_info { uint64_t bytes_read; /* Bytes read this load */ uint64_t bytes_written; /* Bytes written this load */ struct MAM_info mam; struct blk_header_info c_blk; /* Current block header */ }; #endif tgt-1.0.85/usr/target.c000066400000000000000000001540301435417276200146640ustar00rootroot00000000000000/* * SCSI target daemon core functions * * Copyright (C) 2005-2007 FUJITA Tomonori * Copyright (C) 2005-2007 Mike Christie * * 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, version 2 of the * License. * * 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 #include "list.h" #include "util.h" #include "tgtd.h" #include "driver.h" #include "target.h" #include "scsi.h" #include "iscsi/iscsid.h" #include "tgtadm.h" #include "parser.h" #include "spc.h" static LIST_HEAD(device_type_list); static struct target global_target; int device_type_register(struct device_type_template *t) { list_add_tail(&t->device_type_siblings, &device_type_list); return 0; } static struct device_type_template *device_type_lookup(int type) { struct device_type_template *t; list_for_each_entry(t, &device_type_list, device_type_siblings) { if (t->type == type) return t; } return NULL; } static LIST_HEAD(target_list); static struct target *target_lookup(int tid) { struct target *target; list_for_each_entry(target, &target_list, target_siblings) if (target->tid == tid) return target; return NULL; } static int target_name_lookup(char *name) { struct target *target; list_for_each_entry(target, &target_list, target_siblings) if (!strcmp(target->name, name)) return 1; return 0; } struct it_nexus *it_nexus_lookup(int tid, uint64_t itn_id) { struct target *target; struct it_nexus *itn; target = target_lookup(tid); if (!target) return NULL; list_for_each_entry(itn, &target->it_nexus_list, nexus_siblings) { if (itn->itn_id == itn_id) return itn; } return NULL; } static int ua_sense_add(struct it_nexus_lu_info *itn_lu, uint16_t asc) { struct ua_sense *uas; uas = zalloc(sizeof(*uas)); if (!uas) return -ENOMEM; if (itn_lu->lu->attrs.sense_format) { /* descriptor format */ uas->ua_sense_buffer[0] = 0x72; /* current, not deferred */ uas->ua_sense_buffer[1] = UNIT_ATTENTION; uas->ua_sense_buffer[2] = (asc >> 8) & 0xff; uas->ua_sense_buffer[3] = asc & 0xff; uas->ua_sense_len = 8; } else { /* fixed format */ int len = 0xa; uas->ua_sense_buffer[0] = 0x70; /* current, not deferred */ uas->ua_sense_buffer[2] = UNIT_ATTENTION; uas->ua_sense_buffer[7] = len; uas->ua_sense_buffer[12] = (asc >> 8) & 0xff; uas->ua_sense_buffer[13] = asc & 0xff; uas->ua_sense_len = len + 8; } list_add_tail(&uas->ua_sense_siblings, &itn_lu->pending_ua_sense_list); return 0; } int ua_sense_del(struct scsi_cmd *cmd, int del) { struct it_nexus_lu_info *itn_lu = cmd->itn_lu_info; struct ua_sense *uas = NULL; int len = sizeof(cmd->sense_buffer); if (!list_empty(&itn_lu->pending_ua_sense_list)) { uas = list_first_entry(&itn_lu->pending_ua_sense_list, struct ua_sense, ua_sense_siblings); memcpy(cmd->sense_buffer, uas->ua_sense_buffer, min(uas->ua_sense_len, len)); cmd->sense_len = min(uas->ua_sense_len, len); /* * FIXME: we should hook the uas to the command * instead of freeing it here. if a transport fails to * send the response, we should revert the * uas. Hooking the uas enable us to avoid memory * allocation. But a driver can't tell us to free the * command now. */ if (del) { list_del(&uas->ua_sense_siblings); free(uas); } } return uas ? 0 : 1; } void ua_sense_clear(struct it_nexus_lu_info *itn_lu, uint16_t asc) { struct ua_sense *uas, *next; unsigned char *src; list_for_each_entry_safe(uas, next, &itn_lu->pending_ua_sense_list, ua_sense_siblings) { if (uas->ua_sense_buffer[0] == 0x72) src = uas->ua_sense_buffer + 2; else src = uas->ua_sense_buffer + 12; if ((src[0] == ((asc >> 8) & 0xff)) && (src[1] == (asc & 0xff))) { list_del(&uas->ua_sense_siblings); free(uas); } } } static void ua_sense_pending_del(struct it_nexus_lu_info *itn_lu) { struct ua_sense *uas; while (!list_empty(&itn_lu->pending_ua_sense_list)) { uas = list_first_entry(&itn_lu->pending_ua_sense_list, struct ua_sense, ua_sense_siblings); list_del(&uas->ua_sense_siblings); free(uas); } } static void it_nexus_del_lu_info(struct it_nexus *itn) { struct it_nexus_lu_info *itn_lu; while (!list_empty(&itn->itn_itl_info_list)) { itn_lu = list_first_entry(&itn->itn_itl_info_list, struct it_nexus_lu_info, itn_itl_info_siblings); ua_sense_pending_del(itn_lu); list_del(&itn_lu->itn_itl_info_siblings); list_del(&itn_lu->lu_itl_info_siblings); free(itn_lu); } } void ua_sense_add_other_it_nexus(uint64_t itn_id, struct scsi_lu *lu, uint16_t asc) { struct it_nexus *itn; struct it_nexus_lu_info *itn_lu; int ret; list_for_each_entry(itn, &lu->tgt->it_nexus_list, nexus_siblings) { if (itn->itn_id == itn_id) continue; list_for_each_entry(itn_lu, &itn->itn_itl_info_list, itn_itl_info_siblings) { if (itn_lu->lu != lu) continue; ret = ua_sense_add(itn_lu, asc); if (ret) eprintf("fail to add ua %" PRIu64 " %" PRIu64 "\n", lu->lun, itn_id); } } } void ua_sense_add_it_nexus(uint64_t itn_id, struct scsi_lu *lu, uint16_t asc) { struct it_nexus *itn; struct it_nexus_lu_info *itn_lu; int ret; list_for_each_entry(itn, &lu->tgt->it_nexus_list, nexus_siblings) { if (itn->itn_id == itn_id) { list_for_each_entry(itn_lu, &itn->itn_itl_info_list, itn_itl_info_siblings) { if (itn_lu->lu == lu) { ret = ua_sense_add(itn_lu, asc); if (ret) eprintf("fail to add ua %" PRIu64 " %" PRIu64 "\n", lu->lun, itn_id); break; } } break; } } } int lu_prevent_removal(struct scsi_lu *lu) { struct it_nexus *itn; struct it_nexus_lu_info *itn_lu; list_for_each_entry(itn, &lu->tgt->it_nexus_list, nexus_siblings) { list_for_each_entry(itn_lu, &itn->itn_itl_info_list, itn_itl_info_siblings) { if (itn_lu->lu == lu) { if (itn_lu->prevent & PREVENT_REMOVAL) return 1; } } } return 0; } int it_nexus_create(int tid, uint64_t itn_id, int host_no, char *info) { int ret; struct target *target; struct it_nexus *itn; struct scsi_lu *lu; struct it_nexus_lu_info *itn_lu; struct timeval tv; dprintf("%d %" PRIu64 " %d\n", tid, itn_id, host_no); /* for reserve/release code */ if (!itn_id) return -EINVAL; itn = it_nexus_lookup(tid, itn_id); if (itn) return -EEXIST; target = target_lookup(tid); itn = zalloc(sizeof(*itn)); if (!itn) return -ENOMEM; itn->itn_id = itn_id; itn->host_no = host_no; itn->nexus_target = target; itn->info = info; INIT_LIST_HEAD(&itn->itn_itl_info_list); gettimeofday(&tv, NULL); itn->ctime = tv.tv_sec; list_for_each_entry(lu, &target->device_list, device_siblings) { itn_lu = zalloc(sizeof(*itn_lu)); if (!itn_lu) goto out; itn_lu->lu = lu; itn_lu->itn_id = itn_id; INIT_LIST_HEAD(&itn_lu->pending_ua_sense_list); ret = ua_sense_add(itn_lu, ASC_POWERON_RESET); if (ret) { free(itn_lu); goto out; } list_add_tail(&itn_lu->lu_itl_info_siblings, &lu->lu_itl_info_list); list_add(&itn_lu->itn_itl_info_siblings, &itn->itn_itl_info_list); } INIT_LIST_HEAD(&itn->cmd_list); list_add_tail(&itn->nexus_siblings, &target->it_nexus_list); return 0; out: it_nexus_del_lu_info(itn); free(itn); return -ENOMEM; } int it_nexus_destroy(int tid, uint64_t itn_id) { struct it_nexus *itn; struct scsi_lu *lu; dprintf("%d %" PRIu64 "\n", tid, itn_id); itn = it_nexus_lookup(tid, itn_id); if (!itn) return -ENOENT; if (!list_empty(&itn->cmd_list)) return -EBUSY; list_for_each_entry(lu, &itn->nexus_target->device_list, device_siblings) { device_release(tid, itn_id, lu->lun, 0); } it_nexus_del_lu_info(itn); list_del(&itn->nexus_siblings); free(itn); return 0; } static struct scsi_lu *device_lookup(struct target *target, uint64_t lun) { struct scsi_lu *lu; list_for_each_entry(lu, &target->device_list, device_siblings) if (lu->lun == lun) return lu; return NULL; } static void cmd_hlist_insert(struct it_nexus *itn, struct scsi_cmd *cmd) { list_add(&cmd->c_hlist, &itn->cmd_list); } static void cmd_hlist_remove(struct scsi_cmd *cmd) { list_del(&cmd->c_hlist); } static void tgt_cmd_queue_init(struct tgt_cmd_queue *q) { q->active_cmd = 0; q->state = 0; INIT_LIST_HEAD(&q->queue); } tgtadm_err tgt_device_path_update(struct target *target, struct scsi_lu *lu, char *path) { int dev_fd; uint64_t size; int err; if (lu->path) { int ret; if (lu->attrs.online) return TGTADM_INVALID_REQUEST; ret = lu->dev_type_template.lu_offline(lu); if (ret) return ret; lu->bst->bs_close(lu); free(lu->path); lu->fd = 0; lu->addr = 0; lu->size = 0; lu->path = NULL; } path = strdup(path); if (!path) return TGTADM_NOMEM; err = lu->bst->bs_open(lu, path, &dev_fd, &size); if (err) { free(path); return TGTADM_INVALID_REQUEST; } lu->fd = dev_fd; lu->addr = 0; lu->size = size; lu->path = path; return lu->dev_type_template.lu_online(lu); } static struct scsi_lu * __device_lookup(int tid, uint64_t lun, struct target **t) { struct target *target; struct scsi_lu *lu; target = target_lookup(tid); if (!target) return NULL; lu = device_lookup(target, lun); if (!lu) return NULL; if (t) *t = target; return lu; } enum { Opt_path, Opt_bstype, Opt_bsopts, Opt_bsoflags, Opt_blocksize, Opt_err, }; static match_table_t device_tokens = { {Opt_path, "path=%s"}, {Opt_bstype, "bstype=%s"}, {Opt_bsopts, "bsopts=%s"}, {Opt_bsoflags, "bsoflags=%s"}, {Opt_blocksize, "blocksize=%s"}, {Opt_err, NULL}, }; static void __cmd_done(struct target *, struct scsi_cmd *); tgtadm_err tgt_device_create(int tid, int dev_type, uint64_t lun, char *params, int backing) { char *p, *path = NULL, *bstype = NULL, *bsopts = NULL; char *bsoflags = NULL, *blocksize = NULL; int lu_bsoflags = 0; tgtadm_err adm_err = TGTADM_SUCCESS; struct target *target; struct scsi_lu *lu, *pos; struct device_type_template *t; struct backingstore_template *bst; struct it_nexus_lu_info *itn_lu, *itn_lu_pos; struct it_nexus *itn; char strflags[128]; dprintf("%d %" PRIu64 "\n", tid, lun); while ((p = strsep(¶ms, ",")) != NULL) { substring_t args[MAX_OPT_ARGS]; int token; if (!*p) continue; token = match_token(p, device_tokens, args); switch (token) { case Opt_path: path = match_strdup(&args[0]); break; case Opt_bstype: bstype = match_strdup(&args[0]); break; case Opt_bsopts: bsopts = match_strdup(&args[0]); break; case Opt_bsoflags: bsoflags = match_strdup(&args[0]); break; case Opt_blocksize: blocksize = match_strdup(&args[0]); break; default: break; } } target = target_lookup(tid); if (!target) { adm_err = TGTADM_NO_TARGET; goto out; } lu = device_lookup(target, lun); if (lu) { eprintf("device %" PRIu64 " already exists\n", lun); adm_err = TGTADM_LUN_EXIST; goto out; } bst = target->bst; if (backing) { if (bstype) { bst = get_backingstore_template(bstype); if (!bst) { eprintf("failed to find bstype, %s\n", bstype); adm_err = TGTADM_INVALID_REQUEST; goto out; } } } else bst = get_backingstore_template("null"); if ((!strncmp(bst->bs_name, "bsg", 3) || !strncmp(bst->bs_name, "sg", 2)) && dev_type != TYPE_PT) { eprintf("Must set device type to pt for bsg/sg bstype\n"); adm_err = TGTADM_INVALID_REQUEST; goto out; } if (!(!strncmp(bst->bs_name, "bsg", 3) || !strncmp(bst->bs_name, "sg", 2)) && dev_type == TYPE_PT) { eprintf("Must set bstype to bsg or sg for devicetype pt, not %s\n", bst->bs_name); adm_err = TGTADM_INVALID_REQUEST; goto out; } if (bsoflags) { lu_bsoflags = str_to_open_flags(bsoflags); if (lu_bsoflags == -1) { adm_err = TGTADM_INVALID_REQUEST; goto out; } } if (lu_bsoflags && ((bst->bs_oflags_supported & lu_bsoflags) != lu_bsoflags)) { eprintf("bsoflags %s not supported by backing store %s\n", open_flags_to_str(strflags, (bst->bs_oflags_supported & lu_bsoflags) ^ lu_bsoflags), bst->bs_name); adm_err = TGTADM_INVALID_REQUEST; goto out; } t = device_type_lookup(dev_type); if (!t) { eprintf("Unknown device type %d\n", dev_type); adm_err = TGTADM_INVALID_REQUEST; goto out; } lu = zalloc(sizeof(*lu) + bst->bs_datasize); if (!lu) { adm_err = TGTADM_NOMEM; goto out; } lu->dev_type_template = *t; lu->bst = bst; lu->tgt = target; lu->lun = lun; lu->bsoflags = lu_bsoflags; tgt_cmd_queue_init(&lu->cmd_queue); INIT_LIST_HEAD(&lu->registration_list); INIT_LIST_HEAD(&lu->lu_itl_info_list); INIT_LIST_HEAD(&lu->mode_pages); lu->prgeneration = 0; lu->pr_holder = NULL; lu->cmd_perform = &target_cmd_perform; lu->cmd_done = &__cmd_done; lu->blk_shift = 0; if (blocksize) { unsigned int bsize; int bshift; dprintf("blocksize=%s\n", blocksize); bsize = strtoul(blocksize, NULL, 0); bshift = get_blk_shift(bsize); if (bshift > 0) lu->blk_shift = bshift; else { if (bsize > 0) eprintf("invalid block size: %u\n", bsize); else eprintf("invalid block size string: %s\n", blocksize); } } if (lu->dev_type_template.lu_init) { adm_err = lu->dev_type_template.lu_init(lu); if (adm_err) goto fail_lu_init; } if (lu->bst->bs_init) { if (bsopts) dprintf("bsopts=%s\n", bsopts); adm_err = lu->bst->bs_init(lu, bsopts); if (adm_err) goto fail_lu_init; } if (backing && !path) { lu->attrs.removable = 1; lu->attrs.online = 0; } if (backing && path) { adm_err = tgt_device_path_update(target, lu, path); if (adm_err) goto fail_bs_init; } if (tgt_drivers[target->lid]->lu_create) tgt_drivers[target->lid]->lu_create(lu); list_for_each_entry(pos, &target->device_list, device_siblings) { if (lu->lun < pos->lun) break; } list_add_tail(&lu->device_siblings, &pos->device_siblings); list_for_each_entry(itn, &target->it_nexus_list, nexus_siblings) { itn_lu = zalloc(sizeof(*itn_lu)); if (!itn_lu) break; itn_lu->lu = lu; itn_lu->itn_id = itn->itn_id; INIT_LIST_HEAD(&itn_lu->pending_ua_sense_list); /* signal LUNs info change thru all LUNs in the nexus */ list_for_each_entry(itn_lu_pos, &itn->itn_itl_info_list, itn_itl_info_siblings) { int ret; ret = ua_sense_add(itn_lu_pos, ASC_REPORTED_LUNS_DATA_HAS_CHANGED); if (ret) { adm_err = TGTADM_NOMEM; goto fail_bs_init; } } list_add_tail(&itn_lu->lu_itl_info_siblings, &lu->lu_itl_info_list); list_add(&itn_lu->itn_itl_info_siblings, &itn->itn_itl_info_list); } if (backing && !path) lu->dev_type_template.lu_offline(lu); dprintf("Add a logical unit %" PRIu64 " to the target %d\n", lun, tid); out: if (bstype) free(bstype); if (blocksize) free(blocksize); if (path) free(path); if (bsoflags) free(bsoflags); return adm_err; fail_bs_init: if (lu->bst->bs_exit) lu->bst->bs_exit(lu); fail_lu_init: free(lu); goto out; } tgtadm_err tgt_device_destroy(int tid, uint64_t lun, int force) { struct target *target; struct scsi_lu *lu; struct it_nexus *itn; struct it_nexus_lu_info *itn_lu, *next; struct registration *reg, *reg_next; int ret; dprintf("%u %" PRIu64 "\n", tid, lun); /* lun0 is special */ if (!lun && !force) return TGTADM_INVALID_REQUEST; lu = __device_lookup(tid, lun, &target); if (!lu) { eprintf("device %" PRIu64 " not found\n", lun); return TGTADM_NO_LUN; } if (!list_empty(&lu->cmd_queue.queue) || lu->cmd_queue.active_cmd) return TGTADM_LUN_ACTIVE; if (lu->dev_type_template.lu_exit) lu->dev_type_template.lu_exit(lu); if (lu->path) { free(lu->path); lu->bst->bs_close(lu); } if (lu->bst->bs_exit) lu->bst->bs_exit(lu); list_for_each_entry(itn, &target->it_nexus_list, nexus_siblings) { list_for_each_entry_safe(itn_lu, next, &itn->itn_itl_info_list, itn_itl_info_siblings) { if (itn_lu->lu == lu) { ua_sense_pending_del(itn_lu); list_del(&itn_lu->itn_itl_info_siblings); list_del(&itn_lu->lu_itl_info_siblings); free(itn_lu); break; } } } list_del(&lu->device_siblings); list_for_each_entry_safe(reg, reg_next, &lu->registration_list, registration_siblings) { free(reg); } free(lu); list_for_each_entry(itn, &target->it_nexus_list, nexus_siblings) { list_for_each_entry(itn_lu, &itn->itn_itl_info_list, itn_itl_info_siblings) { ret = ua_sense_add(itn_lu, ASC_REPORTED_LUNS_DATA_HAS_CHANGED); if (ret) eprintf("fail to add ua %" PRIu64 " %" PRIu64 "\n", lun, itn->itn_id); } } return TGTADM_SUCCESS; } struct lu_phy_attr *lu_attr_lookup(int tid, uint64_t lun) { struct target *target; struct scsi_lu *lu; lu = __device_lookup(tid, lun, &target); if (!lu) return NULL; return &lu->attrs; } /** * dtd_check_removable * @tid: Target ID * @lun: LUN * * check if a DT can have its media removed or not */ tgtadm_err dtd_check_removable(int tid, uint64_t lun) { struct scsi_lu *lu; lu = __device_lookup(tid, lun, NULL); if (!lu) return TGTADM_NO_LUN; if (lu_prevent_removal(lu)) return TGTADM_PREVENT_REMOVAL; if (!lu->attrs.removable) return TGTADM_INVALID_REQUEST; return TGTADM_SUCCESS; } /** * dtd_load_unload -- Load / unload media * @tid: Target ID * @lun: LUN * @load: True if load, not true - unload * @file: filename of 'media' top open * * load/unload media from the DATA TRANSFER DEVICE. */ tgtadm_err dtd_load_unload(int tid, uint64_t lun, int load, char *file) { struct target *target; struct scsi_lu *lu; tgtadm_err adm_err = TGTADM_SUCCESS; lu = __device_lookup(tid, lun, &target); if (!lu) return TGTADM_NO_LUN; if (lu_prevent_removal(lu)) return TGTADM_PREVENT_REMOVAL; if (!lu->attrs.removable) return TGTADM_INVALID_REQUEST; if (lu->path) { lu->bst->bs_close(lu); free(lu->path); lu->path = NULL; } lu->size = 0; lu->fd = 0; adm_err = lu->dev_type_template.lu_offline(lu); if (adm_err) return adm_err; if (load) { lu->path = strdup(file); if (!lu->path) return TGTADM_NOMEM; lu->bst->bs_open(lu, file, &lu->fd, &lu->size); if (lu->fd < 0) { free(lu->path); lu->path = NULL; return TGTADM_UNSUPPORTED_OPERATION; } adm_err = lu->dev_type_template.lu_online(lu); } return adm_err; } int device_reserve(struct scsi_cmd *cmd) { struct scsi_lu *lu; lu = device_lookup(cmd->c_target, cmd->dev->lun); if (!lu) { eprintf("invalid target and lun %d %" PRIu64 "\n", cmd->c_target->tid, cmd->dev->lun); return 0; } if (lu->reserve_id && lu->reserve_id != cmd->cmd_itn_id) { dprintf("already reserved %" PRIu64 " %" PRIu64 "\n", lu->reserve_id, cmd->cmd_itn_id); return -EBUSY; } lu->reserve_id = cmd->cmd_itn_id; return 0; } int device_release(int tid, uint64_t itn_id, uint64_t lun, int force) { struct target *target; struct scsi_lu *lu; lu = __device_lookup(tid, lun, &target); if (!lu) { eprintf("invalid target and lun %d %" PRIu64 "\n", tid, lun); return 0; } if (force || lu->reserve_id == itn_id) { lu->reserve_id = 0; return 0; } if (lu->reserve_id != itn_id) return 0; return -EBUSY; } int device_reserved(struct scsi_cmd *cmd) { struct scsi_lu *lu; lu = device_lookup(cmd->c_target, cmd->dev->lun); if (!lu || !lu->reserve_id || lu->reserve_id == cmd->cmd_itn_id) return 0; return -EBUSY; } tgtadm_err tgt_device_update(int tid, uint64_t dev_id, char *params) { tgtadm_err adm_err = TGTADM_INVALID_REQUEST; struct target *target; struct scsi_lu *lu; target = target_lookup(tid); if (!target) return TGTADM_NO_TARGET; lu = device_lookup(target, dev_id); if (!lu) { eprintf("device %" PRIu64 " not found\n", dev_id); return TGTADM_NO_LUN; } if (lu->dev_type_template.lu_config) adm_err = lu->dev_type_template.lu_config(lu, params); return adm_err; } void tgt_stat_header(struct concat_buf *b) { concat_printf(b, "tgt lun sid " "rd_subm(bytes,cmds) rd_done(bytes,cmds) " "wr_subm(bytes,cmds) wr_done(bytes,cmds) " "errs\n"); } void tgt_stat_line(int tid, uint64_t lun, uint64_t sid, struct lu_stat *stat, struct concat_buf *b) { concat_printf(b, "%3d %3" PRIu64 " %3" PRIu64 " " "%12" PRIu64 " %6" PRIu32 " " "%12" PRIu64 " %6" PRIu32 " " "%12" PRIu64 " %6" PRIu32 " " "%12" PRIu64 " %6" PRIu32 " " "%4" PRIu32 "\n", tid, lun, sid, stat->rd_subm_bytes, stat->rd_subm_cmds, stat->rd_done_bytes, stat->rd_done_cmds, stat->wr_subm_bytes, stat->wr_subm_cmds, stat->wr_done_bytes, stat->wr_done_cmds, stat->err_num); } void tgt_stat_device(struct target *target, struct scsi_lu *lu, struct concat_buf *b) { struct it_nexus_lu_info *itn_lu; list_for_each_entry(itn_lu, &lu->lu_itl_info_list, lu_itl_info_siblings) { tgt_stat_line(target->tid, lu->lun, itn_lu->itn_id, &itn_lu->stat, b); } } tgtadm_err tgt_stat_device_by_id(int tid, uint64_t dev_id, struct concat_buf *b) { struct target *target; struct scsi_lu *lu; tgtadm_err adm_err = TGTADM_SUCCESS; target = target_lookup(tid); if (!target) return TGTADM_NO_TARGET; lu = device_lookup(target, dev_id); if (!lu) { eprintf("device %" PRIu64 " not found\n", dev_id); return TGTADM_NO_LUN; } tgt_stat_header(b); tgt_stat_device(target, lu, b); return adm_err; } tgtadm_err tgt_stat_target(struct target *target, struct concat_buf *b) { struct scsi_lu *lu; tgtadm_err adm_err = TGTADM_SUCCESS; list_for_each_entry(lu, &target->device_list, device_siblings) tgt_stat_device(target, lu, b); return adm_err; } tgtadm_err tgt_stat_target_by_id(int tid, struct concat_buf *b) { struct target *target; tgtadm_err adm_err = TGTADM_SUCCESS; target = target_lookup(tid); if (!target) return TGTADM_NO_TARGET; tgt_stat_header(b); adm_err = tgt_stat_target(target, b); return adm_err; } tgtadm_err tgt_stat_system(struct concat_buf *b) { struct target *target; tgtadm_err adm_err = TGTADM_SUCCESS; tgt_stat_header(b); list_for_each_entry(target, &target_list, target_siblings) adm_err = tgt_stat_target(target, b); return adm_err; } static int cmd_enabled(struct tgt_cmd_queue *q, struct scsi_cmd *cmd) { int enabled = 0; if (cmd->attribute != MSG_SIMPLE_TAG) dprintf("non simple attribute %" PRIx64 " %x %" PRIu64 " %d\n", cmd->tag, cmd->attribute, cmd->dev ? cmd->dev->lun : UINT64_MAX, q->active_cmd); switch (cmd->attribute) { case MSG_SIMPLE_TAG: if (!queue_blocked(q)) enabled = 1; break; case MSG_ORDERED_TAG: if (!queue_blocked(q) && !queue_active(q)) enabled = 1; break; case MSG_HEAD_TAG: enabled = 1; break; default: eprintf("unknown command attribute %x\n", cmd->attribute); cmd->attribute = MSG_ORDERED_TAG; if (!queue_blocked(q) && !queue_active(q)) enabled = 1; } return enabled; } static void cmd_post_perform(struct tgt_cmd_queue *q, struct scsi_cmd *cmd) { q->active_cmd++; switch (cmd->attribute) { case MSG_ORDERED_TAG: case MSG_HEAD_TAG: set_queue_blocked(q); break; } } static struct it_nexus_lu_info *it_nexus_lu_info_lookup(struct it_nexus *itn, uint64_t lun) { struct it_nexus_lu_info *itn_lu; list_for_each_entry(itn_lu, &itn->itn_itl_info_list, itn_itl_info_siblings) { if (itn_lu->lu->lun == lun) return itn_lu; } return NULL; } int target_cmd_queue(int tid, struct scsi_cmd *cmd) { struct target *target; struct it_nexus *itn; uint64_t dev_id, itn_id = cmd->cmd_itn_id; itn = it_nexus_lookup(tid, itn_id); if (!itn) { eprintf("invalid nexus %d %" PRIx64 "\n", tid, itn_id); return -ENOENT; } cmd->c_target = target = itn->nexus_target; cmd->it_nexus = itn; dev_id = scsi_get_devid(target->lid, cmd->lun); cmd->dev_id = dev_id; dprintf("%p %x %" PRIx64 "\n", cmd, cmd->scb[0], dev_id); cmd->dev = device_lookup(target, dev_id); /* use LUN0 */ if (!cmd->dev) cmd->dev = list_first_entry(&target->device_list, struct scsi_lu, device_siblings); cmd->itn_lu_info = it_nexus_lu_info_lookup(itn, cmd->dev->lun); /* service delivery or target failure */ if (target->target_state != SCSI_TARGET_READY) return -EBUSY; /* by default assume zero residual counts */ scsi_set_in_resid(cmd, 0); scsi_set_in_transfer_len(cmd, scsi_get_in_length(cmd)); scsi_set_out_resid(cmd, 0); scsi_set_out_transfer_len(cmd, scsi_get_out_length(cmd)); /* * Call struct scsi_lu->cmd_perform() that will either be setup for * internal or passthrough CDB processing using 2 functions below. */ return cmd->dev->cmd_perform(tid, cmd); } /* * Used by all non bs_sg backstores for internal STGT port emulation */ int target_cmd_perform(int tid, struct scsi_cmd *cmd) { struct tgt_cmd_queue *q = &cmd->dev->cmd_queue; int result, enabled = 0; cmd_hlist_insert(cmd->it_nexus, cmd); enabled = cmd_enabled(q, cmd); dprintf("%p %x %" PRIx64 " %d\n", cmd, cmd->scb[0], cmd->dev_id, enabled); if (enabled) { result = scsi_cmd_perform(cmd->it_nexus->host_no, cmd); cmd_post_perform(q, cmd); dprintf("%" PRIx64 " %x %p %p %" PRIu64 " %u %u %d %d\n", cmd->tag, cmd->scb[0], scsi_get_out_buffer(cmd), scsi_get_in_buffer(cmd), cmd->offset, scsi_get_out_length(cmd), scsi_get_in_length(cmd), result, cmd_async(cmd)); set_cmd_processed(cmd); if (!cmd_async(cmd)) target_cmd_io_done(cmd, result); } else { set_cmd_queued(cmd); dprintf("blocked %" PRIx64 " %x %" PRIu64 " %d\n", cmd->tag, cmd->scb[0], cmd->dev->lun, q->active_cmd); list_add_tail(&cmd->qlist, &q->queue); } return 0; } /* * Used by bs_sg for CDB passthrough to STGT LUNs */ int target_cmd_perform_passthrough(int tid, struct scsi_cmd *cmd) { int result; dprintf("%p %x %" PRIx64 " PT\n", cmd, cmd->scb[0], cmd->dev_id); result = cmd->dev->dev_type_template.cmd_passthrough(tid, cmd); dprintf("%" PRIx64 " %x %p %p %" PRIu64 " %u %u %d %d\n", cmd->tag, cmd->scb[0], scsi_get_out_buffer(cmd), scsi_get_in_buffer(cmd), cmd->offset, scsi_get_out_length(cmd), scsi_get_in_length(cmd), result, cmd_async(cmd)); set_cmd_processed(cmd); if (!cmd_async(cmd)) target_cmd_io_done(cmd, result); return 0; } void target_cmd_io_done(struct scsi_cmd *cmd, int result) { enum data_direction cmd_dir = scsi_get_data_dir(cmd); struct lu_stat *stat = &cmd->itn_lu_info->stat; int lid = cmd->c_target->lid; scsi_set_result(cmd, result); if (cmd_dir == DATA_WRITE) { stat->wr_done_bytes += scsi_get_out_length(cmd); stat->wr_done_cmds++; } else if (cmd_dir == DATA_READ) { stat->rd_done_bytes += scsi_get_in_length(cmd); stat->rd_done_cmds++; } else if (cmd_dir == DATA_BIDIRECTIONAL) { stat->wr_done_bytes += scsi_get_out_length(cmd); stat->rd_done_bytes += scsi_get_in_length(cmd); stat->bidir_done_cmds++; } if (result != SAM_STAT_GOOD) stat->err_num++; tgt_drivers[lid]->cmd_end_notify(cmd->cmd_itn_id, result, cmd); return; } static void post_cmd_done(struct tgt_cmd_queue *q) { struct scsi_cmd *cmd, *tmp; int enabled, result; list_for_each_entry_safe(cmd, tmp, &q->queue, qlist) { enabled = cmd_enabled(q, cmd); if (enabled) { int tid = cmd->c_target->tid; uint64_t itn_id = cmd->cmd_itn_id; struct it_nexus *nexus; nexus = it_nexus_lookup(tid, itn_id); if (!nexus) eprintf("BUG: %" PRIu64 "\n", itn_id); list_del(&cmd->qlist); dprintf("perform %" PRIx64 " %x\n", cmd->tag, cmd->attribute); result = scsi_cmd_perform(nexus->host_no, cmd); cmd_post_perform(q, cmd); set_cmd_processed(cmd); if (!cmd_async(cmd)) target_cmd_io_done(cmd, result); } else break; } } /* * Used by struct scsi_lu->cmd_done() for normal internal completion * (non passthrough) */ static void __cmd_done(struct target *target, struct scsi_cmd *cmd) { struct tgt_cmd_queue *q; cmd_hlist_remove(cmd); dprintf("%p %p %u %u\n", scsi_get_out_buffer(cmd), scsi_get_in_buffer(cmd), scsi_get_out_length(cmd), scsi_get_in_length(cmd)); q = &cmd->dev->cmd_queue; q->active_cmd--; switch (cmd->attribute) { case MSG_ORDERED_TAG: case MSG_HEAD_TAG: clear_queue_blocked(q); break; } post_cmd_done(q); } /* * Used by struct scsi_lu->cmd_done() for bs_sg (passthrough) completion */ void __cmd_done_passthrough(struct target *target, struct scsi_cmd *cmd) { dprintf("%p %p %u %u\n", scsi_get_out_buffer(cmd), scsi_get_in_buffer(cmd), scsi_get_out_length(cmd), scsi_get_in_length(cmd)); } void target_cmd_done(struct scsi_cmd *cmd) { struct mgmt_req *mreq; mreq = cmd->mreq; if (mreq && !--mreq->busy) { mreq->result = mreq->function == ABORT_TASK ? -EEXIST : 0; tgt_drivers[cmd->c_target->lid]->mgmt_end_notify(mreq); free(mreq); } cmd->dev->cmd_done(cmd->c_target, cmd); } static int abort_cmd(struct target *target, struct mgmt_req *mreq, struct scsi_cmd *cmd) { int err = 0; eprintf("found %" PRIx64 " %lx\n", cmd->tag, cmd->state); if (cmd_processed(cmd)) { /* * We've already sent this command to kernel space. * We'll send the tsk mgmt response when we get the * completion of this command. */ cmd->mreq = mreq; err = -EBUSY; } else { cmd->dev->cmd_done(target, cmd); target_cmd_io_done(cmd, TASK_ABORTED); } return err; } static int abort_task_set(struct mgmt_req *mreq, struct target *target, uint64_t itn_id, uint64_t tag, uint8_t *lun, int all) { struct scsi_cmd *cmd, *tmp; struct it_nexus *itn; int err, count = 0; eprintf("found %" PRIx64 " %d\n", tag, all); list_for_each_entry(itn, &target->it_nexus_list, nexus_siblings) { list_for_each_entry_safe(cmd, tmp, &itn->cmd_list, c_hlist) { if ((all && itn->itn_id == itn_id) || (cmd->tag == tag && itn->itn_id == itn_id) || (lun && !memcmp(cmd->lun, lun, sizeof(cmd->lun)))) { err = abort_cmd(target, mreq, cmd); if (err) mreq->busy++; count++; } } } return count; } enum mgmt_req_result target_mgmt_request(int tid, uint64_t itn_id, uint64_t req_id, int function, uint8_t *lun_buf, uint64_t tag, int host_no) { struct target *target; struct mgmt_req *mreq; int err = 0, count, send = 1; struct it_nexus *itn; struct it_nexus_lu_info *itn_lu; uint64_t lun; uint16_t asc; target = target_lookup(tid); if (!target) { eprintf("invalid tid %d\n", tid); return MGMT_REQ_FAILED; } mreq = zalloc(sizeof(*mreq)); if (!mreq) { eprintf("failed to allocate mgmt_req\n"); return MGMT_REQ_FAILED; } mreq->mid = req_id; mreq->function = function; switch (function) { case ABORT_TASK: count = abort_task_set(mreq, target, itn_id, tag, NULL, 0); if (mreq->busy) send = 0; if (!count) err = -EEXIST; break; case ABORT_TASK_SET: count = abort_task_set(mreq, target, itn_id, 0, NULL, 1); if (mreq->busy) send = 0; break; case CLEAR_ACA: eprintf("We don't support ACA\n"); err = -EINVAL; break; case CLEAR_TASK_SET: /* TAS bit is set to zero. */ lun = scsi_get_devid(target->lid, lun_buf); count = abort_task_set(mreq, target, itn_id, 0, lun_buf, 0); if (mreq->busy) send = 0; list_for_each_entry(itn, &target->it_nexus_list, nexus_siblings) { list_for_each_entry(itn_lu, &itn->itn_itl_info_list, itn_itl_info_siblings) { if (itn_lu->lu->lun == lun) { asc = (itn->itn_id == itn_id) ? ASC_POWERON_RESET : ASC_CMDS_CLEARED_BY_ANOTHER_INI; asc = ua_sense_add(itn_lu, asc); break; } } } break; case LOGICAL_UNIT_RESET: lun = scsi_get_devid(target->lid, lun_buf); device_release(target->tid, itn_id, lun, 1); count = abort_task_set(mreq, target, itn_id, 0, lun_buf, 0); if (mreq->busy) send = 0; list_for_each_entry(itn, &target->it_nexus_list, nexus_siblings) { list_for_each_entry(itn_lu, &itn->itn_itl_info_list, itn_itl_info_siblings) { if (itn_lu->lu->lun == lun) { itn_lu->prevent = 0; ua_sense_add(itn_lu, ASC_POWERON_RESET); break; } } } break; default: err = -EINVAL; eprintf("Unknown task management %x\n", function); } if (send) { mreq->result = err; tgt_drivers[target->lid]->mgmt_end_notify(mreq); free(mreq); } if (err) return MGMT_REQ_FAILED; else if (send) return MGMT_REQ_DONE; return MGMT_REQ_QUEUED; } struct account_entry { int aid; char *user; char *password; struct list_head account_siblings; }; static LIST_HEAD(account_list); static struct account_entry *__account_lookup_id(int aid) { struct account_entry *ac; list_for_each_entry(ac, &account_list, account_siblings) if (ac->aid == aid) return ac; return NULL; } static struct account_entry *__account_lookup_user(char *user) { struct account_entry *ac; list_for_each_entry(ac, &account_list, account_siblings) if (!strcmp(ac->user, user)) return ac; return NULL; } int account_lookup(int tid, int type, char *user, int ulen, char *password, int plen) { int i; struct target *target; struct account_entry *ac; if (tid == GLOBAL_TID) target = &global_target; else target = target_lookup(tid); if (!target) return -ENOENT; if (type == ACCOUNT_TYPE_INCOMING) { for (i = 0; i < target->account.nr_inaccount; i++) { ac = __account_lookup_id(target->account.in_aids[i]); if (ac) { if (!strcmp(ac->user, user)) goto found; } } } else { ac = __account_lookup_id(target->account.out_aid); if (ac) { strncpy(user, ac->user, ulen); goto found; } } return -ENOENT; found: strncpy(password, ac->password, plen); return 0; } tgtadm_err account_add(char *user, char *password) { int aid; struct account_entry *ac; ac = __account_lookup_user(user); if (ac) return TGTADM_USER_EXIST; for (aid = 1; __account_lookup_id(aid) && aid < INT_MAX; aid++) ; if (aid == INT_MAX) return TGTADM_TOO_MANY_USER; ac = zalloc(sizeof(*ac)); if (!ac) return TGTADM_NOMEM; ac->aid = aid; ac->user = strdup(user); if (!ac->user) goto free_account; ac->password = strdup(password); if (!ac->password) goto free_username; list_add(&ac->account_siblings, &account_list); return 0; free_username: free(ac->user); free_account: free(ac); return TGTADM_NOMEM; } static tgtadm_err __inaccount_bind(struct target *target, int aid) { int i; /* first, check whether we already have this account. */ for (i = 0; i < target->account.max_inaccount; i++) if (target->account.in_aids[i] == aid) return TGTADM_USER_EXIST; if (target->account.nr_inaccount < target->account.max_inaccount) { for (i = 0; i < target->account.max_inaccount; i++) if (!target->account.in_aids[i]) break; if (i == target->account.max_inaccount) { eprintf("bug %d\n", target->account.max_inaccount); return TGTADM_UNKNOWN_ERR; } target->account.in_aids[i] = aid; } else { int new_max = target->account.max_inaccount << 1; int *buf; buf = zalloc(new_max * sizeof(int)); if (!buf) return TGTADM_NOMEM; memcpy(buf, target->account.in_aids, target->account.max_inaccount * sizeof(int)); free(target->account.in_aids); target->account.in_aids = buf; target->account.in_aids[target->account.max_inaccount] = aid; target->account.max_inaccount = new_max; } target->account.nr_inaccount++; return TGTADM_SUCCESS; } tgtadm_err account_ctl(int tid, int type, char *user, int bind) { tgtadm_err adm_err = 0; struct target *target; struct account_entry *ac; int i; if (tid == GLOBAL_TID) target = &global_target; else target = target_lookup(tid); if (!target) return TGTADM_NO_TARGET; ac = __account_lookup_user(user); if (!ac) return TGTADM_NO_USER; if (bind) { if (type == ACCOUNT_TYPE_INCOMING) adm_err = __inaccount_bind(target, ac->aid); else { if (target->account.out_aid) adm_err = TGTADM_OUTACCOUNT_EXIST; else target->account.out_aid = ac->aid; } } else if (type == ACCOUNT_TYPE_INCOMING) { for (i = 0; i < target->account.max_inaccount; i++) if (target->account.in_aids[i] == ac->aid) { target->account.in_aids[i] = 0; target->account.nr_inaccount--; break; } if (i == target->account.max_inaccount) adm_err = TGTADM_NO_USER; } else if (target->account.out_aid == ac->aid) target->account.out_aid = 0; else adm_err = TGTADM_NO_USER; return adm_err; } tgtadm_err account_del(char *user) { struct account_entry *ac; struct target *target; ac = __account_lookup_user(user); if (!ac) return TGTADM_NO_USER; list_for_each_entry(target, &target_list, target_siblings) { account_ctl(target->tid, ACCOUNT_TYPE_INCOMING, ac->user, 0); account_ctl(target->tid, ACCOUNT_TYPE_OUTGOING, ac->user, 0); } account_ctl(GLOBAL_TID, ACCOUNT_TYPE_INCOMING, ac->user, 0); account_ctl(GLOBAL_TID, ACCOUNT_TYPE_OUTGOING, ac->user, 0); list_del(&ac->account_siblings); free(ac->user); free(ac->password); free(ac); return TGTADM_SUCCESS; } int account_available(int tid, int dir) { struct target *target; if (tid == GLOBAL_TID) target = &global_target; else target = target_lookup(tid); if (!target) return 0; if (dir == ACCOUNT_TYPE_INCOMING) return target->account.nr_inaccount; else return target->account.out_aid; } tgtadm_err acl_add(int tid, char *address) { char *str; struct target *target; struct acl_entry *acl, *tmp; target = target_lookup(tid); if (!target) return TGTADM_NO_TARGET; list_for_each_entry_safe(acl, tmp, &target->acl_list, aclent_list) if (!strcmp(address, acl->address)) return TGTADM_ACL_EXIST; acl = zalloc(sizeof(*acl)); if (!acl) return TGTADM_NOMEM; str = strdup(address); if (!str) { free(acl); return TGTADM_NOMEM; } acl->address = str; list_add_tail(&acl->aclent_list, &target->acl_list); return TGTADM_SUCCESS; } tgtadm_err acl_del(int tid, char *address) { struct target *target; struct acl_entry *acl, *tmp; tgtadm_err adm_err = TGTADM_ACL_NOEXIST; target = target_lookup(tid); if (!target) return TGTADM_NO_TARGET; list_for_each_entry_safe(acl, tmp, &target->acl_list, aclent_list) { if (!strcmp(address, acl->address)) { list_del(&acl->aclent_list); free(acl->address); free(acl); adm_err = TGTADM_SUCCESS; break; } } return adm_err; } char *acl_get(int tid, int idx) { int i = 0; struct target *target; struct acl_entry *acl; target = target_lookup(tid); if (!target) return NULL; list_for_each_entry(acl, &target->acl_list, aclent_list) { if (idx == i++) return acl->address; } return NULL; } tgtadm_err iqn_acl_add(int tid, char *name) { char *str; struct target *target; struct iqn_acl_entry *iqn_acl, *tmp; target = target_lookup(tid); if (!target) return TGTADM_NO_TARGET; list_for_each_entry_safe(iqn_acl, tmp, &target->iqn_acl_list, iqn_aclent_list) { if (!strcmp(name, iqn_acl->name)) return TGTADM_ACL_EXIST; } iqn_acl = zalloc(sizeof(*iqn_acl)); if (!iqn_acl) return TGTADM_NOMEM; str = strdup(name); if (!str) { free(iqn_acl); return TGTADM_NOMEM; } iqn_acl->name = str; list_add_tail(&iqn_acl->iqn_aclent_list, &target->iqn_acl_list); return TGTADM_SUCCESS; } tgtadm_err iqn_acl_del(int tid, char *name) { struct target *target; struct iqn_acl_entry *iqn_acl, *tmp; tgtadm_err adm_err = TGTADM_ACL_NOEXIST; target = target_lookup(tid); if (!target) return TGTADM_NO_TARGET; list_for_each_entry_safe(iqn_acl, tmp, &target->iqn_acl_list, iqn_aclent_list) { if (!strcmp(name, iqn_acl->name)) { list_del(&iqn_acl->iqn_aclent_list); free(iqn_acl->name); free(iqn_acl); adm_err = TGTADM_SUCCESS; break; } } return adm_err; } char *iqn_acl_get(int tid, int idx) { int i = 0; struct target *target; struct iqn_acl_entry *iqn_acl; target = target_lookup(tid); if (!target) return NULL; list_for_each_entry(iqn_acl, &target->iqn_acl_list, iqn_aclent_list) { if (idx == i++) return iqn_acl->name; } return NULL; } /* * if we have lots of host, use something like radix tree for * efficiency. */ static LIST_HEAD(bound_host_list); struct bound_host { int host_no; struct target *target; struct list_head bhost_siblings; }; tgtadm_err tgt_bind_host_to_target(int tid, int host_no) { struct target *target; struct bound_host *bhost; target = target_lookup(tid); if (!target) { eprintf("can't find a target %d\n", tid); return TGTADM_NO_TARGET; } list_for_each_entry(bhost, &bound_host_list, bhost_siblings) { if (bhost->host_no == host_no) { eprintf("already bound %d\n", host_no); return TGTADM_BINDING_EXIST; } } bhost = malloc(sizeof(*bhost)); if (!bhost) return TGTADM_NOMEM; bhost->host_no = host_no; bhost->target = target; list_add(&bhost->bhost_siblings, &bound_host_list); dprintf("bound the scsi host %d to the target %d\n", host_no, tid); return TGTADM_SUCCESS; } tgtadm_err tgt_unbind_host_to_target(int tid, int host_no) { struct bound_host *bhost; list_for_each_entry(bhost, &bound_host_list, bhost_siblings) { if (bhost->host_no == host_no) { if (!list_empty(&bhost->target->it_nexus_list)) { eprintf("the target has IT_nexus\n"); return TGTADM_TARGET_ACTIVE; } list_del(&bhost->bhost_siblings); free(bhost); return TGTADM_SUCCESS; } } return TGTADM_NO_BINDING; } enum scsi_target_state tgt_get_target_state(int tid) { struct target *target; target = target_lookup(tid); if (!target) return -ENOENT; return target->target_state; } static struct { enum scsi_target_state value; char *name; } target_state[] = { {SCSI_TARGET_OFFLINE, "offline"}, {SCSI_TARGET_READY, "ready"}, }; static char *target_state_name(enum scsi_target_state state) { int i; char *name = NULL; for (i = 0; i < ARRAY_SIZE(target_state); i++) { if (target_state[i].value == state) { name = target_state[i].name; break; } } return name; } tgtadm_err tgt_set_target_state(int tid, char *str) { tgtadm_err adm_err = TGTADM_INVALID_REQUEST; struct target *target; int i; target = target_lookup(tid); if (!target) return TGTADM_NO_TARGET; for (i = 0; i < ARRAY_SIZE(target_state); i++) { if (!strcmp(target_state[i].name, str)) { target->target_state = target_state[i].value; adm_err = TGTADM_SUCCESS; break; } } return adm_err; } static char *print_disksize(uint64_t size) { static char buf[64]; uint64_t mb; mb = ((size / 512) - (((size / 512) / 625) - 974)) / 1950; sprintf(buf, "%" PRIu64 " MB", mb); return buf; } static struct { int value; char *name; } disk_type_names[] = { {TYPE_DISK, "disk"}, {TYPE_TAPE, "tape"}, {TYPE_PRINTER, "printer"}, {TYPE_PROCESSOR, "processor"}, {TYPE_WORM, "worm"}, {TYPE_MMC, "cd/dvd"}, {TYPE_SCANNER, "scanner"}, {TYPE_MOD, "optical"}, {TYPE_MEDIUM_CHANGER, "changer"}, {TYPE_COMM, "communication"}, {TYPE_RAID, "controller"}, {TYPE_ENCLOSURE, "enclosure"}, {TYPE_RBC, "rbc"}, {TYPE_OSD, "osd"}, {TYPE_NO_LUN, "No LUN"}, {TYPE_PT, "passthrough"} }; static char *print_type(int type) { int i; char *name = NULL; for (i = 0; i < ARRAY_SIZE(disk_type_names); i++) { if (disk_type_names[i].value == type) { name = disk_type_names[i].name; break; } } return name; } tgtadm_err tgt_target_show_all(struct concat_buf *b) { char strflags[128]; struct target *target; struct scsi_lu *lu; struct acl_entry *acl; struct iqn_acl_entry *iqn_acl; struct it_nexus *nexus; list_for_each_entry(target, &target_list, target_siblings) { concat_printf(b, "Target %d: %s\n" _TAB1 "System information:\n" _TAB2 "Driver: %s\n" _TAB2 "State: %s\n", target->tid, target->name, tgt_drivers[target->lid]->name, target_state_name(target->target_state)); if (!strcmp(tgt_drivers[target->lid]->name, "iscsi")) iscsi_print_nop_settings(b, target->tid); concat_printf(b, _TAB1 "I_T nexus information:\n"); list_for_each_entry(nexus, &target->it_nexus_list, nexus_siblings) { concat_printf(b, _TAB2 "I_T nexus: %" PRIu64 "\n", nexus->itn_id); if (nexus->info) concat_printf(b, "%s", nexus->info); } concat_printf(b, _TAB1 "LUN information:\n"); list_for_each_entry(lu, &target->device_list, device_siblings) concat_printf(b, _TAB2 "LUN: %" PRIu64 "\n" _TAB3 "Type: %s\n" _TAB3 "SCSI ID: %s\n" _TAB3 "SCSI SN: %s\n" _TAB3 "Size: %s, Block size: %d\n" _TAB3 "Online: %s\n" _TAB3 "Removable media: %s\n" _TAB3 "Prevent removal: %s\n" _TAB3 "Readonly: %s\n" _TAB3 "SWP: %s\n" _TAB3 "Thin-provisioning: %s\n" _TAB3 "Backing store type: %s\n" _TAB3 "Backing store path: %s\n" _TAB3 "Backing store flags: %s\n", lu->lun, print_type(lu->attrs.device_type), lu->attrs.scsi_id, lu->attrs.scsi_sn, print_disksize(lu->size), 1U << lu->blk_shift, lu->attrs.online ? "Yes" : "No", lu->attrs.removable ? "Yes" : "No", lu_prevent_removal(lu) ? "Yes" : "No", lu->attrs.readonly ? "Yes" : "No", lu->attrs.swp ? "Yes" : "No", lu->attrs.thinprovisioning ? "Yes" : "No", lu->bst ? (lu->bst->bs_name ? : "Unknown") : "None", lu->path ? : "None", open_flags_to_str(strflags, lu->bsoflags)); if (!strcmp(tgt_drivers[target->lid]->name, "iscsi") || !strcmp(tgt_drivers[target->lid]->name, "iser")) { int i, aid; concat_printf(b, _TAB1 "Account information:\n"); for (i = 0; i < target->account.nr_inaccount; i++) { aid = target->account.in_aids[i]; concat_printf(b, _TAB2 "%s\n", __account_lookup_id(aid)->user); } if (target->account.out_aid) { aid = target->account.out_aid; concat_printf(b, _TAB2 "%s (outgoing)\n", __account_lookup_id(aid)->user); } } concat_printf(b, _TAB1 "ACL information:\n"); list_for_each_entry(acl, &target->acl_list, aclent_list) concat_printf(b, _TAB2 "%s\n", acl->address); list_for_each_entry(iqn_acl, &target->iqn_acl_list, iqn_aclent_list) { concat_printf(b, _TAB2 "%s\n", iqn_acl->name); } } return TGTADM_SUCCESS; } char *tgt_targetname(int tid) { struct target *target; target = target_lookup(tid); if (!target) return NULL; return target->name; } #define DEFAULT_NR_ACCOUNT 16 tgtadm_err tgt_target_create(int lld, int tid, char *args) { struct target *target, *pos; char *p, *q, *targetname = NULL; struct backingstore_template *bst; p = args; while ((q = strsep(&p, ","))) { char *str; str = strchr(q, '='); if (str) { *str++ = '\0'; if (!strcmp("targetname", q)) targetname = str; else eprintf("Unknow option %s\n", q); } }; for (p = targetname; *p != '\0'; p++) { if (isalnum(*p) || *p == ':' || *p == '.' || *p == '-') continue; eprintf("Target name %s has invalid characters\n", targetname); return TGTADM_INVALID_REQUEST; } if (!targetname) return TGTADM_INVALID_REQUEST; target = target_lookup(tid); if (target) { eprintf("Target id %d already exists\n", tid); return TGTADM_TARGET_EXIST; } if (target_name_lookup(targetname)) { eprintf("Target name %s already exists\n", targetname); return TGTADM_TARGET_EXIST; } bst = get_backingstore_template(tgt_drivers[lld]->default_bst); if (!bst) return TGTADM_INVALID_REQUEST; target = zalloc(sizeof(*target)); if (!target) return TGTADM_NOMEM; target->name = strdup(targetname); if (!target->name) { free(target); return TGTADM_NOMEM; } target->account.in_aids = zalloc(DEFAULT_NR_ACCOUNT * sizeof(int)); if (!target->account.in_aids) { free(target->name); free(target); return TGTADM_NOMEM; } target->account.max_inaccount = DEFAULT_NR_ACCOUNT; target->tid = tid; INIT_LIST_HEAD(&target->device_list); target->bst = bst; target->target_state = SCSI_TARGET_READY; target->lid = lld; list_for_each_entry(pos, &target_list, target_siblings) if (target->tid < pos->tid) break; list_add_tail(&target->target_siblings, &pos->target_siblings); INIT_LIST_HEAD(&target->acl_list); INIT_LIST_HEAD(&target->iqn_acl_list); INIT_LIST_HEAD(&target->it_nexus_list); tgt_device_create(tid, TYPE_RAID, 0, NULL, 0); if (tgt_drivers[lld]->target_create) tgt_drivers[lld]->target_create(target); list_add_tail(&target->lld_siblings, &tgt_drivers[lld]->target_list); dprintf("Succeed to create a new target %d\n", tid); return TGTADM_SUCCESS; } tgtadm_err tgt_target_destroy(int lld_no, int tid, int force) { struct target *target; struct acl_entry *acl, *tmp; struct iqn_acl_entry *iqn_acl, *tmp1; struct scsi_lu *lu; tgtadm_err adm_err; target = target_lookup(tid); if (!target) return TGTADM_NO_TARGET; if (!force && !list_empty(&target->it_nexus_list)) { eprintf("target %d still has it nexus\n", tid); return TGTADM_TARGET_ACTIVE; } while (!list_empty(&target->device_list)) { /* we remove lun0 last */ lu = list_entry(target->device_list.prev, struct scsi_lu, device_siblings); adm_err = tgt_device_destroy(tid, lu->lun, 1); if (adm_err != TGTADM_SUCCESS) return adm_err; } if (tgt_drivers[lld_no]->target_destroy) tgt_drivers[lld_no]->target_destroy(tid, force); list_del(&target->target_siblings); list_for_each_entry_safe(acl, tmp, &target->acl_list, aclent_list) { list_del(&acl->aclent_list); free(acl->address); free(acl); } list_for_each_entry_safe(iqn_acl, tmp1, &target->iqn_acl_list, iqn_aclent_list) { list_del(&iqn_acl->iqn_aclent_list); free(iqn_acl->name); free(iqn_acl); } list_del(&target->lld_siblings); free(target->account.in_aids); free(target->name); free(target); return TGTADM_SUCCESS; } tgtadm_err tgt_portal_create(int lld, char *args) { char *portals = NULL; portals = strstr(args, "portal="); if (!portals) { eprintf("invalid option when creating portals: %s\n", args); return TGTADM_INVALID_REQUEST; } if (tgt_drivers[lld]->portal_create) { if (tgt_drivers[lld]->portal_create(portals)) { eprintf("failed to create portal %s\n", portals); return TGTADM_INVALID_REQUEST; } } else { eprintf("can not create portals for for this lld type\n"); return TGTADM_INVALID_REQUEST; } dprintf("succeed to create new portals %s\n", portals); return TGTADM_SUCCESS; } tgtadm_err tgt_portal_destroy(int lld, char *args) { char *portals = NULL; portals = strstr(args, "portal="); if (!portals) { eprintf("invalid option when destroying portals: %s\n", args); return TGTADM_INVALID_REQUEST; } if (tgt_drivers[lld]->portal_destroy) { if (tgt_drivers[lld]->portal_destroy(portals)) { eprintf("failed to destroy portal %s\n", portals); return TGTADM_INVALID_REQUEST; } } else { eprintf("can not destroy portals for for this lld type\n"); return TGTADM_INVALID_REQUEST; } dprintf("succeed to destroy portals %s\n", portals); return TGTADM_SUCCESS; } tgtadm_err account_show(struct concat_buf *b) { struct account_entry *ac; if (!list_empty(&account_list)) concat_printf(b, "Account list:\n"); list_for_each_entry(ac, &account_list, account_siblings) concat_printf(b, _TAB1 "%s\n", ac->user); return TGTADM_SUCCESS; } static struct { enum tgt_system_state value; char *name; } system_state[] = { {TGT_SYSTEM_OFFLINE, "offline"}, {TGT_SYSTEM_READY, "ready"}, }; static char *system_state_name(enum tgt_system_state state) { int i; char *name = NULL; for (i = 0; i < ARRAY_SIZE(system_state); i++) { if (system_state[i].value == state) { name = system_state[i].name; break; } } return name; } static enum tgt_system_state sys_state = TGT_SYSTEM_READY; tgtadm_err system_set_state(char *str) { tgtadm_err adm_err = TGTADM_INVALID_REQUEST; int i; for (i = 0; i < ARRAY_SIZE(target_state); i++) { if (!strcmp(system_state[i].name, str)) { sys_state = system_state[i].value; adm_err = TGTADM_SUCCESS; break; } } return adm_err; } tgtadm_err system_show(int mode, struct concat_buf *b) { struct backingstore_template *bst; struct device_type_template *devt; int i; char strflags[128]; /* FIXME: too hacky */ if (mode != MODE_SYSTEM) return TGTADM_SUCCESS; concat_printf(b, "System:\n"); concat_printf(b, _TAB1 "State: %s\n", system_state_name(sys_state)); concat_printf(b, _TAB1 "debug: %s\n", is_debug ? "on" : "off"); concat_printf(b, "LLDs:\n"); for (i = 0; tgt_drivers[i]; i++) { concat_printf(b, _TAB1 "%s: %s\n", tgt_drivers[i]->name, driver_state_name(tgt_drivers[i])); } concat_printf(b, "Backing stores:\n"); list_for_each_entry(bst, &bst_list, backingstore_siblings) { if (!bst->bs_oflags_supported) concat_printf(b, _TAB1 "%s\n", bst->bs_name); else concat_printf(b, _TAB1 "%s (bsoflags %s)\n", bst->bs_name, open_flags_to_str(strflags, bst->bs_oflags_supported)); } concat_printf(b, "Device types:\n"); list_for_each_entry(devt, &device_type_list, device_type_siblings) concat_printf(b, _TAB1 "%s\n", print_type(devt->type)); if (global_target.account.nr_inaccount) { int i, aid; concat_printf(b, _TAB1 "%s\n", "Account information:\n"); for (i = 0; i < global_target.account.nr_inaccount; i++) { aid = global_target.account.in_aids[i]; concat_printf(b, _TAB1 "%s\n", __account_lookup_id(aid)->user); } if (global_target.account.out_aid) { aid = global_target.account.out_aid; concat_printf(b, _TAB1 "%s (outgoing)\n", __account_lookup_id(aid)->user); } } return TGTADM_SUCCESS; } tgtadm_err lld_show(struct concat_buf *b) { struct target *target; int i; concat_printf(b, "LLDs:\n"); for (i = 0; tgt_drivers[i]; i++) { concat_printf(b, _TAB1 "%s: %s\n", tgt_drivers[i]->name, driver_state_name(tgt_drivers[i])); list_for_each_entry(target, &tgt_drivers[i]->target_list, lld_siblings) { concat_printf(b, _TAB2 "Target %d: %s\n", target->tid, target->name); } } return TGTADM_SUCCESS; } void update_lbppbe(struct scsi_lu *lu, int blksize) { lu->attrs.lbppbe = 0; while (blksize > (1U << lu->blk_shift)) { lu->attrs.lbppbe++; blksize >>= 1; } } int is_system_available(void) { return (sys_state == TGT_SYSTEM_READY); } int is_system_inactive(void) { return list_empty(&target_list); } static void __attribute__((constructor)) target_constructor(void) { static int global_target_aids[DEFAULT_NR_ACCOUNT]; memset(global_target_aids, 0, sizeof(global_target_aids)); global_target.account.in_aids = global_target_aids; global_target.account.max_inaccount = DEFAULT_NR_ACCOUNT; global_target.tid = GLOBAL_TID; INIT_LIST_HEAD(&global_target.acl_list); INIT_LIST_HEAD(&global_target.iqn_acl_list); } tgt-1.0.85/usr/target.h000066400000000000000000000031711435417276200146700ustar00rootroot00000000000000#ifndef __TARGET_H__ #define __TARGET_H__ #include struct acl_entry { char *address; struct list_head aclent_list; }; struct iqn_acl_entry { char *name; struct list_head iqn_aclent_list; }; struct tgt_account { int out_aid; int nr_inaccount; int max_inaccount; int *in_aids; }; struct target { char *name; int tid; int lid; enum scsi_target_state target_state; struct list_head target_siblings; struct list_head device_list; struct list_head it_nexus_list; struct backingstore_template *bst; struct list_head acl_list; struct list_head iqn_acl_list; struct tgt_account account; struct list_head lld_siblings; }; struct it_nexus { uint64_t itn_id; long ctime; struct list_head cmd_list; struct target *nexus_target; /* the list of i_t_nexus belonging to a target */ struct list_head nexus_siblings; /* dirty hack for IBMVIO */ int host_no; struct list_head itn_itl_info_list; /* only used for show operation */ char *info; }; enum { TGT_QUEUE_BLOCKED, TGT_QUEUE_DELETED, }; #define QUEUE_FNS(bit, name) \ static inline void set_queue_##name(struct tgt_cmd_queue *q) \ { \ (q)->state |= (1UL << TGT_QUEUE_##bit); \ } \ static inline void clear_queue_##name(struct tgt_cmd_queue *q) \ { \ (q)->state &= ~(1UL << TGT_QUEUE_##bit); \ } \ static inline int queue_##name(const struct tgt_cmd_queue *q) \ { \ return ((q)->state & (1UL << TGT_QUEUE_##bit)); \ } static inline int queue_active(const struct tgt_cmd_queue *q) \ { \ return ((q)->active_cmd); \ } QUEUE_FNS(BLOCKED, blocked) QUEUE_FNS(DELETED, deleted) #endif tgt-1.0.85/usr/tgtadm.c000066400000000000000000000603111435417276200146540ustar00rootroot00000000000000/* * SCSI target daemon management interface * * Copyright (C) 2005-2007 FUJITA Tomonori * Copyright (C) 2005-2007 Mike Christie * * 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, version 2 of the * License. * * 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 #include #include #include #include #include "scsi.h" #include "util.h" #include "list.h" #include "tgtadm.h" #define NO_LOGGING #include "log.h" static const char program_name[] = "tgtadm"; static int debug; static const char *tgtadm_strerror(int err) { static const struct { enum tgtadm_errno err; char *desc; } errors[] = { { TGTADM_SUCCESS, "success" }, { TGTADM_UNKNOWN_ERR, "unknown error" }, { TGTADM_NOMEM, "out of memory" }, { TGTADM_NO_DRIVER, "can't find the driver" }, { TGTADM_NO_TARGET, "can't find the target" }, { TGTADM_NO_LUN, "can't find the logical unit" }, { TGTADM_NO_SESSION, "can't find the session" }, { TGTADM_NO_CONNECTION, "can't find the connection" }, { TGTADM_NO_BINDING, "can't find the binding" }, { TGTADM_TARGET_EXIST, "this target already exists" }, { TGTADM_BINDING_EXIST, "this binding already exists" }, { TGTADM_LUN_EXIST, "this logical unit number already exists" }, { TGTADM_ACL_EXIST, "this access control rule already exists" }, { TGTADM_ACL_NOEXIST, "this access control rule does not exist" }, { TGTADM_USER_EXIST, "this account already exists" }, { TGTADM_NO_USER, "can't find the account" }, { TGTADM_TOO_MANY_USER, "too many accounts" }, { TGTADM_INVALID_REQUEST, "invalid request" }, { TGTADM_OUTACCOUNT_EXIST, "this target already has an outgoing account" }, { TGTADM_TARGET_ACTIVE, "this target is still active" }, { TGTADM_LUN_ACTIVE, "this logical unit is still active" }, { TGTADM_DRIVER_ACTIVE, "this driver is busy" }, { TGTADM_UNSUPPORTED_OPERATION, "this operation isn't supported" }, { TGTADM_UNKNOWN_PARAM, "unknown parameter" }, { TGTADM_PREVENT_REMOVAL, "this device has Prevent Removal set" } }; int i; for (i = 0; i < ARRAY_SIZE(errors); ++i) if (errors[i].err == err) return errors[i].desc; return "(unknown tgtadm_errno)"; } struct option const long_options[] = { {"debug", no_argument, NULL, 'd'}, {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'V'}, {"lld", required_argument, NULL, 'L'}, {"op", required_argument, NULL, 'o'}, {"mode", required_argument, NULL, 'm'}, {"tid", required_argument, NULL, 't'}, {"sid", required_argument, NULL, 's'}, {"cid", required_argument, NULL, 'c'}, {"lun", required_argument, NULL, 'l'}, {"name", required_argument, NULL, 'n'}, {"value", required_argument, NULL, 'v'}, {"backing-store", required_argument, NULL, 'b'}, {"bstype", required_argument, NULL, 'E'}, {"bsopts", required_argument, NULL, 'S'}, {"bsoflags", required_argument, NULL, 'f'}, {"blocksize", required_argument, NULL, 'y'}, {"targetname", required_argument, NULL, 'T'}, {"initiator-address", required_argument, NULL, 'I'}, {"initiator-name", required_argument, NULL, 'Q'}, {"user", required_argument, NULL, 'u'}, {"password", required_argument, NULL, 'p'}, {"host", required_argument, NULL, 'H'}, {"force", no_argument, NULL, 'F'}, {"params", required_argument, NULL, 'P'}, {"bus", required_argument, NULL, 'B'}, {"device-type", required_argument, NULL, 'Y'}, {"outgoing", no_argument, NULL, 'O'}, {"control-port", required_argument, NULL, 'C'}, {NULL, 0, NULL, 0}, }; static char *short_options = "dhVL:o:m:t:s:c:l:n:v:b:E:f:y:T:I:Q:u:p:H:F:P:B:Y:O:C:S:"; static void usage(int status) { if (status != 0) { fprintf(stderr, "Try `%s --help' for more information.\n", program_name); exit(EINVAL); } printf("Linux SCSI Target administration utility, version %s\n\n" "Usage: %s [OPTION]\n" "--lld --mode target --op new --tid --targetname \n" "\tadd a new target with and . must not be zero.\n" "--lld --mode target --op delete [--force] --tid \n" "\tdelete the specific target with .\n" "\tWith force option, the specific target is deleted\n" "\teven if there is an activity.\n" "--lld --mode target --op show\n" "\tshow all the targets.\n" "--lld --mode target --op show --tid \n" "\tshow the specific target's parameters.\n" "--lld --mode target --op update --tid --name --value \n" "\tchange the target parameters of the target with .\n" "--lld --mode target --op bind --tid --initiator-address
\n" "--lld --mode target --op bind --tid --initiator-name \n" "\tenable the target to accept the specific initiators.\n" "--lld --mode target --op unbind --tid --initiator-address
\n" "--lld --mode target --op unbind --tid --initiator-name \n" "\tdisable the specific permitted initiators.\n" "--lld --mode logicalunit --op new --tid --lun \n" " --backing-store --bstype --bsopts --bsoflags \n" "\tadd a new logical unit with to the specific\n" "\ttarget with . The logical unit is offered\n" "\tto the initiators. must be block device files\n" "\t(including LVM and RAID devices) or regular files.\n" "\tbstype option is optional.\n" "\tbsopts are specific to the bstype.\n" "\tbsoflags supported options are sync and direct\n" "\t(sync:direct for both).\n" "--lld --mode logicalunit --op delete --tid --lun \n" "\tdelete the specific logical unit with that\n" "\tthe target with has.\n" "--lld --mode account --op new --user --password \n" "\tadd a new account with and .\n" "--lld --mode account --op delete --user \n" "\tdelete the specific account having .\n" "--lld --mode account --op bind --tid --user [--outgoing]\n" "\tadd the specific account having to\n" "\tthe specific target with .\n" "\t could be or .\n" "\tIf you use --outgoing option, the account will\n" "\tbe added as an outgoing account.\n" "--lld --mode account --op unbind --tid --user [--outgoing]\n" "\tdelete the specific account having from specific\n" "\ttarget. The --outgoing option must be added if you\n" "\tdelete an outgoing account.\n" "--lld --mode lld --op start\n" "\tStart the specified lld without restarting the tgtd process.\n" "--control-port use control port \n" "--help\n" "\tdisplay this help and exit\n\n" "Report bugs to .\n", TGT_VERSION, program_name); exit(0); } static void version(void) { printf("%s\n", TGT_VERSION); exit(0); } /* default port to use for the mgmt channel */ static short int control_port; static int ipc_mgmt_connect(int *fd) { int err; struct sockaddr_un addr; char mgmt_path[256], *path; *fd = socket(AF_LOCAL, SOCK_STREAM, 0); if (*fd < 0) { eprintf("can't create a socket, %m\n"); return errno; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_LOCAL; if ((path = getenv("TGT_IPC_SOCKET")) == NULL) path = TGT_IPC_NAMESPACE; snprintf(mgmt_path, sizeof(mgmt_path), "%s.%d", path, control_port); if (strlen(mgmt_path) > (sizeof(addr.sun_path) - 1)) { eprintf("management path too long: %s\n", mgmt_path); return EINVAL; } strcpy(addr.sun_path, mgmt_path); err = connect(*fd, (struct sockaddr *) &addr, sizeof(addr)); if (err < 0) return errno; return 0; } static int ipc_mgmt_rsp(int fd, struct tgtadm_req *req) { struct tgtadm_rsp rsp; int err, len, done; char *buf; retry: err = recv(fd, &rsp, sizeof(rsp), MSG_WAITALL); if (err < 0) { if (errno == EAGAIN) goto retry; else if (errno == EINTR) eprintf("interrupted by a signal\n"); else eprintf("can't get the response, %m\n"); return errno; } else if (err == 0) { eprintf("tgtd closed the socket\n"); return 0; } else if (err != sizeof(rsp)) { eprintf("a partial response\n"); return 0; } if (rsp.err != TGTADM_SUCCESS) { eprintf("%s\n", tgtadm_strerror(rsp.err)); return EINVAL; } if (req->mode == MODE_SYSTEM && req->op == OP_DELETE) { while (1) { int __fd, ret; struct timeval tv; ret = ipc_mgmt_connect(&__fd); if (ret == ECONNREFUSED) break; close(__fd); tv.tv_sec = 0; tv.tv_usec = 100 * 1000; select(0, NULL, NULL, NULL, &tv); } } len = rsp.len - sizeof(rsp); if (!len) return 0; buf = malloc(len); if (!buf) { fprintf(stderr, "failed to allocate %d bytes", len); return -ENOMEM; } done = 0; while (len > done) { int ret; ret = read(fd, buf + done, len - done); if (ret < 0) { if (errno == EAGAIN) continue; fprintf(stderr, "failed to read from tgtd, %d", errno); break; } done += ret; } if (done == len) fputs(buf, stdout); free(buf); return 0; } static int ipc_mgmt_req(struct tgtadm_req *req, struct concat_buf *b) { int err, fd = 0, done = 0; req->len = sizeof(*req) + b->size; err = ipc_mgmt_connect(&fd); if (err < 0) { eprintf("can't connect to tgt daemon, %m\n"); goto out; } err = write(fd, req, sizeof(*req)); if (err < 0 || err != sizeof(*req)) { eprintf("failed to send request hdr to tgt daemon, %m\n"); err = errno; goto out; } while (done < b->size) { err = concat_write(b, fd, done); if (err > 0) done += err; else if (errno != EAGAIN) { eprintf("failed to send request buf to " "tgt daemon, %m\n"); err = errno; goto out; } } dprintf("sent to tgtd %d\n", req->len); err = ipc_mgmt_rsp(fd, req); out: if (fd > 0) close(fd); concat_buf_release(b); return err; } static int filter(const struct dirent *dir) { return strcmp(dir->d_name, ".") && strcmp(dir->d_name, ".."); } static int bus_to_host(char *bus) { int i, nr, host = -1; char path[PATH_MAX], *p; char key[] = "host"; struct dirent **namelist; p = strchr(bus, ','); if (!p) return -EINVAL; *(p++) = '\0'; snprintf(path, sizeof(path), "/sys/bus/%s/devices/%s", bus, p); nr = scandir(path, &namelist, filter, alphasort); if (!nr) return -ENOENT; for (i = 0; i < nr; i++) { if (strncmp(namelist[i]->d_name, key, strlen(key))) continue; p = namelist[i]->d_name + strlen(key); host = strtoull(p, NULL, 10); } for (i = 0; i < nr; i++) free(namelist[i]); free(namelist); if (host == -1) { eprintf("can't find bus: %s\n", bus); exit(EINVAL); } return host; } static int str_to_device_type(char *str) { if (!strcmp(str, "disk")) return TYPE_DISK; else if (!strcmp(str, "tape")) return TYPE_TAPE; else if (!strcmp(str, "cd")) return TYPE_MMC; else if (!strcmp(str, "changer")) return TYPE_MEDIUM_CHANGER; else if (!strcmp(str, "osd")) return TYPE_OSD; else if (!strcmp(str, "ssc")) return TYPE_TAPE; else if (!strcmp(str, "pt")) return TYPE_PT; else { eprintf("unknown target type: %s\n", str); exit(EINVAL); } } static int str_to_mode(char *str) { if (!strcmp("system", str) || !strcmp("sys", str)) return MODE_SYSTEM; else if (!strcmp("target", str) || !strcmp("tgt", str)) return MODE_TARGET; else if (!strcmp("logicalunit", str) || !strcmp("lu", str)) return MODE_DEVICE; else if (!strcmp("portal", str) || !strcmp("pt", str)) return MODE_PORTAL; else if (!strcmp("session", str) || !strcmp("sess", str)) return MODE_SESSION; else if (!strcmp("connection", str) || !strcmp("conn", str)) return MODE_CONNECTION; else if (!strcmp("account", str)) return MODE_ACCOUNT; else if (!strcmp("lld", str)) return MODE_LLD; else { eprintf("unknown mode: %s\n", str); exit(1); } } static int str_to_op(char *str) { if (!strcmp("new", str)) return OP_NEW; else if (!strcmp("delete", str)) return OP_DELETE; else if (!strcmp("bind", str)) return OP_BIND; else if (!strcmp("unbind", str)) return OP_UNBIND; else if (!strcmp("show", str)) return OP_SHOW; else if (!strcmp("update", str)) return OP_UPDATE; else if (!strcmp("stat", str)) return OP_STATS; else if (!strcmp("start", str)) return OP_START; else if (!strcmp("stop", str)) return OP_STOP; else { eprintf("unknown operation: %s\n", str); exit(1); } } static void bad_optarg(int ret, int ch, char *optarg) { if (ret == ERANGE) fprintf(stderr, "-%c argument value '%s' out of range\n", ch, optarg); else fprintf(stderr, "-%c argument value '%s' invalid\n", ch, optarg); usage(ret); } static int verify_mode_params(int argc, char **argv, char *allowed) { int ch, longindex; int ret = 0; optind = 0; while ((ch = getopt_long(argc, argv, short_options, long_options, &longindex)) >= 0) { if (!strchr(allowed, ch) && !strchr("d", ch)) { ret = ch; break; } } return ret; } int main(int argc, char **argv) { int ch, longindex, rc; int op, tid, mode, dev_type, ac_dir; uint32_t cid, hostno; uint64_t sid, lun, force; char *name, *value, *path, *targetname, *address, *iqnname, *targetOps; char *portalOps, *bstype, *bsopts; char *bsoflags; char *blocksize; char *user, *password; struct tgtadm_req adm_req = {0}, *req = &adm_req; struct concat_buf b; char *op_name; op = tid = mode = -1; cid = hostno = sid = 0; lun = UINT64_MAX; rc = 0; dev_type = TYPE_DISK; ac_dir = ACCOUNT_TYPE_INCOMING; name = value = path = targetname = address = iqnname = NULL; targetOps = portalOps = bstype = bsopts = NULL; bsoflags = blocksize = user = password = op_name = NULL; force = 0; optind = 1; while ((ch = getopt_long(argc, argv, short_options, long_options, &longindex)) >= 0) { errno = 0; switch (ch) { case 'L': strncpy(req->lld, optarg, sizeof(req->lld)); break; case 'o': op = str_to_op(optarg); op_name = optarg; break; case 'm': mode = str_to_mode(optarg); break; case 't': rc = str_to_int_ge(optarg, tid, 0); if (rc) bad_optarg(rc, ch, optarg); break; case 's': rc = str_to_int(optarg, sid); if (rc) bad_optarg(rc, ch, optarg); break; case 'c': rc = str_to_int(optarg, cid); if (rc) bad_optarg(rc, ch, optarg); break; case 'l': rc = str_to_int(optarg, lun); if (rc) bad_optarg(rc, ch, optarg); break; case 'P': if (mode == MODE_PORTAL) portalOps = optarg; else targetOps = optarg; break; case 'n': name = optarg; break; case 'v': value = optarg; break; case 'b': path = optarg; break; case 'T': targetname = optarg; break; case 'I': address = optarg; break; case 'Q': iqnname = optarg; break; case 'u': user = optarg; break; case 'p': password = optarg; break; case 'B': hostno = bus_to_host(optarg); break; case 'H': rc = str_to_int_ge(optarg, hostno, 0); if (rc) bad_optarg(rc, ch, optarg); break; case 'F': force = 1; break; case 'f': bsoflags = optarg; break; case 'y': blocksize = optarg; break; case 'E': bstype = optarg; break; case 'S': bsopts = optarg; break; case 'Y': dev_type = str_to_device_type(optarg); break; case 'O': ac_dir = ACCOUNT_TYPE_OUTGOING; break; case 'C': rc = str_to_int_ge(optarg, control_port, 0); if (rc) bad_optarg(rc, ch, optarg); break; case 'V': version(); break; case 'd': debug = 1; break; case 'h': usage(0); break; default: usage(1); } } if (optind < argc) { eprintf("unrecognized option '%s'\n", argv[optind]); usage(1); } if (op < 0) { eprintf("specify the operation type\n"); exit(EINVAL); } if (mode < 0) { eprintf("specify the mode\n"); exit(EINVAL); } if (mode == MODE_SYSTEM) { switch (op) { case OP_UPDATE: rc = verify_mode_params(argc, argv, "LmonvC"); if (rc) { eprintf("system mode: option '-%c' is not " "allowed/supported\n", rc); exit(EINVAL); } if ((!name || !value)) { eprintf("update operation requires 'name'" " and 'value' options\n"); exit(EINVAL); } break; case OP_SHOW: case OP_DELETE: case OP_STATS: break; default: eprintf("operation %s not supported in system mode\n", op_name); exit(EINVAL); break; } } if (mode == MODE_TARGET) { if ((tid <= 0 && (op != OP_SHOW))) { if (tid == 0) eprintf("'tid' cannot be 0\n"); else eprintf("'tid' option is necessary\n"); exit(EINVAL); } switch (op) { case OP_NEW: rc = verify_mode_params(argc, argv, "LmotTC"); if (rc) { eprintf("target mode: option '-%c' is not " "allowed/supported\n", rc); exit(EINVAL); } if (!targetname) { eprintf("creating new target requires " "a name, use --targetname\n"); exit(EINVAL); } break; case OP_DELETE: rc = verify_mode_params(argc, argv, "LmotCF"); if (rc) { eprintf("target mode: option '-%c' is not " "allowed/supported\n", rc); exit(EINVAL); } break; case OP_SHOW: case OP_STATS: rc = verify_mode_params(argc, argv, "LmotC"); if (rc) { eprintf("target mode: option '-%c' is not " "allowed/supported\n", rc); exit(EINVAL); } break; case OP_BIND: case OP_UNBIND: rc = verify_mode_params(argc, argv, "LmotIQBHC"); if (rc) { eprintf("target mode: option '-%c' is not " "allowed/supported\n", rc); exit(EINVAL); } if (!address && !iqnname && !hostno) { eprintf("%s operation requires" " initiator-address, initiator-name" "or bus\n", op_name); exit(EINVAL); } break; case OP_UPDATE: rc = verify_mode_params(argc, argv, "LmotnvC"); if (rc) { eprintf("target mode: option '-%c' is not " \ "allowed/supported\n", rc); exit(EINVAL); } if ((!name || !value)) { eprintf("update operation requires 'name'" \ " and 'value' options\n"); exit(EINVAL); } break; default: eprintf("operation %s not supported in target mode\n", op_name); exit(EINVAL); break; } } if (mode == MODE_ACCOUNT) { switch (op) { case OP_NEW: rc = verify_mode_params(argc, argv, "LmoupfC"); if (rc) { eprintf("account mode: option '-%c' is " "not allowed/supported\n", rc); exit(EINVAL); } if (!user || !password) { eprintf("'user' and 'password' options " "are required\n"); exit(EINVAL); } break; case OP_SHOW: rc = verify_mode_params(argc, argv, "LmoC"); if (rc) { eprintf("account mode: option '-%c' is not " "allowed/supported\n", rc); exit(EINVAL); } break; case OP_DELETE: rc = verify_mode_params(argc, argv, "LmouC"); if (rc) { eprintf("account mode: option '-%c' is not " "allowed/supported\n", rc); exit(EINVAL); } break; case OP_BIND: rc = verify_mode_params(argc, argv, "LmotuOC"); if (rc) { eprintf("account mode: option '-%c' is not " "allowed/supported\n", rc); exit(EINVAL); } if (!user) { eprintf("'user' option is necessary\n"); exit(EINVAL); } if (tid == -1) tid = GLOBAL_TID; break; case OP_UNBIND: rc = verify_mode_params(argc, argv, "LmotuOC"); if (rc) { eprintf("account mode: option '-%c' is not " "allowed/supported\n", rc); exit(EINVAL); } if (!user) { eprintf("'user' option is necessary\n"); exit(EINVAL); } if (tid == -1) tid = GLOBAL_TID; break; default: eprintf("operation %s not supported in account mode\n", op_name); exit(EINVAL); break; } } if (mode == MODE_DEVICE) { if (tid <= 0) { if (tid == 0) eprintf("'tid' must not be 0\n"); else eprintf("'tid' option is necessary\n"); exit(EINVAL); } if (lun == UINT64_MAX) { eprintf("'lun' option is necessary\n"); exit(EINVAL); } switch (op) { case OP_NEW: rc = verify_mode_params(argc, argv, "LmofytlbEYCS"); if (rc) { eprintf("logicalunit mode: option '-%c' is not " "allowed/supported\n", rc); exit(EINVAL); } if (!path && dev_type != TYPE_MMC && dev_type != TYPE_TAPE && dev_type != TYPE_DISK) { eprintf("'backing-store' option " "is necessary\n"); exit(EINVAL); } break; case OP_DELETE: case OP_STATS: rc = verify_mode_params(argc, argv, "LmotlC"); if (rc) { eprintf("logicalunit mode: option '-%c' is not " "allowed/supported\n", rc); exit(EINVAL); } break; case OP_UPDATE: rc = verify_mode_params(argc, argv, "LmofytlPC"); if (rc) { eprintf("option '-%c' not supported in " "logicalunit mode\n", rc); exit(EINVAL); } break; default: eprintf("operation %s not supported in " "logicalunit mode\n", op_name); exit(EINVAL); break; } } if (mode == MODE_PORTAL) { switch (op) { case OP_NEW: rc = verify_mode_params(argc, argv, "LmoCP"); if (rc) { eprintf("portal mode: option '-%c' is not " "allowed/supported\n", rc); exit(EINVAL); } if (!portalOps) { eprintf("you must specify --param " "portal=\n"); exit(EINVAL); } break; case OP_DELETE: rc = verify_mode_params(argc, argv, "LmoCP"); if (rc) { eprintf("portal mode: option '-%c' is not " "allowed/supported\n", rc); exit(EINVAL); } if (!portalOps) { eprintf("you must specify --param " "portal=\n"); exit(EINVAL); } break; case OP_SHOW: rc = verify_mode_params(argc, argv, "LmoC"); if (rc) { eprintf("option '-%c' not supported in " "portal mode\n", rc); exit(EINVAL); } break; default: eprintf("operation %s not supported in portal mode\n", op_name); exit(EINVAL); break; } } if (mode == MODE_LLD) { switch (op) { case OP_START: case OP_STOP: case OP_SHOW: rc = verify_mode_params(argc, argv, "LmoC"); if (rc) { eprintf("lld mode: option '-%c' is not " "allowed/supported\n", rc); exit(EINVAL); } break; default: eprintf("option %d not supported in lld mode\n", op); exit(EINVAL); break; } } req->op = op; req->tid = tid; req->sid = sid; req->cid = cid; req->lun = lun; req->mode = mode; req->host_no = hostno; req->device_type = dev_type; req->ac_dir = ac_dir; req->force = force; concat_buf_init(&b); if (name) concat_printf(&b, "%s=%s", name, value); if (path) concat_printf(&b, "%spath=%s", concat_delim(&b, ","), path); if (req->device_type == TYPE_TAPE) concat_printf(&b, "%sbstype=%s", concat_delim(&b, ","), "ssc"); else if (bstype) concat_printf(&b, "%sbstype=%s", concat_delim(&b, ","), bstype); if (bsopts) concat_printf(&b, "%sbsopts=%s", concat_delim(&b, ","), bsopts); if (bsoflags) concat_printf(&b, "%sbsoflags=%s", concat_delim(&b, ","), bsoflags); if (blocksize) concat_printf(&b, "%sblocksize=%s", concat_delim(&b, ","), blocksize); if (targetname) concat_printf(&b, "%stargetname=%s", concat_delim(&b, ","), targetname); if (address) concat_printf(&b, "%sinitiator-address=%s", concat_delim(&b, ","), address); if (iqnname) concat_printf(&b, "%sinitiator-name=%s", concat_delim(&b, ","), iqnname); if (user) concat_printf(&b, "%suser=%s", concat_delim(&b, ","), user); if (password) concat_printf(&b, "%spassword=%s", concat_delim(&b, ","), password); /* Trailing ',' makes parsing params in modules easier.. */ if (targetOps) concat_printf(&b, "%stargetOps %s,", concat_delim(&b, ","), targetOps); if (portalOps) concat_printf(&b, "%sportalOps %s,", concat_delim(&b, ","), portalOps); if (b.err) { eprintf("BUFSIZE (%zu bytes) isn't long enough\n", b.size + 1); return EINVAL; } rc = concat_buf_finish(&b); if (rc) { eprintf("failed to create request, errno:%d\n", rc); exit(rc); } return ipc_mgmt_req(req, &b); } tgt-1.0.85/usr/tgtadm.h000066400000000000000000000015351435417276200146640ustar00rootroot00000000000000#ifndef TGTADM_H #define TGTADM_H #define TGT_IPC_DIR "/var/run/tgtd" #define TGT_IPC_NAMESPACE TGT_IPC_DIR"/socket" #define TGT_LLD_NAME_LEN 64 #define GLOBAL_TID (~0U) #include "tgtadm_error.h" enum tgtadm_op { OP_NEW, OP_DELETE, OP_SHOW, OP_BIND, OP_UNBIND, OP_UPDATE, OP_STATS, OP_START, OP_STOP, }; enum tgtadm_mode { MODE_SYSTEM, MODE_TARGET, MODE_DEVICE, MODE_PORTAL, MODE_LLD, MODE_SESSION, MODE_CONNECTION, MODE_ACCOUNT, }; enum tgtadm_account_dir { ACCOUNT_TYPE_INCOMING, ACCOUNT_TYPE_OUTGOING, }; struct tgtadm_req { enum tgtadm_mode mode; enum tgtadm_op op; char lld[TGT_LLD_NAME_LEN]; uint32_t len; int32_t tid; uint64_t sid; uint64_t lun; uint32_t cid; uint32_t host_no; uint32_t device_type; uint32_t ac_dir; uint32_t pack; uint32_t force; }; struct tgtadm_rsp { uint32_t err; uint32_t len; }; #endif tgt-1.0.85/usr/tgtadm_error.h000066400000000000000000000012131435417276200160660ustar00rootroot00000000000000#ifndef TGTADM_ERROR_H #define TGTADM_ERROR_H enum tgtadm_errno { TGTADM_SUCCESS, TGTADM_UNKNOWN_ERR, TGTADM_NOMEM, TGTADM_NO_DRIVER, TGTADM_NO_TARGET, TGTADM_NO_LUN, TGTADM_NO_SESSION, TGTADM_NO_CONNECTION, TGTADM_NO_BINDING, TGTADM_TARGET_EXIST, TGTADM_LUN_EXIST, TGTADM_BINDING_EXIST, TGTADM_ACL_EXIST, TGTADM_ACL_NOEXIST, TGTADM_USER_EXIST, TGTADM_NO_USER, TGTADM_TOO_MANY_USER, TGTADM_INVALID_REQUEST, TGTADM_OUTACCOUNT_EXIST, TGTADM_TARGET_ACTIVE, TGTADM_LUN_ACTIVE, TGTADM_DRIVER_ACTIVE, TGTADM_UNSUPPORTED_OPERATION, TGTADM_UNKNOWN_PARAM, TGTADM_PREVENT_REMOVAL, }; typedef enum tgtadm_errno tgtadm_err; #endif tgt-1.0.85/usr/tgtd.c000066400000000000000000000325121435417276200143400ustar00rootroot00000000000000/* * SCSI target daemon * * Copyright (C) 2005-2007 FUJITA Tomonori * Copyright (C) 2005-2007 Mike Christie * * 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, version 2 of the * License. * * 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 #include #include #include #include #include #include #include "list.h" #include "tgtd.h" #include "driver.h" #include "work.h" #include "util.h" unsigned long pagesize, pageshift; int system_active = 1; static int ep_fd; static char program_name[] = "tgtd"; static LIST_HEAD(tgt_events_list); static LIST_HEAD(tgt_sched_events_list); static struct option const long_options[] = { {"foreground", no_argument, 0, 'f'}, {"control-port", required_argument, 0, 'C'}, {"nr_iothreads", required_argument, 0, 't'}, {"pid-file", required_argument, 0, 'p'}, {"debug", required_argument, 0, 'd'}, {"nodaemonize", no_argument, 0, 'D'}, {"version", no_argument, 0, 'V'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0}, }; static char *short_options = "fDC:d:t:p:Vh"; static char *spare_args; static void usage(int status) { if (status) { fprintf(stderr, "Try `%s --help' for more information.\n", program_name); exit(status); } printf("Linux SCSI Target framework daemon, version %s\n\n" "Usage: %s [OPTION]\n" "-f, --foreground make the program run in the foreground\n" "-D, --nodaemonize make the program run in the foreground with logger\n" "-C, --control-port NNNN use port NNNN for the mgmt channel\n" "-t, --nr_iothreads NNNN specify the number of I/O threads\n" "-p, --pid-file filename specify the pid file\n" "-d, --debug debuglevel print debugging information\n" "-V, --version print version and exit\n" "-h, --help display this help and exit\n", TGT_VERSION, program_name); exit(0); } static void bad_optarg(int ret, int ch, char *optarg) { if (ret == ERANGE) fprintf(stderr, "-%c argument value '%s' out of range\n", ch, optarg); else fprintf(stderr, "-%c argument value '%s' invalid\n", ch, optarg); usage(ret); } static void version(void) { printf("%s\n", TGT_VERSION); exit(0); } /* Default TGT mgmt port */ short int control_port; static void signal_catch(int signo) { } static int oom_adjust(void) { int fd, err; const char *path, *score; struct stat st; /* Avoid oom-killer */ path = "/proc/self/oom_score_adj"; score = "-1000\n"; if (stat(path, &st)) { /* oom_score_adj cannot be used, try oom_adj */ path = "/proc/self/oom_adj"; score = "-17\n"; } fd = open(path, O_WRONLY); if (fd < 0) { fprintf(stderr, "can't adjust oom-killer's pardon %s, %m\n", path); return errno; } err = write(fd, score, strlen(score)); if (err < 0) { fprintf(stderr, "can't adjust oom-killer's pardon %s, %m\n", path); close(fd); return errno; } close(fd); return 0; } static int nr_file_adjust(void) { int ret, fd, max = 1024 * 1024; char path[] = "/proc/sys/fs/nr_open"; char buf[64]; struct rlimit rlim; /* Avoid oom-killer */ fd = open(path, O_RDONLY); if (fd < 0) { fprintf(stderr, "can't open %s, %m\n", path); goto set_rlimit; } ret = read(fd, buf, sizeof(buf)); if (ret < 0) { fprintf(stderr, "can't read %s, %m\n", path); close(fd); return errno; } close(fd); max = atoi(buf); set_rlimit: rlim.rlim_cur = rlim.rlim_max = max; ret = setrlimit(RLIMIT_NOFILE, &rlim); if (ret < 0) fprintf(stderr, "can't adjust nr_open %d %m\n", max); return 0; } void create_pid_file(const char *path) { int fd, ret; int mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; char buf[32] = {0}; fd = open(path, O_RDWR | O_CREAT, mode); if (fd < 0) { eprintf("can't create %s, %m\n", path); return; } snprintf(buf, sizeof(buf), "%d", getpid()); ret = write(fd, buf, strlen(buf)); close(fd); if (ret < 0) { eprintf("can't write and remove %s, %m\n", path); unlink(path); } } int tgt_event_add(int fd, int events, event_handler_t handler, void *data) { struct epoll_event ev; struct event_data *tev; int err; tev = zalloc(sizeof(*tev)); if (!tev) return -ENOMEM; tev->data = data; tev->handler = handler; tev->fd = fd; memset(&ev, 0, sizeof(ev)); ev.events = events; ev.data.ptr = tev; err = epoll_ctl(ep_fd, EPOLL_CTL_ADD, fd, &ev); if (err) { eprintf("Cannot add fd, %m\n"); free(tev); } else list_add(&tev->e_list, &tgt_events_list); return err; } static struct event_data *tgt_event_lookup(int fd) { struct event_data *tev; list_for_each_entry(tev, &tgt_events_list, e_list) { if (tev->fd == fd) return tev; } return NULL; } static int event_need_refresh; void tgt_event_del(int fd) { struct event_data *tev; int ret; tev = tgt_event_lookup(fd); if (!tev) { eprintf("Cannot find event %d\n", fd); return; } ret = epoll_ctl(ep_fd, EPOLL_CTL_DEL, fd, NULL); if (ret < 0) eprintf("fail to remove epoll event, %s\n", strerror(errno)); list_del(&tev->e_list); free(tev); event_need_refresh = 1; } int tgt_event_modify(int fd, int events) { struct epoll_event ev; struct event_data *tev; tev = tgt_event_lookup(fd); if (!tev) { eprintf("Cannot find event %d\n", fd); return -EINVAL; } memset(&ev, 0, sizeof(ev)); ev.events = events; ev.data.ptr = tev; return epoll_ctl(ep_fd, EPOLL_CTL_MOD, fd, &ev); } void tgt_init_sched_event(struct event_data *evt, sched_event_handler_t sched_handler, void *data) { evt->sched_handler = sched_handler; evt->scheduled = 0; evt->data = data; INIT_LIST_HEAD(&evt->e_list); } void tgt_add_sched_event(struct event_data *evt) { if (!evt->scheduled) { evt->scheduled = 1; list_add_tail(&evt->e_list, &tgt_sched_events_list); } } void tgt_remove_sched_event(struct event_data *evt) { if (evt->scheduled) { evt->scheduled = 0; list_del_init(&evt->e_list); } } /* strcpy, while eating multiple white spaces */ void str_spacecpy(char **dest, const char *src) { const char *s = src; char *d = *dest; while (*s) { if (isspace(*s)) { if (!*(s+1)) break; if (isspace(*(s+1))) { s++; continue; } } *d++ = *s++; } *d = '\0'; } int call_program(const char *cmd, void (*callback)(void *data, int result), void *data, char *output, int op_len, int flags) { pid_t pid; int fds[2], ret, i; char *pos, arg[256]; char *argv[sizeof(arg) / 2]; i = 0; pos = arg; str_spacecpy(&pos, cmd); if (strchr(cmd, ' ')) { while (*pos != '\0') argv[i++] = strsep(&pos, " "); } else argv[i++] = arg; argv[i] = NULL; ret = pipe(fds); if (ret < 0) { eprintf("pipe create failed for %s, %m\n", cmd); return ret; } dprintf("%s, pipe: %d %d\n", cmd, fds[0], fds[1]); pid = fork(); if (pid < 0) { eprintf("fork failed for: %s, %m\n", cmd); close(fds[0]); close(fds[1]); return pid; } if (!pid) { close(1); ret = dup(fds[1]); if (ret < 0) { eprintf("dup failed for: %s, %m\n", cmd); exit(-1); } close(fds[0]); execv(argv[0], argv); eprintf("execv failed for: %s, %m\n", cmd); exit(-1); } else { struct timeval tv; fd_set rfds; int ret_sel; close(fds[1]); /* 0.1 second is okay, as the initiator will retry anyway */ do { FD_ZERO(&rfds); FD_SET(fds[0], &rfds); tv.tv_sec = 0; tv.tv_usec = 100000; ret_sel = select(fds[0]+1, &rfds, NULL, NULL, &tv); } while (ret_sel < 0 && errno == EINTR); if (ret_sel <= 0) { /* error or timeout */ eprintf("timeout on redirect callback, terminating " "child pid %d\n", pid); kill(pid, SIGTERM); } do { ret = waitpid(pid, &i, 0); } while (ret < 0 && errno == EINTR); if (ret < 0) { eprintf("waitpid failed for: %s, %m\n", cmd); close(fds[0]); return ret; } if (ret_sel > 0) { ret = read(fds[0], output, op_len); if (ret < 0) { eprintf("failed to get output from: %s\n", cmd); close(fds[0]); return ret; } } if (callback) callback(data, WEXITSTATUS(i)); close(fds[0]); } return 0; } static int tgt_exec_scheduled(void) { struct list_head *last_sched; struct event_data *tev, *tevn; int work_remains = 0; if (!list_empty(&tgt_sched_events_list)) { /* execute only work scheduled till now */ last_sched = tgt_sched_events_list.prev; list_for_each_entry_safe(tev, tevn, &tgt_sched_events_list, e_list) { tgt_remove_sched_event(tev); tev->sched_handler(tev); if (&tev->e_list == last_sched) break; } if (!list_empty(&tgt_sched_events_list)) work_remains = 1; } return work_remains; } static void event_loop(void) { int nevent, i, sched_remains, timeout; struct epoll_event events[1024]; struct event_data *tev; retry: sched_remains = tgt_exec_scheduled(); timeout = sched_remains ? 0 : -1; nevent = epoll_wait(ep_fd, events, ARRAY_SIZE(events), timeout); if (nevent < 0) { if (errno != EINTR) { eprintf("%m\n"); exit(1); } } else if (nevent) { for (i = 0; i < nevent; i++) { tev = (struct event_data *) events[i].data.ptr; tev->handler(tev->fd, events[i].events, tev->data); if (event_need_refresh) { event_need_refresh = 0; goto retry; } } } if (system_active) goto retry; } int lld_init_one(int lld_index) { int err; INIT_LIST_HEAD(&tgt_drivers[lld_index]->target_list); if (tgt_drivers[lld_index]->init) { err = tgt_drivers[lld_index]->init(lld_index, spare_args); if (err) { tgt_drivers[lld_index]->drv_state = DRIVER_ERR; return err; } tgt_drivers[lld_index]->drv_state = DRIVER_INIT; } return 0; } static int lld_init(void) { int i, nr; for (i = nr = 0; tgt_drivers[i]; i++) { if (!lld_init_one(i)) nr++; } return nr; } static void lld_exit(void) { int i; for (i = 0; tgt_drivers[i]; i++) { if (tgt_drivers[i]->exit) tgt_drivers[i]->exit(); tgt_drivers[i]->drv_state = DRIVER_EXIT; } } struct tgt_param { int (*parse_func)(char *); char *name; }; static struct tgt_param params[64]; int setup_param(char *name, int (*parser)(char *)) { int i; for (i = 0; i < ARRAY_SIZE(params); i++) if (!params[i].name) break; if (i < ARRAY_SIZE(params)) { params[i].name = name; params[i].parse_func = parser; return 0; } else return -1; } static int parse_params(char *name, char *p) { int i; for (i = 0; i < ARRAY_SIZE(params) && params[i].name; i++) { if (!strcmp(name, params[i].name)) return params[i].parse_func(p); } fprintf(stderr, "'%s' is an unknown option\n", name); return -1; } int main(int argc, char **argv) { struct sigaction sa_old; struct sigaction sa_new; int err, ch, longindex, nr_lld = 0; int is_daemon = 1, is_debug = 0, use_logger = 1; int ret; char *pidfile = NULL; sa_new.sa_handler = signal_catch; sigemptyset(&sa_new.sa_mask); sa_new.sa_flags = 0; sigaction(SIGPIPE, &sa_new, &sa_old); sigaction(SIGTERM, &sa_new, &sa_old); pagesize = sysconf(_SC_PAGESIZE); for (pageshift = 0;; pageshift++) if (1UL << pageshift == pagesize) break; opterr = 0; while ((ch = getopt_long(argc, argv, short_options, long_options, &longindex)) >= 0) { switch (ch) { case 'f': is_daemon = 0; use_logger = 0; break; case 'D': is_daemon = 0; break; case 'C': ret = str_to_int_ge(optarg, control_port, 0); if (ret) bad_optarg(ret, ch, optarg); break; case 't': ret = str_to_int_gt(optarg, nr_iothreads, 0); if (ret) bad_optarg(ret, ch, optarg); break; case 'p': pidfile = strdup(optarg); if (pidfile == NULL) { fprintf(stderr, "can't duplicate pid file\n"); exit(1); } break; case 'd': ret = str_to_int_range(optarg, is_debug, 0, 1); if (ret) bad_optarg(ret, ch, optarg); break; case 'V': version(); break; case 'h': usage(0); break; default: if (strncmp(argv[optind - 1], "--", 2)) usage(1); ret = parse_params(argv[optind - 1] + 2, argv[optind]); if (ret) usage(1); break; } } ep_fd = epoll_create(4096); if (ep_fd < 0) { fprintf(stderr, "can't create epoll fd, %m\n"); exit(1); } spare_args = optind < argc ? argv[optind] : NULL; if (is_daemon && daemon(0, 0)) exit(1); err = ipc_init(); if (err) exit(1); err = log_init(program_name, LOG_SPACE_SIZE, use_logger, is_debug); if (err) exit(1); nr_lld = lld_init(); if (!nr_lld) { fprintf(stderr, "No available low level driver!\n"); exit(1); } err = oom_adjust(); if (err && (errno != EACCES) && getuid() == 0) exit(1); err = nr_file_adjust(); if (err) exit(1); err = work_timer_start(); if (err) exit(1); bs_init(); #ifdef USE_SYSTEMD sd_notify(0, "READY=1\nSTATUS=Starting event loop..."); #endif if (is_daemon && pidfile) create_pid_file(pidfile); event_loop(); lld_exit(); work_timer_stop(); ipc_exit(); log_close(); if (is_daemon && pidfile) unlink(pidfile); return 0; } tgt-1.0.85/usr/tgtd.h000066400000000000000000000302411435417276200143420ustar00rootroot00000000000000#ifndef __TARGET_DAEMON_H #define __TARGET_DAEMON_H #include "log.h" #include "scsi_cmnd.h" #include "tgtadm_error.h" #ifdef USE_SYSTEMD #include #endif struct concat_buf; #define NR_SCSI_OPCODES 256 #define SCSI_ID_LEN 36 #define SCSI_SN_LEN 36 #define VENDOR_ID_LEN 8 #define PRODUCT_ID_LEN 16 #define PRODUCT_REV_LEN 4 #define BLOCK_LIMITS_VPD_LEN 0x3C #define BDC_VPD_LEN 0x40 #define LBP_VPD_LEN 4 #define PCODE_SHIFT 7 #define PCODE_OFFSET(x) (x & ((1 << PCODE_SHIFT) - 1)) #define BLOCK_DESCRIPTOR_LEN 8 #define VERSION_DESCRIPTOR_LEN 8 #define VENDOR_ID "IET" #define _TAB1 " " #define _TAB2 _TAB1 _TAB1 #define _TAB3 _TAB1 _TAB1 _TAB1 #define _TAB4 _TAB2 _TAB2 #define dtype_priv(lu) (lu)->xxc_p enum tgt_system_state { TGT_SYSTEM_OFFLINE = 1, TGT_SYSTEM_READY, }; enum scsi_target_state { SCSI_TARGET_OFFLINE = 1, SCSI_TARGET_READY, }; struct tgt_cmd_queue { int active_cmd; unsigned long state; struct list_head queue; }; struct scsi_lu; struct vpd { uint16_t size; void (*vpd_update)(struct scsi_lu *lu, void *data); uint8_t data[0]; }; #define PREVENT_REMOVAL 0x01 #define PREVENT_REMOVAL_PERSISTENT 0x02 #define PREVENT_MASK 0x03 struct lu_phy_attr { char scsi_id[SCSI_ID_LEN + 1]; char scsi_sn[SCSI_SN_LEN + 1]; uint64_t numeric_id; char vendor_id[VENDOR_ID_LEN + 1]; char product_id[PRODUCT_ID_LEN + 1]; char product_rev[PRODUCT_REV_LEN + 1]; uint16_t version_desc[VERSION_DESCRIPTOR_LEN]; unsigned char device_type; /* Peripheral device type */ char qualifier; /* Peripheral Qualifier */ char removable; /* Removable media */ char readonly; /* Read-Only media */ char swp; /* Software Write Protect */ char thinprovisioning; /* Use thin-provisioning for this LUN */ char online; /* Logical Unit online */ char sense_format; /* Descrptor format sense data supported */ /* For the following see READ CAPACITY (16) */ unsigned char lbppbe; /* Logical blocks per physical block exponent */ char no_auto_lbppbe; /* Do not update it automatically when the backing file changes */ uint16_t la_lba; /* Lowest aligned LBA */ uint16_t rotation_rate; /* Block rotation rate */ /* VPD pages 0x80 -> 0xff masked with 0x80*/ struct vpd *lu_vpd[1 << PCODE_SHIFT]; }; struct ua_sense { struct list_head ua_sense_siblings; unsigned char ua_sense_buffer[SCSI_SENSE_BUFFERSIZE]; int ua_sense_len; }; struct lu_stat { uint64_t rd_subm_bytes; uint64_t rd_done_bytes; uint64_t wr_subm_bytes; uint64_t wr_done_bytes; uint32_t rd_subm_cmds; uint32_t rd_done_cmds; uint32_t wr_subm_cmds; uint32_t wr_done_cmds; uint32_t bidir_subm_cmds; uint32_t bidir_done_cmds; uint32_t err_num; }; struct it_nexus_lu_info { struct scsi_lu *lu; uint64_t itn_id; struct lu_stat stat; struct list_head itn_itl_info_siblings; struct list_head lu_itl_info_siblings; struct list_head pending_ua_sense_list; int prevent; /* prevent removal on this itl nexus ? */ }; struct service_action { uint32_t service_action; int (*cmd_perform)(int host_no, struct scsi_cmd *cmd); }; struct device_type_operations { int (*cmd_perform)(int host_no, struct scsi_cmd *cmd); struct service_action *service_actions; uint8_t pr_conflict_bits; }; #define PR_SPECIAL (1U << 5) #define PR_WE_FA (1U << 4) #define PR_EA_FA (1U << 3) #define PR_RR_FR (1U << 2) #define PR_WE_FN (1U << 1) #define PR_EA_FN (1U << 0) struct device_type_template { unsigned char type; tgtadm_err (*lu_init)(struct scsi_lu *lu); void (*lu_exit)(struct scsi_lu *lu); tgtadm_err (*lu_config)(struct scsi_lu *lu, char *args); tgtadm_err (*lu_online)(struct scsi_lu *lu); tgtadm_err (*lu_offline)(struct scsi_lu *lu); int (*cmd_passthrough)(int, struct scsi_cmd *); struct device_type_operations ops[NR_SCSI_OPCODES]; struct list_head device_type_siblings; }; struct backingstore_template { const char *bs_name; int bs_datasize; int (*bs_open)(struct scsi_lu *dev, char *path, int *fd, uint64_t *size); void (*bs_close)(struct scsi_lu *dev); tgtadm_err (*bs_init)(struct scsi_lu *dev, char *bsopts); void (*bs_exit)(struct scsi_lu *dev); int (*bs_cmd_submit)(struct scsi_cmd *cmd); int bs_oflags_supported; unsigned long bs_supported_ops[NR_SCSI_OPCODES / __WORDSIZE]; struct list_head backingstore_siblings; }; struct mode_pg { struct list_head mode_pg_siblings; uint8_t pcode; /* Page code */ uint8_t subpcode; /* Sub page code */ int16_t pcode_size; /* Size of page code data. */ uint8_t mode_data[0]; /* Rest of mode page info */ }; struct registration { uint64_t key; uint64_t nexus_id; long ctime; struct list_head registration_siblings; uint8_t pr_scope; uint8_t pr_type; }; struct scsi_lu { int fd; uint64_t addr; /* persistent mapped address */ uint64_t size; uint64_t lun; char *path; int bsoflags; unsigned int blk_shift; /* the list of devices belonging to a target */ struct list_head device_siblings; struct list_head lu_itl_info_list; struct tgt_cmd_queue cmd_queue; uint64_t reserve_id; /* we don't use a pointer because a lld could change this. */ struct device_type_template dev_type_template; struct backingstore_template *bst; struct target *tgt; uint8_t mode_block_descriptor[BLOCK_DESCRIPTOR_LEN]; struct list_head mode_pages; struct lu_phy_attr attrs; struct list_head registration_list; uint32_t prgeneration; struct registration *pr_holder; /* A pointer for each modules private use. * Currently used by ssc, smc and mmc modules. */ void *xxc_p; /* * Used internally for usr/target.c:target_cmd_perform() and with * passthrough CMD processing with * struct device_type_template->cmd_passthrough(). */ int (*cmd_perform)(int, struct scsi_cmd *); /* * Used internally for usr/target.c:__cmd_done() and with * passthrough CMD processing with __cmd_done_passthrough() */ void (*cmd_done)(struct target *, struct scsi_cmd *); }; struct mgmt_req { uint64_t mid; int busy; int function; int result; }; enum mgmt_req_result { MGMT_REQ_FAILED = -1, MGMT_REQ_DONE, MGMT_REQ_QUEUED, }; extern int system_active; extern int is_debug; extern int nr_iothreads; extern struct list_head bst_list; extern int ipc_init(void); extern void ipc_exit(void); extern tgtadm_err tgt_device_create(int tid, int dev_type, uint64_t lun, char *args, int backing); extern tgtadm_err tgt_device_destroy(int tid, uint64_t lun, int force); extern tgtadm_err tgt_device_update(int tid, uint64_t dev_id, char *name); extern int device_reserve(struct scsi_cmd *cmd); extern int device_release(int tid, uint64_t itn_id, uint64_t lun, int force); extern int device_reserved(struct scsi_cmd *cmd); extern tgtadm_err tgt_device_path_update(struct target *target, struct scsi_lu *lu, char *path); extern tgtadm_err tgt_target_create(int lld, int tid, char *args); extern tgtadm_err tgt_target_destroy(int lld, int tid, int force); extern char *tgt_targetname(int tid); extern tgtadm_err tgt_target_show_all(struct concat_buf *b); tgtadm_err system_set_state(char *str); tgtadm_err system_show(int mode, struct concat_buf *b); tgtadm_err lld_show(struct concat_buf *b); int is_system_available(void); int is_system_inactive(void); extern tgtadm_err tgt_portal_create(int lld, char *args); extern tgtadm_err tgt_portal_destroy(int lld, char *args); extern tgtadm_err tgt_bind_host_to_target(int tid, int host_no); extern tgtadm_err tgt_unbind_host_to_target(int tid, int host_no); struct event_data; typedef void (*sched_event_handler_t)(struct event_data *tev); extern void tgt_init_sched_event(struct event_data *evt, sched_event_handler_t sched_handler, void *data); typedef void (*event_handler_t)(int fd, int events, void *data); extern int tgt_event_add(int fd, int events, event_handler_t handler, void *data); extern void tgt_event_del(int fd); extern void tgt_add_sched_event(struct event_data *evt); extern void tgt_remove_sched_event(struct event_data *evt); extern int tgt_event_modify(int fd, int events); extern int target_cmd_queue(int tid, struct scsi_cmd *cmd); extern int target_cmd_perform(int tid, struct scsi_cmd *cmd); extern int target_cmd_perform_passthrough(int tid, struct scsi_cmd *cmd); extern void target_cmd_done(struct scsi_cmd *cmd); extern void __cmd_done_passthrough(struct target *target, struct scsi_cmd *cmd); extern enum mgmt_req_result target_mgmt_request(int tid, uint64_t itn_id, uint64_t req_id, int function, uint8_t *lun, uint64_t tag, int host_no); extern struct it_nexus *it_nexus_lookup(int tid, uint64_t itn_id); extern void target_cmd_io_done(struct scsi_cmd *cmd, int result); extern int ua_sense_del(struct scsi_cmd *cmd, int del); extern void ua_sense_clear(struct it_nexus_lu_info *itn_lu, uint16_t asc); extern void ua_sense_add_other_it_nexus(uint64_t itn_id, struct scsi_lu *lu, uint16_t asc); extern void ua_sense_add_it_nexus(uint64_t itn_id, struct scsi_lu *lu, uint16_t asc); extern int lu_prevent_removal(struct scsi_lu *lu); extern uint64_t scsi_get_devid(int lid, uint8_t *pdu); extern int scsi_cmd_perform(int host_no, struct scsi_cmd *cmd); extern void sense_data_build(struct scsi_cmd *cmd, uint8_t key, uint16_t asc); extern void sense_data_build_with_info(struct scsi_cmd *cmd, uint8_t key, uint16_t asc, uint64_t info); extern uint64_t scsi_rw_offset(uint8_t *scb); extern uint32_t scsi_rw_count(uint8_t *scb); extern int scsi_is_io_opcode(unsigned char op); extern enum data_direction scsi_data_dir_opcode(unsigned char op); extern int get_scsi_cdb_size(struct scsi_cmd *cmd); extern int get_scsi_command_size(unsigned char op); extern const unsigned char *get_scsi_cdb_usage_data(unsigned char op, unsigned char sa); extern enum scsi_target_state tgt_get_target_state(int tid); extern tgtadm_err tgt_set_target_state(int tid, char *str); extern tgtadm_err acl_add(int tid, char *address); extern tgtadm_err acl_del(int tid, char *address); extern char *acl_get(int tid, int idx); extern tgtadm_err iqn_acl_add(int tid, char *name); extern tgtadm_err iqn_acl_del(int tid, char *name); extern char *iqn_acl_get(int tid, int idx); extern void tgt_stat_header(struct concat_buf *b); extern void tgt_stat_line(int tid, uint64_t lun, uint64_t sid, struct lu_stat *stat, struct concat_buf *b); extern void tgt_stat_device(struct target *target, struct scsi_lu *lu, struct concat_buf *b); extern tgtadm_err tgt_stat_device_by_id(int tid, uint64_t dev_id, struct concat_buf *b); extern tgtadm_err tgt_stat_target(struct target *target, struct concat_buf *b); extern tgtadm_err tgt_stat_target_by_id(int tid, struct concat_buf *b); extern tgtadm_err tgt_stat_system(struct concat_buf *b); extern int account_lookup(int tid, int type, char *user, int ulen, char *password, int plen); extern tgtadm_err account_add(char *user, char *password); extern tgtadm_err account_del(char *user); extern tgtadm_err account_ctl(int tid, int type, char *user, int bind); extern tgtadm_err account_show(struct concat_buf *b); extern int account_available(int tid, int dir); extern int it_nexus_create(int tid, uint64_t itn_id, int host_no, char *info); extern int it_nexus_destroy(int tid, uint64_t itn_id); extern int device_type_register(struct device_type_template *); extern struct lu_phy_attr *lu_attr_lookup(int tid, uint64_t lun); extern tgtadm_err dtd_load_unload(int tid, uint64_t lun, int load, char *file); extern tgtadm_err dtd_check_removable(int tid, uint64_t lun); extern int register_backingstore_template(struct backingstore_template *bst); extern struct backingstore_template *get_backingstore_template(const char *name); extern void bs_create_opcode_map(struct backingstore_template *bst, unsigned char *opcodes, int num); extern int is_bs_support_opcode(struct backingstore_template *bst, int op); extern int lld_init_one(int lld_index); extern int setup_param(char *name, int (*parser)(char *)); extern int bs_init(void); struct event_data { union { event_handler_t handler; sched_event_handler_t sched_handler; }; union { int fd; int scheduled; }; void *data; struct list_head e_list; }; int call_program(const char *cmd, void (*callback)(void *data, int result), void *data, char *output, int op_len, int flags); void update_lbppbe(struct scsi_lu *lu, int blksize); struct service_action * find_service_action(struct service_action *service_action, uint32_t action); #endif tgt-1.0.85/usr/tgtimg.c000066400000000000000000000310771435417276200146760ustar00rootroot00000000000000/* * Create media files for TGTD devices * * Copyright (C) 2008 Mark Harvey markh794@gmail.com * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #define _XOPEN_SOURCE 600 #include #include #include #include #include #include #include #include #include #include #include #include #include "media.h" #include "bs_ssc.h" #include "ssc.h" #include "libssc.h" #include "scsi.h" #include "util.h" #define NO_LOGGING #include "log.h" enum { OP_NEW, OP_SHOW, }; static char program_name[] = "tgtimg"; static char *short_options = "ho:Y:b:s:t:f:"; struct option const long_options[] = { {"help", no_argument, NULL, 'h'}, {"op", required_argument, NULL, 'o'}, {"device-type", required_argument, NULL, 'Y'}, {"barcode", required_argument, NULL, 'b'}, {"size", required_argument, NULL, 's'}, {"type", required_argument, NULL, 't'}, {"file", required_argument, NULL, 'f'}, {"thin-provisioning", no_argument, NULL, 'T'}, {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("\ Linux SCSI Target Framework Image File Utility, version %s\n\ \n\ --op new --device-type tape --barcode=[code] --size=[size] --type=[type] --file=[path] [--thin-provisioning]\n\ create a new tape image file.\n\ [code] is a string of chars.\n\ [size] is media size(in megabytes).\n\ [type] is media type \n\ (data, clean or WORM) for tape devices\n\ (dvd+r) for cd devices\n\ (disk) for disk devices\n\ [path] is a newly created file\n\ --op show --device-type tape --file=[path]\n\ dump the tape image file contents.\n\ [path] is the tape image file\n\ --thin-provisioning create a sparse file for the media\n\ --help display this help and exit\n\ \n\ Report bugs to .\n", TGT_VERSION); } exit(status == 0 ? 0 : EINVAL); } static int str_to_device_type(char *str) { if (!strcmp(str, "tape")) return TYPE_TAPE; else if (!strcmp(str, "cd")) return TYPE_MMC; else if (!strcmp(str, "disk")) return TYPE_DISK; else { eprintf("unknown target type: %s\n", str); exit(EINVAL); } } static int str_to_op(char *str) { if (!strcmp("new", str)) return OP_NEW; else if (!strcmp("show", str)) return OP_SHOW; else { eprintf("unknown operation: %s\n", str); exit(1); } } static void print_current_header(struct blk_header_info *pos) { switch (pos->blk_type) { case BLK_UNCOMPRESS_DATA: printf(" Uncompressed data"); break; case BLK_FILEMARK: printf(" Filemark"); break; case BLK_BOT: printf("Beginning of Tape"); break; case BLK_EOD: printf(" End of Data"); break; case BLK_NOOP: printf(" No Operation"); break; default: printf(" Unknown type"); break; } if (pos->blk_type == BLK_BOT) printf("(%d): Capacity %d MB, Blk No.: %" PRId64 ", prev %" PRId64 ", curr %" PRId64 ", next %" PRId64 "\n", pos->blk_type, pos->blk_sz, pos->blk_num, (uint64_t)pos->prev, (uint64_t)pos->curr, (uint64_t)pos->next); else printf("(%d): Blk No. %" PRId64 ", prev %" PRId64 "" ", curr %" PRId64 ", next %" PRId64 ", sz %d\n", pos->blk_type, pos->blk_num, (uint64_t)pos->prev, (uint64_t)pos->curr, (uint64_t)pos->next, pos->ondisk_sz); } static int skip_to_next_header(int fd, struct blk_header_info *pos) { int ret; ret = ssc_read_blkhdr(fd, pos, pos->next); if (ret) printf("Could not read complete blk header - short read!!\n"); return ret; } static int ssc_show(char *path) { int ofp; loff_t nread; struct MAM_info mam; struct blk_header_info current_position; time_t t; int a; unsigned char *p; ofp = open(path, O_RDONLY|O_LARGEFILE); if (ofp < 0) { eprintf("can't open %s, %m\n", path); exit(1); } nread = ssc_read_blkhdr(ofp, ¤t_position, 0); if (nread) { perror("Could not read blk header"); exit(1); } nread = ssc_read_mam_info(ofp, &mam); if (nread) { perror("Could not read MAM"); exit(1); } if (mam.tape_fmt_version != TGT_TAPE_VERSION) { printf("Unknown media format version %x\n", mam.tape_fmt_version); exit(1); } printf("Media : %s\n", mam.barcode); switch (mam.medium_type) { case CART_UNSPECIFIED: printf(" type : Unspecified\n"); break; case CART_DATA: printf(" type : Data\n"); break; case CART_CLEAN: printf(" type : Cleaning\n"); break; case CART_DIAGNOSTICS: printf(" type : Diagnostics\n"); break; case CART_WORM: printf(" type : WORM\n"); break; case CART_MICROCODE: printf(" type : Microcode\n"); break; default: printf(" type : Unknown\n"); } printf("Media serial number : %s, ", mam.medium_serial_number); for (a = strlen((const char *)mam.medium_serial_number); a > 0; a--) if (mam.medium_serial_number[a] == '_') break; if (a) { a++; p = &mam.medium_serial_number[a]; t = atoll((const char *)p); printf("created %s", ctime(&t)); } printf("\n"); print_current_header(¤t_position); while (current_position.blk_type != BLK_EOD) { nread = skip_to_next_header(ofp, ¤t_position); if (nread) break; print_current_header(¤t_position); } return 0; } static int ssc_new(int op, char *path, char *barcode, char *capacity, char *media_type) { struct blk_header_info hdr, *h = &hdr; struct MAM_info mi; int fd, ret; uint8_t current_media[1024]; uint64_t size; sscanf(capacity, "%" SCNu64, &size); if (size == 0) size = 8000; memset(h, 0, sizeof(*h)); h->blk_type = BLK_BOT; h->blk_num = 0; h->blk_sz = size; h->prev = 0; h->curr = 0; h->next = sizeof(struct MAM) + SSC_BLK_HDR_SIZE; printf("blk_sz: %d, next %" PRId64 ", %" PRId64 "\n", h->blk_sz, h->next, h->next); printf("Sizeof(mam): %" PRId64 ", sizeof(h): %" PRId64 "\n", (uint64_t)sizeof(struct MAM), (uint64_t)SSC_BLK_HDR_SIZE); memset(&mi, 0, sizeof(mi)); mi.tape_fmt_version = TGT_TAPE_VERSION; mi.max_capacity = size * 1048576; mi.remaining_capacity = size * 1048576; mi.MAM_space_remaining = sizeof(mi.vendor_unique); mi.medium_length = 384; /* 384 tracks */ mi.medium_width = 127; /* 127 x tenths of mm (12.7 mm) */ memcpy(mi.medium_manufacturer, "Foo ", 8); memcpy(mi.application_vendor, "Bar ", 8); if (!strncasecmp("clean", media_type, 5)) { mi.medium_type = CART_CLEAN; mi.medium_type_information = 20; /* Max cleaning loads */ } else if (!strncasecmp("WORM", media_type, 4)) mi.medium_type = CART_WORM; else mi.medium_type = CART_DATA; sprintf((char *)mi.medium_serial_number, "%s_%d", barcode, (int)time(NULL)); sprintf((char *)mi.barcode, "%-31s", barcode); sprintf((char *)current_media, "%s", barcode); syslog(LOG_DAEMON|LOG_INFO, "TAPE %s being created", path); fd = creat(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP); if (fd < 0) { perror("Failed creating file"); exit(2); } ret = ssc_write_blkhdr(fd, h, 0); if (ret) { perror("Unable to write header"); exit(1); } ret = ssc_write_mam_info(fd, &mi); if (ret) { perror("Unable to write MAM"); exit(1); } memset(h, 0, sizeof(*h)); h->blk_type = BLK_EOD; h->blk_num = 1; h->prev = 0; h->next = lseek64(fd, 0, SEEK_CUR); h->curr = h->next; ret = ssc_write_blkhdr(fd, h, h->next); if (ret) { perror("Unable to write header"); exit(1); } close(fd); return 0; } static int ssc_ops(int op, char *path, char *barcode, char *capacity, char *media_type) { if (op == OP_NEW) { if (!media_type) { eprintf("Missing media type: WORM, CLEAN or DATA\n"); usage(1); } if (strncasecmp("data", media_type, 4) && strncasecmp("clean", media_type, 5) && strncasecmp("worm", media_type, 4)) { eprintf("Media type must be WORM, CLEAN or DATA" " for tape devices\n"); usage(1); } if (!barcode) { eprintf("Missing the barcode param\n"); usage(1); } if (!capacity) { eprintf("Missing the capacity param\n"); usage(1); } return ssc_new(op, path, barcode, capacity, media_type); } else if (op == OP_SHOW) return ssc_show(path); else { eprintf("unknown the operation type\n"); usage(1); } return 0; } static int mmc_new(int op, char *path, char *media_type) { int fd; if (!strncasecmp("dvd+r", media_type, 5)) { fd = creat(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP); if (fd < 0) { perror("Failed creating file"); exit(2); } close(fd); printf("Created blank DVD+R image file : %s\n", path); syslog(LOG_DAEMON|LOG_INFO, "DVD+R %s being created", path); } else { eprintf("unknown media type when creating cd\n"); usage(1); } return 0; } static int mmc_ops(int op, char *path, char *media_type) { if (op == OP_NEW) { if (!media_type) { eprintf("Missing media type: DVD+R\n"); usage(1); } if (strncasecmp("dvd+r", media_type, 5)) { eprintf("Media type must be DVD+R for cd devices\n"); usage(1); } return mmc_new(op, path, media_type); } else { eprintf("unknown the operation type\n"); usage(1); } return 0; } static int sbc_new(int op, char *path, char *capacity, char *media_type, int thin) { int fd; if (!strncasecmp("disk", media_type, 4)) { uint32_t size; char *buf; sscanf(capacity, "%d", &size); if (size == 0) { printf("Capacity must be > 0\n"); exit(3); } buf = malloc(1024*1024); if (buf == NULL) { printf("Failed to malloc buffer\n"); exit(4); } fd = creat(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP); if (fd < 0) { perror("Failed creating file"); exit(2); } if (thin) { if (ftruncate(fd, size*1024*1024LL) != 0) { perror("Failed to set file size"); exit(6); } if (unmap_file_region(fd, 0, size*1024*1024LL) != 0) { perror("Thin provisioning not available on" " this file"); exit(5); } } else { if (posix_fallocate(fd, 0, size*1024*1024LL) == -1) { perror("posix_fallocate failed."); exit(3); } } free(buf); close(fd); printf("Created blank DISK image file : %s\n", path); syslog(LOG_DAEMON|LOG_INFO, "DISK %s being created", path); } else { eprintf("unknown media type when creating disk\n"); usage(1); } return 0; } static int sbc_ops(int op, char *path, char *capacity, char *media_type, int thin) { if (op == OP_NEW) { if (!media_type) { eprintf("Missing media type: DISK\n"); usage(1); } if (strncasecmp("disk", media_type, 4)) { eprintf("Media type must be DISK for disk devices\n"); usage(1); } if (!capacity) { eprintf("Missing the capacity param\n"); usage(1); } return sbc_new(op, path, capacity, media_type, thin); } else { eprintf("unknown the operation type\n"); usage(1); } return 0; } int main(int argc, char **argv) { int ch, longindex; char *barcode = NULL; char *media_type = NULL; char *media_capacity = NULL; int dev_type = TYPE_TAPE; int op = -1; char *path = NULL; int thin = 0; while ((ch = getopt_long(argc, argv, short_options, long_options, &longindex)) >= 0) { switch (ch) { case 'o': op = str_to_op(optarg); break; case 'Y': dev_type = str_to_device_type(optarg); break; case 'b': barcode = optarg; break; case 's': media_capacity = optarg; break; case 't': media_type = optarg; break; case 'f': path = optarg; break; case 'h': usage(0); break; case 'T': thin = 1; break; default: eprintf("unrecognized option '%s'\n", optarg); usage(1); } } if (optind < argc) { eprintf("unrecognized option '%s'\n", argv[optind]); usage(1); } if (op < 0) { eprintf("specify the operation type\n"); usage(1); } if (!path) { eprintf("specify a newly created file\n"); usage(1); } switch (dev_type) { case TYPE_TAPE: ssc_ops(op, path, barcode, media_capacity, media_type); break; case TYPE_MMC: mmc_ops(op, path, media_type); break; case TYPE_DISK: sbc_ops(op, path, media_capacity, media_type, thin); break; default: eprintf("unsupported the device type operation\n"); usage(1); } return 0; } tgt-1.0.85/usr/util.c000066400000000000000000000105341435417276200143530ustar00rootroot00000000000000/* * common utility functions * * Copyright (C) 2007 FUJITA Tomonori * Copyright (C) 2007 Mike Christie * * 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, version 2 of the * License. * * 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 #include "log.h" #include "util.h" int chrdev_open(char *modname, char *devpath, uint8_t minor, int *fd) { FILE *fp; char name[256], buf[256]; int err, major; fp = fopen("/proc/devices", "r"); if (!fp) { eprintf("Cannot open /proc/devices, %m\n"); return -1; } major = 0; while (!feof(fp)) { if (!fgets(buf, sizeof (buf), fp)) break; if (sscanf(buf, "%d %s", &major, name) != 2) continue; if (!strcmp(name, modname)) break; major = 0; } fclose(fp); if (!major) { eprintf("cannot find %s in /proc/devices - " "make sure the module is loaded\n", modname); return -1; } unlink(devpath); err = mknod(devpath, (S_IFCHR | 0600), makedev(major, minor)); if (err) { eprintf("cannot create %s, %m\n", devpath); return -errno; } *fd = open(devpath, O_RDWR); if (*fd < 0) { eprintf("cannot open %s, %m\n", devpath); return -errno; } return 0; } int backed_file_open(char *path, int oflag, uint64_t *size, uint32_t *blksize) { int fd, err; struct stat64 st; fd = open(path, oflag); if (fd < 0) { eprintf("Could not open %s, %m\n", path); return fd; } err = fstat64(fd, &st); if (err < 0) { eprintf("Cannot get stat %d, %m\n", fd); goto close_fd; } if (S_ISREG(st.st_mode)) { *size = st.st_size; if (blksize) *blksize = st.st_blksize; } else if (S_ISBLK(st.st_mode)) { err = ioctl(fd, BLKGETSIZE64, size); if (err < 0) { eprintf("Cannot get size, %m\n"); goto close_fd; } } else { eprintf("Cannot use this mode %x\n", st.st_mode); err = -EINVAL; goto close_fd; } return fd; close_fd: close(fd); return err; } int set_non_blocking(int fd) { int err; err = fcntl(fd, F_GETFL); if (err < 0) { eprintf("unable to get fd flags, %m\n"); } else { err = fcntl(fd, F_SETFL, err | O_NONBLOCK); if (err == -1) eprintf("unable to set fd flags, %m\n"); else err = 0; } return err; } int str_to_open_flags(char *buf) { char *bsoflags_tok = NULL; int open_flags = 0; bsoflags_tok = strtok(buf, ":\0"); while (bsoflags_tok != NULL) { while (*bsoflags_tok == ' ') bsoflags_tok++; if (!strncmp(bsoflags_tok, "sync", 4)) open_flags |= O_SYNC; else if (!strncmp(bsoflags_tok, "direct", 6)) open_flags |= O_DIRECT; else { eprintf("bsoflag option %s not supported\n", bsoflags_tok); return -1; } bsoflags_tok = strtok(NULL, ":"); } return open_flags; } char *open_flags_to_str(char *dest, int flags) { *dest = '\0'; if (flags & O_SYNC) strcat(dest, "sync"); if (flags & O_DIRECT) { if (*dest) strcat(dest, ":"); strcat(dest, "direct"); } return dest; } int get_blk_shift(unsigned int size) { int shift = 0; if (!size) return -1; /* find the first non-zero bit */ while ((size & (1 << shift)) == 0) shift++; /* if more non-zero bits, then size is not a power of 2 */ if (size > (1 << shift)) return -1; return shift; } /* * memory copy for spc-style scsi commands. * Copy up to src_len bytes from src, * not exceeding *dst_remain_len bytes available at dst. * Reflect decreased space in *dst_remain_len. * Return actually copied length. */ int spc_memcpy(uint8_t *dst, uint32_t *dst_remain_len, uint8_t *src, uint32_t src_len) { int copy_len = min_t(uint32_t, *dst_remain_len, src_len); if (copy_len) { memcpy(dst, src, copy_len); *dst_remain_len -= copy_len; } return copy_len; } tgt-1.0.85/usr/util.h000066400000000000000000000141211435417276200143540ustar00rootroot00000000000000#ifndef __UTIL_H__ #define __UTIL_H__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "be_byteshift.h" #define roundup(x, y) ((((x) + ((y) - 1)) / (y)) * (y)) #define DIV_ROUND_UP(x, y) (((x) + (y) - 1) / (y)) #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) #define ALIGN(x,a) (((x)+(a)-1)&~((a)-1)) #if __BYTE_ORDER == __LITTLE_ENDIAN #define __cpu_to_be16(x) bswap_16(x) #define __cpu_to_be32(x) bswap_32(x) #define __cpu_to_be64(x) bswap_64(x) #define __be16_to_cpu(x) bswap_16(x) #define __be32_to_cpu(x) bswap_32(x) #define __be64_to_cpu(x) bswap_64(x) #define __cpu_to_le32(x) (x) #else #define __cpu_to_be16(x) (x) #define __cpu_to_be32(x) (x) #define __cpu_to_be64(x) (x) #define __be16_to_cpu(x) (x) #define __be32_to_cpu(x) (x) #define __be64_to_cpu(x) (x) #define __cpu_to_le32(x) bswap_32(x) #endif #define DEFDMODE (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) #define DEFFMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) #define min(x,y) ({ \ typeof(x) _x = (x); \ typeof(y) _y = (y); \ (void) (&_x == &_y); \ _x < _y ? _x : _y; }) #define max(x,y) ({ \ typeof(x) _x = (x); \ typeof(y) _y = (y); \ (void) (&_x == &_y); \ _x > _y ? _x : _y; }) #define min_t(type,x,y) \ ({ type __x = (x); type __y = (y); __x < __y ? __x: __y; }) #define max_t(type,x,y) \ ({ type __x = (x); type __y = (y); __x > __y ? __x: __y; }) extern int get_blk_shift(unsigned int size); extern int chrdev_open(char *modname, char *devpath, uint8_t minor, int *fd); extern int backed_file_open(char *path, int oflag, uint64_t *size, uint32_t *blksize); extern int set_non_blocking(int fd); extern int str_to_open_flags(char *buf); extern char *open_flags_to_str(char *dest, int flags); extern int spc_memcpy(uint8_t *dst, uint32_t *dst_remain_len, uint8_t *src, uint32_t src_len); #define zalloc(size) \ ({ \ void *ptr = malloc(size); \ if (ptr) \ memset(ptr, 0, size); \ else \ eprintf("%m\n"); \ ptr; \ }) static inline int before(uint32_t seq1, uint32_t seq2) { return (int32_t)(seq1 - seq2) < 0; } static inline int after(uint32_t seq1, uint32_t seq2) { return (int32_t)(seq2 - seq1) < 0; } /* is s2<=s1<=s3 ? */ static inline int between(uint32_t seq1, uint32_t seq2, uint32_t seq3) { return seq3 - seq2 >= seq1 - seq2; } extern unsigned long pagesize, pageshift; #if defined(__NR_signalfd) && defined(USE_SIGNALFD) /* * workaround for broken linux/signalfd.h including * usr/include/linux/fcntl.h */ #define _LINUX_FCNTL_H #include static inline int __signalfd(int fd, const sigset_t *mask, int flags) { int fd2, ret; fd2 = syscall(__NR_signalfd, fd, mask, _NSIG / 8); if (fd2 < 0) return fd2; ret = fcntl(fd2, F_GETFL); if (ret < 0) { close(fd2); return -1; } ret = fcntl(fd2, F_SETFL, ret | O_NONBLOCK); if (ret < 0) { close(fd2); return -1; } return fd2; } #else #define __signalfd(fd, mask, flags) (-1) struct signalfd_siginfo { }; #endif /* convert string to integer, check for validity of the string numeric format * and the natural boundaries of the integer value type (first get a 64-bit * value and check that it fits the range of the destination integer). */ #define str_to_int(str, val) \ ({ \ int ret = 0; \ char *ptr; \ unsigned long long ull_val; \ ull_val = strtoull(str, &ptr, 0); \ val = (typeof(val)) ull_val; \ if (ull_val == ULLONG_MAX || ptr == str) \ ret = EINVAL; \ else if (val != ull_val) \ ret = ERANGE; \ ret; \ }) /* convert to int and check: strictly greater than */ #define str_to_int_gt(str, val, minv) \ ({ \ int ret = str_to_int(str, val); \ if (!ret && (val <= minv)) \ ret = ERANGE; \ ret; \ }) /* convert and check: greater than or equal */ #define str_to_int_ge(str, val, minv) \ ({ \ int ret = str_to_int(str, val); \ if (!ret && (val < minv)) \ ret = ERANGE; \ ret; \ }) /* convert and check: strictly less than */ #define str_to_int_lt(str, val, maxv) \ ({ \ int ret = str_to_int(str, val); \ if (!ret && (val >= maxv)) \ ret = ERANGE; \ ret; \ }) /* convert and check: range, ends inclusive */ #define str_to_int_range(str, val, minv, maxv) \ ({ \ int ret = str_to_int(str, val); \ if (!ret && (val < minv || val > maxv)) \ ret = ERANGE; \ ret; \ }) struct concat_buf { FILE *streamf; int err; int used; char *buf; size_t size; }; void concat_buf_init(struct concat_buf *b); int concat_printf(struct concat_buf *b, const char *format, ...); const char *concat_delim(struct concat_buf *b, const char *delim); int concat_buf_finish(struct concat_buf *b); int concat_write(struct concat_buf *b, int fd, int offset); void concat_buf_release(struct concat_buf *b); /* If we have recent enough glibc to support PUNCH HOLE we try to unmap * the region. */ static inline int unmap_file_region(int fd, off_t offset, off_t length) { #ifdef FALLOC_FL_PUNCH_HOLE if (fallocate(fd, FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE, offset, length) == 0) return 0; #endif return -1; } #define BITS_PER_LONG __WORDSIZE #define BITS_PER_BYTE 8 #define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long)) static inline void set_bit(int nr, unsigned long *addr) { addr[nr / BITS_PER_LONG] |= 1UL << (nr % BITS_PER_LONG); } static inline void clear_bit(int nr, unsigned long *addr) { addr[nr / BITS_PER_LONG] &= ~(1UL << (nr % BITS_PER_LONG)); } static __always_inline int test_bit(unsigned int nr, const unsigned long *addr) { return ((1UL << (nr % BITS_PER_LONG)) & (((unsigned long *)addr)[nr / BITS_PER_LONG])) != 0; } #define scsi_sprintf(str, size, format, ...) \ do { \ char buf[size + 1]; \ memset(buf, 0, sizeof(buf)); \ snprintf(buf, sizeof(buf), format, ## __VA_ARGS__); \ memcpy(str, buf, size); \ } while (0) #endif tgt-1.0.85/usr/work.c000066400000000000000000000125611435417276200143620ustar00rootroot00000000000000/* * work scheduler, loosely timer-based * * Copyright (C) 2006-2007 FUJITA Tomonori * Copyright (C) 2006-2007 Mike Christie * Copyright (C) 2011 Alexander Nezhinsky * * 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, version 2 of the * License. * * 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 "list.h" #include "util.h" #include "log.h" #include "work.h" #include "tgtd.h" #define WORK_TIMER_INT_MSEC 500 #define WORK_TIMER_INT_USEC (WORK_TIMER_INT_MSEC * 1000) static struct itimerval work_timer = { {0, WORK_TIMER_INT_USEC}, {0, WORK_TIMER_INT_USEC} }; static unsigned int elapsed_msecs; static int timer_pending; static int timer_fd[2] = {-1, -1}; static LIST_HEAD(active_work_list); static LIST_HEAD(inactive_work_list); static void execute_work(void); static inline unsigned int timeval_to_msecs(struct timeval t) { return t.tv_sec * 1000 + t.tv_usec / 1000; } static void work_timer_schedule_evt(void) { unsigned int n = 0; int err; if (timer_pending || timer_fd[1] < 0) return; timer_pending = 1; err = write(timer_fd[1], &n, sizeof(n)); if (err < 0) eprintf("Failed to write to pipe, %m\n"); } static void work_timer_sig_handler(int data) { work_timer_schedule_evt(); } static void work_timer_evt_handler(int fd, int events, void *data) { int err; static int first = 1; if (timer_fd[1] == -1) { unsigned long long s; struct timeval cur_time; err = read(timer_fd[0], &s, sizeof(s)); if (err < 0) { if (err != -EAGAIN) eprintf("failed to read from timerfd, %m\n"); return; } if (first) { first = 0; err = gettimeofday(&cur_time, NULL); if (err) { eprintf("gettimeofday failed, %m\n"); exit(1); } elapsed_msecs = timeval_to_msecs(cur_time); return; } elapsed_msecs += (unsigned int)s * WORK_TIMER_INT_MSEC; } else { unsigned int n; struct timeval cur_time; err = read(timer_fd[0], &n, sizeof(n)); if (err < 0) { eprintf("Failed to read from pipe, %m\n"); return; } timer_pending = 0; err = gettimeofday(&cur_time, NULL); if (err) { eprintf("gettimeofday failed, %m\n"); exit(1); } elapsed_msecs = timeval_to_msecs(cur_time); } execute_work(); } int work_timer_start(void) { struct timeval t; int err; if (elapsed_msecs) return 0; err = gettimeofday(&t, NULL); if (err) { eprintf("gettimeofday failed, %m\n"); exit(1); } elapsed_msecs = timeval_to_msecs(t); timer_fd[0] = __timerfd_create(WORK_TIMER_INT_USEC); if (timer_fd[0] >= 0) eprintf("use timer_fd based scheduler\n"); else { struct sigaction s; eprintf("use signal based scheduler\n"); sigemptyset(&s.sa_mask); sigaddset(&s.sa_mask, SIGALRM); s.sa_flags = 0; s.sa_handler = work_timer_sig_handler; err = sigaction(SIGALRM, &s, NULL); if (err) { eprintf("Failed to setup timer handler\n"); goto timer_err; } err = setitimer(ITIMER_REAL, &work_timer, 0); if (err) { eprintf("Failed to set timer\n"); goto timer_err; } err = pipe(timer_fd); if (err) { eprintf("Failed to open timer pipe\n"); goto timer_err; } } err = tgt_event_add(timer_fd[0], EPOLLIN, work_timer_evt_handler, NULL); if (err) { eprintf("failed to add timer event, fd:%d\n", timer_fd[0]); goto timer_err; } dprintf("started, timeout: %d msec\n", WORK_TIMER_INT_MSEC); return 0; timer_err: work_timer_stop(); return err; } void work_timer_stop(void) { if (!elapsed_msecs) return; elapsed_msecs = 0; tgt_event_del(timer_fd[0]); if (timer_fd[0] > 0) close(timer_fd[0]); if (timer_fd[1] > 0) { int ret; close(timer_fd[1]); ret = setitimer(ITIMER_REAL, 0, 0); if (ret) eprintf("Failed to stop timer\n"); else dprintf("Timer stopped\n"); } } void add_work(struct tgt_work *work, unsigned int second) { struct tgt_work *ent; struct timeval t; int err; if (second) { err = gettimeofday(&t, NULL); if (err) { eprintf("gettimeofday failed, %m\n"); exit(1); } work->when = timeval_to_msecs(t) + second * 1000; list_for_each_entry(ent, &inactive_work_list, entry) { if (before(work->when, ent->when)) break; } list_add_tail(&work->entry, &ent->entry); } else { list_add_tail(&work->entry, &active_work_list); work_timer_schedule_evt(); } } void del_work(struct tgt_work *work) { list_del_init(&work->entry); } static void execute_work() { struct tgt_work *work, *n; list_for_each_entry_safe(work, n, &inactive_work_list, entry) { if (before(elapsed_msecs, work->when)) break; list_del(&work->entry); list_add_tail(&work->entry, &active_work_list); } while (!list_empty(&active_work_list)) { work = list_first_entry(&active_work_list, struct tgt_work, entry); list_del_init(&work->entry); work->func(work->data); } } tgt-1.0.85/usr/work.h000066400000000000000000000016071435417276200143660ustar00rootroot00000000000000#ifndef __SCHED_H #define __SCHED_H #if defined(__NR_timerfd_create) && defined(USE_TIMERFD) #include static inline int __timerfd_create(unsigned int usec) { struct itimerspec new_t, old_t; int fd, err; fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK); if (fd < 0) return -1; new_t.it_value.tv_sec = 0; new_t.it_value.tv_nsec = 1; new_t.it_interval.tv_sec = 0; new_t.it_interval.tv_nsec = usec * 1000; err = timerfd_settime(fd, TFD_TIMER_ABSTIME, &new_t, &old_t); if (err < 0) { close(fd); return -1; } return fd; } #else #define __timerfd_create(usec) (-1) #endif struct tgt_work { struct list_head entry; void (*func)(void *); void *data; unsigned int when; }; extern int work_timer_start(void); extern void work_timer_stop(void); extern void add_work(struct tgt_work *work, unsigned int second); extern void del_work(struct tgt_work *work); #endif