pax_global_header00006660000000000000000000000064144214753560014525gustar00rootroot0000000000000052 comment=cbc7c7cdc8ef45606f2b7ebd6f36502b2084b6a7 rapiddisk-9.1.0/000077500000000000000000000000001442147535600135065ustar00rootroot00000000000000rapiddisk-9.1.0/.gitignore000066400000000000000000000002231442147535600154730ustar00rootroot00000000000000*.a *.o *.o.* *.d *.mod *.mod.* *.ko *.cmd *.mk *.symvers *.order *.swp src/rapiddisk src/rapiddiskd test/rxflush test/rxio test/rxioctl test/rxro rapiddisk-9.1.0/AUTHORS000066400000000000000000000004261442147535600145600ustar00rootroot00000000000000AUTHOR / MAINTAINER Petros Koutoupis CONTRIBUTORS James Plummer Gustaf Ullberg Dmitry Trikoz Neo Efstathiou Efstathios Marcel Huber Steven Rudolph Oliver Rath Scott McGillivray Boian Berberov Shub77 Matteo Tenca betawaffle q5sys (JT) Andrea Righi rapiddisk-9.1.0/CHANGELOG.md000066400000000000000000000414421442147535600153240ustar00rootroot00000000000000### Release 9.1.0 ### - module: Fixed large performance regression bug with resize mutex. - module: Added RHEL 9 support. - module: Added support for new ioctls. - module: Fixed GFP_HIGHMEM page allocation bug. - utility: Added NVMe Target loopback support. - utility: Fixed issue #155 with strlen of NULL string segfault. - utility: Reworked nvmet exports, unexports and port scanning logic (thank you Matteo Tenca). - scripts: Added example BCC tools script. - documentation: Added RPM building documentation. ### Release 9.0.0 ### - module: Added code to capture page count metrics. - module: Updated ioctl support. - module: Fixed page alloc usage decrement on discard. - module: Added shrink volume support. - module: Added support for 5.19 Linux kernels (thank you Andrea Righi). - module: Added support for 6.0 Linux kernels. - utility: Added code to lock/unlock RAM device. - utility: Added flag to suppress header in stdout (thank you Matteo Tenca). - utility: Added support for RapidDisk "model" branding and support for revalidate size in NVMe Target logic. - utility: Traced, found and fixed all discovered memory leaks (a huge undertaking, thank you Matteo Tenca!!!). - utility: Rewrite daemon to execute commands from a shared library instead of popen to utility (another huge undertaking, thank you Matteo Tenca!!!). - utility: Added checks in URL parsing logic for REST API (thank you Matteo Tenca). - utility: Fix forking logic in daemon and remove need for realpath() usage (thank you Matteo Tenca). - utility: Define and standardize error messaging for consistency and improve verbose mode (thank you Matteo Tenca). - utility: Fix resize operation error messaging (github issue #142). - utility: Cleaned up and optimized NVMe Target management. - scripts: Fixed error checking in NVMe Target hostnqn script file. - scripts: Added fio execution script file examples. - scripts: Added valgrind test script to check for memory leaks (thank you Matteo Tenca). - build: Update Makefiles and add support for CPPFLAGS, CFLAGS, LDFLAGS, etc. (thank you Matteo Tenca). - build: Update Debian and RPM packaging with updated depends (thank you Matteo Tenca). - documentation: Added dm-writecache stats notes. - documentation: Added contrib file and cleaned up README and man pages. - documentation: Added doxygen documentation support (thank you Matteo Tenca). ### Release 8.2.0 ### - module: Fixed support for 5.14. - documentation: Updated README files. - utility: Fixed buffer overflow in NVMe structure. - utility: Improved on MHD version check (thank you Matteo Tenca). - utility: Clean up / optimize systemd service file. - packaging: Improved and overhauled debian packaging (thank you Matteo Tenca). - scripts: Optimized and fixed bugs for rapiddisk/cache on root during boot support (thank you Matteo Tenca). - misc: Cleaned up Makefiles and impr ved dkms build/installation process. ### Release 8.1.0 ### - module: Added support for 5.16 and 5.17 kernels (thank you Nitrooo) - module: Fixed queue allocation defect for 5.15 kernel (thank you Nitrooo) - module: Fixed ram drive allocation bug when sending invalid disk number (thank you betawaffle) - utility: Expanded writecache stats to supported in 5.15+ kernels. - misc: Cleaned up Makefiles ### Release 8.0.1 ### - packaging: Fixed package descriptions - misc: update authors file - misc: updated copyright - misc: added GPL disclaimer to files missing it ### Release 8.0.0 ### - module: Updated for 5.14 and 5.15 kernels - utility: Added NVMe Target support / framework - utility: Added support for dm-writecache status readout in API - utility: Module checker code now traverses via sysfs - utility: Added module checker in daemon - documentation: Clean up formatting and add content - packaging: Fixed dependencies in spec and debian control files. - misc: Cleaned up and optimized scripts to enable rapiddisk/cache on root during boot (thank you Matteo Tenca) ### Release 7.2.1 ### - module: Added support for RHEL 8.4 kernel - utility: Added support for libmicrohttpd v0.9.71 and newer while still supporting legacy versions ### Release 7.2.0 ### - module: Updated for 5.12 kernels and later (thank you Michael) - utility: remove unused headers (thank you Marcel Huber) - utility: add CLI support for dm-writecache wrapper - utility: Fixed property check bug when parsing sysfs block subtree params (github issue #55) ### Release 7.1.0 ### - module: Updated for 5.9 kernels and later - documentation: Updated copyrights ### Release 7.0.1 ### - misc: Fixed typo in utility Makefile ### Release 7.0.0 ### - module: Updated for 5.8 kernels and later - module: fixed cache status format typo - daemon: Implement http-driven API to monitor/manage rapiddisk/cache functions - utility: Removing support for RHEL / CentOS 6.x - utility: Restructured userspace CLI - test: Restructured and improved test framework - misc: Code / documentation cleanup ### Release 6.1 ### - kernel: added support for 5.7 kernel - utility: fixed GCC compilation warnings - utility: code style cleanup - installer: additional dkms installation/removal cleanup (thank you Shub77) - documentation: fixed / updated README to reflect newer dkms installation/removal instructions - documentation: updated copyright dates - documentation: added AUTHORS file - misc: added experimental scripts to enable rapiddisk/cache on root device during boot (thank you Shub77) ### Release 6.0-1 ### - kernel: Fix dkms version in module/Makefile. - installer: modified dkms installation/removal procedures (thank you Shub77) ### Release 6.0 ### - kernel: Fixed module compilation error with modern version of GCC. - utility: Remove dm-crypt code; Not sure why i had it in the first place. Doesn't really belong. Just use cryptsetup. - utility: Removed archive/restore code and dependency on zlib. Again, can just use dd and tar. Is anyone even using this? - misc: Updated licensing and logo location (thanks Boian!). ### Release 5.2 ### - kernel: added support for 4.17 kernel. - build: Cleaned up module clean Makefile. - Updated Copyright years. ### Release 5.1 ### - kernel: added support for 4.13 kernel. - kernel: added support for 4.12 kernel (thank you Marcel Huber). - utility: fixed compilation warnings (thank you Marcel Huber). ### Release 5.0 ### - kernel: Remove kernel mainline specific code (intended for brd replacement). - kernel: Change spinlock types to work better with virtio (github issue #13). - test: Updated tests to a work with the modern version of RapidDisk. - utility: Add JSON output for RapidDisk configuration (requires libjansson). - www: remove fat-free (f3) RESTful API. ### Release 4.5 ### - kernel: cache - Fixed I/O handler bug for 4.8+ kernels - documentation: Cleaned up formatting and license disclaimers (thanks Boian!) ### Release 4.4 ### - kernel: Update to 4.8 and 4.9 kernels. - build: Cleaned up Makefiles (thanks Marcel!) ### Release 4.3 ### - kernel: Add support for the 4.7 kernel (patch supplied by Marcel Huber) - packaging: Updated DEB control for PHP changes between Ubuntu 14.04/16.04 ### Release 4.2-3 ### - utility: Fixed bug in `make install` with `nocrypt` enabled. - documentation: clean up. ### Release 4.2-2 ### - utility: Added more complex default DES key with backwards compatibility to legacy key. - ha: added write around support to HA resource agents. - documentation: corrections / clean up. ### Release 4.2 ### - kernel: Added Write-Around support to `rapiddisk-cache`. - kernel: Fixed `LINUX_VERSION_CODE` check for `rapiddisk-cache` to accommodate changes in 3.8.3. - utility: Added a `nocrypt` build flag. - utility: Added user definable keys for encryption setup. ### Release 4.1-2 ### - kernel: Readjusted misaligned discard request check to build on kernels older than 4.3. ### Release 4.1 ### - kernel: Refuse misaligned discard requests. - kernel: Convert `ENOMEM` to `ENOSPC` when cannot alloc pages. - kernel: Added 4k physical block size attribute. ### Release 4.0-4 ### - Code cleanup. - packaging: Also need to remove `CONFIG_BLK_DEV_RAM_COUNT` from distro specific packages. ### Release 4.0-3 ### - utility: Fixed all references of RapidCache to RapidDisk-Cache. ### Release 4.0-2 ### - utility: Updated fatfree-framework. - utility: Addressed bug in RapidDisk REST implementation. ### Release 4.0 ### - Fixed `libcryptsetup` build error for RHEL6. - Renamed `rxdsk`/`rxcache` modules. - Did massive cleanup of administration utility code. - Converted most (if not all) return codes to POSIX.1 error numbers. - Code cleanup in RESTful API. - RapidDisk volumes now show up as non-rotational. - Cleaned up ioctls in both module and utilies. ### Release 3.7 ### - Cleaned up kernel module code. - Fixed stack overflow bug in administration utility. - Fixed error print statement in administration utility. - Updated copyright years. - Cleaned up build environment, including Makefiles. - Fixed bug in configuring encryption on device. ### Release 3.6-3 ### - Updated/corrected documentation in manual page and in source. ### Release 3.6-2 ### - Fixed a memory leak in administration utility. - Placed better checks before deallocating memory in administration utility. ### Release 3.6 ### - Updated RapidDisk modules for Linux kernel version 4.4. ### Release 3.5-2 ### - Updated kernel documentation. ### Release 3.5 ### - Forcing `rxdsk` driver to do the drive enumeration. Removed functionality from administration utility. - Appropriately initializing major number variable to 0 before registering block device module. - Administration utility now checks for `sysfs` entry of `rxdsk` and not module name in `/proc/modules`. - Converted sector size input in `rxdsk` module to KB input. Modified administration utility to this. - Fixed bug in check for total `rxdsk` devices in module. ### Release 3.4-2 ### - Updated Makefile for tools install to add HA scripts. - Fixed bugs with HA scripts (both `rgmanager` and `pacemaker`). ### Release 3.4 ### - Added ability to autoload RapidDisk volumes during module insertion. - Fixed bug in RapidDisk (volatile) volume size definition across 32 to 64 bit types. - Making use of `BIT()` macro in the driver. - Removed RapidDisk-NV support. It was redundant with the recently kernel integrated `pmem` code. ### Release 3.3 ### - Updated code for the 4.3 kernel. - Cleaned up the main `Makefile`. - Cleaned up entire driver code. Adjusted formatting. ### Release 3.2 ### - Replaced `procfs` management to `sysfs`. - Identified & corrected a couple of memory leaks. - Massive code cleanup (intended for kernel submission). - Minor code optimizations (slight performance improvements). ### Release 3.1-2 ### - Fixed on-line menu of administration binary. - Updated spec file to autoload modules after install. - Corrected package description in spec file. ### Release 3.1 ### - Fixed memory leak and an exit on failure before removing mutex during a `procfs` read. - Added RESTful test file to test API from CLI. - Integrated encryption support via `dm-crypt`. - Enabled RPM builds for Red Hat / CentOS 6 & 7. - Enabled RapidDisk YUM repo for Red Hat / CentOS 6 & 7 support. - Added Pacemaker and rgmanager resource files to enable HA support. ### Release 3.0 ### - Added NVDIMM support. - Added RESTful API support. - Updated administration binary and cleaned up a lot of its code. - Removed pyRxAdm graphical wrapper. - Fixed bug when erroring during RapidCache module insertion. - Cleaned up RapidCache module code (removed `procfs` entry). ### Release 2.13 ### - Bug fix with `rxadm` binary and mapping RapidCache to pre-existing partitions. - Added more information to RapidDisk `procfs` file. - Addressed compilation warnings for GCC 5.1 ### Release 2.12 ### - Updated modules for kernel 3.14 ### Release 2.11 ### - Updated `rxcache` for Red Hat 6.4 (device mapper conflict) - Addressed incorrect description of maximum number of rxdsks supported. ### Release 2.10 ### - Updated modules for kernel 3.10. - Updated `Makefile` for cross compiling install. ### Release 2.9.2 ### - Addressed a bug in `rxdsk` print statement (wrong type). Thanks go to Neo for discovering and patching it. ### Release 2.9.1 ### - Minor update adding DKMS support. - Adding support to build and install/uninstall tools separately (i.e. without modules, as in when installing with DKMS). ### Release 2.9 ### - Added better implementation of `BLKFLSBUF` ioctl to rxdsk module. This will "flush data" and truncate pages. - Added flush command to `rxadm` utility. - Added support for Linux kernel 3.9. Tested on 3.9.2. ### Release 2.8 ### - Cleaned up code and removed unused and unimplemented caching feature (write-around). - Added support for Linux kernel 3.8. Tested on 3.8-rc7. ### Release 2.7 ### - Made some modifications to the modules' makefile. - Added support for Linux kernels 3.6 & 3.7. Tested on 3.6.9 and 3.7-rc8. ### Release 2.6 ### - Minor `rxcache` kernel update: Make spinlocks less greedy by removing most of the disable ALL interupts spinlocks and replacing them with spinlocks to disable interrupts ONLY from bottom halves. ### Release 2.5 ### - Added support for building in 3.4 and later Linux kernels. - Update module Makefile to point to a different `DESTDIR` and `KSRC` (for cross-compiling) ### Release 2.4 ### - Removed warning for RapidCache build. - Addressed an issue with `md` raid 1 (mirror) and using `rxdsk` in which the `md` driver would routinely send i/o of size 0 and `rxdsk` would return an `EIO`, failing the array. Problem and solution found and provided by Dmitry Trikoz of Stratus Technologies. ### Release 2.3 ### - Addressed warning generated for kernels 3.2 and later with the return type of the `blk_queue_make_request` `request_queue` function. - Added comments and cleaned error messages in pyRxAdm. - Added comments to `rxadm` files. ### Release 2.2.1 ### - Added additional functionality to pyRxAdm (add, map, archive, restore) also added some more error checking. - Fixed bug in `rxadm` (`archive.c`) during the archival process. - Updated version no. (`cmd/common.h`, `rxcommon.h`) and removed b's to move from beta to production. - Added a couple more switches to list version/help info of `rxadm` (`main.c`) - Cleaned up `rxadm` logo for pyRxAdm (`rxadm_logo_48x48.png`) ### Release 2.2 ### - Added cmd/pyRxAdm wrapper - Modifed short-list feature output and modified error statement (`cmd.c`, `common.h`) - Modified `cmd/Makefile` - Added logo for wrapper (`misc/rxadm_logo_48x48.png`). - Updated version no. (`rxcommon.h`) ### Release 2.1 ### - Added `--short-list` support. - Cleaned up debug messages on modules and added a couple of more. ### Release 2.0.1 ### - Fixed bug #5 relating to using the `rxadm` utility without any nodes listed in `/dev/mapper`. - Cleaned up a few messages in both `rxdsk.c` and `rxcache.c`. ### Release 2.0 ### - Added `rxcache` write/read through caching module support. - Added `rxcache` management features in `rxadm` utility. - Modified input for archive/restore in `rxadm` to not use absolute path for `rxdsk` node. It maintains a form of consistency across all commands. ### Release 1.4 ### - Fixed bug #4 by adding the `BLKFLSBUF` ioctl() command to process. This is specifically for when the user places an rxd node in an `mdadm` raid array. - Added a new test file to test the new ioctl command. - Cleaned up the Makefiles a bit and now the user can build and install the kernel module from the root of the package tree as opposed to doing it from the module directory. ### Release 1.3.2 ### - Fixed bug #3 which was for a warning during the build of `rxadm` on an x86_64 architecture. Thanks go to Gustaf Ullberg for discovering the root cause and providing a quick solution. ### Release 1.3.1r2 ### - Removed the "b" from version strings to signify non-beta. This project seems to be production ready. - Also added a test suite and some additional test tools for testing data integrity, performance, etc. This will help a lot for feature testing in future releases. ### Release 1.3.1b ### - Added check in management utility to make sure that `rxdsk` node is present before archiving and restoring compressed/ decompressed images. ### Release 1.3b ### - Added new feature to archive/restore an rxd volume to/from a zlib compressed data file (many thanks to Simon Ball for suggesting a similar feature). - Added `discard` support. - Added an ioctl to handle an invalid query sent by later versions of `udev` that correspond with Linux kernels 2.6.35 and above. - Also integrated patch submitted by James Plummer of Stratus Technologies to address 32 bit limitation of `rxadm` utility `rxdsk` creation/resizing by casting the variable to a 64-bit type. Patch also included minor clean up code/optimizations for the same `rxadm` util. ### Release 1.2b ### - Optimized the configuring of the request queue. - Added checks to build from 2.6.32 all the way to the latest (currently 3.0.3). ### Release 1.1b ### - Added support for dynamic resizing of attached `rxdsk` volumes. ### Release 1.0b ### - Official public release. rapiddisk-9.1.0/CONTRIBUTING.md000066400000000000000000000027051442147535600157430ustar00rootroot00000000000000# Contributing When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owner(s) of this repository before making a change. ## Submitting Issues Issues for the RapidDisk project should be filed on the project's GitHub page: https://github.com/pkoutoupis/rapiddisk/issues. When submitting an issue, please provide the following information (where applicable): 1. Linux distribution and distribution version details 2. Linux kernel version and architecture details (uname -a) 3. RapidDisk version (rapiddisk -v) 4. Steps for reproducing the error / issue 5. Relevant system log / dmesg and CLI output ## Pull Requests All pull requests should have a clear description explaining the submitted code and the reason for it. Before submitting code changes, please ensure that the project is able to build and that the code has been thoroughly tested. ### Code Reviews All submitted pull requests will undergo a traditional code review process where reviewers will be assigned or volunteer to review the code submitted in the pull request. This may be followed by some back and forth dialogue. During the review process, there may be times when the original submitter will be asked to modify their code. Upon submitting newly committed code, the reviewers will review the updated code. Only when the code looks good, then the project owner(s) will merge the pull request into the master branch. rapiddisk-9.1.0/COPYRIGHT000066400000000000000000000423111442147535600150020ustar00rootroot00000000000000GNU 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. 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. one line to give the program's name and an idea of what it does. Copyright (C) yyyy name of author 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. signature of Ty Coon, 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. rapiddisk-9.1.0/Makefile000066400000000000000000000041201442147535600151430ustar00rootroot00000000000000# Copyright © 2016 - 2023 Petros Koutoupis # All rights reserved. # # 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 . # # SPDX-License-Identifier: GPL-2.0-or-later MODULE_SUBDIR = module TEST_SUBDIR = test TOOLS_SUBDIR = src CONF_SUBDIRS = conf doc .PHONY: all all: $(MODULE_SUBDIR) $(TOOLS_SUBDIR) $(TEST_SUBDIR) .PHONY: $(MODULE_SUBDIR) $(MODULE_SUBDIR): $(MAKE) -C $@ $(MAKECMDGOALS) .PHONY: $(TEST_SUBDIR) $(TEST_SUBDIR): $(MAKE) -C $@ $(MAKECMDGOALS) .PHONY: $(TOOLS_SUBDIR) $(TOOLS_SUBDIR): $(MAKE) -C $@ $(MAKECMDGOALS) .PHONY: $(CONF_SUBDIRS) $(CONF_SUBDIRS): $(MAKE) -C $@ $(MAKECMDGOALS) .PHONY: clean clean: $(MODULE_SUBDIR) $(TOOLS_SUBDIR) $(TEST_SUBDIR) .PHONY: run-test run-test: $(MODULE_SUBDIR) $(TOOLS_SUBDIR) $(TEST_SUBDIR) .PHONY: install install: $(MODULE_SUBDIR) $(TOOLS_SUBDIR) $(CONF_SUBDIRS) .PHONY: install-strip install-strip: MAKECMDGOALS=install install-strip: install $(MAKE) -C $(TOOLS_SUBDIR) install-strip .PHONY: uninstall uninstall: $(MODULE_SUBDIR) $(TOOLS_SUBDIR) $(CONF_SUBDIRS) .PHONY: dkms-install dkms-install: $(MODULE_SUBDIR) .PHONY: dkms-uninstall dkms-uninstall: $(MODULE_SUBDIR) .PHONY: tools tools: $(TOOLS_SUBDIR) .PHONY: clean-tools clean-tools: $(TOOLS_SUBDIR) .PHONY: tools-install tools-install: $(TOOLS_SUBDIR) .PHONY: tools-install-strip tools-install-strip: $(TOOLS_SUBDIR) .PHONY: tools-uninstall tools-uninstall: $(TOOLS_SUBDIR) .PHONY: tools-debug tools-debug: $(TOOLS_SUBDIR) .PHONY: tools-strip tools-strip: $(TOOLS_SUBDIR) .PHONY: debug debug: tools-debug rapiddisk-9.1.0/README.md000066400000000000000000000154051442147535600147720ustar00rootroot00000000000000![RapidDisk](logo.png) Author: Petros Koutoupis () ## About the RapidDisk Project RapidDisk contains a set of advanced Linux RAM Drive and Caching kernel modules. The user space utilities allow you to dynamically allocate RAM as block devices to either use them as stand alone drives or even map them as caching nodes to slower local (or remote) disk drives. The same utilies provide users with the capability to export the same volumes across an NVMe Target network. ![Diagram](diagram.png) ### Caching Policies Leverage a high speed RAM drive to add speed to a slower volume by utilizing the (Linux native) Device-Mapper framework. Enable a Write / Read-through or Write Around Least Recently Used or LRU (FIFO) cache. #### Write-Through / Read-Through Caching This is where an application treats cache as the main data store and reads data from it and writes data to it. The cache is responsible for reading and writing this data to the permanent storage volume, thereby relieving the application of this responsibility. In this mode, all writes are cached to a RapidDisk RAM drive but are also written to disk immediately. All disk reads are cached. Cache is not persistent over device removal, reboots, or after you remove the Device-Mapper mapping. This module does not store any cache metadata on RapidDisk volumes but instead in memory outside of RapidDisk. You can map and unmap a cache drive to any volume at any time and it will not affect the integrity of the data on the persistent storage drive. #### Write-Around Caching Write Around caching shares some similarities with the Write-Through implementation. However, in this method, only read operations are cached and not write operations. This way, all read data considered hot can remain in cache a bit longer before being evicted. ### RESTful API The RapidDisk Daemon (rapiddiskd) enabled remote management of RapidDisk volumes. The management commands are simplified into a set of GET and POST commands. It operates over port 9118 by default. This can be changed when invoking the daemon with the use of a parameter. Either way, please ensure that the port is open for TCP within your firewall rules. An example of a GET command: ```console # curl -s --output - 127.0.0.1:9118/v1/listRapidDiskVolumes|jq . { "volumes": [ { "rapiddisk": [ { "device": "rd1", "size": 67108864 }, { "device": "rd0", "size": 67108864 } ] }, { "rapiddisk_cache": [ { "device": "rc-wa_loop7", "cache": "rd0", "source": "loop7", "mode": "write-around" } ] } ] } ``` An example of a POST command: ```console # curl -X POST -s 127.0.0.1:9118/v1/createRapidDisk/128|jq . { "status": "Success" } ``` ## Building and Installing the rapiddisk kernel modules and utilities Change into the project's parent directory path. > To build the rapiddisk management utility, you will need to have the > `libjansson`, `libpcre2`, `libdevmapper` and `libmicrohttpd` > development library files installed on your host system. > > You are required to having either the full kernel source or the kernel > headers installed for your current kernel revision. To build rapiddisk from source, you would type the following on the command line: ```console # make ``` To install rapiddisk (must execute with superuser rights: `sudo`): ```console # make install ``` To uninstall rapiddisk (must execute with superuser rights: `sudo`): ```console # make uninstall ``` The rapiddisk utility will install in `/sbin/` For utility information please reference the rapiddisk manual page: ```console # man 1 rapiddisk ``` ## Inserting/Removing the rapiddisk / rapiddisk-cache kernel modules Both modules are required to be loaded for the rapiddisk daemon to start. To insert the rapiddisk module: ```console # modprobe rapiddisk ``` To remove the rapiddisk module: ```console # modprobe -r rapiddisk ``` To insert the rapiddisk-cache module: ```console # modprobe rapiddisk-cache ``` To remove the rapiddisk-cache module: ```console # modprobe -r rapiddisk-cache ``` ## Building and installing / uninstalling the tools ONLY Installing: ```console # make tools-install ``` Uninstalling: ```console # make tools-uninstall ``` ## Installing modules for DKMS support ```console # make dkms-install ``` ## Uninstalling modules for DKMS support ```console # make dkms-uninstall ``` ## Managing the RapidDisk daemon service After installation, to start the service via systemd: ```console # systemctl start rapiddiskd.service ``` To check the status of the service via systemd: ```console # systemctl status rapiddiskd.service ``` To stop the service via systemd: ```console # systemctl stop rapiddiskd.service ``` To start the service at boot via systemd: ```console # systemctl enable rapiddiskd.service ``` ## Managing RapidDisk as an NVMe Target There are a few things that need to be known when using the NVMe Target features of the RapidDisk suite. ### Loading the NVMe Target Kernel Modules In order to map any RapidDisk device and export it in the NVMe Target framework, the nvmet and the nvmet-tcp or nvmet-rdma (or nvme-loop) kernel modules must be inserted. ```console # modprobe nvmet nvmet-tcp ``` ### Enabling NVMe Target Ports At least one Ethernet interface will need to be configured as a target port to export the RapidDisk volume from. ```console # rapiddisk -i eth -P 1 -t tcp ``` ### Exporting Targets When exporting a volume, a RapidDisk volume and a target port must be defined. If a host NQN is not defined, the administration utility will provide access to any host NQN. Note - a target can be exported across more than one target port. ```console # rapiddisk -e -b rd3 -P 1 ``` If a host NQN is defined, access is restricted to only those host NQNs. Note - the following command example can be repeated multiple times to add additional host NQNs for the specified target export. ```console # rapiddisk -e -b rd3 -P 1 -H nqn.host1 ``` ### Unexporting Targets Unexporting RapidDisk volumes looks a bit different than exporting. If a host NQN is defined for a specified target, only that NQN will be removed from accessing the exported target. ```console # rapiddisk -x -b rd3 -H nqn.host1 ``` Removing all allowed host NQNs will revert access to any and all host NQNs requesting access to the target. If a target port is defined, the exported target will not be exported from the interface if one condition is met: the target has no defined allowed host NQNs. ```console # rapiddisk -x -b rd3 -P 1 -H nqn.host1 ``` OR ```console # rapiddisk -x -b rd3 -P 1 ``` And if there are no defined allowed host NQNs and the target is not being exported across any target ports, the entire target is removed from the subsystem. rapiddisk-9.1.0/conf/000077500000000000000000000000001442147535600144335ustar00rootroot00000000000000rapiddisk-9.1.0/conf/Makefile000066400000000000000000000031571442147535600161010ustar00rootroot00000000000000# Copyright © 2016 - 2023 Petros Koutoupis # All rights reserved. # # 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 . # # SPDX-License-Identifier: GPL-2.0-or-later CP := cp RM := rm -rf MKDIR := mkdir -pv -m 755 SERVICE = rapiddisk CFG_DIR := /etc/$(SERVICE)/ SYSD_DIR := /etc/systemd/system CHMOD := chmod INSTALL := install -D -m 755 -t .PHONY: all all: @echo Nothing to do. .PHONY: install install: all @echo Installing rapiddisk high availability and rapiddiskd daemon files. $(INSTALL) $(DESTDIR)$(CFG_DIR) $(SERVICE).* $(INSTALL) $(DESTDIR)$(SYSD_DIR) $(SERVICE)d.* .PHONY: uninstall uninstall: @echo Uninstalling rapiddisk high availability and rapiddiskd daemon files. $(RM) $(DESTDIR)$(CFG_DIR) $(RM) $(DESTDIR)$(SYSD_DIR)/$(SERVICE)d.* .PHONY: clean install-strip debug tools-strip tools-debug tools-uninstall tools-install-strip tools-install clean-tools tools dkms-uninstall dkms-install clean install-strip debug tools-strip tools-debug tools-uninstall tools-install-strip tools-install clean-tools tools dkms-uninstall dkms-install: rapiddisk-9.1.0/conf/rapiddisk.sh.pacemaker000077500000000000000000000101171442147535600206730ustar00rootroot00000000000000#!/bin/bash # # Copyright (C) 2015 - 2023 Petros Koutoupis. All rights reserved. # PATH=/bin:/sbin:/usr/bin:/usr/sbin export PATH : ${OCF_FUNCTIONS_DIR=/usr/lib/ocf/lib/heartbeat} . ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs RDSK_bin="/sbin/rapiddisk" ####################################################################### metadata() { cat < 1.1 This defines a standard RapidDisk HA failover. Defines a standard RapidDisk HA failover. This is the name of the resource rule. Name This is the absolute path of the storage device to map with RapidDisk-Cache. Path of storage device for RapidDisk-Cache. This is the size of the RapidDisk (and cache for RapidDisk-Cache) volume. RapidDisk size. This caching mode: Write-Through (wt) or Write-Around (wa). Caching mode: wt or wa. END exit $OCF_SUCCESS } verify_all() { if [ -z "$OCF_RESKEY_name" ]; then ocf_log err "Invalid Name Of Service: $OCF_RESKEY_name" return $OCF_ERR_ARGS fi if [ -z "$OCF_RESKEY_volume" ]; then ocf_log err "Invalid Name Of Volume: $OCF_RESKEY_volume" return $OCF_ERR_ARGS fi if [ -z "$OCF_RESKEY_size" ]; then ocf_log err "Invalid Size Of RapidDisk Volume: $OCF_RESKEY_size" return $OCF_ERR_ARGS fi return $OCF_SUCCESS } do_start() { verify_all || exit $? if do_status; then ocf_log info "Starting Service $OCF_RESOURCE_INSTANCE > Already running" return $OCF_SUCCESS fi if [ -z "$OCF_RESKEY_mode" ]; then mode="wt" else mode=$OCF_RESKEY_mode fi output=`$RDSK_bin --attach $OCF_RESKEY_size` if [ $? -ne 0 ]; then ocf_log info "Unable to create a RapidDisk volume of $OCF_RESKEY_size MB" return $OCF_ERR_GENERIC fi volume="$OCF_RESKEY_volume" rdsk=`echo $output|sed -e 's/^.*rd/rd/' -e 's/ .*$//'` $RDSK_bin --cache-map $rdsk $volume $mode return $OCF_SUCCESS } do_stop() { verify_all || exit $? if [ -z "$OCF_RESKEY_mode" ]; then mode="wt" else mode=$OCF_RESKEY_mode fi cache="rc-${mode}_`echo $OCF_RESKEY_volume|rev|cut -d'/' -f1|rev`" output=`$RDSK_bin --short-list|grep "$cache:"` $RDSK_bin --cache-unmap $cache $RDSK_bin --detach `echo "$output"|sed -e 's/^rc.*://' -e 's/,.*$//'` return $OCF_SUCCESS } do_status() { verify_all || exit $? if [ -z "$OCF_RESKEY_mode" ]; then mode="wt" else mode=$OCF_RESKEY_mode fi volume=`echo $OCF_RESKEY_volume|rev|cut -d'/' -f1|rev` if [ -e "/dev/mapper/rc-${mode}_$volume" ]; then return $OCF_SUCCESS fi ocf_log debug "RapidDisk resource for $OCF_RESKEY_volume is not running." return $OCF_NOT_RUNNING } # # # case $1 in validate-all|verify-all) verify_all exit $? ;; start) do_start exit $? ;; stop) do_stop exit $? ;; restart) do_stop do_start exit $? ;; status|monitor) do_status exit $? ;; meta-data) metadata exit 0 ;; *) echo "usage: $0 {start|stop|restart|status|meta-data|validate-all}" exit $OCF_ERR_UNIMPLEMENTED ;; esac rapiddisk-9.1.0/conf/rapiddisk.sh.rgmanager000077500000000000000000000120521442147535600207060ustar00rootroot00000000000000#!/bin/bash # # Copyright (C) 2015 - 2023 Petros Koutoupis. All rights reserved. # PATH=/bin:/sbin:/usr/bin:/usr/sbin export PATH . $(dirname $0)/ocf-shellfuncs || exit 1 . $(dirname $0)/utils/config-utils.sh . $(dirname $0)/utils/messages.sh . $(dirname $0)/utils/ra-skelet.sh declare RDSK_bin="/sbin/rapiddisk" metadata() { cat < 1.1 This defines a standard RapidDisk HA failover. Defines a standard RapidDisk HA failover. This is the name of the resource rule. Name This is the absolute path of the storage device to map with RapidDisk-Cache. Path of storage device for RapidDisk-Cache. This is the size of the RapidDisk (and cache for RapidDisk-Cache) volume. RapidDisk size. This caching mode: Write-Through (wt) or Write-Around (wa). Caching mode: wt or wa. EOT } verify_all() { clog_service_verify $CLOG_INIT if [ -z "$OCF_RESKEY_name" ]; then clog_service_verify $CLOG_FAILED "Invalid Name Of Service" return $OCF_ERR_ARGS fi if [ -z "$OCF_RESKEY_volume" ]; then clog_service_verify $CLOG_FAILED "Invalid Name Of Volume" return $OCF_ERR_ARGS fi if [ -z "$OCF_RESKEY_size" ]; then clog_service_verify $CLOG_FAILED "Invalid Size Of RapidDisk Volume" return $OCF_ERR_ARGS fi clog_service_verify $CLOG_SUCCEED return $OCF_SUCCESS } do_start() { verify_all || exit $? if do_status; then ocf_log info "Starting Service $OCF_RESOURCE_INSTANCE > Already running" return $OCF_SUCCESS fi clog_service_start $CLOG_INIT if [ -z "$OCF_RESKEY_mode" ]; then mode="wt" else mode=$OCF_RESKEY_mode fi output=`$RDSK_bin --attach $OCF_RESKEY_size` if [ $? -ne 0 ]; then ocf_log info "Unable to create a RapidDisk volume of $OCF_RESKEY_size MB" clog_service_start $CLOG_FAILED return $OCF_ERR_GENERIC fi volume="$OCF_RESKEY_volume" rdsk=`echo $output|sed -e 's/^.*rd/rd/' -e 's/ .*$//'` $RDSK_bin --cache-map $rdsk $volume $mode clog_service_start $CLOG_SUCCEED return $OCF_SUCCESS } do_stop() { verify_all || exit $? clog_service_stop $CLOG_INIT if [ -z "$OCF_RESKEY_mode" ]; then mode="wt" else mode=$OCF_RESKEY_mode fi cache="rc-${mode}_`echo $OCF_RESKEY_volume|rev|cut -d'/' -f1|rev`" output=`$RDSK_bin --short-list|grep "$cache:"` $RDSK_bin --cache-unmap $cache $RDSK_bin --detach `echo "$output"|sed -e 's/^rc.*://' -e 's/,.*$//'` clog_service_stop $CLOG_SUCCEED return $OCF_SUCCESS } do_status() { verify_all || exit $? clog_service_status $CLOG_INIT if [ -z "$OCF_RESKEY_mode" ]; then mode="wt" else mode=$OCF_RESKEY_mode fi volume=`echo $OCF_RESKEY_volume|rev|cut -d'/' -f1|rev` if [ -e "/dev/mapper/rc-${mode}_$volume" ]; then clog_service_status $CLOG_SUCCEED return 0 fi clog_service_status $CLOG_FAILED "$OCF_RESKEY_volume" return $OCF_ERR_GENERIC } # # # case $1 in validate-all|verify-all) verify_all exit $? ;; start) do_start exit $? ;; stop) do_stop exit $? ;; restart) do_stop do_start exit $? ;; status|monitor) do_status exit $? ;; meta-data) metadata exit 0 ;; *) echo "usage: $0 {start|stop|restart|status|meta-data|validate-all}" exit 1 ;; esac rapiddisk-9.1.0/conf/rapiddiskd.service000077500000000000000000000006401442147535600201360ustar00rootroot00000000000000[Unit] Description=A rapiddisk network management daemon After=network.target [Service] ExecStartPre=sh -c 'lsmod | grep -q rapiddisk || { modprobe rapiddisk ; modprobe rapiddisk-cache ; }' ExecStart=/sbin/rapiddiskd Type=forking Restart=always RestartPreventExitStatus=255 RestartSec=10 PIDFile=/run/rapiddiskd.pid StandardError=journal StandardOutput=journal StandardInput=null [Install] WantedBy=default.target rapiddisk-9.1.0/diagram.png000066400000000000000000001054631442147535600156310ustar00rootroot00000000000000PNG  IHDRpV!sRGBPeXIfMM*i&YiTXtXML:com.adobe.xmp 1 L'Y@IDATx$UkwYr,, KFr$Q@ `QT PLY䤀 <5}wk{{gg{f;u֭[U}{nfYX0 @0 @0 @0 @0 @0 @0 @0 @0 @0 @0 @0 @0 @0 @0 @0 @0 @0 @0 @0 }g`T#Àߓb`^0رcGrYT ^π@X00"cD: 4|4[ 4z.?mڴEF5ה)SF2f̨V>cSe*Dy{ fGU<?i1c:u=|~LaaoMX0 ̀GڔӦU蔗+$ey;4Ú)es W2>Mi7/C]ͩO?` F#{ 1oSSQ3c~tm<*o9 ϐVH ϸbEY9br_~LrO_RUSOiQys7ϖ)rh``82oǫcjd]]p#ϏnٕoQV *SN?XΨW+lUnu(Q965e ?:⛎h";GL/p.&v* =Sa7P02c947XFҖ"8Y#9WÂ` 2GĎd "6}iv=e[GTv`@0 t>f= JN`4eUܵR{we6]5y\z`AQ @0 (\$?uc0s(4pjC{Ө?jͷ$/.xqT{@|.;Ùr;xh,O@J9D^_ywgVy[Xrl^{^|O w3cG\!_a8f[SQ+z6/۬D~l=h\gJ~{î/\i?eQct(Á/MQ{vc 5{\i?$eeɱ+5Va~YHf=w}cr_s/ 8jy^*D=?^|=aM`% 8jnĚlk}32߻l>C lz}Fe)4sXEkgl4&/g^ kh^M_>տ~skTEY2.#M2CۋԦǤ5*'?>,_aܛeQA,%Ś``x00 0<3FVrF,ŽY]_,)$b W^ f+Yo& B +~XJBkmV I@ʺٍÕg#?R&/fc-V8+ÏMCfcXJ[@V?93ѣۈ# I ۆ#Nk0ChϲzǬ"͏ج1E;6{ڴ b^=P¥8?ws|AVS鍢 #B +\K*raץݯ$!:W+SW4r)>EYYȌrO*iy?=}V.hmf{2?=j9F2GL]6ߗ㥍a@װ@B_i]ʏyޖ|-o}wط7(lD8pČ? 3 C$bE4 W" ;:V:#>я[,?^K/tK|N>Z([feZ:.׿xc|sM?W3's) CGe4j_;6a)ËW^9z뭳/x*l=SN:luͯG_vgw\ ֽJmYvgg]tQ^{vK.$җjA4S_+b?!W\;3?ZkwpM;c9+2a}.&Qr[,; ^iˆEM?[nɞ|+^*djye]/~gWV,ٸq㲧~:߯o^s\A66hƲ5Ԗ k+X"eÞ]nY,&b;8y4&l]wͅ@Yph+R˵?R ,7^{˅/Xf)8S'xbzǾkyf{,:y5̾U_x˲+"C ]g*Df.3l؛pɮˆy lخ/; 1'hˀ ~53!qB:{m喙UzPq=䙑K Yߟ3@-٣>gJ?Oz*g;}twwˋ.{B?c 70;#_|NS]"$!•/#KnjȃamyIh3 xP΀߀(UM R~VyW<J zvnY*믿~% \^ ^7|2 /SVX~H +m8x /;P 9y:z+[~ +Pfm*d~gdLlzeM_>$9ޡt^ 嗸6zPjy2ߩa4ˆx,\m` a`z~@Ijϣx kN8Ƌ(38t_7n$ ds)/Q?Tk?Q.37Mp g~]` a`0=}VRĆpqWg҆ mY_'S‚a@d^%d!y.f\S2D.;Z5ۆ2O2&)T&Qy?D7z oDc` W^e… )]6Tz~kWeR*m;qH 8 d`7YG3UHhQVw^_;~lF;qB&47܏xh +gX * T0:G5_hp77i_U)yAb+vЌV=eχPi"\3VЦ C7s|7%钨z` aBhu}lIqHϙJT^o2ﳾ޹o \\0}b8@0 WƁ` pq1ςMEe@X0 aeEK eu~/ b‚` I A>8%ÂHnvy~Dm}1k\"8IoL.B !,ʀόV_CeeEX- ϳRg)7#V65_| $ l<pqn0XeuyKp<tFW7*TCAs{1tků-v< $4f`|WjT+F; 1x5y(.n x't3MgZU =?zg 4IKUq2>cP!'^qm Uz ٻ(vpH6V}Fu 76`%8{Z.E{^l՞Mss~ :XwpMmp9 a@0^&)wkςOdl5 l4cS`0^e!ocR323l|px ٪TnKAX E4{l+S/ؚ~0{+|JV̼>F7SkԘmg^ɷi#0 56~ j:鱽 -M‚``6Hg't̗(zpKP40F֗h [1Km~bCʣqi*H;'VL(0OqiSVmeF(N@MSHSv&@I!UHN//Yoe&i? йxld_k6gpU.rÂ` ~w֭/ S5&w(/ G!cVtHpn R[3.bo5 1BB ) 5*|)Pe`9K^XhgG1;ڢPM7RcGa1lS2MvT ``Gxsq$d H ?.n~B(o6MU[` Ms*dE&`j#@1KF3DԪu4Sm&>Q=%Tg\ |( ̛͖!c7z.5ngպ uf\E[<&īL3.RFn]]JS/ˉ",#Mk?lLM3g53~o=A+9kIT*l>.CF`tHі?.->+~|kZފ.oͥdYߝvVΚji`M 8`;R0; ԚzEǪۮ Vd+TnNŶ[ ojxxٴkP7Б]sfqE^p8(Vb>_ .f9jk&0`}!Y*&<'EZAج3òZKmD7_fbb>:u,|'8I|e eK{8+/7kwEvo[7r\j' GtF~Tnk rJ$`}v x- uk*qN@Uz7UU;mGE3+K~\€k)Žn ĝŤ} K_ߒr_QH^ݬw5Жw-3AVѦ}*YKjv8J? R}m_ j`GpxXbG}!`g0(θq65de{ye PON)73Zhtۃb_k{xM흠Y߄4W~T[粽dA^/?d&U+DzM:aY V}ۀd=MwB~}Bjv"xP %^[@ѧ{9 j/$2~SfSmnXuAAUF-@{Zo룎dEJ=76-`¾nW ɎymR])•=@ /:Hdm f2-NmN~nζh!]t`lᙔ  wyPʩd^vvpg+(Q87X @=חaf~T>NNL }/-f`~?Gu1sAKmP(^f@jVWENjλ ){c~_^Z; & UB YBmF&;? 6\mWuik+`{%@-IEWǯP_m2 O6B*[@WT,FU?עղW7~WA7Z( @muP볟AcL?\]2 If8Mٖ]x6Z` P:>dKig9i ,{`wyA=`OX ,[`!dS^'b/H(XW,>>ϩ]6}?l=FsT1s35ef̠{0v`hѝ@afReKaTb<0KmTf`aF% 蜭u²nt|u[;@)ˈa3E_s;`N2sR'ޖ l2ˣ8vHsRUŢ#tFSDM0`}bf nc!`]QF=߲0 Xȱ@/m>|&;|c5>Xx k_N"wrd;σg[/&X(#\v" ^$ĸƀoB i֥[_c(fK0 fՎe=zු\r2̪g'}6>7jԨQd#MÂ`  !^tM5ZrGAq|8x PlӶYS=AEѶ|#8fޖf[j7254,x\&H7jl{󁿬m=ti7htO(>+jԶv_7O@dfiԞͲz++`.#)٠ѹ'lQ]1umS^46wzE}.h3)`tb|9`$hE7  1̎v(.M jS\JSg]"`vވ>gi`CW3F)v`v~3U1.-\EKG_V3X/Yigl'_z & }^1{@_l9ܷھg+@{cBj&}y@3O@#TnIp[=|-sA6 jev=q ' dˀE{}A-IŮbЊ8Q$aؔIDPgl| YukW8f`|rkIƂypVvl6y~ab7ܳ\ez7A56^eZY 2vYE ߎT[px뫶^OfMx4Z{.Oe+/@oBX1b=Vn]nǵ~LMepl(`3+3F@ jO`"hgq0kyly:(QV_{[*KGi/rٛ nݳ`[Wש5}}g:_ V6Kӱғ3K|zfV1cȶQVTW@3".*' 8z=-֨|7mwݹ>ҧF}O]X4ev_Jە`ZW']#Nc&7g P0,`HX v`9Yp'JM:v(H+.\f#*3Jbh/"fz`_1ԛɵ"uR2e6vڴicŲ%uORw!P˶D e zG3fSqO/X4r/qA6oA@!E23s7F٫| %֞3}̱]43AYF4~?Ͼԗu 42j%p*N|n+ ,MWC,{2}d ,ۖw .'nθ NTm)xm4:ʂtq,*@S3 `!P6> z[GoAY=W8,4 P&0~}n -[6E[z}iAf~}[yvlV ^v} lt yfs3Ю y7fEzig0|| ?)=+<;}mp*06.pY$Oڲ~ |bĴm}xmA`IPFq*r9K:}QP7zYzX?q}/|ɾ`'0/ *.?˵jS`u)$ސ{ڷcCNp]3g(6?FFKo`X42E9' ǀt>ڝ߿eTcs`SPe_BWF}NweKU&U!\]tڱvhcqo+wktf` K`fRd {43vŮQ?Io,ζ@>liߎD`FSO~([v}~ط~5 ھg٧v3l2Hmʶ@j[$~ mˉ&dY7C no^`->Yf>W) J.}4];&zf `p"pL}w*bAo zz'ăgv=SԾ~K|a!\_ޮ`9kqٻ!ϭ{~8N (h`2hVLlf{Ѻc ]2^ٺ4chK{"P4^ |< zSz8?@H4ʞ\#jmT(8͢~.  m "㚽X`l|?g}V0aBf]]]B -8٫j7[lM<9{_=6|=X*dkV~P/(y6,2eJ^My7}K۹;V[-{'7465okv d?op[3g1 'S4> V(.ZO5lE 2Ow_(-L4~NEyq 6WnPvs@e1jP);PrЌnQtܒK.8V>믿^yG:$y/3 G_s=yβɲw1obF>b絰y>dgX^gU}{}c\sL^z _B>94~߯=w |;A ^6ozf n~5ek_Pȼ`r$v?] X+޺ZSt.ǁmB3F";/H=hN0NSy||Υ3D\f1MzMSO1nܸq׿쓟d~Mc|ь_$=><^Xg TY'q[o͌Au]71Ao]Յ.x[ޒpV#ͼy[Y U:-R9>SO=2_ʟ<9+yΔymv*+6y$կYS UWЕ~>TR{VH+><_a4sC(+,T~TJ:_\s|~ 7z;Ne܏6oL2 LҦYJoR;G7[EW,K7#P6̠P=Ol~ 6 wCg@:v<ƌ .+Ffx L﯉fp$0 v;^:&Yϱ i,~̋yC*W_}u?O9S]9d5,U?2/)WN9 <ƥ8я~4Iq ͼ{ĉDޤ2yW2 m9M߬Zz(ׁl7<]v%i> /g+\X2gf`f/nmP]3mϩfe`eGqDvgd>ӸQ2$;r_=xohyf|N,j-_o/gY7pCEKvO:kWva̙y睗o|#[pgZ=g?q㱽 v3Oz;'k؍ffaCA7xB}8K)^~ f6Xj԰fh>/VGׁf_ ,f1\ܣ"b­):_K6jXkz$4/|](e-p 60v'Q,r3!W[|.n~0? rn"2[o]zywcwx̒atmr+Siuq,59U7ٽޛPS _ϟopkrb|yXi|ifOrh~N)} )&.?o0{^vqBV 9r璡7eO?=_Tai_Q-eD-=eo2Sz_*d妏-?~B7OW3Yl6 , 9T|l Z=@i$\?ȉzM"sm&|&|8%NB߇*@7bK@>4}fQՏm=v3ɨ@7}O]2<|$w#G >P\1ΗDk/c)_s . lM2ta]w~Yݷc}h9٘ /"clM7jNl߾chGuTf泰5Xc7Yf` =%S,d>rX\7"gd7tS_d4sTff<@IDATyۘrꪫ`Y>)\F<_fH1v(&kQu)'?Ł^{c|O"&OPg<8(8DT;5|rq_{/ Ƃ;¥83q=qFO9=?Uyb#>/Ano%Uw ,Svoeo3Adn[o˂c\Z r}d|1}>򹙫?+tjcrS\r?K3#<2W|Ѹwg8NLIuuV͖ .9۸YuVXaN|lͺC=t̂L Fy_P4| keg!Z.W^ye.fA^`և;s&a77}ݥBz)t,H]4zhΘ)ƊO<,o4{?Rk( 5Eo908sg c3+;P k N j=SY %E_f_sڠNUnݾ6LQjF7g7TPf "c;u=^cps,ի~+&;l- )^.$xBYbn1>21' ?e4fL] +Q> }6 JcHR .I`zp^ff u^@.A5hPa\Muʠ'NЬ߶(hd Sn|,۾֬.>Z[n 5]@y4=_2Vpvix? ,:ùA6mgqStc˻`{4x4s8T<ҙ}Y>A3نWh|5~2i?e~ Sc`)ЌmM#]Vw!m? |WFc~]Ŷ^a9-`(,3GAOQ^^|y / 6 Sf7 Ծ 6fHYµ"a]khs%0j{#@Q|N콿m? oT|.ʵ~'.>&~Xo9f׌mO5@ m(ڛp}'0ۓkk~p/`}wh0 #*6_=`qP'3.|1bzW_+fm^)*;WmSv"(.O x+] EA6_r?(?`90'HDa`FR\ښf[8$t ׶Y-5SIꠙ{fanj=3"qog;S@mVP:a "qY6X֭]:{l t_LNmm5 Xդԇ[5ȧSS*fN6zjяT~&8}ikN\[)K`8Ҹc>.z?Ņo߅v W3#1?&AgA1/ 6w|eO5]DykWS PP[jI~uA>w ~}fLD0̮w{+<KWg[?\( d ߝZr{* L]Gh334 >Ll\Z,R|/7h1Pqom`f (]fךx>>:K~g]0y*ˈ@ٲٝWf 7kl6=wx;u,jc~9Ad\TTwX>x>1:fJeЀߣ}o#J6Pے<'M}4;l߾^}*}z_ **\F,fX} y8Kn'eT-IGoUl3ϟ5X:'r;$~0c l،{ 3I}>{NY*7}2\jyrp)O3^g&4lxͅc\ֻ (܃aNM߁/(f#@h z0fu|{ 7.#6yݢLѠ )O} Veu.25`0g w_YI3/> \ V˩‚` \ǵALXke{p8o$0MH3 AzUHMi v<<l| _`r>Sl,{'0|&dx;x l\Nt2`.sr"x,k}P/K\3Sta@0 MҗOR,n~7IY"sзlx:iR(kq}8hWȩÂ/YkGʀϐb\2[p lif`7q,̀o3mc铙*`>^.'AX00d2jX9gHˊo#* Av@O wr~p'/3Hkׂ@X00p 'O\-(jK@;F㜙~֧/Ř֫*,ÀAUj5#~@0 @0 @0 @0 CqI6,` x-|//Y8 f(\!`#@0 WXG_p.@# F>d KN@002hEBF=`pu% g` y GC)~-jQ#gg62PzPZ` a@0 @{j/q` >0` 2^@0 }` ī!@0 e īك` @WHC` @W{@0 >@0 g` !^} - ` h/!^? @0B@Z @0^B=` x8$` x8{0 @iqH0 @{j/q` >0#0P)`J/^` `ؓEAj=` &苀p5Al4 ` XZ{0 @ 4#`!\-M` F58  @0,D!@0 @QB8[0 l0 u 1 ` ` ` ` ` ` ``30j07h ϙV˃`Y X.b ZF/;^/g (Ic2(mi>;Aۼ_Jr|/ plڱ9~^@t(X ƶeu\󀲱fcilK<غ)?l !^E߈?x, k5 , z.p'u_{ c[8.7 mU7846/sl cZ 846'?ɅBh9;@=`eBZ,9z`K σ"c]<~,_,AޫEg7kUfG&dz8NzL#q }ӊc3T01558v%̎ɟ8,[ [x~il^3uslך<0qF1,蝁ڛ#Hf'S9 PX0A2n.pe X 0Hb. dWE*IilRv\Imk^crKǶpl >g[hlv dS(x}Gc{x݊c{iluN tݼW*w2Q/y`)_F#` H .B`*-]`e Xh ;p=06csh}!PP46p'Hc리l[nrjWWilkRvlA\oDp;P<1csp ȥ5/vplnMaG6>'TM‚` )u`1;gكami`k0/̾shpX/a`;`4ЦP ( _o@\&PyJFqL>9'a80X ΀KYG~8l 4wf(_׀WA 'QVpSBqs_8).A*ZѠ6'jilP^ 42f6'awq18563-}q '_Spa jyy}AZV ^< p魞ˎzoN0>k=0kg s@rlfpGA1uApp"1`f l1GaX\*|(\Nq2P^K<.v^y_=[ilWP޺^6lL3MFCHχȅjkNyNNqH2j+ŷ5nCtRQv(glzp:\xꗓ@'N.Ʀꄫ>D\^A:kKncRFS<>:\lר 8)8M3ЗY\ӝGaɀ` u4pș-|`WS?12A7X  }4] C ,h0lL`0"Ɋ3P|X`Mp>|`ny)ʎO᚝׺K@A%j3F`oa@K xDW4*Pr@꒤cS,8z5cwpr.Ǘ& ga@S x32x zΞ \/ Jv@o T T/l* RfjO0 .`<l lkbp!lVP8f8c{86ǤՎm^ϱ)ȎmY~NBL17 \Uzcw][@r&&'c{8.]昼"]7%OV ((c'p񂄰 F2InV DLlf{(^1c֬$}p0@jpP8 ߙe`3946wG[ /3ů8[j`{p=p\^;3!6Uɼ cҼűɉr=Z 9vlN Ā7]X0 d`}6å%7k˗YS!p;0uKT4p|Z`p9865ke939=M_x5cs8{ݖkJfhel= =JA {)a@K xDW4.a|g;J`X, ^Ί̪3u3o"0Yvyjv2[36 0. &f@\:s|fil "Ǧ(1&yb @6”veT9g\cU86mX0'BDۈ=R=3D2q&o 4`)` s$ZI X+L 6"S&T74l(f"cxm>MEhW&?gl_ѹs)̋3.[IGcs|űu(pRs 1}"~B dDW fg`/f x p<` fLx.Dx:?=fW&T74gq`EAitbD5OWV pqM35,h';k=` C撟NJvblrgGچˠlSu.S4ͦH iM3ЗY\ӝGaBn2p, ~̼ٙ6(ctX0,Δ[SUQ0f T9l |0Pb._hx q (+- L$Da;Pi=Day/ <ڰ`)B)UP 3ٲ0>#Y (\p;Ъxx%0Q]><\ Y8Ц+Ձ'N˂Kَh4ׂ{c`2H3kLk'@^7y-XxABXs x5Sa MpPvˉǁ3.np7o?}AUSo'\rf*Y@Ax̮I8l3̿ÁנUSt0ؼnm1J}QqyV.}ii瀳@X04!^MS T .;Ձw(tOl33`lFY &XWħ\nزybx Z1v\4W);.}HS;6Ǖƶ0`<0;6EZt"`pMj!0ǵx,pl"M,q$]73߅X`{9pB/a@K xӅ}a fP73EƠh^3(K=e3-c*X`Vpx(nec0/ 7@옴cIcc{ @A\I,(ߕW5@/^7(nus #c4P{&8 }>2 z0dϩegߋ|?Gq\$07r7PR cSV73M

Um[}&~ْT ;3CYtw Zݮ+`˱ 2@L`npYo&yUK j ]>60 >.%p }@Pas&pRUg.4a "* =8a`FGFQoFDM>#EAqYwQ̝!E]]}~VչUƥw KY02I`[+տ$Pҷn-0&}WJp>|xb GC{h*>pTߜC^4: We<hh }+$o4Z@myH|a < m(̊ƬR'Vpooc4)FC/qݰk5h15G W1-B wa1`0`VIbf7 0?¡P[S]κ Gép"4XP_kE ֛ ҝ Cp%xjkrB5N c|uUm3kV[ gm<3%0NbJW9&[G79Zx"W޽'t+ 1&M')JVRX @1q &J؏`r7 }3vo@I?f1[B7ť;-/Ǖ?9]s?fPl)|󵾹Wp\q:E W–L"-`ٲM ъdT3 ~GA|Cy?0nU՚K} v ["mUlaD+:PRŧ5|)3L-hd, }ӿ}sb<΋8 [{Eo@>U%'!`&'+!ͤLj^Ek&2ݷ+`4,]*H(N,Tl&O5ɇحL+ظ&@wo[6i=7LQo_ }ح<ǡq EnAQ -M"*fL lTa[[2 ͿnWxBe75h4ĩi &4}{S!+d>TPDg9h; Md LзiŃߩav6FT׮ozG@; LSttE=n ax L~`R~ /@v720Kk*h ~p.zwJʹ +eLϥ=}oL; f+hk*P4hRFҨ8mdcB\037T"njehm"w, >4BnUe2 .Baz+t ?};c59lV+<)筈Um/7E 8@Sưo^l!+ɻ)O ŋۿ2&J d/x R^7Էn0ƀXHΕ}G|PBfƹ7BQo 2x΀NА}l"τ{O JEV<'߇+8-0hcYLga.4䒐Iեap2;BowpC6> hhx 6߫o`RVO~U )/lk'`E4/,ȸ\fᲔqic AuV&r&:m u2f7 ewsb\[uuފ@зXL` .)7ZoV.M}A! }< w-F/_YK4K-TZDu8{Q`xeLAȼ/M~`ӷayz?Ѫ:Zv{&q2lذ裏N.]}1[ouү_dwL_7k,޽{*$A Kq!zN;픜yIV׊o6;/N:7owLpۻw 92y7X2vN袋?<رcrW$/Nڷoe]6v!ѣG|%IΝ7.]$ٮ]m۶g[mU:o s60OgUY3t:8G+pJp1K>}#EEx}i[fڴiC= /VQnr'&o}Z̟??y뭷SO=5YbE3ϤUm'ɒ%Kӧ(VJ?={v*o͚5ɐ!CRO?ʕ+Ssm̙_"Yzur7&~x(|ƍΩ_裏믿>[;#ꫯ/߆̝;7[nIO1Yϥ&tj8dլgsurnwS!,;9&3u>bk*.&b"QF%ofҫW䪫Jf͚A~$-ZHnW^I+c9&N kʔ)ie1"N9唴rn7pC*FVZwygcm?>riBQ쓕,\0!W=Sa^̙3'iٲe2tд(^.nX'pBZqV[ׯON:餴2*P2t eyuS;bUż *[eM+'xyoȪ+gK8w}uQ>seK|{7}% /Z({>ԆwIڨM ZyY1E,#<.m`$O=TG.0Y%>qm3&ٳgzǻC(j<2d_=:*dƌˠ~SNM|>`zO>7ߜ.ѣG̸>$>hinXsQ Uu?lˆߏa/h @9K}s s[ep˷AwhMe.;Xm7|a~n9-F`@Hh<\*%=0aP[{"5(,_#@Ayf}]{uf+noC[7`}g#D)6g2^?w.Xl^:AGfL+ǘ d_XP莅s|~ 'YE9n8N˭ Y1UbB|ֲ7d8h1@LLSzٜ*,t14p9Do)$gv71`M ɬ^\t^H}>A_AhPo 7@A%b2(^=5ؖ"\A0L2̆3v +v`Qh$r[ͬg*KĂl y5|XUS9@!VK`5ot-FU6u[pk~&u {YvP W*k?+䫼>61M Ve]Av]%[+~m%ֲn7K"Z@#+V҄Y)SȬ$올M!mh-$}3 ׂ˅(P`.潶~Ucr}ʿ¹*߇`wPKLFO&''U2[ph7rR(ǂӶ6&pG --q>&P¨J֚] ȾuALF W&OK'% AP\TSKoO`bXgwu1a$\ VSMaV>ކAa#F \ KO͑[зb|Y!~.a0ED\* 湜ˡ:! =}bv(օ9-F 3WfNE&V\!1V:wPLuXjk8eo~p ŭ1-փAφo\cw#l -]v_׃50.zVB mV$Zqo .wOem[`\ iLF V^<-5N \˿3·arilcSN8ͣ(b^3 α.q+2= Õp m: zCwp `P>/A0G@F>mY0vsJL l?00H0حB0;؟\u|ʧ3{6l.L10\t>U_s.`*A ( ŠoB*VJf_{ue?`(oxcٟs#TBDhPM`ȨIߊ"$`pDl0P&b̂@ꋏl nPxi/ۃ`7 xm 9~CFb*JWg{(V+jCZw`gpe$^h @k䃘x0ha4U,(f_]-y71N~B~8i0bCz IDAT2(^?E_ {MLAR^PU<>`"mw8V|@ƺ+J̈́r} ~-6|8jo~nMEOfge@nVV Xu9i }*f}X?rӪܹVڷ*T^$| V!Z@&#+%N*6l aZެa߀UPbV=a14DGЇ?ap2!Z@@@9F T&Sh-u SV|@Ĥ<ˡ:E[VKeeoGDpY4Ӌs Z@f"T 3t0;@/cLeepV+Ojk> 4EJp*8\" b2\ 2`\»khkV}VMOep;*6VjmUPC~??`}x*1Z@&#+L8| U<.aY³=ӆ1j?>n Pl(a nd+HL8tX @}L | 0cLF W&OKTAL&WDo`=M2{BXVE)2Ah\q+v̷hnO:} /10E3xހEpnsqYVo]׷v0<^ އ?-F S'Vj+$PI6Iz/0zLu8${bx頨I]i| }[~|n`Tou gX.]nT>Q*cFiKo.YMXqmcg2k ƊCRLLa>o (*o$w*?[hYS@l@z,h1@̟/Dd'LV V< >ް&B6|ىip%(jVw_1YY)F̹9B:>U8'y(b2(^<-_IYqXYi+䋓8o F%#~9uМKufh(D=vkJ-[}t ELxeTĉ#0K&O0\l s)1PCm5LJ3\sOh [bEp29z`RB1LR\"J9ƁKmѹBu(V2>Ta }t E9*WASŞlؓi> ^+`FccDx=Ps2;lj1nl62.5) ]ZA$L>~A[hh 4C3p+ 10  Voa`%k߬ G~Po4#8h8Qbjm8+P7_+YrkLu1?  h. t~3##X \sn\ #Hy+&S9u1H9ߌoNbA#/k&'# ph 3^jX?`k[)Q4L޻נ[c([s0ϡo p> H0r)*K@a m7m޿ ua+8^KYfU9-F ;╝sgR:wש0:BU_<$RJH&p~ݍ߂0<kaJ}}s} Ms)(.Z* fFىƉ+V`җ~0߯Ẏip +!Zt: Jkoa&=,. # # SPDX-License-Identifier: GPL-2.0-or-later CP := cp RM := rm -f MKDIR := mkdir -pv -m 755 MAN_DIR := /usr/share/man/man1 .PHONY: all all: @echo Nothing to do. .PHONY: install install: all @echo Installing rapiddisk man pages. $(MKDIR) $(DESTDIR)$(MAN_DIR) $(CP) rapiddisk*.1 $(DESTDIR)$(MAN_DIR)/ .PHONY: uninstall uninstall: @echo Uninstalling rapiddisk man pages. $(RM) $(DESTDIR)$(MAN_DIR)/rapiddisk.1 $(RM) $(DESTDIR)$(MAN_DIR)/rapiddiskd.1 .PHONY: clean install-strip debug tools-strip tools-debug tools-uninstall tools-install-strip tools-install clean-tools tools dkms-uninstall dkms-install clean install-strip debug tools-strip tools-debug tools-uninstall tools-install-strip tools-install clean-tools tools dkms-uninstall dkms-install: rapiddisk-9.1.0/doc/cache-stats.txt000066400000000000000000000040341442147535600172140ustar00rootroot00000000000000RAPIDDISK-CACHE STATISTICS --------------------------- *** Normal output *** 0 78156289 rapiddisk-cache stats: reads(1108), writes(297349) cache hits(699), replacement(134), write replacement(146086) read invalidates(2), write invalidates(34) uncached reads(7), uncached writes(13328) disk reads(409), disk writes(297349) cache reads(699), cache writes(284423) *** JSON output *** { "statistics": [ { "cache_stats": [ { "device": "rc-wt_sdf", "reads": 1108, "writes": 297349, "cache_hits": 699, "replacement": 134, "write_replacement": 146086, "read_invalidates": 2, "write_invalidates": 34, "uncached_reads": 7, "uncached_writes": 13328, "disk_reads": 409, "disk_writes": 297349, "cache_reads": 699, "cache_writes": 284423 } ] } ] } device: RapidDisk-Cache device name. reads: Number of total reads to the RapidDisk-Cache front-node device. That means total reads that will hit both the cache and backing store devices. writes: Number of total writes to the RapidDisk-Cache front-node device. That means total writes that will hit both the cache and backing store devices. cache_hits: Number of read/write operations that hit the cache device. replacement: Follows a cache miss, where blocks are replaced into the cache device. write_replacement: Writes replaced out of the cache. Will be "0" in WA mode. read_invalidates: Invalidated overlapping read cache blocks. write_invalidates: Invalidated overlapping write cache blocks. uncached_reads: Backing store reads that were not cached. uncached_writes: Backing store writes that were not cached. disk_reads: Reads from the backing store device. disk_writes: Writes to the backing store device. cache_reads: Reads from the cache device. cache_writes: Writes to the cache device. * For details on dm-writecache stats, please refer to https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/writecache.html. rapiddisk-9.1.0/doc/rapiddisk.1000077500000000000000000000123531442147535600163160ustar00rootroot00000000000000.TH RAPIDDISK 1 "Oct 16 2010" "Linux" "GENERAL COMMANDS" .SH NAME rapiddisk \- An administration tool to manage the RapidDisk RAM disk devices and RapidDisk-Cache mappings. .SH SYNOPSIS rapiddisk [ -h | -v ] function [ parameters ] .SH DESCRIPTION rapiddisk is a RapidDisk module management tool to manage RapidDisk RAM disk devices. Dynamically create, remove, resize RAM volumes and if desired, map or unmap them as a cache volume to any block device. Access those drives locally or export those volumes across an NVMe Target network. .SS Options .TP -h Invoke the help menu. .TP -v Display the version number. .SS Functions .TP -a Attach RAM disk device (size in MBytes). .TP -b Backend block device absolute path (for cache mapping). .TP -c Input capacity for size or resize of RAM disk device (in MBytes). .TP -d Detach RAM disk device. .TP -e Export a RapidDisk block device as an NVMe Target. .TP -f Erase all data to a specified RapidDisk device (dangerous). .TP -g Do not print header. .TP -H The host to export / unexport the NVMe Target to / from. .TP -i Define the network interface to enable for NVMe Target exporting. Port must not already exist and interface must not be already enabled. .TP -j Enable JSON formatted output. .TP -L Lock a RapidDisk block device (set to read-only). .TP -l List all attached RAM disk devices. .TP -m Map an RapidDisk device as a caching node to another block device. .TP -N List only enabled NVMe Target ports. .TP -n List RapidDisk enabled NVMe Target exports. .TP -P The port to export / unexport the NVMe Target to / from. .TP -p Define cache policy: write-through (wt), write-around (wa) or writeback (wb) (dangerous) (default: write-through). Writeback caching is supplied by the dm-writecache kernel module and is not intended for production use as it may result in data loss on hardware/power failure. .TP -q List all system memory and block device resources. .TP -R Revalidate size of NVMe export using existing RapidDisk device. .TP -r Dynamically grow the size of an existing RapidDisk device. .TP -s Obtain RapidDisk-Cache Mappings statistics. .TP -t Define the NVMe Target port's transfer protocol (i.e. tcp, rdma or loop). .TP -U Unlock a RapidDisk block device (set to read-write). .TP -u Unmap a RapidDisk device from another block device. .TP -X Remove the NVMe Target port (must be unused). .TP -x Unexport a RapidDisk block device from an NVMe Target. To remove export to host or port, only define the host and / or port. Not defining a host or port will result in the block device being removed from the NVMe Target subsystem. .SS Parameters (if applicable) .TP [size] Specify desired size of attaching RAM disk device in MBytes. .TP [mode] Write Through (wt) or Write Around (wa) for cache. .SH EXAMPLE USAGE .TP rapiddisk -l .TP rapiddisk -l -j .TP rapiddisk -a 64 .TP rapiddisk -d rd2 .TP rapiddisk -r rd2 -c 128 .TP rapiddisk -m rd1 -b /dev/sdb .TP rapiddisk -m rd1 -b /dev/sdb -p wt .TP rapiddisk -m rd3 -b /dev/mapper/rc-wa_sdb -p wb .TP rapiddisk -u rc-wt_sdb .TP rapiddisk -s rc-wt_sdb .TP rapiddisk -f rd2 .TP rapiddisk -L rd2 .TP rapiddisk -U rd3 .TP rapiddisk -i eth0 -P 1 -t tcp .TP rapiddisk -i NULL -P 1 -t loop .TP rapiddisk -X -P 1 .TP rapiddisk -e -b rd3 -P 1 -H nqn.host1 .TP rapiddisk -R -b rd0 .TP rapiddisk -x -b rd3 -P 1 -H nqn.host1 .SH MANAGING RAPIDDISK AS AN NVME TARGET There are a few things that need to be known when using the NVMe Target features of the RapidDisk suite. .TP 1. In order to map any RapidDisk device and export it in the NVMe Target framework, the nvmet and the nvmet-tcp or nvmet-rdma kernel modules must be inserted. .TP 2. At least one Ethernet interface will need to be configured as a target port to export the RapidDisk volume from. .RS .P ex) rapiddisk -i eth -P 1 -t tcp .RE .TP 3. When exporting a volume, a RapidDisk volume and a target port must be defined. If a host NQN is not defined, the administration utility will provide access to any host NQN. Note - a target can be exported across more than one target port. .RS .P ex) rapiddisk -e -b rd3 -P 1 .P If a host NQN is defined, access is restricted to only those host NQNs. Note - the following command example can be repeated multiple times to add additional host NQNs for the specified target export. .P ex) rapiddisk -e -b rd3 -P 1 -H nqn.host1 .RE .TP 4. Unexporting RapidDisk volumes looks a bit different than exporting. If a host NQN is defined for a specified target, only that NQN will be removed from accessing the exported target. .RS .P ex) rapiddisk -x -b rd3 -H nqn.host1 .P Removing all allowed host NQNs will revert access to any and all host NQNs requesting access to the target. .P If a target port is defined, the exported target will not be exported from the interface if one condition is met: the target has no defined allowed host NQNs. .P ex) rapiddisk -x -b rd3 -P 1 -H nqn.host1 .P ex) rapiddisk -x -b rd3 -P 1 .P And if there are no defined allowed host NQNs and the target is not being exported across any target ports, the entire target is removed from the subsystem. .RE .SH EXIT STATUS rapiddisk returns a zero exit status if no error occurs during operation. A non-zero value is returned on error. .SH AUTHORS Original version: Petros Koutoupis (petros@petroskoutoupis.com) .SH SEE ALSO View the RapidDisk project page: http://www.rapiddisk.org rapiddisk-9.1.0/doc/rapiddiskd.1000077500000000000000000000015101442147535600164530ustar00rootroot00000000000000.TH RAPIDDISK 1 "Sep 27 2020" "Linux" "GENERAL COMMANDS" .SH NAME rapiddiskd \- A daemon intended to listen for API requests. .SH SYNOPSIS rapiddiskd [ -h | -v ] [ options ] .SH DESCRIPTION rapiddiskd is a daemon intended to listen for API requests to manage RapidDisk functions. .SS Options .TP -d Remain in the foreground (implies -V). .TP -h Invoke the help menu. .TP -p Change port to listen on (default: 9118). .TP -V Enable debug messages to stderr (this is ugly). .TP -v Display the version number. .SH EXAMPLE USAGE .TP rapiddiskd -p 8888 .TP rapiddisk -V .SH EXIT STATUS rapiddisk returns a zero exit status if no error occurs during operation. A non-zero value is returned on error. .SH AUTHORS Original version: Petros Koutoupis (petros@petroskoutoupis.com) .SH SEE ALSO View the RapidDisk project page: http://www.rapiddisk.org rapiddisk-9.1.0/logo.png000066400000000000000000000163061442147535600151620ustar00rootroot00000000000000PNG  IHDR^j9zTXtRaw profile type exifx[( EY,,ꚞ6a2HBGrf=_H85ϩqi~_6Ό!ճ!r?tk#c=yGۋ2΋E=8G0$t(Gu,g:>qau*W`9#n}9*? }| xIܽ^ʺY*FEHt7 \NlQW4b _9I7Ix4Hs7\όoS%-yqj‘9 {[Bs܆V2ʥu-^h 1MRTi{ҨGlYMt]&钁!UVBneXLPF;,Us]"} ~J{ȭ@ ?{BÜq[e#d3w{Rmpb"1Jى @bFYdN$Ato9 aH,UCfP515dnي$I*,%Os[]\<{5Kl9e9\  ʱRJYPBr%^v˯|6Of-5oV;w㞺KRi0Gu"צL6gIP}jIu=/P+/Hh^b&U^Xc8iMX)Al~B60'%n]+QP}x?r\ׯh Q/_a}?iCCPICC profilex}=H@_ӊ"P,Jq*BZu0 4$).kŪ "%/)=B4+0hmq1]_BALf1'IIt_.ʳ:s9>xMA۴ aVUsq.Hu76өy0XhcYԈ#SXY+WYuH`K BA%a#JN;\D.\%0r, ZI/)^c5>v Rf>I.[\Olʮ)}Sz׼ޚ8}U88 ={r6bKGD!? pHYs  tIME"tEXtCommentCreated with GIMPWIDATxy]U}o̒$IbB R&-eXR*PhkW*KTZM6!$,1,!Nf&y?~{ovo0 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 (pri50Q05;X,>j".>OY\z}:X<6<6fD`XSG_x&w00R8`"0Aa h}ǀf+`~w=X < Rv`XX}&x #y'݉gFa*0.TXZ ߻k`^ZP]|Wnomns L?^ T}>]rp3i> Aq O9~&U٨-r+?_.窴A, 8j 2KEC/YU78s̶3k$T3~; 108Ժsu?GN ?;m~]VSlgHN7RO]ö[5Y݀& (ߴ3k$Ks={몪B;}}&xd"pq|cEU ,V|buMU|^6$XƋ^4L ɴ:7u/ڣ _Ϙ5kFlAdڎn2kF<mڭ ^0N5`QEoo##ފ 9I5W3|:5C۬LeYܨ%Dc amHX~cGfHb!wm1ޅؠ#H65i +xt!H DsEN\#Pooy5 /0 tn6oDA˹W#Re06L/o6h{{um~ZIFޥ4j$nF*rRku) Ff"g=LIٻx>_ƶ#IgTp2%.>|$ȗ_ bHyH}zS$g#z9*Rn)E-{Ft'0}wk'܏0>@k(/v܅D~2q2EpB!T NCqO 3f~܉ EԦhO0GL$te=k?X@.k5&jgehkNw n` "zOcKt*87C ݨ88ءWș]jūT>9S "ȃlI$[X Ev洑QਔuU(2X|diR~kKWy uH|C{-ۅBN+{U#iG{B|g0SF'M!_aJ~o>9pQ)m-pgr[bpN<##IZt%=2.NQS*~/CF-g)EA哺ݝ-"NfU 6# %.C"v!K[t!oVf 6ܝ@ʤ[$&E -=YJOe[ |0v﫫O1#UR \W=Ѣ[{bn02 <\'S}L5ޠC߽8[O`Hm?v^ul"ތطG˝I~~.UfTδi"l1^w6ZEtϡYQo5Q࿋^`iV|fL!no &5Ǒٙp FGFR$ R0xgߡ}*gTɼiQ օ5#۵<˭HI辉ؾ(kj[s4#z}ir+^H*#sVB#L 3pP".gW$,v.ӫhxaaH4!޻Uב%XmM:Ʌ RP z6M$N3+S1EqQkWX}:^$;_FrTG,Dra͈ہǢԂ߮@}Nt2L> |t%>)4(Iyϙ,nh4H/>^՘Zǻ9` Iq_D 8Bm;T? 6Fˁ9/yPeʇo ׾XLP(n-+I}&A=rU ?Rt;[Iz҃ػdx]N"}Qn^R+7Kp diq'q/sh$AwOqT/!=.u w;ChL5z5#n@'Do q^VTvP8X׸"Bd $݈cw: V.wr  ! t\?P-wn!&mAl y{J/FKO 6CZ Ë,I!T&@yb;9с; qI}oE\㮧1 iD{%\4G\œ 6T`K78`ޙ,Q{| !߫iD"UA$Rᶔj+=vjd9j &8!l2x=Ǥ5fA:@Rn ޵Hv\5ꐄG G_Z')F# eIFa)y!j/9iόS<sѱ0l؂Q?9MSUQ+@HIc` $@ ^jᾺP,G$'y9E{Z9zRdl$,v?Pv[ +hC6wṠa*NGZ  1/C'Qo-p HHI]$Id hR+lg_(<e_#?7|Ii "ipî/ W Hۮ3T\a1GWo"R< t^C`@G#UΊ1i .߽gRK>74Wc=؜эdxJMH&yb3ˀ#9m?=,giԋ2b;G1`Puï-'\8㪙zO&}\󮒂wUXx jZFIyIxQjf%!Uojsń]T_>ӸAɞt^^$u1:É4yrzPOUˑ8 poނTyq=E\%-ϧnKVSҿب]M# GUm 0l4oTw9"3B G՚7cpc*hW-`$Bצ0$vB.v&륺q.J׬E/6}I(p ^=5p% ɇ{k${I\h>-@ẅ_|}TN `jXNV!ۧ+znVכzlGĭ|M4΄soɢ ⌶k[/!C94}PPyp,@J2MR!V( e.o"_R9ۖXhc6=@ zWރw+tn(2aTނ[ zs`s/8q EVw·N}flvBʐ DsSk@\\ 惧&f@J`["x vֳڥ| Qw wa=hqE' iSFNKzUjyl1 \ 2 (ra&x 0La&x 0La&x 0La.0 0ka0 0[i2_3 HUjbeOvLL0fg0ޱ_搌c/Lw0 0 0 0 0 0 #A#~FIENDB`rapiddisk-9.1.0/logo_small.png000066400000000000000000000057041442147535600163520ustar00rootroot00000000000000PNG  IHDR<tlbKGD pHYs  tIME (qbtEXtCommentCreated with GIMPW ,IDATx{WU?\ޗx%BTDC "0{h3jbd2Xc YL$$j $sA\z/…{׏wٿs.;sg^kBP( EF'(GWӬ/hT`=:,f<0Xl#1xK}:p nkm@]A}?]H``򾥠5;&bc^Om3GY,B9:,3֫@weh.cc0?ǁofKTEiÁkDp6?kXꟶRPm.%'ޙ<6?KMWyB [>m.%s[i?%TN u\f.mk<[ܤͤPxfʷο๾mls:1+ %BHM0'}{uZ`+ xhH`p1>G')q;!eR٦|!f0f;8&nwi_3#ۀ0hĵڳn10fRO 4_DD`A/гQi`7X.h#k'ʴ/i[. Euأ[pco\[#4B!:_(!ǁ1sw!$ +Odd ~}l!/(cīN&+?$a)ϹI#go#rE{<Q Z4m%PwBH,$un1^Fx)A+_Ge/x@V\}dS.UB"9GkҘu&|r`2L:O̺ `|^F\|+ĿVT$-r. ?~Vĥ=EF_0MjB ĘUbp,uf_1I8?),vT끅Rblk1^#s]& a\+¥-7I*KhHLm>2Qt FȻ'D Y}eJm9ȏ5C^S$ARKٷ^oYMf#ݚkTvC^<~v2LI >#VĈkh2mɁ!v>q:GXuEWK#"ϧ<շRT |W:fZ^"B; 3ԑy J!.cԍ ħo&81J䜃lqX3rIɘExSf/ǜ?~)Rm(Ƶ{vzf\{iWj1 Ql(f˺+;Rsig"dA%1{E遱rXjvQr2.E#Ʋ9c꠼0Ky_@1" =h7l2ԁ:ZqO<#l <$8fϐvBL~;\`F9!3B [dxûSkyszla 9Uuڻ:S٢I`)M~G3si~ᣳ³!`wu~avm>1Uo[ &ӱrQ:9N簮6xWкHWnsC.wou y=VY5qZ}[$i}؅18 ynXB]]p $|[??b,Oq:j鸏7[fQ6^I8N-UAbs|fzgc"}Σew˿)bH7-] +$ {c#{#~=~O4K=>9h~;jR[I#:Lhr-;5'8{<gFI͘uoXUgh:oa\jV@S(1nom0>.]x=-D,f!0kJ!X630w]T1R$8< |oGOb%9S#:bR Wksl.og%a'CT2=qZq=[LOiqĺ'qh2͸ufv)uWHY8tqHG{(ZhMSq&"Zo7?q/ޟYOۍE b}"!fC 5,)c(7=3m96k"9 Wg!U`* Kc9udY`C&a\*sK-f_5յ Fmw0 ̙I4S#Tَ0!ĦbnkĞui$blƚa9-(Nj]w$x {ٍ QalH#nOv1K3Kք0+E@+7 lEcjAo)o7 ,EnEɟT~աB@P+ %BP+ %BP+ %B#ڵ5b #t_P( BP(…IJrIENDB`rapiddisk-9.1.0/module/000077500000000000000000000000001442147535600147735ustar00rootroot00000000000000rapiddisk-9.1.0/module/Makefile000066400000000000000000000053551442147535600164430ustar00rootroot00000000000000# Copyright © 2011 - 2023 Petros Koutoupis # All rights reserved. # # 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; under 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, see . # # SPDX-License-Identifier: GPL-2.0-only VERSION = 9.1.0 ifeq ($(KSRC),) KSRC := /lib/modules/$(shell uname -r)/build endif ifeq ($(KVER),) KVER := $(shell uname -r) endif MKDIR := mkdir -pv CP := cp -v DKMSFILES := rapiddisk.c rapiddisk-cache.c dkms.conf Makefile DKMSDEST := /usr/src/rapiddisk-$(VERSION) obj-m += rapiddisk.o obj-m += rapiddisk-cache.o all: $(MAKE) -C $(KSRC) M=$(CURDIR) install: all $(MKDIR) $(DESTDIR)/lib/modules/$(KVER)/kernel/drivers/block/ install -o root -g root -m 0755 rapiddisk.ko $(DESTDIR)/lib/modules/$(KVER)/kernel/drivers/block/ install -o root -g root -m 0755 rapiddisk-cache.ko $(DESTDIR)/lib/modules/$(KVER)/kernel/drivers/block/ depmod -a uninstall: rm -f $(DESTDIR)/lib/modules/$(KVER)/kernel/drivers/block/rapiddisk.ko rm -f $(DESTDIR)/lib/modules/$(KVER)/kernel/drivers/block/rapiddisk-cache.ko depmod -a clean: rm -rf *.o *.ko *.symvers *.mod.c .*.cmd Module.markers modules.order *.o.* built-in* rm -rf .tmp_versions .rapiddisk.o.d .rapiddisk-cache.o.d *.unsigned *.sdtinfo.c .ctf/ .cache.mk *.mod dkms-install: clean uninstall ifeq ($(shell which dkms >/dev/null 2>/dev/null; echo $$?),1) $(error No 'dkms' command found) else ifeq ($(shell dkms status rapiddisk/$(VERSION) -k $(KVER) | grep '$(KVER)' >/dev/null 2>/dev/null; echo $$?),0) $(error rapiddisk version $(VERSION) is already installed for kernel $(KVER). Use 'make dkms-uninstall' first) else dkms add . || true dkms install rapiddisk/$(VERSION) -k $(KVER) endif dkms-uninstall: clean uninstall ifeq ($(shell which dkms >/dev/null 2>/dev/null; echo $$?),1) $(error No 'dkms' command found) else ifeq ($(shell dkms status rapiddisk/$(VERSION) -k $(KVER) | grep '$(KVER)' >/dev/null 2>/dev/null; echo $$?),0) dkms remove rapiddisk/$(VERSION) -k $(KVER) else $(error rapiddisk version $(VERSION) is not installed for kernel $(KVER)) endif .PHONY: test run-test: all .PHONY: install-strip debug tools-strip tools-debug tools-uninstall tools-install-strip tools-install clean-tools tools install-strip debug tools-strip tools-debug tools-uninstall tools-install-strip tools-install clean-tools tools: rapiddisk-9.1.0/module/dkms.conf000066400000000000000000000003711442147535600166010ustar00rootroot00000000000000PACKAGE_NAME="rapiddisk" PACKAGE_VERSION="9.1.0" BUILT_MODULE_NAME[0]="rapiddisk" BUILT_MODULE_NAME[1]="rapiddisk-cache" DEST_MODULE_LOCATION[0]="/kernel/rapiddisk/" DEST_MODULE_LOCATION[1]="/kernel/rapiddisk/" AUTOINSTALL="yes" REMAKE_INITRD="yes" rapiddisk-9.1.0/module/rapiddisk-cache.c000066400000000000000000001061771442147535600201660ustar00rootroot00000000000000/******************************************************************************* ** Copyright © 2011 - 2023 Petros Koutoupis ** All rights reserved. ** ** 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; under 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, see . ** ** SPDX-License-Identifier: GPL-2.0-only ** ** filename: rapiddisk-cache.c ** description: Device mapper target for block-level disk write-through and ** write-around caching. This module is based on Flashcache-wt: ** Copyright 2010 Facebook, Inc. ** Author: Mohan Srinivasan (mohan@facebook.com) ** ** Which in turn was based on DM-Cache: ** Copyright (C) International Business Machines Corp., 2006 ** Author: Ming Zhao (mingzhao@ufl.edu) ** ** created: 3Dec11, petros@petroskoutoupis.com ** ******************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ASSERT(x) do { \ if (unlikely(!(x))) { \ dump_stack(); \ panic("ASSERT: assertion (%s) failed at %s (%d)\n", \ #x, __FILE__, __LINE__); \ } \ } while (0) #define VERSION_STR "9.1.0" #define DM_MSG_PREFIX "rapiddisk-cache" #define READCACHE 1 #define WRITECACHE 2 #define READSOURCE 3 #define WRITESOURCE 4 #define READCACHE_DONE 5 #define WRITETHROUGH 0 #define WRITEAROUND 1 #define GENERIC_ERROR -1 #define BYTES_PER_BLOCK 512 /* Default cache parameters */ #define DEFAULT_CACHE_ASSOC 512 #define CACHE_BLOCK_SIZE (PAGE_SIZE / BYTES_PER_BLOCK) #define CONSECUTIVE_BLOCKS 512 /* States of a cache block */ #define INVALID 0 #define VALID 1 #define INPROG 2 /* IO (cache fill) is in progress */ #define CACHEREADINPROG 3 #define INPROG_INVALID 4 /* Write invalidated during a refill */ #define DEV_PATHLEN 128 #ifndef DM_MAPIO_SUBMITTED #define DM_MAPIO_SUBMITTED 0 #endif #define WT_MIN_JOBS 1024 /* Number of pages for I/O */ #if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,39) #define COPY_PAGES (1024) #endif /* Cache context */ struct cache_context { struct dm_target *tgt; struct dm_dev *disk_dev; /* Source device */ struct dm_dev *cache_dev; /* Cache device */ spinlock_t cache_spin_lock; struct cache_block *cache; u8 *cache_state; u32 *set_lru_next; int mode; /* Write Through / Around */ struct dm_io_client *io_client; sector_t size; unsigned int assoc; unsigned int block_size; unsigned int block_shift; unsigned int block_mask; unsigned int consecutive_shift; wait_queue_head_t destroyq; /* Wait queue for I/O completion */ atomic_t nr_jobs; /* Number of I/O jobs */ /* Stats */ unsigned long reads; unsigned long writes; unsigned long cache_hits; unsigned long replace; unsigned long wr_invalidates; unsigned long rd_invalidates; unsigned long cached_blocks; unsigned long cache_wr_replace; unsigned long uncached_reads; unsigned long uncached_writes; unsigned long cache_reads, cache_writes; unsigned long disk_reads, disk_writes; char cache_devname[DEV_PATHLEN]; char disk_devname[DEV_PATHLEN]; }; /* Cache block metadata structure */ struct cache_block { sector_t dbn; /* Sector number of the cached block */ }; /* Structure for a kcached job */ struct kcached_job { struct list_head list; struct cache_context *dmc; struct bio *bio; /* Original bio */ struct dm_io_region disk; struct dm_io_region cache; int index; int rw; int error; }; static struct workqueue_struct *kcached_wq; static struct work_struct kcached_work; static struct kmem_cache *job_cache; static mempool_t *job_pool; static DEFINE_SPINLOCK(job_lock); static LIST_HEAD(complete_jobs); static LIST_HEAD(io_jobs); static void cache_read_miss(struct cache_context *, struct bio *, int); static void cache_write(struct cache_context *, struct bio *); static int cache_invalidate_blocks(struct cache_context *, struct bio *); static void rc_uncached_io_callback(unsigned long, void *); static void rc_start_uncached_io(struct cache_context *, struct bio *); int dm_io_async_bvec(unsigned int num_regions, struct dm_io_region *where, #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0) int rw, struct bio *bio, io_notify_fn fn, void *context) #else int rw, struct bio_vec *bvec, io_notify_fn fn, void *context) #endif { struct kcached_job *job = (struct kcached_job *)context; struct cache_context *dmc = job->dmc; struct dm_io_request iorq; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,8,0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6,0,0) (rw == WRITE) ? (iorq.bi_opf = REQ_OP_WRITE) : (iorq.bi_opf = REQ_OP_READ); #else iorq.bi_op = rw; iorq.bi_op_flags = 0; #endif #else iorq.bi_rw = rw; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0) iorq.mem.type = DM_IO_BIO; iorq.mem.ptr.bio = bio; #else iorq.mem.type = DM_IO_BVEC; iorq.mem.ptr.bvec = bvec; #endif iorq.notify.fn = fn; iorq.notify.context = context; iorq.client = dmc->io_client; return dm_io(&iorq, num_regions, where, NULL); } static int jobs_init(void) { job_cache = kmem_cache_create("kcached-jobs-wt", sizeof(struct kcached_job), __alignof__(struct kcached_job), 0, NULL); if (!job_cache) return -ENOMEM; job_pool = mempool_create(WT_MIN_JOBS, mempool_alloc_slab, mempool_free_slab, job_cache); if (!job_pool) { kmem_cache_destroy(job_cache); return -ENOMEM; } return 0; } static void jobs_exit(void) { BUG_ON(!list_empty(&complete_jobs)); BUG_ON(!list_empty(&io_jobs)); mempool_destroy(job_pool); kmem_cache_destroy(job_cache); job_pool = NULL; job_cache = NULL; } static inline struct kcached_job *pop(struct list_head *jobs) { struct kcached_job *job = NULL; unsigned long flags; spin_lock_irqsave(&job_lock, flags); if (!list_empty(jobs)) { job = list_entry(jobs->next, struct kcached_job, list); list_del(&job->list); } spin_unlock_irqrestore(&job_lock, flags); return job; } static inline void push(struct list_head *jobs, struct kcached_job *job) { unsigned long flags; spin_lock_irqsave(&job_lock, flags); list_add_tail(&job->list, jobs); spin_unlock_irqrestore(&job_lock, flags); } void rc_io_callback(unsigned long error, void *context) { struct kcached_job *job = (struct kcached_job *)context; struct cache_context *dmc = job->dmc; struct bio *bio; unsigned long flags; int invalid = 0; ASSERT(job); bio = job->bio; ASSERT(bio); if (error) DMERR("%s: io error %ld", __func__, error); if (job->rw == READSOURCE || job->rw == WRITESOURCE) { spin_lock_irqsave(&dmc->cache_spin_lock, flags); if (dmc->cache_state[job->index] != INPROG) { ASSERT(dmc->cache_state[job->index] == INPROG_INVALID); invalid++; } spin_unlock_irqrestore(&dmc->cache_spin_lock, flags); if (error || invalid) { if (invalid) DMERR("%s: cache fill invalidation, sector %lu, size %u", #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0) __func__, (unsigned long)bio->bi_iter.bi_sector, bio->bi_iter.bi_size); #else __func__, (unsigned long)bio->bi_sector, bio->bi_size); #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(4,3,0) bio_endio(bio, error); #else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,13,0) bio->bi_status= error; #else bio->bi_error = error; #endif bio_io_error(bio); #endif spin_lock_irqsave(&dmc->cache_spin_lock, flags); dmc->cache_state[job->index] = INVALID; spin_unlock_irqrestore(&dmc->cache_spin_lock, flags); goto out; } else { job->rw = WRITECACHE; push(&io_jobs, job); queue_work(kcached_wq, &kcached_work); return; } } else if (job->rw == READCACHE) { spin_lock_irqsave(&dmc->cache_spin_lock, flags); ASSERT(dmc->cache_state[job->index] == INPROG_INVALID || dmc->cache_state[job->index] == CACHEREADINPROG); if (dmc->cache_state[job->index] == INPROG_INVALID) invalid++; spin_unlock_irqrestore(&dmc->cache_spin_lock, flags); if (!invalid && !error) { #if LINUX_VERSION_CODE < KERNEL_VERSION(4,3,0) bio_endio(bio, 0); #else bio_endio(bio); #endif spin_lock_irqsave(&dmc->cache_spin_lock, flags); dmc->cache_state[job->index] = VALID; spin_unlock_irqrestore(&dmc->cache_spin_lock, flags); goto out; } /* error || invalid || bounce back to source device */ job->rw = READCACHE_DONE; push(&complete_jobs, job); queue_work(kcached_wq, &kcached_work); return; } else { ASSERT(job->rw == WRITECACHE); #if LINUX_VERSION_CODE < KERNEL_VERSION(4,3,0) bio_endio(bio, 0); #else bio_endio(bio); #endif spin_lock_irqsave(&dmc->cache_spin_lock, flags); ASSERT((dmc->cache_state[job->index] == INPROG) || (dmc->cache_state[job->index] == INPROG_INVALID)); if (error || dmc->cache_state[job->index] == INPROG_INVALID) { dmc->cache_state[job->index] = INVALID; } else { dmc->cache_state[job->index] = VALID; dmc->cached_blocks++; } spin_unlock_irqrestore(&dmc->cache_spin_lock, flags); } out: mempool_free(job, job_pool); if (atomic_dec_and_test(&dmc->nr_jobs)) wake_up(&dmc->destroyq); } EXPORT_SYMBOL(rc_io_callback); static int do_io(struct kcached_job *job) { int r = 0; struct cache_context *dmc = job->dmc; struct bio *bio = job->bio; ASSERT(job->rw == WRITECACHE); dmc->cache_writes++; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0) r = dm_io_async_bvec(1, &job->cache, WRITE, bio, rc_io_callback, job); #else r = dm_io_async_bvec(1, &job->cache, WRITE, bio->bi_io_vec + bio->bi_idx, rc_io_callback, job); #endif ASSERT(r == 0); /* dm_io_async_bvec() must always return 0 */ return r; } int rc_do_complete(struct kcached_job *job) { struct bio *bio = job->bio; struct cache_context *dmc = job->dmc; unsigned long flags; ASSERT(job->rw == READCACHE_DONE); /* error || block invalidated while reading from cache */ spin_lock_irqsave(&dmc->cache_spin_lock, flags); dmc->cache_state[job->index] = INVALID; spin_unlock_irqrestore(&dmc->cache_spin_lock, flags); mempool_free(job, job_pool); if (atomic_dec_and_test(&dmc->nr_jobs)) wake_up(&dmc->destroyq); /* Kick this IO back to the source bdev */ rc_start_uncached_io(dmc, bio); return 0; } EXPORT_SYMBOL(rc_do_complete); static void process_jobs(struct list_head *jobs, int (*fn)(struct kcached_job *)) { struct kcached_job *job; while ((job = pop(jobs))) (void)fn(job); } static void do_work(struct work_struct *work) { process_jobs(&complete_jobs, rc_do_complete); process_jobs(&io_jobs, do_io); } static int kcached_init(struct cache_context *dmc) { init_waitqueue_head(&dmc->destroyq); atomic_set(&dmc->nr_jobs, 0); return 0; } void kcached_client_destroy(struct cache_context *dmc) { wait_event(dmc->destroyq, !atomic_read(&dmc->nr_jobs)); } static unsigned long hash_block(struct cache_context *dmc, sector_t dbn) { unsigned long set_number; uint64_t value; value = (dbn >> (dmc->block_shift + dmc->consecutive_shift)); set_number = do_div(value, (dmc->size >> dmc->consecutive_shift)); return set_number; } static int find_valid_dbn(struct cache_context *dmc, sector_t dbn, int start_index, int *index) { int i; int end_index = start_index + dmc->assoc; for (i = start_index ; i < end_index ; i++) { if (dbn == dmc->cache[i].dbn && (dmc->cache_state[i] == VALID || dmc->cache_state[i] == CACHEREADINPROG || dmc->cache_state[i] == INPROG)) { *index = i; return dmc->cache_state[i]; } } return GENERIC_ERROR; } static void find_invalid_dbn(struct cache_context *dmc, int start_index, int *index) { int i; int end_index = start_index + dmc->assoc; /* Find INVALID slot that we can reuse */ for (i = start_index ; i < end_index ; i++) { if (dmc->cache_state[i] == INVALID) { *index = i; return; } } } static void find_reclaim_dbn(struct cache_context *dmc, int start_index, int *index) { int i; int end_index = start_index + dmc->assoc; int set = start_index / dmc->assoc; int slots_searched = 0; /* Find the "oldest" VALID slot to recycle. For each set, we keep * track of the next "lru" slot to pick off. Each time we pick off * a VALID entry to recycle we advance this pointer. So we sweep * through the set looking for next blocks to recycle. This * approximates to FIFO (modulo for blocks written through). */ i = dmc->set_lru_next[set]; while (slots_searched < dmc->assoc) { ASSERT(i >= start_index); ASSERT(i < end_index); if (dmc->cache_state[i] == VALID) { *index = i; break; } slots_searched++; i++; if (i == end_index) i = start_index; } i++; if (i == end_index) i = start_index; dmc->set_lru_next[set] = i; } /* dbn is the starting sector, io_size is the number of sectors. */ static int cache_lookup(struct cache_context *dmc, struct bio *bio, int *index) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0) sector_t dbn = bio->bi_iter.bi_sector; #else sector_t dbn = bio->bi_sector; #endif unsigned long set_number = hash_block(dmc, dbn); int invalid = -1, oldest_clean = -1; int start_index; int ret; start_index = dmc->assoc * set_number; ret = find_valid_dbn(dmc, dbn, start_index, index); if (ret == VALID || ret == INPROG || ret == CACHEREADINPROG) { /* We found the exact range of blocks we are looking for */ return ret; } ASSERT(ret == -1); find_invalid_dbn(dmc, start_index, &invalid); if (invalid == -1) { /* We didn't find an invalid entry, * search for oldest valid entry */ find_reclaim_dbn(dmc, start_index, &oldest_clean); } /* Cache miss : We can't choose an entry marked INPROG, * but choose the oldest INVALID or the oldest VALID entry. */ *index = start_index + dmc->assoc; if (invalid != -1) *index = invalid; else if (oldest_clean != -1) *index = oldest_clean; if (*index < (start_index + dmc->assoc)) return INVALID; else return GENERIC_ERROR; } static struct kcached_job *new_kcached_job(struct cache_context *dmc, struct bio *bio, int index) { struct kcached_job *job; job = mempool_alloc(job_pool, GFP_NOIO); if (!job) return NULL; job->disk.bdev = dmc->disk_dev->bdev; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0) job->disk.sector = bio->bi_iter.bi_sector; #else job->disk.sector = bio->bi_sector; #endif if (index != -1) job->disk.count = dmc->block_size; else #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0) job->disk.count = to_sector(bio->bi_iter.bi_size); #else job->disk.count = to_sector(bio->bi_size); #endif job->cache.bdev = dmc->cache_dev->bdev; if (index != -1) { job->cache.sector = index << dmc->block_shift; job->cache.count = dmc->block_size; } job->dmc = dmc; job->bio = bio; job->index = index; job->error = 0; return job; } static void cache_read_miss(struct cache_context *dmc, struct bio *bio, int index) { struct kcached_job *job; unsigned long flags; job = new_kcached_job(dmc, bio, index); if (unlikely(!job)) { DMERR("%s: Cannot allocate job\n", __func__); spin_lock_irqsave(&dmc->cache_spin_lock, flags); dmc->cache_state[index] = INVALID; spin_unlock_irqrestore(&dmc->cache_spin_lock, flags); #if LINUX_VERSION_CODE < KERNEL_VERSION(4,3,0) bio_endio(bio, -EIO); #else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,13,0) bio->bi_status = -EIO; #else bio->bi_error = -EIO; #endif bio_io_error(bio); #endif } else { job->rw = READSOURCE; atomic_inc(&dmc->nr_jobs); dmc->disk_reads++; dm_io_async_bvec(1, &job->disk, READ, #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0) bio, rc_io_callback, job); #else bio->bi_io_vec + bio->bi_idx, rc_io_callback, job); #endif } } static void cache_read(struct cache_context *dmc, struct bio *bio) { int index; int res; unsigned long flags; spin_lock_irqsave(&dmc->cache_spin_lock, flags); res = cache_lookup(dmc, bio, &index); #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0) if ((res == VALID) && (dmc->cache[index].dbn == bio->bi_iter.bi_sector)) { #else if ((res == VALID) && (dmc->cache[index].dbn == bio->bi_sector)) { #endif struct kcached_job *job; dmc->cache_state[index] = CACHEREADINPROG; dmc->cache_hits++; spin_unlock_irqrestore(&dmc->cache_spin_lock, flags); job = new_kcached_job(dmc, bio, index); if (unlikely(!job)) { DMERR("cache_read(_hit): Cannot allocate job\n"); spin_lock_irqsave(&dmc->cache_spin_lock, flags); dmc->cache_state[index] = VALID; spin_unlock_irqrestore(&dmc->cache_spin_lock, flags); #if LINUX_VERSION_CODE < KERNEL_VERSION(4,3,0) bio_endio(bio, -EIO); #else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,13,0) bio->bi_status = -EIO; #else bio->bi_error = -EIO; #endif bio_io_error(bio); #endif } else { job->rw = READCACHE; atomic_inc(&dmc->nr_jobs); dmc->cache_reads++; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0) dm_io_async_bvec(1, &job->cache, READ, bio, rc_io_callback, job); #else dm_io_async_bvec(1, &job->cache, READ, bio->bi_io_vec + bio->bi_idx, rc_io_callback, job); #endif } return; } if (cache_invalidate_blocks(dmc, bio) > 0) { /* A non zero return indicates an inprog invalidation */ spin_unlock_irqrestore(&dmc->cache_spin_lock, flags); rc_start_uncached_io(dmc, bio); return; } if (res == -1 || res >= INPROG) { /* We either didn't find a cache slot in the set we were * looking at or the block we are trying to read is being * refilled into cache. */ spin_unlock_irqrestore(&dmc->cache_spin_lock, flags); rc_start_uncached_io(dmc, bio); return; } /* (res == INVALID) Cache Miss And we found cache blocks to replace * Claim the cache blocks before giving up the spinlock */ if (dmc->cache_state[index] == VALID) { dmc->cached_blocks--; dmc->replace++; } dmc->cache_state[index] = INPROG; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0) dmc->cache[index].dbn = bio->bi_iter.bi_sector; #else dmc->cache[index].dbn = bio->bi_sector; #endif spin_unlock_irqrestore(&dmc->cache_spin_lock, flags); cache_read_miss(dmc, bio, index); } static int cache_invalidate_block_set(struct cache_context *dmc, int set, sector_t io_start, sector_t io_end, int rw, int *inprog_inval) { int start_index, end_index, i; int invalidations = 0; start_index = dmc->assoc * set; end_index = start_index + dmc->assoc; for (i = start_index ; i < end_index ; i++) { sector_t start_dbn = dmc->cache[i].dbn; sector_t end_dbn = start_dbn + dmc->block_size; if (dmc->cache_state[i] == INVALID || dmc->cache_state[i] == INPROG_INVALID) continue; if ((io_start >= start_dbn && io_start < end_dbn) || (io_end >= start_dbn && io_end < end_dbn)) { if (rw == WRITE) dmc->wr_invalidates++; else dmc->rd_invalidates++; invalidations++; if (dmc->cache_state[i] == VALID) { dmc->cached_blocks--; dmc->cache_state[i] = INVALID; } else if (dmc->cache_state[i] >= INPROG) { (*inprog_inval)++; dmc->cache_state[i] = INPROG_INVALID; DMERR("%s: sector %lu, size %lu, rw %d", __func__, (unsigned long)io_start, (unsigned long)io_end - (unsigned long)io_start, rw); } } } return invalidations; } static int cache_invalidate_blocks(struct cache_context *dmc, struct bio *bio) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0) sector_t io_start = bio->bi_iter.bi_sector; sector_t io_end = bio->bi_iter.bi_sector + (to_sector(bio->bi_iter.bi_size) - 1); #else sector_t io_start = bio->bi_sector; sector_t io_end = bio->bi_sector + (to_sector(bio->bi_size) - 1); #endif int start_set, end_set; int inprog_inval_start = 0, inprog_inval_end = 0; start_set = hash_block(dmc, io_start); end_set = hash_block(dmc, io_end); cache_invalidate_block_set(dmc, start_set, io_start, io_end, bio_data_dir(bio), &inprog_inval_start); if (start_set != end_set) cache_invalidate_block_set(dmc, end_set, io_start, io_end, bio_data_dir(bio), &inprog_inval_end); return (inprog_inval_start + inprog_inval_end); } static void cache_write(struct cache_context *dmc, struct bio *bio) { int index; int res; unsigned long flags; struct kcached_job *job; spin_lock_irqsave(&dmc->cache_spin_lock, flags); if (cache_invalidate_blocks(dmc, bio) > 0) { /* A non zero return indicates an inprog invalidation */ spin_unlock_irqrestore(&dmc->cache_spin_lock, flags); rc_start_uncached_io(dmc, bio); return; } res = cache_lookup(dmc, bio, &index); ASSERT(res == -1 || res == INVALID); if (res == -1) { spin_unlock_irqrestore(&dmc->cache_spin_lock, flags); rc_start_uncached_io(dmc, bio); return; } if (dmc->cache_state[index] == VALID) { dmc->cached_blocks--; dmc->cache_wr_replace++; } dmc->cache_state[index] = INPROG; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0) dmc->cache[index].dbn = bio->bi_iter.bi_sector; #else dmc->cache[index].dbn = bio->bi_sector; #endif spin_unlock_irqrestore(&dmc->cache_spin_lock, flags); job = new_kcached_job(dmc, bio, index); if (unlikely(!job)) { DMERR("%s: Cannot allocate job\n", __func__); spin_lock_irqsave(&dmc->cache_spin_lock, flags); dmc->cache_state[index] = INVALID; spin_unlock_irqrestore(&dmc->cache_spin_lock, flags); #if LINUX_VERSION_CODE < KERNEL_VERSION(4,3,0) bio_endio(bio, -EIO); #else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,13,0) bio->bi_status= -EIO; #else bio->bi_error = -EIO; #endif bio_io_error(bio); #endif return; } job->rw = WRITESOURCE; atomic_inc(&job->dmc->nr_jobs); dmc->disk_writes++; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0) dm_io_async_bvec(1, &job->disk, WRITE, bio, rc_io_callback, job); #else dm_io_async_bvec(1, &job->disk, WRITE, bio->bi_io_vec + bio->bi_idx, rc_io_callback, job); #endif } #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,36) #define bio_barrier(bio) ((bio)->bi_rw & (1 << BIO_RW_BARRIER)) #else #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37) #define bio_barrier(bio) ((bio)->bi_rw & REQ_HARDBARRIER) #else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,8,0) #define bio_barrier(bio) ((bio)->bi_opf & REQ_PREFLUSH) #else #define bio_barrier(bio) ((bio)->bi_rw & REQ_FLUSH) #endif #endif #endif #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,8,0) int rc_map(struct dm_target *ti, struct bio *bio) #else int rc_map(struct dm_target *ti, struct bio *bio, union map_info *map_context) #endif { struct cache_context *dmc = (struct cache_context *)ti->private; unsigned long flags; if (bio_barrier(bio)) return -EOPNOTSUPP; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0) ASSERT(to_sector(bio->bi_iter.bi_size) <= dmc->block_size); #else ASSERT(to_sector(bio->bi_size) <= dmc->block_size); #endif if (bio_data_dir(bio) == READ) dmc->reads++; else dmc->writes++; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0) if (to_sector(bio->bi_iter.bi_size) != dmc->block_size || #else if (to_sector(bio->bi_size) != dmc->block_size || #endif (dmc->mode && (bio_data_dir(bio) == WRITE))) { spin_lock_irqsave(&dmc->cache_spin_lock, flags); (void)cache_invalidate_blocks(dmc, bio); spin_unlock_irqrestore(&dmc->cache_spin_lock, flags); rc_start_uncached_io(dmc, bio); } else { if (bio_data_dir(bio) == READ) cache_read(dmc, bio); else cache_write(dmc, bio); } return DM_MAPIO_SUBMITTED; } EXPORT_SYMBOL(rc_map); static void rc_uncached_io_callback(unsigned long error, void *context) { struct kcached_job *job = (struct kcached_job *)context; struct cache_context *dmc = job->dmc; unsigned long flags; spin_lock_irqsave(&dmc->cache_spin_lock, flags); if (bio_data_dir(job->bio) == READ) dmc->uncached_reads++; else dmc->uncached_writes++; (void)cache_invalidate_blocks(dmc, job->bio); spin_unlock_irqrestore(&dmc->cache_spin_lock, flags); #if LINUX_VERSION_CODE < KERNEL_VERSION(4,3,0) bio_endio(job->bio, error); #else if (error) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,13,0) job->bio->bi_status = error; #else job->bio->bi_error = error; #endif bio_io_error(job->bio); } else { bio_endio(job->bio); } #endif mempool_free(job, job_pool); if (atomic_dec_and_test(&dmc->nr_jobs)) wake_up(&dmc->destroyq); } static void rc_start_uncached_io(struct cache_context *dmc, struct bio *bio) { int is_write = (bio_data_dir(bio) == WRITE); struct kcached_job *job; job = new_kcached_job(dmc, bio, -1); if (unlikely(!job)) { #if LINUX_VERSION_CODE < KERNEL_VERSION(4,3,0) bio_endio(bio, -EIO); #else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,13,0) bio->bi_status= -EIO; #else bio->bi_error = -EIO; #endif bio_io_error(bio); #endif return; } atomic_inc(&dmc->nr_jobs); if (bio_data_dir(job->bio) == READ) dmc->disk_reads++; else dmc->disk_writes++; dm_io_async_bvec(1, &job->disk, ((is_write) ? WRITE : READ), #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0) bio, rc_uncached_io_callback, job); #else bio->bi_io_vec + bio->bi_idx, rc_uncached_io_callback, job); #endif } static inline int rc_get_dev(struct dm_target *ti, char *pth, struct dm_dev **dmd, char *dmc_dname, sector_t tilen) { int rc; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34) rc = dm_get_device(ti, pth, dm_table_get_mode(ti->table), dmd); #else #if defined(RHEL_MAJOR) && RHEL_MAJOR == 6 rc = dm_get_device(ti, pth, dm_table_get_mode(ti->table), dmd); #else rc = dm_get_device(ti, pth, 0, tilen, dm_table_get_mode(ti->table), dmd); #endif #endif if (!rc) strncpy(dmc_dname, pth, DEV_PATHLEN); return rc; } /* Construct a cache mapping. * arg[0]: path to source device * arg[1]: path to cache device * arg[2]: cache size (in blocks) * arg[3]: mode: write through / around * arg[4]: cache associativity */ static int cache_ctr(struct dm_target *ti, unsigned int argc, char **argv) { struct cache_context *dmc; unsigned int consecutive_blocks; sector_t i, order, tmpsize; sector_t data_size, dev_size; int r = -EINVAL; if (argc < 2) { ti->error = "rapiddisk-cache: Need at least 2 arguments"; goto construct_fail; } dmc = kzalloc(sizeof(*dmc), GFP_KERNEL); if (!dmc) { ti->error = "rapiddisk-cache: Failed to allocate cache context"; r = -ENOMEM; goto construct_fail; } dmc->tgt = ti; if (rc_get_dev(ti, argv[0], &dmc->disk_dev, dmc->disk_devname, ti->len)) { ti->error = "rapiddisk-cache: Disk device lookup failed"; goto construct_fail1; } if (strncmp(argv[1], "/dev/rd", 7) != 0) { pr_err("%s: %s is not a valid cache device for rapiddisk-cache.", DM_MSG_PREFIX, argv[1]); ti->error = "rapiddisk-cache: Invalid cache device. Not a RapidDisk volume."; goto construct_fail2; } if (rc_get_dev(ti, argv[1], &dmc->cache_dev, dmc->cache_devname, 0)) { ti->error = "rapiddisk-cache: Cache device lookup failed"; goto construct_fail2; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27) #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,39) dmc->io_client = dm_io_client_create(); #else #if defined(RHEL_MAJOR) && RHEL_MAJOR == 6 && RHEL_MINOR >= 4 dmc->io_client = dm_io_client_create(); #else dmc->io_client = dm_io_client_create(COPY_PAGES); #endif #endif if (IS_ERR(dmc->io_client)) { r = PTR_ERR(dmc->io_client); ti->error = "Failed to create io client\n"; goto construct_fail3; } #endif r = kcached_init(dmc); if (r) { ti->error = "Failed to initialize kcached"; goto construct_fail4; } dmc->block_size = CACHE_BLOCK_SIZE; dmc->block_shift = ffs(dmc->block_size) - 1; dmc->block_mask = dmc->block_size - 1; if (argc >= 3) { if (kstrtoul(argv[2], 10, (unsigned long *)&dmc->size)) { ti->error = "rapiddisk-cache: Invalid cache size"; r = -EINVAL; goto construct_fail5; } } else { dmc->size = to_sector(dmc->cache_dev->bdev->bd_inode->i_size); } if (argc >= 4) { if (sscanf(argv[3], "%d", &dmc->mode) != 1) { ti->error = "rapiddisk-cache: Invalid mode"; r = -EINVAL; goto construct_fail5; } } else { dmc->mode = WRITETHROUGH; } if (argc >= 5) { if (kstrtoint(argv[4], 10, &dmc->assoc)) { ti->error = "rapiddisk-cache: Invalid cache associativity"; r = -EINVAL; goto construct_fail5; } if (!dmc->assoc || (dmc->assoc & (dmc->assoc - 1)) || dmc->size < dmc->assoc) { ti->error = "rapiddisk-cache: Invalid cache associativity"; r = -EINVAL; goto construct_fail5; } } else { dmc->assoc = DEFAULT_CACHE_ASSOC; } /* Convert size (in sectors) to blocks. Then round size * (in blocks now) down to a multiple of associativity */ do_div(dmc->size, dmc->block_size); tmpsize = dmc->size; do_div(tmpsize, dmc->assoc); dmc->size = tmpsize * dmc->assoc; dev_size = to_sector(dmc->cache_dev->bdev->bd_inode->i_size); data_size = dmc->size * dmc->block_size; if (data_size > dev_size) { DMERR("Requested cache size exceeds the cache device's capacity (%lu>%lu)", (unsigned long)data_size, (unsigned long)dev_size); ti->error = "rapiddisk-cache: Invalid cache size"; r = -EINVAL; goto construct_fail5; } consecutive_blocks = dmc->assoc; dmc->consecutive_shift = ffs(consecutive_blocks) - 1; order = dmc->size * sizeof(struct cache_block); DMINFO("Allocate %luKB (%luB per) mem for %lu-entry cache" "(capacity:%luMB, associativity:%u, block size:%u sectors(%uKB))", (unsigned long)order >> 10, (unsigned long)sizeof(struct cache_block), (unsigned long)dmc->size, (unsigned long)data_size >> (20 - SECTOR_SHIFT), dmc->assoc, dmc->block_size, dmc->block_size >> (10 - SECTOR_SHIFT)); dmc->cache = vmalloc(order); if (!dmc->cache) goto construct_fail6; dmc->cache_state = vmalloc(dmc->size); if (!dmc->cache_state) goto construct_fail7; order = (dmc->size >> dmc->consecutive_shift) * sizeof(u32); dmc->set_lru_next = vmalloc(order); if (!dmc->set_lru_next) goto construct_fail8; for (i = 0; i < dmc->size ; i++) { dmc->cache[i].dbn = 0; dmc->cache_state[i] = INVALID; } /* Initialize the point where LRU sweeps begin for each set */ for (i = 0 ; i < (dmc->size >> dmc->consecutive_shift) ; i++) dmc->set_lru_next[i] = i * dmc->assoc; spin_lock_init(&dmc->cache_spin_lock); dmc->reads = 0; dmc->writes = 0; dmc->cache_hits = 0; dmc->replace = 0; dmc->wr_invalidates = 0; dmc->rd_invalidates = 0; dmc->cached_blocks = 0; dmc->cache_wr_replace = 0; #if LINUX_VERSION_CODE < KERNEL_VERSION(3,6,0) ti->split_io = dmc->block_size; #else r = dm_set_target_max_io_len(ti, dmc->block_size); if (r) goto construct_fail8; #endif ti->private = dmc; return 0; construct_fail8: vfree(dmc->cache_state); construct_fail7: vfree(dmc->cache); construct_fail6: r = -ENOMEM; ti->error = "Unable to allocate memory"; construct_fail5: kcached_client_destroy(dmc); construct_fail4: #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27) dm_io_client_destroy(dmc->io_client); #endif construct_fail3: dm_put_device(ti, dmc->cache_dev); construct_fail2: dm_put_device(ti, dmc->disk_dev); construct_fail1: kfree(dmc); construct_fail: return r; } static void cache_dtr(struct dm_target *ti) { struct cache_context *dmc = (struct cache_context *) ti->private; kcached_client_destroy(dmc); if (dmc->reads + dmc->writes > 0) { DMINFO("stats:\n\treads(%lu), writes(%lu)\n", dmc->reads, dmc->writes); DMINFO("\tcache hits(%lu), replacement(%lu), write replacement(%lu)\n" "\tread invalidates(%lu), write invalidates(%lu)\n", dmc->cache_hits, dmc->replace, dmc->cache_wr_replace, dmc->rd_invalidates, dmc->wr_invalidates); DMINFO("conf:\n\tcapacity(%luM), associativity(%u), block size(%uK)\n" "\ttotal blocks(%lu), cached blocks(%lu)\n", (unsigned long)dmc->size * dmc->block_size >> 11, dmc->assoc, dmc->block_size >> (10 - SECTOR_SHIFT), (unsigned long)dmc->size, dmc->cached_blocks); } #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27) dm_io_client_destroy(dmc->io_client); #endif vfree(dmc->cache); vfree(dmc->cache_state); vfree(dmc->set_lru_next); dm_put_device(ti, dmc->disk_dev); dm_put_device(ti, dmc->cache_dev); kfree(dmc); } static void rc_status_info(struct cache_context *dmc, status_type_t type, char *result, unsigned int maxlen) { int sz = 0; DMEMIT("stats:\n\treads(%lu), writes(%lu)\n", dmc->reads, dmc->writes); DMEMIT("\tcache hits(%lu), replacement(%lu), write replacement(%lu)\n" "\tread invalidates(%lu), write invalidates(%lu)\n" "\tuncached reads(%lu), uncached writes(%lu)\n" "\tdisk reads(%lu), disk writes(%lu)\n" "\tcache reads(%lu), cache writes(%lu)\n", dmc->cache_hits, dmc->replace, dmc->cache_wr_replace, dmc->rd_invalidates, dmc->wr_invalidates, dmc->uncached_reads, dmc->uncached_writes, dmc->disk_reads, dmc->disk_writes, dmc->cache_reads, dmc->cache_writes); } static void rc_status_table(struct cache_context *dmc, status_type_t type, char *result, unsigned int maxlen) { int sz = 0; DMEMIT("conf:\n\tRapidDisk dev (%s), disk dev (%s) mode (%s)\n" "\tcapacity(%luM), associativity(%u), block size(%uK)\n" "\ttotal blocks(%lu), cached blocks(%lu)\n", dmc->cache_devname, dmc->disk_devname, ((dmc->mode) ? "WRITE_AROUND" : "WRITETHROUGH"), (unsigned long)dmc->size * dmc->block_size >> 11, dmc->assoc, dmc->block_size >> (10 - SECTOR_SHIFT), (unsigned long)dmc->size, dmc->cached_blocks); } #if LINUX_VERSION_CODE < KERNEL_VERSION(3,8,3) static int #else static void #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(3,6,0) cache_status(struct dm_target *ti, status_type_t type, char *result, unsigned int maxlen) #else cache_status(struct dm_target *ti, status_type_t type, unsigned status_flags, char *result, unsigned int maxlen) #endif { #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,15,0) || (defined(RHEL_MAJOR) && RHEL_MAJOR >= 9 && RHEL_MINOR >= 0) int sz = 0; #endif struct cache_context *dmc = (struct cache_context *)ti->private; switch (type) { case STATUSTYPE_INFO: rc_status_info(dmc, type, result, maxlen); break; case STATUSTYPE_TABLE: rc_status_table(dmc, type, result, maxlen); break; #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,15,0) || (defined(RHEL_MAJOR) && RHEL_MAJOR >= 9 && RHEL_MINOR >= 0) case STATUSTYPE_IMA: DMEMIT_TARGET_NAME_VERSION(ti->type); DMEMIT(",source_device_name=%s,cache_device_name=%s;", dmc->disk_dev->name, dmc->cache_dev->name); break; #endif } #if LINUX_VERSION_CODE < KERNEL_VERSION(3,8,3) return 0; #endif } static struct target_type cache_target = { .name = "rapiddisk-cache", .version = {9, 1, 0}, .module = THIS_MODULE, .ctr = cache_ctr, .dtr = cache_dtr, .map = rc_map, .status = cache_status, }; int __init rc_init(void) { int ret; ret = jobs_init(); if (ret) return ret; kcached_wq = create_singlethread_workqueue("kcached"); if (!kcached_wq) return -ENOMEM; INIT_WORK(&kcached_work, do_work); ret = dm_register_target(&cache_target); if (ret < 0) return ret; return 0; } void rc_exit(void) { dm_unregister_target(&cache_target); jobs_exit(); destroy_workqueue(kcached_wq); } module_init(rc_init); module_exit(rc_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Petros Koutoupis "); MODULE_DESCRIPTION("RapidDisk-Cache DM target is a write-through caching target with RapidDisk volumes."); MODULE_VERSION(VERSION_STR); MODULE_INFO(Copyright, "Copyright 2010 - 2023 Petros Koutoupis"); rapiddisk-9.1.0/module/rapiddisk.c000066400000000000000000000665751442147535600171340ustar00rootroot00000000000000/******************************************************************************* ** Copyright © 2011 - 2023 Petros Koutoupis ** All rights reserved. ** ** 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; under 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, see . ** ** SPDX-License-Identifier: GPL-2.0-only ** ** filename: rapiddisk.c ** description: RapidDisk is an enhanced Linux RAM disk module to dynamically ** create, remove, and resize non-persistent RAM drives. ** created: 1Jun11, petros@petroskoutoupis.com ** ******************************************************************************/ #include #include #include #include #include #include #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,3,0) #include #else #include #endif #include #include #include #include #include #include #define VERSION_STR "9.1.0" #define PREFIX "rapiddisk" #define BYTES_PER_SECTOR 512 #define MAX_RDSKS 1024 #define DEFAULT_MAX_SECTS 127 #define DEFAULT_REQUESTS 128 #define GENERIC_ERROR -1 #define SUCCESS 0 #define FREE_BATCH 16 #if LINUX_VERSION_CODE < KERNEL_VERSION(5,15,0) #if (defined(RHEL_MAJOR) && RHEL_MAJOR >= 9 && RHEL_MINOR >= 0) /* Not sure of a cleaner way to do this. */ #else #define SECTOR_SHIFT 9 #define PAGE_SECTORS_SHIFT (PAGE_SHIFT - SECTOR_SHIFT) #define PAGE_SECTORS BIT(PAGE_SECTORS_SHIFT) #endif #endif /* ioctls */ #define IOCTL_INVALID_CDQUERY 0x5331 #define IOCTL_INVALID_CDQUERY2 0x5395 #define IOCTL_INVALID_TTY 0x5401 #define IOCTL_INVALID_SG_IO 0x2285 #define IOCTL_RD_GET_STATS 0x0529 #define IOCTL_RD_GET_USAGE 0x0530 #define IOCTL_RD_BLKFLSBUF 0x0531 static DEFINE_MUTEX(sysfs_mutex); static DEFINE_MUTEX(ioctl_mutex); struct rdsk_device { int num; #if LINUX_VERSION_CODE < KERNEL_VERSION(5,15,0) struct request_queue *rdsk_queue; #endif struct gendisk *rdsk_disk; struct list_head rdsk_list; unsigned long long max_blk_alloc; /* rdsk: to keep track of highest sector write */ unsigned long long max_page_cnt; unsigned long long size; unsigned long error_cnt; spinlock_t rdsk_lock; struct radix_tree_root rdsk_pages; }; static unsigned long rd_max_nr = MAX_RDSKS, rd_ma_no, rd_total; /* no. of attached devices */ static unsigned long rd_size = 0, rd_nr = 0; static int max_sectors = DEFAULT_MAX_SECTS, nr_requests = DEFAULT_REQUESTS; static LIST_HEAD(rdsk_devices); static struct kobject *rdsk_kobj; module_param(max_sectors, int, S_IRUGO); MODULE_PARM_DESC(max_sectors, " Maximum sectors (in KB) for the request queue. (Default = 127)"); module_param(nr_requests, int, S_IRUGO); MODULE_PARM_DESC(nr_requests, " Number of requests at a given time for the request queue. (Default = 128)"); module_param(rd_nr, ulong, S_IRUGO); MODULE_PARM_DESC(rd_nr, " Maximum number of RapidDisk devices to load on insertion. (Default = 0)"); module_param(rd_size, ulong, S_IRUGO); MODULE_PARM_DESC(rd_size, " Size of each RAM disk (in MB) loaded on insertion. (Default = 0)"); module_param(rd_max_nr, ulong, S_IRUGO); MODULE_PARM_DESC(rd_max_nr, " Maximum number of RAM Disks. (Default = 1024)"); static int rdsk_do_bvec(struct rdsk_device *, struct page *, #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,8,0) unsigned int, unsigned int, bool, sector_t); #else unsigned int, unsigned int, int, sector_t); #endif static int rdsk_ioctl(struct block_device *, fmode_t, unsigned int, unsigned long); #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,2,0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,4,0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,9,0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,16,0) || (defined(RHEL_MAJOR) && RHEL_MAJOR >= 9 && RHEL_MINOR >= 0) static void rdsk_submit_bio(struct bio *); #else static blk_qc_t rdsk_submit_bio(struct bio *); #endif #else static blk_qc_t rdsk_make_request(struct request_queue *, struct bio *); #endif #else static void rdsk_make_request(struct request_queue *, struct bio *); #endif #else static int rdsk_make_request(struct request_queue *, struct bio *); #endif static int attach_device(unsigned long, unsigned long long); /* disk size */ static int detach_device(unsigned long); /* disk num */ static int resize_device(unsigned long, unsigned long long); /* disk num, disk size */ static ssize_t mgmt_show(struct kobject *, struct kobj_attribute *, char *); static ssize_t mgmt_store(struct kobject *, struct kobj_attribute *, const char *, size_t); static ssize_t devices_show(struct kobject *, struct kobj_attribute *, char *); static ssize_t mgmt_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int len = 0; len = sprintf(buf, "RapidDisk %s\n\nMaximum Number of Attachable Devices: %lu\n", VERSION_STR, rd_max_nr); len += sprintf(buf + len, "Number of Attached Devices: %lu\nMax Sectors (KB): %d\nNumber of Requests: %d\n", rd_total, max_sectors, nr_requests); return len; } static ssize_t devices_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int len = 0; struct rdsk_device *rdsk; mutex_lock(&sysfs_mutex); len += sprintf(buf + len, "Device\tSize\tErrors\tUsed\n"); list_for_each_entry(rdsk, &rdsk_devices, rdsk_list) { len += scnprintf(buf + len, PAGE_SIZE - len, "rd%d\t%llu\t%lu\t%llu\n", rdsk->num, rdsk->size, rdsk->error_cnt, (rdsk->max_page_cnt * PAGE_SIZE)); } mutex_unlock(&sysfs_mutex); return len; } static ssize_t mgmt_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buffer, size_t count) { int err = (int)count; unsigned long num; unsigned long long size = 0; char *ptr, *buf; if (!buffer || count > PAGE_SIZE) return -EINVAL; mutex_lock(&sysfs_mutex); buf = (char *)__get_free_page(GFP_KERNEL); if (!buf) { err = -ENOMEM; goto write_sysfs_error; } strcpy(buf, buffer); if (!strncmp("rapiddisk attach ", buffer, 17)) { ptr = buf + 17; num = simple_strtoul(ptr, &ptr, 0); size = (simple_strtoull(ptr + 1, &ptr, 0)); if (attach_device(num, size) != SUCCESS) { pr_err("%s: Unable to attach a new RapidDisk device.\n", PREFIX); err = -EINVAL; } } else if (!strncmp("rapiddisk detach ", buffer, 17)) { ptr = buf + 17; num = simple_strtoul(ptr, &ptr, 0); if (detach_device(num) != SUCCESS) { pr_err("%s: Unable to detach rd%lu\n", PREFIX, num); err = -EINVAL; } } else if (!strncmp("rapiddisk resize ", buffer, 17)) { ptr = buf + 17; num = simple_strtoul(ptr, &ptr, 0); size = (simple_strtoull(ptr + 1, &ptr, 0)); if (resize_device(num, size) != SUCCESS) { pr_err("%s: Unable to resize rd%lu\n", PREFIX, num); err = -EINVAL; } } else { pr_err("%s: Unsupported command: %s\n", PREFIX, buffer); err = -EINVAL; } free_page((unsigned long)buf); write_sysfs_error: mutex_unlock(&sysfs_mutex); return err; } static struct kobj_attribute mgmt_attribute = __ATTR(mgmt, 0664, mgmt_show, mgmt_store); static struct kobj_attribute dev_attribute = __ATTR(devices, 0664, devices_show, NULL); static struct attribute *attrs[] = { &mgmt_attribute.attr, &dev_attribute.attr, NULL, }; static struct attribute_group attr_group = { .attrs = attrs, }; static struct page *rdsk_lookup_page(struct rdsk_device *rdsk, sector_t sector) { pgoff_t idx; struct page *page; rcu_read_lock(); idx = sector >> PAGE_SECTORS_SHIFT; /* sector to page index */ page = radix_tree_lookup(&rdsk->rdsk_pages, idx); rcu_read_unlock(); BUG_ON(page && page->index != idx); return page; } static struct page *rdsk_insert_page(struct rdsk_device *rdsk, sector_t sector) { pgoff_t idx; struct page *page; gfp_t gfp_flags; page = rdsk_lookup_page(rdsk, sector); if (page) return page; /* * Must use NOIO because we don't want to recurse back into the * block or filesystem layers from page reclaim. * * Cannot support XIP and highmem, because our ->direct_access * routine for XIP must return memory that is always addressable. * If XIP was reworked to use pfns and kmap throughout, this * restriction might be able to be lifted. */ gfp_flags = GFP_NOIO | __GFP_ZERO | __GFP_HIGHMEM; page = alloc_page(gfp_flags); if (!page) return NULL; if (radix_tree_preload(GFP_NOIO)) { __free_page(page); return NULL; } spin_lock(&rdsk->rdsk_lock); idx = sector >> PAGE_SECTORS_SHIFT; page->index = idx; if (radix_tree_insert(&rdsk->rdsk_pages, idx, page)) { __free_page(page); page = radix_tree_lookup(&rdsk->rdsk_pages, idx); BUG_ON(!page); BUG_ON(page->index != idx); } spin_unlock(&rdsk->rdsk_lock); radix_tree_preload_end(); rdsk->max_page_cnt++; return page; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) static void rdsk_zero_page(struct rdsk_device *rdsk, sector_t sector) { struct page *page; page = rdsk_lookup_page(rdsk, sector); if (page) { clear_highpage(page); rdsk->max_page_cnt--; } } #endif static void rdsk_free_pages(struct rdsk_device *rdsk) { unsigned long pos = 0; struct page *pages[FREE_BATCH]; int nr_pages; do { int i; nr_pages = radix_tree_gang_lookup(&rdsk->rdsk_pages, (void **)pages, pos, FREE_BATCH); for (i = 0; i < nr_pages; i++) { void *ret; BUG_ON(pages[i]->index < pos); pos = pages[i]->index; ret = radix_tree_delete(&rdsk->rdsk_pages, pos); BUG_ON(!ret || ret != pages[i]); __free_page(pages[i]); } pos++; } while (nr_pages == FREE_BATCH); } static int copy_to_rdsk_setup(struct rdsk_device *rdsk, sector_t sector, size_t n) { unsigned int offset = (sector & (PAGE_SECTORS - 1)) << SECTOR_SHIFT; size_t copy; copy = min_t(size_t, n, PAGE_SIZE - offset); if (!rdsk_insert_page(rdsk, sector)) return -ENOSPC; if (copy < n) { sector += copy >> SECTOR_SHIFT; if (!rdsk_insert_page(rdsk, sector)) return -ENOSPC; } return SUCCESS; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) static void discard_from_rdsk(struct rdsk_device *rdsk, sector_t sector, size_t n) { while (n >= PAGE_SIZE) { rdsk_zero_page(rdsk, sector); sector += PAGE_SIZE >> SECTOR_SHIFT; n -= PAGE_SIZE; } } #endif static void copy_to_rdsk(struct rdsk_device *rdsk, const void *src, sector_t sector, size_t n) { struct page *page; void *dst; unsigned int offset = (sector & (PAGE_SECTORS - 1)) << SECTOR_SHIFT; size_t copy; copy = min_t(size_t, n, PAGE_SIZE - offset); page = rdsk_lookup_page(rdsk, sector); BUG_ON(!page); #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,0) dst = kmap_atomic(page); #else dst = kmap_atomic(page, KM_USER1); #endif memcpy(dst + offset, src, copy); #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,0) kunmap_atomic(dst); #else kunmap_atomic(dst, KM_USER1); #endif if (copy < n) { src += copy; sector += copy >> SECTOR_SHIFT; copy = n - copy; page = rdsk_lookup_page(rdsk, sector); BUG_ON(!page); #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,0) dst = kmap_atomic(page); #else dst = kmap_atomic(page, KM_USER1); #endif memcpy(dst, src, copy); #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,0) kunmap_atomic(dst); #else kunmap_atomic(dst, KM_USER1); #endif } if ((sector + (n / BYTES_PER_SECTOR)) > rdsk->max_blk_alloc) rdsk->max_blk_alloc = (sector + (n / BYTES_PER_SECTOR)); } static void copy_from_rdsk(void *dst, struct rdsk_device *rdsk, sector_t sector, size_t n) { struct page *page; void *src; unsigned int offset = (sector & (PAGE_SECTORS - 1)) << SECTOR_SHIFT; size_t copy; copy = min_t(size_t, n, PAGE_SIZE - offset); page = rdsk_lookup_page(rdsk, sector); if (page) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,0) src = kmap_atomic(page); #else src = kmap_atomic(page, KM_USER1); #endif memcpy(dst, src + offset, copy); #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,0) kunmap_atomic(src); #else kunmap_atomic(src, KM_USER1); #endif } else { memset(dst, 0, copy); } if (copy < n) { dst += copy; sector += copy >> SECTOR_SHIFT; copy = n - copy; page = rdsk_lookup_page(rdsk, sector); if (page) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,0) src = kmap_atomic(page); #else src = kmap_atomic(page, KM_USER1); #endif memcpy(dst, src, copy); #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,0) kunmap_atomic(src); #else kunmap_atomic(src, KM_USER1); #endif } else { memset(dst, 0, copy); } } } static int rdsk_do_bvec(struct rdsk_device *rdsk, struct page *page, #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,8,0) unsigned int len, unsigned int off, bool is_write, #else unsigned int len, unsigned int off, int rw, #endif sector_t sector){ void *mem; int err = SUCCESS; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,8,0) if (is_write) { #else if (rw != READ) { #endif err = copy_to_rdsk_setup(rdsk, sector, len); if (err) goto out; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,0) mem = kmap_atomic(page); #else mem = kmap_atomic(page, KM_USER0); #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,8,0) if (!is_write) { #else if (rw == READ) { #endif copy_from_rdsk(mem + off, rdsk, sector, len); flush_dcache_page(page); } else { flush_dcache_page(page); copy_to_rdsk(rdsk, mem + off, sector, len); } #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,0) kunmap_atomic(mem); #else kunmap_atomic(mem, KM_USER0); #endif out: return err; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,2,0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,4,0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,16,0) || (defined(RHEL_MAJOR) && RHEL_MAJOR >= 9 && RHEL_MINOR >= 0) static void #else static blk_qc_t #endif #else static void #endif #else static int #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,9,0) rdsk_submit_bio(struct bio *bio) #else rdsk_make_request(struct request_queue *q, struct bio *bio) #endif { #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0)) && (LINUX_VERSION_CODE < KERNEL_VERSION(5,12,0)) struct rdsk_device *rdsk = bio->bi_disk->private_data; #else struct block_device *bdev = bio->bi_bdev; struct rdsk_device *rdsk = bdev->bd_disk->private_data; #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(4,8,0) int rw; #endif sector_t sector; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0) struct bio_vec bvec; struct bvec_iter iter; #else struct bio_vec *bvec; int i; #endif int err = -EIO; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0) sector = bio->bi_iter.bi_sector; #else sector = bio->bi_sector; #endif #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0)) && (LINUX_VERSION_CODE < KERNEL_VERSION(5,12,0)) if ((sector + bio_sectors(bio)) > get_capacity(bio->bi_disk)) #else if ((sector + bio_sectors(bio)) > get_capacity(bdev->bd_disk)) #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(4,3,0) goto out; #else goto io_error; #endif err = SUCCESS; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,336) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,8,0) if (unlikely(bio_op(bio) == REQ_OP_DISCARD)) { #elif LINUX_VERSION_CODE >= KERNEL_VERSION(4,12,0) if ((unlikely(bio_op(bio) == REQ_OP_DISCARD)) || (unlikely(bio_op(bio) == REQ_OP_WRITE_ZEROES))) { #else if (unlikely(bio->bi_rw & REQ_DISCARD)) { #endif if (sector & ((PAGE_SIZE >> SECTOR_SHIFT) - 1) || #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0) bio->bi_iter.bi_size & ~PAGE_MASK) #else bio->bi_size & ~PAGE_MASK) #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,3,0) goto io_error; #else goto out; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0) discard_from_rdsk(rdsk, sector, bio->bi_iter.bi_size); #else discard_from_rdsk(rdsk, sector, bio->bi_size); #endif goto out; } #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(4,8,0) rw = bio_rw(bio); if (rw == READA) rw = READ; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0) bio_for_each_segment(bvec, bio, iter) { unsigned int len = bvec.bv_len; err = rdsk_do_bvec(rdsk, bvec.bv_page, len, #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,8,0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,16,0) bvec.bv_offset, bio_op(bio), sector); #else bvec.bv_offset, op_is_write(bio_op(bio)), sector); #endif #else bvec.bv_offset, rw, sector); #endif #else bio_for_each_segment(bvec, bio, i) { unsigned int len = bvec->bv_len; err = rdsk_do_bvec(rdsk, bvec->bv_page, len, bvec->bv_offset, rw, sector); #endif if (err) { rdsk->error_cnt++; #if LINUX_VERSION_CODE < KERNEL_VERSION(4,3,0) break; #else goto io_error; #endif } sector += len >> SECTOR_SHIFT; } out: #if LINUX_VERSION_CODE < KERNEL_VERSION(4,3,0) set_bit(BIO_UPTODATE, &bio->bi_flags); bio_endio(bio, err); #else bio_endio(bio); #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,4,0) #if LINUX_VERSION_CODE < KERNEL_VERSION(5,16,0) #if (defined(RHEL_MAJOR) && RHEL_MAJOR >= 9 && RHEL_MINOR >= 0) return; #else return BLK_QC_T_NONE; #endif #else return; #endif #endif io_error: #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,13,0) bio->bi_status= err; #else bio->bi_error = err; #endif bio_io_error(bio); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,4,0) #if LINUX_VERSION_CODE < KERNEL_VERSION(5,16,0) #if (defined(RHEL_MAJOR) && RHEL_MAJOR >= 9 && RHEL_MINOR >= 0) return; #else return BLK_QC_T_NONE; #endif #endif #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(3,2,0) return SUCCESS; #endif } #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,19,0) || (defined(RHEL_MAJOR) && RHEL_MAJOR >= 9 && RHEL_MINOR >= 0) static inline int bdev_openers(struct block_device *bdev) { return atomic_read(&bdev->bd_openers); } #else static inline int bdev_openers(struct block_device *bdev) { return bdev->bd_openers; } #endif static int rdsk_ioctl(struct block_device *bdev, fmode_t mode, unsigned int cmd, unsigned long arg) { int error = 0; struct rdsk_device *rdsk = bdev->bd_disk->private_data; switch (cmd) { case IOCTL_RD_BLKFLSBUF: /* We are killing the RAM disk data. */ mutex_lock(&ioctl_mutex); #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,14,0) mutex_lock(&bdev->bd_disk->open_mutex); #else mutex_lock(&bdev->bd_mutex); #endif error = -EBUSY; if (bdev_openers(bdev) <= 1) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,3,0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,8,0) || (defined(RHEL_MAJOR) && RHEL_MAJOR == 8 && RHEL_MINOR >= 4) invalidate_bdev(bdev); #else kill_bdev(bdev); #endif #else invalidate_bh_lrus(); truncate_inode_pages(bdev->bd_inode->i_mapping, 0); #endif rdsk_free_pages(rdsk); error = 0; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,14,0) mutex_unlock(&bdev->bd_disk->open_mutex); #else mutex_unlock(&bdev->bd_mutex); #endif rdsk->max_blk_alloc = 0; rdsk->max_page_cnt = 0; mutex_unlock(&ioctl_mutex); return error; case IOCTL_INVALID_CDQUERY: case IOCTL_INVALID_CDQUERY2: case IOCTL_INVALID_TTY: case IOCTL_INVALID_SG_IO: return -EINVAL; case IOCTL_RD_GET_STATS: return copy_to_user((void __user *)arg, &rdsk->max_blk_alloc, sizeof(rdsk->max_blk_alloc)) ? -EFAULT : 0; case IOCTL_RD_GET_USAGE: return copy_to_user((void __user *)arg, &rdsk->max_page_cnt, sizeof(rdsk->max_page_cnt)) ? -EFAULT : 0; } pr_warn("%s: 0x%x invalid ioctl.\n", PREFIX, cmd); return -ENOTTY; /* unknown command */ } static const struct block_device_operations rdsk_fops = { .owner = THIS_MODULE, #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,9,0) .submit_bio = rdsk_submit_bio, #endif .ioctl = rdsk_ioctl, }; static int attach_device(unsigned long num, unsigned long long size) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,16,0) || (defined(RHEL_MAJOR) && RHEL_MAJOR >= 9 && RHEL_MINOR >= 0) int err = GENERIC_ERROR; #endif struct rdsk_device *rdsk, *tmp; struct gendisk *disk; sector_t sectors = 0; if (num > MINORMASK) { pr_warn("%s: Reached maximum value for the attached disk.\n", PREFIX); goto out; } if (rd_total >= rd_max_nr) { pr_warn("%s: Reached maximum number of attached disks.\n", PREFIX); goto out; } if (size % BYTES_PER_SECTOR != 0) { pr_err("%s: Invalid size input. Size must be a multiple of sector size %d.\n", PREFIX, BYTES_PER_SECTOR); goto out; } sectors = (size / BYTES_PER_SECTOR); list_for_each_entry(tmp, &rdsk_devices, rdsk_list) { if (tmp->num == num) goto out; } rdsk = kzalloc(sizeof(*rdsk), GFP_KERNEL); if (!rdsk) goto out; rdsk->num = num; rdsk->error_cnt = 0; rdsk->max_blk_alloc = 0; rdsk->max_page_cnt = 0; rdsk->size = size; spin_lock_init(&rdsk->rdsk_lock); INIT_RADIX_TREE(&rdsk->rdsk_pages, GFP_ATOMIC); #if LINUX_VERSION_CODE < KERNEL_VERSION(5,14,0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,9,0) rdsk->rdsk_queue = blk_alloc_queue(NUMA_NO_NODE); #else rdsk->rdsk_queue = blk_alloc_queue(rdsk_make_request, NUMA_NO_NODE); #endif if (!rdsk->rdsk_queue) goto out_free_dev; #else rdsk->rdsk_queue = blk_alloc_queue(GFP_KERNEL); if (!rdsk->rdsk_queue) goto out_free_dev; blk_queue_make_request(rdsk->rdsk_queue, rdsk_make_request); #endif #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,14,0) disk = rdsk->rdsk_disk = blk_alloc_disk(NUMA_NO_NODE); #else disk = rdsk->rdsk_disk = alloc_disk(1); #endif if (!disk) goto out_free_queue; #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,14,0) disk = rdsk->rdsk_disk = blk_alloc_disk(NUMA_NO_NODE); #else disk = rdsk->rdsk_disk = alloc_disk(1); #endif if (!disk) goto out_free_queue; #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,14,0) blk_queue_logical_block_size(disk->queue, BYTES_PER_SECTOR); blk_queue_physical_block_size(disk->queue, PAGE_SIZE); #else blk_queue_logical_block_size(rdsk->rdsk_queue, BYTES_PER_SECTOR); blk_queue_physical_block_size(rdsk->rdsk_queue, PAGE_SIZE); #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,14,0) blk_queue_write_cache(disk->queue, false, false); #else blk_queue_write_cache(rdsk->rdsk_queue, false, false); #endif #elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37) blk_queue_flush(rdsk->rdsk_queue, REQ_FLUSH); #else blk_queue_ordered(rdsk->rdsk_queue, QUEUE_ORDERED_TAG, NULL); #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,14,0) disk->queue->limits.max_sectors = (max_sectors * 2); disk->queue->nr_requests = nr_requests; disk->queue->limits.discard_granularity = PAGE_SIZE; disk->queue->limits.max_discard_sectors = UINT_MAX; #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,19,0) || (defined(RHEL_MAJOR) && RHEL_MAJOR >= 9 && RHEL_MINOR >= 0) blk_queue_max_discard_sectors(disk->queue, 0); #else blk_queue_flag_set(QUEUE_FLAG_DISCARD, disk->queue); #endif blk_queue_flag_set(QUEUE_FLAG_NONROT, disk->queue); #else rdsk->rdsk_queue->limits.max_sectors = (max_sectors * 2); rdsk->rdsk_queue->nr_requests = nr_requests; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) rdsk->rdsk_queue->limits.discard_granularity = PAGE_SIZE; #if LINUX_VERSION_CODE < KERNEL_VERSION(4,12,0) rdsk->rdsk_queue->limits.discard_zeroes_data = 1; #endif rdsk->rdsk_queue->limits.max_discard_sectors = UINT_MAX; #if LINUX_VERSION_CODE < KERNEL_VERSION(4,17,0) queue_flag_set_unlocked(QUEUE_FLAG_DISCARD, rdsk->rdsk_queue); #else blk_queue_flag_set(QUEUE_FLAG_DISCARD, rdsk->rdsk_queue); #endif #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(4,17,0) queue_flag_set_unlocked(QUEUE_FLAG_NONROT, rdsk->rdsk_queue); #else blk_queue_flag_set(QUEUE_FLAG_NONROT, rdsk->rdsk_queue); #endif #endif disk->major = rd_ma_no; disk->first_minor = num; #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,14,0) disk->minors = 1; #endif disk->fops = &rdsk_fops; disk->private_data = rdsk; #if LINUX_VERSION_CODE < KERNEL_VERSION(5,14,0) disk->queue = rdsk->rdsk_queue; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,17,0) || (defined(RHEL_MAJOR) && RHEL_MAJOR >= 9 && RHEL_MINOR >= 0) disk->flags |= GENHD_FL_NO_PART; #else disk->flags |= GENHD_FL_SUPPRESS_PARTITION_INFO; #endif sprintf(disk->disk_name, "rd%lu", num); set_capacity(disk, sectors); #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,16,0) || (defined(RHEL_MAJOR) && RHEL_MAJOR >= 9 && RHEL_MINOR >= 0) err = add_disk(disk); if (err) goto out_free_queue; #else add_disk(disk); #endif list_add_tail(&rdsk->rdsk_list, &rdsk_devices); rd_total++; pr_info("%s: Attached rd%lu of %llu bytes in size.\n", PREFIX, num, rdsk->size); return SUCCESS; out_free_queue: #if LINUX_VERSION_CODE < KERNEL_VERSION(5,14,0) blk_cleanup_queue(rdsk->rdsk_queue); out_free_dev: #endif kfree(rdsk); out: return GENERIC_ERROR; } static int detach_device(unsigned long num) { struct rdsk_device *rdsk; bool found = false; list_for_each_entry(rdsk, &rdsk_devices, rdsk_list) if (rdsk->num == num) { found = true; break; } if (!found) return GENERIC_ERROR; list_del(&rdsk->rdsk_list); del_gendisk(rdsk->rdsk_disk); put_disk(rdsk->rdsk_disk); #if LINUX_VERSION_CODE < KERNEL_VERSION(5,15,0) blk_cleanup_queue(rdsk->rdsk_queue); #endif rdsk_free_pages(rdsk); kfree(rdsk); rd_total--; pr_info("%s: Detached rd%lu.\n", PREFIX, num); return SUCCESS; } static int resize_device(unsigned long num, unsigned long long size) { struct rdsk_device *rdsk; bool found = false; sector_t sectors = 0; if (size % BYTES_PER_SECTOR != 0) { pr_err("%s: Invalid size input. Size must be a multiple of sector size %d.\n", PREFIX, BYTES_PER_SECTOR); return GENERIC_ERROR; } sectors = (size / BYTES_PER_SECTOR); list_for_each_entry(rdsk, &rdsk_devices, rdsk_list) if (rdsk->num == num) { found = true; break; } if (!found) return GENERIC_ERROR; /* WARNING - I am unable to rely on mutexes here due to its impact on performance. * As a result, if reducing to a smaller size, there is a risk of a memory leak. * If a resize is done, it should be done while no I/O is running to the device. */ if (size <= (rdsk->max_blk_alloc * BYTES_PER_SECTOR)) { pr_warn("%s: Please specify a larger size for resizing.\n", PREFIX); return GENERIC_ERROR; } set_capacity(rdsk->rdsk_disk, sectors); rdsk->size = size; pr_info("%s: Resized rd%lu of %llu bytes in size.\n", PREFIX, num, size); return SUCCESS; } static int __init init_rd(void) { int retval, i; rd_total = rd_ma_no = 0; rd_ma_no = register_blkdev(rd_ma_no, PREFIX); if (rd_ma_no < 0) { pr_err("%s: Failed registering rdsk, returned %lu\n", PREFIX, rd_ma_no); return rd_ma_no; } rdsk_kobj = kobject_create_and_add("rapiddisk", kernel_kobj); if (!rdsk_kobj) goto init_failure; retval = sysfs_create_group(rdsk_kobj, &attr_group); if (retval) goto init_failure2; for (i = 0; i < rd_nr; i++) { retval = attach_device(i, rd_size * 2048); if (retval) { pr_err("%s: Failed to load RapidDisk volume rd%d.\n", PREFIX, i); goto init_failure2; } } return SUCCESS; init_failure2: kobject_put(rdsk_kobj); init_failure: unregister_blkdev(rd_ma_no, PREFIX); return -ENOMEM; } static void __exit exit_rd(void) { struct rdsk_device *rdsk, *next; kobject_put(rdsk_kobj); list_for_each_entry_safe(rdsk, next, &rdsk_devices, rdsk_list) detach_device(rdsk->num); unregister_blkdev(rd_ma_no, PREFIX); } module_init(init_rd); module_exit(exit_rd); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Petros Koutoupis "); MODULE_DESCRIPTION("RapidDisk is an enhanced RAM disk block device driver."); MODULE_VERSION(VERSION_STR); MODULE_INFO(Copyright, "Copyright 2010 - 2023 Petros Koutoupis"); rapiddisk-9.1.0/module/rapiddisk.txt000066400000000000000000000056141442147535600175140ustar00rootroot00000000000000RapidDisk RAM disk and RapidDisk-Cache Linux modules == Description == RapidDisk was designed to be used in high performing environments and has been designed with simplicity in mind. Utilizing a user land binary, the system administrator is capable of dynamically adding new RAM based block devices of varying sizes, removing existing ones to even listing all existing RAM block devices. The rapiddisk module has been designed to allocate from the system's memory pages and is capable of addressing memory to support Gigabytes if not a Terabyte of available Random Access Memory. RapidDisk-Cache is designed to leverage the high speed performing technologies of the RapidDisk RAM disk and utilizing the Device Mapper framework, map an RapidDisk volume to act as a block device's Write/Read- through cache. This can significantly boost the performance of a local or remote disk device. == Module Parameters == RapidDisk --------- max_sectors: Maximum sectors (in KB) for the request queue. (Default = 127) (int) nr_requests: Number of requests at a given time for the request queue. (Default = 128) (int) rd_nr: Maximum number of RapidDisk devices to load on insertion. (Default = 0) (int) rd_size: Size of each RAM disk (in KB) loaded on insertion. (Default = 0) (int) rd_max_nr: Maximum number of RAM Disks. (Default = 1024) (int) RapidDisk-Cache --------- None. == Usage == RapidDisk --------- It is advised to utilize the userland utility, rapiddisk, but this is essentially what is written to the /sys/kernel/rapiddisk/mgmt sysfs file to manage RapidDisk volumes: Attach a new RapidDisk volume labeled rd0 by typing both the numeric value of the device and the size in bytes: # echo "rapiddisk attach 0 8192" > /sys/kernel/rapiddisk/mgmt Detach an existing RapidDisk volume by typing the numeric value of the device: # echo "rapiddisk detach 0" > /sys/kernel/rapiddisk/mgmt Resize an existing RapidDisk volume by typing both the numeric value of the device and the size in bytes: # echo "rapiddisk resize 0 65536" > /sys/kernel/rapiddisk/mgmt To view existing RapidDisk/RapidDisk-Cache volumes directly from the module: # cat /sys/kernel/rapiddisk/devices RapidDisk-Cache --------- It is advised to utilize the userland utility, rapiddisk, but this is essentially what is sent to the dmsetup command: Map an existing RapidDisk-Cache volume to a block device by typing: # echo 0 4194303 rapiddisk-cache /dev/sdb /dev/rd0 196608|dmsetup create rc_sdb Parameter 1: Start block of source volume (in sectors). Parameter 2: End block of source volume (in sectors). Parameter 4: Source volume. Parameter 5: Cache volume. Parameter 6: Cache size (in sectors). Unmap an RapidDisk volume: # dmsetup remove rc_sdb == Design == The memory management of the RapidDisk module was inspired by the brd Linux RAM disk module. The functionality of the RapidDisk-Cache module was inspired by Flashcache-wt and dm-cache. rapiddisk-9.1.0/pkg/000077500000000000000000000000001442147535600142675ustar00rootroot00000000000000rapiddisk-9.1.0/pkg/debian/000077500000000000000000000000001442147535600155115ustar00rootroot00000000000000rapiddisk-9.1.0/pkg/debian/README.Debian000066400000000000000000000010051442147535600175460ustar00rootroot00000000000000rapiddisk for Debian ----------------- The systemd service file "rapiddiskd.service" is copied from the source tree to the debian directory with the name changed to "rapiddiskd.service", on each build. The dh_clean target deletes all the files ending in ".orig" in the build tree, leading to the removal of two project files under "scripts/rapiddisk-rootdev". This problem is fixed overriding the dh_clean target and excluding those files. -- Matteo Tenca Sat, 07 May 2022 05:44:16 +0200 rapiddisk-9.1.0/pkg/debian/changelog000066400000000000000000000207731442147535600173740ustar00rootroot00000000000000rapiddisk (9.1.0-1) UNRELEASED; urgency=medium * module: Fixed large performance regression bug with resize mutex. * module: Added RHEL 9 support. * module: Added support for new ioctls. * module: Fixed GFP_HIGHMEM page allocation bug. * utility: Added NVMe Target loopback support. * utility: Fixed issue #155 with strlen of NULL string segfault. * utility: Reworked nvmet exports, unexports and port scanning logic (thank you Matteo Tenca). * scripts: Added example BCC tools script. * documentation: Added RPM building documentation. rapiddisk (9.0.0-1) UNRELEASED; urgency=medium * module: Added code to capture page count metrics. * module: Updated ioctl support. * module: Fixed page alloc usage decrement on discard. * module: Added shrink volume support. * module: Added support for 5.19 Linux kernels (thank you Andrea Righi). * module: Added support for 6.0 Linux kernels. * utility: Added code to lock/unlock RAM device. * utility: Added flag to suppress header in stdout (thank you Matteo Tenca). * utility: Added support for RapidDisk "model" branding and support for revalidate size in NVMe Target logic. * utility: Traced, found and fixed all discovered memory leaks (a huge undertaking, thank you Matteo Tenca!!!). * utility: Rewrite daemon to execute commands from a shared library instead of popen to utility (another huge undertaking, thank you Matteo Tenca!!!). * utility: Added checks in URL parsing logic for REST API (thank you Matteo Tenca). * utility: Fix forking logic in daemon and remove need for realpath() usage (thank you Matteo Tenca). * utility: Define and standardize error messaging for consistency and improve verbose mode (thank you Matteo Tenca). * utility: Fix resize operation error messaging (github issue #142). * utility: Cleaned up and optimized NVMe Target management. * scripts: Fixed error checking in NVMe Target hostnqn script file. * scripts: Added fio execution script file examples. * scripts: Added valgrind test script to check for memory leaks (thank you Matteo Tenca). * build: Update Makefiles and add support for CPPFLAGS, CFLAGS, LDFLAGS, etc. (thank you Matteo Tenca). * build: Update Debian and RPM packaging with updated depends (thank you Matteo Tenca). * documentation: Added dm-writecache stats notes. * documentation: Added contrib file and cleaned up README and man pages. * documentation: Added doxygen documentation support (thank you Matteo Tenca). -- Petros Koutoupis Wed, 28 Dec 2022 08:56:52 +0000 rapiddisk (8.2.0-1) UNRELEASED; urgency=medium * module: Fixed support for 5.14. * documentation: Updated README files. * utility: Fixed buffer overflow in NVMe structure. * utility: Improved on MHD version check (thank you Matteo Tenca). * utility: Clean up / optimize systemd service file. * packaging: Improved and overhauled debian packaging (thank you Matteo Tenca). * scripts: Optimized and fixed bugs for rapiddisk/cache on root during boot support (thank you Matteo Tenca). * misc: Cleaned up Makefiles and impr ved dkms build/installation process. -- Petros Koutoupis Sat, 11 Jun 2022 13:48:57 +0000 rapiddisk (8.1.0-1) UNRELEASED; urgency=medium * module: Added support for 5.16 and 5.17 kernels (thank you Nitrooo) * module: Fixed queue allocation defect for 5.15 kernel (thank you Nitrooo) * module: Fixed ram drive allocation bug when sending invalid disk number (thank you betawaffle) * utility: Expanded writecache stats to supported in 5.15+ kernels. * misc: Cleaned up Makefiles -- Petros Koutoupis Fri, 11 Feb 2022 01:15:34 +0000 rapiddisk (8.0.1-1) UNRELEASED; urgency=medium * packaging: Fixed package descriptions * misc: update authors file * misc: updated copyright * misc: added GPL disclaimer to files missing it -- Petros Koutoupis Mon, 27 Dec 2021 16:02:59 +0000 rapiddisk (8.0.0-1) UNRELEASED; urgency=medium * module: Updated for 5.14 and 5.15 kernels * utility: Added NVMe Target support / framework * utility: Added support for dm-writecache status readout in API (github issue #65) * utility: Module checker code now traverses via sysfs * utility: Added module checker in daemon * documentation: Clean up formatting and add content * packaging: Fixed dependencies * misc: Cleaned up and optimized scripts to enable rapiddisk/cache on root during boot (thank you Matteo Tenca) -- Petros Koutoupis Tue, 21 Dec 2021 15:41:27 -0600 rapiddisk (7.2.1-1) UNRELEASED; urgency=medium * module: Added support for RHEL 8.4 kernel * utility: added support for libmicrohttpd v0.9.71 and newer while still supporting legacy versions -- Petros Koutoupis Sat, 26 Jun 2021 08:20:23 -0500 rapiddisk (7.2.0-1) UNRELEASED; urgency=medium * module: Updated for 5.12 kernels and later (thank you Michael) * utility: remove unused headers (thank you Marcel Huber) * utility: add CLI support for dm-writecache wrapper * utility: Fixed property check bug when parsing sysfs block subtree params (github issue #55) -- Petros Koutoupis Sat, 12 Jun 2021 09:47:32 -0500 rapiddisk (7.1.0-1) UNRELEASED; urgency=medium * module: Updated for 5.9 kernels and later * documentation: Updated copyrights -- Petros Koutoupis Thu, 21 Jan 2021 14:35:32 +0000 rapiddisk (7.0.1-1) UNRELEASED; urgency=medium * misc: Fixed typo in utility Makefile -- Petros Koutoupis Sun, 18 Oct 2020 21:45:35 +0000 rapiddisk (7.0.0-1) UNRELEASED; urgency=medium * module: Updated for 5.8 kernels and later * module: fixed cache status format typo * daemon: Implement http-driven API to monitor/manage rapiddisk/cache functions * utility: Removing support for RHEL / CentOS 6.x * utility: Restructured userspace CLI * test: Restructured and improved test framework * misc: Code / documentation cleanup -- Petros Koutoupis Sat, 17 Oct 2020 12:42:46 -0500 rapiddisk (6.1-1) UNRELEASED; urgency=medium * kernel: added support for 5.7 kernel * utility: fixed GCC compilation warnings * utility: code style cleanup * installer: additional dkms installation/removal cleanup (thank you Shub77) * documentation: fixed / updated README to reflect newer dkms installation/removal instructions * documentation: updated copyright dates * documentation: added AUTHORS file * misc: added experimental scripts to enable rapiddisk/cache on root device during boot (thank you Shub77) -- Petros Koutoupis Sun, 31 May 2020 08:12:16 -0500 rapiddisk (6.0-1) UNRELEASED; urgency=medium * kernel: Fixed module compilation error with modern version of GCC. * utility: Remove dm-crypt code; Not sure why i had it in the first place. Doesn't really belong. Just use cryptsetup. * utility: Removed archive/restore code and dependency on zlib. Again, can just use dd and tar. Is anyone even using this? * misc: Updated licensing and logo location (thanks Boian!). -- Petros Koutoupis Fri, 24 May 2019 07:47:28 -0500 rapiddisk (5.2-1) UNRELEASED; urgency=medium * kernel: added support for 4.17 kernel. * build: Cleaned up module clean Makefile. * Updated copyright years. -- Petros Koutoupis Fri, 25 May 2018 09:35:36 -0500 rapiddisk (5.1-1) UNRELEASED; urgency=medium * kernel: added support for 4.13 kernel. * kernel: added support for 4.12 kernel (thank you Marcel Huber). * utility: fixed compilation warnings (thank you Marcel Huber). -- Petros Koutoupis Mon, 4 Sep 2017 11:44:35 -0500 rapiddisk (5.0-1) UNRELEASED; urgency=medium * kernel: Remove kernel mainline specific code (intended for brd replacement). * utility: Add JSON output for RapidDisk configuration (requires libjansson). * www: remove fat-free (f3) RESTful API. -- Petros Koutoupis Tue, 29 Nov 2016 08:29:27 -0600 rapiddisk (4.5-1) UNRELEASED; urgency=medium * kernel: cache - Fixed I/O handler bug for 4.8+ kernels * documentation: Cleaned up formatting and license disclaimers (thanks Boian!) -- Petros Koutoupis Sat, 19 Nov 2016 14:12:09 -0600 rapiddisk (4.4-1) UNRELEASED; urgency=medium * kernel: Update to 4.8 and 4.9 kernels. * build: Cleaned up Makefiles (thanks Marcel!) -- Petros Koutoupis Fri, 28 Oct 2016 14:23:34 -0500 rapiddisk-9.1.0/pkg/debian/clean000066400000000000000000000000761442147535600165210ustar00rootroot00000000000000debian/rapiddisk.install debian/rapiddisk.rapiddiskd.service rapiddisk-9.1.0/pkg/debian/compat000066400000000000000000000000031442147535600167100ustar00rootroot0000000000000012 rapiddisk-9.1.0/pkg/debian/control000066400000000000000000000017451442147535600171230ustar00rootroot00000000000000Source: rapiddisk Section: misc Priority: optional Maintainer: Petros Koutoupis Build-Depends: debhelper, dkms, libjansson4, libmicrohttpd12, libjansson-dev, libmicrohttpd-dev, libpcre2-8-0, libpcre2-dev, libdevmapper1.02.1, libdevmapper-dev Homepage: https://github.com/pkoutoupis/rapiddisk/ Standards-Version: 4.4.1 Package: rapiddisk Architecture: amd64 Depends: dkms, ${shlibs:Depends}, ${misc:Depends} Suggests: rapiddisk-on-boot Description: RapidDisk The RapidDisk software defined advanced RAM drive and storage caching solution. This suite includes a collection of modules, configuration files, and command line utilities for managing RapidDisk enabled storage volumes and accessing them either locally or across an NVMe Target network. Package: rapiddisk-on-boot Architecture: amd64 Depends: rapiddisk Description: RapidDisk Boot Install Script Allows to install a RapidDisk cache on the boot volume manipulating the initramfs-tools configuration. rapiddisk-9.1.0/pkg/debian/copyright000066400000000000000000000024261442147535600174500ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: rapiddisk Upstream-Contact: Petros Koutoupis Source: https://github.com/pkoutoupis/rapiddisk/ Files: * Copyright: 2015-2023 Petros Koutoupis License: GPL-2.0+ Files: pkg/debian/* Copyright: 2022-2023 Matteo Tenca License: GPL-2.0+ Files: scripts/rapiddisk-rootdev/* Copyright: 2022-2023 Matteo Tenca License: GPL-2.0+ License: GPL-2.0+ This package 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 package 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 . On Debian systems, the complete text of the GNU General Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". rapiddisk-9.1.0/pkg/debian/rapiddisk-on-boot.docs000066400000000000000000000001551442147535600217110ustar00rootroot00000000000000scripts/rapiddisk-rootdev/AUTHORS scripts/rapiddisk-rootdev/CHANGELOG.md scripts/rapiddisk-rootdev/README.md rapiddisk-9.1.0/pkg/debian/rapiddisk-on-boot.install000066400000000000000000000001621442147535600224250ustar00rootroot00000000000000scripts/rapiddisk-rootdev/rapiddisk-on-boot usr/sbin scripts/rapiddisk-rootdev/ubuntu usr/share/rapiddisk-on-boot rapiddisk-9.1.0/pkg/debian/rapiddisk-on-boot.manpages000066400000000000000000000000561442147535600225540ustar00rootroot00000000000000scripts/rapiddisk-rootdev/rapiddisk-on-boot.1 rapiddisk-9.1.0/pkg/debian/rapiddisk-on-boot.postinst000066400000000000000000000002241442147535600226410ustar00rootroot00000000000000#!/bin/sh case "$1" in configure) if [ -f /tmp/rapiddisk-insts ] ; then . /tmp/rapiddisk-insts rm -f /tmp/rapiddisk-insts fi ;; esac rapiddisk-9.1.0/pkg/debian/rapiddisk-on-boot.prerm000066400000000000000000000023171442147535600221100ustar00rootroot00000000000000#!/bin/sh PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" case "$1" in remove) rapiddisk-on-boot --global-uninstall || true ;; upgrade) hooks_dir="/usr/share/initramfs-tools/hooks" installations="$(for k in "$(ls 2>/dev/null /usr/share/initramfs-tools/hooks/rapiddisk_kernel* | grep -oP 'kernel_.*$' | sed 's/kernel_//')" ; do echo "$k"; done)" for inst in $installations ; do current_file="${hooks_dir:?}/rapiddisk_kernel_${inst}" file="$file ${current_file}" kernel="$kernel $inst" size="$size $(head -n 1 "$current_file")" device="$device $(head -n 2 "$current_file" | tail -n 1)" cache_mode="$cache_mode $(tail -n 1 "$current_file")" done if [ -f /tmp/rapiddisk-insts ] ; then rm -f /tmp/rapiddisk-insts fi if [ -n "$inst" ] ; then i=1 for k in $kernel ; do s=$(echo $size | cut --delimiter=" " --field=$i) c=$(echo $cache_mode | cut --delimiter=" " --field=$i) d=$(echo $device | cut --delimiter=" " --field=$i) echo >>/tmp/rapiddisk-insts "rapiddisk-on-boot --install --kernel=${k} --size=${s} --cache-mode=${c} --root=${d} --force" i=$((i+1)) done fi rapiddisk-on-boot --global-uninstall --no-initramfs || true ;; esac exit 0 rapiddisk-9.1.0/pkg/debian/rapiddisk-on-boot_kernel_prerm000066400000000000000000000002561442147535600235310ustar00rootroot00000000000000#!/bin/sh # We're passed the version of the kernel being removed inst_kern=$1 rapiddisk-on-boot --uninstall --kernel="$inst_kern" --skip-kernel-check --force || true exit 0 rapiddisk-9.1.0/pkg/debian/rapiddisk.dkms000066400000000000000000000007061442147535600203460ustar00rootroot00000000000000PACKAGE_NAME="rapiddisk" PACKAGE_VERSION="#MODULE_VERSION#" MAKE[0]="make -C ${kernel_source_dir} M=${dkms_tree}/${PACKAGE_NAME}/${PACKAGE_VERSION}/build" CLEAN="make -C ${kernel_source_dir} M=${dkms_tree}/${PACKAGE_NAME}/${PACKAGE_VERSION}/build clean" BUILT_MODULE_NAME[0]="rapiddisk" BUILT_MODULE_NAME[1]="rapiddisk-cache" DEST_MODULE_LOCATION[0]="/kernel/rapiddisk/" DEST_MODULE_LOCATION[1]="/kernel/rapiddisk/" AUTOINSTALL="yes" REMAKE_INITRD="yes" rapiddisk-9.1.0/pkg/debian/rapiddisk.install.in000066400000000000000000000003561442147535600214640ustar00rootroot00000000000000module/Makefile usr/src/rapiddisk-DEB_VERSION_UPSTREAM module/rapiddisk-cache.c usr/src/rapiddisk-DEB_VERSION_UPSTREAM module/rapiddisk.c usr/src/rapiddisk-DEB_VERSION_UPSTREAM module/rapiddisk.txt usr/src/rapiddisk-DEB_VERSION_UPSTREAM rapiddisk-9.1.0/pkg/debian/rapiddisk.manpages000066400000000000000000000000411442147535600211730ustar00rootroot00000000000000doc/rapiddisk.1 doc/rapiddiskd.1 rapiddisk-9.1.0/pkg/debian/rules000077500000000000000000000031501442147535600165700ustar00rootroot00000000000000#!/usr/bin/make -f # See debhelper(7) (uncomment to enable) # output every command that modifies files on the build system. #export DH_VERBOSE = 1 # see FEATURE AREAS in dpkg-buildflags(1) #export DEB_BUILD_MAINT_OPTIONS = hardening=+all # see ENVIRONMENT in dpkg-buildflags(1) # package maintainers to append CFLAGS #export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic # package maintainers to append LDFLAGS #export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed # see EXAMPLES in dpkg-buildflags(1) and read /usr/share/dpkg/* DPKG_EXPORT_BUILDFLAGS = 1 include /usr/share/dpkg/default.mk DEB_BUILD_OPTIONS += noautodbgsym JOBS:=$(patsubst parallel=%,-j%,$(filter parallel=%,$(DEB_BUILD_OPTIONS))) override_dh_auto_clean: $(MAKE) -$(MAKEFLAGS) $(JOBS) -C $(CURDIR)/src clean override_dh_auto_build: $(MAKE) -$(MAKEFLAGS) $(JOBS) -C $(CURDIR)/src cp $(CURDIR)/conf/rapiddiskd.service $(CURDIR)/debian/rapiddisk.rapiddiskd.service override_dh_clean: dh_clean --exclude=rapiddisk_sub.orig --exclude=run_rapiddisk.sh.orig override_dh_auto_install: $(MAKE) -$(MAKEFLAGS) $(JOBS) -C $(CURDIR)/src install DESTDIR=$(CURDIR)/debian/rapiddisk sed -e "s/DEB_VERSION_UPSTREAM/$(DEB_VERSION_UPSTREAM)/g" debian/rapiddisk.install.in > debian/rapiddisk.install override_dh_install: install -T -g root -o root -m 755 -D $(CURDIR)/debian/rapiddisk-on-boot_kernel_prerm $(CURDIR)/debian/rapiddisk-on-boot/etc/kernel/prerm.d/rapiddisk-on-boot dh_install override_dh_installsystemd: dh_installsystemd --name=rapiddiskd override_dh_auto_test: override_dh_dkms: dh_dkms -V $(DEB_VERSION_UPSTREAM) override_dh_dwz: %: dh ${@} --with dkms rapiddisk-9.1.0/pkg/debian/source/000077500000000000000000000000001442147535600170115ustar00rootroot00000000000000rapiddisk-9.1.0/pkg/debian/source/format000066400000000000000000000000141442147535600202170ustar00rootroot000000000000003.0 (quilt) rapiddisk-9.1.0/pkg/debian/watch000066400000000000000000000003001442147535600165330ustar00rootroot00000000000000version=4 opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%rapiddisk-$1.tar.gz%" \ https://github.com/pkoutoupis/rapiddisk/tags \ (?:.*?/)?v?(\d[\d.]*)\.tar\.gz debian uupdate rapiddisk-9.1.0/pkg/rpm/000077500000000000000000000000001442147535600150655ustar00rootroot00000000000000rapiddisk-9.1.0/pkg/rpm/README.md000066400000000000000000000013561442147535600163510ustar00rootroot00000000000000# Build RPM RapidDisk Packages Prepare your build environment. ```console # mkdir -p ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} # echo '%_topdir %(echo $HOME)/rpmbuild' > ~/.rpmmacros ``` In your distribution, ensure that the `rpm-build` packages is already installed. The package name may vary from distribution to distribution. Copy the appropriate spec file into the ~/rpmbuild/SPECS directory and rename it to rapiddisk.spec. Then copy the compressed tarball of rapiddisk into the ~/rpmbuild/SOURCES directory. Run the following command: ```console # rpmbuild -bb ~/rpmbuild/SPECS/rapiddisk.spec ``` If the build succeeds, the output rpm files will be placed in the ~/rpmbuild/RPMS directory and under the target architecture (i.e x86_64). rapiddisk-9.1.0/pkg/rpm/rapiddisk.spec.rhel000066400000000000000000000327401442147535600206520ustar00rootroot00000000000000Summary: The RapidDisk software defined advanced RAM drive and storage caching solution. Name: rapiddisk Version: 9.1.0 Release: 1 License: General Public License Version 2 Group: Applications/System Source: %{name}-%{version}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root Requires: gcc,make,kernel-headers,kernel-devel,dkms BuildRequires: kernel-headers,kernel-devel,gcc,make %description The RapidDisk software defined advanced RAM drive and storage caching solution. This suite includes a collection of modules, configuration files, and command line utilities for managing RapidDisk enabled storage volumes and accessing them either locally or across an NVMe Target network. %package utils Summary: The RapidDisk administration utilities. Group: Applications/System Requires: rapiddisk,jansson,libmicrohttpd,pcre2-utf16,pcre2-utf32,device-mapper-libs BuildRequires: gcc,make,jansson-devel,libmicrohttpd-devel,pcre2-devel,device-mapper-devel,systemd %description utils The RapidDisk software defined advanced RAM drive and storage caching solution. This packages includes a collection of configuration files, and command line utilities for managing RapidDisk enabled storage volumes and accessing them either locally or across an NVMe Target network. %global debug_package %{nil} %prep %setup -q %build # invokes `make` into selected subdirs only make -j -C src strip %install rm -rf %{buildroot} # no `make install` nor `make` needed here, since the modules will be compiled and # put in place into the `post` section by `dkms` # # prepare the source directory for `dkms` with the proper files mkdir -pv %{buildroot}/usr/src/rapiddisk-%{version} cd module cp -v dkms.conf %{buildroot}/usr/src/rapiddisk-%{version} cp -v {rapiddisk,rapiddisk-cache}.c %{buildroot}/usr/src/rapiddisk-%{version} cp -v Makefile %{buildroot}/usr/src/rapiddisk-%{version} # invokes `make install` into selected subdirs only cd ../src make -j DESTDIR=%{buildroot} install cd ../doc make -j DESTDIR=%{buildroot} install cd ../conf install -D -m 755 -t %{buildroot}/etc/rapiddisk rapiddisk.sh.pacemaker rapiddisk.sh.rgmanager install -D -m 644 -t %{buildroot}%{_unitdir} rapiddiskd.service # creates the `etc/sysconfig/modules/rapiddisk.modules` file which loads the rapiddisk's modules on boot mkdir -pv %{buildroot}/etc/sysconfig/modules echo -ne "#!/bin/sh\nmodprobe rapiddisk max_sectors=2048 nr_requests=1024 2>&1 >/dev/null" > %{buildroot}/etc/sysconfig/modules/rapiddisk.modules echo -e "\nmodprobe rapiddisk-cache 2>&1 >/dev/null" >> %{buildroot}/etc/sysconfig/modules/rapiddisk.modules chmod +x %{buildroot}/etc/sysconfig/modules/rapiddisk.modules mkdir -pv %{buildroot}/usr/lib/systemd/system-preset echo "enable rapiddiskd.service" >%{buildroot}/usr/lib/systemd/system-preset/30-rapiddiskd.preset %post # invokes dkms to add the source files installed in the `install` section. Note the `--rpm_safe_upgrade` option dkms --rpm_safe_upgrade add -m rapiddisk -v %{version} # invokes dkms to build and install the modules for the current kernel dkms install -m rapiddisk -v %{version} # loads the modules sh /etc/sysconfig/modules/rapiddisk.modules %post utils %systemd_post rapiddiskd.service %preun # note the `--rpm_safe_upgrade` option dkms --rpm_safe_upgrade remove -m rapiddisk -v %{version} --all %preun utils %systemd_preun rapiddiskd.service %postun %postun utils %systemd_postun_with_restart rapiddiskd.service %clean rm -rf %{buildroot} %files %defattr(-,root,root) /usr/src/rapiddisk-* %attr(0755,root,root) /etc/sysconfig/modules/rapiddisk.modules %doc %attr(0444,root,root) /usr/share/man/man1/* %files utils %defattr(-,root,root) %attr(0644,root,root) %{_unitdir}/rapiddiskd.service %attr(0755,root,root) /etc/rapiddisk %attr(0755,root,root) /sbin/rapiddisk %attr(0755,root,root) /sbin/rapiddiskd %attr(0644,root,root) /usr/lib/systemd/system-preset/30-rapiddiskd.preset %changelog * Sun Apr 23 2023 Petros Koutoupis - module: Fixed large performance regression bug with resize mutex. - module: Added RHEL 9 support. - module: Added support for new ioctls. - module: Fixed GFP_HIGHMEM page allocation bug. - utility: Added NVMe Target loopback support. - utility: Fixed issue #155 with strlen of NULL string segfault. - utility: Reworked nvmet exports, unexports and port scanning logic (thank you Matteo Tenca). - scripts: Added example BCC tools script. - documentation: Added RPM building documentation. * Wed Dec 28 2022 Petros Koutoupis - module: Added code to capture page count metrics. - module: Updated ioctl support. - module: Fixed page alloc usage decrement on discard. - module: Added shrink volume support. - module: Added support for 5.19 Linux kernels (thank you Andrea Righi). - module: Added support for 6.0 Linux kernels. - utility: Added code to lock/unlock RAM device. - utility: Added flag to suppress header in stdout (thank you Matteo Tenca). - utility: Added support for RapidDisk "model" branding and support for revalidate size in NVMe Target logic. - utility: Traced, found and fixed all discovered memory leaks (a huge undertaking, thank you Matteo Tenca!!!). - utility: Rewrite daemon to execute commands from a shared library instead of popen to utility (another huge undertaking, thank you Matteo Tenca!!!). - utility: Added checks in URL parsing logic for REST API (thank you Matteo Tenca). - utility: Fix forking logic in daemon and remove need for realpath() usage (thank you Matteo Tenca). - utility: Define and standardize error messaging for consistency and improve verbose mode (thank you Matteo Tenca). - utility: Fix resize operation error messaging (github issue #142). - utility: Cleaned up and optimized NVMe Target management. - scripts: Fixed error checking in NVMe Target hostnqn script file. - scripts: Added fio execution script file examples. - scripts: Added valgrind test script to check for memory leaks (thank you Matteo Tenca). - build: Update Makefiles and add support for CPPFLAGS, CFLAGS, LDFLAGS, etc. (thank you Matteo Tenca). - build: Update Debian and RPM packaging with updated depends (thank you Matteo Tenca). - documentation: Added dm-writecache stats notes. - documentation: Added contrib file and cleaned up README and man pages. - documentation: Added doxygen documentation support (thank you Matteo Tenca). * Sat Jun 11 2022 Petros Koutoupis - module: Fixed support for 5.14. - documentation: Updated README files. - utility: Fixed buffer overflow in NVMe structure. - utility: Improved on MHD version check (thank you Matteo Tenca). - utility: Clean up / optimize systemd service file. - packaging: Improved and overhauled debian packaging (thank you Matteo Tenca). - scripts: Optimized and fixed bugs for rapiddisk/cache on root during boot support (thank you Matteo Tenca). - misc: Cleaned up Makefiles and impr ved dkms build/installation process. * Fri Feb 11 2022 Petros Koutoupis - module: Added support for 5.16 and 5.17 kernels (thank you Nitrooo) - module: Fixed queue allocation defect for 5.15 kernel (thank you Nitrooo) - module: Fixed ram drive allocation bug when sending invalid disk number (thank you betawaffle) - utility: Expanded writecache stats to supported in 5.15+ kernels. - misc: Cleaned up Makefiles * Thu Dec 23 2021 Petros Koutoupis - packaging: Fixed package descriptions - misc: update authors file - misc: updated copyright - misc: added GPL disclaimer to files missing it * Fri Dec 17 2021 Petros Koutoupis - module: Updated for 5.14 and 5.15 kernels - utility: Added NVMe Target support / framework - utility: Added support for dm-writecache status readout in API (github issue ##65) - utility: Module checker code now traverses via sysfs - utility: Added module checker in daemon - documentation: Clean up formatting and add content - packaging: Fixed dependencies - misc: Cleaned up and optimized scripts to enable rapiddisk/cache on root during boot (thank you Matteo Tenca) * Sun Jun 13 2021 Petros Koutoupis - module: Added support for RHEL 8.4 kernel - utility: added support for libmicrohttpd v0.9.71 and newer while still supporting legacy versions * Fri May 28 2021 Petros Koutoupis - module: Updated for 5.12 kernels and later (thank you Michael) - utility: remove unused headers (thank you Marcel Huber) - utility: add CLI support for dm-writecache wrapper - utility: Fixed property check bug when parsing sysfs block subtree params (github issue #55) * Thu Jan 21 2021 Petros Koutoupis - module: Updated for 5.9 kernels and later - documentation: Updated copyrights * Sun Oct 18 2020 Petros Koutoupis - misc: Fixed typo in utility Makefile * Sun Oct 11 2020 Petros Koutoupis - module: Updated for 5.8 kernels and later - module: fixed cache status format typo - daemon: Implement http-driven API to monitor/manage rapiddisk/cache functions - utility: Removing support for RHEL / CentOS 6.x - utility: Restructured userspace CLI - test: Restructured and improved test framework - misc: Code / documentation cleanup * Tue May 26 2020 Petros Koutoupis - kernel: added support for 5.7 kernel - utility: fixed GCC compilation warnings - utility: code style cleanup - installer: additional dkms installation/removal cleanup (thank you Shub77) - documentation: fixed / updated README to reflect newer dkms installation/removal instructions - documentation: updated copyright dates - documentation: added AUTHORS file - misc: added experimental scripts to enable rapiddisk/cache on root device during boot (thank you Shub77) * Thu May 2 2019 Petros Koutoupis - kernel: Fixed module compilation error with modern version of GCC. - utility: Remove dm-crypt code; Not sure why i had it in the first place. Doesn't really belong. Just use cryptsetup. - utility: Removed archive/restore code and dependency on zlib. Again, can just use dd and tar. Is anyone even using this? - misc: Updated licensing and logo location (thanks Boian!). * Fri May 25 2018 Petros Koutoupis - kernel: added support for 4.17 kernel. - build: Cleaned up module clean Makefile. - Updated copyright years. * Mon Sep 4 2017 Petros Koutoupis - kernel: added support for 4.13 kernel. - kernel: added support for 4.12 kernel (thank you Marcel Huber). - utility: fixed compilation warnings (thank you Marcel Huber). * Fri Nov 25 2016 Petros Koutoupis - kernel: Remove kernel mainline specific code (intended for brd replacement). - utility: Add JSON output for RapidDisk configuration (requires libjansson). - www: remove fat-free (f3) RESTful API. * Sat Nov 19 2016 Petros Koutoupis - kernel: cache - Fixed I/O handler bug for 4.8+ kernels - documentation: Cleaned up formatting and license disclaimers (thanks Boian!) * Fri Oct 28 2016 Petros Koutoupis - kernel: Update to 4.8 and 4.9 kernels. - build: Cleaned up Makefiles (thanks Marcel!) * Fri Aug 12 2016 Petros Koutoupis - kernel: Add support for the 4.7 kernel (patch supplied by Marcel Huber) * Sun May 22 2016 Petros Koutoupis - utility: Added more complex default DES key with backwards compatibility to legacy key. - ha: added write around support to HA resource agents. - documentation: corrections / clean up. * Tue May 17 2016 Petros Koutoupis - kernel: Added Write-Around support to rapiddisk-cache. - kernel: Fixed LINUX_VERSION check for rapiddisk-cache to accommodate changes in 3.8.3. - utility: Added a NOCRYPT build flag. - utility: Added user definable keys for encryption setup. * Tue May 10 2016 Petros Koutoupis - kernel: Readjusted misaligned discard request check to build on kernels older than 4.3. * Mon May 9 2016 Petros Koutoupis - kernel: Refuse misaligned discard requests. - kernel: Convert ENOMEM to ENOSPC when cannot alloc pages. - kernel: Added 4k physical block size attribute * Fri Apr 22 2016 Petros Koutoupis - Code cleanup. - Also need to remove CONFIG_BLK_DEV_RAM_COUNT from distro specific packages. * Thu Apr 7 2016 Petros Koutoupis - Fixed all references of RapidCache to RapidDisk-Cache. * Fri Apr 1 2016 Petros Koutoupis - Updated fatfree-framework. - Addressed bug in RapidDisk REST implementation. * Sat Mar 12 2016 Petros Koutoupis - Fixed libcryptsetup build error for RHEL6. - Renamed rxdsk/rxcache modules. - Did massive cleanup of administration utility code. - Converted most (if not all) return codes to POSIX.1 error numbers. - Code cleanup in RESTful API. - RapidDisk volumes now show up as non-rotational. - Cleaned up ioctls in both module and utilies. * Sun Feb 21 2016 Petros Koutoupis - Cleaned up kernel module code. - Fixed stack overflow bug in administration utility. - Fixed error print statement in administration utility. - Updated copyright years. - Cleaned up build environment, including Makefiles. - Fixed bug in configuring encryption on device. * Wed Feb 10 2016 Petros Koutoupis - Fixed errors in documentation. * Thu Aug 13 2015 Petros Koutoupis - Adding a separate utils only build. * Tue Jul 7 2015 Petros Koutoupis - Initial RPM package build. rapiddisk-9.1.0/pkg/rpm/rapiddisk.spec.sles000066400000000000000000000327501442147535600206670ustar00rootroot00000000000000Summary: The RapidDisk software defined advanced RAM drive and storage caching solution. Name: rapiddisk Version: 9.1.0 Release: 1 License: General Public License Version 2 Group: Applications/System Source: %{name}-%{version}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root Requires: gcc,make,kernel-headers,kernel-devel,dkms BuildRequires: kernel-headers,kernel-devel,gcc,make %description The RapidDisk software defined advanced RAM drive and storage caching solution. This suite includes a collection of modules, configuration files, and command line utilities for managing RapidDisk enabled storage volumes and accessing them either locally or across an NVMe Target network. %package utils Summary: The RapidDisk administration utilities. Group: Applications/System Requires: rapiddisk,libjansson4,libmicrohttpd12,libpcre2-16-0,libpcre2-32-0,device-mapper BuildRequires: gcc,make,libjansson-devel,libmicrohttpd-devel,pcre2-devel,device-mapper-devel,systemd %description utils The RapidDisk software defined advanced RAM drive and storage caching solution. This packages includes a collection of configuration files, and command line utilities for managing RapidDisk enabled storage volumes and accessing them either locally or across an NVMe Target network. %global debug_package %{nil} %prep %setup -q %build # invokes `make` into selected subdirs only make -j -C src strip %install rm -rf %{buildroot} # no `make install` nor `make` needed here, since the modules will be compiled and # put in place into the `post` section by `dkms` # # prepare the source directory for `dkms` with the proper files mkdir -pv %{buildroot}/usr/src/rapiddisk-%{version} cd module cp -v dkms.conf %{buildroot}/usr/src/rapiddisk-%{version} cp -v {rapiddisk,rapiddisk-cache}.c %{buildroot}/usr/src/rapiddisk-%{version} cp -v Makefile %{buildroot}/usr/src/rapiddisk-%{version} # invokes `make install` into selected subdirs only cd ../src make -j DESTDIR=%{buildroot} install cd ../doc make -j DESTDIR=%{buildroot} install cd ../conf install -D -m 755 -t %{buildroot}/etc/rapiddisk rapiddisk.sh.pacemaker rapiddisk.sh.rgmanager install -D -m 644 -t %{buildroot}%{_unitdir} rapiddiskd.service # creates the `etc/sysconfig/modules/rapiddisk.modules` file which loads the rapiddisk's modules on boot mkdir -pv %{buildroot}/etc/sysconfig/modules echo -ne "#!/bin/sh\nmodprobe rapiddisk max_sectors=2048 nr_requests=1024 2>&1 >/dev/null" > %{buildroot}/etc/sysconfig/modules/rapiddisk.modules echo -e "\nmodprobe rapiddisk-cache 2>&1 >/dev/null" >> %{buildroot}/etc/sysconfig/modules/rapiddisk.modules chmod +x %{buildroot}/etc/sysconfig/modules/rapiddisk.modules mkdir -pv %{buildroot}/usr/lib/systemd/system-preset echo "enable rapiddiskd.service" >%{buildroot}/usr/lib/systemd/system-preset/30-rapiddiskd.preset %post # invokes dkms to add the source files installed in the `install` section. Note the `--rpm_safe_upgrade` option dkms --rpm_safe_upgrade add -m rapiddisk -v %{version} # invokes dkms to build and install the modules for the current kernel dkms install -m rapiddisk -v %{version} # loads the modules sh /etc/sysconfig/modules/rapiddisk.modules %post utils %systemd_post rapiddiskd.service %preun # note the `--rpm_safe_upgrade` option dkms --rpm_safe_upgrade remove -m rapiddisk -v %{version} --all %preun utils %systemd_preun rapiddiskd.service %postun %postun utils %systemd_postun_with_restart rapiddiskd.service %clean rm -rf %{buildroot} %files %defattr(-,root,root) /usr/src/rapiddisk-* %attr(0755,root,root) /etc/sysconfig/modules/rapiddisk.modules %doc %attr(0444,root,root) /usr/share/man/man1/* %files utils %defattr(-,root,root) %attr(0644,root,root) %{_unitdir}/rapiddiskd.service %attr(0755,root,root) /etc/rapiddisk %attr(0755,root,root) /sbin/rapiddisk %attr(0755,root,root) /sbin/rapiddiskd %attr(0644,root,root) /usr/lib/systemd/system-preset/30-rapiddiskd.preset %changelog * Sun Apr 23 2023 Petros Koutoupis - module: Fixed large performance regression bug with resize mutex. - module: Added RHEL 9 support. - module: Added support for new ioctls. - module: Fixed GFP_HIGHMEM page allocation bug. - utility: Added NVMe Target loopback support. - utility: Fixed issue #155 with strlen of NULL string segfault. - utility: Reworked nvmet exports, unexports and port scanning logic (thank you Matteo Tenca). - scripts: Added example BCC tools script. - documentation: Added RPM building documentation. * Wed Dec 28 2022 Petros Koutoupis - module: Added code to capture page count metrics. - module: Updated ioctl support. - module: Fixed page alloc usage decrement on discard. - module: Added shrink volume support. - module: Added support for 5.19 Linux kernels (thank you Andrea Righi). - module: Added support for 6.0 Linux kernels. - utility: Added code to lock/unlock RAM device. - utility: Added flag to suppress header in stdout (thank you Matteo Tenca). - utility: Added support for RapidDisk "model" branding and support for revalidate size in NVMe Target logic. - utility: Traced, found and fixed all discovered memory leaks (a huge undertaking, thank you Matteo Tenca!!!). - utility: Rewrite daemon to execute commands from a shared library instead of popen to utility (another huge undertaking, thank you Matteo Tenca!!!). - utility: Added checks in URL parsing logic for REST API (thank you Matteo Tenca). - utility: Fix forking logic in daemon and remove need for realpath() usage (thank you Matteo Tenca). - utility: Define and standardize error messaging for consistency and improve verbose mode (thank you Matteo Tenca). - utility: Fix resize operation error messaging (github issue #142). - utility: Cleaned up and optimized NVMe Target management. - scripts: Fixed error checking in NVMe Target hostnqn script file. - scripts: Added fio execution script file examples. - scripts: Added valgrind test script to check for memory leaks (thank you Matteo Tenca). - build: Update Makefiles and add support for CPPFLAGS, CFLAGS, LDFLAGS, etc. (thank you Matteo Tenca). - build: Update Debian and RPM packaging with updated depends (thank you Matteo Tenca). - documentation: Added dm-writecache stats notes. - documentation: Added contrib file and cleaned up README and man pages. - documentation: Added doxygen documentation support (thank you Matteo Tenca). * Sat Jun 11 2022 Petros Koutoupis - module: Fixed support for 5.14. - documentation: Updated README files. - utility: Fixed buffer overflow in NVMe structure. - utility: Improved on MHD version check (thank you Matteo Tenca). - utility: Clean up / optimize systemd service file. - packaging: Improved and overhauled debian packaging (thank you Matteo Tenca). - scripts: Optimized and fixed bugs for rapiddisk/cache on root during boot support (thank you Matteo Tenca). - misc: Cleaned up Makefiles and impr ved dkms build/installation process. * Fri Feb 11 2022 Petros Koutoupis - module: Added support for 5.16 and 5.17 kernels (thank you Nitrooo) - module: Fixed queue allocation defect for 5.15 kernel (thank you Nitrooo) - module: Fixed ram drive allocation bug when sending invalid disk number (thank you betawaffle) - utility: Expanded writecache stats to supported in 5.15+ kernels. - misc: Cleaned up Makefiles * Thu Dec 23 2021 Petros Koutoupis - packaging: Fixed package descriptions - misc: update authors file - misc: updated copyright - misc: added GPL disclaimer to files missing it * Fri Dec 17 2021 Petros Koutoupis - module: Updated for 5.14 and 5.15 kernels - utility: Added NVMe Target support / framework - utility: Added support for dm-writecache status readout in API (github issue ##65) - utility: Module checker code now traverses via sysfs - utility: Added module checker in daemon - documentation: Clean up formatting and add content - packaging: Fixed dependencies - misc: Cleaned up and optimized scripts to enable rapiddisk/cache on root during boot (thank you Matteo Tenca) * Sun Jun 13 2021 Petros Koutoupis - module: Added support for RHEL 8.4 kernel - utility: added support for libmicrohttpd v0.9.71 and newer while still supporting legacy versions * Fri May 28 2021 Petros Koutoupis - module: Updated for 5.12 kernels and later (thank you Michael) - utility: remove unused headers (thank you Marcel Huber) - utility: add CLI support for dm-writecache wrapper - utility: Fixed property check bug when parsing sysfs block subtree params (github issue #55) * Thu Jan 21 2021 Petros Koutoupis - module: Updated for 5.9 kernels and later - documentation: Updated copyrights * Sun Oct 18 2020 Petros Koutoupis - misc: Fixed typo in utility Makefile * Sun Oct 11 2020 Petros Koutoupis - module: Updated for 5.8 kernels and later - module: fixed cache status format typo - daemon: Implement http-driven API to monitor/manage rapiddisk/cache functions - utility: Removing support for RHEL / CentOS 6.x - utility: Restructured userspace CLI - test: Restructured and improved test framework - misc: Code / documentation cleanup * Tue May 26 2020 Petros Koutoupis - kernel: added support for 5.7 kernel - utility: fixed GCC compilation warnings - utility: code style cleanup - installer: additional dkms installation/removal cleanup (thank you Shub77) - documentation: fixed / updated README to reflect newer dkms installation/removal instructions - documentation: updated copyright dates - documentation: added AUTHORS file - misc: added experimental scripts to enable rapiddisk/cache on root device during boot (thank you Shub77) * Thu May 2 2019 Petros Koutoupis - kernel: Fixed module compilation error with modern version of GCC. - utility: Remove dm-crypt code; Not sure why i had it in the first place. Doesn't really belong. Just use cryptsetup. - utility: Removed archive/restore code and dependency on zlib. Again, can just use dd and tar. Is anyone even using this? - misc: Updated licensing and logo location (thanks Boian!). * Fri May 25 2018 Petros Koutoupis - kernel: added support for 4.17 kernel. - build: Cleaned up module clean Makefile. - Updated copyright years. * Mon Sep 4 2017 Petros Koutoupis - kernel: added support for 4.13 kernel. - kernel: added support for 4.12 kernel (thank you Marcel Huber). - utility: fixed compilation warnings (thank you Marcel Huber). * Fri Nov 25 2016 Petros Koutoupis - kernel: Remove kernel mainline specific code (intended for brd replacement). - utility: Add JSON output for RapidDisk configuration (requires libjansson). - www: remove fat-free (f3) RESTful API. * Sat Nov 19 2016 Petros Koutoupis - kernel: cache - Fixed I/O handler bug for 4.8+ kernels - documentation: Cleaned up formatting and license disclaimers (thanks Boian!) * Fri Oct 28 2016 Petros Koutoupis - kernel: Update to 4.8 and 4.9 kernels. - build: Cleaned up Makefiles (thanks Marcel!) * Fri Aug 12 2016 Petros Koutoupis - kernel: Add support for the 4.7 kernel (patch supplied by Marcel Huber) * Sun May 22 2016 Petros Koutoupis - utility: Added more complex default DES key with backwards compatibility to legacy key. - ha: added write around support to HA resource agents. - documentation: corrections / clean up. * Tue May 17 2016 Petros Koutoupis - kernel: Added Write-Around support to rapiddisk-cache. - kernel: Fixed LINUX_VERSION check for rapiddisk-cache to accommodate changes in 3.8.3. - utility: Added a NOCRYPT build flag. - utility: Added user definable keys for encryption setup. * Tue May 10 2016 Petros Koutoupis - kernel: Readjusted misaligned discard request check to build on kernels older than 4.3. * Mon May 9 2016 Petros Koutoupis - kernel: Refuse misaligned discard requests. - kernel: Convert ENOMEM to ENOSPC when cannot alloc pages. - kernel: Added 4k physical block size attribute * Fri Apr 22 2016 Petros Koutoupis - Code cleanup. - Also need to remove CONFIG_BLK_DEV_RAM_COUNT from distro specific packages. * Thu Apr 7 2016 Petros Koutoupis - Fixed all references of RapidCache to RapidDisk-Cache. * Fri Apr 1 2016 Petros Koutoupis - Updated fatfree-framework. - Addressed bug in RapidDisk REST implementation. * Sat Mar 12 2016 Petros Koutoupis - Fixed libcryptsetup build error for RHEL6. - Renamed rxdsk/rxcache modules. - Did massive cleanup of administration utility code. - Converted most (if not all) return codes to POSIX.1 error numbers. - Code cleanup in RESTful API. - RapidDisk volumes now show up as non-rotational. - Cleaned up ioctls in both module and utilies. * Sun Feb 21 2016 Petros Koutoupis - Cleaned up kernel module code. - Fixed stack overflow bug in administration utility. - Fixed error print statement in administration utility. - Updated copyright years. - Cleaned up build environment, including Makefiles. - Fixed bug in configuring encryption on device. * Wed Feb 10 2016 Petros Koutoupis - Fixed errors in documentation. * Thu Aug 13 2015 Petros Koutoupis - Adding a separate utils only build. * Tue Jul 7 2015 Petros Koutoupis - Initial RPM package build. rapiddisk-9.1.0/scripts/000077500000000000000000000000001442147535600151755ustar00rootroot00000000000000rapiddisk-9.1.0/scripts/bcc-tools/000077500000000000000000000000001442147535600170625ustar00rootroot00000000000000rapiddisk-9.1.0/scripts/bcc-tools/README.md000066400000000000000000000007241442147535600203440ustar00rootroot00000000000000# Kernel profiling using BCC tools This directory contains assorted script files to profile rapiddisk module functions utilizing the BCC suite. In order to leverage these scripts you will need to ensure that the `bcc` tools are installed on your system. If you are building them from source, you can download the latest from: `https://github.com/iovisor/bcc` In Ubuntu, install `bcc` and `bpfcc-tools`. In RedHat (and CentOS and Rocky Linux) install `bcc-tools`. rapiddisk-9.1.0/scripts/bcc-tools/rapiddisk-delta-example.txt000066400000000000000000000021511442147535600243140ustar00rootroot00000000000000Demonstration of rapiddisk-delta, the Linux eBPF/bcc version. rapiddisk-delta shows rapiddisk make_request and ioctl invocations and the time it took for the commands to return. For example: # ./rapiddisk-delta.py TIME(s) CPU COMM PID MESSAGE 406.054258000 0 b'probe-bcache' 1510 b'op: rdsk_make_request() delta (ns): 7066' 406.054281000 0 b'probe-bcache' 1510 b'op: rdsk_make_request() delta (ns): 3773' 406.054294000 0 b'probe-bcache' 1510 b'op: rdsk_make_request() delta (ns): 2819' 406.095975000 0 b'multipathd' 831 b'op: rdsk_ioctl() delta (ns): 6356' 406.095993000 0 b'multipathd' 831 b'op: rdsk_ioctl() delta (ns): 1270' 417.443112000 0 b'dd' 1527 b'op: rdsk_make_request() delta (ns): 7393' 417.443163000 0 b'dd' 1527 b'op: rdsk_make_request() delta (ns): 4580' [...] In a future revision, more details such as function operation type information will be included such as if it were a read, write, discard, etc. command. rapiddisk-9.1.0/scripts/bcc-tools/rapiddisk-delta.py000066400000000000000000000100301442147535600224670ustar00rootroot00000000000000#!/usr/bin/python # rapiddisk-delta Trace rapiddisk events and deltas between start to finish. # For Linux, uses BCC, eBPF. # # Todo: Add operation types (i.e. read, write, discard, etc). I am having # a difficult time grabbing the value of bio->bi_opf. # # Copyright 2023 Petros Koutoupis # Licensed under the Apache License, Version 2.0 (the "License") # # 5-Feb-2023 Petros Koutoupis Created this. from bcc import BPF import argparse # arguments examples = """examples: ./rapiddisk-delta.py # trace all operations (default) ./rapiddisk-delta.py -u 10 # trace operations slower than specified microseconds (default: 0) ./rapiddisk-delta.py -p 185 # trace PID 185 only """ parser = argparse.ArgumentParser( description="Trace rapiddisk operations", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=examples) parser.add_argument("-u", "--min_us", default=0, help="trace operations slower than specified microseconds") parser.add_argument("-p", "--pid", help="trace this PID only") args = parser.parse_args() min_us = (int(args.min_us) * 1000) pid = args.pid # define BPF program prog = """ #include #include struct val_t { u64 ts; }; BPF_HASH(entryinfo, u64, struct val_t); int trace_entry(struct pt_regs *ctx) { u64 id = bpf_get_current_pid_tgid(); u32 pid = id >> 32; // PID is higher part struct val_t val = {}; if (FILTER_PID) return 0; val.ts = bpf_ktime_get_ns(); entryinfo.update(&id, &val); return 0; } static u64 delta_return(void) { struct val_t *valp; u64 id = bpf_get_current_pid_tgid(); valp = entryinfo.lookup(&id); if (valp == 0) { // missed tracing issue or filtered return 0; } // calculate delta u64 ts = bpf_ktime_get_ns(); u64 delta_ns = ts - valp->ts; entryinfo.delete(&id); return delta_ns; } int trace_ioctl_return(struct pt_regs *ctx) { u64 delta_ns = delta_return(); u32 pid = bpf_get_current_pid_tgid() >> 32; // PID is higher part if (FILTER_PID) return 0; // delta of operation is less than the specified minimum time if (delta_ns <= FILTER_US) return 0; bpf_trace_printk("op: rdsk_ioctl() delta (ns): %llu\\n", delta_ns); return 0; } int trace_make_request_return(struct pt_regs *ctx) { u64 delta_ns = delta_return(); u32 pid = bpf_get_current_pid_tgid() >> 32; // PID is higher part if (FILTER_PID) return 0; // delta of operation is less than the specified minimum time if (delta_ns <= FILTER_US) return 0; #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,9,0) bpf_trace_printk("op: rdsk_submit_bio() delta (ns): %llu\\n", delta_ns); #else bpf_trace_printk("op: rdsk_make_request() delta (ns): %llu\\n", delta_ns); #endif return 0; } """ # Starting from Linux 5.9, make_request operations were replaced with submit_bio. # We are detecing proper functions to trace here. if BPF.get_kprobe_functions(b'rdsk_submit_bio'): make_request_rn = 'rdsk_submit_bio' else: make_request_fn = 'rdsk_make_request' # code replacement if args.pid: prog = prog.replace('FILTER_PID', 'pid != %s' % pid) else: prog = prog.replace('FILTER_PID', '0') if min_us == 0: prog = prog.replace('FILTER_US', '0') else: prog = prog.replace('FILTER_US', '%s' % str(min_us)) # load BPF program b = BPF(text=prog) b.attach_kprobe(event="rdsk_ioctl", fn_name="trace_entry") b.attach_kprobe(event=make_request_fn, fn_name="trace_entry") b.attach_kretprobe(event="rdsk_ioctl", fn_name="trace_ioctl_return") b.attach_kretprobe(event=make_request_fn, fn_name="trace_make_request_return") # header print("Tracing rapiddisk operation latency... Hit Ctrl-C to end.") print("%-18s %-6s %-16s %-6s %s" % ("TIME(s)", "CPU", "COMM", "PID", "MESSAGE")) # format output while 1: try: (task, pid, cpu, flags, ts, msg) = b.trace_fields() except ValueError: continue print("%-18.9f %-6s %-16s %-6d %s" % (ts, cpu, task, pid, msg)) rapiddisk-9.1.0/scripts/fio/000077500000000000000000000000001442147535600157525ustar00rootroot00000000000000rapiddisk-9.1.0/scripts/fio/fio_simple_4k_1g_raw_randread_dio.sh000066400000000000000000000005341442147535600247670ustar00rootroot00000000000000#!/bin/bash if [ ! "$BASH_VERSION" ] ; then exec /bin/bash "$0" "$@" fi [ $# -ne "1" ] && echo "Error. Please input a RapidDisk or RapidDisk-Cache device." fio --bs=4k --ioengine=libaio --iodepth=32 --size=1g --direct=1 --runtime=60 --filename=$1 --rw=randread --name=fio-rapiddisk-randread-test --numjobs=8 --group_reporting exit $? rapiddisk-9.1.0/scripts/fio/fio_simple_4k_1g_raw_randwrite_dio.sh000066400000000000000000000005371442147535600252110ustar00rootroot00000000000000#!/bin/bash if [ ! "$BASH_VERSION" ] ; then exec /bin/bash "$0" "$@" fi [ $# -ne "1" ] && echo "Error. Please input a RapidDisk or RapidDisk-Cache device." fio --bs=4k --ioengine=libaio --iodepth=32 --size=1g --direct=1 --runtime=60 --filename=$1 --rw=randwrite --name=fio-rapiddisk-readnwrite-test --numjobs=8 --group_reporting exit $? rapiddisk-9.1.0/scripts/nvmet/000077500000000000000000000000001442147535600163265ustar00rootroot00000000000000rapiddisk-9.1.0/scripts/nvmet/README.md000066400000000000000000000003471442147535600176110ustar00rootroot00000000000000## Description Assorted NVMe Target related scripts to manage RapidDisk volumes across a TCP network. ***Note -** Port 4420 for both incoming and outgoing traffic will need to be open for NVMe targets to be imported / exported.* rapiddisk-9.1.0/scripts/nvmet/nvmet-generate-hostnqn.sh000066400000000000000000000052671442147535600233050ustar00rootroot00000000000000#!/bin/bash # Copyright © 2021 - 2023 Petros Koutoupis # All rights reserved. # # 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 . # # SPDX-License-Identifier: GPL-2.0-or-later if [ ! "$BASH_VERSION" ] ; then exec /bin/bash "$0" "$@" fi ## usage ## function help_menu() { echo -e "$1 Generate or set a Host NQN to the local system." echo -e "" echo -e "usage: sh $1 [parameter: force]" echo -e "" echo -e "Functions:" echo -e "\t--generate\tGenerate a random Host NQN but do not set it to the local system." echo -e "\t--get\tGet the Host NQN from the local system." echo -e "\t--set\tSet the randomly generated Host NQN to the local system." echo -e "" echo -e "Parameters:" echo -e "\t[force]\t\tOverride a pre-existing host NQN." echo -e "" exit 0 } # # # echo -e "$0 1.0.0" echo -e "" [ $# -lt "1" ] && help_menu $0 # Example Host NQN: # nqn.2021-06.org.rapiddisk:- force=0 arr=($@) case "${arr[0]}" in --generate) echo "nqn.2021-06.org.rapiddisk:`hostname`-`(echo "obase=16"; echo $((10000 + ${RANDOM} % 100000))) | bc`" ;; --get) if [ ! -e /etc/nvme/hostnqn ]; then echo -e "A Host NQN has not been set" exit 1 else cat /etc/nvme/hostnqn fi ;; --set) if [ $# -gt "1" ]; then if [ ${arr[1]} = "force" ]; then force=1 fi fi if [ -e /etc/nvme/hostnqn ] && [ ${force} -ne 1 ]; then echo -e "A Host NQN file already exists. If you wish to override, please set the \"force\" option." else NQN=`echo "nqn.2021-06.org.rapiddisk:\`hostname\`-\`(echo "obase=16"; echo $((10000 + ${RANDOM} % 100000))) | bc\`"` if [ ! -d /etc/nvme ]; then mkdir -p /etc/nvme if [ $? -ne 0 ]; then echo -e "Error. Unable to create the /etc/nvme directory." exit 1 fi fi echo "${NQN}" > /etc/nvme/hostnqn if [ $? -ne 0 ]; then echo "Error. Unable to set ${NQN} to /etc/nvme/hostnqn." exit 1 else echo "${NQN} has been set to /etc/nvme/hostnqn." fi fi ;; --help) help_menu $0 ;; *) echo -ne "Option ${arr[0]} does not exist.\n\n" exit 1 esac exit 0 rapiddisk-9.1.0/scripts/nvmet/nvmet-tune-system.sh000066400000000000000000000060201442147535600223040ustar00rootroot00000000000000#!/bin/bash # Copyright © 2021 - 2023 Petros Koutoupis # All rights reserved. # # 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 . # # SPDX-License-Identifier: GPL-2.0-or-later if [ ! "$BASH_VERSION" ] ; then exec /bin/bash "$0" "$@" fi ## Usage ## function help_menu() { echo -e "$1 Tune parts of the system for NVMe Target support." echo -e "" echo -e "usage: sh $1 " echo -e "" echo -e "Functions:" echo -e "\t--cpu\tDisable CPU idling and adjust frequency scaling." echo -e "\t--mod\tLoad NVMeT modules for tcp and rdma." echo -e "" echo -e "Examples:" echo -e "\tsh $1 --cpu" echo -e "\tsh $1 --mod" exit 0 } ## Enable NVMeT load kernel modules ## function nvmet_load_modules() { # Check that the nvmet/nvmet_tcp modules are loaded lsmod|grep -q nvmet if [ $? -ne 0 ]; then modprobe nvmet; fi lsmod|grep -q nvmet_tcp if [ $? -ne 0 ]; then modprobe nvmet_tcp; fi lsmod|grep -q nvmet_rdmad if [ $? -ne 0 ]; then modprobe nvmet_rdma; fi # Mount configfs (for nvmet) mount -t configfs none /sys/kernel/config 2>&1 } ## Disable CPU idling and adjust frequency scaling ## function nvmet_disable_cpu_idle() { # Set scaling_min_freq to scaling_max_freq for each CPU for i in `ls /sys/devices/system/cpu/cpufreq/`; do if [ -e /sys/devices/system/cpu/cpufreq/$i/scaling_max_freq ]; then freq=`cat /sys/devices/system/cpu/cpufreq/$i/scaling_max_freq` && \ echo $freq > /sys/devices/system/cpu/cpufreq/$i/scaling_min_freq 2>&1 echo "CPU $i: Set scaling_min_freq to: $freq" fi done # Set min_per_pct for CPU pstate to 100 percent if [ -e /sys/devices/system/cpu/intel_pstate/min_perf_pct ]; then echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct 2>&1 echo "CPU pstate min_perf_pct set to 100." fi # Set cpuidle for each CPU state to disable for i in `ls /sys/devices/system/cpu|grep ^cpu|grep -v freq|grep -v idle`; do if [ -d /sys/devices/system/cpu/$i/cpuidle ]; then for j in `ls /sys/devices/system/cpu/$i/cpuidle|grep state`; do echo 1 > /sys/devices/system/cpu/$i/cpuidle/$j/disable 2>&1 echo "CPU $i State $j: Set cpuidle to disable" done fi done } # # # echo -e "$0 1.0.0" echo -e "" [ $# -lt "1" ] && help_menu $0 arr=($@) case "${arr[0]}" in --cpu) nvmet_disable_cpu_idle ;; --mod) nvmet_load_modules ;; --help) help_menu $0 ;; *) echo -ne "Option ${arr[0]} does not exist.\n\n" exit 1 esac exit 0 rapiddisk-9.1.0/scripts/rapiddisk-legacy/000077500000000000000000000000001442147535600204115ustar00rootroot00000000000000rapiddisk-9.1.0/scripts/rapiddisk-legacy/README.md000066400000000000000000000023211442147535600216660ustar00rootroot00000000000000## Description The `rapiddisk-legacy.sh` shell script provides users with an interface to use pre-7.0.0 parameters and functions. It is a wrapper to the updated and new rapiddisk binary. ## Installation Copy the shell script anywhere but most preferrably to a directory in your current working path. It is expected that you have already installed the rapiddisk binary in a current working path as it directly calls on this binary for execution. ## Execution Examples Help menu: ``` # sh rapiddisk-legacy.sh --help ``` Attach a device: ``` # sh rapiddisk-legacy.sh --attach 64 ``` Detach a device: ``` # sh rapiddisk-legacy.sh --detach rd0 ``` Flush a device: ``` # sh rapiddisk-legacy.sh --flush rd0 ``` Resize a device: ``` # sh rapiddisk-legacy.sh --resize rd0 128 ``` Map a ram drive as a cache to a backend device: ``` # sh rapiddisk-legacy.sh --cache-map rd1 /dev/sdb ``` Map a ram drive as a cache to a backend device with a specific caching policy: ``` # sh rapiddisk-legacy.sh --cache-map rd1 /dev/sdb wt ``` Unmap a ram drive from a backend device: ``` # sh rapiddisk-legacy.sh --cache-map rd1 /dev/sdb wt ``` View caching statistics from a cache mapping: ``` # sh rapiddisk-legacy.sh --stat-cache rc_sdb ``` rapiddisk-9.1.0/scripts/rapiddisk-legacy/rapiddisk-legacy.sh000066400000000000000000000051541442147535600241660ustar00rootroot00000000000000#!/bin/bash if [ ! "$BASH_VERSION" ] ; then exec /bin/bash "$0" "$@" fi ## usage ## function help_menu() { echo -e "$1 9.1.0" echo -e "Copyright 2011 - 2023 Petros Koutoupis" echo -e "" echo -e "$1 is an administration tool to manage the RapidDisk RAM disk devices and" echo -e "\tRapidDisk-Cache mappings." echo -e "" echo -e "usage: sh $1 [ parameters: unit | size | start & end | src & dest |" echo -e "\tcache & source | mode ]" echo -e "" echo -e "Functions:" echo -e "\t--attach\tAttach RAM disk device." echo -e "\t--detach\tDetach RAM disk device." echo -e "\t--list\t\tList all attached RAM disk devices." echo -e "\t--list-json\tList all attached RAM disk devices in JSON format." echo -e "\t--flush\t\tErase all data to a specified RapidDisk device (dangerous)." echo -e "\t--resize\tDynamically grow the size of an existing RapidDisk device." echo -e "\t--cache-map\tMap an RapidDisk device as a caching node to another block device." echo -e "\t--cache-unmap\tRemove cache map of a RapidDisk device from another block device." echo -e "\t--stat-cache\tObtain RapidDisk-Cache Mappings statistics." echo -e "" echo -e "Parameters:" echo -e "\t[size]\t\tSpecify desired size of attaching RAM disk device in MBytes." echo -e "\t[unit]\t\tSpecify unit number of RAM disk device to detach." echo -e "\t[cache]\t\tSpecify RapidDisk node to use as caching volume." echo -e "\t[source]\tSpecify block device to map cache to." echo -e "\t[mode]\t\tWrite Through (wt) or Write Around (wa) for cache." echo -e "" echo -e "Example Usage:" echo -e "\t$1 --attach 64" echo -e "\t$1 --detach rd2" echo -e "\t$1 --resize rd2 128" echo -e "\t$1 --cache-map rd1 /dev/sdb" echo -e "\t$1 --cache-map rd1 /dev/sdb wt" echo -e "\t$1 --cache-unmap rc_sdb" echo -e "\t$1 --flush rd2" echo -e "" exit 0 } # # # [ $# -lt "1" ] && help_menu $0 arr=($@) case "${arr[0]}" in --attach) [ $# -lt "2" ] && help_menu $0 rapiddisk -a ${arr[1]} ;; --detach) [ $# -lt "2" ] && help_menu $0 rapiddisk -d ${arr[1]} ;; --list) rapiddisk -l ;; --list-json) rapiddisk -l -j ;; --flush) [ $# -lt "2" ] && help_menu $0 rapiddisk -f ${arr[1]} ;; --resize) [ $# -lt "3" ] && help_menu $0 rapiddisk -r ${arr[1]} -c ${arr[2]} ;; --cache-map) MODE="wt" [ $# -lt "3" ] && help_menu $0 [ $# -eq "4" ] && MODE="${arr[3]}" rapiddisk -m ${arr[1]} -b ${arr[2]} -p ${MODE} ;; --cache-unmap) [ $# -lt "2" ] && help_menu $0 rapiddisk -u ${arr[1]} ;; --stat-cache) [ $# -lt "2" ] && help_menu $0 rapiddisk -s ${arr[1]} ;; --help) help_menu $0 ;; *) echo -ne "Option ${arr[0]} does not exist.\n\n" exit 1 esac exit $? rapiddisk-9.1.0/scripts/rapiddisk-rootdev/000077500000000000000000000000001442147535600206275ustar00rootroot00000000000000rapiddisk-9.1.0/scripts/rapiddisk-rootdev/AUTHORS000066400000000000000000000002011442147535600216700ustar00rootroot00000000000000AUTHOR / MAINTAINER Matteo Tenca (https://github.com/matteotenca) CONTRIBUTORS Mark Boddington (https://github.com/TuxInvader) rapiddisk-9.1.0/scripts/rapiddisk-rootdev/CHANGELOG.md000066400000000000000000000031551442147535600224440ustar00rootroot00000000000000# Changelog ### Version 0.1.5 Released 2022-05-30 * rapiddisk-on-boot now tries to invoke dkms to build modules for the specified kernel if not present (Ubuntu) * Added a prerm hook script called when a kernel package is removed (Ubuntu deb package) ### Version 0.1.4 Released 2022-05-28 * Added prerm and postinst actions to rapiddisk-on-boot debian files * Improved workaround to attach ramdisk under Ubuntu * Added a clean script to improve the workaround ### Version 0.1.3 Released 2022-05-07 * Now searches for data files in /etc/rapiddisk-on-boot first * Can be installed as a deb package under Ubuntu * Added man page ### Version 0.1.2 Released 2022-04-26 * Fixed issue under Ubuntu, which caused unsigned kernel images not to be detected, preventing installation. Thanks to [Augusto7743](https://github.com/Augusto7743), and to [Mark Boddington](https://github.com/TuxInvader) (who provided the solution) * Added rapiddisk executable presence check * Added rapiddisk's modules presence check for the chosen kernel under Ubuntu ### Version 0.1.1 Released 2022-03-02 * Fixed issue in boot script under Ubuntu 18 and earlier * Improved global uninstall under Ubuntu ### Version 0.1.0 Released 2021-12-12 * Added --cache-mode parameter * Added sanity checks in Ubuntu initrd scripts * Added many debug messages in Ubuntu initrd scripts * Added a workaround to Ubuntu boot script in case the first ramdisk can't be attached * Now initrd scripts are placed in /usr dir under Ubuntu * The dm-writecache module is added to the initrd file only when needed under Ubuntu * Fixed Centos script to report dependencies correctly rapiddisk-9.1.0/scripts/rapiddisk-rootdev/README.md000066400000000000000000000155151442147535600221150ustar00rootroot00000000000000# rapiddisk-on-boot ### A simple bash script to add rapiddisk cache to your root volume ##### Author: [Matteo Tenca](https://github.com/matteotenca) () **Warning**: this is **experimental** - use at your own risk and **only if you know what you're doing.** ### Long story short To be able to map a rapiddisk cache to your `/` volume to speed it up, the filesystem has to be umounted, and this is not possible. So, the mapping must happen at boot time, before `/` is mounted. This script adds the correct initrd related files to the system, and recreates the initrd file(s) used at boot, so that, upon the next reboot, `/` will end up mounted on a rapiddisk cached device. ### Long story An initrd (intial ramdisk) file is a special kind of archive used during boot. The kernel, upon loading, may need to perform some actions before it can mount the final root filesystem, such as loading furher kernel modules, or make accessible a partition placed on a software RAID array. To achieve these goals, an initrd file is specially crafted, so that the kernel, upon boot, can load it, extract it to a ramdisk, mount it as a temporary filesystem and access this preliminary boot environment to perform the kind of operations described above. `rapiddisk-on-boot` adds to this operation list the mapping of a cache to the device where the root filesystem resides. So that, when at last the root filesystem is mounted, it will be cached by rapiddisk. ### System-wide but kenel version-wise rapiddisk's modules, like every kernel module, are specific for each kernel version, so must be built and installed separately for each kernel you want to use them with. On the other hand, the tools used to create initrd files follow a system-wide rule set. They are smart enough to choose the right module version to add for the right kernel. But there is no way to tell them to use a different rule set for each kernel version. So, for example, the boot-time instruction to map a 100 MB ramdisk cache to /dev/sda1 would be valid, and added, to every initrd files upon re-building, regardless of the kernel version it is built or rebuilt for. This script alters this approach: the rule set remains system-wide, but allows the user to choose different options for each kernel version. ### 1) Usage when installing (`--install`) ``` sudo rapiddisk-on-boot --install --root= --size= --kernel= --cache-mode= [--force] ``` The installation process (`--install` switch) can be performed several times. The options you provide will affect only one initrd file per time, i.e. the one related to the specified kernel version (**--kernel** switch). If you use the `--install` option using a kernel version for which a configuration was already created by `rapiddisk-on-boot`, it is mandatory to specify the `--force` argument too to overwrite the configuration. #### --root= must be equal to the root device where `/` is mounted, for example `/dev/sda1`. You can find it this way: ``` $ mount | grep -P 'on\s+/\s+' /dev/sda1 on / type xfs (rw,relatime,seclabel,attr2,inode64,noquota) ``` The in this case is `/dev/sda1`. **If you omit this paramenter, the script will parse `/etc/fstab` to find the correct device by itself, and ask for confirmation before doing anything.** If you don't have any clue about your root device, omit `--root` and check the script's proposal. #### --size= indicates the number of megabyte rapiddisk will use as cache for your root device. For example `--size=1000` means your cache will be 1 GB big. #### --cache-mode= indicates the caching behavoiur, must be `wt`, `wa` or `wb`. See `rapiddisk`'s documentation for more. #### --kernel= Instructs the script about which initrd file to update. On many linux distributions there are more than one kernel version installed at the same time, and there is one initrd file for each version. You can find the kernel version you are using with `uname -r`: ``` $ uname -r 5.4.0-31-generic ``` If you're **sure** you have a second bootable kernel installed other than the one you're using, you can specify: ``` --kernel=`uname -r` ``` And go. #### --force This forces the script to perform a reinstall with the new parameters for the specified kernel version. It is mandatory when a configuration already exists for the provided kernel version. ### 2) Usage when uninstalling (`--uninstall`) ``` sudo rapiddisk-on-boot --uninstall --kernel= [--force] ``` From the initrd file linked to the specified kernel version, the rapiddisk cache creation will be removed. If rapiddisk was `--installed` for other kernel versions, those installations will not be affected. #### --uninstall This switch causes the script to alter the system-wide rule set so that rapiddisk will not be invoked at boot time **for the specified kernel version only**. Note: The system-wide rule set is not removed, just altered, so that it is possible to load rapiddisk on boot on different kernel versions independently. #### --kernel= Instructs the script about which initrd file to update. #### --force Trys to remove the installation even if it cannot be found. ### 3) Usage when global uninstalling (`--global-uninstall`) ``` sudo rapiddisk-on-boot --global-uninstall ``` This operation removes everything the script could have installed, and rebuilds ALL the initrd files. A complete uninstallation is performed. ## Notes * Under Centos 8, using xfs as the root filesystem, rapiddisk may not work if write-back mode is selected. ## Tips You can see the content of an initrd with `lsinitrd` on CentOS and `lsinitramfs` on Ubuntu: #### CentOS ``` $ sudo lsinitrd /boot/initramfs-`uname -r`.img |grep -i rapidd rapiddisk -rwxr-xr-x 2 root root 0 Jan 3 19:12 usr/lib/dracut/hooks/pre-mount/00-run_rapiddisk.sh -rwxr-xr-x 1 root root 40000 Jan 3 19:12 usr/lib/modules/4.18.0-147.8.1.el8_1.x86_64/extra/rapiddisk-cache.ko.xz -rwxr-xr-x 1 root root 32008 Jan 3 19:12 usr/lib/modules/4.18.0-147.8.1.el8_1.x86_64/extra/rapiddisk.ko.xz -rwxr-xr-x 1 root root 36240 Jan 3 19:12 usr/sbin/rapiddisk -rwxr-xr-x 2 root root 120 Jan 3 19:12 usr/sbin/run_rapiddisk.sh $ ``` To check the rapiddisk options for the current kernel: ``` $ sudo lsinitrd /boot/initramfs-`uname -r`.img -f usr/sbin/run_rapiddisk.sh #!/bin/bash modprobe rapiddisk modprobe rapiddisk-cache rapiddisk -a 300 rapiddisk -m rd0 -b /dev/sda2 -p wa $ ``` #### Ubuntu ``` $ sudo lsinitramfs /boot/initrd.img-`uname -r` |grep -i rapidd scripts/init-premount/rapiddisk_boot usr/lib/modules/5.4.0-37-generic/updates/dkms/rapiddisk-cache.ko usr/lib/modules/5.4.0-37-generic/updates/dkms/rapiddisk.ko usr/sbin/rapiddisk usr/sbin/rapiddisk_sub $ ``` rapiddisk-9.1.0/scripts/rapiddisk-rootdev/centos/000077500000000000000000000000001442147535600221225ustar00rootroot00000000000000rapiddisk-9.1.0/scripts/rapiddisk-rootdev/centos/96rapiddisk/000077500000000000000000000000001442147535600242535ustar00rootroot00000000000000rapiddisk-9.1.0/scripts/rapiddisk-rootdev/centos/96rapiddisk/module-setup.sh000077500000000000000000000016101442147535600272330ustar00rootroot00000000000000#!/bin/bash # called by dracut check() { for i in $moddir/* do if [ "${moddir}/${kernel}" = "$i" ] ; then size="$(head -n 1 "$i")" device="$(head -n 2 "$i" | tail -n 1)" cache_mode="$(tail -n 1 "$i")" cp -f "$moddir/run_rapiddisk.sh.orig" "$moddir/run_rapiddisk.sh" sed -i 's,RAMDISKSIZE,'"$size"',g' "$moddir/run_rapiddisk.sh" sed -i 's,BOOTDEVICE,'"$device"',g' "$moddir/run_rapiddisk.sh" sed -i 's,CACHEMODE,'"$cache_mode"',g' "$moddir/run_rapiddisk.sh" chmod +x "$moddir/run_rapiddisk.sh" return 0 fi done return 255 } depends() { echo dm return 0 } installkernel() { hostonly='' instmods rapiddisk rapiddisk-cache dm-writecache dm-mod return 0 } install() { inst /sbin/rapiddisk inst_hook pre-mount 50 "$moddir/run_rapiddisk.sh" inst "$moddir/run_rapiddisk.sh" "/sbin/run_rapiddisk.sh" rm -f "$moddir/run_rapiddisk.sh" return 0 } rapiddisk-9.1.0/scripts/rapiddisk-rootdev/centos/96rapiddisk/run_rapiddisk.sh.orig000066400000000000000000000002621442147535600304040ustar00rootroot00000000000000#!/bin/bash modprobe -q dm-mod modprobe -q dm-writecache modprobe -q rapiddisk modprobe -q rapiddisk-cache rapiddisk -a RAMDISKSIZE rapiddisk -m rd0 -b BOOTDEVICE -p CACHEMODE rapiddisk-9.1.0/scripts/rapiddisk-rootdev/rapiddisk-on-boot000077500000000000000000000352071442147535600241110ustar00rootroot00000000000000#!/bin/bash VERSION='0.1.5' echo "rapiddisk-on-boot version $VERSION" ihelp() { echo "Usage:" echo "$(basename "$0") --help" echo "$(basename "$0") --install --root= --size= --kernel= --cache-mode= [--force]" echo "$(basename "$0") --uninstall --kernel= [--force]" echo "$(basename "$0") --global-uninstall" echo "" echo "Where:" echo "" echo "--help prints this help and exit" echo "" echo "In '--install' mode:" echo " is the device mounted as '/' as in /etc/fstab, in the /dev/xxxn format" echo " if not provided, /etc/fstab will be parsed to determine it automatically" echo " and you'll be asked to confirm" echo " is the size in MB of the ramdisk to be used as cache" echo " is needed to determine which initrd file to alter" echo " is the rapiddisk caching mode (wt, wa, wb)" echo "--force even if everything is already in place, force reinstalling." echo " Can be useful to change the ramdisk size without perform an uninstall" echo "" echo "In '--uninstall' mode:" echo " is needed to determine which initrd file to alter. Other initrd files are left intact" echo "--force perform the uninstall actions even if there is nothing to remove" echo "" echo "In '--global-uninstall' mode:" echo " everything ever installed by the script will be removed once and for all" echo " all the initrd files will be rebuild" echo "" echo "Note: kernel_version is really important: if you end up with a system that" echo "cannot boot, you can choose another kernel version from the grub menu," echo "boot successfully, and use the --uninstall command with the of" echo "the non-booting kernel to create a working initrd file." echo "" echo "You can usually try 'uname -r' to obtain the current kernel version." echo "" } is_num() { [ "$1" ] && [ -z "${1//[0-9]/}" ] } # Credits: https://stackoverflow.com/questions/806906/how-do-i-test-if-a-variable-is-a-number-in-bash myerror() { echo "**** Error: $1 Exiting..." exit 1 } centos_install () { echo " - Creating module's dir..." mkdir -p "${module_destination}/${module_name}" echo " - Copying module's files in place..." cp -f "${source_dir}/${os_name}/${module_name}/run_rapiddisk.sh.orig" "${module_destination}/${module_name}/" cp -f "${source_dir}/${os_name}/${module_name}/module-setup.sh" "${module_destination}/${module_name}/" echo " - chmod module's files..." chmod +x "${module_destination}/${module_name}/module-setup.sh" chmod -x "${module_destination}/${module_name}/run_rapiddisk.sh.orig" echo " - Activating rapiddisk for kernel version ${kernel_version}." echo >"${module_destination}/${module_name}/${kernel_version_file}" "${ramdisk_size}" echo >>"${module_destination}/${module_name}/${kernel_version_file}" "${root_device}" echo >>"${module_destination}/${module_name}/${kernel_version_file}" "${cache_mode}" } centos_end () { echo " - Running 'dracut --kver $kernel_version -f'" dracut --kver "$kernel_version" -f echo " - Done under CentOS. A reboot is needed." } ubuntu_install () { if [ ! -x "${hook_dest}" -o -n "${force}" ] ; then echo " - Copying ${source_dir}/ubuntu/rapiddisk_hook to ${hook_dest}..." if ! cp -f "${source_dir}/ubuntu/rapiddisk_hook" "${hook_dest}" ; then myerror "could not copy rapiddisk_hook to ${hook_dest}." fi chmod +x "${hook_dest}" 2>/dev/null fi if [ ! -x "${bootscript_dest}" -o -n "${force}" ] ; then echo " - Copying ${source_dir}/ubuntu/rapiddisk_boot to ${bootscript_dest}..." if ! cp -f "${source_dir}/ubuntu/rapiddisk_boot" "${bootscript_dest}" ; then myerror "could not copy rapiddisk_boot to ${bootscript_dest}." fi chmod +x "${bootscript_dest}" 2>/dev/null fi if [ ! -f "${subscript_dest_orig}" -o -n "${force}" ] ; then echo " - Copying ${source_dir}/ubuntu/rapiddisk_sub.orig to ${subscript_dest_orig}..." if ! cp -f "${source_dir}/ubuntu/rapiddisk_sub.orig" "${subscript_dest_orig}"; then myerror "could not copy rapiddisk_sub.orig to ${subscript_dest_orig}." fi chmod -x "${subscript_dest_orig}" 2>/dev/null fi if [ ! -x "${cleanscript_dest_orig}" -o -n "${force}" ] ; then echo " - Copying ${source_dir}/ubuntu/rapiddisk_clean to ${cleanscript_dest_orig}..." if ! cp -f "${source_dir}/ubuntu/rapiddisk_clean" "${cleanscript_dest_orig}"; then myerror "could not copy rapiddisk_clean to ${cleanscript_dest_orig}." fi chmod +x "${cleanscript_dest_orig}" 2>/dev/null fi echo " - Creating kernel options file ..." echo >"${kernel_version_file_dest}" "${ramdisk_size}" echo >>"${kernel_version_file_dest}" "${root_device}" echo >>"${kernel_version_file_dest}" "${cache_mode}" } ubuntu_end () { # this is needed when an --uninstall is peformed by the apt prerm kernel script # we need to skip the kernel version test (skipped using the --skip-kernel-check) # and to skip the initramfs creation if [ "$install_mode" = "simple_uninstall" -a -n "$skipkernelcheck" ] ; then noinitramfs=1 fi if [ -z "$noinitramfs" ] ; then echo " - Updating initramfs for kernel version ${kernel_version}..." echo "" update-initramfs -u -k "$kernel_version" echo "" fi echo " - Done under Ubuntu. A reboot is needed." } install_options_checks () { [ -n "$ramdisk_size" ] || myerror "missing argument '--size'." is_num "$ramdisk_size" || myerror "the ramdisk size must be a positive integer." [ -n "$cache_mode" ] || myerror "missing argument '--cache-mode'." cache_mode="$(echo "$cache_mode" | tr '[:upper:]' '[:lower:]')" if [[ ! "$cache_mode" = w[tab] ]] ; then myerror "cache mode in '--cache-mode parameter' must be one of 'wt', 'wa' or 'wb'." fi if [ -z "$root_device" ] ; then echo " - No root device was specified, we start looking for it in /etc/fstab..." root_line="$(grep -vE '^[ #]+' /etc/fstab | grep -m 1 -oP '^.*?[^\s]+\s+/\s+')" root_first_char="$(echo "$root_line" | grep -o '^.')" case $root_first_char in U) uuid="$(echo "$root_line" | grep -oP '[\w\d]{8}-([\w\d]{4}-){3}[\w\d]{12}')" root_device=/dev/"$(ls 2>/dev/null -l /dev/disk/by-uuid/*"$uuid"* | grep -oE '[^/]+$')" ;; L) label="$(echo "$root_line" | grep -oP '=[^\s]+' | tr -d '=')" root_device=/dev/"$(ls 2>/dev/null -l /dev/disk/by-label/*"$label"* | grep -oE '[^/]+$')" ;; /) device="$(echo "$root_line" | grep -oP '^[^\s]+')" root_device=/dev/"$(ls 2>/dev/null -l "$device" | grep -oP '[^/]+$')" ;; *) myerror "could not find the root device from /etc/fstab. Use the '--root' option." ;; esac # TODO this check must be improved if ! echo "$root_device" | grep -P '^/dev/\w{1,4}\d{0,99}$' >/dev/null 2>/dev/null ; then myerror "root_device '$root_device' must be in the form '/dev/xxx' or '/dev/xxxn with n as a positive integer. Use the '--root' option." fi echo " - Root device '$root_device' was found!" echo ' - Is it ok to use it? [yN]' read -r yn if [ ! "$yn" = "y" ] && [ ! "$yn" = "Y" ] ; then myerror "please use the '--root' option." fi fi } # checks for current user == root whoami | grep '^root$' 2>/dev/null 1>/dev/null || myerror "sorry, this must be run as root." # checks for rapiddisk executables rapiddisk_command="$(which 2>/dev/null rapiddisk | head -n 1)" if [ -z "$rapiddisk_command" ] ; then myerror "'rapiddisk' command not found." fi # looks for the OS name if cat /etc/*-release 2>/dev/null | grep "CentOS" >/dev/null 2>/dev/null; then os_name="centos" kernel_installed="$(rpm -qa kernel-*| sed -E 's/^kernel-[^[:digit:]]+//'|sort -u)" elif cat /etc/*-release 2>/dev/null | grep "Ubuntu" >/dev/null 2>/dev/null; then os_name="ubuntu" kernel_installed="$(dpkg-query --list | grep -P 'linux-image-(unsigned-)?\d' |grep '^.i'| awk '{ print $2 }'| sed -re 's,linux-image-(unsigned-)?,,')" else myerror "operating system not supported." fi # set the data dir if [ -d /usr/share/rapiddisk-on-boot ] ; then source_dir="/usr/share/rapiddisk-on-boot" else source_dir="$(dirname "$0")" fi # parsing arguments for i in "$@"; do case $i in --kernel=*) kernel_version="${i#*=}" shift # past argument=value ;; --uninstall) install_mode=simple_uninstall shift # past argument with no value ;; --global-uninstall) if [ -n "$install_mode" ] ; then ihelp myerror "only one betweeen '--install, '--uninstall' and --global-uninstall can be specified." fi install_mode=global_uninstall shift # past argument with no value ;; --install) if [ -n "$install_mode" ] ; then ihelp myerror "only one betweeen '--install, '--uninstall' and --global-uninstall can be specified." fi install_mode=simple_install shift # past argument with no value ;; --root=*) root_device="${i#*=}" shift # past argument=value ;; --size=*) ramdisk_size="${i#*=}" shift # past argument=value ;; --cache-mode=*) cache_mode="${i#*=}" shift # past argument=value ;; --force) force=1 shift # past argument with no value ;; --no-initramfs) noinitramfs=1 shift # past argument with no value ;; --skip-kernel-check) skipkernelcheck=1 shift # past argument with no value ;; --help) ihelp exit 1 ;; -h) ihelp exit 1 ;; *) ihelp myerror "unknown argument." ;; esac done # Credits https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash # the action must always be specified if [ -z "$install_mode" ] ; then ihelp myerror "one betweeen '--install, '--uninstall' and '--global-uninstall' must be specified." fi # --kernel option is mandatory, except when --global-uninstall is specified if [ -z "$kernel_version" ] && [ ! "$install_mode" = "global_uninstall" ] ; then ihelp myerror "missing argument '--kernel'." fi # check if the kernel version specified is installed if [ ! "$install_mode" = "global_uninstall" ] ; then for v in $kernel_installed do if [ "$v" = "$kernel_version" ] ; then kernel_found=1 break fi done if [ -z "$kernel_found" -a -z "$skipkernelcheck" ] ; then myerror "the kernel version you specified is not installed on the machine." fi fi # start installation if [ "$os_name" = "centos" ] ; then echo " - CentOS detected!" # prepare some vars module_destination="/usr/lib/dracut/modules.d" module_name="96rapiddisk" kernel_version_file="$kernel_version" # what should we do? if [ "$install_mode" = "simple_install" ] ; then # installing on CentOS # now we can perform some parameters' checks which would be senseless to do earlier install_options_checks if [ -z "$force" ] ; then # without --force, we have do some more checks if [ -d "${module_destination}/${module_name}" ] ; then # module installed, check for kernel activation file if [ -f "${module_destination}/${module_name}/${kernel_version_file}" ] ; then # everything is already installed myerror "module already installed and already active for kernel version $kernel_version. Use '--force' to reinstall." fi fi fi centos_install centos_end elif [ "$install_mode" = "simple_uninstall" ] ; then echo " - Uninstalling for kernel ${kernel_version}..." if [ -z "$force" ] ; then if [ ! -f "${module_destination}/${module_name}/${kernel_version_file}" ] ; then myerror "module not active for kernel version ${kernel_version}. Use '--force' to remove it anyway." fi fi echo " - Removing rapiddisk from ${kernel_version} initrd file..." rm -f "${module_destination}/${module_name}/${kernel_version_file}" centos_end elif [ "$install_mode" = "global_uninstall" ] ; then echo " - Global uninstalling and rebuilding initrd for all kernels..." rm -rf "${module_destination:?}/${module_name:?}" echo " - Rebuilding all the initrd files.." for k in $kernel_installed do echo " - dracut --kver $k -f" dracut --kver "$k" -f done fi elif [ "$os_name" = "ubuntu" ] ; then echo " - Ubuntu detected!" # prepare some vars hooks_dir="/usr/share/initramfs-tools/hooks" scripts_dir="/usr/share/initramfs-tools/scripts/init-premount" alt_scripts_dir="/usr/share/initramfs-tools/scripts/local-bottom" if [ ! -d "$hooks_dir" -o ! -d "$scripts_dir" -o ! -d "$alt_scripts_dir" ] ; then myerror "I can't find any suitable place to write initramfs' scripts." fi hook_dest="${hooks_dir}/rapiddisk_hook" bootscript_dest="${scripts_dir}/rapiddisk_boot" subscript_dest_orig="${hooks_dir}/rapiddisk_sub.orig" cleanscript_dest_orig="${alt_scripts_dir}/rapiddisk_clean" kernel_version_file="rapiddisk_kernel_${kernel_version}" kernel_version_file_dest="${hooks_dir:?}/${kernel_version_file:?}" # what should we do? if [ "$install_mode" = "simple_install" ] ; then # installing on Ubuntu # check if rapiddisk modules are installed for chosen kernel # TODO: move this check to install_options_checks function if is ok under CentOS too # checks for modinfo executable modinfo_command="$(which 2>/dev/null modinfo | head -n 1)" if [ -z "$modinfo_command" ] ; then myerror "'modinfo' command not found." fi if ! $modinfo_command >/dev/null 2>&1 -k "$kernel_version" -n rapiddisk ; then if dkms status -m rapiddisk -k "$kernel_version" | grep -q 'added$' ; then rapiddisk_version="$(dkms status -m rapiddisk -k "$kernel_version" | grep -oP ',\s+[^:\s]+'|sed 's/, //')" if dkms 2>/dev/null install -m rapiddisk -v "$rapiddisk_version" -k "$kernel_version" ; then missing_modules=0 fi fi if [ -z "$missing_modules" ] ; then myerror "no rapiddisk modules found for chosen kernel." fi fi if [ -z "$force" ] ; then if [ -f "${kernel_version_file_dest}" ] ; then myerror "the config file for kernel version ${kernel_version} is already installed. Use '--force' to reinstall it." fi fi # now we can perform some parameters' checks which would be senseless to do earlier install_options_checks # calls install function ubuntu_install elif [ "$install_mode" = "simple_uninstall" ] ; then if [ -z "$force" ] ; then if [ ! -f "${kernel_version_file_dest}" ] ; then myerror "it seems there is not a valid installation for kernel version ${kernel_version}. You should use the '--force' option." fi fi echo " - Uninstalling for kernel $kernel_version..." rm -f "${kernel_version_file_dest}" 2>/dev/null elif [ "$install_mode" = "global_uninstall" ] ; then echo " - Global uninstalling for all kernel versions.." echo " - Deleting all files and rebuilding all the initrd files.." rm -f "${hooks_dir:?}"/rapiddisk_kernel_* rm -f "${hook_dest}" 2>/dev/null rm -f "${bootscript_dest}" 2>/dev/null rm -f "${subscript_dest_orig}" 2>/dev/null rm -f "${cleanscript_dest_orig}" 2>/dev/null kernel_version=all fi ubuntu_end fi exit 0 rapiddisk-9.1.0/scripts/rapiddisk-rootdev/rapiddisk-on-boot.1000066400000000000000000000146551442147535600242510ustar00rootroot00000000000000.\" Automatically generated by Pandoc 2.5 .\" .TH "RAPIDDISK\-ON\-BOOT" "1" "2022\-05\-30" "GNU" "" .hy .SH NAME .PP rapiddisk\-on\-boot \- a simple bash script to add rapiddisk cache to your root volume .SH SYNOPSIS .PP \f[B]rapiddisk\-on\-boot\f[R] \f[B]\-\-install\f[R] [\f[B]\-\-root=\f[R]\f[I]root_partition\f[R]] \f[B]\-\-size=\f[R]\f[I]ramdisk_size\f[R] \f[B]\-\-kernel=\f[R]\f[I]kernel_version\f[R] \f[B]\-\-cache\-mode=\f[R]\f[I]mode\f[R] [\f[B]\-\-force\f[R]] .PD 0 .P .PD \f[B]rapiddisk\-on\-boot\f[R] \f[B]\-\-uninstall\f[R] \f[B]\-\-kernel=\f[R]\f[I]kernel_version\f[R] [\f[B]\-\-force\f[R]] .PD 0 .P .PD \f[B]rapiddisk\-on\-boot\f[R] \f[B]\-\-global\-uninstall\f[R] .SH DESCRIPTION .PP To be able to map a rapiddisk cache to your \[ga]/\[ga] volume to speed it up, the filesystem has to be umounted, and this is not possible. So, the mapping must happen at boot time, before \[ga]/\[ga] is mounted. This script adds the correct initrd related files to the system, and recreates the initrd file(s) used at boot, so that, upon the next reboot, \[ga]/\[ga] will end up mounted on a rapiddisk cached device. .SS Long story .PP An initrd (intial ramdisk) file is a special kind of archive used during boot. The kernel, upon loading, may need to perform some actions before it can mount the final root filesystem, such as loading furher kernel modules, or make accessible a partition placed on a software RAID array. To achieve these goals, an initrd file is specially crafted, so that the kernel, upon boot, can load it, extract it to a ramdisk, mount it as a temporary filesystem and access this preliminary boot environment to perform the kind of operations described above. \f[B]rapiddisk\-on\-boot\f[R] adds to this operation list the mapping of a cache to the device where the root filesystem resides. So that, when at last the root filesystem is mounted, it will be cached by rapiddisk. .SS System\-wide but kenel version\-wise .PP rapiddisk\[cq]s modules, like every kernel module, are specific for each kernel version, so must be built and installed separately for each kernel you want to use them with. On the other hand, the tools used to create initrd files follow a system\-wide rule set. They are smart enough to choose the right module version to add for the right kernel. But there is no way to tell them to use a different rule set for each kernel version. So, for example, the boot\-time instruction to map a 100 MB ramdisk cache to /dev/sda1 would be valid, and added, to every initrd files upon re\-building, regardless of the kernel version it is built or rebuilt for. .PD 0 .P .PD This script alters this approach: the rule set remains system\-wide, but allows the user to choose different options for each kernel version. .SH OPTIONS .SS Installing (\-\-install) .PP The installation process (\f[B]\-\-install\f[R] switch) can be performed several times. The options you provide will affect only one initrd file per time, i.e.\ the one related to the specified kernel version (\f[B]\-\-kernel\f[R] switch). If you use the \f[B]\-\-install\f[R] option using a kernel version for which a configuration was already created by \f[B]rapiddisk\-on\-boot\f[R], it is mandatory to specify the \f[B]\-\-force\f[R] argument too to overwrite the configuration. .TP .B \f[B]\-\-install\f[R] triggers the installation mode .TP .B \f[B]\-\-root=\f[R]\f[I]root_partition\f[R] must be equal to the root device where \[ga]/\[ga] is mounted, for example \[ga]/dev/sda1\[ga]. If you omit this paramenter, the script will parse \[ga]/etc/fstab\[ga] to find the correct device by itself, and ask for confirmation before doing anything. .TP .B \f[B]\-\-size=\f[R]\f[I]ramdisk_size\f[R] \f[I]ramdisk_size\f[R] indicates the number of megabyte rapiddisk will use as cache for your root device. For example \[ga]\[en]size=1000\[ga] means your cache will be 1 GB big. .TP .B \f[B]\-\-cache\-mode=\f[R]\f[I]mode\f[R] \f[I]mode\f[R] indicates the caching behavoiur, must be \[ga]wt\[ga], \[ga]wa\[ga] or \[ga]wb\[ga]. See \f[B]rapiddisk(1)\f[R]\[cq]s documentation for more. .TP .B \f[B]\-\-kernel=\f[R]\f[I]kernel_version\f[R] instructs the script about which initrd file to update. On many linux distributions there are more than one kernel version installed at the same time, and there is one initrd file for each version. You can find the kernel version you are using with \[ga]uname \-r\[ga] .TP .B \f[B]\-\-force\f[R] forces the script to perform a reinstall with the new parameters for the specified kernel version. It is mandatory when a configuration already exists for the provided kernel version. .SS Uninstalling (\-\-uninstall) .PP From the initrd file linked to the specified kernel version, the rapiddisk cache creation will be removed. If rapiddisk was installed for other kernel versions, those installations will not be affected. .TP .B \f[B]\-\-uninstall\f[R] this switch causes the script to alter the system\-wide rule set so that rapiddisk will not be invoked at boot time \f[I]for the specified kernel version only\f[R]. The system\-wide rule set is not removed, just altered, so that it is possible to load rapiddisk on boot on different kernel versions independently. .TP .B \f[B]\-\-kernel=\f[R]\f[I]kernel_version\f[R] instructs the script about which initrd file to update. .TP .B \f[B]\-\-force\f[R] trys to remove the installation even if it cannot be found. .SS Global uninstalling (\-\-global\-uninstall) .TP .B \f[B]\-\-global\-uninstall\f[R] This operation removes everything the script could have installed, and rebuilds ALL the initrd files. A complete uninstallation is performed. .SH FILES .PP \f[I]/usr/share/rapiddisk\-on\-boot/*\f[R] .SH NOTES .PP You can see the content of an initrd with \[ga]lsinitrd\[ga] on CentOS and \[ga]lsinitramfs\[ga] on Ubuntu. .SH BUGS .PP Under Centos 8, using xfs as the root filesystem, rapiddisk may not work if write\-back mode is selected. .SH EXAMPLES .TP .B \f[B]rapiddisk\-on\-boot \-\-install \-\-kernel=\[ga]uname \-r\[ga] \-\-root=/dev/sda1 \-\-cache\-mode=wa \-\-size=100\f[R] on next boot with the current running kernel will be created a 100 MB ramdisk and it will be attached to /dev/sda1 .TP .B \f[B]rapiddisk\-on\-boot \-\-uninstall \-\-kernel=\[ga]uname \-r\[ga]\f[R] removes creation of a rapiddisk cache at boot time for the current running kernel .TP .B \f[B]rapiddisk\-on\-boot \-\-global\-uninstall\f[R] removes any file installed for every installed kernel \- complete clean. .SH SEE ALSO .PP \f[B]rapiddisk(1)\f[R] .SH AUTHORS Matteo Tenca (https://github.com/matteotenca). rapiddisk-9.1.0/scripts/rapiddisk-rootdev/rapiddisk-on-boot.1.md000066400000000000000000000134201442147535600246350ustar00rootroot00000000000000% RAPIDDISK-ON-BOOT(1) GNU % [Matteo Tenca](https://github.com/matteotenca) % 2022-05-30 # NAME rapiddisk-on-boot - a simple bash script to add rapiddisk cache to your root volume # SYNOPSIS **rapiddisk-on-boot** **\-\-install** [**\-\-root=***root_partition*] **\-\-size=***ramdisk_size* **\-\-kernel=***kernel_version* **\-\-cache-mode=***mode* [**\-\-force**]\ **rapiddisk-on-boot** **\-\-uninstall** **\-\-kernel=***kernel_version* [**\-\-force**]\ **rapiddisk-on-boot** **\-\-global-uninstall** # DESCRIPTION To be able to map a rapiddisk cache to your \`/\` volume to speed it up, the filesystem has to be umounted, and this is not possible. So, the mapping must happen at boot time, before \`/\` is mounted. This script adds the correct initrd related files to the system, and recreates the initrd file(s) used at boot, so that, upon the next reboot, \`/\` will end up mounted on a rapiddisk cached device. ## Long story An initrd (intial ramdisk) file is a special kind of archive used during boot. The kernel, upon loading, may need to perform some actions before it can mount the final root filesystem, such as loading furher kernel modules, or make accessible a partition placed on a software RAID array. To achieve these goals, an initrd file is specially crafted, so that the kernel, upon boot, can load it, extract it to a ramdisk, mount it as a temporary filesystem and access this preliminary boot environment to perform the kind of operations described above. **rapiddisk-on-boot** adds to this operation list the mapping of a cache to the device where the root filesystem resides. So that, when at last the root filesystem is mounted, it will be cached by rapiddisk. ## System-wide but kenel version-wise rapiddisk's modules, like every kernel module, are specific for each kernel version, so must be built and installed separately for each kernel you want to use them with. On the other hand, the tools used to create initrd files follow a system-wide rule set. They are smart enough to choose the right module version to add for the right kernel. But there is no way to tell them to use a different rule set for each kernel version. So, for example, the boot-time instruction to map a 100 MB ramdisk cache to /dev/sda1 would be valid, and added, to every initrd files upon re-building, regardless of the kernel version it is built or rebuilt for. \ This script alters this approach: the rule set remains system-wide, but allows the user to choose different options for each kernel version. # OPTIONS ## Installing (\-\-install) The installation process (**\-\-install** switch) can be performed several times. The options you provide will affect only one initrd file per time, i.e. the one related to the specified kernel version (**\-\-kernel** switch). If you use the **\-\-install** option using a kernel version for which a configuration was already created by **rapiddisk-on-boot**, it is mandatory to specify the **\-\-force** argument too to overwrite the configuration. **\-\-install** : triggers the installation mode **\-\-root=***root_partition* : must be equal to the root device where \`/\` is mounted, for example \`/dev/sda1\`. If you omit this paramenter, the script will parse \`/etc/fstab\` to find the correct device by itself, and ask for confirmation before doing anything. **\-\-size=***ramdisk_size* : *ramdisk_size* indicates the number of megabyte rapiddisk will use as cache for your root device. For example \`--size=1000\` means your cache will be 1 GB big. **\-\-cache-mode=***mode* : *mode* indicates the caching behavoiur, must be \`wt\`, \`wa\` or \`wb\`. See **rapiddisk(1)**'s documentation for more. **\-\-kernel=***kernel_version* : instructs the script about which initrd file to update. On many linux distributions there are more than one kernel version installed at the same time, and there is one initrd file for each version. : You can find the kernel version you are using with \`uname -r\` **\-\-force** : forces the script to perform a reinstall with the new parameters for the specified kernel version. It is mandatory when a configuration already exists for the provided kernel version. ## Uninstalling (\-\-uninstall) From the initrd file linked to the specified kernel version, the rapiddisk cache creation will be removed. If rapiddisk was installed for other kernel versions, those installations will not be affected. **\-\-uninstall** : this switch causes the script to alter the system-wide rule set so that rapiddisk will not be invoked at boot time *for the specified kernel version only*. The system-wide rule set is not removed, just altered, so that it is possible to load rapiddisk on boot on different kernel versions independently. **\-\-kernel=***kernel_version* : instructs the script about which initrd file to update. **\-\-force** : trys to remove the installation even if it cannot be found. ## Global uninstalling (\-\-global-uninstall) **\-\-global-uninstall** : This operation removes everything the script could have installed, and rebuilds ALL the initrd files. A complete uninstallation is performed. # FILES */usr/share/rapiddisk-on-boot/\** # NOTES You can see the content of an initrd with \`lsinitrd\` on CentOS and \`lsinitramfs\` on Ubuntu. # BUGS Under Centos 8, using xfs as the root filesystem, rapiddisk may not work if write-back mode is selected. # EXAMPLES **rapiddisk-on-boot \-\-install \-\-kernel=\`uname -r\` \-\-root=/dev/sda1 \-\-cache-mode=wa \-\-size=100** : on next boot with the current running kernel will be created a 100 MB ramdisk and it will be attached to /dev/sda1 **rapiddisk-on-boot \-\-uninstall \-\-kernel=\`uname -r\`** : removes creation of a rapiddisk cache at boot time for the current running kernel **rapiddisk-on-boot \-\-global-uninstall** : removes any file installed for every installed kernel - complete clean. # SEE ALSO **rapiddisk(1)** rapiddisk-9.1.0/scripts/rapiddisk-rootdev/ubuntu/000077500000000000000000000000001442147535600221515ustar00rootroot00000000000000rapiddisk-9.1.0/scripts/rapiddisk-rootdev/ubuntu/rapiddisk_boot000077500000000000000000000003511442147535600250730ustar00rootroot00000000000000#!/bin/sh PREREQ="" prereqs() { echo "$PREREQ" } case $1 in prereqs) prereqs exit 0 ;; esac . /scripts/functions # Begin real processing below this line if [ -x /sbin/rapiddisk_sub ] ; then /sbin/rapiddisk_sub fi exit 0 rapiddisk-9.1.0/scripts/rapiddisk-rootdev/ubuntu/rapiddisk_clean000077500000000000000000000014551442147535600252200ustar00rootroot00000000000000#!/bin/sh PREREQ="" prereqs() { echo "$PREREQ" } case $1 in prereqs) prereqs exit 0 ;; esac . /scripts/functions # Begin real processing below this line cache="$(rapiddisk -l)" ramdisks="$(echo $cache | grep -oE 'RapidDisk Device +[[:digit:]]+: +[^ ]+'|grep -oE '[^ ]+$')" mapped="$(echo $cache | grep -oE 'Cache: +[^ ]+'| grep -oE '[^ ]+$')" for map in $mapped ; do for rd in $ramdisks ; do if [ $map = $rd ]; then tokeep="$tokeep $rd" fi done done for rmv in $tokeep ; do ramdisks="$(echo $ramdisks | sed "s/$rmv//g")" done for remove in $ramdisks ; do if rapiddisk 2>&1 -d $remove ; then log_warning_msg "rapiddisk: deleted $remove ramdisk" else log_warning_msg "rapiddisk: failed to delete $remove ramdisk" fi done exit 0 rapiddisk-9.1.0/scripts/rapiddisk-rootdev/ubuntu/rapiddisk_hook000077500000000000000000000017441442147535600250770ustar00rootroot00000000000000#!/bin/sh PREREQ="" prereqs() { echo "$PREREQ" } case $1 in prereqs) prereqs exit 0 ;; esac . /usr/share/initramfs-tools/hook-functions # Begin real processing below this line cwd="$(dirname "$0")" for i in "$cwd"/rapiddisk_kernel_* do if [ "${cwd}/rapiddisk_kernel_${version}" = "$i" ] ; then size="$(head -n 1 "$i")" device="$(head -n 2 "$i" | tail -n 1)" cache_mode="$(tail -n 1 "$i")" cp "${cwd}/rapiddisk_sub.orig" "${cwd}/rapiddisk_sub" sed -i 's,RAMDISKSIZE,'"$size"',g' "${cwd}/rapiddisk_sub" sed -i 's,BOOTDEVICE,'"$device"',g' "${cwd}/rapiddisk_sub" sed -i 's,CACHEMODE,'"$cache_mode"',g' "${cwd}/rapiddisk_sub" chmod +x "${cwd}/rapiddisk_sub" manual_add_modules rapiddisk manual_add_modules rapiddisk-cache if [ "$cache_mode" = "wb" ] ; then manual_add_modules dm-writecache fi copy_exec /sbin/rapiddisk /sbin/rapiddisk copy_file binary "${cwd}/rapiddisk_sub" /sbin/ rm "${cwd}/rapiddisk_sub" break 2 fi done exit 0 rapiddisk-9.1.0/scripts/rapiddisk-rootdev/ubuntu/rapiddisk_sub.orig000066400000000000000000000033721442147535600256630ustar00rootroot00000000000000#!/bin/sh . /scripts/functions FAILUREMSG="rapiddisk: failed." log_begin_msg "rapiddisk: starting rapiddisk preparation, ramdisk size: RAMDISKSIZE, boot device: BOOTDEVICE, caching mode: CACHEMODE." if [ "CACHEMODE" = "wb" ] ; then modprobe -q dm-writecache if [ ! -d /sys/module/dm_writecache ] ; then log_failure_msg "rapiddisk: unable to load dm-writecache module" log_failure_msg "$FAILUREMSG" exit 0; fi fi modprobe -q rapiddisk modprobe -q rapiddisk-cache if { [ ! -d /sys/module/rapiddisk ] || [ ! -d /sys/module/rapiddisk_cache ] ; } ; then log_failure_msg "rapiddisk: unable to load rapiddisk modules" log_failure_msg "$FAILUREMSG" exit 0; fi rapiddisk >/dev/null 2>&1 -a RAMDISKSIZE if ! rapiddisk >/dev/null 2>&1 -m rd0 -b BOOTDEVICE -p CACHEMODE ; then log_failure_msg "rapiddisk: attaching of the ramdisk failed, trying with a workaround" rapiddisk >/dev/null 2>&1 -a 5 || log_warning_msg "rapiddisk: failed to add 5 MB rd1 ramdisk" rapiddisk >/dev/null 2>&1 -d rd0 || log_warning_msg "rapiddisk: failed to delete RAMDISKSIZE MB rd0 ramdisk" rapiddisk >/dev/null 2>&1 -a RAMDISKSIZE || log_warning_msg "rapiddisk: failed to add RAMDISKSIZE MB rd0 ramdisk" rapiddisk >/dev/null 2>&1 -a 5 || log_warning_msg "rapiddisk: failed to add 5 MB rd2 ramdisk" rapiddisk >/dev/null 2>&1 -d rd1 || log_warning_msg "rapiddisk: failed to delete 5 MB rd1 ramdisk" if ! rapiddisk >/dev/null 2>&1 -m rd0 -b BOOTDEVICE -p CACHEMODE ; then rapiddisk >/dev/null 2>&1 -d rd0 rapiddisk >/dev/null 2>&1 -d rd1 rapiddisk >/dev/null 2>&1 -d rd2 log_end_msg "rapiddisk: attaching of the ramdisk failed" exit 0 fi fi result="$(rapiddisk 2>&1 -l)" log_success_msg "$result" log_end_msg "rapiddisk: RAMDISKSIZE MB ramdisk attached to BOOTDEVICE successfully." exit 0 rapiddisk-9.1.0/src/000077500000000000000000000000001442147535600142755ustar00rootroot00000000000000rapiddisk-9.1.0/src/Makefile000066400000000000000000000140101442147535600157310ustar00rootroot00000000000000# Copyright © 2011 - 2023 Petros Koutoupis # All rights reserved. # # This file is part of RapidDisk. # # RapidDisk 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. # # RapidDisk 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 RapidDisk. If not, see . # # SPDX-License-Identifier: GPL-2.0-or-later ifeq ($(CC),) CC := gcc -Werror endif RM := rm -f STRIP_CMD := strip -s DIR := /sbin DAEMON_CFLAGS := -DSERVER INSTALL := install -D -m 755 -t $(DESTDIR)$(DIR) INSTALL_STRIP := install -D -m 755 -s -t $(DESTDIR)$(DIR) DEBUG_FLAGS := -DDEBUG -Wl,--export-dynamic -g $(CFLAGS) DEBUG_LDFLAGS := -DDEBUG -Wl,--export-dynamic -g $(LDFLAGS) ifndef CFLAGS NDEBUG_CFLAGS := -O2 -Wno-unused-result -flto=auto -ffat-lto-objects -fstack-protector-strong else NDEBUG_CFLAGS := -O2 -Wno-unused-result -flto=auto -ffat-lto-objects -fstack-protector-strong $(CFLAGS) endif ifndef LDLIBS COMMON_LDLIBS := -ljansson -ldevmapper -lpcre2-8 DAEMON_LDLIBS := -ljansson -ldevmapper -lpcre2-8 -lmicrohttpd else COMMON_LDLIBS := $(LDLIBS) DAEMON_LDLIBS := $(LDLIBS) endif ifndef LDFLAGS NDEBUG_LDFLAGS := -Wl,-Bsymbolic-functions -flto=auto -ffat-lto-objects -flto=auto -Wl,-z,relro else NDEBUG_LDFLAGS := -Wl,-Bsymbolic-functions -flto=auto -ffat-lto-objects -flto=auto -Wl,-z,relro $(LDFLAGS) endif ifndef CPPFLAGS NDEBUG_CPPFLAGS := -Wdate-time else NDEBUG_CPPFLAGS := -Wdate-time $(CPPFLAGS) endif BIN_TOOL_NDEBUG := rapiddisk BIN_DAEMON_NDEBUG := rapiddiskd BIN_TOOL_DEBUG := rapiddisk_debug BIN_DAEMON_DEBUG := rapiddiskd_debug OBJS_TOOL_NDEBUG := main_ndebug.o utils_ndebug.o json_ndebug.o rdsk_ndebug.o nvmet_ndebug.o sys_ndebug.o OBJS_DAEMON_NDEBUG := rapiddiskd_ndebug.o utils-server_ndebug.o json-server_ndebug.o nvmet-server_ndebug.o net_ndebug.o rdsk-server_ndebug.o sys-server_ndebug.o OBJS_TOOL_DEBUG := main_debug.o utils_debug.o json_debug.o nvmet_debug.o rdsk_debug.o sys_debug.o OBJS_DAEMON_DEBUG := rapiddiskd_debug.o utils-server_debug.o json-server_debug.o nvmet-server_debug.o net_debug.o rdsk-server_debug.o sys-server_debug.o SRC := json.c main.c net.c nvmet.c rapiddiskd.c rdsk.c sys.c utils.c .PHONY: all all: $(BIN_TOOL_NDEBUG) $(BIN_DAEMON_NDEBUG) @echo Successfully built all $(BIN_TOOL_NDEBUG) and $(BIN_DAEMON_NDEBUG) binary files. # This checks avoid creating/including the .h dependencies Makefiles # Disables parallelization if "clean" is present in the goal list ifneq (,$(filter clean,$(MAKECMDGOALS))) ifneq (1, $(words $(MAKECMDGOALS))) .NOTPARALLEL: endif else include $(SRC:.c=.d) endif .PHONY: clean clean: rm -f *.d $(OBJS_TOOL_NDEBUG) $(OBJS_DAEMON_NDEBUG) $(OBJS_TOOL_DEBUG) $(OBJS_DAEMON_DEBUG) $(BIN_TOOL_NDEBUG) $(BIN_DAEMON_NDEBUG) $(BIN_TOOL_DEBUG) $(BIN_DAEMON_DEBUG) MAKECMDGOALS:=$(filter-out clean,$(MAKECMDGOALS))) .PHONY: tools tools: all .PHONY: run-test run-test: all debug .PHONY: clean-tools clean-tools: clean .PHONY: tools-install tools-install: install .PHONY: debug debug: $(BIN_TOOL_DEBUG) $(BIN_DAEMON_DEBUG) .PHONY: tools-debug tools-debug: debug .PHONY: strip strip: tools-strip .PHONY: tools-strip tools-strip: all $(STRIP_CMD) $(BIN_TOOL_NDEBUG) $(STRIP_CMD) $(BIN_DAEMON_NDEBUG) .PHONY: install install: all @echo Installing all $(BIN_TOOL_NDEBUG) and $(BIN_DAEMON_NDEBUG) binary files. $(INSTALL) $(BIN_TOOL_NDEBUG) $(BIN_DAEMON_NDEBUG) .PHONY: install-strip install-strip: tools-install-strip .PHONY: tools-install-strip tools-install-strip: all @echo Installing stripped $(BIN_TOOL_NDEBUG) and $(BIN_DAEMON_NDEBUG) binary files. $(INSTALL_STRIP) $(BIN_TOOL_NDEBUG) $(BIN_DAEMON_NDEBUG) .PHONY: uninstall uninstall: @echo Uninstalling $(BIN_TOOL_NDEBUG) and $(BIN_DAEMON_NDEBUG) binary files. $(RM) $(DESTDIR)$(DIR)/$(BIN_TOOL_NDEBUG) $(RM) $(DESTDIR)$(DIR)/$(BIN_DAEMON_NDEBUG) .PHONY: tools-uninstall tools-uninstall: uninstall %_debug.o: override CFLAGS += $(DEBUG_FLAGS) %_debug.o: %.c %.d $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ %-server_debug.o: %.c %.d $(CC) $(CPPFLAGS) $(DAEMON_CFLAGS) $(CFLAGS) -c $< -o $@ %_ndebug.o: CFLAGS = $(NDEBUG_CFLAGS) %_ndebug.o: CPPFLAGS = $(NDEBUG_CPPFLAGS) %_ndebug.o : %.c %.d $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ %-server_ndebug.o: %.c %.d $(CC) $(CPPFLAGS) $(DAEMON_CFLAGS) $(CFLAGS) -c $< -o $@ $(BIN_TOOL_NDEBUG): LDLIBS = $(COMMON_LDLIBS) $(BIN_TOOL_NDEBUG): LDFLAGS = $(NDEBUG_LDFLAGS) $(BIN_TOOL_NDEBUG): $(OBJS_TOOL_NDEBUG) $(CC) $(LDFLAGS) -o $(BIN_TOOL_NDEBUG) $(OBJS_TOOL_NDEBUG) $(LOADLIBES) $(LDLIBS) $(BIN_DAEMON_NDEBUG): LDLIBS = $(DAEMON_LDLIBS) $(BIN_DAEMON_NDEBUG): LDFLAGS = $(NDEBUG_LDFLAGS) $(BIN_DAEMON_NDEBUG): $(OBJS_DAEMON_NDEBUG) $(CC) $(LDFLAGS) -o $(BIN_DAEMON_NDEBUG) $(OBJS_DAEMON_NDEBUG) $(LOADLIBES) $(LDLIBS) $(BIN_TOOL_DEBUG): override LDFLAGS += $(DEBUG_LDFLAGS) $(BIN_TOOL_DEBUG): override LDLIBS += $(COMMON_LDLIBS) $(BIN_TOOL_DEBUG): $(OBJS_TOOL_DEBUG) $(CC) $(LDFLAGS) -o $(BIN_TOOL_DEBUG) $(OBJS_TOOL_DEBUG) $(LOADLIBES) $(LDLIBS) $(BIN_DAEMON_DEBUG): override LDFLAGS = $(DEBUG_LDFLAGS) $(BIN_DAEMON_DEBUG): override LDLIBS = $(DAEMON_LDLIBS) $(BIN_DAEMON_DEBUG): $(OBJS_DAEMON_DEBUG) $(CC) $(LDFLAGS) -o $(BIN_DAEMON_DEBUG) $(OBJS_DAEMON_DEBUG) $(LOADLIBES) $(LDLIBS) .PHONY: tools-clean tools-clean: clean .PHONY: dkms-uninstall dkms-install dkms-uninstall dkms-install: %.d: %.c ifeq (,$(filter clean,$(MAKECMDGOALS))) @echo Creating Makefile $@ @set -e; rm -f $@; \ $(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \ sed 's,\($*\)\.o[ :]*,\1_ndebug.o \1_debug.o \1\.d : ,g' < $@.$$$$ > $@; \ rm -f $@.$$$$; \ $(CC) -MM $(DAEMON_CFLAGS) $(CPPFLAGS) $< > $@.$$$$; \ sed 's,\($*\)\.o[ :]*,\1-server_ndebug\.o \1-server_debug\.o \1\.d : ,g' < $@.$$$$ >> $@; \ rm -f $@.$$$$ endif %.c: rapiddisk-9.1.0/src/common.h000066400000000000000000000134411442147535600157410ustar00rootroot00000000000000/** * @file * @brief Common structure and value definitions * @details This header file defines constants and structures shared by the rapiddisk and the rapiddiskd source * @copyright @verbatim Copyright © 2011 - 2023 Petros Koutoupis All rights reserved. This file is part of RapidDisk. RapidDisk is free software: you can redistribute it and/or modify@n 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. RapidDisk 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 RapidDisk. If not, see . SPDX-License-Identifier: GPL-2.0-or-later @endverbatim * @author Petros Koutoupis \ * @author Matteo Tenca \ * @version 9.1.0 * @date 21 April 2023 */ #ifndef COMMON_H #define COMMON_H #include #include #include #include #include #include #include #include #include #include #include /** Rapiddisk Process name */ #define PROCESS "rapiddisk" /** Rapiddiskd (daemon) Process name */ #define DAEMON PROCESS "d" #define COPYRIGHT "Copyright 2011 - 2023 Petros Koutoupis" #define VERSION_NUM "9.1.0" #define SUCCESS 0 #define INVALID_VALUE -1 #define NAMELEN 0x200 #define BUFSZ 0x10000 #define PAYLOADSZ 0x80000 /* 512K: this is our max read limit for libcurl */ /** Internal bool like type */ typedef char bool; #define FALSE 0 #define TRUE 1 #define SYS_RDSK "/sys/kernel/rapiddisk/mgmt" #define SYS_MODULE "/sys/module" #define FILEDATA 0x40 #define NAMELEN 0x200 #define DISABLED 0 #define ENABLED 1 #define ERR_CALLOC "%s: calloc: %s" #define ERR_FLUSHING "Error flushing file descriptors: %s, %s." #define ERR_MALFORMED "Error: wrong number of arguments or malformed URL." #define ERR_INVALIDURL "Invalid URL." #define ERR_INVALIDDEVNAME "Invalid device name." #define ERR_DEV_STATUS "Can't get device status." #define ERR_UNSUPPORTED "Unsupported." #define ERR_INVALID_SIZE "Invalid size." #define ERR_NOTANUMBER "Not a number." #define ERR_SCANDIR "%s: scandir: %s" #define ERR_FOPEN "%s: fopen: %s, %s" #define ERR_FREAD "%s: fread: %s, %s" #define ERR_MODULES "%s, The needed modules are not loaded..." #define ERR_ALREADY_RUNNING "%s, The daemon is already running..." #define ERR_NEW_MHD_DAEMON "Error creating MHD Daemon: %s, %s." #define ERR_SIGPIPE_HANDLER "Failed to install SIGPIPE handler: %s, %s" #define ERR_INVALID_MODE "Invalid cache mode in URL." #define ERR_DEV_NOEXIST "Error. Device %s does not exist." #define ERR_PORT_NOEXIST "Error. Port %d does not exist." #define ERR_CACHE_TGT_NOEXIST "Error. Cache target %s does not exist." /** * For RapidDisk device list */ typedef struct RD_PROFILE { /** Device name */ char device[0xf]; /** Device size */ unsigned long long size; /** Lock status */ int lock_status; /** Device usage */ unsigned long long usage; /** Pointer to next RapidDisk device */ struct RD_PROFILE *next; } RD_PROFILE; /** * For RapidDisk-Cache node list */ typedef struct RC_PROFILE { /** Device name */ char device[NAMELEN]; /** Cache name */ char cache[0x20]; /** Cache source */ char source[NAMELEN]; /** Pointer to next RapidDisk-Cache node */ struct RC_PROFILE *next; } RC_PROFILE; /** * Represents current memory status */ typedef struct MEM_PROFILE { /** Total memory */ unsigned long long mem_total; /** Free memory */ unsigned long long mem_free; } MEM_PROFILE; /** * Represents a volume list */ typedef struct VOLUME_PROFILE { /** Volume device name */ char device[0x20]; /** Volume size */ unsigned long long size; /** Volume vendor */ char vendor[FILEDATA]; /** Volume model */ char model[FILEDATA]; /** Pointer to next volume */ struct VOLUME_PROFILE *next; } VOLUME_PROFILE; typedef struct RC_STATS { char device[NAMELEN]; unsigned int reads; unsigned int writes; unsigned int cache_hits; unsigned int replacement; unsigned int write_replacement; unsigned int read_invalidates; unsigned int write_invalidates; unsigned int uncached_reads; unsigned int uncached_writes; unsigned int disk_reads; unsigned int disk_writes; unsigned int cache_reads; unsigned int cache_writes; /* Unsupported in this release */ unsigned int read_ops; unsigned int write_ops; } RC_STATS; typedef struct WC_STATS { char device[NAMELEN]; bool expanded; int errors; unsigned int num_blocks; unsigned int num_free_blocks; unsigned int num_wb_blocks; /* For 5.15 and later */ unsigned int num_read_req; unsigned int num_read_cache_hits; unsigned int num_write_req; unsigned int num_write_uncommitted_blk_hits; unsigned int num_write_committed_blk_hits; unsigned int num_write_cache_bypass; unsigned int num_write_cache_alloc; unsigned int num_write_freelist_blocked; unsigned int num_flush_req; unsigned int num_discard_req; } WC_STATS; enum CACHE_TYPE { WRITETHROUGH, WRITEAROUND, WRITEBACK }; typedef struct NVMET_PROFILE { char nqn[NAMELEN]; int namespc; char device[0x1F]; int enabled; struct NVMET_ALLOWED_HOST *allowed_hosts; struct NVMET_PORTS *assigned_ports; struct NVMET_PROFILE *next; } NVMET_PROFILE; typedef struct NVMET_PORTS { int port; char addr[0x1F]; char nqn[NAMELEN]; char protocol[0xF]; struct NVMET_PORTS *next; } NVMET_PORTS; typedef struct DAEMON_ARGS { bool verbose; char port[0xf]; } DAEMON_ARGS; typedef struct NVMET_ALLOWED_HOST { ; char allowed_host[NAMELEN]; struct NVMET_ALLOWED_HOST *next; } NVMET_ALLOWED_HOST; #endif rapiddisk-9.1.0/src/json.c000066400000000000000000000534511442147535600154220ustar00rootroot00000000000000/** * @file json.c * @brief JSON functions implementation * @details This file contains all the JSON related functions * @copyright @verbatim Copyright © 2011 - 2023 Petros Koutoupis All rights reserved. This file is part of RapidDisk. RapidDisk is free software: you can redistribute it and/or modify@n 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. RapidDisk 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 RapidDisk. If not, see . SPDX-License-Identifier: GPL-2.0-or-later @endverbatim * @author Petros Koutoupis \ * @author Matteo Tenca \ * @version 9.1.0 * @date 23 April 2023 */ #include "common.h" #include /* * JSON output format: * { * "message": "Optional message", * "status": "Success" * } */ /** * * This function is intended to provide a simple way to create a JSON string which contains * a status ("Success" or "Failed") derived from an integer, and an optional message. * * @param return_value if @p return_value is != @p 'SUCCESS', the JSON result will * contain: * @code{.json} * { * "status": "Failed" * } * @endcode * else * @code{.json} * { * "status": "Success" * } * @endcode * @param optional_message if != NULL, the JSON result string will contain * an additional field called @p message: * @code{.json} * { * "message": result_message * } * @endcode * @param json_result if @p wantresult is @p TRUE, the JSON string pointer is copied into @p *json_result, which * @b must be @p free()d later. Otherwise, the JSON string is printed to stdout. * @param wantresult see @p json_result paramater * @return An @p int representing the result of the operation. */ int json_status_return(int return_value, char *optional_message, char **json_result, bool wantresult) { json_t *root = json_object(); json_object_set_new(root, "status", json_string((return_value == SUCCESS) ? "Success" : "Failed")); if (optional_message && (strlen(optional_message) > 0)) json_object_set_new(root, "message", json_string(optional_message)); char *jdumped = json_dumps(root, 0); json_decref(root); if (jdumped != NULL) { if (wantresult) { *json_result = jdumped; } else { printf("%s\n", jdumped); if (jdumped != NULL) free(jdumped); } return SUCCESS; } return INVALID_VALUE; } /* * JSON output format: * { * "volumes": [ * { * "rapiddisk": [ * { * "device": "rd1", * "size": 67108864, * "usage": 4096, * "status": "locked" * }, * { * "device": "rd0", * "size": 67108864, * "usage": 65536, * "status": "unlocked" * } * ] * }, * { * "rapiddisk_cache": [ * { * "device": "rc-wa_loop7", * "cache": "rd0", * "source": "loop7", * "mode": "write-around" * } * ] * } * ] * } */ /** * It takes a linked list of RD_PROFILE and RC_PROFILE structures, and returns or print a JSON string * * @param rd A pointer to the first element of the linked list of RD_PROFILE structures. * @param rc The RC_PROFILE pointer to the first element of the linked list of RC_PROFILEs. * @param list_result This is the pointer to the pointer to the JSON string that will be valued if needed. * @param wantresult If TRUE, the function will place the pointer to the JSON string into *list_result (must be free()d later). * If FALSE, the function will print the JSON string to stdout. * * @return An @p int representing the result. */ int json_device_list(struct RD_PROFILE *rd, struct RC_PROFILE *rc, char **list_result, bool wantresult) { json_t *root, *array = json_array(); json_t *rd_array = json_array(), *rc_array = json_array(); json_t *rd_object = json_object(), *rc_object = json_object() ; char mode[0x20] = {0x0}; char *jdumped = NULL; int res = SUCCESS; while (rd != NULL) { json_t *object = json_object(); json_object_set_new(object, "device", json_string(rd->device)); json_object_set_new(object, "size", json_integer(rd->size)); json_object_set_new(object, "usage", json_integer(rd->usage)); memset(mode, 0x0, sizeof(mode)); if (rd->lock_status == TRUE) { sprintf(mode, "locked"); } else if (rd->lock_status == FALSE) { sprintf(mode, "unlocked"); } else { sprintf(mode, "unavailable"); } json_object_set_new(object, "status", json_string(mode)); json_array_append_new(rd_array, object); rd = rd->next; } json_object_set_new(rd_object, "rapiddisk", rd_array); json_array_append_new(array, rd_object); while (rc != NULL) { json_t *object = json_object(); json_object_set_new(object, "device", json_string(rc->device)); json_object_set_new(object, "cache", json_string(rc->cache)); json_object_set_new(object, "source", json_string(rc->source)); memset(mode, 0x0, sizeof(mode)); if (strncmp(rc->device, "rc-wt_", 5) == 0) { sprintf(mode, "write-through"); } else if (strncmp(rc->device, "rc-wb_", 5) == 0) { sprintf(mode, "writeback"); } else { sprintf(mode, "write-around"); } json_object_set_new(object, "mode", json_string(mode)); json_array_append_new(rc_array, object); rc = rc->next; } json_object_set_new(rc_object, "rapiddisk_cache", rc_array); json_array_append_new(array, rc_object); root = json_pack("{s:o}", "volumes", array); jdumped = json_dumps(root, 0); json_decref(root); if (jdumped != NULL) { if (!wantresult) { // We just need the JSON string to be printed printf("%s\n", jdumped); free(jdumped); jdumped = NULL; } else { // We want to return the JSON string as a pointer == daemon == must be free()d *list_result = jdumped; } } else { res = INVALID_VALUE; } return res; } /* * JSON output format: * { * "resources": [ * { * "memory": [ * { * "mem_total": 2084130816, * "mem_free": 236990464 * } * ] * }, * { * "volumes": [ * { * "device": "sda", * "size": 26843545600, * "vendor": "ATA", * "model": "VBOX HARDDISK" * } * ] * } * ] * } */ /** * It takes a pointer to a memory profile and a pointer to a volume profile, and returns or print a JSON string containing the * memory and volume profiles * * @param mem a pointer to a struct MEM_PROFILE * @param volume A pointer to the first element of the linked list of VOLUME_PROFILE structures. * @param list_result This is the pointer to the pointer to the JSON string that will be valued if needed. * @param wantresult If TRUE, the function will place the pointer to the JSON string into *list_result (must be free()d later). * If FALSE, the function will print the JSON string to stdout. * * @return An @p int representing the result. */ int json_resources_list(struct MEM_PROFILE *mem, struct VOLUME_PROFILE *volume, char **list_result, bool wantresult) { json_t *root, *array = json_array(), *mem_array = json_array(), *vol_array = json_array(); json_t *mem_object = json_object(), *vol_object = json_object() ; char *jdumped = NULL; int res = SUCCESS; if (mem != NULL) { json_t *object = json_object(); json_object_set_new(object, "mem_total", json_integer(mem->mem_total)); json_object_set_new(object, "mem_free", json_integer(mem->mem_free)); json_array_append_new(mem_array, object); } json_object_set_new(mem_object, "memory", mem_array); json_array_append_new(array, mem_object); while (volume != NULL) { json_t *object = json_object(); json_object_set_new(object, "device", json_string(volume->device)); json_object_set_new(object, "size", json_integer(volume->size)); json_object_set_new(object, "vendor", json_string(volume->vendor)); json_object_set_new(object, "model", json_string(volume->model)); json_array_append_new(vol_array, object); volume = volume->next; } json_object_set_new(vol_object, "volumes", vol_array); json_array_append_new(array, vol_object); root = json_pack("{s:o}", "resources", array); jdumped = json_dumps(root, 0); json_decref(root); if (jdumped != NULL) { if (!wantresult) { // We just need the JSON string to be printed printf("%s\n", jdumped); free(jdumped); jdumped = NULL; } else { // We want to return the JSON string as a pointer == daemon == must be free()d *list_result = jdumped; } } else { res = INVALID_VALUE; } return res; } /* * JSON output format: * { * "statistics": [ * { * "cache_stats": [ * { * "device": "rc-wt_loop7", * "reads": 527, * "writes": 1, * "cache_hits": 264, * "replacement": 0, * "write_replacement": 0, * "read_invalidates": 1, * "write_invalidates": 1, * "uncached_reads": 1, * "uncached_writes": 0, * "disk_reads": 263, * "disk_writes": 1, * "cache_reads": 264, * "cache_writes": 263 * } * ] * } * ] * } */ /** * It takes a pointer to a struct RC_STATS, a pointer to a pointer to a char, and a bool, and returns an int * * @param stats A pointer to a struct RC_STATS. If this is NULL, then the function will return an empty JSON object. * @param stats_result A pointer to a char pointer. This is where the pointer to the JSON string will be stored. * @param wantresult If TRUE, the function will place the pointer to the JSON string into *stats_result (must be free()d later). * If FALSE, the JSON string will be printed to stdout. * * @return An @p int representing the result. */ int json_cache_statistics(struct RC_STATS *stats, char **stats_result, bool wantresult) { json_t *root, *array = json_array(), *stats_array = json_array(), *stats_object = json_object(); char *jdumped = NULL; int res = SUCCESS; if (stats != NULL) { json_t *object = json_object(); json_object_set_new(object, "device", json_string(stats->device)); json_object_set_new(object, "reads", json_integer(stats->reads)); json_object_set_new(object, "writes", json_integer(stats->writes)); json_object_set_new(object, "cache_hits", json_integer(stats->cache_hits)); json_object_set_new(object, "replacement", json_integer(stats->replacement)); json_object_set_new(object, "write_replacement", json_integer(stats->write_replacement)); json_object_set_new(object, "read_invalidates", json_integer(stats->read_invalidates)); json_object_set_new(object, "write_invalidates", json_integer(stats->write_invalidates)); json_object_set_new(object, "uncached_reads", json_integer(stats->uncached_reads)); json_object_set_new(object, "uncached_writes", json_integer(stats->uncached_writes)); json_object_set_new(object, "disk_reads", json_integer(stats->disk_reads)); json_object_set_new(object, "disk_writes", json_integer(stats->disk_writes)); json_object_set_new(object, "cache_reads", json_integer(stats->cache_reads)); json_object_set_new(object, "cache_writes", json_integer(stats->cache_writes)); json_array_append_new(stats_array, object); } json_object_set_new(stats_object, "cache_stats", stats_array); json_array_append_new(array, stats_object); root = json_pack("{s:o}", "statistics", array); jdumped = json_dumps(root, 0); json_decref(root); if (jdumped != NULL) { if (!wantresult) { // We just need the JSON string to be printed printf("%s\n", jdumped); free(jdumped); jdumped = NULL; } else { // We want to return the JSON string as a pointer == daemon == must be free()d *stats_result = jdumped; } } else { res = INVALID_VALUE; } return res; } /* * JSON output format: * { * "statistics": [ * { * "cache_stats": [ * { * "device": "rc-wb_sdd", * "errors": 0, * "num_blocks": 16320, * "num_free_blocks": 16320, * "num_wb_blocks": 0 * } * ] * } * ] * } */ /** * It takes a pointer to a struct WC_STATS, and returns a JSON string containing the statistics * * @param stats A pointer to a struct WC_STATS. If NULL, the function will return an empty JSON object. * @param stats_result This is a pointer to a pointer to a char. It's a pointer to a pointer because we want to return the * JSON string to the caller. The caller will need to free() the JSON string. * @param wantresult If TRUE, the function will place the pointer to the JSON string into *stats_result (must be free()d later). * If FALSE, the JSON string will be printed to stdout. * * @return An @p int representing the result. */ int json_cache_wb_statistics(struct WC_STATS *stats, char **stats_result, bool wantresult) { json_t *root, *array = json_array(), *stats_array = json_array(), *stats_object = json_object(); char *jdumped = NULL; int res = SUCCESS; if (stats != NULL) { json_t *object = json_object(); json_object_set_new(object, "device", json_string(stats->device)); json_object_set_new(object, "errors", json_integer(stats->errors)); json_object_set_new(object, "num_blocks", json_integer(stats->num_blocks)); json_object_set_new(object, "num_free_blocks", json_integer(stats->num_free_blocks)); json_object_set_new(object, "num_wb_blocks", json_integer(stats->num_wb_blocks)); if (stats->expanded == TRUE) { json_object_set_new(object, "num_read_req", json_integer(stats->num_read_req)); json_object_set_new(object, "num_read_cache_hits", json_integer(stats->num_read_cache_hits)); json_object_set_new(object, "num_write_req", json_integer(stats->num_write_req)); json_object_set_new(object, "num_write_uncommitted_blk_hits", json_integer(stats->num_write_uncommitted_blk_hits)); json_object_set_new(object, "num_write_committed_blk_hits", json_integer(stats->num_write_committed_blk_hits)); json_object_set_new(object, "num_write_cache_bypass", json_integer(stats->num_write_cache_bypass)); json_object_set_new(object, "num_write_cache_alloc", json_integer(stats->num_write_cache_alloc)); json_object_set_new(object, "num_write_freelist_blocked", json_integer(stats->num_write_freelist_blocked)); json_object_set_new(object, "num_flush_req", json_integer(stats->num_flush_req)); json_object_set_new(object, "num_discard_req", json_integer(stats->num_discard_req)); } json_array_append_new(stats_array, object); } json_object_set_new(stats_object, "cache_stats", stats_array); json_array_append_new(array, stats_object); root = json_pack("{s:o}", "statistics", array); jdumped = json_dumps(root, 0); json_decref(root); if (jdumped != NULL) { if (!wantresult) { // We just need the JSON string to be printed printf("%s\n", jdumped); free(jdumped); jdumped = NULL; } else { // We want to return the JSON string as a pointer == daemon == must be free()d *stats_result = jdumped; } } else { res = INVALID_VALUE; } return res; } /* * JSON output format: * { * "targets": [ * { * "nvmet_targets": [ * { * "nqn": "nqn.2021-06.org.rapiddisk:ubu22042-rd2", * "namespace": 1, * "device": "/dev/rd2", * "enabled": "true", * "allowed_hosts": [ * { * "host": "nqn.host2" * } * ], * "assigned_ports": [ * { * "port": 1, * "address": "10.0.0.104", * "protocol": "tcp" * } * ] * }, * { * "nqn": "nqn.2021-06.org.rapiddisk:ubu22042-rd0", * "namespace": 1, * "device": "/dev/rd0", * "enabled": "true", * "allowed_hosts": [], * "assigned_ports": [ * { * "port": 2, * "address": "UNDEFINED", * "protocol": "loop" * } * ] * } * ] * } * ] * } */ /** * It takes a linked list of NVMET_PROFILE structures and converts them to JSON * * @param nvmet A pointer to the first element of the linked list of NVMET_PROFILE structures. * @param json_result This is a pointer to a pointer to a char. It's used to return the JSON string to the caller. * @param wantresult If TRUE, the function will place the pointer to the JSON string into *stats_result (must be free()d later). * If FALSE, the JSON string will be printed to stdout. * * @return A JSON string containing the NVMET targets and ports. */ int json_nvmet_view_exports(struct NVMET_PROFILE *nvmet, char **json_result, bool wantresult) { json_t *root, *array = json_array(), *nvmet_array = json_array(); json_t *nvmet_object = json_object(); NVMET_PROFILE *nvmet_orig = nvmet; NVMET_PORTS *ports; NVMET_ALLOWED_HOST *allowed_hosts; int res = SUCCESS; while (nvmet != NULL) { json_t *object = json_object(); json_object_set_new(object, "nqn", json_string(nvmet->nqn)); json_object_set_new(object, "namespace", json_integer(nvmet->namespc)); json_object_set_new(object, "device", json_string(nvmet->device)); json_object_set_new(object, "enabled", json_string((nvmet->enabled == ENABLED) ? "true" : "false")); json_t *hosts_allowed_array = json_array(); allowed_hosts = nvmet->allowed_hosts; while (allowed_hosts != NULL) { json_t *object_h = json_object(); json_object_set_new(object_h, "host", json_string(allowed_hosts->allowed_host)); json_array_append_new(hosts_allowed_array, object_h); allowed_hosts = allowed_hosts->next; } json_object_set_new(object, "allowed_hosts", hosts_allowed_array); json_t *assigned_ports_array = json_array(); ports = nvmet->assigned_ports; while (ports != NULL) { json_t *object_p = json_object(); json_object_set_new(object_p, "port", json_integer(ports->port)); json_object_set_new(object_p, "address", json_string(ports->addr)); json_object_set_new(object_p, "protocol", json_string(ports->protocol)); json_array_append_new(assigned_ports_array, object_p); ports = ports->next; } json_object_set_new(object, "assigned_ports", assigned_ports_array); json_array_append_new(nvmet_array, object); nvmet = nvmet->next; } json_object_set_new(nvmet_object, "nvmet_targets", nvmet_array); json_array_append_new(array, nvmet_object); root = json_pack("{s:o}", "targets", array); char *jdumped = json_dumps(root, 0); if (jdumped != NULL) { if (!wantresult) { // We just need the JSON string to be printed printf("%s\n", jdumped); free(jdumped); jdumped = NULL; } else { // We want to return the JSON string as a pointer == daemon == must be free()d *json_result = jdumped; } } else { res = INVALID_VALUE; } json_decref(root); return res; } /* * JSON output format: * { * "targets": [ * { * "nvmet_ports": [ * { * "port": 1, * "address": "10.0.0.185", * "protocol": "tcp" * } * ] * } * ] * } */ /** * It takes a linked list of NVMET ports and returns a JSON string * * @param ports A pointer to the first element of a linked list of NVMET_PORTS structures. * @param json_result This is a pointer to a char pointer. If you want to return the JSON string, you must set this to the * address of a char pointer. If you don't want to return the JSON string, you must set this to NULL. * @param wantresult If true, the JSON string is returned as a pointer. If false, the JSON string is printed to stdout. * * @return The function status result. */ int json_nvmet_view_ports(struct NVMET_PORTS *ports, char **json_result, bool wantresult) { json_t *root, *array = json_array(), *ports_array = json_array(); json_t *ports_object = json_object(); int res = SUCCESS; while (ports != NULL) { json_t *object = json_object(); json_object_set_new(object, "port", json_integer(ports->port)); json_object_set_new(object, "address", json_string(ports->addr)); json_object_set_new(object, "protocol", json_string(ports->protocol)); json_array_append_new(ports_array, object); ports = ports->next; } json_object_set_new(ports_object, "nvmet_ports", ports_array); json_array_append_new(array, ports_object); root = json_pack("{s:o}", "targets", array); char *jdumped = json_dumps(root, 0); if (jdumped != NULL) { if (!wantresult) { // We just need the JSON string to be printed printf("%s\n", jdumped); free(jdumped); jdumped = NULL; } else { // We want to return the JSON string as a pointer == daemon == must be free()d *json_result = jdumped; } } else { res = INVALID_VALUE; } json_decref(root); return res; } #ifdef SERVER /* * JSON output format: * { * "status": "OK", * "version": "7.0.0" * } */ /** * It creates a JSON object with two key/value pairs, "status" and "version", and returns the JSON object as a string * * @param json_str This is a pointer to a pointer to a char. It's used to return the JSON string to the caller. * * @return An @p int representing the result. */ int json_status_check(char **json_str) { int rc = INVALID_VALUE; json_t *root = json_object(); json_object_set_new(root, "status", json_string("OK")); json_object_set_new(root, "version", json_string(VERSION_NUM)); char *jdumped = json_dumps(root, 0); if (jdumped != NULL) { if (json_str != NULL) { *json_str = jdumped; rc = SUCCESS; } } json_decref(root); return rc; } #endif rapiddisk-9.1.0/src/json.h000066400000000000000000000035371442147535600154270ustar00rootroot00000000000000/** * @file json.h * @brief JSON function declarations * @details This header file declares some JSON related functions * @copyright @verbatim Copyright © 2011 - 2023 Petros Koutoupis All rights reserved. This file is part of RapidDisk. RapidDisk is free software: you can redistribute it and/or modify@n 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. RapidDisk 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 RapidDisk. If not, see . SPDX-License-Identifier: GPL-2.0-or-later @endverbatim * @author Petros Koutoupis \ * @author Matteo Tenca \ * @version 9.1.0 * @date 23 April 2023 */ #ifndef JSON_H #define JSON_H #include "common.h" int json_device_list(struct RD_PROFILE *rd, struct RC_PROFILE *rc, char **list_result, bool wantresult); int json_resources_list(struct MEM_PROFILE *mem, struct VOLUME_PROFILE *volume, char **list_result, bool wantresult); int json_cache_statistics(struct RC_STATS *stats, char **stats_result, bool wantresult); int json_cache_wb_statistics(struct WC_STATS *stats, char **stats_result, bool wantresult); int json_status_return(int return_value, char *optional_message, char **json_result, bool wantresult); int json_nvmet_view_exports(struct NVMET_PROFILE *nvmet, char **json_result, bool wantresult); int json_nvmet_view_ports(struct NVMET_PORTS *ports, char **json_result, bool wantresult); #ifdef SERVER int json_status_check(char **json_str); #endif #endif //JSON_H rapiddisk-9.1.0/src/main.c000066400000000000000000000362721442147535600153770ustar00rootroot00000000000000/** * @file main.c * @brief Main administration functions implementation * @details This file contains all the functions related to the administration utility. * @copyright @verbatim Copyright © 2011 - 2023 Petros Koutoupis All rights reserved. This file is part of RapidDisk. RapidDisk is free software: you can redistribute it and/or modify@n 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. RapidDisk 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 RapidDisk. If not, see . SPDX-License-Identifier: GPL-2.0-or-later @endverbatim * @author Petros Koutoupis \ * @author Matteo Tenca \ * @version 9.1.0 * @date 23 April 2023 */ #include "main.h" #include "utils.h" #include "sys.h" #include "rdsk.h" #include "json.h" #include "nvmet.h" bool writeback_enabled; void online_menu(char *string) { printf("%s is an administration tool to manage RapidDisk RAM disk devices and\n" "\tRapidDisk-Cache mappings.\n\n", string); printf("Usage: %s [ -h | -v ] function [ parameters ]\n\n", string); printf("Description:\n\t%s is a RapidDisk module management tool to manage RapidDisk\n" "\tRAM disk devices. Dynamically create, remove, resize RAM volumes and if\n" "\tdesired, map or unmap them as a cache volume to any block device.\n\n", string); printf("Functions:\n" "\t-a\t\tAttach RAM disk device (size in MBytes).\n" "\t-b\t\tBackend block device absolute path (for cache mapping).\n" "\t-c\t\tInput capacity for size or resize of RAM disk device (in MBytes).\n" "\t-d\t\tDetach RAM disk device.\n" "\t-e\t\tExport a RapidDisk block device as an NVMe Target.\n" "\t-f\t\tErase all data to a specified RapidDisk device \033[31;1m(dangerous)\033[0m.\n" "\t-g\t\tDo not print header.\n" "\t-H\t\tThe host to export / unexport the NVMe Target to / from.\n" "\t-h\t\tDisplay the help menu.\n" "\t-i\t\tDefine the network interface to enable for NVMe Target exporting.\n" "\t-j\t\tEnable JSON formatted output.\n" "\t-L\t\tLock a RapidDisk block device (set to read-only).\n" "\t-l\t\tList all attached RAM disk devices.\n" "\t-m\t\tMap an RapidDisk device as a caching node to another block device.\n" "\t-N\t\tList only enabled NVMe Target ports.\n" "\t-n\t\tList RapidDisk enabled NVMe Target exports.\n" "\t-P\t\tThe port to export / unexport the NVMe Target to / from.\n" "\t-p\t\tDefine cache policy: write-through, write-around or writeback \033[31;1m(dangerous)\033[0m\n" "\t\t\t(default: write-through). Writeback caching is supplied by the dm-writecache\n" "\t\t\tkernel module and is not intended for production use as it may result in data\n" "\t\t\tloss on hardware/power failure.\n" "\t-q\t\tList all system memory and block device resources.\n" "\t-R\t\tRevalidate size of NVMe export using existing RapidDisk device.\n" "\t-r\t\tDynamically grow the size of an existing RapidDisk device.\n" "\t-s\t\tObtain RapidDisk-Cache Mappings statistics.\n" "\t-t\t\tDefine the NVMe Target port's transfer protocol (i.e. tcp, rdma or loop).\n" "\t-U\t\tUnlock a RapidDisk block device (set to read-write).\n" "\t-u\t\tUnmap a RapidDisk device from another block device.\n" "\t-v\t\tDisplay the utility version string.\n" "\t-X\t\tRemove the NVMe Target port (must be unused).\n" "\t-x\t\tUnexport a RapidDisk block device from an NVMe Target.\n\n"); printf("Example Usage:\n\trapiddisk -a 64\n" "\trapiddisk -d rd2\n" "\trapiddisk -r rd2 -c 128\n" "\trapiddisk -m rd1 -b /dev/sdb\n" "\trapiddisk -m rd1 -b /dev/sdb -p wt\n" "\trapiddisk -m rd3 -b /dev/mapper/rc-wa_sdb -p wb\n" "\trapiddisk -u rc-wt_sdb\n" "\trapiddisk -f rd2\n" "\trapiddisk -L rd2\n" "\trapiddisk -U rd3\n" "\trapiddisk -i eth0 -P 1 -t tcp\n" "\trapiddisk -i NULL -P 2 -t loop\n" "\trapiddisk -X -P 1\n" "\trapiddisk -e -b rd3 -P 1 -H nqn.host1\n" "\trapiddisk -R -b rd0\n" "\trapiddisk -x -b rd3 -P 1 -H nqn.host1\n\n"); } int exec_cmdline_arg(int argcin, char *argvin[]) { int rc = INVALID_VALUE, mode = WRITETHROUGH, action = ACTION_NONE, i, port = INVALID_VALUE, xfer = XFER_MODE_TCP; unsigned long long size = 0; bool json_flag = FALSE; bool header_flag = TRUE; char device[NAMELEN] = {0}, backing[NAMELEN] = {0}, host[NAMELEN] = {0}, header[NAMELEN] = {0}; char generic_msg[NAMELEN] = {0}; struct RD_PROFILE *disk = NULL; struct RC_PROFILE *cache = NULL; struct MEM_PROFILE *mem = NULL; struct VOLUME_PROFILE *volumes = NULL; sprintf(header, "%s %s\n%s\n\n", PROCESS, VERSION_NUM, COPYRIGHT); while ((i = getopt(argcin, argvin, "?:a:b:c:d:ef:gH:hi:jL:lm:NnP:p:qRr:s:t:U:u:VvXx")) != INVALID_VALUE) { switch (i) { case 'h': printf("%s", header); online_menu(PROCESS); return SUCCESS; case 'a': action = ACTION_ATTACH; size = strtoul(optarg, (char **)NULL, 10); break; case 'b': sprintf(backing, "%s", optarg); break; case 'c': size = strtoul(optarg, (char **)NULL, 10); break; case 'd': action = ACTION_DETACH; sprintf(device, "%s", optarg); break; case 'e': action = ACTION_EXPORT_NVMET; break; case 'f': action = ACTION_FLUSH; sprintf(device, "%s", optarg); break; case 'g': header_flag = FALSE; break; case 'H': sprintf(host, "%s", optarg); break; case 'i': action = ACTION_ENABLE_NVMET_PORT; sprintf(host, "%s", optarg); break; case 'j': header_flag = FALSE; json_flag = TRUE; break; case 'L': action = ACTION_LOCK; sprintf(device, "%s", optarg); break; case 'l': action = ACTION_LIST; break; case 'm': action = ACTION_CACHE_MAP; sprintf(device, "%s", optarg); break; case 'N': action = ACTION_LIST_NVMET_PORTS; break; case 'n': action = ACTION_LIST_NVMET; break; case 'P': port = atoi(optarg); break; case 'p': if (strcmp(optarg, "wa") == 0) mode = WRITEAROUND; else if (strcmp(optarg, "wb") == 0) { mode = WRITEBACK; } break; case 'q': action = ACTION_QUERY_RESOURCES; break; case 'R': action = ACTION_REVALIDATE_NVMET_SIZE; break; case 'r': action = ACTION_RESIZE; sprintf(device, "%s", optarg); break; case 's': action = ACTION_CACHE_STATS; sprintf(device, "%s", optarg); break; case 't': if (strcmp(optarg, "rdma") == 0) xfer = XFER_MODE_RDMA; else if (strcmp(optarg, "loop") == 0) xfer = XFER_MODE_LOOP; break; case 'U': action = ACTION_UNLOCK; sprintf(device, "%s", optarg); break; case 'u': action = ACTION_CACHE_UNMAP; sprintf(device, "%s", optarg); break; case 'v': printf("%s", header); return SUCCESS; case 'X': action = ACTION_DISABLE_NVMET_PORT; break; case 'x': action = ACTION_UNEXPORT_NVMET; break; default: case '?': printf("%s", header); online_menu(PROCESS); return INVALID_VALUE; } } if (header_flag == TRUE) { printf("%s", header); } if ((writeback_enabled == FALSE) && (mode == WRITEBACK)) { print_message(-EPERM, ERR_NOWB_MODULE, json_flag); return -EPERM; } disk = search_rdsk_targets(generic_msg); if ((disk == NULL) && (strlen(generic_msg) != 0)) { print_message(INVALID_VALUE, generic_msg, json_flag); return INVALID_VALUE; } cache = search_cache_targets(generic_msg); if ((cache == NULL) && (strlen(generic_msg) != 0)) { print_message(INVALID_VALUE, generic_msg, json_flag); free_linked_lists(NULL, disk, NULL); return INVALID_VALUE; } switch(action) { case ACTION_ATTACH: if (size <= 0) { rc = -EINVAL; print_message(rc, ERR_INVALID_ARG, json_flag); } else { char mem_device_attach_msg[NAMELEN] = {0}; rc = mem_device_attach(disk, size, mem_device_attach_msg); print_message(rc, mem_device_attach_msg, json_flag); } break; case ACTION_DETACH: if (disk == NULL) { rc = -EINVAL; print_message(rc, ERR_NO_DEVICES, json_flag); break; } else { if (strlen(device) <= 0) { rc = -EINVAL; print_message(rc, ERR_INVALID_ARG, json_flag); break; } rc = mem_device_detach(disk, cache, device, generic_msg); } print_message(rc, generic_msg, json_flag); break; case ACTION_FLUSH: rc = mem_device_flush(disk, cache, device, generic_msg); print_message(rc, generic_msg, json_flag); break; case ACTION_LIST: if (disk == NULL) { rc = -ENOENT; print_message(rc, ERR_NO_DEVICES, json_flag); } else { if (json_flag == TRUE) rc = json_device_list(disk, cache, NULL, FALSE); else rc = mem_device_list(disk, cache); } break; case ACTION_CACHE_MAP: if ((strlen(device) <= 0) || (strlen(backing) <= 0)) { rc = -EINVAL; print_message(rc, ERR_INVALID_ARG, json_flag); break; } rc = cache_device_map(disk, cache, device, backing, mode, generic_msg); print_message(rc, generic_msg, json_flag); break; case ACTION_RESIZE: if (disk == NULL) { rc = -EINVAL; print_message(rc, ERR_NO_DEVICES, json_flag); break; } else { if (size <= 0 || strlen(device) <= 0) { rc = -EINVAL; print_message(rc, ERR_INVALID_ARG, json_flag); break; } rc = mem_device_resize(disk, device, size, generic_msg); } print_message(rc, generic_msg, json_flag); break; case ACTION_CACHE_STATS: if (strlen(device) <= 0) { rc = -EINVAL; print_message(rc, ERR_INVALID_ARG, json_flag); break; } else { if (strstr(device, "rc-wb") != NULL) { if (!json_flag) rc = cache_wb_device_stat(cache, device); else { struct WC_STATS *wc_stats = NULL; if (cache_wb_device_stat_json(cache, device, &wc_stats) == SUCCESS) { rc = json_cache_wb_statistics(wc_stats, NULL, FALSE); } if (wc_stats) free(wc_stats); } } else { if (!json_flag) rc = cache_device_stat(cache, device); else { struct RC_STATS *rc_stats = NULL; if (cache_device_stat_json(cache, device, &rc_stats) == SUCCESS) { rc = json_cache_statistics(rc_stats, NULL, FALSE); } if (rc_stats) free(rc_stats); } } } break; case ACTION_CACHE_UNMAP: rc = cache_device_unmap(cache, device, generic_msg); print_message(rc, generic_msg, json_flag); break; case ACTION_QUERY_RESOURCES: mem = (struct MEM_PROFILE *)calloc(1, sizeof(struct MEM_PROFILE)); if (get_memory_usage(mem, generic_msg) != SUCCESS) { if (mem) free(mem); mem = NULL; rc = -EIO; print_message(rc, ERR_NO_MEMUSAGE, json_flag); break; } volumes = search_volumes_targets(generic_msg); if ((volumes == NULL) && (strlen(generic_msg) != 0)) { if (mem) free(mem); mem = NULL; rc = INVALID_VALUE; print_message(rc, generic_msg, json_flag); break; } if (json_flag == TRUE) rc = json_resources_list(mem, volumes, NULL, FALSE); else rc = resources_list(mem, volumes); if (mem) free(mem); mem = NULL; break; case ACTION_LIST_NVMET_PORTS: rc = nvmet_view_ports(json_flag, generic_msg); if (rc != SUCCESS) { print_message(rc, generic_msg, json_flag); } break; case ACTION_LIST_NVMET: rc = nvmet_view_exports(json_flag, generic_msg); if (rc != SUCCESS) { print_message(rc, generic_msg, json_flag); } break; case ACTION_ENABLE_NVMET_PORT: if (port == INVALID_VALUE) { rc = -EINVAL; print_message(rc, ERR_INVALID_PORT, json_flag); break; } if (json_flag) { rc = nvmet_enable_port(host, port, xfer, generic_msg); print_message(rc, generic_msg, json_flag); } else { rc = nvmet_enable_port(host, port, xfer, NULL); } break; case ACTION_DISABLE_NVMET_PORT: if (port == INVALID_VALUE) { rc = -EINVAL; print_message(rc, ERR_INVALID_PORT, json_flag); break; } if (json_flag) { rc = nvmet_disable_port(port, generic_msg); print_message(rc, generic_msg, json_flag); } else { rc = nvmet_disable_port(port, NULL); } break; case ACTION_EXPORT_NVMET: if (strlen(backing) <= 0) { rc = -EINVAL; print_message(rc, ERR_INVALID_ARG, json_flag); break; } if ((disk == NULL) && (cache == NULL)) { rc = SUCCESS; print_message(rc, ERR_NO_DEVICES, json_flag); break; } if (json_flag) { rc = nvmet_export_volume(disk, cache, backing, host, port, generic_msg); print_message(rc, generic_msg, json_flag); } else { rc = nvmet_export_volume(disk, cache, backing, host, port, NULL); } break; case ACTION_UNEXPORT_NVMET: if (strlen(backing) <= 0){ rc = -EINVAL; print_message(rc, ERR_INVALID_ARG, json_flag); break; } if (json_flag) { rc = nvmet_unexport_volume(backing, host, port, generic_msg); print_message(rc, generic_msg, json_flag); } else { rc = nvmet_unexport_volume(backing, host, port, NULL); } break; case ACTION_LOCK: if (strlen(device) <= 0){ rc = -EINVAL; print_message(rc, ERR_INVALID_ARG, json_flag); break; } rc = mem_device_lock(disk, device, TRUE, generic_msg); print_message(rc, generic_msg, json_flag); break; case ACTION_UNLOCK: if (strlen(device) <= 0){ rc = -EINVAL; print_message(rc, ERR_INVALID_ARG, json_flag); break; } rc = mem_device_lock(disk, device, FALSE, generic_msg); print_message(rc, generic_msg, json_flag); break; case ACTION_REVALIDATE_NVMET_SIZE: if (strlen(backing) <= 0){ rc = -EINVAL; print_message(rc, ERR_INVALID_ARG, json_flag); break; } if (json_flag) { rc = nvmet_revalidate_size(disk, cache, backing, generic_msg); print_message(rc, generic_msg, json_flag); } else { rc = nvmet_revalidate_size(disk, cache, backing, NULL); } break; default: case ACTION_NONE: if (header_flag == FALSE) { printf("%s", header); } online_menu(PROCESS); break; } free_linked_lists(cache, disk, volumes); cache = NULL; disk = NULL; volumes = NULL; return rc; } /** * It checks if the user is root, then checks if the required kernel modules are loaded, then executes the command line * arguments * * @param argc The number of arguments passed to the program. * @param argv The command line arguments * * @return The return code of the last executed command. */ int main(int argc, char *argv[]) { int rc = INVALID_VALUE; /* This preprocessor if/endif is useful when the debbugger can't run as root */ #ifndef DEBUG if (geteuid() != 0) { printf("\nYou must be root or contain sudo permissions to initiate this.\n\n"); return -EACCES; } #endif writeback_enabled = FALSE; rc = check_loaded_modules(); if (rc != SUCCESS) { if (rc == 1) writeback_enabled = TRUE; else return -EPERM; } rc = exec_cmdline_arg(argc, argv); return rc; } rapiddisk-9.1.0/src/main.h000066400000000000000000000040331442147535600153720ustar00rootroot00000000000000/** * @file * @brief rapiddisk tool definitions * @details This header file defines some constants used by the rapiddisk tool * @copyright @verbatim Copyright © 2011 - 2023 Petros Koutoupis All rights reserved. This file is part of RapidDisk. RapidDisk is free software: you can redistribute it and/or modify@n 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. RapidDisk 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 RapidDisk. If not, see . SPDX-License-Identifier: GPL-2.0-or-later @endverbatim * @author Petros Koutoupis \ * @author Matteo Tenca \ * @version 9.1.0 * @date 23 April 2023 */ #ifndef MAIN_H #define MAIN_H #define ACTION_NONE 0x0 #define ACTION_ATTACH 0x1 #define ACTION_DETACH 0x2 #define ACTION_FLUSH 0x3 #define ACTION_LIST 0x4 #define ACTION_CACHE_MAP 0x5 #define ACTION_RESIZE 0x6 #define ACTION_CACHE_STATS 0x7 #define ACTION_CACHE_UNMAP 0x8 #define ACTION_QUERY_RESOURCES 0x9 #define ACTION_LIST_NVMET_PORTS 0xa #define ACTION_LIST_NVMET 0xb #define ACTION_ENABLE_NVMET_PORT 0xc #define ACTION_DISABLE_NVMET_PORT 0xd #define ACTION_EXPORT_NVMET 0xe #define ACTION_UNEXPORT_NVMET 0xf #define ACTION_LOCK 0x10 #define ACTION_UNLOCK 0x11 #define ACTION_REVALIDATE_NVMET_SIZE 0x12 #define ERR_INVALID_ARG "Error. Invalid argument(s) or values entered." #define ERR_NOWB_MODULE "Please ensure that the dm-writecache module is loaded and retry." #define ERR_NO_DEVICES "Unable to locate any RapidDisk devices." #define ERR_NO_MEMUSAGE "Error. Unable to retrieve memory usage data." #define ERR_INVALID_PORT "Error. Invalid port number." #endif rapiddisk-9.1.0/src/net.c000066400000000000000000000713121442147535600152330ustar00rootroot00000000000000/** * @file net.c * @brief Daemon functions implementation * @details This file contains all the function related to the daemon * @copyright @verbatim Copyright © 2011 - 2023 Petros Koutoupis All rights reserved. This file is part of RapidDisk. RapidDisk is free software: you can redistribute it and/or modify@n 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. RapidDisk 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 RapidDisk. If not, see . SPDX-License-Identifier: GPL-2.0-or-later @endverbatim * @author Petros Koutoupis \ * @author Matteo Tenca \ * @version 9.1.0 * @date 23 April 2023 */ #define SERVER #include "rapiddiskd.h" #include "utils.h" #include "rdsk.h" #include "sys.h" #include "json.h" #include "nvmet.h" #include #include #include int server_stop_requested = 0; /** * The responses to our GET requests. Although, we are not SPECIFICALLY checking that they are GETs. * We are just check the URL and the string command. */ #if MHD_VERSION >= 0x00097100 static enum MHD_Result answer_to_connection(void *cls, struct MHD_Connection *connection, const char *url, #else static int answer_to_connection(void *cls, struct MHD_Connection *connection, const char *url, #endif const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **con_cls) { struct MHD_Response *response; int rc, status = MHD_HTTP_OK; unsigned long long size; char device[NAMELEN] = {0}, source[NAMELEN] = {0}; char *dup = NULL, *token = NULL; char *json_str = NULL; struct RD_PROFILE *disk = NULL; struct RC_PROFILE *cache = NULL; struct VOLUME_PROFILE *volumes = NULL; struct MEM_PROFILE *mem = NULL; struct DAEMON_ARGS *args = (struct DAEMON_ARGS *) cls; char *error_message = calloc(1, NAMELEN); char *split_arr[64] = {NULL}; char msg[NAMELEN] = {0}; if (strcmp(method, "GET") == SUCCESS) { if (strcmp(url, CMD_PING_DAEMON) == SUCCESS) { if (args->verbose) fprintf(stderr, verbose_msg(msg, D_RECV_REQ), DAEMON, CMD_PING_DAEMON); json_status_check(&json_str); } else if (strcmp(url, CMD_LIST_RESOURCES) == SUCCESS) { if (args->verbose) fprintf(stderr, verbose_msg(msg, D_RECV_REQ), DAEMON, CMD_LIST_RESOURCES); volumes = search_volumes_targets(error_message); if ((volumes == NULL) && (strlen(error_message) != 0)) { rc = INVALID_VALUE; syslog(LOG_ERR|LOG_DAEMON, "%s.", error_message); if (args->verbose) fprintf(stderr, verbose_msg(msg, error_message), DAEMON); json_status_return(rc, error_message, &json_str, TRUE); } else { mem = (struct MEM_PROFILE *) calloc(1, sizeof(struct MEM_PROFILE)); if ((rc = get_memory_usage(mem, error_message)) == INVALID_VALUE) { syslog(LOG_ERR|LOG_DAEMON, "%s.", error_message); if (args->verbose) fprintf(stderr, verbose_msg(msg, error_message), DAEMON); json_status_return(rc, error_message, &json_str, TRUE); } else { json_resources_list(mem, volumes, &json_str, TRUE); } if (mem) free(mem); mem = NULL; free_linked_lists(NULL, NULL, volumes); volumes = NULL; } } else if (strcmp(url, CMD_LIST_RD_VOLUMES) == SUCCESS) { if (args->verbose) fprintf(stderr, verbose_msg(msg, D_RECV_REQ), DAEMON, CMD_LIST_RD_VOLUMES); disk = search_rdsk_targets(error_message); if ((disk == NULL) && (strlen(error_message) != 0)) { syslog(LOG_ERR|LOG_DAEMON, "%s", error_message); if (args->verbose) fprintf(stderr, verbose_msg(msg, error_message), DAEMON); json_status_return(INVALID_VALUE, error_message, &json_str, TRUE); } else { cache = search_cache_targets(error_message); if ((cache == NULL) && (strlen(error_message) != 0)) { syslog(LOG_ERR|LOG_DAEMON, "%s", error_message); if (args->verbose) fprintf(stderr, verbose_msg(msg, error_message), DAEMON); json_status_return(INVALID_VALUE, error_message, &json_str, TRUE); free_linked_lists(NULL, disk, NULL); disk = NULL; } else { json_device_list(disk, cache, &json_str, TRUE); free_linked_lists(cache, disk, NULL); cache = NULL; disk = NULL; } } } else if (strncmp(url, CMD_RCACHE_STATS, sizeof(CMD_RCACHE_STATS) - 1) == SUCCESS) { if (args->verbose) fprintf(stderr, verbose_msg(msg, D_RECV_REQ), DAEMON, CMD_RCACHE_STATS); dup = strdup(url); preg_replace(CMD_RCACHE_STATS, "", dup, dup, strlen(dup)); int last = split(dup, split_arr, "/"); if (last != 0) { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_MALFORMED); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_MALFORMED), DAEMON); json_status_return(INVALID_VALUE, ERR_MALFORMED, &json_str, TRUE); } else { token = split_arr[last]; if (!token) { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_INVALIDURL); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_INVALIDURL), DAEMON); json_status_return(INVALID_VALUE, ERR_INVALIDURL, &json_str, TRUE); } else { if (strstr(token, "rc-wb") != NULL) { struct WC_STATS *stats = dm_get_status(token, WRITEBACK); if (stats) { json_cache_wb_statistics(stats, &json_str, TRUE); free(stats); } else { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_DEV_STATUS); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_DEV_STATUS), DAEMON); json_status_return(INVALID_VALUE, ERR_DEV_STATUS, &json_str, TRUE); } } else { struct RC_STATS *stats = dm_get_status(token, WRITETHROUGH); if (stats) { json_cache_statistics(stats, &json_str, TRUE); free(stats); } else { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_DEV_STATUS); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_DEV_STATUS), DAEMON); json_status_return(INVALID_VALUE, ERR_DEV_STATUS, &json_str, TRUE); } } } } } else if (strncmp(url, CMD_LIST_NVMET, sizeof(CMD_LIST_NVMET) - 1) == SUCCESS) { if (args->verbose) fprintf(stderr, verbose_msg(msg, D_RECV_REQ), DAEMON, CMD_LIST_NVMET); rc = nvmet_view_exports_json(error_message, &json_str); if (rc != SUCCESS) { json_status_return(rc, error_message, &json_str, TRUE); } } else if (strncmp(url, CMD_LIST_NVMET_PORTS, sizeof(CMD_LIST_NVMET_PORTS) - 1) == SUCCESS) { if (args->verbose) fprintf(stderr, verbose_msg(msg, D_RECV_REQ), DAEMON, CMD_LIST_NVMET_PORTS); rc = nvmet_view_ports_json(error_message, &json_str); if (rc != SUCCESS) { json_status_return(rc, error_message, &json_str, TRUE); } } else { syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_UNSUPPORTED); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_UNSUPPORTED), DAEMON); json_status_return(INVALID_VALUE, ERR_UNSUPPORTED, &json_str, TRUE); status = MHD_HTTP_BAD_REQUEST; } } else if (strcmp(method, "POST") == SUCCESS) { if ((strncmp(url, CMD_RDSK_CREATE, sizeof(CMD_RDSK_CREATE) - 1) == SUCCESS) && \ (strncmp(url, CMD_RCACHE_CREATE, sizeof(CMD_RCACHE_CREATE) - 1) != SUCCESS)) { if (args->verbose) fprintf(stderr, verbose_msg(msg, D_RECV_REQ), DAEMON, CMD_RDSK_CREATE); dup = strdup(url); preg_replace(CMD_RDSK_CREATE, "", dup, dup, strlen(dup)); int last = split(dup, split_arr, "/"); if (last != 0) { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_MALFORMED); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_MALFORMED), DAEMON); json_status_return(INVALID_VALUE, ERR_MALFORMED, &json_str, TRUE); } else { token = split_arr[last]; if (!token) { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_INVALIDURL); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_INVALIDURL), DAEMON); json_status_return(INVALID_VALUE, NULL, &json_str, TRUE); } else { errno = 0; char *end_ptr = NULL; size = strtoull(token, &end_ptr, 10); if (errno != 0) { syslog(LOG_ERR|LOG_DAEMON, "%s, %s", ERR_INVALID_SIZE, strerror(errno)); if (args->verbose) fprintf(stderr, verbose_msg(msg, "%s, %s"), DAEMON, ERR_INVALID_SIZE, strerror(errno)); json_status_return(INVALID_VALUE, ERR_INVALID_SIZE, &json_str, TRUE); } else if (end_ptr == token) { syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_NOTANUMBER); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_NOTANUMBER), DAEMON); json_status_return(INVALID_VALUE, ERR_NOTANUMBER, &json_str, TRUE); } else { disk = search_rdsk_targets(error_message); if ((disk == NULL) && (strlen(error_message) != 0)) { syslog(LOG_ERR | LOG_DAEMON, "%s", error_message); if (args->verbose) fprintf(stderr, verbose_msg(msg, error_message), DAEMON); json_status_return(INVALID_VALUE, error_message, &json_str, TRUE); } else { rc = mem_device_attach(disk, size, error_message); json_status_return(rc, error_message, &json_str, TRUE); int pri = LOG_INFO; if (rc < SUCCESS) pri = LOG_ERR; syslog(pri | LOG_DAEMON, "%s", error_message); if (args->verbose) fprintf(stderr, verbose_msg(msg, error_message), DAEMON); free_linked_lists(NULL, disk, NULL); disk = NULL; } } } } } else if ((strncmp(url, CMD_RDSK_REMOVE, sizeof(CMD_RDSK_REMOVE) - 1) == SUCCESS) && \ (strncmp(url, CMD_RCACHE_REMOVE, sizeof(CMD_RCACHE_REMOVE) - 1) != SUCCESS)) { if (args->verbose) fprintf(stderr, verbose_msg(msg, D_RECV_REQ), DAEMON, CMD_RCACHE_REMOVE); dup = strdup(url); preg_replace(CMD_RDSK_REMOVE, "", dup, dup, strlen(dup)); int last = split(dup, split_arr, "/"); if (last != 0) { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_MALFORMED); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_MALFORMED), DAEMON); json_status_return(INVALID_VALUE, ERR_MALFORMED, &json_str, TRUE); } else { token = split_arr[last]; if (!token) { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_INVALIDDEVNAME); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_INVALIDDEVNAME), DAEMON); json_status_return(INVALID_VALUE, ERR_INVALIDDEVNAME, &json_str, TRUE); } else { disk = search_rdsk_targets(error_message); if ((disk == NULL) && (strlen(error_message) != 0)) { syslog(LOG_ERR|LOG_DAEMON, "%s", error_message); if (args->verbose) fprintf(stderr, verbose_msg(msg, error_message), DAEMON); json_status_return(INVALID_VALUE, error_message, &json_str, TRUE); } else { cache = search_cache_targets(error_message); if (cache == NULL && strlen(error_message) != 0) { syslog(LOG_ERR|LOG_DAEMON, "%s", error_message); if (args->verbose) fprintf(stderr, verbose_msg(msg, error_message), DAEMON); json_status_return(INVALID_VALUE, error_message, &json_str, TRUE); free_linked_lists(NULL, disk, NULL); disk = NULL; } else { rc = mem_device_detach(disk, cache, token, error_message); int pri = LOG_INFO; if (rc < SUCCESS) pri = LOG_ERR; syslog(pri|LOG_DAEMON, "%s", error_message); if (args->verbose) fprintf(stderr, verbose_msg(msg, error_message), DAEMON); json_status_return(rc, error_message, &json_str, TRUE); free_linked_lists(cache, disk, NULL); disk = NULL; cache = NULL; } } } } } else if (strncmp(url, CMD_RDSK_RESIZE, sizeof(CMD_RDSK_RESIZE) - 1) == SUCCESS) { if (args->verbose) fprintf(stderr, verbose_msg(msg, D_RECV_REQ), DAEMON, CMD_RDSK_RESIZE); dup = strdup(url); preg_replace(CMD_RDSK_RESIZE, "", dup, dup, strlen(dup)); int last = split(dup, split_arr, "/"); if (last != 1) { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_MALFORMED); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_MALFORMED), DAEMON); json_status_return(INVALID_VALUE, ERR_MALFORMED, &json_str, TRUE); } else { token = split_arr[last - 1]; if (!token) { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_INVALIDURL); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_INVALIDURL), DAEMON); json_status_return(INVALID_VALUE, NULL, &json_str, TRUE); } else { sprintf(device, "%s", token); token = split_arr[last]; /* time to get the size */ if (!token) { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_INVALIDURL); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_INVALIDURL), DAEMON); json_status_return(INVALID_VALUE, NULL, &json_str, TRUE); } else { char *end_ptr = NULL; errno = 0; size = strtoull(token, &end_ptr, 10); if (errno != 0) { syslog(LOG_ERR|LOG_DAEMON, "%s, %s", ERR_INVALID_SIZE, strerror(errno)); if (args->verbose) fprintf(stderr, verbose_msg(msg, "%s, %s"), DAEMON, ERR_INVALID_SIZE, strerror(errno)); json_status_return(INVALID_VALUE, ERR_INVALID_SIZE, &json_str, TRUE); } else if (end_ptr == token) { syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_NOTANUMBER); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_NOTANUMBER), DAEMON); json_status_return(INVALID_VALUE, ERR_NOTANUMBER, &json_str, TRUE); } else { if ((disk = search_rdsk_targets(error_message)) == NULL) { syslog(LOG_ERR | LOG_DAEMON, "%s", error_message); if (args->verbose) fprintf(stderr, verbose_msg(msg, error_message), DAEMON); json_status_return(INVALID_VALUE, error_message, &json_str, TRUE); } else { rc = mem_device_resize(disk, device, size, error_message); int pri = LOG_INFO; if (rc < SUCCESS) pri = LOG_ERR; syslog(pri | LOG_DAEMON, "%s", error_message); if (args->verbose) fprintf(stderr, verbose_msg(msg, error_message), DAEMON); json_status_return(rc, error_message, &json_str, TRUE); free_linked_lists(NULL, disk, NULL); disk = NULL; } } } } } } else if (strncmp(url, CMD_RDSK_FLUSH, sizeof(CMD_RDSK_FLUSH) - 1) == SUCCESS) { if (args->verbose) fprintf(stderr, verbose_msg(msg, D_RECV_REQ), DAEMON, CMD_RDSK_FLUSH); dup = strdup(url); preg_replace(CMD_RDSK_FLUSH, "", dup, dup, strlen(dup)); int last = split(dup, split_arr, "/"); if (last != 0) { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_MALFORMED); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_MALFORMED), DAEMON); json_status_return(INVALID_VALUE, ERR_MALFORMED, &json_str, TRUE); } else { token = split_arr[last]; if (!token) { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_INVALIDDEVNAME); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_INVALIDDEVNAME), DAEMON); json_status_return(INVALID_VALUE, ERR_INVALIDDEVNAME, &json_str, TRUE); } else { disk = search_rdsk_targets(error_message); if ((disk == NULL) && (strlen(error_message) != 0)) { syslog(LOG_ERR|LOG_DAEMON, "%s", error_message); if (args->verbose) fprintf(stderr, verbose_msg(msg, error_message), DAEMON); json_status_return(INVALID_VALUE, error_message, &json_str, TRUE); } else { cache = search_cache_targets(error_message); if (cache == NULL && strlen(error_message) != 0) { syslog(LOG_ERR|LOG_DAEMON, "%s", error_message); if (args->verbose) fprintf(stderr, verbose_msg(msg, error_message), DAEMON); json_status_return(INVALID_VALUE, error_message, &json_str, TRUE); free_linked_lists(NULL, disk, NULL); disk = NULL; } else { rc = mem_device_flush(disk, cache, token, error_message); int pri = LOG_INFO; if (rc < SUCCESS) pri = LOG_ERR; syslog(pri|LOG_DAEMON, "%s", error_message); if (args->verbose) fprintf(stderr, verbose_msg(msg, error_message), DAEMON); json_status_return(rc, error_message, &json_str, TRUE); free_linked_lists(cache, disk, NULL); disk = NULL; cache = NULL; } } } } } else if (strncmp(url, CMD_RCACHE_CREATE, sizeof(CMD_RCACHE_CREATE) - 1) == SUCCESS) { if (args->verbose) fprintf(stderr, verbose_msg(msg, D_RECV_REQ), DAEMON, CMD_RCACHE_CREATE); int cache_mode = INVALID_VALUE; char block_dev[NAMELEN + 5] = {0}; dup = strdup(url); preg_replace(CMD_RCACHE_CREATE, "", dup, dup, strlen(dup)); int last = split(dup, split_arr, "/"); if (last != 2) { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_MALFORMED); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_MALFORMED), DAEMON); json_status_return(INVALID_VALUE, ERR_MALFORMED, &json_str, TRUE); } else { token = split_arr[last - 2]; if (!token) { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_INVALIDURL); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_INVALIDURL), DAEMON); json_status_return(INVALID_VALUE, NULL, &json_str, TRUE); } else { sprintf(device, "%s", token); token = split_arr[last - 1]; /* time to get the size */ if (!token) { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_INVALIDURL); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_INVALIDURL), DAEMON); json_status_return(INVALID_VALUE, ERR_INVALIDURL, &json_str, TRUE); } else { sprintf(source, "%s", token); token = split_arr[last]; /* time to get the size */ if (!token) { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_INVALIDURL); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_INVALIDURL), DAEMON); json_status_return(INVALID_VALUE, NULL, &json_str, TRUE); } else { if (strcmp(token, "write-through") == SUCCESS) { cache_mode = WRITETHROUGH; } else if (strcmp(token, "write-around") == SUCCESS) { cache_mode = WRITEAROUND; } else if (strcmp(token, "write-back") == SUCCESS) { cache_mode = WRITEBACK; } else { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_INVALID_MODE); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_INVALID_MODE), DAEMON); json_status_return(INVALID_VALUE, ERR_INVALID_MODE, &json_str, TRUE); } if (cache_mode >= SUCCESS) { disk = search_rdsk_targets(error_message); if ((disk == NULL) && (strlen(error_message) != 0)) { syslog(LOG_ERR | LOG_DAEMON, "%s", error_message); if (args->verbose) fprintf(stderr, verbose_msg(msg, error_message), DAEMON); json_status_return(INVALID_VALUE, error_message, &json_str, TRUE); } else { cache = search_cache_targets(error_message); if (cache == NULL && strlen(error_message) != 0) { syslog(LOG_ERR | LOG_DAEMON, "%s", error_message); if (args->verbose) fprintf(stderr, verbose_msg(msg, error_message), DAEMON); json_status_return(INVALID_VALUE, error_message, &json_str, TRUE); free_linked_lists(NULL, disk, NULL); disk = NULL; } else { sprintf(block_dev, "/dev/%s", source); rc = cache_device_map(disk, cache, device, block_dev, cache_mode, error_message); int pri = LOG_INFO; if (rc < SUCCESS) pri = LOG_ERR; syslog(pri | LOG_DAEMON, "%s", error_message); if (args->verbose) fprintf(stderr, verbose_msg(msg, error_message), DAEMON); json_status_return(rc, error_message, &json_str, TRUE); free_linked_lists(cache, disk, NULL); cache = NULL; disk = NULL; } } } } } } } } else if (strncmp(url, CMD_RCACHE_REMOVE, sizeof(CMD_RCACHE_REMOVE) - 1) == SUCCESS) { if (args->verbose) fprintf(stderr, verbose_msg(msg, D_RECV_REQ), DAEMON, CMD_RCACHE_REMOVE); dup = strdup(url); preg_replace(CMD_RCACHE_REMOVE, "", dup, dup, strlen(dup)); int last = split(dup, split_arr, "/"); if (last != 0) { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_MALFORMED); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_MALFORMED), DAEMON); json_status_return(INVALID_VALUE, ERR_MALFORMED, &json_str, TRUE); } else { token = split_arr[last]; if (!token) { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_INVALIDURL); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_INVALIDURL), DAEMON); json_status_return(INVALID_VALUE, ERR_INVALIDURL, &json_str, TRUE); } else { cache = search_cache_targets(error_message); if (cache == NULL && strlen(error_message) != 0) { syslog(LOG_ERR|LOG_DAEMON, "%s", error_message); if (args->verbose) fprintf(stderr, verbose_msg(msg, error_message), DAEMON); json_status_return(INVALID_VALUE, error_message, &json_str, TRUE); } else { rc = cache_device_unmap(cache, token, error_message); int pri = LOG_INFO; if (rc < SUCCESS) pri = LOG_ERR; syslog(pri|LOG_DAEMON, "%s", error_message); if (args->verbose) fprintf(stderr, verbose_msg(msg, error_message), DAEMON); json_status_return(rc, error_message, &json_str, TRUE); free_linked_lists(cache, NULL, NULL); cache = NULL; } } } } else if (strncmp(url, CMD_RDSK_LOCK, sizeof(CMD_RDSK_LOCK) - 1) == SUCCESS) { if (args->verbose) fprintf(stderr, verbose_msg(msg, D_RECV_REQ), DAEMON, CMD_RDSK_LOCK); dup = strdup(url); preg_replace(CMD_RDSK_LOCK, "", dup, dup, strlen(dup)); int last = split(dup, split_arr, "/"); if (last != 0) { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_MALFORMED); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_MALFORMED), DAEMON); json_status_return(INVALID_VALUE, ERR_MALFORMED, &json_str, TRUE); } else { token = split_arr[last]; if (!token) { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_INVALIDURL); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_INVALIDURL), DAEMON); json_status_return(INVALID_VALUE, ERR_INVALIDURL, &json_str, TRUE); } else { disk = search_rdsk_targets(error_message); if ((disk == NULL) && (strlen(error_message) != 0)) { syslog(LOG_ERR|LOG_DAEMON, "%s", error_message); if (args->verbose) fprintf(stderr, verbose_msg(msg, error_message), DAEMON); json_status_return(INVALID_VALUE, error_message, &json_str, TRUE); } else { rc = mem_device_lock(disk, token, TRUE, error_message); int pri = LOG_INFO; if (rc < SUCCESS) pri = LOG_ERR; syslog(pri|LOG_DAEMON, "%s", error_message); if (args->verbose) fprintf(stderr, verbose_msg(msg, error_message), DAEMON); json_status_return(rc, error_message, &json_str, TRUE); free_linked_lists(NULL, disk, NULL); disk = NULL; } } } } else if (strncmp(url, CMD_RDSK_UNLOCK, sizeof(CMD_RDSK_UNLOCK) - 1) == SUCCESS) { if (args->verbose) fprintf(stderr, verbose_msg(msg, D_RECV_REQ), DAEMON, CMD_RDSK_UNLOCK); dup = strdup(url); preg_replace(CMD_RDSK_UNLOCK, "", dup, dup, strlen(dup)); int last = split(dup, split_arr, "/"); if (last != 0) { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_MALFORMED); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_MALFORMED), DAEMON); json_status_return(INVALID_VALUE, ERR_MALFORMED, &json_str, TRUE); } else { token = split_arr[last]; if (!token) { status = MHD_HTTP_BAD_REQUEST; syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_INVALIDURL); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_INVALIDURL), DAEMON); json_status_return(INVALID_VALUE, NULL, &json_str, TRUE); } else { disk = search_rdsk_targets(error_message); if ((disk == NULL) && (strlen(error_message) != 0)) { syslog(LOG_ERR|LOG_DAEMON, "%s", error_message); if (args->verbose) fprintf(stderr, verbose_msg(msg, error_message), DAEMON); json_status_return(INVALID_VALUE, error_message, &json_str, TRUE); } else { rc = mem_device_lock(disk, token, FALSE, error_message); int pri = LOG_INFO; if (rc < SUCCESS) pri = LOG_ERR; syslog(pri|LOG_DAEMON, "%s", error_message); if (args->verbose) fprintf(stderr, verbose_msg(msg, error_message), DAEMON); json_status_return(rc, error_message, &json_str, TRUE); free_linked_lists(NULL, disk, NULL); disk = NULL; } } } } else { syslog(LOG_ERR|LOG_DAEMON, "%s", ERR_UNSUPPORTED); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_UNSUPPORTED), DAEMON); json_status_return(INVALID_VALUE, ERR_UNSUPPORTED, &json_str, TRUE); status = MHD_HTTP_BAD_REQUEST; } } else status = MHD_HTTP_BAD_REQUEST; if (json_str == NULL) { json_status_return(INVALID_VALUE, "", &json_str, TRUE); } response = MHD_create_response_from_buffer(strlen(json_str), (void *) json_str, MHD_RESPMEM_MUST_COPY); rc = MHD_queue_response (connection, status, response); MHD_destroy_response (response); if (dup) free(dup); if (json_str) free(json_str); if (error_message) free(error_message); dup = NULL; json_str = NULL; error_message = NULL; return rc; } /** * This function nullify the effects of a SIGPIPE signal (needed by libmicrohttpd: * https://www.gnu.org/software/libmicrohttpd/manual/html_node/microhttpd_002dintro.html#SIGPIPE ) */ static void catcher (int sig) { } /** * This function is called upon receiving a signal, it sets server_stop_requested so the main loop will exit */ static void signal_handler(int sig) { char msg[NAMELEN] = {0}; server_stop_requested = 1; fprintf(stderr, verbose_msg(msg, D_SIGNAL_RECEIVED), DAEMON, strsignal(sig)); syslog(LOG_INFO|LOG_DAEMON, D_SIGNAL_RECEIVED, strsignal(sig)); } /** * Installs the SIGPIPE signal catcher */ static void ignore_sigpipe (DAEMON_ARGS *args) { struct sigaction oldsig; struct sigaction sig; char msg[NAMELEN] = {0}; sig.sa_handler = &catcher; sigemptyset (&sig.sa_mask); sig.sa_flags = SA_RESTART; if (0 != sigaction (SIGPIPE, &sig, &oldsig)) { syslog(LOG_ERR|LOG_DAEMON, ERR_SIGPIPE_HANDLER, strerror(errno), __func__); if (args->verbose) fprintf (stderr, verbose_msg(msg, ERR_SIGPIPE_HANDLER), DAEMON, strerror(errno), __func__); } } /** * Catch all the appropriate signals to be able to exit in a clean way */ static void catch_exit_signals() { struct sigaction sig; sig.sa_handler = &signal_handler; sigemptyset (&sig.sa_mask); sig.sa_flags = SA_RESTART; sigaction(SIGHUP, &sig, NULL); sigaction(SIGUSR1, &sig, NULL); sigaction(SIGUSR2, &sig, NULL); sigaction(SIGALRM, &sig, NULL); sigaction(SIGINT, &sig, NULL); sigaction(SIGTERM, &sig, NULL); } /** * It creates a daemon that listens on the specified port and calls the function `answer_to_connection` when a connection * is received * * @param arg a pointer to the arguments passed to the thread function. * * @return The return value of the function is the exit status of the program. */ int mgmt_thread(void *arg) { struct DAEMON_ARGS *args = (struct DAEMON_ARGS *)arg; struct MHD_Daemon *mydaemon = NULL; char msg[NAMELEN] = {0}; ignore_sigpipe(args); catch_exit_signals(); /* * Now 'args' is passed to the callback function 'answer_to_connection' */ mydaemon = MHD_start_daemon(MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_AUTO, atoi(args->port), NULL, NULL, &answer_to_connection, args, MHD_OPTION_END); /* * The `daemon` var was replaced by `mydaemon` cause it clashed with linux/kernel headers var with the same name */ if (mydaemon == NULL) { syslog(LOG_INFO|LOG_DAEMON, ERR_NEW_MHD_DAEMON, strerror(errno), __func__); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_NEW_MHD_DAEMON), DAEMON, strerror(errno), __func__); return INVALID_VALUE; } while (!server_stop_requested) { /* On any signal, sleep is always interrupted and this allows to the signal handler function to be invoked */ sleep(60 * 8); } MHD_stop_daemon(mydaemon); syslog(LOG_INFO|LOG_DAEMON, D_LOOP_EXITING, __func__); if (args->verbose) fprintf(stderr, verbose_msg(msg, D_LOOP_EXITING), DAEMON, __func__); return SUCCESS; } rapiddisk-9.1.0/src/nvmet.c000066400000000000000000001422311442147535600155750ustar00rootroot00000000000000/** * @file nvmet.c * @brief NVME function definitions * @details This file defines some NVME related functions * @copyright @verbatim Copyright © 2011 - 2023 Petros Koutoupis All rights reserved. This file is part of RapidDisk. RapidDisk is free software: you can redistribute it and/or modify@n 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. RapidDisk 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 RapidDisk. If not, see . SPDX-License-Identifier: GPL-2.0-or-later @endverbatim * @author Petros Koutoupis \ * @author Matteo Tenca \ * @version 9.1.0 * @date 23 April 2023 */ #include "nvmet.h" #include "utils.h" #include "rdsk.h" #include "json.h" #include #include #include #include #include #define SYS_NVMET "/sys/kernel/config/nvmet" #define SYS_NVMET_TGT SYS_NVMET "/subsystems" #define SYS_NVMET_PORTS SYS_NVMET "/ports" #define SYS_NVMET_HOSTS SYS_NVMET "/hosts" #define SYS_CLASS_NVME "/sys/class/nvme" #define NQN_HDR_STR "nqn.2021-06.org.rapiddisk:" /* Followed by . */ #define SYS_CLASS_NET "/sys/class/net" #define NVME_PORT "4420" struct NVMET_PROFILE *nvmet_head = NULL; struct NVMET_PROFILE *nvmet_end = NULL; struct NVMET_PORTS *ports_head = NULL; struct NVMET_PORTS *ports_end = NULL; struct NVMET_ALLOWED_HOST *allowed_host_head = NULL; struct NVMET_ALLOWED_HOST *allowed_host_end = NULL; /* * description: Scan all NVMe Targets NQNs */ /** * It scans the NVMe Target subsystem for all the subsystems and namespaces and returns a linked list of the subsystems and * namespaces. * * @param return_message This is a pointer to a string that will be used to return error messages. * * @return A linked list of NVMET_PROFILE structs. */ struct NVMET_PROFILE *nvmet_scan_subsystem(char *return_message, int *rc) { int err2, n, i, ii, allowed_hosts, subsystems_number, rv = INVALID_VALUE; char file[NAMELEN * 2] = {0}; struct NVMET_PROFILE *nvmet = NULL; struct NVMET_ALLOWED_HOST *allowed_host = NULL; struct NVMET_PORTS *ports = NULL, *orig_ports = NULL, *new_ports = NULL; struct dirent **list, **sublist, **allowed_hosts_list; nvmet_head = NULL; nvmet_end = NULL; allowed_host_head = NULL; allowed_host_end = NULL; ports_head = NULL; ports_end = NULL; char *msg; if (access(SYS_NVMET, F_OK) != SUCCESS) { msg = "The NVMe Target subsystem is not loaded. Please load the nvmet and nvmet-tcp (or nvme-loop) kernel modules and ensure that the kernel user configuration filesystem is mounted."; print_error("%s", return_message, msg); return NULL; } ports = nvmet_scan_ports(return_message, &rv); if (rv == INVALID_VALUE) { return NULL; } orig_ports = ports; if ((subsystems_number = scandir(SYS_NVMET_TGT, &list, scandir_filter_no_dot, NULL)) < 0) { msg = "%s: scandir: %s"; print_error(msg, return_message, __func__, strerror(errno)); free_nvmet_linked_lists(orig_ports, NULL); return NULL; } for (n = 0; n < subsystems_number; n++) { sprintf(file, "%s/%s/namespaces/", SYS_NVMET_TGT, list[n]->d_name); if ((err2 = scandir(file, &sublist, scandir_filter_no_dot, NULL)) < 0) { msg = "%s: scandir: %s"; print_error(msg, return_message, __func__, strerror(errno)); list = clean_scandir(list, subsystems_number); free_nvmet_linked_lists(orig_ports, NULL); return NULL; } for (i = 0; i < err2; i++) { if ((nvmet = (struct NVMET_PROFILE *)calloc(1, sizeof(struct NVMET_PROFILE))) == NULL ) { msg = ERR_CALLOC; print_error(msg, return_message, __func__, strerror(errno)); list = clean_scandir(list, subsystems_number); sublist = clean_scandir(sublist, err2); free_nvmet_linked_lists(orig_ports, nvmet_head); return NULL; } strcpy(nvmet->nqn, (char *)list[n]->d_name); ports_head = NULL; while (ports != NULL) { if (strcmp(nvmet->nqn, ports->nqn) == 0) { if ((new_ports = (struct NVMET_PORTS *)calloc(1, sizeof(struct NVMET_PORTS))) == NULL ) { msg = ERR_CALLOC; print_error(msg, return_message, __func__, strerror(errno)); list = clean_scandir(list, subsystems_number); sublist = clean_scandir(sublist, err2); free_nvmet_linked_lists(orig_ports, NULL); free_nvmet_linked_lists(ports_head, nvmet_head); return NULL; } memcpy(new_ports, ports, sizeof(NVMET_PORTS)); new_ports->next = NULL; if (ports_head == NULL) ports_head = new_ports; else ports_end->next = new_ports; ports_end = new_ports; new_ports->next = NULL; } ports = ports->next; } nvmet->assigned_ports = ports_head; ports = orig_ports; sprintf(file, "%s/%s/allowed_hosts/", SYS_NVMET_TGT, list[n]->d_name); if ((allowed_hosts = scandir(file, &allowed_hosts_list, scandir_filter_no_dot, NULL)) < 0) { msg = "%s: scandir: %s"; print_error(msg, return_message, __func__, strerror(errno)); list = clean_scandir(list, subsystems_number); sublist = clean_scandir(sublist, err2); free_nvmet_linked_lists(orig_ports, nvmet_head); return NULL; } allowed_host_head = NULL; for (ii = 0; ii < allowed_hosts; ii++) { if ((allowed_host = (struct NVMET_ALLOWED_HOST *)calloc(1, sizeof(struct NVMET_ALLOWED_HOST))) == NULL ) { msg = ERR_CALLOC; print_error(msg, return_message, __func__, strerror(errno)); list = clean_scandir(list, subsystems_number); sublist = clean_scandir(sublist, err2); allowed_hosts_list = clean_scandir(allowed_hosts_list, allowed_hosts); free_nvmet_linked_lists(orig_ports, nvmet_head); return NULL; } sprintf(allowed_host->allowed_host, "%s", allowed_hosts_list[ii]->d_name); if (allowed_host_head == NULL) allowed_host_head = allowed_host; else allowed_host_end->next = allowed_host; allowed_host_end = allowed_host; allowed_host->next = NULL; } allowed_hosts_list = clean_scandir(allowed_hosts_list, allowed_hosts); nvmet->allowed_hosts = allowed_host_head; sprintf(file, "%s/%s/namespaces/%s", SYS_NVMET_TGT, list[n]->d_name, sublist[i]->d_name); if (access(file, F_OK) != INVALID_VALUE) { char *info_device = read_info(file, "device_path", return_message); if (info_device == NULL) { free(nvmet); nvmet = NULL; list = clean_scandir(list, subsystems_number); sublist = clean_scandir(sublist, err2); free_nvmet_linked_lists(orig_ports, nvmet_head); nvmet_head = NULL; return NULL; } sprintf(nvmet->device, "%s", info_device); info_device = read_info(file, "enable", return_message); if (info_device == NULL) { free(nvmet); nvmet = NULL; list = clean_scandir(list, subsystems_number); sublist = clean_scandir(sublist, err2); free_nvmet_linked_lists(orig_ports, nvmet_head); nvmet_head = NULL; return NULL; } nvmet->enabled = atoi(info_device); nvmet->namespc = atoi(sublist[i]->d_name); } if (nvmet_head == NULL) nvmet_head = nvmet; else nvmet_end->next = nvmet; nvmet_end = nvmet; nvmet->next = NULL; } sublist = clean_scandir(sublist, err2); } list = clean_scandir(list, subsystems_number); *rc = SUCCESS; free_nvmet_linked_lists(orig_ports, NULL); return nvmet_head; } /* * description: Scan all exported NVMe Targets ports. */ /** * It scans the NVMe Target subsystem for all ports and exports and returns a linked list of all ports and exports * * @param return_message This is a pointer to a string that will be populated with an error message if the function fails. * * @return A pointer to the head of a linked list of structs. */ struct NVMET_PORTS *nvmet_scan_ports(char *return_message, int *rc) { int err, err2, n = 0, i; char file[NAMELEN * 2] = {0}; struct NVMET_PORTS *nvmet_ports = NULL; struct dirent **ports, **exports; ports_head = NULL; ports_end = NULL; char *msg; if (access(SYS_NVMET, F_OK) != SUCCESS) { msg = "The NVMe Target subsystem is not loaded. Please load the nvmet and nvmet-tcp kernel modules and ensure that the kernel user configuration filesystem is mounted."; print_error("%s", return_message, msg); return NULL; } if ((err = scandir(SYS_NVMET_PORTS, &ports, scandir_filter_no_dot, NULL)) < 0) { msg = "%s: scandir: %s"; print_error(msg, return_message, __func__, strerror(errno)); return NULL; } for (; n < err; n++) { memset(file, 0x0, NAMELEN); sprintf(file, "%s/%s", SYS_NVMET_PORTS, ports[n]->d_name); if (access(file, F_OK) != INVALID_VALUE) { sprintf(file, "%s/%s/subsystems", SYS_NVMET_PORTS, ports[n]->d_name); if ((err2 = scandir(file, &exports, scandir_filter_no_dot, NULL)) < 0) { msg = "%s: scandir: %s"; print_error(msg, return_message, __func__, strerror(errno)); ports = clean_scandir(ports, err); return NULL; } for (i = 0; i < err2; i++) { if ((nvmet_ports = (struct NVMET_PORTS *)calloc(1, sizeof(struct NVMET_PORTS))) == NULL ) { msg = ERR_CALLOC; print_error(msg, return_message, __func__, strerror(errno)); ports = clean_scandir(ports, err); exports = clean_scandir(exports, err2); return NULL; } nvmet_ports->port = atoi(ports[n]->d_name); sprintf(file, "%s/%s", SYS_NVMET_PORTS, ports[n]->d_name); char *info = read_info(file, "addr_traddr", return_message); if (info == NULL) { free(nvmet_ports); nvmet_ports = NULL; ports = clean_scandir(ports, err); exports = clean_scandir(exports, err2); free_nvmet_linked_lists(ports_head, NULL); return NULL; } sprintf(nvmet_ports->addr, "%s", info); if (strlen(nvmet_ports->addr) < 1) sprintf(nvmet_ports->addr, "UNDEFINED"); info = read_info(file, "addr_trtype", return_message); if (info == NULL) { free(nvmet_ports); nvmet_ports = NULL; ports = clean_scandir(ports, err); exports = clean_scandir(exports, err2); free_nvmet_linked_lists(ports_head, NULL); return NULL; } sprintf(nvmet_ports->protocol, "%s", info); if (strlen(nvmet_ports->protocol) < 1) sprintf(nvmet_ports->protocol, "UNDEFINED"); sprintf(nvmet_ports->nqn, "%s", exports[i]->d_name); if (strlen(nvmet_ports->nqn) < 1) sprintf(nvmet_ports->nqn, "UNDEFINED"); if (ports_head == NULL) ports_head = nvmet_ports; else ports_end->next = nvmet_ports; ports_end = nvmet_ports; nvmet_ports->next = NULL; } exports = clean_scandir(exports, err2); } } ports = clean_scandir(ports, err); *rc = SUCCESS; return ports_head; } /* * description: Scan all NVMe Targets ports. */ /** * This function scans the /sys/kernel/config/nvmet/ports directory for all the ports that are currently configured * * @param return_message This is a pointer to a string that will be populated with an error message if the function fails. * @param rc return code * * @return A pointer to the first element of a linked list of struct NVMET_PORTS. */ struct NVMET_PORTS *nvmet_scan_all_ports(char *return_message, int *rc) { int err, n = 0; char file[NAMELEN * 2] = {0}; struct NVMET_PORTS *nvmet_ports; struct dirent **ports; ports_head = NULL; ports_end = NULL; char *msg; if (access(SYS_NVMET, F_OK) != SUCCESS) { msg = "The NVMe Target subsystem is not loaded. Please load the nvmet and nvmet-tcp kernel modules and ensure that the kernel user configuration filesystem is mounted."; print_error("%s", return_message, msg); return NULL; } if ((err = scandir(SYS_NVMET_PORTS, &ports, scandir_filter_no_dot, NULL)) < 0) { msg = "%s: scandir: %s"; print_error(msg, return_message, __func__, strerror(errno)); return NULL; } for (; n < err; n++) { memset(file, 0x0, NAMELEN); sprintf(file, "%s/%s", SYS_NVMET_PORTS, ports[n]->d_name); if (access(file, F_OK) != INVALID_VALUE) { if ((nvmet_ports = (struct NVMET_PORTS *)calloc(1, sizeof(struct NVMET_PORTS))) == NULL ) { msg = ERR_CALLOC; print_error(msg, return_message, __func__, strerror(errno)); ports = clean_scandir(ports, err); return NULL; } nvmet_ports->port = atoi(ports[n]->d_name); char *info = read_info(file, "addr_traddr", return_message); if (info == NULL) { free(nvmet_ports); nvmet_ports = NULL; ports = clean_scandir(ports, err); free_nvmet_linked_lists(ports_head, NULL); ports_head = NULL; return NULL; } sprintf(nvmet_ports->addr, "%s", info); if (strlen(nvmet_ports->addr) < 1) sprintf(nvmet_ports->addr, "UNDEFINED"); info = read_info(file, "addr_trtype", return_message); if (info == NULL) { free(nvmet_ports); nvmet_ports = NULL; ports = clean_scandir(ports, err); free_nvmet_linked_lists(ports_head, NULL); ports_head = NULL; return NULL; } sprintf(nvmet_ports->protocol, "%s", info); if (strlen(nvmet_ports->protocol) < 1) sprintf(nvmet_ports->protocol, "UNDEFINED"); if (ports_head == NULL) ports_head = nvmet_ports; else ports_end->next = nvmet_ports; ports_end = nvmet_ports; nvmet_ports->next = NULL; } } ports = clean_scandir(ports, err); *rc = SUCCESS; return ports_head; } /** * If the string is not a number, return INVALID_VALUE, otherwise return SUCCESS. * * @param str The string to validate. * * @return the value of the variable "number_validate". */ int number_validate(char *str) { while (*str) { if (!isdigit(*str)) return INVALID_VALUE; str++; } return SUCCESS; } /** * It takes a string as input and returns SUCCESS if the string is a valid IP address, else it returns INVALID_VALUE * * @param ip The IP address to validate. * * @return the value of the variable "dots". */ int ip_validate(char *ip) { int num, dots = 0; char *ptr, *ip_dup; ip_dup = strdup(ip); if (ip_dup == NULL) return INVALID_VALUE; ptr = strtok(ip_dup, "."); if (ptr == NULL) return INVALID_VALUE; while (ptr) { if (number_validate(ptr) != SUCCESS) return INVALID_VALUE; num = atoi(ptr); if (num >= 0 && num <= 255) { ptr = strtok(NULL, "."); if (ptr != NULL) dots++; } else return INVALID_VALUE; } if (dots != 3) return INVALID_VALUE; if (ip_dup) free(ip_dup); return SUCCESS; } /** * It gets the IP address of a given interface * * @param interface the interface name (ex: eth0 or eno1) * @param return_message a pointer to a string that will be filled with the error message if the function fails. * * @return The IP address of the interface. */ char *nvmet_interface_ip_get(char *interface, char *return_message) { int fd; struct ifreq ifr; static char ip[0xF] = {0}; char path[NAMELEN] = {0}; char *msg; struct in_addr addr; sprintf(path, "%s/%s", SYS_CLASS_NET, interface); if (access(path, F_OK) != SUCCESS) { msg = "%s: access: %s"; print_error(msg, return_message, __func__, strerror(errno)); return NULL; } if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < SUCCESS) { msg = "%s: open: %s"; print_error(msg, return_message, __func__, strerror(errno)); return NULL; } /* I want to get an IPv4 IP address */ ifr.ifr_addr.sa_family = AF_INET; /* I want the IP address attached to the interface (ex: eth0 or eno1) */ strncpy(ifr.ifr_name, interface, IFNAMSIZ-1); if (ioctl(fd, SIOCGIFADDR, &ifr) == INVALID_VALUE) { msg = "%s: ioctl: %s"; print_error(msg, return_message, __func__, strerror(errno)); close(fd); return NULL; } close(fd); /* Some more checks to addr.s_addr may be added here */ addr = ( (struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; sprintf(ip, "%s", inet_ntoa(addr)); return ip; } #ifndef SERVER /* * description: List NVMe Target exports / ports. */ /** * This function scans the NVMe subsystems and ports and prints the results to the screen * * @param json_flag This is a boolean value that indicates whether the output should be in JSON format or not. * @param error_message This is a pointer to a string that will be populated with an error message if the function fails. */ int nvmet_view_exports(bool json_flag, char *error_message) { int i = 1; int rc = SUCCESS, rv = INVALID_VALUE; struct NVMET_PROFILE *nvmet, *nvmet_orig; struct NVMET_PORTS *ports; struct NVMET_ALLOWED_HOST *hosts; nvmet = nvmet_scan_subsystem(error_message, &rv); if (rv == INVALID_VALUE) { return INVALID_VALUE; } nvmet_orig = nvmet; if (json_flag == TRUE) { rc = json_nvmet_view_exports(nvmet, NULL, FALSE); free_nvmet_linked_lists(NULL, nvmet_orig); return rc; } printf("NVMe Target Exports\n\n"); if (nvmet == NULL) { printf("\tNone.\n\n"); } else { while (nvmet != NULL) { printf("\t%d: NQN: %s \tNamespace: %d\tDevice: %s \tEnabled: %s\n", i, nvmet->nqn, nvmet->namespc, nvmet->device, ((nvmet->enabled == 0) ? "False" : "True")); hosts = nvmet->allowed_hosts; while (hosts != NULL) { printf("\t\tAllowed host: %s\n", hosts->allowed_host); hosts = hosts->next; } ports = nvmet->assigned_ports; while (ports != NULL) { printf("\t\tLinked port: %d\tIP address: %s (%s)\n", ports->port, ports->addr, ports->protocol); ports = ports->next; } i++; nvmet = nvmet->next; } } free_nvmet_linked_lists(NULL, nvmet_orig); return SUCCESS; } /* * description: Export an NVMe Target. */ /** * This function exports a block device to the NVMe-oF subsystem * * @param rd_prof A pointer to the RapidDisk profile linked list. * @param rc_prof This is a pointer to the RapidCache profile linked list. * @param device The device name of the volume to export. * @param host The hostname of the client that will be accessing the volume. If this is set to an empty string, then the * volume will be accessible by all hosts. * @param port The port number to export the volume to. If this is set to INVALID_VALUE, then the volume will be exported * to all ports. * @param return_message This is a pointer to a character array that will be populated with the return message. * * @return The return value is the return code of the function. */ int nvmet_export_volume(struct RD_PROFILE *rd_prof, RC_PROFILE *rc_prof, char *device, char *host, int port, char *return_message) { int rc = INVALID_VALUE, n, err, rv = INVALID_VALUE; FILE *fp; mode_t mode = (S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); char hostname[0x40] = {0x0}, path[NAMELEN] = {0x0}, link[NAMELEN] = {0x0}; struct dirent **list; char *msg; struct NVMET_PORTS *ports; struct NVMET_PORTS *orig_ports; /* * We do not care if the device has already been exported. We will continue to go through * the motions to add more host NQNs to export the target to. */ /* Check to see if device exists */ /* Looks for rd* ramdisk */ while (rd_prof != NULL) { if (strcmp(device, rd_prof->device) == SUCCESS) { rc = SUCCESS; break; } rd_prof = rd_prof->next; } /* Looks for rc-* mappings */ while (rc_prof != NULL) { if (strcmp(device, rc_prof->device) == SUCCESS) { rc = SUCCESS; break; } rc_prof = rc_prof->next; } if (rc != SUCCESS) { print_error(ERR_DEV_NOEXIST, return_message, device); return rc; } /* Reset rc to INVALID_VALUE after device check */ rc = INVALID_VALUE; if (port != INVALID_VALUE) { ports = nvmet_scan_all_ports(return_message, &rv); if (rv == INVALID_VALUE) { return rc; } orig_ports = ports; while (ports != NULL) { if (port == ports->port) { rc = SUCCESS; break; } ports = ports->next; } if (rc != SUCCESS) { print_error(ERR_PORT_NOEXIST, return_message, port); free_nvmet_linked_lists(orig_ports, NULL); return rc; } /* Reset rc to INVALID_VALUE after port check */ rc = INVALID_VALUE; free_nvmet_linked_lists(orig_ports, NULL); } /* Grab hostname for NQN */ if (gethostname(hostname, sizeof(hostname)) < 0) { msg = "Error. Unable to obtain hostname. %s: gethostname: %s"; print_error(msg, return_message, __func__, strerror(errno)); return rc; } /* Example of this path: * /sys/kernel/config/nvmet/subsystems/nqn.2021-06.org.rapiddisk:ubuserver-rd0 */ sprintf(path, "%s/%s%s-%s", SYS_NVMET_TGT, NQN_HDR_STR, hostname, device); if (access(path, F_OK) != SUCCESS) { if (mkdir(path, mode) != SUCCESS) { msg = "Error. Unable to create target directory %s. %s: mkdir: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return rc; } } /* Set host NQNs to access target */ /* If host is empty, it means we can allow all remotes to connect */ if (strlen(host) == 0) { /* Example of this path: * /sys/kernel/config/nvmet/subsystems/nqn.2021-06.org.rapiddisk:ubuserver-rd0/allowed_hosts */ sprintf(path, "%s/%s%s-%s/allowed_hosts", SYS_NVMET_TGT, NQN_HDR_STR, hostname, device); if ((err = scandir(path, &list, scandir_filter_no_dot, NULL)) < 0) { msg = "Error. Unable to access %s. %s: scandir: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return INVALID_VALUE; } list = clean_scandir(list, err); /* If err > 0 we have already specified some remote hosts which are the only ones * allowed to connect, so it is pointless to go further since we cannot enable * the unrestricted access mode */ if (err > 0) { msg = "You did not specified any allowed host, but one or more already exist. Please remove existing host(s) or specify a new one."; print_error("%s", return_message, msg); return INVALID_VALUE; } /* Example of this path: * /sys/kernel/config/nvmet/subsystems/nqn.2021-06.org.rapiddisk:ubuserver-rd0/attr_allow_any_host */ sprintf(path, "%s/%s%s-%s/attr_allow_any_host", SYS_NVMET_TGT, NQN_HDR_STR, hostname, device); if ((fp = fopen(path, "w")) == NULL){ msg = "Error. Unable to open %s. %s: fopen: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return INVALID_VALUE; } fprintf(fp, "1"); fclose(fp); } else { /* Configure the target to be seen only by the specified host(s) */ /* Make sure that no other hosts can access the target * * Example of this path: * /sys/kernel/config/nvmet/subsystems/nqn.2021-06.org.rapiddisk:ubuserver-rd0/attr_allow_any_host */ sprintf(path, "%s/%s%s-%s/attr_allow_any_host", SYS_NVMET_TGT, NQN_HDR_STR, hostname, device); if ((fp = fopen(path, "w")) == NULL){ msg = "Error. Unable to open %s. %s: fopen: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return INVALID_VALUE; } fprintf(fp, "0"); fclose(fp); /* Example of this path: * /sys/kernel/config/nvmet/hosts/ */ sprintf(path, "%s/%s", SYS_NVMET_HOSTS, host); if (access(path, F_OK) != SUCCESS) { if (mkdir(path, mode) != SUCCESS) { msg = "Error. Unable to create host directory %s. %s: mkdir: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return rc; } } /* Example of this path: * /sys/kernel/config/nvmet/subsystems/nqn.2021-06.org.rapiddisk:ubuserver-rd0/allowed_hosts/ */ sprintf(link, "%s/%s%s-%s/allowed_hosts/%s", SYS_NVMET_TGT, NQN_HDR_STR, hostname, device, host); if (access(link, F_OK) != SUCCESS) { if (symlink(path, link) != SUCCESS) { msg = "Error. Unable to link host to target. %s: symlink: %s"; print_error(msg, return_message, __func__, strerror(errno)); return rc; } } else goto nvme_export_enable_volume; } /* Set model * * Example of this path: * /sys/kernel/config/nvmet/subsystems/nqn.2021-06.org.rapiddisk:ubuserver-rd0/attr_model */ sprintf(path, "%s/%s%s-%s/attr_model", SYS_NVMET_TGT, NQN_HDR_STR, hostname, device); if (access(path, F_OK) == SUCCESS) { if ((fp = fopen(path, "w")) == NULL){ msg = "Error. Unable to set model string to file %s. %s: fopen: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return rc; } fprintf(fp, "RapidDisk"); fclose(fp); } /* Create namespace * * Example of this path: * /sys/kernel/config/nvmet/subsystems/nqn.2021-06.org.rapiddisk:ubuserver-rd0/namespaces/1 */ sprintf(path, "%s/%s%s-%s/namespaces/1", SYS_NVMET_TGT, NQN_HDR_STR, hostname, device); if (access(path, F_OK) != SUCCESS) { if (mkdir(path, mode) != SUCCESS) { msg = "Error. Unable to create namespace directory %s. %s: mkdir: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return rc; } } /* Set device * * Example of this path: * /sys/kernel/config/nvmet/subsystems/nqn.2021-06.org.rapiddisk:ubuserver-rd0/namespaces/1/device_path */ sprintf(path, "%s/%s%s-%s/namespaces/1/device_path", SYS_NVMET_TGT, NQN_HDR_STR, hostname, device); if ((fp = fopen(path, "w")) == NULL){ msg = "Error. Unable to open device_path file: %s. %s: fopen: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return rc; } if (strncmp(device, "rd", 2) == SUCCESS) fprintf(fp, "/dev/%s", device); else fprintf(fp, "/dev/mapper/%s", device); fclose(fp); nvme_export_enable_volume: /* Enable volume * * Example of this path: * /sys/kernel/config/nvmet/subsystems/nqn.2021-06.org.rapiddisk:ubuserver-rd0/namespaces/1/enable */ sprintf(path, "%s/%s%s-%s/namespaces/1/enable", SYS_NVMET_TGT, NQN_HDR_STR, hostname, device); if ((fp = fopen(path, "w")) == NULL){ msg = "Error. Unable to open namespace enable file %s. %s: fopen: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return rc; } fprintf(fp, "1"); fclose(fp); /* Set to a port */ if (port != INVALID_VALUE) { /* Example of this path: * /sys/kernel/config/nvmet/subsystems/nqn.2021-06.org.rapiddisk:ubuserver-rd0 */ sprintf(path, "%s/%s%s-%s", SYS_NVMET_TGT, NQN_HDR_STR, hostname, device); /* Example of this path: * /sys/kernel/config/nvmet/ports//subsystems/nqn.2021-06.org.rapiddisk:ubuserver-rd0 */ sprintf(link, "%s/%d/subsystems/%s%s-%s", SYS_NVMET_PORTS, port, NQN_HDR_STR, hostname, device); if (access(link, F_OK) != SUCCESS) { if (symlink(path, link) != SUCCESS) { msg = "Error. Unable to create link of NQN to port. %s: symlink: %s"; print_error(msg, return_message, __func__, strerror(errno)); return rc; } } } else { /* Iterate through all ports and enable target on each */ if ((err = scandir(SYS_NVMET_PORTS, &list, scandir_filter_no_dot, NULL)) < 0) { msg = "%s: scandir: %s"; print_error(msg, return_message, __func__, strerror(errno)); return rc; } for (n = 0; n < err; n++) { /* Example of this path: * /sys/kernel/config/nvmet/subsystems/nqn.2021-06.org.rapiddisk:ubuserver-rd0 */ sprintf(path, "%s/%s%s-%s", SYS_NVMET_TGT, NQN_HDR_STR, hostname, device); /* Example of this path: * /sys/kernel/config/nvmet/ports//subsystems/nqn.2021-06.org.rapiddisk:ubuserver-rd0 */ sprintf(link, "%s/%s/subsystems/%s%s-%s", SYS_NVMET_PORTS, list[n]->d_name, NQN_HDR_STR, hostname, device); if (access(link, F_OK) != SUCCESS) { if (symlink(path, link) != SUCCESS) { msg = "Error. Unable to create link of NQN to port. %s: symlink: %s"; print_error(msg, return_message, __func__, strerror(errno)); list = clean_scandir(list, err); return rc; } } } list = clean_scandir(list, err); } sprintf(path, "port %d", port); msg = "Block device %s has been mapped to %s through %s as %s%s-%s."; print_error(msg, return_message, device, ((strlen(host) == 0) ? "all hosts" : (char *)host), ((port == INVALID_VALUE) ? "all ports" : (char *)path), NQN_HDR_STR, hostname, device); return SUCCESS; } /** * It writes a 1 to the revalidate_size attribute of the namespace * * @param rd_prof A pointer to the RapidDisk profile list. * @param rc_prof This is the RC_PROFILE structure that contains the RapidCache device information. * @param device The device name. * @param return_message This is a pointer to a buffer that will contain the return message. * * @return The return code is the return code from the function. */ int nvmet_revalidate_size(struct RD_PROFILE *rd_prof, RC_PROFILE *rc_prof, char *device, char *return_message) { int rc = INVALID_VALUE; FILE *fp; char hostname[0x40] = {0x0}, path[NAMELEN] = {0x0}; char *msg; /* Check to see if device exists */ while (rd_prof != NULL) { if (strcmp(device, rd_prof->device) == SUCCESS) { rc = SUCCESS; break; } rd_prof = rd_prof->next; } while (rc_prof != NULL) { if (strcmp(device, rc_prof->device) == SUCCESS) { rc = SUCCESS; break; } rc_prof = rc_prof->next; } if (rc != SUCCESS) { print_error(ERR_DEV_NOEXIST, return_message, device); return INVALID_VALUE; } else rc = INVALID_VALUE; gethostname(hostname, sizeof(hostname)); sprintf(path, "%s/%s%s-%s", SYS_NVMET_TGT, NQN_HDR_STR, hostname, device); if (access(path, F_OK) != SUCCESS) { sprintf(path, "%s%s-%s", NQN_HDR_STR, hostname, device); msg = "Error. NQN export: %s does not exist."; print_error(msg, return_message, path); return rc; } /* Check if namespace 1 exists. That is the only namespace we define. */ sprintf(path, "%s/%s%s-%s/namespaces/1", SYS_NVMET_TGT, NQN_HDR_STR, hostname, device); if (access(path, F_OK) != SUCCESS) { msg = "%s: A RapidDisk defined namespace does not exist."; print_error(msg, return_message, __func__); return rc; } /* Check if the revalidate_size attribute exists. */ sprintf(path, "%s/%s%s-%s/namespaces/1/revalidate_size", SYS_NVMET_TGT, NQN_HDR_STR, hostname, device); if (access(path, F_OK) != SUCCESS) { msg = "%s: The kernel nvmet module version utilized does not support this function."; print_error(msg, return_message, __func__); return rc; } if ((fp = fopen(path, "w")) == NULL){ msg = "Error. Unable to open %s. %s: fopen: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return rc; } fprintf(fp, "1"); fclose(fp); msg = "NVMe Target Namespace size for %s revalidated."; print_error(msg, return_message, device); return SUCCESS; } /* * description: Unexport an NVMe Target. */ /** * It removes a block device from the NVMe Target subsystem * * @param device The block device to be exported. * @param host The hostname of the NVMe Target host. * @param port The port number to export the volume to. * @param return_message If you want to return a message to the caller, pass a pointer to a char array. * * @return The return value is the return code from the function. */ int nvmet_unexport_volume(char *device, char *host, int port, char *return_message) { int rc = INVALID_VALUE, n, allowed_host_number, rv = INVALID_VALUE, port_number; int nqn_ok = 0, host_ok = 0, port_ok = 0; FILE *fp; char hostname[0x40] = {0x0}, path[NAMELEN] = {0x0}, nqn[NAMELEN] = {0x0}; mode_t mode = (S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); struct dirent **list; char *msg; char target_string[NAMELEN] = {0}; char target_string_final[NAMELEN] = {0}; char regexp[NAMELEN] = {0}; struct NVMET_PROFILE *profile; struct NVMET_PROFILE *orig_profile; struct NVMET_PORTS *ports; struct NVMET_ALLOWED_HOST *allowed_hosts; if (gethostname(hostname, sizeof(hostname)) < 0) { msg = "Error. Unable to obtain hostname. %s: gethostname: %s"; print_error(msg, return_message, __func__, strerror(errno)); return rc; } sprintf(nqn, "%s%s-%s", NQN_HDR_STR, hostname, device); profile = nvmet_scan_subsystem(return_message, &rv); if (rv == INVALID_VALUE) { return rc; } orig_profile = profile; /* * This loop gathers information about the user specified port, the NQN, and the user specified host. * At the end, we will know: * 1) if the NQN specified by the user exists or not * 2) if the host specified by the user is in the list of allowed hosts of the specified NQN * 3) if the host specified by the user is in the list of allowed hosts of another NQN * 4) if the port specfied by the user is linked to the NQN specified by the user * 5) if the port specified by the user is linked to another NQN * */ while (profile != NULL) { /* looks for the NQN in the profile */ if (strcmp(nqn, profile->nqn) == 0) { nqn_ok = TRUE; } if (port != INVALID_VALUE) { ports = profile->assigned_ports; while (ports != NULL) { if ((port == ports->port) && (strcmp(nqn, ports->nqn) == 0)) { /* In the specified port the specified NQN esists */ port_ok = port_ok | 1 << 0; } else if ((port == ports->port) && (strcmp(nqn, ports->nqn) != 0)) { /* In the specified port another NQN esists */ port_ok = port_ok | 1 << 1; } else if ((port != ports->port) && (strcmp(nqn, ports->nqn) == 0)) { /* The NQN is mapped to other ports than the specified one */ port_ok = port_ok | 1 << 2; } ports = ports->next; } } if (strlen(host) > 0) { allowed_hosts = profile->allowed_hosts; while (allowed_hosts != NULL) { if (strcmp(allowed_hosts->allowed_host, host) == 0) { if (strcmp(nqn, profile->nqn) != 0) { /* the host is present in another NQN, in * /sys/kernel/config/nvmet/subsystems//allowed_hosts/ */ host_ok = host_ok | 1 << 0; } else { /* the host is present in the user-specified NQN, in * /sys/kernel/config/nvmet/subsystems//allowed_hosts/ */ host_ok = host_ok | 1 << 1; } } allowed_hosts = allowed_hosts->next; } } profile = profile->next; } free_nvmet_linked_lists(NULL, orig_profile); if (! nqn_ok) { print_error("NQN %s does not exists!", return_message, nqn); return rc; } if ((port != INVALID_VALUE) && (! (port_ok & (1<<0)))) { print_error("NQN %s on port %d does not exists!", return_message, nqn, port); return rc; } if ((strlen(host) > 0) && (! (host_ok & (1<<1)))) { print_error("Error. Host name '%s' is not in the list of allowed hosts for this NQN: '%s'", return_message, host, nqn); return rc; } /* Example of this path: * /sys/kernel/config/nvmet/subsystems/nqn.2021-06.org.rapiddisk:ubuserver-rd0/allowed_hosts */ sprintf(path, "%s/%s/allowed_hosts", SYS_NVMET_TGT, nqn); if ((allowed_host_number = scandir(path, &list, scandir_filter_no_dot, NULL)) < 0) { msg = "Error. Unable to access %s. %s: scandir: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return rc; } if (allowed_host_number > 0) { for (n = 0; n < allowed_host_number; n++) { strcat(target_string, list[n]->d_name); strcat(target_string, " "); } size_t size = strlen(target_string); size = size - 1; target_string[size] = '\0'; } list = clean_scandir(list, allowed_host_number); if (allowed_host_number > 0 && strlen(host) <= 0) { msg = "Error. No host provided, but one or more target(s) is/are still mapped to NQN(s): %s. Please remove them first."; print_error(msg, return_message, target_string); return rc; } else if (allowed_host_number > 0 && strlen(host) > 0) { if (! (host_ok & (1<<1))) { print_error("Error. Host name '%s' is not in the list of allowed hosts for this NQN: '%s'", return_message, host, nqn); return INVALID_VALUE; } /* Example of this path: * /sys/kernel/config/nvmet/subsystems/nqn.2021-06.org.rapiddisk:ubuserver-rd0/allowed_hosts/ */ sprintf(path, "%s/%s/allowed_hosts/%s", SYS_NVMET_TGT, nqn, host); if (access(path, F_OK) == SUCCESS && (host_ok & (1<<1))) { if (unlink(path) != SUCCESS) { msg = "Error. Unable to remove link %s. %s: unlink: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return rc; } allowed_host_number--; } /* Example of this path: * /sys/kernel/config/nvmet/hosts/ * * The "global" directory is removed ONLY if no other NQNs are using it. */ sprintf(path, "%s/%s", SYS_NVMET_HOSTS, host); if (access(path, F_OK) == SUCCESS && ! (host_ok & (1<<0))) { if (rmdir(path) != SUCCESS) { msg = "Error. Unable to remove dir %s. %s: rmdir: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return rc; } } if (allowed_host_number > 0) { sprintf(regexp, "%s%s%s", "\\s?", host, "\\s?"); preg_replace(regexp, "", target_string, target_string_final, sizeof(target_string_final)); msg = "Warning. One or more target(s) is/are still mapped to NQN(s): %s. Please remove them first."; print_error(msg, return_message, target_string_final); return SUCCESS; } } if (allowed_host_number == 0) { if (port != INVALID_VALUE) { /* We remove the NQN from ONLY */ /* Example of this path: * /sys/kernel/config/nvmet/ports//subsystems/nqn.2021-06.org.rapiddisk:ubuserver-rd0 */ sprintf(path, "%s/%d/subsystems/%s", SYS_NVMET_PORTS, port, nqn); if (access(path, F_OK) == SUCCESS) { if (unlink(path) != SUCCESS) { msg = "Error. Unable to remove link of NQN from port %d. %s: unlink: %s"; print_error(msg, return_message, port, __func__, strerror(errno)); return rc; } } } else { /* We remove the NQN from ALL the s */ /* Example of this path: * /sys/kernel/config/nvmet/ports */ sprintf(path, "%s", SYS_NVMET_PORTS); if ((port_number = scandir(path, &list, scandir_filter_no_dot, NULL)) < 0) { msg = "Error. Unable to scan NVMe Target ports directory for %s. %s: scandir: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return rc; } for (n = 0; n < port_number; n++) { /* Example of this path: * /sys/kernel/config/nvmet/ports/ */ sprintf(path, "%s/%s", SYS_NVMET_PORTS, list[n]->d_name); if (access(path, F_OK) == SUCCESS) { /* Example of this path: * /sys/kernel/config/nvmet/ports// */ sprintf(path, "%s/%s/subsystems/%s", SYS_NVMET_PORTS, list[n]->d_name, nqn); if (access(path, F_OK) == SUCCESS) { if (unlink(path) != SUCCESS) { msg = "Error. Unable to remove NQN %s. %s: rmdir: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); list = clean_scandir(list, port_number); return rc; } } } } list = clean_scandir(list, port_number); } if ((port_ok & (1<<0)) && (port_ok & (1<<2))) { /* The NQN is mapped to the user specified port AND some other ports, * so we should not remove it globally */ msg = "Warning. The NQN was removed from port %d, but is still exported on other ports."; print_error(msg, return_message, port); return SUCCESS; } sprintf(path, "%s/%s/attr_allow_any_host", SYS_NVMET_TGT, nqn); if ((fp = fopen(path, "w")) == NULL){ msg = "Error. Unable to open %s. %s: fopen: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return INVALID_VALUE; } fprintf(fp, "0"); fclose(fp); /* Disable volume */ sprintf(path, "%s/%s/namespaces/1/enable", SYS_NVMET_TGT, nqn); if ((fp = fopen(path, "w")) == NULL){ msg = "Error. Unable to open namespace enable file %s. %s: fopen: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return rc; } fprintf(fp, "0"); fclose(fp); /* Remove namespace */ sprintf(path, "%s/%s/namespaces/1", SYS_NVMET_TGT, nqn); if (access(path, F_OK) == SUCCESS) { if (rmdir(path) != SUCCESS) { msg = "Error. Unable to remove namespace %s. %s: rmdir: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return rc; } } /* Remove NQN from NVMeT Subsystem */ /* * The check for the rmdir() result and the operations carried on upon failure * are intended to prevent the situation described below: * * if the rmdir() call fails, it means we could not remove the NQN dir: * * "/sys/kernel/config/nvmet/subsystems/" * * from the NVMe subsystem. * * So, an additional test is performed to determine wheather the namespace directory: * * "/sys/kernel/config/nvmet/subsystems//namespaces/1" * * still exists or not. * If it was removed, the NVMe's kernel dir structure is corrupt, since the NQN's namespace has been * removed, but the NQN itself was not. * * Since the functions creating some linked lists, nvmet_scan_subsystem(), nvmet_scan_ports() and * nvmet_scan_all_ports(), immediatly check for the namespace to exist and return an error if it does not, * and since at least one of them is called when a NVMe operation is to be performed, any further rapiddisk * command intended to manipulate the NVMe subsystem will fail immediately. * * So, when the removal of: * * "/sys/kernel/config/nvmet/subsystems/" * * fails, we check if the dir: * * "/sys/kernel/config/nvmet/subsystems//namespaces/1" * * is still present. If it is not, we recreate it. * * By doing this operation, we ensure that any further invocation of rapiddisk which is aimed to handle * the NVMe subsystem, would not fail immediatly. * */ sprintf(path, "%s/%s", SYS_NVMET_TGT, nqn); if (access(path, F_OK) == SUCCESS) { if (rmdir(path) != SUCCESS) { msg = "Error. Unable to remove NQN from NVMe Target subsystem %s. %s: rmdir: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); sprintf(path, "%s/%s/namespaces/1", SYS_NVMET_TGT, nqn); if (access(path, F_OK) != SUCCESS) { if (mkdir(path, mode) != SUCCESS) { msg = "Error. Unable to restore namespace %s. %s: mkdir: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return rc; } } return rc; } } } msg = "Block device %s has been removed from the NVMe Target subsystem."; print_error(msg, return_message, device); return SUCCESS; } /* * description: List all enabled NVMe Target ports. */ /** * It scans the system for all NVMe ports and returns a linked list of all the ports * * @param json_flag If true, then the output will be in JSON format. * @param error_message This is a pointer to a string that will be populated with an error message if the function fails. * * @return The return value is the return code from the function. */ int nvmet_view_ports(bool json_flag, char *error_message) { int i = 1; int rc = SUCCESS, rv = INVALID_VALUE; struct NVMET_PORTS *ports = NULL, *tmp_ports = NULL; ports = nvmet_scan_all_ports(error_message, &rv); if (rv == INVALID_VALUE) { return INVALID_VALUE; } if (json_flag == TRUE) { rc = json_nvmet_view_ports(ports, NULL, FALSE); free_nvmet_linked_lists(ports, NULL); ports = NULL; return rc; } printf("Exported NVMe Ports\n\n"); if (ports == NULL) { printf("\tNone.\n\n"); return SUCCESS; } while (ports != NULL) { printf("\t%d: Port: %d - %s (%s)\n", i, ports->port, ports->addr, ports->protocol); i++; tmp_ports = ports; ports = ports->next; free(tmp_ports); } free_nvmet_linked_lists(ports, NULL); ports = NULL; return SUCCESS; } /** * It creates a new port. * * @param interface The interface to use for the port. * @param port The port number to create. * @param protocol 0 = TCP, 1 = RDMA, 2 = LOOP * @param return_message This is a pointer to a buffer that will be filled with the return message. * * @return The return value is the return code of the function. */ int nvmet_enable_port(char *interface, int port, int protocol, char *return_message) { int rc = INVALID_VALUE, rv = INVALID_VALUE; FILE *fp; char path[NAMELEN] = {0x0}, ip[0xF] = {0x0}, proto[0x5] = {0x0}; mode_t mode = (S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); struct NVMET_PORTS *ports, *tmp_ports; char *msg; char error_message[NAMELEN] = {0}; sprintf(path, "%s/%d", SYS_NVMET_PORTS, port); if (access(path, F_OK) == SUCCESS) { msg = "Error. NVMe Target Port %d already exists."; print_error(msg, return_message, port); return rc; } if (protocol != XFER_MODE_LOOP) { ports = nvmet_scan_ports(return_message, &rv); if (rv == INVALID_VALUE) { return INVALID_VALUE; } char *ipaddr = nvmet_interface_ip_get(interface, error_message); if (ipaddr == NULL) { msg = "Cannot find the IP address of interface %s. Error: %s"; print_error(msg, return_message, interface, error_message); free_nvmet_linked_lists(ports, NULL); return INVALID_VALUE; } sprintf(ip, "%s", ipaddr); if (ip_validate(ip) != SUCCESS) { msg = "Error. IP address %s is invalid."; print_error(msg, return_message, ip); free_nvmet_linked_lists(ports, NULL); return rc; } tmp_ports = ports; while (ports != NULL) { if (strcmp(ports->addr, ip) == SUCCESS) { msg = "Error. Interface %s with IP address %s is already in use on port %d."; print_error(msg, return_message, interface, ip, ports->port); free_nvmet_linked_lists(tmp_ports, NULL); return rc; } ports = ports->next; } free_nvmet_linked_lists(tmp_ports, NULL); } if ((rc = mkdir(path, mode)) != SUCCESS) { msg = "Error. Unable to create port directory. %s: mkdir: %s"; print_error(msg, return_message, __func__, strerror(errno)); return rc; } if (protocol == XFER_MODE_LOOP) { sprintf(proto, "loop"); sprintf(path, "%s/%d/addr_trtype", SYS_NVMET_PORTS, port); if ((fp = fopen(path, "w")) == NULL) { msg = "Error. Unable to open %s. %s: fopen: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return INVALID_VALUE; } fprintf(fp, "%s", proto); fclose(fp); msg = "Successfully created port %d set for %s."; print_error(msg, return_message, port, proto); return SUCCESS; } sprintf(path, "%s/%d/addr_trsvcid", SYS_NVMET_PORTS, port); if ((fp = fopen(path, "w")) == NULL) { msg = "Error. Unable to open %s. %s: fopen: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return INVALID_VALUE; } fprintf(fp, "4420"); fclose(fp); sprintf(path, "%s/%d/addr_adrfam", SYS_NVMET_PORTS, port); if ((fp = fopen(path, "w")) == NULL) { msg = "Error. Unable to open %s. %s: fopen: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return INVALID_VALUE; } fprintf(fp, "ipv4"); fclose(fp); if (protocol == XFER_MODE_RDMA) sprintf(proto, "rdma"); else sprintf(proto, "tcp"); sprintf(path, "%s/%d/addr_trtype", SYS_NVMET_PORTS, port); if ((fp = fopen(path, "w")) == NULL) { msg = "Error. Unable to open %s. %s: fopen: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return INVALID_VALUE; } fprintf(fp, "%s", proto); fclose(fp); sprintf(path, "%s/%d/addr_traddr", SYS_NVMET_PORTS, port); if ((fp = fopen(path, "w")) == NULL) { msg = "Error. Unable to open %s. %s: fopen: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return INVALID_VALUE; } fprintf(fp, "%s", ip); fclose(fp); msg = "Successfully created port %d set to %s for interface %s (with IP address %s)."; print_error(msg, return_message, port, proto, interface, ip); return SUCCESS; } /** * This function removes the specified NVMe Target port * * @param port The port number to be disabled. * @param return_message This is a pointer to a buffer that will be filled with a message * * @return The return code of the function. */ int nvmet_disable_port(int port, char *return_message) { int rc = INVALID_VALUE, err; char path[NAMELEN] = {0x0}; struct dirent **list; char *msg; sprintf(path, "%s/%d", SYS_NVMET_PORTS, port); if (access(path, F_OK) != SUCCESS) { msg = "Error. NVMe Target Port %d does not exist."; print_error(msg, return_message, port); return rc; } /* Make sure that no subsystems are mapped from this port. */ sprintf(path, "%s/%d/subsystems/", SYS_NVMET_PORTS, port); if ((err = scandir(path, &list, NULL, NULL)) < 0) { msg = "Error. Unable to access %s. %s: scandir: %s"; print_error(msg, return_message, path, __func__, strerror(errno)); return rc; } list = clean_scandir(list, err); if (err > 2) { msg = "%s"; print_error(msg, return_message, "This port is currently in use."); return rc; } /* Remove the port */ sprintf(path, "%s/%d", SYS_NVMET_PORTS, port); if ((rc = rmdir(path)) != SUCCESS) { msg = "Error. Unable to remove port. %s: rmdir: %s"; print_error(msg, return_message, __func__, strerror(errno)); return rc; } msg = "NVMe Target port %d has been removed."; print_error(msg, return_message, port); return SUCCESS; } #else /** * This function scans the system for NVMe-oF subsystems and ports, and then creates a JSON string that contains the * information * * @param error_message This is a pointer to a character array that will contain the error message if the function fails. * @param json_result This is the JSON string that will be returned to the caller. */ int nvmet_view_exports_json(char *error_message, char **json_result) { int rc = SUCCESS, rv = INVALID_VALUE; struct NVMET_PROFILE *nvmet = NULL; nvmet = nvmet_scan_subsystem(error_message, &rv); if (rv == INVALID_VALUE) { return INVALID_VALUE; } rc = json_nvmet_view_exports(nvmet, json_result, TRUE); free_nvmet_linked_lists(NULL, nvmet); return rc; } /** * This function scans the system for all NVMET ports and returns the results in JSON format * * @param error_message This is a pointer to a string that will be populated with an error message if the function fails. * @param json_result This is the JSON string that will be returned to the caller. */ int nvmet_view_ports_json(char *error_message, char **json_result) { int rc = SUCCESS, rv = INVALID_VALUE; struct NVMET_PORTS *ports = NULL; ports = nvmet_scan_all_ports(error_message, &rv); if (rv == INVALID_VALUE) { return INVALID_VALUE; } rc = json_nvmet_view_ports(ports, json_result, TRUE); free_nvmet_linked_lists(ports, NULL); ports = NULL; return rc; } #endif rapiddisk-9.1.0/src/nvmet.h000066400000000000000000000043241442147535600156020ustar00rootroot00000000000000/** * @file nvmet.h * @brief NVME function declarations * @details This header file declares some NVME related functions * @copyright @verbatim Copyright © 2011 - 2023 Petros Koutoupis All rights reserved. This file is part of RapidDisk. RapidDisk is free software: you can redistribute it and/or modify@n 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. RapidDisk 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 RapidDisk. If not, see . SPDX-License-Identifier: GPL-2.0-or-later @endverbatim * @author Petros Koutoupis \ * @author Matteo Tenca \ * @version 9.1.0 * @date 23 April 2023 */ #ifndef NVMET_H #define NVMET_H #include "common.h" #define XFER_MODE_TCP 0 #define XFER_MODE_RDMA 1 #define XFER_MODE_LOOP 2 struct NVMET_PROFILE *nvmet_scan_subsystem(char *return_message, int *rc); struct NVMET_PORTS *nvmet_scan_ports(char *return_message, int *rc); struct NVMET_PORTS *nvmet_scan_all_ports(char *return_message, int *rc); char *nvmet_interface_ip_get(char *interface, char *return_message); #ifndef SERVER int nvmet_view_exports(bool json_flag, char *error_message); int nvmet_view_ports(bool json_flag, char *error_message); int nvmet_enable_port(char *interface, int port, int protocol, char *return_message); int nvmet_disable_port(int port, char *return_message); int nvmet_export_volume(struct RD_PROFILE *rd_prof, RC_PROFILE *rc_prof, char *device, char *host, int port, char *return_message); int nvmet_revalidate_size(struct RD_PROFILE *rd_prof, RC_PROFILE *rc_prof, char *device, char *return_message); int nvmet_unexport_volume(char *device, char *host, int port, char *return_message); #else int nvmet_view_ports_json(char *error_message, char **json_result); int nvmet_view_exports_json(char *error_message, char **json_result); #endif #endif //NVMET_H rapiddisk-9.1.0/src/rapiddiskd.c000066400000000000000000000252051442147535600165630ustar00rootroot00000000000000/** * @copyright @verbatim Copyright © 2011 - 2023 Petros Koutoupis All rights reserved. This file is part of RapidDisk. RapidDisk is free software: you can redistribute it and/or modify@n 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. RapidDisk 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 RapidDisk. If not, see . SPDX-License-Identifier: GPL-2.0-or-later @endverbatim * @author Petros Koutoupis \ * @author Matteo Tenca \ * @version 9.1.0 * @date 23 April 2023 */ #define SERVER #include "rapiddiskd.h" #include "utils.h" /* * description: print the help menu. */ void online_menu(void) { printf("%s %s\n%s\n\n", DAEMON, VERSION_NUM, COPYRIGHT); printf("%s is a daemon intended to listen for API requests.\n\n" "Usage: %s [ -h | -v ] [ options ]\n\n", DAEMON, DAEMON); printf("Functions:\n" "\t-d\tRemain in the foreground (implies -V).\n" "\t-h\tPrint this exact help menu.\n" "\t-p\tChange port to listen on (default: 9118).\n" "\t-V\tEnable debug messages to stderr (this is ugly).\n" "\t-v\tPrint out version information.\n\n"); } /* * description: make sure that the daemon is not already running. */ int proc_find(void) { DIR *dir; struct dirent *list; char buf[NAMELEN * 2] = {0}, name[NAMELEN] = {0}, state; long pid; int count = 0, rc = SUCCESS; FILE *fp = NULL; if (!(dir = opendir("/proc"))) { syslog(LOG_ERR|LOG_DAEMON, "%s (%d): opendir: %s.", __func__, __LINE__, strerror(errno)); return INVALID_VALUE; } while ((list = readdir(dir)) != NULL) { pid = atol(list->d_name); if (pid < 0) continue; snprintf(buf, sizeof(buf), "/proc/%ld/stat", pid); fp = fopen(buf, "r"); if (fp) { if ((fscanf(fp, "%ld (%[^)]) %c", &pid, name, &state)) != 3 ) { syslog(LOG_ERR|LOG_DAEMON, "%s (%d): fscanf: %s.", __func__, __LINE__, strerror(errno)); fclose(fp); rc = INVALID_VALUE; goto proc_exit_on_failure; } fclose(fp); if (!strcmp(name, DAEMON)) count++; } } if (count > 1) rc = INVALID_VALUE; proc_exit_on_failure: closedir(dir); return rc; } int main(int argc, char *argv[]) { pid_t pid; int rc = SUCCESS, i; int stdin_f = -1, stdout_f = -1, stderr_f = -1; struct DAEMON_ARGS *args = NULL; bool verbose = FALSE, debug = FALSE; char port[6] = {0}; // max port is 65535 so 5 chars + 1 (NULL) char msg[NAMELEN] = {0}; #ifndef DEBUG if (getuid() != 0) { fprintf(stderr, "\nYou must be root or contain sudo permissions to initiate this\n\n"); return -EACCES; } #endif while ((i = getopt(argc, argv, "?dhp:vV")) != INVALID_VALUE) { switch (i) { case '?': case 'd': verbose = 1; debug = 1; break; case 'h': online_menu(); return SUCCESS; case 'p': /** * This checks prevents a buffer overflow, and checks if the provided string * can be converted to an int, and if the int is > 0 and < 65536. * This is needed because libmicrohttpd would choose a random port if the provided one * is 0 or > 65535. In case the port is in use, libmicrohttpd will return an (handled) error later. */ /** * This very first check with strlen() allows to discriminate between * strtol() errors due to conversion issues from the ones related to * the number provided being too big. */ if (strlen(optarg) >= 6) { fprintf(stderr, "The provided port number should be > 0 and <= 65535.\n"); return EXIT_FAILURE; } errno = 0; char *endptr = NULL; long port_int = strtol(optarg, &endptr, 10); if (errno != 0) { fprintf(stderr, "Error: %s\n", strerror(errno)); return EXIT_FAILURE; } if (endptr == optarg) { fprintf(stderr, "The provided port is not a number.\n"); return EXIT_FAILURE; } if ((port_int <= 0) || (port_int > 65535)) { fprintf(stderr, "The provided port number should be > 0 and <= 65535.\n"); return EXIT_FAILURE; } sprintf(port, "%s", optarg); break; case 'v': printf("%s %s\n%s\n\n", DAEMON, VERSION_NUM, COPYRIGHT); return SUCCESS; case 'V': verbose = 1; break; default: break; } } openlog(DAEMON, LOG_PID, LOG_DAEMON); if (check_loaded_modules() < SUCCESS) { if (verbose) { fprintf(stderr, verbose_msg(msg, ERR_MODULES), DAEMON, __func__); fprintf(stderr, verbose_msg(msg, D_EXITING), DAEMON); } syslog(LOG_ERR|LOG_DAEMON, ERR_MODULES, __func__); syslog(LOG_ERR|LOG_DAEMON, D_EXITING); return -EPERM; } /* Make sure that only a single instance is running */ if (proc_find() != SUCCESS) { if (verbose) { fprintf(stderr, verbose_msg(msg, ERR_ALREADY_RUNNING), DAEMON, __func__); fprintf(stderr, verbose_msg(msg, D_EXITING), DAEMON); } syslog(LOG_ERR|LOG_DAEMON, ERR_ALREADY_RUNNING, __func__); syslog(LOG_ERR|LOG_DAEMON, D_EXITING); return INVALID_VALUE; } /* * if debug is not set, daemonize and redirect STDIN/OUT/ERR to null or to a file if verbose is set */ if (!debug) { /* * Double fork to prevent any chance for the daemon to regain a terminal */ pid = fork(); if (pid > 0) { /** * This is the parent process */ if (verbose) fprintf(stderr, "%s: First Non-Daemon exiting.\n", DAEMON); closelog(); fflush(NULL); close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); _exit(EXIT_SUCCESS); } setsid(); /** Sets the first child PID as Session ID */ pid = fork(); if (pid > 0) { /** * This is the first child, which owns the Session */ if (verbose) fprintf(stderr, "%s: Second Non-Daemon exiting.\n", DAEMON); closelog(); fflush(NULL); close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); _exit(EXIT_SUCCESS); } /** * This is the second child, whose PID is different from its SID. * This means it can't regain access to a terminal once and for all. */ chdir("/"); umask(0); /** * Closing STDIN, STDOUT and STDERR */ if (close(STDIN_FILENO) < SUCCESS) { syslog(LOG_ERR | LOG_DAEMON, "%s: Close STDIN: %s", __func__, strerror(errno)); syslog(LOG_ERR | LOG_DAEMON, D_EXITING); if (verbose) { fprintf(stderr, "%s, %s: Close STDIN: %s\n", DAEMON, __func__, strerror(errno)); fprintf(stderr, "%s: Daemon exiting.\n", DAEMON); } return INVALID_VALUE; } if (close(STDOUT_FILENO) < SUCCESS) { syslog(LOG_ERR | LOG_DAEMON, "%s: Close STDOUT: %s", __func__, strerror(errno)); syslog(LOG_ERR | LOG_DAEMON, D_EXITING); if (verbose) { fprintf(stderr, "%s, %s: Close STDOUT: %s\n", DAEMON, __func__, strerror(errno)); fprintf(stderr, "%s: Daemon exiting.\n", DAEMON); } return INVALID_VALUE; } if (close(STDERR_FILENO) < SUCCESS) { syslog(LOG_ERR | LOG_DAEMON, "%s: Close STDERR: %s", __func__, strerror(errno)); syslog(LOG_ERR | LOG_DAEMON, D_EXITING); if (verbose) { fprintf(stderr, "%s, %s: Close STDERR: %s\n", DAEMON, __func__, strerror(errno)); fprintf(stderr, "%s: Daemon exiting.\n", DAEMON); } return INVALID_VALUE; } /** * Reopening STDIN, STDOUT and STDERR, pointing to "/dev/null" or to a file in verbose mode. * File names are arbitrary/temporary and can be changed. */ if ((stdin_f = open("/dev/null", O_RDONLY)) < SUCCESS) { syslog(LOG_ERR | LOG_DAEMON, "%s: open STDIN: %s", __func__, strerror(errno)); syslog(LOG_ERR | LOG_DAEMON, D_EXITING); /* We can't log to STDERR yet (for verbose mode) */ return INVALID_VALUE; } char *new_stdout = "/dev/null"; if (verbose) { new_stdout = D_STDOUT_LOG; } if ((stdout_f = open(new_stdout, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)) < SUCCESS) { syslog(LOG_ERR | LOG_DAEMON, "%s: Error opening STDOUT as %s: %s", __func__, new_stdout, strerror(errno)); syslog(LOG_ERR | LOG_DAEMON, D_EXITING); close(stdin_f); /* We can't log to STDERR yet (for verbose mode) */ return INVALID_VALUE; } char *new_stderr = "/dev/null"; if (verbose) { new_stderr = D_STDERR_LOG; } if ((stderr_f = open(new_stderr, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)) < SUCCESS) { syslog(LOG_ERR | LOG_DAEMON, "%s: Error opening STDERR as %s: %s", __func__, new_stderr, strerror(errno)); syslog(LOG_ERR | LOG_DAEMON, D_EXITING); /* We can't log to STDERR yet (for verbose mode) */ close(stdin_f); close(stdout_f); return INVALID_VALUE; } } pid = getpid(); FILE *fh = NULL; if ((fh = fopen(PID_FILE, "w")) != NULL) { if (fprintf(fh, "%d\n", pid) < 0) { syslog(LOG_ERR|LOG_DAEMON, "Impossible to write to pidfile /run/rapiddiskd.pid."); if (verbose) { fprintf(stderr, "%s: Impossible to write to pidfile /run/rapiddiskd.pid.\n", DAEMON); } } fclose(fh); } else { syslog(LOG_ERR|LOG_DAEMON, "Impossible to open pidfile /run/rapiddiskd.pid."); if (verbose) { fprintf(stderr, "%s: Impossible to open pidfile /run/rapiddiskd.pid.\n", DAEMON); } } /* Allocating the args structure, this was moved after the fork to avoid duplicating/freeing it for every fork() */ args = (struct DAEMON_ARGS *)calloc(1, sizeof(struct DAEMON_ARGS)); if (args == NULL) { syslog(LOG_ERR|LOG_DAEMON, ERR_CALLOC, __func__, strerror(errno)); syslog(LOG_ERR|LOG_DAEMON, D_EXITING); if (verbose) { fprintf(stderr, verbose_msg(msg, ERR_CALLOC), DAEMON, __func__, strerror(errno)); fprintf(stderr, verbose_msg(msg, D_EXITING), DAEMON); } return -ENOMEM; } /** Put some config values into args */ args->verbose = verbose; if (strlen(port) == 0) { sprintf(args->port, "%s", DEFAULT_MGMT_PORT); } else { sprintf(args->port, "%s", port); } syslog(LOG_INFO|LOG_DAEMON, D_STARTING); if (args->verbose) fprintf(stderr, verbose_msg(msg, D_STARTING), DAEMON, D_STARTING); rc = mgmt_thread((void *)args); if (fflush(NULL) == EOF) { syslog(LOG_ERR|LOG_DAEMON, ERR_FLUSHING, strerror(errno), __func__); if (args->verbose) fprintf(stderr, verbose_msg(msg, ERR_FLUSHING), DAEMON, strerror(errno), __func__); rc = INVALID_VALUE; } syslog(LOG_INFO|LOG_DAEMON, D_EXITING); if (args->verbose) fprintf(stderr, verbose_msg(msg, D_EXITING), DAEMON); free(args); args = NULL; if (stdin_f == 0) close(stdin_f); if (stdout_f > 0) close(stdout_f); if (stderr_f > 0) close(stderr_f); closelog(); if (access(PID_FILE, F_OK) == 0) { unlink(PID_FILE); } exit(rc); } rapiddisk-9.1.0/src/rapiddiskd.h000066400000000000000000000043351442147535600165710ustar00rootroot00000000000000/** * @file * @brief Daemon defines * @details This header file defines some constants used by the daemon * @copyright @verbatim Copyright © 2011 - 2023 Petros Koutoupis All rights reserved. This file is part of RapidDisk. RapidDisk is free software: you can redistribute it and/or modify@n 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. RapidDisk 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 RapidDisk. If not, see . SPDX-License-Identifier: GPL-2.0-or-later @endverbatim * @author Petros Koutoupis \ * @author Matteo Tenca \ * @version 9.1.0 * @date 23 April 2023 */ #ifndef DAEMON_H #define DAEMON_H #include #define DEFAULT_MGMT_PORT "9118" #define CMD_PING_DAEMON "/v1/checkServiceStatus" #define CMD_LIST_RESOURCES "/v1/listAllResources" #define CMD_LIST_RD_VOLUMES "/v1/listRapidDiskVolumes" #define CMD_RDSK_CREATE "/v1/createRapidDisk" #define CMD_RDSK_REMOVE "/v1/removeRapidDisk" #define CMD_RDSK_RESIZE "/v1/resizeRapidDisk" #define CMD_RDSK_FLUSH "/v1/flushRapidDisk" #define CMD_RDSK_LOCK "/v1/lockRapidDisk" #define CMD_RDSK_UNLOCK "/v1/unlockRapidDisk" #define CMD_RCACHE_CREATE "/v1/createRapidDiskCache" #define CMD_RCACHE_REMOVE "/v1/removeRapidDiskCache" #define CMD_RCACHE_STATS "/v1/showRapidDiskCacheStats" #define CMD_LIST_NVMET "/v1/listAllNVMeTargets" #define CMD_LIST_NVMET_PORTS "/v1/listAllNVMePorts" #define PID_FILE "/run/rapiddiskd.pid" #define D_STDERR_LOG "/tmp/rapiddiskd_err.log" #define D_STDOUT_LOG "/tmp/rapiddiskd_out.log" #define D_EXITING "Daemon exiting." #define D_STARTING "Starting daemon..." #define D_RECV_REQ "Recevied request '%s'." #define D_LOOP_EXITING "Daemon loop function exiting: %s." #define D_SIGNAL_RECEIVED "Signal_handler function, SIGNAL received: %s." int mgmt_thread(void *arg); #endif rapiddisk-9.1.0/src/rdsk.c000066400000000000000000001150231442147535600154060ustar00rootroot00000000000000/** * @file rdsk.c * @brief RapidDisk function definitions * @details This file defines some RapidDisk related functions * @copyright @verbatim Copyright © 2011 - 2023 Petros Koutoupis All rights reserved. This file is part of RapidDisk. RapidDisk is free software: you can redistribute it and/or modify@n 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. RapidDisk 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 RapidDisk. If not, see . SPDX-License-Identifier: GPL-2.0-or-later @endverbatim * @author Petros Koutoupis \ * @author Matteo Tenca \ * @version 9.1.0 * @date 23 April 2023 */ #include "rdsk.h" #include "utils.h" #ifdef SERVER #include "rapiddiskd.h" #endif #include #include #include #include #define BYTES_PER_BLOCK 512 #define IOCTL_RD_GET_STATS 0x0529 #define IOCTL_RD_BLKFLSBUF 0x0531 struct RD_PROFILE *rdsk_head = NULL; struct RD_PROFILE *rdsk_end = NULL; struct RC_PROFILE *cache_head = NULL; struct RC_PROFILE *cache_end = NULL; static const size_t RC_STATS_OFFSETS[] = { offsetof(RC_STATS, device), offsetof(RC_STATS, reads), offsetof(RC_STATS, writes), offsetof(RC_STATS, cache_hits), offsetof(RC_STATS, replacement), offsetof(RC_STATS, write_replacement), offsetof(RC_STATS, read_invalidates), offsetof(RC_STATS, write_invalidates), offsetof(RC_STATS, uncached_reads), offsetof(RC_STATS, uncached_writes), offsetof(RC_STATS, disk_reads), offsetof(RC_STATS, disk_writes), offsetof(RC_STATS, cache_reads), offsetof(RC_STATS, cache_writes), /* Unsupported in this release */ offsetof(RC_STATS, read_ops), offsetof(RC_STATS, write_ops), }; static const size_t WC_STATS_OFFSETS[] = { offsetof(WC_STATS, device), offsetof(WC_STATS, expanded), offsetof(WC_STATS, errors), offsetof(WC_STATS, num_blocks), offsetof(WC_STATS, num_free_blocks), offsetof(WC_STATS, num_wb_blocks), /* For 5.15 and later */ offsetof(WC_STATS, num_read_req), offsetof(WC_STATS, num_read_cache_hits), offsetof(WC_STATS, num_write_req), offsetof(WC_STATS, num_write_uncommitted_blk_hits), offsetof(WC_STATS, num_write_committed_blk_hits), offsetof(WC_STATS, num_write_cache_bypass), offsetof(WC_STATS, num_write_cache_alloc), offsetof(WC_STATS, num_write_freelist_blocked), offsetof(WC_STATS, num_flush_req), offsetof(WC_STATS, num_discard_req), }; /** * It's a printf() function that can optionally return the formatted string instead of printing it * * @param format_string The error format string. * @param return_message This is a pointer to a string that will be filled with the error message. If this is NULL, the * error message will be printed to stdout. * @param ... Values to be inserted. */ void print_error(char *format_string, char *return_message, ...) { va_list args; va_start(args, return_message); if (return_message) { vsprintf(return_message, format_string, args); } else { vprintf(format_string, args); printf("\n"); } va_end(args); } /** * It reads a file and returns the contents of the file * * @param name The name of the directory to read from. * @param string The file to read from. * @param return_message This is the message that will be returned to the user. * * @return A pointer to a static buffer. */ char *read_info(char *name, char *string, char *return_message) { size_t len; char file[NAMELEN] = {0}; char buf[0xFF] = {0}; static char obuf[0xFF] = {0}; FILE *fp = NULL; size_t r; char *msg; memset(&buf, 0, sizeof(buf)); memset(&obuf, 0, sizeof(obuf)); sprintf(file, "%s/%s", name, string); fp = fopen(file, "r"); if (fp == NULL) { msg = ERR_FOPEN; print_error(msg, return_message, __func__, strerror(errno), file); return NULL; } r = fread(buf, FILEDATA, 1, fp); if ((r != 1) && (feof(fp) == 0) && (ferror(fp) != 0)) { msg = ERR_FREAD; print_error(msg, return_message, __func__, "could not read file", file); fclose(fp); return NULL; } fclose(fp); len = strlen(buf); strncpy(obuf, buf, (len - 1)); /* Do not copy tailing escape character */ return obuf; } /** * scandir() filter: "If the first two characters of the file name are 'rd', return TRUE, otherwise return FALSE" * * @param list This is the directory entry that is being passed to the function. * * @return the value of the comparison of the first two characters of the file name to the string "rd". */ int scandir_filter_rd(const struct dirent *list) { if (strncmp(list->d_name, "rd", 2) == SUCCESS) { return TRUE; } return FALSE; } /** * It searches the /sys/block directory for any device that starts with "rd" and then populates a linked list of struct * RD_PROFILE's with the device name, size, lock status, and usage * * @param return_message This is a pointer to a string that will be used to return error messages. * * @return A pointer to the first element in the linked list. */ struct RD_PROFILE *search_rdsk_targets(char *return_message) { int rc, n = 0; char file[NAMELEN] = {0}; struct dirent **list; struct RD_PROFILE *prof = NULL; char *msg; rdsk_head = NULL; rdsk_end = NULL; if ((rc = scandir(SYS_BLOCK, &list, scandir_filter_rd, NULL)) < 0) { msg = ERR_SCANDIR; print_error(msg, return_message, __func__, strerror(errno)); return NULL; } for (;n < rc; n++) { prof = calloc(1, sizeof(struct RD_PROFILE)); if (prof == NULL) { msg = ERR_CALLOC; print_error(msg, return_message, __func__, strerror(errno)); list = clean_scandir(list, rc); free_linked_lists(NULL, rdsk_head, NULL); return NULL; } strcpy(prof->device, (char *)list[n]->d_name); sprintf(file, "%s/%s", SYS_BLOCK, list[n]->d_name); char *info = read_info(file, "size", return_message); if (info == NULL) { free(prof); prof = NULL; list = clean_scandir(list, rc); free_linked_lists(NULL, rdsk_head, NULL); return NULL; } prof->size = (BYTES_PER_SECTOR * strtoull(info, NULL, 10)); prof->lock_status = mem_device_lock_status(prof->device); prof->usage = mem_device_get_usage(prof->device); if (rdsk_head == NULL) rdsk_head = prof; else rdsk_end->next = prof; rdsk_end = prof; prof->next = NULL; } list = clean_scandir(list, rc); return rdsk_head; } /** * scandir() filter: "If the first two characters of the file name are 'rc', return TRUE, otherwise return FALSE." * * The function is passed a pointer to a struct dirent, which is defined in the dirent.h header file. The struct dirent * contains the following members: * * @param list This is the directory entry that is being passed to the function. * * @return TRUE or FALSE */ int scandir_filter_rc(const struct dirent *list) { if (strncmp(list->d_name, "rc", 2) == SUCCESS) { return TRUE; } return FALSE; } /** * scandir() filter: "If the name of the directory/file entry begins with 'dm-', return TRUE, otherwise return FALSE" * * @param list This is the directory entry that is being passed to the function. * * @return TRUE or FALSE */ int scandir_filter_dm(const struct dirent *list) { if (strncmp(list->d_name, "dm-", 3) == SUCCESS) { return TRUE; } return FALSE; } /** * It searches for all the cache targets in the system and returns a linked list of them * * @param return_message This is a pointer to a string that will be used to return any error messages. * * @return A pointer to the first element of a linked list of structs. */ struct RC_PROFILE *search_cache_targets(char *return_message) { int num, num2, num3, n = 0, i, z; struct dirent **list, **nodes, **maps; char file[NAMELEN] = {0}; struct RC_PROFILE *prof = NULL; char *msg; cache_head = NULL; cache_end = NULL; if ((num = scandir(DEV_MAPPER, &list, scandir_filter_rc, NULL)) < 0) { msg = ERR_SCANDIR; print_error(msg, return_message, __func__, strerror(errno)); return NULL; } if ((num2 = scandir(SYS_BLOCK, &nodes, scandir_filter_dm, NULL)) < 0) { msg = ERR_SCANDIR; print_error(msg, return_message, __func__, strerror(errno)); list = clean_scandir(list, num); return NULL; } for (;n < num; n++) { prof = calloc(1, sizeof(struct RC_PROFILE)); if (prof == NULL) { msg = ERR_CALLOC; print_error(msg, return_message, __func__, strerror(errno)); list = clean_scandir(list, num); nodes = clean_scandir(nodes, num2); free_linked_lists(cache_head, NULL, NULL); return NULL; } strcpy(prof->device, list[n]->d_name); for (i = 0; i < num2; i++) { sprintf(file, "%s/%s", SYS_BLOCK, nodes[i]->d_name); char *info_size = read_info(file, "dm/name", return_message); if (info_size == NULL) { list = clean_scandir(list, num); nodes = clean_scandir(nodes, num2); free(prof); free_linked_lists(cache_head, NULL, NULL); return NULL; } if (strncmp(info_size, prof->device, sizeof(prof->device)) == 0) { sprintf(file, "%s/%s/slaves", SYS_BLOCK, nodes[i]->d_name); if ((num3 = scandir(file, &maps, scandir_filter_no_dot, NULL)) < 0) { msg = ERR_SCANDIR; print_error(msg, return_message, __func__, strerror(errno)); list = clean_scandir(list, num); nodes = clean_scandir(nodes, num2); free(prof); free_linked_lists(cache_head, NULL, NULL); return NULL; } for (z=0;z < num3; z++) { if (strncmp(maps[z]->d_name, "rd", 2) == SUCCESS) strcpy(prof->cache, (char *)maps[z]->d_name); else strcpy(prof->source, (char *)maps[z]->d_name); } maps = clean_scandir(maps, num3); } } if (cache_head == NULL) cache_head = prof; else cache_end->next = prof; cache_end = prof; prof->next = NULL; } list = clean_scandir(list, num); nodes = clean_scandir(nodes, num2); return cache_head; } /** * This function takes a string as an argument and returns the lock status of the device * * @param string The name of the device to lock. * @result The lock status of the device. */ int mem_device_lock_status(char *string) { int fd, rc = INVALID_VALUE; char file[NAMELEN] = {0}; sprintf(file, "/dev/%s", string); if ((fd = open(file, O_WRONLY)) < SUCCESS) return -ENOENT; if((ioctl(fd, BLKROGET, &rc)) == INVALID_VALUE) { close(fd); return -EIO; } close(fd); return rc; } /** * It opens the device file, calls the ioctl() function to get the usage, and returns the result * * @param string The name of the device. * * @return The amount of memory used by the device. */ unsigned long long mem_device_get_usage(char *string) { int fd; unsigned long long rc = INVALID_VALUE; char file[NAMELEN] = {0}; sprintf(file, "/dev/%s", string); if ((fd = open(file, O_WRONLY)) < SUCCESS) return -ENOENT; if ((ioctl(fd, RD_GET_USAGE, &rc)) == INVALID_VALUE) { close(fd); return -EIO; } close(fd); return (rc * PAGE_SIZE); } /** * It sends a flush command to the device mapper * * @param device the device name, e.g. /dev/mapper/my_device * * @return The return value is the return value of the dm_task_run function. */ int dm_flush_device(char *device) { int cmdno = DM_DEVICE_TARGET_MSG; struct dm_task *dmt; uint64_t sector = 0; char *command = "flush"; if (!(dmt = dm_task_create(cmdno))) { return INVALID_VALUE; } if (!dm_task_set_name(dmt, device)) { dm_task_destroy(dmt); return INVALID_VALUE; } if (!dm_task_set_sector(dmt, sector)) { dm_task_destroy(dmt); return INVALID_VALUE; } if (!dm_task_set_message(dmt, command)) { dm_task_destroy(dmt); return INVALID_VALUE; } if (!dm_task_run(dmt)) { dm_task_destroy(dmt); return INVALID_VALUE; } const char *response = dm_task_get_message_response(dmt); dm_task_destroy(dmt); return SUCCESS; } /** * It removes a device mapping * * @param device the name of the device to be removed. * * @return The return value is the status of the operation. */ int dm_remove_mapping(char *device) { int cmdno = DM_DEVICE_REMOVE; struct dm_task *dmt; uint32_t cookie = 0; uint16_t udev_flags = 0; if (!(dmt = dm_task_create(cmdno))) { return INVALID_VALUE; } if (!dm_task_set_name(dmt, device)) { dm_task_destroy(dmt); return INVALID_VALUE; } if (!dm_task_set_add_node(dmt, DM_ADD_NODE_ON_CREATE)) { dm_task_destroy(dmt); return INVALID_VALUE; } if (!dm_task_set_cookie(dmt, &cookie, udev_flags)) { dm_task_destroy(dmt); return INVALID_VALUE; } if (!dm_task_run(dmt)) { dm_task_destroy(dmt); return INVALID_VALUE; } dm_udev_wait(cookie); dm_task_destroy(dmt); return SUCCESS; } /** * It takes a device name and a cache type, and returns a pointer to a struct containing the cache statistics * * @param device the device name, e.g. /dev/mapper/cache0 * @param cache_type WRITETHROUGH, WRITEAROUND or WRITEBACK * * @return A pointer to a struct containing the cache statistics. */ void *dm_get_status(char* device, enum CACHE_TYPE cache_type) { int cmdno = DM_DEVICE_STATUS; uint64_t start, length; char *target_type = NULL; char *params; struct dm_task *dmt = NULL; RC_STATS *rc_stats = NULL; WC_STATS *wc_stats = NULL; void *stats = NULL; struct dm_info info; if (!(dmt = dm_task_create(cmdno))) { return NULL; } if (!dm_task_set_name(dmt, device)) { dm_task_destroy(dmt); return NULL; } if (!dm_task_run(dmt)) { dm_task_destroy(dmt); return NULL; } if (!dm_task_get_info(dmt, &info)) { dm_task_destroy(dmt); return NULL; } if (!info.exists) { dm_task_destroy(dmt); return NULL; } dm_get_next_target(dmt, NULL, &start, &length, &target_type, ¶ms); if (target_type) { size_t result_size = strlen(params) * 2; char *result = calloc(1, result_size); if (preg_replace("[^\\d ]", "", params, result, result_size) < SUCCESS) { if (result) free(result); dm_task_destroy(dmt); return NULL; } if (preg_replace(" ", " ", result, result, result_size) < SUCCESS) { if (result) free(result); dm_task_destroy(dmt); return NULL; } char *split_arr[64] = {NULL}; split(result, split_arr, " "); if (cache_type == WRITETHROUGH || cache_type == WRITEAROUND) { rc_stats = calloc(1, sizeof(struct RC_STATS)); sprintf(rc_stats->device, "%s", device); int i = 1; for (; split_arr[i - 1] != NULL; i++) { unsigned int *pvalue = (unsigned int *) (((char *) rc_stats) + RC_STATS_OFFSETS[i]); unsigned int v = strtol(split_arr[i - 1], NULL, 10); *pvalue = v; } stats = (void *) rc_stats; } else if (cache_type == WRITEBACK) { wc_stats = calloc(1, sizeof(struct WC_STATS)); sprintf(wc_stats->device, "%s", device); wc_stats->expanded = FALSE; int i = 2; for (; split_arr[i - 2] != NULL; i++) { unsigned int *pvalue = (unsigned int *) (((char *) wc_stats) + WC_STATS_OFFSETS[i]); unsigned int v = strtol(split_arr[i - 2], NULL, 10); *pvalue = v; } if (i > 5) wc_stats->expanded = TRUE; stats = (void *) wc_stats; } if (result) free(result); } dm_task_destroy(dmt); return stats; } /** * It creates a device-mapper mapping with the name specified in the first argument, and the table specified in the second * argument * * @param final_map_name the name of the device mapper device to create. * @param table a table definition, i.e. "0 452591616 rapiddisk-cache /dev/sdb1 /dev/rd2 20480 0" */ int dm_create_mapping(char* final_map_name, char *table) { int cmdno = DM_DEVICE_CREATE; struct dm_task *dmt = NULL; struct dm_info info; char ttype[4096]; unsigned long long size, start; int n; uint32_t cookie = 0; uint16_t udev_flags = 0; /* table example: "0 452591616 rapiddisk-cache /dev/sdb1 /dev/rd2 20480 0" */ if (!(dmt = dm_task_create(cmdno))) { return INVALID_VALUE; } if (!dm_task_set_name(dmt, final_map_name)) { dm_task_destroy(dmt); return INVALID_VALUE; } if (sscanf(table, "%llu %llu %s %n", &start, &size, ttype, &n) < 3) { dm_task_destroy(dmt); return INVALID_VALUE; } table += n; if (!dm_task_add_target(dmt, start, size, ttype, table)) { dm_task_destroy(dmt); return INVALID_VALUE; } if (!dm_task_set_add_node(dmt, DM_ADD_NODE_ON_RESUME)) { dm_task_destroy(dmt); return INVALID_VALUE; } if (!dm_task_set_cookie(dmt, &cookie, udev_flags)) { dm_task_destroy(dmt); return INVALID_VALUE; } if (!dm_task_run(dmt)) { dm_task_destroy(dmt); return INVALID_VALUE; } dm_udev_wait(cookie); if (!dm_task_get_info(dmt, &info)) { dm_task_destroy(dmt); return INVALID_VALUE; } if (!info.exists) { dm_task_destroy(dmt); return INVALID_VALUE; } dm_task_destroy(dmt); return SUCCESS; } /** * It takes a ramdisk device, a block device, and a cache mode, and creates a mapping between the two * * @param rd_prof pointer to the head of the linked list of RD_PROFILE structures * @param rc_prof pointer to the head of the linked list of RC_PROFILE structures * @param ramdisk the name of the ramdisk device * @param block_dev the block device to be mapped to the ramdisk * @param cache_mode WRITETHROUGH = 0 = write-through, WRITEAROUND = 1 = write-around, WRITEBACK = 2 = write-back * @param return_message This is a pointer to a buffer that will contain the error message if the function fails. */ int cache_device_map(struct RD_PROFILE *rd_prof, struct RC_PROFILE *rc_prof, char *ramdisk, char *block_dev, int cache_mode, char *return_message) { int rc = INVALID_VALUE, fd; unsigned long long block_dev_sz = 0, ramdisk_sz = 0; FILE *fp = NULL; char *buf, table[BUFSZ] = {0}, name[NAMELEN] = {0}, str[NAMELEN - 6] = {0}; char *token, *dup; char *msg; while (rd_prof != NULL) { if (strcmp(ramdisk, rd_prof->device) == SUCCESS) { rc = SUCCESS; } rd_prof = rd_prof->next; } if (rc != SUCCESS) { print_error(ERR_DEV_NOEXIST, return_message, ramdisk); return -ENOENT; } /* Check to make sure it is a normal block device found in /dev */ if (strncmp(block_dev, "/dev/", 5) != SUCCESS) { msg = "Error. Source device does not seem to be a normal block device listed in the /dev directory path."; if (return_message) { sprintf(return_message, "%s", msg); } else { printf("%s\n", msg); } return INVALID_VALUE; } /* Check to make sure that ramdisk/block_dev devices are not in a mapping already */ while (rc_prof != NULL) { if ((strcmp(ramdisk, rc_prof->cache) == SUCCESS) || \ (strcmp(block_dev + 5, rc_prof->source) == SUCCESS)) { msg = "Error. At least one of your cache/source devices is currently mapped to %s."; print_error(msg, return_message, rc_prof->device); return INVALID_VALUE; } rc_prof = rc_prof->next; } if ((buf = calloc(1,BUFSZ)) == NULL) { msg = "%s: malloc: Unable to allocate memory."; print_error(msg, return_message, __func__); return INVALID_VALUE; } if ((fp = fopen(ETC_MTAB, "r")) == NULL) { msg = ERR_FOPEN; print_error(msg, return_message, __func__, ETC_MTAB, strerror(errno)); if (buf) free(buf); return INVALID_VALUE; } fread(buf, BUFSZ, 1, fp); fclose(fp); if ((strstr(buf, ramdisk) != NULL)) { msg = "%s is currently mounted. Please \"umount\" and retry."; print_error(msg, return_message, ramdisk); if (buf) free(buf); return INVALID_VALUE; } if ((strstr(buf, block_dev) != NULL)) { msg = "%s is currently mounted. Please \"umount\" and retry."; print_error(msg, return_message, block_dev); if (buf) free(buf); return INVALID_VALUE; } if (buf) free(buf); if ((fd = open(block_dev, O_RDONLY)) < SUCCESS) { msg = "%s: open: %s"; print_error(msg, return_message, __func__, strerror(errno)); return -ENOENT; } if (ioctl(fd, BLKGETSIZE, &block_dev_sz) == INVALID_VALUE) { msg = "%s: ioctl: %s"; print_error(msg, return_message, __func__, strerror(errno)); close(fd); return -EIO; } close(fd); sprintf(name, "/dev/%s", ramdisk); if ((fd = open(name, O_RDONLY)) < SUCCESS) { msg = "%s: open: %s"; print_error(msg, return_message, __func__, strerror(errno)); return -ENOENT; } if (ioctl(fd, BLKGETSIZE, &ramdisk_sz) == INVALID_VALUE) { msg = "%s: ioctl: %s"; print_error(msg, return_message, __func__, strerror(errno)); close(fd); return -EIO; } close(fd); memset(name, 0x0, sizeof(name)); dup = strdup(block_dev); token = strtok(dup, "/"); while (token != NULL) { sprintf(str, "%s", token); token = strtok(NULL, "/"); } if (cache_mode == WRITETHROUGH) sprintf(name, "rc-wt_%s", str); else if (cache_mode == WRITEBACK) /* very dangerous mode */ sprintf(name, "rc-wb_%s", str); else sprintf(name, "rc-wa_%s", str); memset(table, 0x0, BUFSZ); if (cache_mode == WRITEBACK) /* very dangerous mode */ sprintf(table, "0 %llu writecache s %s /dev/%s 4096 0", block_dev_sz, block_dev, ramdisk); else { sprintf(table, "0 %llu rapiddisk-cache %s /dev/%s %llu %d", block_dev_sz, block_dev, ramdisk, ramdisk_sz, cache_mode); } rc = dm_create_mapping(name, table); if (rc == SUCCESS) { msg = "Command to map %s with %s and %s has been sent."; print_error(msg, return_message, name, ramdisk, block_dev); } else { msg = "Error. Unable to create map. Please verify all input values are correct."; print_error("%s", return_message, msg); } if (dup) free(dup); return rc; } /** * It resizes the device * * @param prof This is a pointer to the linked list of RD_PROFILE structures. * @param string The device name * @param size The size of the device in Mbytes * @param return_message This is a pointer to a buffer that will contain the return message. * * @return The return value is SUCCESS upon result */ int mem_device_resize(struct RD_PROFILE *prof, char *string, unsigned long long size, char *return_message) { int rc = INVALID_VALUE, fd; FILE *fp = NULL; char file[NAMELEN] = {0}; unsigned long long max_sectors = 0, rd_size = 0; char *msg; /* echo "rapiddisk resize 1 131072 " > /sys/kernel/rapiddisk/mgmt */ while (prof != NULL) { if (strcmp(string, prof->device) == SUCCESS) { rd_size = prof->size; rc = SUCCESS; } prof = prof->next; } if (rc != SUCCESS) { print_error(ERR_DEV_NOEXIST, return_message, string); return -ENOENT; } sprintf(file, "/dev/%s", string); if ((fd = open(file, O_WRONLY)) < SUCCESS) { msg = "%s: open: %s"; print_error(msg, return_message, __func__, strerror(errno)); return -ENOENT; } /** * TODO: check why max_sectors is 0 in some cases */ if ((ioctl(fd, IOCTL_RD_GET_STATS, &max_sectors)) == INVALID_VALUE) { msg = "%s: ioctl: %s"; print_error(msg, return_message, __func__, strerror(errno)); close(fd); return -EIO; } close(fd); if ((((size * 1024 * 1024) / BYTES_PER_BLOCK) <= (max_sectors)) || ((size * 1024) == (rd_size / 1024))) { if ((size * 1024) == (rd_size / 1024)) { msg = "Error. Size is currently set to %llu Mbytes. Please specify a size larger than %llu Mbytes."; print_error(msg, return_message, (rd_size / 1024) / 1024, (((max_sectors * BYTES_PER_BLOCK) / 1024) / 1024)); } else { msg = "Error. Please specify a size larger than %llu Mbytes."; print_error(msg, return_message, (((max_sectors * BYTES_PER_BLOCK) / 1024) / 1024)); } return -EINVAL; } /* This is where we begin to detach the block device */ if ((fp = fopen(SYS_RDSK, "w")) == NULL) { msg = "%s: fopen: %s: %s"; print_error(msg, return_message, __func__, SYS_RDSK, strerror(errno)); return -ENOENT; } if (fprintf(fp, "rapiddisk resize %s %llu\n", string + 2, (size * 1024 * 1024)) < 0) { msg = "%s: fprintf: %s"; print_error(msg, return_message, __func__, strerror(errno)); fclose(fp); return -EIO; } fclose(fp); print_error("Resized device %s to %llu Mbytes.", return_message, string, size); return SUCCESS; } /** * It attaches a new device to the kernel * * @param prof This is a pointer to the linked list of RD_PROFILE structures. * @param size size of the device in Mbytes * @param return_message This is a pointer to a buffer that will contain the error message if the function fails. * * @return The return value is SUCCESS upon result */ int mem_device_attach(struct RD_PROFILE *prof, unsigned long long size, char *return_message) { int dsk; FILE *fp = NULL; char string[BUFSZ] = {0}, name[16] = {0}; char *msg; /* echo "rapiddisk attach 65536" > /sys/kernel/rapiddisk/mgmt <- in bytes */ for (dsk = 0; prof != NULL; dsk++) { strcat(string, ","); strcat(string, prof->device); prof = prof->next; } while (dsk >= 0) { sprintf(name, "rd%d", dsk); if (strstr(string, (const char *)name) == NULL) { break; } dsk--; } if ((fp = fopen(SYS_RDSK, "w")) == NULL) { msg = "%s: fopen: %s: %s"; print_error(msg, return_message, __func__, SYS_RDSK, strerror(errno)); return -ENOENT; } if (fprintf(fp, "rapiddisk attach %d %llu\n", dsk, (size * 1024 * 1024)) < 0) { msg = "%s: fprintf: %s"; print_error(msg, return_message, __func__, strerror(errno)); fclose(fp); return -EIO; } fclose(fp); print_error("Attached device rd%d of size %llu Mbytes.", return_message, dsk, size); return SUCCESS; } /** * It detaches a RapidDisk device from the system * * @param rd_prof This is a pointer to the first element in the linked list of RapidDisk devices. * @param rc_prof This is a pointer to the first element in the linked list of RapidCache devices. * @param string The device name to be detached. * @param return_message This is a pointer to a char array that will be filled with the return message. * * @return The return value is the return code of the function. */ int mem_device_detach(struct RD_PROFILE *rd_prof, RC_PROFILE *rc_prof, char *string, char *return_message) { int rc = INVALID_VALUE; FILE *fp = NULL; char *buf = NULL; char *msg; /* echo "rapiddisk detach 1" > /sys/kernel/rapiddisk/mgmt */ while (rd_prof != NULL) { if (strcmp(string, rd_prof->device) == SUCCESS) rc = SUCCESS; rd_prof = rd_prof->next; } if (rc != SUCCESS) { print_error(ERR_DEV_NOEXIST, return_message, string); return INVALID_VALUE; } /* Check to make sure RapidDisk device isn't in a mapping */ while (rc_prof != NULL) { if (strcmp(string, rc_prof->cache) == SUCCESS) { msg = "Error. Unable to remove %s. This RapidDisk device is currently" " mapped as a cache drive to %s."; print_error(msg, return_message, string, rc_prof->device); return INVALID_VALUE; } rc_prof = rc_prof->next; } if ((buf = (char *)calloc(1, BUFSZ)) == NULL) { msg = "%s: malloc: Unable to allocate memory."; print_error(msg, return_message, __func__); return INVALID_VALUE; } /* Here we are starting to check to see if the device is mounted */ if ((fp = fopen(ETC_MTAB, "r")) == NULL) { msg = "%s: fopen: %s: %s"; print_error(msg, return_message, __func__, ETC_MTAB, strerror(errno)); if (buf) free(buf); return -ENOENT; } fread(buf, BUFSZ, 1, fp); fclose(fp); if ((strstr(buf, string) != NULL)) { msg = "%s is currently mounted. Please \"umount\" and retry."; print_error(msg, return_message, string); if (buf) free(buf); return INVALID_VALUE; } /* This is where we begin to detach the block device */ if ((fp = fopen(SYS_RDSK, "w")) == NULL) { msg = "%s: fopen: %s: %s"; print_error(msg, return_message, __func__, SYS_RDSK, strerror(errno)); if (buf) free(buf); return -ENOENT; } if (fprintf(fp, "rapiddisk detach %s\n", string + 2) < 0) { msg = "%s: fprintf: %s"; print_error(msg, return_message, __func__, strerror(errno)); if (buf) free(buf); fclose(fp); return -EIO; } fclose(fp); print_error("Detached device %s.", return_message, string); if (buf) free(buf); return SUCCESS; } /** * It takes a device name and a boolean value, and sets the device to read-only or read-write * * @param rd_prof This is a pointer to the first element of the linked list of devices. * @param string The device name to lock/unlock * @param lock TRUE or FALSE * @param return_message This is a pointer to a buffer that will contain the error message if the function fails. * * @return The return value is the return code of the function. */ int mem_device_lock(struct RD_PROFILE *rd_prof, char *string, bool lock, char *return_message) { int fd, rc = INVALID_VALUE, state = (unsigned char)lock; char file[NAMELEN] = {0}; char *msg; while (rd_prof != NULL) { if (strcmp(string, rd_prof->device) == SUCCESS) { rc = SUCCESS; } rd_prof = rd_prof->next; } if (rc != SUCCESS) { print_error(ERR_DEV_NOEXIST, return_message, string); return -ENOENT; } sprintf(file, "/dev/%s", string); if ((fd = open(file, O_WRONLY)) < SUCCESS) { msg = "%s: open: %s"; print_error(msg, return_message, __func__, strerror(errno)); return -ENOENT; } if ((ioctl(fd, BLKROSET, &state)) == INVALID_VALUE) { msg = "%s: ioctl: %s"; print_error(msg, return_message, __func__, strerror(errno)); close(fd); return -EIO; } close(fd); print_error("Device %s is now set to %s", return_message, string, ((lock == TRUE) ? "read-only" : "read-write")); return SUCCESS; } /** * This function is used to unmap a cache device * * @param prof the head of the profile list * @param string the name of the cache device to be unmapped * @param return_message This is a pointer to a buffer that will be filled with the return message. * * @return The return code of the system call. */ int cache_device_unmap(struct RC_PROFILE *prof, char *string, char *return_message) { int rc = INVALID_VALUE; FILE *fp = NULL; char *buf = NULL; char *msg; /* dmsetup remove rc-wt_sdb */ while (prof != NULL) { if (strcmp(string, prof->device) == SUCCESS) rc = SUCCESS; prof = prof->next; } if (rc != SUCCESS) { print_error(ERR_CACHE_TGT_NOEXIST, return_message, string); return -ENOENT; } if ((buf = calloc(1,BUFSZ)) == NULL) { msg = "%s: malloc: Unable to allocate memory."; print_error(msg, return_message, __func__); return -ENOMEM; } /* Here we are starting to check to see if the device is mounted */ if ((fp = fopen(ETC_MTAB, "r")) == NULL) { msg = "%s: fopen: %s: %s"; print_error(msg, return_message, __func__, ETC_MTAB, strerror(errno)); if (buf) free(buf); return -ENOENT; } fread(buf, BUFSZ, 1, fp); fclose(fp); if ((strstr(buf, string) != NULL)) { msg = "%s is currently mounted. Please \"umount\" and retry."; print_error(msg, return_message, string); if (buf) free(buf); return -EBUSY; } if (buf) free(buf); /* if the mapping is a write-back one, flush before remove */ if (strstr(string, "rc-wb") != NULL) { if ((rc = dm_flush_device(string) != SUCCESS)) { msg = "Unable to flush dirty cache data to %s."; print_error(msg, return_message, string); return rc; } } if ((rc = dm_remove_mapping(string)) == SUCCESS) { msg = "Command to unmap %s has been sent."; } else { msg = "Error. Unable to unmap %s. Please check to make sure nothing is wrong."; } print_error(msg, return_message, string); return rc; } /** * It flushes all data from a RapidDisk device * * @param rd_prof This is a pointer to the RapidDisk profile list. * @param rc_prof This is a pointer to the RC_PROFILE structure list that contains the RapidCache mapping information. * @param string The device name * @param return_message This is a pointer to a buffer that will contain the return message. * * @return The return value is the return code of the function. */ int mem_device_flush(struct RD_PROFILE *rd_prof, RC_PROFILE *rc_prof, char *string, char *return_message) { int fd, rc = INVALID_VALUE; char file[NAMELEN] = {0}, *buf = NULL; FILE *fp = NULL; char *msg; while (rd_prof != NULL) { if (strcmp(string, rd_prof->device) == SUCCESS) rc = SUCCESS; rd_prof = rd_prof->next; } if (rc != SUCCESS) { print_error(ERR_DEV_NOEXIST, return_message, string); return -ENOENT; } /* Check to make sure RapidDisk device isn't in a mapping */ while (rc_prof != NULL) { if (strcmp(string, rc_prof->cache) == SUCCESS) { msg = "Error. Unable to remove %s. This RapidDisk device is currently mapped as a cache drive to %s."; print_error(msg, return_message, string, rc_prof->device); return -EBUSY; } rc_prof = rc_prof->next; } if ((buf = calloc(1, BUFSZ)) == NULL) { msg = "%s: malloc: Unable to allocate memory."; print_error(msg, return_message, __func__); return -ENOMEM; } /* Here we are starting to check to see if the device is mounted */ if ((fp = fopen(ETC_MTAB, "r")) == NULL) { msg = "%s: fopen: %s: %s"; print_error(msg, return_message, __func__, ETC_MTAB, strerror(errno)); if (buf) free(buf); return -ENOENT; } fread(buf, BUFSZ, 1, fp); fclose(fp); if ((strstr(buf, string) != NULL)) { msg = "%s is currently mounted. Please \"umount\" and retry."; print_error(msg, return_message, string); if (buf) free(buf); return -EBUSY; } if (buf) free(buf); sprintf(file, "/dev/%s", string); if ((fd = open(file, O_WRONLY)) < SUCCESS) { msg = "%s: open: %s"; print_error(msg, return_message, __func__, strerror(errno)); return -ENOENT; } if (ioctl(fd, IOCTL_RD_BLKFLSBUF, 0) == INVALID_VALUE) { msg = "%s: ioctl: %s"; print_error(msg, return_message, __func__, strerror(errno)); close(fd); return -EIO; } close(fd); print_error("Flushed all data from device %s.", return_message, string); return SUCCESS; } /** * These functions are used by rapiddisk only */ #ifndef SERVER /** * It prints out the list of RapidDisk devices and RapidDisk-Cache mappings * * @param rd_prof This is a pointer to the first element in the linked list of RD_PROFILE structures. * @param rc_prof This is a pointer to the first element in the linked list of RC_PROFILE structures. */ int mem_device_list(struct RD_PROFILE *rd_prof, struct RC_PROFILE *rc_prof) { int num = 1; char status[0xf] = {0}; printf("List of RapidDisk device(s):\n\n"); while (rd_prof != NULL) { memset(status, 0x0, sizeof(status)); if (rd_prof->lock_status == TRUE) { sprintf(status, "Locked"); } else if (rd_prof->lock_status == FALSE) { sprintf(status, "Unlocked"); } else { sprintf(status, "Unavailable"); } printf(" RapidDisk Device %d: %s\tSize (KB): %llu\tUsage (KB): %llu\tStatus: %s\n", num, rd_prof->device, (rd_prof->size / 1024), (rd_prof->usage / 1024), status); num++; rd_prof = rd_prof->next; } printf("\nList of RapidDisk-Cache mapping(s):\n\n"); if (rc_prof == NULL) { printf(" None\n"); goto list_out; } num = 1; while (rc_prof != NULL) { if (strstr(rc_prof->device, "rc-wb") != NULL) { printf(" dm-writecache Target %d: %s\tCache: %s Target: %s (WRITEBACK)\n", num, rc_prof->device, rc_prof->cache, rc_prof->source); } else { printf(" RapidDisk-Cache Target %d: %s\tCache: %s Target: %s (%s)\n", num, rc_prof->device, rc_prof->cache, rc_prof->source, (strncmp(rc_prof->device, "rc-wt_", 5) == 0) ? \ "WRITE THROUGH" : "WRITE AROUND"); } num++; rc_prof = rc_prof->next; } list_out: printf("\n"); return SUCCESS; } /** * It prints out the statistics of the wb cache device * * @param rc_prof the RC_PROFILE structure that contains the cache device name and the cache device size. * @param cache the name of the cache device * * @return The function status result */ int cache_wb_device_stat(struct RC_PROFILE *rc_prof, char *cache) { int rc = INVALID_VALUE; char *msg; if (rc_prof == NULL) { msg = "No RapidDisk-Cache Mappings exist."; print_error("%s", NULL, msg); return rc; } while (rc_prof != NULL) { if (strcmp(cache, rc_prof->device) == SUCCESS) rc = SUCCESS; rc_prof = rc_prof->next; } if (rc != SUCCESS) { print_error(ERR_CACHE_TGT_NOEXIST, NULL, cache); return -ENOENT; } struct WC_STATS *wc_stats = dm_get_status(cache, WRITEBACK); int i = 2; unsigned int *pvalue = NULL; for (; i < (((sizeof(WC_STATS_OFFSETS) / sizeof(size_t)) - 1)); i++) { pvalue = (unsigned int *) (((char *) wc_stats) + WC_STATS_OFFSETS[i]); printf("%d ", *pvalue); } pvalue = (unsigned int *) (((char *) wc_stats) + WC_STATS_OFFSETS[i]); printf("%d\n", *pvalue); free(wc_stats); return SUCCESS; } /** * It prints the statistics of a cache device * * @param rc_prof This is the list of RapidDisk-Cache mappings. * @param cache The cache device name. * * @return The function status result */ int cache_device_stat(struct RC_PROFILE *rc_prof, char *cache) { int rc = INVALID_VALUE; char *msg; if (rc_prof == NULL) { msg = "No RapidDisk-Cache Mappings exist."; print_error("%s", NULL, msg); return rc; } while (rc_prof != NULL) { if (strcmp(cache, rc_prof->device) == SUCCESS) rc = SUCCESS; rc_prof = rc_prof->next; } if (rc != SUCCESS) { print_error(ERR_CACHE_TGT_NOEXIST, NULL, cache); return -ENOENT; } struct RC_STATS *rc_stats = dm_get_status(cache, WRITETHROUGH); int i = 1; unsigned int *pvalue = NULL; for (; i < (((sizeof(RC_STATS_OFFSETS) / sizeof(size_t)) - 1)); i++) { pvalue = (unsigned int *) (((char *) rc_stats) + RC_STATS_OFFSETS[i]); printf("%d ", *pvalue); } pvalue = (unsigned int *) (((char *) rc_stats) + RC_STATS_OFFSETS[i]); printf("%d\n", *pvalue); free(rc_stats); return SUCCESS; } /** * This function is used to get the statistics of a cache device * * @param rc_prof This is the list of cache mappings. * @param cache The name of the cache device. * @param rc_stats This is a pointer to a pointer to a RC_STATS structure. This is the structure that will be filled with * the statistics. * * @return The function status result */ int cache_device_stat_json(struct RC_PROFILE *rc_prof, char *cache, RC_STATS **rc_stats) { int rc = INVALID_VALUE; char *msg; char status_message[NAMELEN] = {0}; if (rc_prof == NULL) { msg = "No RapidDisk-Cache Mappings exist."; print_message(rc, msg, TRUE); return rc; } while (rc_prof != NULL) { if (strcmp(cache, rc_prof->device) == SUCCESS) rc = SUCCESS; rc_prof = rc_prof->next; } if (rc != SUCCESS) { print_error(ERR_CACHE_TGT_NOEXIST, status_message, cache); print_message(rc, status_message, TRUE); return -ENOENT; } *rc_stats = dm_get_status(cache, WRITETHROUGH); return SUCCESS; } /** * This function is used to get the writeback cache statistics for a given cache target * * @param rc_prof This is the linked list of RapidDisk cache mappings. * @param cache The cache device name * @param wc_stats This is a pointer to a pointer to a WC_STATS structure. This is the structure that will be filled with * the statistics. * * @return The function status result */ int cache_wb_device_stat_json(struct RC_PROFILE *rc_prof, char *cache, WC_STATS **wc_stats) { int rc = INVALID_VALUE; char *msg; char status_message[NAMELEN] = {0}; if (rc_prof == NULL) { msg = "No RapidDisk-Cache Mappings exist."; print_message(rc, msg, TRUE); return rc; } while (rc_prof != NULL) { if (strcmp(cache, rc_prof->device) == SUCCESS) rc = SUCCESS; rc_prof = rc_prof->next; } if (rc != SUCCESS) { print_error(ERR_CACHE_TGT_NOEXIST, status_message, cache); print_message(rc, status_message, TRUE); return -ENOENT; } *wc_stats = dm_get_status(cache, WRITEBACK); return SUCCESS; } #endif rapiddisk-9.1.0/src/rdsk.h000066400000000000000000000056131442147535600154160ustar00rootroot00000000000000/** * @file * @brief Disk functions and constants * @details This header file defines constants and functions related to disk access * @copyright @verbatim Copyright © 2011 - 2023 Petros Koutoupis All rights reserved. This file is part of RapidDisk. RapidDisk is free software: you can redistribute it and/or modify@n 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. RapidDisk 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 RapidDisk. If not, see . SPDX-License-Identifier: GPL-2.0-or-later @endverbatim * @author Petros Koutoupis \ * @author Matteo Tenca \ * @version 9.1.0 * @date 23 April 2023 */ #ifndef RDSK_H #define RDSK_H #include "common.h" #define BYTES_PER_SECTOR 0x200 #define SYS_BLOCK "/sys/block" #define ETC_MTAB "/etc/mtab" #define DEV_MAPPER "/dev/mapper" #define RD_GET_USAGE 0x0530 #define PAGE_SIZE 0x1000 void print_error(char *format_string, char *return_message, ...); char *read_info(char *name, char *string, char *return_message); unsigned long long mem_device_get_usage(char *); int mem_device_lock_status(char *); struct RD_PROFILE *search_rdsk_targets(char *return_message); struct RC_PROFILE *search_cache_targets(char *return_message); void *dm_get_status(char *device, enum CACHE_TYPE cache_type); int dm_create_mapping(char* device, char *table); int cache_device_map(struct RD_PROFILE *rd_prof, struct RC_PROFILE *rc_prof, char *ramdisk, char *block_dev, int cache_mode, char *return_message); int mem_device_resize(struct RD_PROFILE *prof, char *string, unsigned long long size, char *return_message); int mem_device_attach(struct RD_PROFILE *, unsigned long long, char *return_message); int mem_device_detach(struct RD_PROFILE *, struct RC_PROFILE *, char *, char *return_message); int mem_device_lock(struct RD_PROFILE *, char *, bool, char *return_message); int cache_device_unmap(struct RC_PROFILE *, char *, char *return_message); int dm_remove_mapping(char* device); int dm_flush_device(char *device); int mem_device_flush(struct RD_PROFILE *rd_prof, RC_PROFILE *rc_prof, char *string, char *return_message); #ifndef SERVER int cache_device_stat(struct RC_PROFILE *rc_profile, char *cache); int cache_device_stat_json(struct RC_PROFILE *rc_prof, char *cache, RC_STATS **rc_stats); int cache_wb_device_stat(struct RC_PROFILE *rc_prof, char *cache); int cache_wb_device_stat_json(struct RC_PROFILE *rc_prof, char *cache, WC_STATS **wc_stats); int mem_device_list(struct RD_PROFILE *, struct RC_PROFILE *); #endif #endif //RDSK_H rapiddisk-9.1.0/src/sys.c000066400000000000000000000135711442147535600152660ustar00rootroot00000000000000/** * @file sys.c * @brief System function definitions * @details This file contains all the system functions * @copyright @verbatim Copyright © 2011 - 2023 Petros Koutoupis All rights reserved. This file is part of RapidDisk. RapidDisk is free software: you can redistribute it and/or modify@n 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. RapidDisk 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 RapidDisk. If not, see . SPDX-License-Identifier: GPL-2.0-or-later @endverbatim * @author Petros Koutoupis \ * @author Matteo Tenca \ * @version 9.1.0 * @date 23 April 2023 */ #include "sys.h" #include "utils.h" #include "rdsk.h" #include struct VOLUME_PROFILE *volume_head = NULL; struct VOLUME_PROFILE *volume_end = NULL; #if !defined SERVER /** * It prints out the memory and block device information * * @param mem A pointer to a struct MEM_PROFILE. * @param volumes A pointer to the first element of a linked list of VOLUME_PROFILE structures. * * @return The function status result */ int resources_list(struct MEM_PROFILE *mem, struct VOLUME_PROFILE *volumes) { int num = 1; printf("List of memory usage:\n\n"); if (mem != NULL) { printf(" Memory total: %llu\n Memory free: %llu\n", mem->mem_total, mem->mem_free); } printf("\nList of block device(s):\n\n"); while (volumes != NULL) { printf(" Block Device %d:\n\tDevice: %s\n\tSize (MB): %llu\n" "\tVendor: %s\n\tModel: %s\n", num, volumes->device, ((volumes->size / 1024) / 1024), volumes->vendor, volumes->model); num++; volumes = volumes->next; } printf("\n"); return SUCCESS; } #endif /** * It scans the /sys/block directory for block devices and populates a linked list of struct VOLUME_PROFILE with the device * name, size, model, and vendor * * @param return_message This is a pointer to a string that will be populated with an error message if the function fails. * * @return A linked list of struct VOLUME_PROFILE. */ struct VOLUME_PROFILE *search_volumes_targets(char *return_message) { int rc, n = 0, i; char file[NAMELEN] = {0}, test[NAMELEN + 32] = {0}; struct dirent **list; struct VOLUME_PROFILE *volume = NULL; char *msg = NULL; volume_head = NULL; volume_end = NULL; if ((rc = scandir(SYS_BLOCK, &list, NULL, NULL)) < 0) { msg = "%s: scandir: %s"; print_error(msg, return_message, __func__, strerror(errno)); return NULL; } for (;n < rc; n++) { if ((strncmp(list[n]->d_name, "sd", 2) == SUCCESS) || \ (strncmp(list[n]->d_name, "nvme", 4) == SUCCESS) || \ (strncmp(list[n]->d_name, "pmem", 4) == SUCCESS)) { volume = (struct VOLUME_PROFILE *)calloc(1, sizeof(struct VOLUME_PROFILE)); if (volume == NULL) { msg = ERR_CALLOC; print_error(msg, return_message, __func__, strerror(errno)); list = clean_scandir(list, rc); free_linked_lists(NULL, NULL, volume_head); return NULL; } strcpy(volume->device, (char *)list[n]->d_name); sprintf(file, "%s/%s", SYS_BLOCK, list[n]->d_name); char *info_size = read_info(file, "size", return_message); if (info_size == NULL) { free(volume); volume = NULL; list = clean_scandir(list, rc); free_linked_lists(NULL, NULL, volume_head); return NULL; } volume->size = (BYTES_PER_SECTOR * strtoull(info_size, NULL, 10)); sprintf(file, "%s/%s/device", SYS_BLOCK, list[n]->d_name); sprintf(test, "%s/model", file); if (access(test, F_OK) != INVALID_VALUE) { char *info_model = read_info(file, "model", return_message); if (info_model == NULL) { free(volume); volume = NULL; list = clean_scandir(list, rc); free_linked_lists(NULL, NULL, volume_head); return NULL; } sprintf(volume->model, "%s", info_model); } else sprintf(volume->model, "UNAVAILABLE"); /* trim whitespace of model string */ for (i = 0; i < strlen(volume->model); i++) { if ((strncmp(volume->model + i, " ", 1) == SUCCESS) || (strncmp(volume->model + i, "\n", 1) == SUCCESS)) volume->model[i] = '\0'; } sprintf(test, "%s/vendor", file); if (access(test, F_OK) != INVALID_VALUE) { char *info_vendor = read_info(file, "vendor", return_message); if (info_vendor == NULL) { free(volume); volume = NULL; list = clean_scandir(list, rc); free_linked_lists(NULL, NULL, volume_head); return NULL; } sprintf(volume->vendor, "%s", info_vendor); } else sprintf(volume->vendor, "UNAVAILABLE"); /* trim whitespace of vendor string */ for (i = 0; i < strlen(volume->vendor); i++) { if ((strncmp(volume->vendor+ i, " ", 1) == SUCCESS) || (strncmp(volume->vendor + i, "\n", 1) == SUCCESS)) volume->vendor[i] = '\0'; } if (volume_head == NULL) volume_head = volume; else volume_end->next = volume; volume_end = volume; volume->next = NULL; } } list = clean_scandir(list, rc); return volume_head; } /** * It retrieves the total and free memory of the system and stores it in the struct MEM_PROFILE * * @param mem A pointer to a struct MEM_PROFILE. * @param return_message A pointer to a string that will be populated with an error message if the function fails. * * @return The function status result. */ int get_memory_usage(struct MEM_PROFILE *mem, char *return_message) { struct sysinfo si; if (sysinfo(&si) < 0) { char *msg = "%s (%d): Unable to retrieve memory usage."; print_error(msg, return_message, __func__, __LINE__); return INVALID_VALUE; } mem->mem_total = si.totalram; mem->mem_free = si.freeram; return SUCCESS; } rapiddisk-9.1.0/src/sys.h000066400000000000000000000025431442147535600152700ustar00rootroot00000000000000/** * @file sys.h * @brief System-related function definitions * @details This header file defines system-related functions * @copyright @verbatim Copyright © 2011 - 2023 Petros Koutoupis All rights reserved. This file is part of RapidDisk. RapidDisk is free software: you can redistribute it and/or modify@n 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. RapidDisk 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 RapidDisk. If not, see . SPDX-License-Identifier: GPL-2.0-or-later @endverbatim * @author Petros Koutoupis \ * @author Matteo Tenca \ * @version 9.1.0 * @date 23 April 2023 */ #ifndef SYS_H #define SYS_H #include "common.h" #ifndef SERVER int resources_list(struct MEM_PROFILE *mem_profile, struct VOLUME_PROFILE *); #endif int get_memory_usage(struct MEM_PROFILE *mem_profile, char *return_message); struct VOLUME_PROFILE *search_volumes_targets(char *return_message); #endif //SYS_H rapiddisk-9.1.0/src/utils.c000066400000000000000000000262761442147535600156160ustar00rootroot00000000000000/** * @file utils.c * @brief Utility function definitions * @details This file contains all the utility functions * @copyright @verbatim Copyright © 2011 - 2023 Petros Koutoupis All rights reserved. This file is part of RapidDisk. RapidDisk is free software: you can redistribute it and/or modify@n 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. RapidDisk 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 RapidDisk. If not, see . SPDX-License-Identifier: GPL-2.0-or-later @endverbatim * @author Petros Koutoupis \ * @author Matteo Tenca \ * @version 9.1.0 * @date 23 April 2023 */ #include "utils.h" #include "json.h" #define PCRE2_CODE_UNIT_WIDTH 8 #include /** * Helper funtion for free_linked_lists() * @param head linked list to free */ void clean_rc(RC_PROFILE *head) { /* if there is only one item in the list, remove it */ if (head->next == NULL) { if (head != NULL) free(head); head = NULL; } else { /* get to the second to last node in the list */ struct RC_PROFILE *current = head; while (current->next->next != NULL) { current = current->next; } /* now current points to the second to last item of the list, so let's remove current->next */ if (current->next != NULL) { free(current->next); current->next = NULL; } clean_rc(head); } } /** * Helper funtion for free_linked_lists() * @param head linked list to free */ void clean_rd(RD_PROFILE *head) { /* if there is only one item in the list, remove it */ if (head->next == NULL) { if (head != NULL) free(head); head = NULL; } else { /* get to the second to last node in the list */ struct RD_PROFILE *current = head; while (current->next->next != NULL) { current = current->next; } /* now current points to the second to last item of the list, so let's remove current->next */ if (current->next != NULL) { free(current->next); current->next = NULL; } clean_rd(head); } } /** * Helper funtion for free_linked_lists() * @param head linked list to free */ void clean_vp(VOLUME_PROFILE *head) { /* if there is only one item in the list, remove it */ if (head->next == NULL) { if (head != NULL) free(head); head = NULL; } else { /* get to the second to last node in the list */ struct VOLUME_PROFILE *current = head; while (current->next->next != NULL) { current = current->next; } /* now current points to the second to last item of the list, so let's remove current->next */ if (current->next != NULL) { free(current->next); current->next = NULL; } clean_vp(head); } } /** * It frees the memory allocated to the linked lists * * @param rc_head The head of the linked list of RC_PROFILE structs. * @param rd_head The head of the linked list of RD_PROFILE structs. * @param vp_head The head of the linked list of VOLUME_PROFILE structs. */ void free_linked_lists(RC_PROFILE *rc_head, RD_PROFILE *rd_head, VOLUME_PROFILE *vp_head) { if (rc_head != NULL) { clean_rc(rc_head); } if (rd_head != NULL) { clean_rd(rd_head); } if (vp_head != NULL) { clean_vp(vp_head); } } void clean_ports(NVMET_PORTS *head) { /* if there is only one item in the list, remove it */ if (head->next == NULL) { if (head != NULL) free(head); head = NULL; } else { /* get to the second to last node in the list */ struct NVMET_PORTS *current = head; while (current->next->next != NULL) { current = current->next; } /* now current points to the second to last item of the list, so let's remove current->next */ if (current->next != NULL) { free(current->next); current->next = NULL; } clean_ports(head); } } void clean_hosts(NVMET_ALLOWED_HOST *head) { /* if there is only one item in the list, remove it */ if (head->next == NULL) { if (head != NULL) free(head); head = NULL; } else { /* get to the second to last node in the list */ struct NVMET_ALLOWED_HOST *current = head; while (current->next->next != NULL) { current = current->next; } /* now current points to the second to last item of the list, so let's remove current->next */ if (current->next != NULL) { free(current->next); current->next = NULL; } clean_hosts(head); } } void clean_nvmet(NVMET_PROFILE *head) { /* if there is only one item in the list, remove it */ if (head->next == NULL) { if (head->allowed_hosts != NULL) { clean_hosts(head->allowed_hosts); head->allowed_hosts = NULL; } if (head->assigned_ports != NULL) { clean_ports(head->assigned_ports); head->assigned_ports = NULL; } if (head != NULL) free(head); head = NULL; } else { /* get to the second to last node in the list */ struct NVMET_PROFILE *current = head; while (current->next->next != NULL) { current = current->next; } if (current->next->allowed_hosts != NULL) { clean_hosts(current->next->allowed_hosts); current->next->allowed_hosts = NULL; } if (current->next->assigned_ports != NULL) { clean_ports(current->next->assigned_ports); current->next->assigned_ports = NULL; } /* now current points to the second to last item of the list, so let's remove current->next */ if (current->next != NULL) { free(current->next); current->next = NULL; } clean_nvmet(head); } } /** * This function frees the memory allocated for the linked lists of NVMET ports and NVMET profiles * * @param ports_head This is the head of the linked list of NVMET_PORTS. * @param nvmet_head This is the head of the linked list that contains the NVMET profile information. */ void free_nvmet_linked_lists(struct NVMET_PORTS *ports_head, struct NVMET_PROFILE *nvmet_head) { if (ports_head != NULL) { clean_ports(ports_head); } if (nvmet_head != NULL) { clean_nvmet(nvmet_head); } } /** * Helper function to replace matches of regular expression with replacement in the subject string. * @param re regular expression * @param replacement replacement string * @param subject haystack * @param result buffer to write the result string to * @param pcre2_result_len length of the buffer * @return 0 on success, -1 on error, upon error result will the contain the error message */ int preg_replace(const char *re, char *replacement, char *subject, char *result, size_t pcre2_result_len) { int rc, errorcode, res = SUCCESS; PCRE2_SIZE erroroffset; PCRE2_UCHAR reg_error[4096] = {0}; PCRE2_SPTR pcre2_re = (PCRE2_SPTR) re; pcre2_code *compiled_re = pcre2_compile(pcre2_re, PCRE2_ZERO_TERMINATED, 0, &errorcode, &erroroffset, NULL); if (compiled_re) { PCRE2_UCHAR *pcre2_result = (PCRE2_UCHAR *) result; PCRE2_SPTR pcre2_subject = (PCRE2_SPTR) subject; size_t subject_length = strlen(subject); PCRE2_SPTR pcre2_replacement = (PCRE2_SPTR) replacement; size_t replacement_length = strlen(replacement); rc = pcre2_substitute( compiled_re, pcre2_subject, subject_length, 0, PCRE2_SUBSTITUTE_GLOBAL, NULL, NULL, pcre2_replacement, replacement_length, pcre2_result, &pcre2_result_len ); if (rc < SUCCESS) { // Syntax error in the replacement string pcre2_get_error_message(rc, reg_error, sizeof(reg_error)); sprintf(result, "Error during replace: '%s'.", reg_error); res = INVALID_VALUE; } pcre2_code_free(compiled_re); } else { // Syntax error in the regular expression at erroroffset pcre2_get_error_message(errorcode, reg_error, sizeof(reg_error)); sprintf(result, "Error compiling regexp at offset #%ld: '%s'.", erroroffset, reg_error); res = INVALID_VALUE; } return res; } /** * It takes a string and a message, and returns a string that contains the message prefixed with the string * * @param dest The destination string. * @param msg The message to be displayed. * * @return A pointer to the destination string. */ char *verbose_msg(char *dest, char *msg) { strcpy(dest, "%s: "); strcat(dest, msg); strcat(dest, "\n"); return dest; } /** * Splits a string using the delimiter and put pieces in array. * @param input_string string to split * @param output_arr array of pointer to string parts * @param delim delimiter * @return the index of the last element in output_arr */ int split(char *input_string, char **output_arr, char *delim) { char *temp; int i; temp = strtok(input_string, delim); for(i = 0; temp != NULL; i++) { output_arr[i] = temp; temp = strtok(NULL, delim); } i--; return i; } /* * Return codes: * 0 - All RapidDisk modules inserted * 1 - All RapidDisk and dm-writecache modules inserted * <0 - One or more RapidDisk modules are not inserted */ /** * Check for needed modules to be loaded. * @return 0 - All RapidDisk modules inserted, 1 - All RapidDisk and dm-writecache modules inserted, \<0 - One or more RapidDisk modules are not inserted */ int check_loaded_modules(void) { int rc = INVALID_VALUE, n, i; struct dirent **list; if (access(SYS_RDSK, F_OK) == INVALID_VALUE) { #ifndef SERVER fprintf(stderr, "Please ensure that the RapidDisk module is loaded and retry.\n"); #endif return -EPERM; } /* Check for rapiddisk */ if ((i = scandir(SYS_MODULE, &list, NULL, NULL)) < 0) { #ifndef SERVER fprintf(stderr, "%s: scandir: %s\n", __func__, strerror(errno)); #endif return -ENOENT; } /* Check for rapiddisk-cache */ for (n = 0; n < i; n++) { if (strcmp(list[n]->d_name, "rapiddisk_cache") == SUCCESS) { rc = SUCCESS; break; } } if (rc != SUCCESS) { #ifndef SERVER fprintf(stderr, "Please ensure that the RapidDisk-Cache module is loaded and retry.\n"); #endif list = clean_scandir(list, i); return rc; } /* Check for dm-writecach */ for (n = 0; n < i; n++) { if (strcmp(list[n]->d_name, "dm_writecache") == SUCCESS) { rc = 1; } } list = clean_scandir(list, i); return rc; } /** * Free a scandir() result struct * @param scanlist the result of a scandir call to be freed * @param num the number of entry in the scandir result * @return always NULL */ struct dirent **clean_scandir(struct dirent **scanlist, int num) { if (scanlist != NULL) { while (num--) { if (scanlist[num] != NULL) { free(scanlist[num]); scanlist[num] = NULL; } } free(scanlist); } return NULL; } /** * It prints a message to the screen * * @param ret_value The return value * @param message The message to print * @param json_flag TRUE if you want to print the message in JSON format, FALSE if you want to print the message in plain * text format. */ void print_message(int ret_value, char *message, bool json_flag) { if (json_flag == TRUE) { json_status_return(ret_value, message, NULL, FALSE); } else { printf("%s\n", message); } } /** * If the file name starts with a dot, return false. Otherwise, return true. * * @param list This is the directory entry that is being passed to the function. * * @return the number of files in the directory. */ int scandir_filter_no_dot(const struct dirent *list) { if (strncmp(list->d_name, ".", 1) == SUCCESS) { return FALSE; } return TRUE; } rapiddisk-9.1.0/src/utils.h000066400000000000000000000033271442147535600156130ustar00rootroot00000000000000/** * @file utils.h * @brief Utility functions declarations * @details This header file defines some utility functions * @copyright @verbatim Copyright © 2011 - 2023 Petros Koutoupis All rights reserved. This file is part of RapidDisk. RapidDisk is free software: you can redistribute it and/or modify@n 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. RapidDisk 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 RapidDisk. If not, see . SPDX-License-Identifier: GPL-2.0-or-later @endverbatim * @author Petros Koutoupis \ * @author Matteo Tenca \ * @version 9.1.0 * @date 23 April 2023 */ #ifndef UTILS_H #define UTILS_H #include "common.h" int preg_replace(const char *re, char *replacement, char *subject, char *result, size_t pcre2_result_len); int split(char* input_string, char** output_arr, char* delim); int scandir_filter_no_dot(const struct dirent *list); void free_linked_lists(RC_PROFILE *rc_head, RD_PROFILE *rd_head, VOLUME_PROFILE *vp_head); void free_nvmet_linked_lists(struct NVMET_PORTS *ports_head, struct NVMET_PROFILE *nvmet_head); struct dirent **clean_scandir(struct dirent **scanlist, int num); int check_loaded_modules(void); void print_message(int ret_value, char *message, bool json_flag); char *verbose_msg(char *dest, char *msg); #endif //UTILS_H rapiddisk-9.1.0/test/000077500000000000000000000000001442147535600144655ustar00rootroot00000000000000rapiddisk-9.1.0/test/Makefile000066400000000000000000000023731442147535600161320ustar00rootroot00000000000000# Copyright © 2016 - 2023 Petros Koutoupis # All rights reserved. # # 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 . # # SPDX-License-Identifier: GPL-2.0-or-later ifeq ($(CC),) CC := gcc -Werror endif BIN = rxflush rxio rxioctl rxro .PHONY: all all: $(BIN) .PHONY: run-test run-test: all ./test-leaks.sh ./cache-test.sh .PHONY: clean clean: rm -f $(BIN) .PHONY: install install-strip uninstall debug tools-strip tools-debug tools-uninstall tools-install-strip tools-install clean-tools tools dkms-uninstall dkms-install install install-strip uninstall debug tools-strip tools-debug tools-uninstall tools-install-strip tools-install clean-tools tools dkms-uninstall dkms-install: rapiddisk-9.1.0/test/README.md000066400000000000000000000004721442147535600157470ustar00rootroot00000000000000This is just a small collection of very quick tests to run on a RapidDisk attached RAM disk raw block device. To compile: make To execute in place: ```console # ./rxio # ./rxioctl # ./rxflush ``` Note that they will only test the node named /dev/rd0. You can change this in the code if necessary and recompile. rapiddisk-9.1.0/test/cache-test.sh000077500000000000000000000031251442147535600170450ustar00rootroot00000000000000#!/bin/bash who="$(whoami)" if [ "$who" != "root" ] ; then echo "Please run as root!" exit 0 fi if [ ! "$BASH_VERSION" ] ; then exec /bin/bash "$0" "$@" fi PATH=$PATH:$(pwd) RD=rd0 function cleanup() { ../src/rapiddisk -u rc-wa_loop7 ../src/rapiddisk -d ${RD} losetup -d /dev/loop7 rm -f /tmp/test1 } function createLoopbackDevices() { dd if=/dev/zero of=/tmp/test1 bs=1M count=256 losetup /dev/loop7 /tmp/test1 } function removeLoopbackDevices() { losetup -d /dev/loop7 rm -f /tmp/test1 } function createCacheVolumes() { RD=`../src/rapiddisk -a 64 -g|cut -d' ' -f3` ../src/rapiddisk -m ${RD} -b /dev/loop7 RETVAL=$? if [ ${RETVAL} -ne 0 ]; then cleanup exit ${RETVAL} fi } function removeCacheVolumes() { ../src/rapiddisk -u rc-wt_loop7 ../src/rapiddisk -d ${RD} RETVAL=$? if [ ${RETVAL} -ne 0 ]; then cleanup exit ${RETVAL} fi } function statCacheVolumes() { ../src/rapiddisk -s rc-wt_loop7 RETVAL=$? if [ ${RETVAL} -ne 0 ]; then cleanup exit ${RETVAL} fi } # # # COUNT=`lsmod|grep rapiddisk|wc -l` if [ ${COUNT} -ne 2 ]; then insmod ../module/rapiddisk.ko max_sectors=2048 nr_requests=1024 2>&1 >/dev/null insmod ../module/rapiddisk-cache.ko 2>&1 >/dev/null fi echo "Create Loopback Devices..." createLoopbackDevices echo "Create Cache Volumes..." createCacheVolumes ls -l /dev/mapper|grep "rc-wa_" echo "Stat Cache Volumes..." statCacheVolumes echo "Remove Cache Volumes..." removeCacheVolumes ls -l /dev/mapper|grep "rc-wa_" echo "Remove Loopback Devices..." removeLoopbackDevices rmmod rapiddisk.ko rmmod rapiddisk-cache.ko exit 0 rapiddisk-9.1.0/test/rxflush.c000066400000000000000000000024171442147535600163300ustar00rootroot00000000000000/* rxflush.c */ /** Copyright © 2016 - 2023 Petros Koutoupis ** All rights reserved. ** ** 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 . ** ** SPDX-License-Identifier: GPL-2.0-or-later **/ #include #include #include #include #include #include #include #include #include #define IOCTL_RD_BLKFLSBUF 0x0531 int main () { int fd, rc; if ((fd = open("/dev/rd0", O_WRONLY)) < 0) { printf("%s\n", strerror(errno)); return errno; } rc = ioctl(fd, IOCTL_RD_BLKFLSBUF, 0); printf("Sent ioctl() IOCTL_RD_BLKFLSBUF and got return: %d\n", rc); close (fd); return rc; } rapiddisk-9.1.0/test/rxio.c000066400000000000000000000040261442147535600156140ustar00rootroot00000000000000/* rxio.c */ /** Copyright © 2016 - 2023 Petros Koutoupis ** All rights reserved. ** ** 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 . ** ** SPDX-License-Identifier: GPL-2.0-or-later **/ #include #include #include #include #include #include #include #include #include #include #define XFER_SIZE 4096 #define BYTES_PER_BLOCK 512 int main () { int fd; unsigned long long size; char *buf; off_t offset = 0; buf = (char *)malloc(XFER_SIZE); memset(buf, 0x2F, XFER_SIZE); if ((fd = open("/dev/rd0", O_RDWR, O_NONBLOCK)) < 0) { printf("%s\n", strerror(errno)); return errno; } if ((ioctl(fd, BLKGETSIZE, &size)) == -1) { printf("%s\n", strerror(errno)); return errno; } else { printf("total block count: %llu\n", size); printf("total bytes count: %llu\n", (size * BYTES_PER_BLOCK)); } if ((write (fd, buf, XFER_SIZE)) <= 0) { printf("%s\n", strerror(errno)); return errno; } printf ("wrote %d bytes at offset %lu\n", XFER_SIZE, offset); offset = (offset + 65536); if ((lseek (fd, offset, SEEK_SET)) != offset) { printf("%s\n", strerror(errno)); return errno; } printf ("seeked to offset %lu\n", offset); if ((read(fd, buf, XFER_SIZE)) <= 0) { printf("%s\n", strerror(errno)); return errno; } printf ("read %d bytes at offset %lu\n", XFER_SIZE, offset); close (fd); return 0; } rapiddisk-9.1.0/test/rxioctl.c000066400000000000000000000033271442147535600163220ustar00rootroot00000000000000/* rxioctl.c */ /** Copyright © 2016 - 2023 Petros Koutoupis ** All rights reserved. ** ** 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 . ** ** SPDX-License-Identifier: GPL-2.0-or-later **/ #include #include #include #include #include #include #include #include #include #include #define IOCTL_RD_GET_STATS 0x0529 #define IOCTL_RD_GET_USAGE 0x0530 int main () { int fd, max_sectors; unsigned long long max_usage; if ((fd = open("/dev/rd0", O_WRONLY)) < 0) { printf("%s\n", strerror(errno)); return errno; } if (ioctl(fd, IOCTL_RD_GET_STATS, &max_sectors) == -1) { printf("%s\n", strerror(errno)); return errno; } else { printf("max sectors allocated: %d\n", max_sectors); } close (fd); if ((fd = open("/dev/rd0", O_WRONLY)) < 0) { printf("%s\n", strerror(errno)); return errno; } if (ioctl(fd, IOCTL_RD_GET_USAGE, &max_usage) == -1) { printf("%s\n", strerror(errno)); return errno; } else { printf("max pages allocated: %llu\n", max_usage); } close (fd); return 0; } rapiddisk-9.1.0/test/rxro.c000066400000000000000000000050041442147535600156220ustar00rootroot00000000000000/* rxro.c */ /** Copyright © 2016 - 2023 Petros Koutoupis ** All rights reserved. ** ** 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 . ** ** SPDX-License-Identifier: GPL-2.0-or-later **/ #include #include #include #include #include #include #include #include #include #include #define XFER_SIZE 4096 #define BYTES_PER_BLOCK 512 int main () { int fd, state = 1; char *buf; off_t offset = 0; buf = (char *)malloc(XFER_SIZE); memset(buf, 0x2F, XFER_SIZE); if((fd = open("/dev/rd0", O_RDWR, O_NONBLOCK)) < 0){ printf("%s\n", strerror(errno)); return errno; } /* You get the same result with: "blockdev --setro /dev/rd0" */ if ((ioctl(fd, BLKROSET, &state)) == -1) { printf("%s\n", strerror(errno)); return errno; } else { printf("device rd0 set to read-only\n"); } state = -1; if((ioctl(fd, BLKROGET, &state)) == -1){ printf("%s\n", strerror(errno)); return errno; } else { printf("Verifying lock state on device rd0: %d\n", state); } if ((write (fd, buf, XFER_SIZE)) <= 0) printf("Write: %s\n", strerror(errno)); offset = (offset + 65536); if ((lseek (fd, offset, SEEK_SET)) != offset) { printf("%s\n", strerror(errno)); return errno; } printf ("seeked to offset %lu\n", offset); if((read(fd, buf, XFER_SIZE)) <= 0){ printf("%s\n", strerror(errno)); return errno; } printf ("read %d bytes at offset %lu\n", XFER_SIZE, offset); state = 0; /* You get the same result with: "blockdev --setrw /dev/rd0" */ if ((ioctl(fd, BLKROSET, &state)) == -1) { printf("%s\n", strerror(errno)); return errno; } else { printf("device rd0 set to read-write\n"); } state = -1; if((ioctl(fd, BLKROGET, &state)) == -1){ printf("%s\n", strerror(errno)); return errno; } else { printf("Verifying lock state on device rd0: %d\n", state); } close (fd); return 0; } rapiddisk-9.1.0/test/test-leaks.sh000077500000000000000000000061221442147535600171010ustar00rootroot00000000000000#!/bin/bash who="$(whoami)" if [ "$who" != "root" ] ; then echo "Please run as root!" exit 0 fi if [ ! "$BASH_VERSION" ] ; then exec /bin/bash "$0" "$@" fi PATH=$PATH:$(pwd) RD=rd0 j=tee jj=/dev/null if [ "$1" == "json" ] ; then json=" -j -g " if which jqa; then j=jq jj= elif which json_pp; then j=json_pp jj= else json= fi fi if which >/dev/null 2>&1 valgrind ; then cmd="valgrind --sim-hints=lax-ioctls --show-leak-kinds=all --leak-check=full --track-origins=yes -s ../src/rapiddisk_debug" else cmd="../src/rapiddisk_debug" fi function cont() { echo -e "\npress a key..." read -r -n 1 -s q } function cleanup() { ../src/rapiddisk_debug -u rc-wa_loop7 >/dev/null ../src/rapiddisk_debug -d ${RD} >/dev/null [ -f /dev/loop7 ] && losetup -d /dev/loop7 >/dev/null rm -f /tmp/test1 >/dev/null rmmod >/dev/null 2>&1 rapiddisk.ko rmmod >/dev/null 2>&1 rapiddisk-cache.ko } function createLoopbackDevices() { dd if=/dev/zero of=/tmp/test1 bs=1M count=256 >/dev/null 2>&1 losetup /dev/loop7 /tmp/test1 >/dev/null 2>&1 } function removeLoopbackDevices() { losetup -d /dev/loop7 >/dev/null 2>&1 rm -f /tmp/test1 >/dev/null 2>&1 } function createCacheVolumes() { O=$($cmd -g -a 64 $json) RETVAL=$? if [ -n "$json" ] ; then RD=$(echo "$O"|cut -d' ' -f6) else RD=$(echo "$O"|cut -d' ' -f3) fi echo -e >/dev/stderr "\n$O" if [ ${RETVAL} -ne 0 ]; then cleanup exit ${RETVAL} fi echo "$RD" } function mapCacheVolumes() { RD="$1" o=$($cmd -m "${RD}" -b /dev/loop7 $json) echo "$o" | $j $jj RETVAL=$? if [ ${RETVAL} -ne 0 ]; then cleanup exit ${RETVAL} fi } function unampCacheVolumes() { o=$($cmd -u rc-wt_loop7 $json) echo "$o" | $j $jj RETVAL=$? if [ ${RETVAL} -ne 0 ]; then cleanup exit ${RETVAL} fi } function removeCacheVolumes() { o=$($cmd -d "${RD}" $json) echo "$o" | $j $jj RETVAL=$? if [ ${RETVAL} -ne 0 ]; then cleanup exit ${RETVAL} fi } function statCacheVolumes() { o=$($cmd -s rc-wt_loop7 $json) echo "$o" | $j $jj RETVAL=$? if [ ${RETVAL} -ne 0 ]; then cleanup exit ${RETVAL} fi } function listVolumes() { o=$($cmd -l $json) echo "$o" | $j $jj RETVAL=$? # if [ ${RETVAL} -ne 0 ]; then # cleanup # exit ${RETVAL} # fi } # # # COUNT=$(lsmod|grep -c rapiddisk) if [ "${COUNT}" -ne 2 ]; then insmod ../module/rapiddisk.ko max_sectors=2048 nr_requests=1024 >/dev/null 2>&1 insmod ../module/rapiddisk-cache.ko >/dev/null 2>&1 fi echo "Create Loopback Devices..." createLoopbackDevices echo -e "\nCreate Ramdisk..." ramdisk=$(createCacheVolumes) cont echo -e "\nMap Ramdisk..." mapCacheVolumes "$ramdisk" cont echo -e "\nRapiddisk -l..." listVolumes cont echo -e "\nListing /dev/mapper..." ls -l /dev/mapper/*rc-* cont echo -e "\nStat Cache Volumes..." statCacheVolumes cont echo -e "\nUnmapCacheVolumes..." unampCacheVolumes cont echo -e "\nRemove Ramdisk..." removeCacheVolumes cont echo -e "\nRapiddisk -l..." listVolumes cont echo -e "\nListing /dev/mapper..." ls -l /dev/mapper/*rc-* cont echo -e "\nRemove Loopback Devices..." removeLoopbackDevices cleanup exit 0