├── .clang-format ├── .fmf └── version ├── .github ├── CODEOWNERS └── workflows │ └── pre-commit.yml ├── .packit.yaml ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── bin └── initoverlayfs-install ├── build-scripts ├── create-spec.sh └── version.sh ├── config-parser.h ├── docs └── devel │ └── LISTING-FILES-INITRAMFS.md ├── initoverlayfs.c ├── initoverlayfs.h ├── initoverlayfs.spec.in ├── integration ├── plans │ └── tier-0.fmf └── tests │ └── boot-validation │ ├── main.fmf │ └── test.sh ├── lib ├── dracut │ └── modules.d │ │ └── 81initoverlayfs │ │ └── module-setup.sh ├── kernel │ └── install.d │ │ └── 94-initoverlayfs.install └── systemd │ └── system │ ├── pre-initoverlayfs-switch-root.service │ ├── pre-initoverlayfs.service │ └── pre-initoverlayfs.target ├── scripts ├── Containerfile ├── build-bin-only.sh ├── build-bls-parser.sh ├── build.sh ├── gnuplot.sh ├── install.sh ├── install_perf.sh └── test.sh ├── scsi_probe.note.md └── scsi_probe ├── Makefile ├── scsi_probe.c ├── scsi_probe.h ├── testfiles ├── cmd1 ├── cmd2 ├── host0scan └── host2scan ├── tests ├── fetch_kernel_cmdline.c ├── parse_kernel_cmdline.c └── trigger_scan.c └── tests_res └── placeholder /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: chromium 2 | 3 | -------------------------------------------------------------------------------- /.fmf/version: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # See github docs for more info on the syntax: 2 | # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners 3 | 4 | # Default to all maintainers if nothing more specific matches 5 | * @ericcurtin @rhatdan @dougsland @masneyb 6 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: pre-commit-checks 2 | 3 | on: 4 | pull_request 5 | 6 | jobs: 7 | pre_commit: 8 | name: Check code with precommit 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Install Python 13 | uses: actions/setup-python@v4 14 | with: 15 | python-version: '3.10' 16 | 17 | - name: Checkout code 18 | uses: actions/checkout@v3 19 | 20 | - name: Setup environment 21 | run: | 22 | pip install pre-commit 23 | pre-commit install 24 | 25 | - name: Run precommit check 26 | run: | 27 | pre-commit run --file bin/* --files lib/dracut/modules.d/81initoverlayfs/* --files scripts/* 28 | -------------------------------------------------------------------------------- /.packit.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # See the documentation for more information: 3 | # https://packit.dev/docs/configuration/ 4 | 5 | upstream_project_url: https://github.com/containers/initoverlayfs 6 | issue_repository: https://github.com/containers/initoverlayfs 7 | specfile_path: initoverlayfs.spec 8 | upstream_package_name: initoverlayfs 9 | 10 | update_release: false 11 | 12 | srpm_build_deps: 13 | - make 14 | 15 | actions: 16 | post-upstream-clone: 17 | - bash -c 'git config --global --add safe.directory $(pwd)' 18 | - bash -c './build-scripts/create-spec.sh' 19 | 20 | get-current-version: 21 | - bash -c './build-scripts/version.sh' 22 | 23 | jobs: 24 | - job: copr_build 25 | trigger: pull_request 26 | # x86_64 is assumed by default 27 | targets: 28 | - fedora-39-x86_64 29 | - centos-stream-9 30 | 31 | - job: tests 32 | trigger: pull_request 33 | identifier: integration-tiers 34 | tmt_plan: /integration/plans/tier-0 35 | targets: 36 | - fedora-39-x86_64 37 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | 4 | repos: 5 | - repo: https://github.com/pre-commit/pre-commit-hooks 6 | rev: v4.4.0 7 | hooks: 8 | - id: trailing-whitespace 9 | 10 | - repo: https://github.com/shellcheck-py/shellcheck-py 11 | rev: v0.9.0.2 12 | hooks: 13 | - id: shellcheck 14 | args: ["-x", "-a"] 15 | 16 | - repo: https://github.com/igorshubovych/markdownlint-cli 17 | rev: v0.34.0 18 | hooks: 19 | - id: markdownlint 20 | args: ["--disable", "MD013", "MD041", "--"] 21 | # MD041 disabled because pandoc requires first line to be a metadata block 22 | # while markdownlint want it to be a level 1 header 23 | 24 | - repo: https://github.com/PyCQA/flake8 25 | rev: 6.0.0 26 | hooks: 27 | - id: flake8 28 | additional_dependencies: [ 29 | 'flake8-blind-except', 30 | 'flake8-docstrings', 31 | 'flake8-bugbear', 32 | 'flake8-comprehensions', 33 | 'flake8-docstrings', 34 | 'flake8-implicit-str-concat', 35 | 'pydocstyle>=5.0.0', 36 | ] 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # initoverlayfs 2 | 3 | A scalable solution for initial filesystems focused on minimal resource usage, suitable for both critical and non-critical environments. 4 | 5 | - [What is initoverlayfs?](#what-is-initoverlayfs) 6 | - [Why use initoverlayfs?](#why-use-initoverlayfs) 7 | - [Dependencies](#dependencies) 8 | - [Installation](#installation) 9 | * [Step 1 - Deploy the software](#step-1---deploy-the-software) 10 | * [Step 2 - Run initoverlayfs-install](#step-2---run-initoverlayfs-install) 11 | * [Step 3 - Reboot to test](#step-3---reboot-to-test) 12 | * [Step 4 - Validating the boot](#step-4---validating-the-boot) 13 | 14 | # What is initoverlayfs? 15 | 16 | initoverlayfs is a solution that uses transient overlays as an initial filesystem rather than soley an initramfs. If compression is used, it relies on transparent decompression, rather than upfront decompression. This results in more scalable, maintainable initial filesystems. 17 | 18 | Here we see a traditional boot sequence: 19 | 20 | ``` 21 | fw -> bootloader -> kernel -> initramfs ---------------------------------------> rootfs 22 | 23 | fw -> bootloader -> kernel -> init ---------------------------------------------------> 24 | ``` 25 | 26 | Here is the boot sequence with initoverlayfs integrated, the mini-initramfs contains just enough to get storage drivers loaded and storage devices initialized. storage-init is a process that is not designed to replace init, it does just enough to initialize storage, switch-root's to initoverlayfs and continues as normal. 27 | 28 | ``` 29 | fw -> bootloader -> kernel -> mini-initramfs --------------> initoverlayfs -> rootfs 30 | 31 | fw -> bootloader -> kernel -> init ------------------------------------------------> 32 | | 33 | `-initoverlayfs-+ 34 | ``` 35 | 36 | # Why use initoverlayfs? 37 | 38 | An initramfs (Initial RAM File System) image is a fundamental component in preparing Linux systems during the boot process, preceding the initiation of the init process. 39 | 40 | Typically, generating an initramfs involves assembling all available kernel modules and necessary files to boot and support any hardware using the specific Linux kernel version XYZ. This may also include some initialization that's not hardware specific, such as disk encryption, disk verification, early graphics, early camera input, ostree prepare root, etc. 41 | 42 | However, this conventional approach presents a significant challenge: loading such a voluminous image into memory during boot is time-consuming and can be problematic in critical scenarios where time to boot is critical. Edge devices commonly need to boot as quickly as possible such as healthcare, automotive and aviation. 43 | 44 | Conversely, the initoverlayfs approach proposes a solution: dividing the initramfs image into two parts, relying on transparent decompression rather than upfront decompression. 45 | 46 | This division entails segregating the initramfs image into two distinct components. 47 | 48 | The first component (initramfs) contains init, kernel modules, udev-rules and an initoverlayfs tool, responsible for setting up and mounting initoverlayfs. Then we switches to the second component (initoverlayfs), containing all additional kernel modules and essential files required to support the Linux boot process. 49 | 50 | This scalable approach moves a significant portion on the initial filesystem content to initoverlayfs which is more scalable as it does on-demand decompression. 51 | 52 | For illustration, consider a comparison of the sizes using dracut versus initoverlayfs: 53 | 54 | **Using dracut only**: 55 | ``` bash 56 | # dracut -f 57 | # du -sh /boot/initramfs-6.5.5-300.fc39.x86_64.img 58 | 36M /boot/initramfs-6.5.5-300.fc39.x86_64.img 59 | ``` 60 | 61 | **Using dracut + initoverlayfs**: 62 | ``` bash 63 | # /usr/bin/initoverlayfs-install -f 64 | # du -sh /boot/initramfs-6.5.5-300.fc39.x86_64.img 65 | 13M /boot/initramfs-6.5.5-300.fc39.x86_64.img 66 | ^^ <--- from 36M to 13M 67 | ``` 68 | 69 | The advantages of adopting the initoverlayfs approach are evident in the substantial reduction in the size of the initramfs image, thereby significantly expediting the boot process, making it especially appealing in resource-constrained and time-sensitive scenarios. 70 | 71 | This is a graphic comparing the boot time effect of increasing initramfs size vs the effect of increasing initoverlayfs size, initoverlayfs is only effected by bytes you use: 72 | 73 | ![initramfs-vs-initoverlayfs-scale](https://github.com/containers/initoverlayfs/assets/1694275/6f339016-7bcf-4129-af0e-a3f0be7c9be0) 74 | 75 | This is a graphic comparing the systemd start time using initramfs only vs using initramfs + initoverlayfs on Raspberry Pi 4 with NVMe drive over USB: 76 | 77 | ![image](https://github.com/containers/initoverlayfs/assets/1694275/f18db634-1c51-4ff7-9c68-423abee0fce4) 78 | 79 | # Dependencies 80 | 81 | - EROFS - Initoverlayfs uses erofs as the underlying filesystem. 82 | - dracut - As the initramfs and initoverlayfs composing tool. 83 | - systemd - As the init system. 84 | 85 | Note: none of the above dependencies are strictly needed, all the tools could be swapped out for other similar tools. 86 | 87 | # Installation 88 | 89 | ### Step 1 - Deploy the software 90 | 91 | Currently, RPM packages are available through the Copr Packages repository. 92 | 93 | ``` bash 94 | dnf copr enable -y @centos-automotive-sig/next 95 | dnf install initoverlayfs 96 | Copr repo for next owned by @centos-automotive-sig 2.4 kB/s | 3.3 kB 00:01 97 | Dependencies resolved. 98 | ============================================================================= 99 | Package Arch Version Repository Size 100 | ============================================================================= 101 | Installing: 102 | initoverlayfs x86_64 0.96-1.fc39 @commandline 22 k 103 | Installing dependencies: 104 | libdeflate x86_64 1.9-7.fc39 fedora 55 k 105 | Installing weak dependencies: 106 | erofs-utils x86_64 1.7.1-1.fc39 updates-testing 140 k 107 | 108 | Transaction Summary 109 | ============================================================================= 110 | Install 3 Packages 111 | 112 | Total size: 217 k 113 | Total download size: 195 k 114 | Installed size: 487 k 115 | Is this ok [y/N]: y 116 | 117 | # rpm -qa | grep -i initoverlayfs 118 | initoverlayfs-0.96-1.fc39.x86_64 119 | ``` 120 | 121 | **Note:** 122 | centos-stream-9 requires package from epel-release 123 | 124 | ``` 125 | dnf install -y epel-release 126 | ``` 127 | 128 | ### Step 2 - Run initoverlayfs-install 129 | Once the deployment is completed, the next step is to execute the /usr/bin/initoverlayfs-install tool. This tool is responsible for generating both the initramfs and initoverlayfs images, along with the essential initoverlayfs.conf configuration. 130 | 131 | ``` bash 132 | # /usr/bin/initoverlayfs-install -f 133 | 134 | initoverlayfs 135 | kernel-modules 136 | udev-rules 137 | dracut: Skipping udev rule: 40-redhat.rules 138 | dracut: Skipping udev rule: 50-firmware.rules 139 | dracut: Skipping udev rule: 50-udev.rules 140 | dracut: Skipping udev rule: 91-permissions.rules 141 | dracut: Skipping udev rule: 80-drivers-modprobe.rules 142 | dracut: *** Including modules done *** 143 | dracut: *** Installing kernel module dependencies *** 144 | dracut: *** Installing kernel module dependencies done *** 145 | dracut: *** Resolving executable dependencies *** 146 | dracut: *** Resolving executable dependencies done *** 147 | dracut: *** Hardlinking files *** 148 | dracut: Mode: real 149 | dracut: Method: sha256 150 | dracut: Files: 819 151 | dracut: Linked: 0 files 152 | dracut: Compared: 0 xattrs 153 | dracut: Compared: 77 files 154 | dracut: Saved: 0 B 155 | dracut: Duration: 0.007777 seconds 156 | dracut: *** Hardlinking files done *** 157 | dracut: *** Generating early-microcode cpio image *** 158 | dracut: *** Constructing AuthenticAMD.bin *** 159 | dracut: *** Constructing GenuineIntel.bin *** 160 | dracut: *** Store current command line parameters *** 161 | dracut: *** Stripping files *** 162 | dracut: *** Stripping files done *** 163 | dracut: *** Creating image file '/boot/initramfs-6.5.5-300.fc39.x86_64.img' *** 164 | dracut: Using auto-determined compression method 'pigz' 165 | dracut: *** Creating initramfs image file '/boot/initramfs-6.5.5-300.fc39.x86_64.img' done *** 166 | ``` 167 | 168 | Excellent! Now, let's proceed to compare the size of the newly generated initramfs image 169 | with the existing ones in the filesystem. 170 | 171 | ``` bash 172 | # uname -r 173 | 6.5.5-300.fc39.x86_64 174 | 175 | # du -sh /boot/init* 176 | 81M /boot/initramfs-0-rescue-285b2edb8ad94c7381215fd5720afd54.img 177 | 34M /boot/initramfs-6.4.12-200.fc38.x86_64.img 178 | 34M /boot/initramfs-6.5.5-200.fc38.x86_64.img 179 | 180 | 13M /boot/initramfs-6.5.5-300.fc39.x86_64.img <- first image to load (storage drivers only) 181 | 149M /boot/initoverlayfs-6.5.5-300.fc39.x86_64.img <- second image (extra kernel mods and files) 182 | ``` 183 | 184 | ### Step 3 - Reboot to test 185 | 186 | To load the new generated **initramfs and initoverlayfs** images a reboot of the system is necessary. 187 | ``` bash 188 | # reboot 189 | ``` 190 | 191 | ### Step 4 - Validating the boot 192 | 193 | To validate whether the new image has been successfully loaded after the reboot, you can execute the following journalctl command and search for the keyword initoverlayfs: 194 | 195 | ``` bash 196 | # journalctl -b -o short-monotonic | grep -i initoverlayfs 197 | [ 4.949129] fedora systemd[1]: Queued start job for default target pre-initoverlayfs.target. 198 | [ 5.526459] fedora systemd[1]: Starting pre-initoverlayfs.service - pre-initoverlayfs initialization... 199 | [ 9.179469] fedora initoverlayfs[193]: bootfs: {"UUID=1a3a6db4-a7c2-43e5-bed5-9385f26c68ff", "bootfs UUID=1a3a6db4-a7c2-43e5-bed5-9385f26c68ff"}, bootfstype: {"ext4", "bootfstype ext4"}, fs: {"(null)", "(null)"}, fstype: {"(null)", "(null)"} 200 | [ 9.179469] fedora initoverlayfs[193]: fork_execlp("udevadm") 201 | [ 9.179469] fedora initoverlayfs[193]: forked 199 fork_execlp 202 | [ 9.179469] fedora initoverlayfs[193]: mount("/boot", "/initoverlayfs/boot", "ext4", MS_MOVE, NULL) 2 (No such file or directory) 203 | [ 9.216158] fedora systemd[1]: Finished pre-initoverlayfs.service - pre-initoverlayfs initialization. 204 | [ 9.235546] fedora systemd[1]: Starting pre-initoverlayfs-switch-root.service - Switch Root pre-initoverlayfs... 205 | [ 12.207906] fedora systemd[1]: pre-initoverlayfs-switch-root.service: Deactivated successfully. 206 | [ 12.232609] fedora systemd[1]: Stopped pre-initoverlayfs-switch-root.service. 207 | [ 12.375125] fedora systemd[1]: pre-initoverlayfs.service: Deactivated successfully. 208 | [ 12.393613] fedora systemd[1]: Stopped pre-initoverlayfs.service. 209 | ``` 210 | 211 | That's fantastic news! The system is up and running smoothly. 212 | -------------------------------------------------------------------------------- /bin/initoverlayfs-install: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | # Constants with default value 6 | CAT="lz4cat" 7 | 8 | INITRAMFS_DIR="" 9 | INITOVERLAYFS_CONF="/etc/initoverlayfs.conf" 10 | INITRAMFS_DUMP_DIR="/var/lib/initoverlayfs" 11 | 12 | # Only erofs-based initoverlayfs supported, ext4, btrfs, xfs, etc. to be implemented" 13 | SUPPORTED_FILESYSTEM=("erofs") 14 | SKIPCPIO_BIN="/usr/lib/dracut/skipcpio" 15 | 16 | detect_path_initramfs() { 17 | paths_initramfs=( 18 | "/boot/initramfs-$kver.img" 19 | "/usr/lib/modules/$kver/initramfs.img" 20 | ) 21 | 22 | for path in "${paths_initramfs[@]}"; do 23 | if [[ -e "${path}" ]]; then 24 | INITRAMFS_DIR=$(dirname "${path}") 25 | return 26 | fi 27 | 28 | done 29 | 30 | # on first build, like in osbuild, there will be no prior initrd to detect 31 | INITRAMFS_DIR="/boot" 32 | } 33 | 34 | exec_erofs() { 35 | pushd "$INITRAMFS_DUMP_DIR" 36 | "$SKIPCPIO_BIN" "${INITRAMFS_DIR}/initramfs-$kver.img" | "$CAT" | cpio -ivd 37 | popd 38 | rm -f "${INITRAMFS_DIR}/initoverlayfs-$kver.img" 39 | mkfs.erofs $erofs_compression "${INITRAMFS_DIR}/initoverlayfs-$kver.img" ${INITRAMFS_DUMP_DIR} 40 | if false; then 41 | veritysetup format "${INITRAMFS_DIR}/initoverlayfs-$kver.img" "/etc/initoverlayfs-hash-$kver.img" 42 | fi 43 | } 44 | 45 | # Support for ext4 is currently under development. 46 | exec_ext4() { 47 | dd if=/dev/zero of="${INITRAMFS_DIR}"/initoverlayfs-"$kver".img bs=64M count=1 48 | dev=$(losetup -fP --show "${INITRAMFS_DIR}"/initoverlayfs-"$kver".img) 49 | mkfs.ext4 "${dev}" 50 | mount "${dev}" "${INITRAMFS_DUMP_DIR}" 51 | 52 | pushd "${INITRAMFS_DUMP_DIR}" || exit 53 | "${SKIPCPIO_BIN}" "${INITRAMFS_DIR}"/initramfs-"$kver".img | zstd -d --stdout | cpio -ivd 54 | sync 55 | popd || exit 56 | 57 | while ! umount "${INITRAMFS_DUMP_DIR}"; do 58 | sleep 1 59 | done 60 | 61 | losetup -d "${dev}" 62 | } 63 | 64 | detect_initramfs() { 65 | mkdir -p "${INITRAMFS_DUMP_DIR}" 66 | 67 | echo "Extracting initrd into initoverlayfs..." 68 | 69 | file_path="${INITRAMFS_DIR}/initramfs-$kver.img" 70 | skipcpio="/usr/lib/dracut/skipcpio" 71 | if $skipcpio $file_path | gzip -t - >/dev/null 2>&1; then 72 | CAT="zcat" 73 | elif $skipcpio $file_path | zstd -q -c -t - >/dev/null 2>&1; then 74 | CAT="zstd" 75 | elif $skipcpio $file_path | xzcat -t - >/dev/null 2>&1; then 76 | CAT="xzcat" 77 | elif $skipcpio $file_path | lz4cat -t - >/dev/null 2>&1; then 78 | CAT="lz4cat" 79 | elif $skipcpio $file_path | bzip2 -t - >/dev/null 2>&1; then 80 | CAT="bzcat" 81 | elif $skipcpio $file_path | lzop -t - >/dev/null 2>&1; then 82 | CAT="lzop" 83 | else 84 | CAT="cat" 85 | fi 86 | 87 | echo " - File path: ${file_path}" 88 | echo " - Decompressor: $CAT" 89 | } 90 | 91 | extract_initrd_into_initoverlayfs() { 92 | if command -v mkfs.erofs; then 93 | fstype="erofs" 94 | elif command -v mkfs.ext4; then 95 | fstype="ext4" 96 | else 97 | fstype="unsupported" 98 | fi 99 | 100 | case "${fstype}" in 101 | # Support for ext4 is currently under development. 102 | # *ext4*) 103 | # exec_ext4 104 | # ;; 105 | *erofs*) 106 | exec_erofs 107 | ;; 108 | *) 109 | echo -e "The detected filesytem: is ${fstype}." \ 110 | "Unfortunately it's not supported at moment." 111 | echo -e "Supported filesystems: ${SUPPORTED_FILESYSTEM[*]}" 112 | exit 1 113 | ;; 114 | esac 115 | 116 | rm -rf "$INITRAMFS_DUMP_DIR" & 117 | } 118 | 119 | # main() 120 | 121 | args="$*" 122 | initoverlayfs_init="false" 123 | while [[ $# -gt 0 ]]; do 124 | echo "$1" 125 | case $1 in 126 | --kver) 127 | kver="$2" 128 | shift 2 129 | ;; 130 | --initoverlayfs-init) 131 | initoverlayfs_init="true" 132 | args=$(echo "$args" | sed "s/--initoverlayfs-init//g") 133 | shift 1 134 | ;; 135 | *) 136 | shift 1 137 | ;; 138 | esac 139 | done 140 | 141 | # This logic for the case where there is no kernel present 142 | # in this directory, some microVMs and containers. 143 | # 144 | # no_kern="" 145 | # if ! compgen -G /boot/vmlinu* > /dev/null; then 146 | # no_kern="--no-kernel" 147 | # fi 148 | 149 | if [ -z "$kver" ]; then 150 | kver="$(uname -r)" 151 | fi 152 | 153 | detect_path_initramfs 154 | 155 | if ! [ -e "$INITOVERLAYFS_CONF" ] || ! grep -q '[^[:space:]]' "$INITOVERLAYFS_CONF"; then 156 | boot_partition=$(grep "${INITRAMFS_DIR}.*ext4" /etc/fstab | awk '{print $1}') 157 | boot_partition_hint=$(blkid -t $boot_partition | awk -F: '{print $1}') 158 | 159 | printf "%s\n%s\n%s\n%s\n" \ 160 | "bootfs $boot_partition" \ 161 | "bootfs_hint $boot_partition_hint" \ 162 | "bootfstype ext4" \ 163 | "initoverlayfs_builder dracut -M -o \"initoverlayfs fcoe\"" > $INITOVERLAYFS_CONF 164 | 165 | if $initoverlayfs_init; then 166 | printf "%s\n" "initrd_builder dracut -M -m \"initoverlayfs\" -o \"kernel-modules udev-rules systemd base bash systemd-initrd i18n kernel-modules-extra rootfs-block dracut-systemd usrmount fs-lib microcode_ctl-fw_dir_override shutdown nss-softokn\"" >> $INITOVERLAYFS_CONF 167 | else 168 | printf "%s\n" "initrd_builder dracut -M -m \"kernel-modules udev-rules initoverlayfs systemd base\" -o \"bash systemd-initrd i18n kernel-modules-extra rootfs-block dracut-systemd usrmount fs-lib microcode_ctl-fw_dir_override shutdown nss-softokn\"" >> $INITOVERLAYFS_CONF 169 | fi 170 | 171 | erofs_compression_supported="true" 172 | # shellcheck disable=SC2034 173 | . /etc/os-release 174 | for i in $ID_LIKE; do 175 | if [ "$i" == "rhel" ]; then 176 | if [ "$VERSION_ID" -le 9 ]; then 177 | erofs_compression_supported="false" 178 | break 179 | fi 180 | fi 181 | done 182 | 183 | if $initoverlayfs_init; then 184 | printf "%s\n" "initoverlayfs_init true" >> $INITOVERLAYFS_CONF 185 | fi 186 | 187 | if $erofs_compression_supported; then 188 | printf "%s\n" "erofs_compression -zlz4hc,11" >> $INITOVERLAYFS_CONF 189 | fi 190 | fi 191 | 192 | erofs_compression=$(sed -ne "s/^erofs_compression\s//pg" "$INITOVERLAYFS_CONF") 193 | initoverlayfs_builder=$(sed -ne "s/^initoverlayfs_builder\s//pg" "$INITOVERLAYFS_CONF") 194 | /bin/bash -c "$initoverlayfs_builder $args" 195 | 196 | detect_initramfs 197 | extract_initrd_into_initoverlayfs 198 | 199 | initrd_builder=$(sed -ne "s/^initrd_builder\s//pg" "$INITOVERLAYFS_CONF") 200 | /bin/bash -c "$initrd_builder $args" 201 | 202 | -------------------------------------------------------------------------------- /build-scripts/create-spec.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | # SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | VERSION_SCRIPT=$(dirname "$(readlink -f "$0")")/version.sh 5 | 6 | VERSION="$(${VERSION_SCRIPT} short)" 7 | RELEASE="$(${VERSION_SCRIPT} release)" 8 | 9 | # Set version and release 10 | sed \ 11 | -e "s|@VERSION@|${VERSION}|g" \ 12 | -e "s|@RELEASE@|${RELEASE}|g" \ 13 | < initoverlayfs.spec.in \ 14 | > initoverlayfs.spec 15 | -------------------------------------------------------------------------------- /build-scripts/version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | # Current version of initoverlayfs 5 | VERSION=0.995 6 | # Specify if build is a official release or a snapshot build 7 | IS_RELEASE=false 8 | # Used for official releases. Increment if necessary 9 | RELEASE="1" 10 | 11 | function short(){ 12 | echo ${VERSION} 13 | } 14 | 15 | function long(){ 16 | echo "$(short)-$(release)" 17 | } 18 | 19 | function release(){ 20 | # Package release 21 | 22 | if [ $IS_RELEASE = false ]; then 23 | # Used for nightly builds 24 | RELEASE="0.$(date +%04Y%02m%02d%02H%02M).git$(git rev-parse --short ${GITHUB_SHA:-HEAD})" 25 | fi 26 | echo $RELEASE 27 | } 28 | 29 | [ -z $1 ] && short || $1 30 | -------------------------------------------------------------------------------- /config-parser.h: -------------------------------------------------------------------------------- 1 | static inline bool is_line_key(const str* line, const str* key) { 2 | return line->len > key->len && isspace(line->c_str[key->len]) && 3 | !strncmp(line->c_str, key->c_str, key->len); 4 | } 5 | 6 | static inline int conf_construct(conf* c) { 7 | c->bootfs.val = (str*)calloc(1, sizeof(str)); 8 | c->bootfs.scoped = (str*)calloc(1, sizeof(str)); 9 | c->bootfs_hint.val = (str*)calloc(1, sizeof(str)); 10 | c->bootfs_hint.scoped = (str*)calloc(1, sizeof(str)); 11 | c->bootfstype.val = (str*)calloc(1, sizeof(str)); 12 | c->bootfstype.scoped = (str*)calloc(1, sizeof(str)); 13 | c->fs.val = (str*)calloc(1, sizeof(str)); 14 | c->fs.scoped = (str*)calloc(1, sizeof(str)); 15 | c->fstype.val = (str*)calloc(1, sizeof(str)); 16 | c->fstype.scoped = (str*)calloc(1, sizeof(str)); 17 | #ifdef SCSI_PROBE 18 | c->scsi_dev.val = (str*)calloc(1, sizeof(str)); 19 | c->scsi_dev.scoped = (str*)calloc(1, sizeof(str)); 20 | #endif 21 | return !c->bootfs.val || !c->bootfs.scoped || !c->bootfs_hint.val || 22 | !c->bootfs_hint.scoped || !c->bootfstype.val || 23 | !c->bootfstype.scoped || !c->fs.val || !c->fs.scoped || 24 | !c->fstype.val || !c->fstype.scoped; 25 | } 26 | 27 | static inline void set_conf(pair* conf, str** line, const size_t key_len) { 28 | int i; 29 | for (i = key_len; isspace((*line)->c_str[i]); ++i) 30 | ; 31 | conf->val->c_str = (*line)->c_str + i; 32 | 33 | for (i = (*line)->len; isspace((*line)->c_str[i]); --i) 34 | ; 35 | (*line)->c_str[i - 1] = 0; 36 | 37 | swap(conf->scoped, *line); 38 | } 39 | 40 | static inline void conf_set_pick(conf* c, str** line) { 41 | const str bootfs_str = {.c_str = "bootfs", .len = sizeof("bootfs") - 1}; 42 | const str bootfs_hint_str = {.c_str = "bootfs_hint", 43 | .len = sizeof("bootfs_hint") - 1}; 44 | const str bootfstype_str = {.c_str = "bootfstype", 45 | .len = sizeof("bootfstype") - 1}; 46 | const str fs_str = {.c_str = "fs", .len = sizeof("fs") - 1}; 47 | const str fstype_str = {.c_str = "fstype", .len = sizeof("fstype") - 1}; 48 | #ifdef SCSI_PROBE 49 | const str devtoscan_str = {.c_str = SCSI_ADDR_BOOT_ARG, .len = sizeof(SCSI_ADDR_BOOT_ARG) - 1}; 50 | #endif 51 | 52 | if (is_line_key(*line, &bootfs_str)) 53 | set_conf(&c->bootfs, line, bootfs_str.len); 54 | else if (is_line_key(*line, &bootfs_hint_str)) 55 | set_conf(&c->bootfs_hint, line, bootfs_hint_str.len); 56 | else if (is_line_key(*line, &bootfstype_str)) 57 | set_conf(&c->bootfstype, line, bootfstype_str.len); 58 | else if (is_line_key(*line, &fs_str)) 59 | set_conf(&c->fs, line, fs_str.len); 60 | else if (is_line_key(*line, &fstype_str)) 61 | set_conf(&c->fstype, line, fstype_str.len); 62 | #ifdef SCSI_PROBE 63 | else if (is_line_key(*line, &devtoscan_str)){ 64 | (*line)->c_str[devtoscan_str.len]='='; 65 | set_conf(&c->scsi_dev, line, devtoscan_str.len); 66 | } 67 | #endif 68 | } 69 | 70 | static inline conf* conf_print(conf* c) { 71 | #ifdef DEBUG 72 | printd( 73 | "bootfs: {\"%s\", \"%s\"}, bootfs_hint: {\"%s\", \"%s\"}, bootfstype: " 74 | "{\"%s\", \"%s\"}, fs: {\"%s\", " 75 | "\"%s\"}, fstype: {\"%s\", \"%s\"}" 76 | #ifdef SCSI_PROBE 77 | ", scsi_dev: {\"%s\", \"%s\"}" 78 | #endif 79 | "\n", 80 | c->bootfs.val->c_str, c->bootfs.scoped->c_str, c->bootfs_hint.val->c_str, 81 | c->bootfs_hint.scoped->c_str, c->bootfstype.val->c_str, 82 | c->bootfstype.scoped->c_str, c->fs.val->c_str, c->fs.scoped->c_str, 83 | c->fstype.val->c_str, c->fstype.scoped->c_str 84 | #ifdef SCSI_PROBE 85 | , c->scsi_dev.val->c_str, c->scsi_dev.scoped->c_str 86 | #endif 87 | ); 88 | #endif 89 | return c; 90 | } 91 | 92 | static inline char* conf_read(conf* c, const char* file) { 93 | autofclose FILE* f = fopen(file, "r"); 94 | autofree_str str* line = (str*)malloc(sizeof(str)); 95 | if (!line) 96 | return NULL; 97 | 98 | line->c_str = 0; 99 | 100 | if (!f) 101 | return NULL; 102 | 103 | for (size_t len_alloc; 104 | (line->len = getline(&line->c_str, &len_alloc, f)) != -1;) 105 | conf_set_pick(c, &line); 106 | 107 | conf_print(c); 108 | 109 | return NULL; 110 | } 111 | -------------------------------------------------------------------------------- /docs/devel/LISTING-FILES-INITRAMFS.md: -------------------------------------------------------------------------------- 1 | # Reading content of initramfs image 2 | 3 | During the development of initoverlayfs might be useful to identify files included in the initramfs. 4 | For this example, let's use a real use case from CentOS Automotive Stream Distribution (qcow2 image). 5 | 6 | Build the CentOS Automotive Stream Distribution image 7 | ```bash 8 | git clone https://gitlab.com/CentOS/automotive/sample-images 9 | cd sample-images/osbuild-manifests 10 | make cs9-qemu-qm-minimal-ostree.x86_64.qcow2 11 | ``` 12 | 13 | Enable NBD on the Host 14 | ```bash 15 | modprobe nbd max_part=8 16 | ``` 17 | 18 | Connect the QCOW2 as network block device 19 | ```bash 20 | qemu-nbd --connect=/dev/nbd0 ./cs9-qemu-qm-minimal-ostree.x86_64.qcow2 21 | ``` 22 | 23 | Understanding the output from fdisk 24 | ``` 25 | # fdisk /dev/nbd0 -l 26 | Disk /dev/nbd0: 8 GiB, 8589934592 bytes, 16777216 sectors 27 | Units: sectors of 1 * 512 = 512 bytes 28 | Sector size (logical/physical): 512 bytes / 512 bytes 29 | I/O size (minimum/optimal): 512 bytes / 512 bytes 30 | Disklabel type: gpt 31 | Disk identifier: D209C89E-EA5E-4FBD-B161-B461CCE297E0 32 | 33 | Device Start End Sectors Size Type 34 | /dev/nbd0p1 2048 206847 204800 100M EFI System <--- EFI boot partition 35 | /dev/nbd0p2 206848 821247 614400 300M Linux filesystem <--- initramfs partition 36 | /dev/nbd0p3 821248 16775167 15953920 7.6G Linux filesystem <--- rootfs partition (main fs partition) 37 | ``` 38 | 39 | Mount the initramfs partition 40 | ```bash 41 | mount /dev/nbd0p2 /mnt 42 | ``` 43 | 44 | find the initramfs file 45 | ```bash 46 | # find . -name initramfs* 47 | ./ostree/centos-082d9caaac6cb51dc22cd7d9881692d44c9d7cbc6b3d8edf8709286cb8bd12f9/initramfs-5.14.0-438.391.el9iv.x86_64.img 48 | ``` 49 | 50 | now use lsinitrd tool to list the content of the initramfs image 51 | ``` 52 | lsinitrd ./ostree/centos-082d9caaac6cb51dc22cd7d9881692d44c9d7cbc6b3d8edf8709286cb8bd12f9/initramfs-5.14.0-438.391.el9iv.x86_64.img 53 | Image: ./ostree/centos-082d9caaac6cb51dc22cd7d9881692d44c9d7cbc6b3d8edf8709286cb8bd12f9/initramfs-5.14.0-438.391.el9iv.x86_64.img: 17M 54 | ======================================================================== 55 | dracut modules: 56 | systemd 57 | systemd-initrd 58 | kernel-modules 59 | kernel-modules-extra 60 | rootfs-block 61 | udev-rules 62 | dracut-systemd 63 | ostree 64 | usrmount 65 | base 66 | fs-lib 67 | microcode_ctl-fw_dir_override 68 | shutdown 69 | ======================================================================== 70 | drwxr-xr-x 1 root root 0 Jan 3 20:49 . 71 | lrwxrwxrwx 1 root root 7 Jan 3 20:49 bin -> usr/bin 72 | drwxr-xr-x 1 root root 0 Jan 3 20:49 dev 73 | drwxr-xr-x 1 root root 0 Jan 3 20:49 etc 74 | drwxr-xr-x 1 root root 0 Jan 3 20:49 etc/cmdline.d 75 | drwxr-xr-x 1 root root 0 Jan 3 20:49 etc/conf.d 76 | -rw-r--r-- 1 root root 124 Jan 3 20:49 etc/conf.d/systemd.conf 77 | -rw-r--r-- 1 root root 0 Jan 3 20:49 etc/fstab.empty 78 | -rw-r--r-- 1 root root 203 Jan 3 20:49 etc/group 79 | lrwxrwxrwx 1 root root 25 Jan 3 20:49 etc/initrd-release -> ../usr/lib/initrd-release 80 | -rw-r--r-- 1 root root 2431 Jan 3 20:49 etc/ld.so.cache 81 | -rw-r--r-- 1 root root 28 Aug 2 2021 etc/ld.so.conf 82 | -rw-r--r-- 1 root root 0 Jan 3 20:49 etc/machine-id 83 | lrwxrwxrwx 1 root root 17 Jan 3 20:49 etc/mtab -> /proc/self/mounts 84 | lrwxrwxrwx 1 root root 14 Jan 3 20:49 etc/os-release -> initrd-release 85 | drwxr-xr-x 1 root root 0 Jan 3 20:49 etc/ostree 86 | -rw-r--r-- 1 root root 44 Jan 3 20:49 etc/ostree/initramfs-root-binding.key 87 | -rw-r--r-- 1 root root 121 Jan 3 20:49 etc/passwd 88 | drwxr-xr-x 1 root root 0 Jan 3 20:49 etc/systemd 89 | 90 | .... 91 | snip 92 | .... 93 | ``` 94 | 95 | unmount and disconnect as soon as you are done 96 | 97 | ``` 98 | umount /mnt 99 | qemu-nbd -d /dev/nbd0 100 | ``` 101 | -------------------------------------------------------------------------------- /initoverlayfs.c: -------------------------------------------------------------------------------- 1 | #ifndef _GNU_SOURCE 2 | #define _GNU_SOURCE 3 | #endif 4 | 5 | #include "initoverlayfs.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #ifdef SCSI_PROBE 26 | #define CMDLINE "/proc/cmdline" 27 | #define SCSI_SYS_TMPL "/sys/class/scsi_host/host%d/scan" 28 | #define SCSI_SYS_TMPL_SZ 40 29 | #include "scsi_probe/scsi_probe.h" 30 | #include 31 | #endif 32 | #include "config-parser.h" 33 | 34 | #define fork_execl_no_wait(pid, exe, ...) \ 35 | do { \ 36 | pid = fork(); \ 37 | if (pid == -1) { \ 38 | print("fail fork_execl_no_wait\n"); \ 39 | break; \ 40 | } else if (pid > 0) { \ 41 | break; \ 42 | } \ 43 | \ 44 | execl(exe, exe, __VA_ARGS__, (char*)NULL); \ 45 | exit(errno); \ 46 | } while (0) 47 | 48 | #define fork_execl(exe, ...) \ 49 | do { \ 50 | printd("fork_execl(\"" exe "\")\n"); \ 51 | const pid_t pid = fork(); \ 52 | if (pid == -1) { \ 53 | print("fail fork_execl\n"); \ 54 | break; \ 55 | } else if (pid > 0) { \ 56 | printd("forked %d fork_execl\n", pid); \ 57 | waitpid(pid, 0, 0); \ 58 | break; \ 59 | } \ 60 | \ 61 | execl(exe, exe, __VA_ARGS__, (char*)NULL); \ 62 | exit(errno); \ 63 | } while (0) 64 | 65 | #define fork_execlp(exe, ...) \ 66 | do { \ 67 | printd("fork_execlp(\"" exe "\")\n"); \ 68 | const pid_t pid = fork(); \ 69 | if (pid == -1) { \ 70 | print("fail fork_execlp\n"); \ 71 | break; \ 72 | } else if (pid > 0) { \ 73 | printd("forked %d fork_execlp\n", pid); \ 74 | waitpid(pid, 0, 0); \ 75 | break; \ 76 | } \ 77 | \ 78 | execlp(exe, exe, __VA_ARGS__, (char*)NULL); \ 79 | exit(errno); \ 80 | } while (0) 81 | 82 | #define fork_execlp_no_wait(pid, exe, ...) \ 83 | do { \ 84 | printd("fork_execlp_no_wait(\"" exe "\")\n"); \ 85 | pid = fork(); \ 86 | if (pid == -1) { \ 87 | print("fail fork_execlp_no_wait\n"); \ 88 | break; \ 89 | } else if (pid > 0) { \ 90 | printd("forked %d fork_execlp_no_wait\n", pid); \ 91 | break; \ 92 | } \ 93 | \ 94 | execlp(exe, exe, __VA_ARGS__, (char*)NULL); \ 95 | exit(errno); \ 96 | } while (0) 97 | 98 | #define fork_execvp_no_wait(pid, exe) \ 99 | do { \ 100 | printd("fork_execvp_no_wait(%p)\n", (void*)exe); \ 101 | pid = fork(); \ 102 | if (pid == -1) { \ 103 | print("fail execvp_no_wait\n"); \ 104 | break; \ 105 | } else if (pid > 0) { \ 106 | printd("forked %d fork_execvp_no_wait\n", pid); \ 107 | break; \ 108 | } \ 109 | \ 110 | execvp(exe[0], exe); \ 111 | exit(errno); \ 112 | } while (0) 113 | 114 | static long loop_ctl_get_free(void) { 115 | autoclose const int loopctlfd = open("/dev/loop-control", O_RDWR | O_CLOEXEC); 116 | if (loopctlfd < 0) { 117 | print("open(\"/dev/loop-control\", O_RDWR | O_CLOEXEC) = %d %d (%s)\n", 118 | loopctlfd, errno, strerror(errno)); 119 | return 0; // in error just try and continue with loop0 120 | } 121 | 122 | const long devnr = ioctl(loopctlfd, LOOP_CTL_GET_FREE); 123 | if (devnr < 0) { 124 | print("ioctl(%d, LOOP_CTL_GET_FREE) = %ld %d (%s)\n", loopctlfd, devnr, 125 | errno, strerror(errno)); 126 | return 0; // in error just try and continue with loop0 127 | } 128 | 129 | return devnr; 130 | } 131 | 132 | static int loop_configure(const long devnr, 133 | const int filefd, 134 | char** loopdev, 135 | const char* file) { 136 | const struct loop_config loopconfig = { 137 | .fd = (unsigned int)filefd, 138 | .block_size = 0, 139 | .info = {.lo_device = 0, 140 | .lo_inode = 0, 141 | .lo_rdevice = 0, 142 | .lo_offset = 0, 143 | .lo_sizelimit = 0, 144 | .lo_number = 0, 145 | .lo_encrypt_type = LO_CRYPT_NONE, 146 | .lo_encrypt_key_size = 0, 147 | .lo_flags = LO_FLAGS_PARTSCAN, 148 | .lo_file_name = "", 149 | .lo_crypt_name = "", 150 | .lo_encrypt_key = "", 151 | .lo_init = {0, 0}}, 152 | .__reserved = {0, 0, 0, 0, 0, 0, 0, 0}}; 153 | strncpy((char*)loopconfig.info.lo_file_name, file, LO_NAME_SIZE - 1); 154 | if (asprintf(loopdev, "/dev/loop%ld", devnr) < 0) 155 | return -1; 156 | 157 | autoclose const int loopfd = open(*loopdev, O_RDWR | O_CLOEXEC); 158 | if (loopfd < 0) { 159 | print("open(\"%s\", O_RDWR | O_CLOEXEC) = %d %d (%s)\n", *loopdev, loopfd, 160 | errno, strerror(errno)); 161 | return errno; 162 | } 163 | 164 | if (ioctl(loopfd, LOOP_CONFIGURE, &loopconfig) < 0) { 165 | print("ioctl(%d, LOOP_CONFIGURE, %p) %d (%s)\n", loopfd, (void*)&loopconfig, 166 | errno, strerror(errno)); 167 | return errno; 168 | } 169 | 170 | return 0; 171 | } 172 | 173 | static int losetup(char** loopdev, const char* file) { 174 | autoclose const int filefd = open(file, O_RDONLY | O_CLOEXEC); 175 | if (filefd < 0) { 176 | print("open(\"%s\", O_RDONLY| O_CLOEXEC) = %d %d (%s)\n", file, filefd, 177 | errno, strerror(errno)); 178 | return errno; 179 | } 180 | 181 | const int ret = loop_configure(loop_ctl_get_free(), filefd, loopdev, file); 182 | if (ret) 183 | return ret; 184 | 185 | return 0; 186 | } 187 | 188 | static bool convert_bootfs(conf* c, const bool systemd) { 189 | if (!c->bootfs.val->c_str) { 190 | print("c->bootfs.val.c_str pointer is null\n"); 191 | return false; 192 | } 193 | 194 | if (!c->bootfs.val->c_str[0]) { 195 | print("c->bootfs.val.c_str string is \"%s\"\n", c->bootfs.val->c_str); 196 | return false; 197 | } 198 | 199 | autofree char* bootfs_tmp = 0; 200 | if (systemd) { 201 | const char* token = strtok(c->bootfs.val->c_str, "="); 202 | if (!strcmp(token, "PARTLABEL")) { 203 | token = strtok(NULL, "="); 204 | if (asprintf(&bootfs_tmp, "/dev/disk/by-partlabel/%s", token) < 0) 205 | return false; 206 | } else if (!strcmp(token, "LABEL")) { 207 | token = strtok(NULL, "="); 208 | if (asprintf(&bootfs_tmp, "/dev/disk/by-label/%s", token) < 0) 209 | return false; 210 | } else if (!strcmp(token, "UUID")) { 211 | token = strtok(NULL, "="); 212 | if (asprintf(&bootfs_tmp, "/dev/disk/by-uuid/%s", token) < 0) 213 | return false; 214 | } else if (!strcmp(token, "PARTUUID")) { 215 | token = strtok(NULL, "="); 216 | if (asprintf(&bootfs_tmp, "/dev/disk/by-partuuid/%s", token) < 0) 217 | return false; 218 | } else 219 | return false; 220 | } else { 221 | blkid_cache cache; 222 | 223 | const char* read = NULL; 224 | 225 | // Open the cache 226 | if (blkid_get_cache(&cache, read)) 227 | print("blkid_get_cache(?, \"%s\")\n", read ? read : "NULL"); 228 | 229 | const char* type = strtok(c->bootfs.val->c_str, "="); 230 | const char* value = strtok(NULL, "="); 231 | if (c->bootfs_hint.val->c_str && c->bootfs_hint.val->c_str[0] && 232 | !strcmp(value, 233 | blkid_get_tag_value(cache, type, c->bootfs_hint.val->c_str))) { 234 | bootfs_tmp = strdup(c->bootfs_hint.val->c_str); 235 | } else { 236 | if (blkid_probe_all(cache)) 237 | print("blkid_probe_all()\n"); 238 | 239 | const blkid_dev b_dev = blkid_find_dev_with_tag(cache, type, value); 240 | if (asprintf(&bootfs_tmp, "%s", blkid_dev_devname(b_dev)) < 0) 241 | return false; 242 | } 243 | 244 | blkid_put_cache(cache); 245 | } 246 | 247 | swap(c->bootfs.scoped->c_str, bootfs_tmp); 248 | c->bootfs.val->c_str = c->bootfs.scoped->c_str; 249 | 250 | return true; 251 | } 252 | 253 | static int convert_fs(conf* c) { 254 | if (!c->fstype.scoped->c_str) { 255 | c->fstype.scoped->c_str = strdup("erofs"); 256 | c->fstype.val->c_str = c->fstype.scoped->c_str; 257 | } 258 | 259 | autofree char* fs = 0; 260 | if (!c->fs.scoped->c_str) { 261 | struct utsname buf; 262 | uname(&buf); 263 | if (asprintf(&fs, "/boot/initoverlayfs-%s.img", buf.release) < 0) 264 | return -1; 265 | 266 | swap(fs, c->fs.scoped->c_str); 267 | c->fs.val->c_str = c->fs.scoped->c_str; 268 | return 0; 269 | } 270 | 271 | if (!c->fs.val->c_str) { 272 | print("c->fs.val->c_str pointer is null\n"); 273 | return -2; 274 | } 275 | 276 | if (!c->fs.val->c_str[0]) { 277 | print("c->fs.val->c_str string is \"\"\n"); 278 | return -3; 279 | } 280 | 281 | if (asprintf(&fs, "/boot%s", c->fs.val->c_str) < 0) 282 | return -4; 283 | 284 | swap(fs, c->fs.scoped->c_str); 285 | c->fs.val->c_str = c->fs.scoped->c_str; 286 | 287 | return 0; 288 | } 289 | 290 | #define OVERLAY_STR \ 291 | "redirect_dir=on,lowerdir=/initrofs,upperdir=/overlay/upper,workdir=/" \ 292 | "overlay/work" 293 | 294 | #define SYSROOT "/initoverlayfs" 295 | 296 | static void mounts(const conf* c) { 297 | autofree char* dev_loop = 0; 298 | if (c->fs.val->c_str && losetup(&dev_loop, c->fs.val->c_str)) 299 | print("losetup(\"%s\", \"%s\") %d (%s)\n", dev_loop, c->fs.val->c_str, 300 | errno, strerror(errno)); 301 | 302 | if (mount(dev_loop, "/initrofs", c->fstype.val->c_str, MS_RDONLY, NULL)) 303 | print( 304 | "mount(\"%s\", \"/initrofs\", \"%s\", MS_RDONLY, NULL) " 305 | "%d (%s)\n", 306 | dev_loop, c->fstype.val->c_str, errno, strerror(errno)); 307 | 308 | if (mount("overlay", SYSROOT, "overlay", 0, "volatile," OVERLAY_STR) && 309 | errno == EINVAL && mount("overlay", SYSROOT, "overlay", 0, OVERLAY_STR)) 310 | print("mount(\"overlay\", \"" SYSROOT "\", \"overlay\", 0, \"" OVERLAY_STR 311 | "\") %d (%s)\n", 312 | errno, strerror(errno)); 313 | 314 | if (mount("/boot", SYSROOT "/boot", c->bootfstype.val->c_str, MS_MOVE, NULL)) 315 | print("mount(\"/boot\", \"" SYSROOT 316 | "/boot\", \"%s\", MS_MOVE, NULL) " 317 | "%d (%s)\n", 318 | c->bootfstype.val->c_str, errno, strerror(errno)); 319 | } 320 | 321 | static void wait_mount_bootfs(const conf* c, const bool systemd) { 322 | if (systemd) 323 | fork_execlp("udevadm", "wait", c->bootfs.val->c_str); 324 | 325 | errno = 0; 326 | if (mount(c->bootfs.val->c_str, "/boot", c->bootfstype.val->c_str, 0, NULL)) 327 | print( 328 | "mount(\"%s\", \"/boot\", \"%s\", 0, NULL) " 329 | "%d (%s)\n", 330 | c->bootfs.val->c_str, c->bootfstype.val->c_str, errno, strerror(errno)); 331 | } 332 | 333 | static int recursive_rm(const int fd); 334 | 335 | static int if_directory(const int dfd, 336 | const struct dirent* d, 337 | const struct stat* rb, 338 | int* isdir) { 339 | struct stat sb; 340 | if (fstatat(dfd, d->d_name, &sb, AT_SYMLINK_NOFOLLOW)) { 341 | print("stat of %s failed\n", d->d_name); 342 | return 1; 343 | } 344 | 345 | /* skip if device is not the same */ 346 | if (sb.st_dev != rb->st_dev) 347 | return 1; 348 | 349 | /* remove subdirectories */ 350 | if (S_ISDIR(sb.st_mode)) { 351 | autoclose const int cfd = openat(dfd, d->d_name, O_RDONLY); 352 | if (cfd >= 0) 353 | recursive_rm(cfd); /* it closes cfd too */ 354 | 355 | *isdir = 1; 356 | } 357 | 358 | return 0; 359 | } 360 | 361 | static int for_each_directory(DIR* dir, const int dfd, const struct stat* rb) { 362 | errno = 0; 363 | struct dirent* d = readdir(dir); 364 | if (!d) { 365 | if (errno) { 366 | print("failed to read directory\n"); 367 | return -1; 368 | } 369 | 370 | return 0; /* end of directory */ 371 | } 372 | 373 | if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..") || 374 | !strcmp(d->d_name, "initoverlayfs")) 375 | return 1; 376 | 377 | int isdir = 0; 378 | if (d->d_type == DT_DIR || d->d_type == DT_UNKNOWN) 379 | if (if_directory(dfd, d, rb, &isdir)) 380 | return 1; 381 | 382 | if (unlinkat(dfd, d->d_name, isdir ? AT_REMOVEDIR : 0)) 383 | print("failed to unlink %s\n", d->d_name); 384 | 385 | return 1; 386 | } 387 | 388 | /* remove all files/directories below dirName -- don't cross mountpoints */ 389 | static int recursive_rm(const int fd) { 390 | autoclosedir DIR* dir = fdopendir(fd); 391 | if (!dir) { 392 | print("failed to open directory\n"); 393 | return -1; 394 | } 395 | 396 | struct stat rb; 397 | const int dfd = dirfd(dir); 398 | if (fstat(dfd, &rb)) { 399 | print("stat failed\n"); 400 | return -1; 401 | } 402 | 403 | while (1) { 404 | const int ret = for_each_directory(dir, dfd, &rb); 405 | if (ret <= 0) 406 | return ret; 407 | } 408 | 409 | return 0; 410 | } 411 | 412 | static int move_chroot_chdir(const char* newroot) { 413 | printd("move_chroot_chdir(\"%s\")\n", newroot); 414 | if (mount(newroot, "/", NULL, MS_MOVE, NULL) < 0) { 415 | print("failed to mount moving %s to /\n", newroot); 416 | return -1; 417 | } 418 | 419 | if (chroot(".")) { 420 | print("failed to change root\n"); 421 | return -1; 422 | } 423 | 424 | if (chdir("/")) { 425 | print("cannot change directory to %s\n", "/"); 426 | return -1; 427 | } 428 | 429 | return 0; 430 | } 431 | 432 | static int switchroot_move(const char* newroot) { 433 | if (chdir(newroot)) { 434 | print("failed to change directory to %s", newroot); 435 | return -1; 436 | } 437 | 438 | autoclose const int cfd = open("/", O_RDONLY | O_CLOEXEC); 439 | if (cfd < 0) { 440 | print("cannot open %s", "/"); 441 | return -1; 442 | } 443 | 444 | if (move_chroot_chdir(newroot)) 445 | return -1; 446 | 447 | switch (fork()) { 448 | case 0: /* child */ 449 | { 450 | struct statfs stfs; 451 | if (fstatfs(cfd, &stfs) == 0 && 452 | (stfs.f_type == RAMFS_MAGIC || stfs.f_type == TMPFS_MAGIC)) { 453 | recursive_rm(cfd); 454 | } else 455 | print("old root filesystem is not an initramfs"); 456 | 457 | exit(EXIT_SUCCESS); 458 | } 459 | case -1: /* error */ 460 | break; 461 | 462 | default: /* parent */ 463 | return 0; 464 | } 465 | 466 | return -1; 467 | } 468 | 469 | static int stat_oldroot_newroot(const char* newroot, 470 | struct stat* newroot_stat, 471 | struct stat* oldroot_stat) { 472 | if (stat("/", oldroot_stat) != 0) { 473 | print("stat of %s failed\n", "/"); 474 | return -1; 475 | } 476 | 477 | if (stat(newroot, newroot_stat) != 0) { 478 | print("stat of %s failed\n", newroot); 479 | return -1; 480 | } 481 | 482 | return 0; 483 | } 484 | 485 | static int switchroot(const char* newroot) { 486 | /* Don't try to unmount the old "/", there's no way to do it. */ 487 | // const char* umounts[] = {"/dev", "/proc", "/sys", "/run", NULL}; 488 | const char* umounts[] = {"/dev", "/sys", "/run", NULL}; 489 | // const char* umounts[] = {"/dev", NULL}; 490 | struct stat newroot_stat, oldroot_stat, sb; 491 | if (stat_oldroot_newroot(newroot, &newroot_stat, &oldroot_stat)) 492 | return -1; 493 | 494 | for (int i = 0; umounts[i] != NULL; ++i) { 495 | autofree char* newmount; 496 | if (asprintf(&newmount, "%s%s", newroot, umounts[i]) < 0) { 497 | print( 498 | "asprintf(%p, \"%%s%%s\", \"%s\", \"%s\") MS_NODEV, NULL) %d (%s)\n", 499 | (void*)newmount, newroot, umounts[i], errno, strerror(errno)); 500 | return -1; 501 | } 502 | 503 | if ((stat(umounts[i], &sb) == 0) && sb.st_dev == oldroot_stat.st_dev) { 504 | /* mount point to move seems to be a normal directory or stat failed */ 505 | continue; 506 | } 507 | 508 | printd("(stat(\"%s\", %p) == 0) && %lx != %lx)\n", newmount, (void*)&sb, 509 | sb.st_dev, newroot_stat.st_dev); 510 | if ((stat(newmount, &sb) != 0) || (sb.st_dev != newroot_stat.st_dev)) { 511 | /* mount point seems to be mounted already or stat failed */ 512 | umount2(umounts[i], MNT_DETACH); 513 | continue; 514 | } 515 | 516 | printd("mount(\"%s\", \"%s\", NULL, MS_MOVE, NULL)\n", umounts[i], 517 | newmount); 518 | if (mount(umounts[i], newmount, NULL, MS_MOVE, NULL) < 0) { 519 | print("failed to mount moving %s to %s, forcing unmount\n", umounts[i], 520 | newmount); 521 | umount2(umounts[i], MNT_FORCE); 522 | } 523 | } 524 | 525 | return switchroot_move(newroot); 526 | } 527 | 528 | static int mount_proc_sys_dev(void) { 529 | if (true) { 530 | if (mount("proc", "/proc", "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV, 531 | NULL)) { 532 | print( 533 | "mount(\"proc\", \"/proc\", \"proc\", MS_NOSUID | MS_NOEXEC | " 534 | "MS_NODEV, NULL) %d (%s)\n", 535 | errno, strerror(errno)); 536 | return errno; 537 | } 538 | } 539 | 540 | if (mount("sysfs", "/sys", "sysfs", MS_NOSUID | MS_NOEXEC | MS_NODEV, NULL)) { 541 | print( 542 | "mount(\"sysfs\", \"/sys\", \"sysfs\", MS_NOSUID | MS_NOEXEC | " 543 | "MS_NODEV, NULL) %d (%s)\n", 544 | errno, strerror(errno)); 545 | return errno; 546 | } 547 | 548 | if (mount("devtmpfs", "/dev", "devtmpfs", MS_NOSUID | MS_STRICTATIME, 549 | "mode=0755,size=4m")) { 550 | print( 551 | "mount(\"devtmpfs\", \"/dev\", \"devtmpfs\", MS_NOSUID | " 552 | "MS_STRICTATIME, \"mode=0755,size=4m\") %d (%s)\n", 553 | errno, strerror(errno)); 554 | return errno; 555 | } 556 | 557 | return 0; 558 | } 559 | 560 | static FILE* log_open_kmsg(void) { 561 | kmsg_f = fopen("/dev/kmsg", "w"); 562 | if (!kmsg_f) { 563 | print("open(\"/dev/kmsg\", \"w\"), %d = errno\n", errno); 564 | return kmsg_f; 565 | } 566 | 567 | setvbuf(kmsg_f, 0, _IOLBF, 0); 568 | return kmsg_f; 569 | } 570 | 571 | static void execl_single_arg(const char* exe) { 572 | printd("execl_single_arg(\"%s\")\n", exe); 573 | execl(exe, exe, (char*)NULL); 574 | } 575 | 576 | static void exec_init(void) { 577 | execl_single_arg("/sbin/init"); 578 | execl_single_arg("/etc/init"); 579 | execl_single_arg("/bin/init"); 580 | execl_single_arg("/bin/sh"); 581 | } 582 | 583 | int main(int argc, char* argv[]) { 584 | (void)argv; 585 | bool systemd = false; 586 | if (argc > 1) 587 | systemd = true; 588 | 589 | autofclose FILE* kmsg_f_scoped = NULL; 590 | if (!systemd) { 591 | mount_proc_sys_dev(); 592 | log_open_kmsg(); 593 | kmsg_f_scoped = kmsg_f; 594 | } 595 | 596 | pid_t loop_pid = 0; 597 | if (systemd) { 598 | fork_execl_no_wait(loop_pid, "/usr/sbin/modprobe", "loop"); 599 | } 600 | 601 | autofree_conf conf conf = {.bootfs = {0, 0}, 602 | .bootfs_hint = {0, 0}, 603 | .bootfstype = {0, 0}, 604 | .fs = {0, 0}, 605 | .fstype = {0, 0}}; 606 | if (conf_construct(&conf)) 607 | return 0; 608 | 609 | conf_read(&conf, "/etc/initoverlayfs.conf"); 610 | #ifdef SCSI_PROBE 611 | struct args ba; 612 | char cmdline[50]; 613 | FILE *file; 614 | 615 | file = fopen(CMDLINE, "r"); 616 | if (file != NULL) { 617 | if (fgets(cmdline, sizeof(cmdline), file)) { 618 | char *kcmdline = fetch_kernel_cmdline(CMDLINE); 619 | conf_print(&conf); 620 | parse_kernel_cmdline(kcmdline, &ba); 621 | update_scsi_args(conf.scsi_dev.scoped->c_str, &ba); 622 | trigger_scan(&ba, SCSI_SYS_TMPL, SCSI_SYS_TMPL_SZ); 623 | } 624 | fclose(file); 625 | } 626 | #endif 627 | 628 | const bool do_bootfs = convert_bootfs(&conf, systemd); 629 | convert_fs(&conf); 630 | if (systemd) 631 | waitpid(loop_pid, 0, 0); 632 | 633 | if (do_bootfs) 634 | wait_mount_bootfs(&conf, systemd); 635 | 636 | errno = 0; 637 | mounts(&conf); 638 | if (switchroot("/initoverlayfs")) { 639 | print("switchroot(\"/initoverlayfs\") %d (%s)\n", errno, strerror(errno)); 640 | return 0; 641 | } 642 | 643 | exec_init(); 644 | 645 | return 0; 646 | } 647 | -------------------------------------------------------------------------------- /initoverlayfs.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define autofree __attribute__((cleanup(cleanup_free))) 12 | #define autofree_str __attribute__((cleanup(cleanup_free_str))) 13 | #define autofree_conf __attribute__((cleanup(cleanup_free_conf))) 14 | #define autoclose __attribute__((cleanup(cleanup_close))) 15 | #define autofclose __attribute__((cleanup(cleanup_fclose))) 16 | #define autoclosedir __attribute__((cleanup(cleanup_closedir))) 17 | 18 | #ifdef __cplusplus 19 | #define typeof decltype 20 | #endif 21 | 22 | #define swap(a, b) \ 23 | do { \ 24 | typeof(a) temp = a; \ 25 | a = b; \ 26 | b = temp; \ 27 | } while (0) 28 | 29 | #define print(...) \ 30 | do { \ 31 | if (kmsg_f) { \ 32 | fprintf(kmsg_f, "storage-init: " __VA_ARGS__); \ 33 | break; \ 34 | } \ 35 | \ 36 | printf(__VA_ARGS__); \ 37 | } while (0) 38 | 39 | #if 1 40 | #define DEBUG 41 | #define printd(...) \ 42 | do { \ 43 | print(__VA_ARGS__); \ 44 | } while (0) 45 | #else 46 | #define printd(...) 47 | #endif 48 | 49 | static FILE* kmsg_f = 0; 50 | 51 | typedef struct str { 52 | char* c_str; 53 | int len; 54 | } str; 55 | 56 | typedef struct pair { 57 | str* val; 58 | str* scoped; 59 | } pair; 60 | 61 | typedef struct conf { 62 | pair bootfs; 63 | pair bootfs_hint; 64 | pair bootfstype; 65 | pair fs; 66 | pair fstype; 67 | #ifdef SCSI_PROBE 68 | pair scsi_dev; 69 | #endif 70 | } conf; 71 | 72 | static inline void cleanup_free_conf(conf* p) { 73 | if (p->bootfs.scoped) 74 | free(p->bootfs.scoped->c_str); 75 | if (p->bootfs_hint.scoped) 76 | free(p->bootfs_hint.scoped->c_str); 77 | if (p->bootfstype.scoped) 78 | free(p->bootfstype.scoped->c_str); 79 | if (p->fs.scoped) 80 | free(p->fs.scoped->c_str); 81 | if (p->fstype.scoped) 82 | free(p->fstype.scoped->c_str); 83 | #ifdef SCSI_PROBE 84 | if (p->scsi_dev.scoped) 85 | free(p->scsi_dev.scoped->c_str); 86 | free(p->scsi_dev.scoped); 87 | free(p->scsi_dev.val); 88 | #endif 89 | free(p->bootfs.scoped); 90 | free(p->bootfs_hint.scoped); 91 | free(p->bootfstype.scoped); 92 | free(p->fs.scoped); 93 | free(p->fstype.scoped); 94 | free(p->bootfs.val); 95 | free(p->bootfs_hint.val); 96 | free(p->bootfstype.val); 97 | free(p->fs.val); 98 | free(p->fstype.val); 99 | } 100 | 101 | static inline void cleanup_free(void* p) { 102 | free(*(void**)p); 103 | } 104 | 105 | static inline void cleanup_free_str(str** p) { 106 | if (!*p) 107 | return; 108 | 109 | free((*p)->c_str); 110 | free(*p); 111 | } 112 | 113 | static inline void cleanup_close(const int* fd) { 114 | if (*fd >= 0) 115 | close(*fd); 116 | } 117 | 118 | static inline void cleanup_fclose(FILE** stream) { 119 | if (*stream) 120 | fclose(*stream); 121 | } 122 | 123 | static inline void cleanup_closedir(DIR** dir) { 124 | if (*dir) 125 | closedir(*dir); 126 | } 127 | -------------------------------------------------------------------------------- /initoverlayfs.spec.in: -------------------------------------------------------------------------------- 1 | Name: initoverlayfs 2 | Version: @VERSION@ 3 | Release: @RELEASE@%{?dist} 4 | Summary: An initial scalable filesystem for Linux operating systems 5 | License: GPL-2.0-only 6 | URL: https://github.com/containers/initoverlayfs 7 | Source0: %{url}/archive/%{version}/%{name}-%{version}.tar.gz 8 | 9 | BuildRequires: gcc 10 | BuildRequires: libblkid-devel 11 | Recommends: lz4 12 | Recommends: gzip 13 | Requires: erofs-utils 14 | Requires: dracut 15 | 16 | %global debug_package %{nil} 17 | 18 | %description 19 | %{summary}. 20 | 21 | %prep 22 | %setup -q -n %{name}-%{version} 23 | 24 | %build 25 | RPM_OPT_FLAGS="${RPM_OPT_FLAGS/-flto=auto /}" 26 | gcc ${RPM_OPT_FLAGS} -lblkid initoverlayfs.c scsi_probe/scsi_probe.c -o initoverlayfs 27 | 28 | %install 29 | install -D -m755 bin/initoverlayfs-install ${RPM_BUILD_ROOT}/%{_bindir}/initoverlayfs-install 30 | install -D -m755 initoverlayfs ${RPM_BUILD_ROOT}/%{_sbindir}/initoverlayfs 31 | install -D -m755 lib/dracut/modules.d/81initoverlayfs/module-setup.sh ${RPM_BUILD_ROOT}/%{_prefix}/lib/dracut/modules.d/81initoverlayfs/module-setup.sh 32 | install -D -m644 lib/systemd/system/pre-initoverlayfs.target ${RPM_BUILD_ROOT}/%{_prefix}/lib/systemd/system/pre-initoverlayfs.target 33 | install -D -m644 lib/systemd/system/pre-initoverlayfs.service ${RPM_BUILD_ROOT}/%{_prefix}/lib/systemd/system/pre-initoverlayfs.service 34 | install -D -m644 lib/systemd/system/pre-initoverlayfs-switch-root.service ${RPM_BUILD_ROOT}/%{_prefix}/lib/systemd/system/pre-initoverlayfs-switch-root.service 35 | install -D -m755 lib/kernel/install.d/94-initoverlayfs.install ${RPM_BUILD_ROOT}/%{_prefix}/lib/kernel/install.d/94-initoverlayfs.install 36 | 37 | %files 38 | %license LICENSE 39 | %doc README.md 40 | %attr(0755,root,root) 41 | %{_bindir}/initoverlayfs-install 42 | %{_sbindir}/initoverlayfs 43 | %{_prefix}/lib/dracut/modules.d/81initoverlayfs/ 44 | %{_prefix}/lib/systemd/system/pre-initoverlayfs.target 45 | %{_prefix}/lib/systemd/system/pre-initoverlayfs.service 46 | %{_prefix}/lib/systemd/system/pre-initoverlayfs-switch-root.service 47 | %{_prefix}/lib/kernel/install.d/94-initoverlayfs.install 48 | 49 | %changelog 50 | * Wed Mar 20 2024 Stephen Smoogen - 0.995-1 51 | - Create tag for new RPM release. 52 | 53 | * Tue Mar 12 2024 Eric Curtin - 0.994-1 54 | - Automatically rebuild initoverlayfs on kernel update. 55 | 56 | * Mon Mar 11 2024 Eric Curtin - 0.993-1 57 | - Add option to use initoverlayfs as an init system and mount 58 | storage immediately. 59 | - Used in Automotive, requires storage drivers to be built 60 | directly in the kernel. 61 | 62 | * Mon Feb 12 2024 Eric Curtin - 0.992-1 63 | - Update to 0.992 release. 64 | - Automatically rebuild kernel on upgrade (ecurtin) 65 | - Build initrd in no-hostonly mode for generic initrd (ecurtin) 66 | 67 | * Wed Jan 17 2024 Stephen Smoogen - 0.991-1 68 | - Update to 0.991 release. 69 | - fork storage-init as a systemd unit (ecurtin) 70 | - shell-less initrd work (ecurtin) 71 | 72 | * Thu Dec 14 2023 Stephen Smoogen - 0.99-1 73 | - Update to 0.99 release. 74 | - shellcheck corrections (Yariv) 75 | - install: detect where the initramfs exists (Douglas) 76 | - fix bootfs wait (Eric) 77 | - Add code for packit 78 | - Add autotools items for building. 79 | 80 | * Thu Dec 7 2023 Stephen Smoogen - 0.98-1 81 | - Release 0.98 82 | - Improve documentation (PR31 and ecurtin) 83 | - Only wait for bootfs storage device if it is configured (PR32) 84 | 85 | * Fri Nov 17 2023 Eric Curtin - 0.97-1 86 | - Raspberry Pi 4 enablement. 87 | 88 | * Wed Nov 8 2023 Stephen Smoogen - 0.96-2 89 | - Make changes to pass fedora-review tests on permissions and other items 90 | 91 | * Tue Oct 24 2023 Eric Curtin - 0.96-1 92 | - Leave initoverlayfs dracut module out of initoverlayfs. 93 | * Tue Oct 24 2023 Eric Curtin - 0.95-1 94 | - Install script changes. 95 | * Wed Oct 18 2023 Eric Curtin - 0.94-1 96 | - Add partlabel support. 97 | * Mon Oct 16 2023 Eric Curtin - 0.93-1 98 | - More optimization, can now install initoverlayfs files for kernels that 99 | aren't currently running. 100 | * Fri Oct 13 2023 Eric Curtin - 0.92-1 101 | - Add default fs and fstype. 102 | * Fri Oct 13 2023 Eric Curtin - 0.91-1 103 | - Rm custom tokenizer, replace with strtok. 104 | * Thu Oct 12 2023 Eric Curtin - 0.9-1 105 | - Change to bls_parser, split into separate files. Remove grub dependency. 106 | * Thu Oct 5 2023 Eric Curtin - 0.8-1 107 | - Change to initoverlayfs.bootfs and initoverlay.bootfstype 108 | * Wed Oct 4 2023 Eric Curtin - 0.7-1 109 | - Some bugfixes, leading / in fs=, asprintf check incorrect 110 | * Tue Oct 3 2023 Eric Curtin - 0.6-1 111 | - initoverlayfs-install generates /etc/initoverlayfs.conf 112 | * Mon Oct 2 2023 Eric Curtin - 0.5-1 113 | - Add dracut files to initramfs 114 | * Wed Sep 27 2023 Eric Curtin - 0.4-1 115 | - Package initoverlayfs 116 | 117 | -------------------------------------------------------------------------------- /integration/plans/tier-0.fmf: -------------------------------------------------------------------------------- 1 | summary: Tier 0 - initoverlayfs 2 | 3 | discover: 4 | how: fmf 5 | filter: tier:0 6 | 7 | provision: 8 | how: local 9 | 10 | adjust: 11 | - when: distro == centos-stream-9 12 | prepare+: 13 | - name: Prepare c9s Repo 14 | how: shell 15 | order: 10 16 | script: | 17 | dnf install -y epel-release 18 | 19 | - when: scenario == manual 20 | prepare+: 21 | - name: Prepare Repos 22 | how: shell 23 | order: 10 24 | script: | 25 | dnf copr enable -y @centos-automotive-sig/next 26 | 27 | prepare: 28 | - name: Install rpms 29 | how: install 30 | package: initoverlayfs 31 | 32 | - name: Verify rpm 33 | how: shell 34 | script: | 35 | echo $(rpm -qa | grep -i initoverlayfs) 36 | 37 | execute: 38 | how: tmt 39 | 40 | report: 41 | how: junit 42 | 43 | -------------------------------------------------------------------------------- /integration/tests/boot-validation/main.fmf: -------------------------------------------------------------------------------- 1 | summary: test verify install is working 2 | test: ./test.sh 3 | tier: 0 4 | framework: shell 5 | 6 | -------------------------------------------------------------------------------- /integration/tests/boot-validation/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x 4 | 5 | if [ "${TMT_REBOOT_COUNT}" == "1" ];then 6 | echo -n "machine is up" 7 | storage_init=$(journalctl -r | grep "init -> usr/sbin/initoverlayfs" | tail -1) 8 | exit_code="$?" 9 | if [ "$exit_code" != "0" ]; then 10 | echo -n "initoverlayfs, initoverlayfs messages not found in journal" 11 | exit "$exit_code" 12 | fi 13 | echo -n "initoverlayfs boot complete" 14 | exit 0 15 | fi 16 | 17 | RPM_EXIST=$(rpm -qa | grep -i initoverlayfs) 18 | 19 | if [ -z "${RPM_EXIST}" ]; then 20 | echo -n "initoverlayfs rpm is missing" 21 | exit 127 22 | fi 23 | 24 | echo -n "Install initoverlayfs" 25 | /usr/bin/initoverlayfs-install -f 26 | 27 | exit_code="$?" 28 | 29 | if [ "$exit_code" != "0" ]; then 30 | echo -n "initoverlayfs-install completeted with $exit_code" 31 | exit "$exit_code" 32 | fi 33 | 34 | echo -n "Verifying initoverlayfs in /boot" 35 | du -sh /boot/init* | grep initoverlayfs 36 | 37 | exit_code="$?" 38 | if [ "$exit_code" != "0" ]; then 39 | echo -n "initoverlayfs not found under /boot please check runnin machine" 40 | exit "$exit_code" 41 | fi 42 | 43 | /usr/local/bin/tmt-reboot 44 | 45 | -------------------------------------------------------------------------------- /lib/dracut/modules.d/81initoverlayfs/module-setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | installkernel() { 4 | hostonly="" instmods erofs overlay loop 5 | } 6 | 7 | install() { 8 | INITOVERLAYFS_CONF="/etc/initoverlayfs.conf" 9 | INITOVERLAYFS_INIT=$(sed -ne "s/^initoverlayfs_init\s//pg" "$INITOVERLAYFS_CONF") 10 | USE_SYSTEMD="true" 11 | if [ "$INITOVERLAYFS_INIT" = "true" ]; then 12 | USE_SYSTEMD="false" 13 | fi 14 | 15 | inst_multiple -o $INITOVERLAYFS_CONF /usr/sbin/initoverlayfs \ 16 | /usr/sbin/blkid /usr/bin/bash "/etc/initoverlayfs-hash-$kernel.img" 17 | 18 | if $USE_SYSTEMD; then 19 | inst_multiple -o "$systemdsystemunitdir/pre-initoverlayfs.target" \ 20 | "$systemdsystemunitdir/pre-initoverlayfs.service" \ 21 | "$systemdsystemunitdir/pre-initoverlayfs-switch-root.service" 22 | fi 23 | 24 | inst_dir /boot /initrofs /overlay /overlay/upper /overlay/work /initoverlayfs 25 | 26 | if $USE_SYSTEMD; then 27 | $SYSTEMCTL -q --root "$initdir" set-default pre-initoverlayfs.target 28 | $SYSTEMCTL -q --root "$initdir" add-wants sysinit.target pre-initoverlayfs.service 29 | $SYSTEMCTL -q --root "$initdir" add-wants sysinit.target pre-initoverlayfs-switch-root.service 30 | else 31 | ln_r "/usr/sbin/initoverlayfs" "/usr/sbin/init" 32 | ln_r "/usr/sbin/initoverlayfs" "/init" 33 | fi 34 | 35 | > "${initdir}/usr/bin/bash" 36 | } 37 | 38 | -------------------------------------------------------------------------------- /lib/kernel/install.d/94-initoverlayfs.install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | COMMAND="$1" 4 | KERNEL_VERSION="$2" 5 | 6 | case "$COMMAND" in 7 | add) 8 | initoverlayfs-install -f --kver "$KERNEL_VERSION" 9 | ;; 10 | remove) 11 | rm -f /boot/initoverlayfs-$KERNEL_VERSION 12 | ;; 13 | *) 14 | exit 0 15 | esac 16 | 17 | -------------------------------------------------------------------------------- /lib/systemd/system/pre-initoverlayfs-switch-root.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Switch Root pre-initoverlayfs 3 | AssertPathExists=/etc/initrd-release 4 | DefaultDependencies=no 5 | ConditionPathExists=/etc/initrd-release 6 | AllowIsolate=yes 7 | OnFailure=emergency.target 8 | OnFailureJobMode=replace-irreversibly 9 | Before=sysinit.target pre-initoverlayfs.target 10 | After=systemd-journald.service pre-initoverlayfs.service 11 | 12 | [Service] 13 | Type=oneshot 14 | ExecStart=systemctl --no-block switch-root /initoverlayfs 15 | 16 | -------------------------------------------------------------------------------- /lib/systemd/system/pre-initoverlayfs.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=pre-initoverlayfs initialization 3 | AssertPathExists=/etc/initrd-release 4 | DefaultDependencies=no 5 | ConditionPathExists=/etc/initrd-release 6 | OnFailure=emergency.target 7 | OnFailureJobMode=replace-irreversibly 8 | Before=sysinit.target pre-initoverlayfs.target 9 | After=systemd-journald.service 10 | 11 | [Service] 12 | Type=oneshot 13 | ExecStart=/usr/sbin/initoverlayfs --systemd 14 | StandardInput=null 15 | StandardOutput=journal+console 16 | StandardError=journal+console 17 | RemainAfterExit=yes 18 | 19 | -------------------------------------------------------------------------------- /lib/systemd/system/pre-initoverlayfs.target: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=pre-initoverlayfs Default Target 3 | OnFailure=emergency.target 4 | OnFailureJobMode=replace-irreversibly 5 | AssertPathExists=/etc/initrd-release 6 | Requires=basic.target 7 | After=pre-initoverlayfs.service rescue.target 8 | AllowIsolate=yes 9 | 10 | -------------------------------------------------------------------------------- /scripts/Containerfile: -------------------------------------------------------------------------------- 1 | FROM centos:stream9 2 | 3 | RUN dnf install -y valgrind clang gcc erofs-utils dracut rpm-build git 4 | 5 | -------------------------------------------------------------------------------- /scripts/build-bin-only.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | FLAGS="-lblkid" 6 | 7 | clang++ -o a -O3 $FLAGS -pedantic -fno-exceptions -fno-rtti -Wall -Wextra -Werror -Wno-write-strings -Wno-language-extension-token -Wno-deprecated -std=c++20 initoverlayfs.c & 8 | g++ -o b -O0 $FLAGS -ggdb -pedantic -fno-exceptions -fno-rtti -Wall -Wextra -Werror -Wno-write-strings -Wno-language-extension-token -std=c++20 initoverlayfs.c & 9 | clang -o c -O3 $FLAGS -pedantic -fno-exceptions -Wall -Wextra -Werror -Wno-language-extension-token initoverlayfs.c & 10 | 11 | if [ -e /usr/lib/rpm/redhat/redhat-hardened-cc1 ]; then 12 | gcc -O2 $FLAGS -fanalyzer -fno-exceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection initoverlayfs.c -o d & 13 | else 14 | gcc -O2 $FLAGS -fanalyzer -fno-exceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -fstack-protector-strong -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection initoverlayfs.c -o d & 15 | fi 16 | 17 | wait 18 | 19 | gcc -O0 $FLAGS -fno-exceptions -ggdb -pedantic -Wall -Wextra -Werror -fanalyzer initoverlayfs.c 20 | #sudo valgrind ./a.out 21 | 22 | -------------------------------------------------------------------------------- /scripts/build-bls-parser.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | clang -o c -O3 -pedantic -Wno-gnu-conditional-omitted-operand -Wall -Wextra -Werror -Wno-language-extension-token bls-parser.c & 6 | 7 | if [ -e /usr/lib/rpm/redhat/redhat-hardened-cc1 ]; then 8 | gcc -O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wno-gnu-conditional-omitted-operand -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection bls-parser.c -o d & 9 | else 10 | gcc -O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wno-gnu-conditional-omitted-operand -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -fstack-protector-strong -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection bls-parser.c -o d & 11 | fi 12 | 13 | wait 14 | 15 | gcc -O0 -ggdb -Wall -Wextra -Werror -Wno-language-extension-token -fanalyzer bls-parser.c 16 | valgrind ./a.out 17 | 18 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # https://www.shellcheck.net/wiki/SC2016 -- Expressions don't expand in singl... 4 | # this comes up due to us trying to output a shell script which needs to keep its variables. Safe to disable 5 | # shellcheck disable=SC2016 6 | # this needs a larger set of hand tests to fix. 7 | # https://www.shellcheck.net/wiki/SC2086 -- Double quote to prevent globbing ... 8 | # shellcheck disable=SC2086 9 | 10 | set -e 11 | 12 | failure() { 13 | local lineno=$1 14 | local msg=$2 15 | echo "Failed at $lineno: $msg" 16 | } 17 | 18 | trap 'failure ${LINENO} "$BASH_COMMAND"' ERR 19 | 20 | 21 | release=$(uname -r) 22 | 23 | DIR_TO_DUMP_INITRAMFS="/run/initoverlayfs" 24 | #UUID="1dd3a986-997c-0c48-1d1b-b0d0399f3153" 25 | 26 | fs="erofs" 27 | 28 | extract_initrd_into_initoverlayfs() { 29 | sudo mkdir -p "$DIR_TO_DUMP_INITRAMFS" 30 | 31 | file_type=$(file /boot/initramfs-"$release".img) 32 | decompressor="lz4cat" 33 | decompressor_dracut="--lz4" 34 | if [[ "$file_type" == *"ASCII cpio archive (SVR4 with no CRC)"* ]]; then 35 | decompressor_dracut="" 36 | decompressor="zcat" 37 | elif [[ "$file_type" == *"regular file, no read permission"* ]]; then 38 | decompressor_dracut="" 39 | decompressor="zcat" 40 | fi 41 | 42 | if command -v mkfs.erofs; then 43 | cd /run/initoverlayfs/ 44 | sudo /usr/lib/dracut/skipcpio /boot/initramfs-"$release".img | "$decompressor" | sudo cpio -ivd 45 | cd - 46 | else 47 | fs="ext4" 48 | dd if=/dev/zero of=/boot/initoverlayfs-"$release".img bs=64M count=1 49 | dev=$(sudo losetup -fP --show /boot/initoverlayfs-"$release".img) 50 | sudo mkfs."$fs" "$dev" 51 | sudo mount "$dev" "$DIR_TO_DUMP_INITRAMFS" 52 | cd "$DIR_TO_DUMP_INITRAMFS" 53 | sudo /usr/lib/dracut/skipcpio /boot/initramfs-"$release".img | zstd -d --stdout | sudo cpio -ivd 54 | sudo sync 55 | cd - 56 | while ! sudo umount "$DIR_TO_DUMP_INITRAMFS"; do 57 | sleep 1 58 | done 59 | 60 | sudo losetup -d "$dev" 61 | fi 62 | } 63 | 64 | cd 65 | #epoch=$(date +%s) 66 | # systemd-analyze > systemd-analyze$epoch.txt 67 | #journalctl --output=short-monotonic > journalctl$epoch.txt 68 | #journalctl --output=short-monotonic | grep -i "Reached target" > reached_target$epoch.txt 69 | #sed -i "s/UUID=2aadcf0d-81dc-4b21-99ef-74b96bb357ad/# UUID=2aadcf0d-81dc-4b21-99ef-74b96bb357ad/g" /etc/fstab 70 | if false; then 71 | cp mount-sysroot.service /usr/lib/systemd/system/ 72 | mkdir -p /usr/lib/dracut/modules.d/00early-boot-service 73 | cat << 'EOF' > /usr/lib/dracut/modules.d/00early-boot-service/module-setup.sh 74 | #!/usr/bin/bash 75 | 76 | install() { 77 | inst_multiple -o \ 78 | "$systemdsystemunitdir"/mount-sysroot.service \ 79 | "$systemdsystemunitdir"/sysinit.target.wants/mount-sysroot.service 80 | } 81 | EOF 82 | 83 | sed -i "s/initrd-udevadm-cleanup-db.service/initrd-udevadm-cleanup-db.service mount-sysroot.service/g" /usr/lib/systemd/system/initrd-switch-root.target 84 | chcon system_u:object_r:systemd_unit_file_t:s0 /usr/lib/systemd/system/mount-sysroot.service 85 | cd /usr/lib/systemd/system/sysinit.target.wants/ 86 | ln -s ../mount-sysroot.service . 87 | cd - 88 | systemctl daemon-reload 89 | du -sh /boot/initramfs* 90 | dracut -f --lz4 91 | fi 92 | 93 | set -ex 94 | 95 | cd ~/git/initoverlayfs 96 | if [ "$2" = "initramfs" ]; then 97 | sudo clang -O3 -pedantic -Wall -Wextra -Werror -Wno-language-extension-token initoverlayfs.c -o /usr/sbin/initoverlayfs 98 | sudo gcc -O3 -pedantic -Wall -Wextra -Werror -Wno-language-extension-token -fanalyzer initoverlayfs.c -o /usr/sbin/initoverlayfs 99 | 100 | sudo cp -r lib/dracut/modules.d/81initoverlayfs /usr/lib/dracut/modules.d/ 101 | sudo cp -r lib/dracut/modules.d/81kamoso /usr/lib/dracut/modules.d/ 102 | du -sh /boot/initramfs* 103 | sudo dd if=/dev/urandom of=/usr/bin/random-file count=1 bs="$1" 104 | sudo dracut --lz4 -v -f --strip -f -M 105 | exit 0 106 | fi 107 | 108 | # sudo lsinitrd | grep "init\|boot\|overlay\|erofs" 109 | 110 | UNLOCK_OVERLAYDIR="$DIR_TO_DUMP_INITRAMFS" 111 | extract_initrd_into_initoverlayfs 112 | sudo mkdir -p "$UNLOCK_OVERLAYDIR/upper" "$UNLOCK_OVERLAYDIR/work" 113 | # sudo valgrind /usr/sbin/initoverlayfs 114 | # sudo ln -sf initoverlayfs $DIR_TO_DUMP_INITRAMFS/usr/sbin/init 115 | # sudo ln -sf usr/bin/initoverlayfs $DIR_TO_DUMP_INITRAMFS/init 116 | if [ $fs == "erofs" ]; then 117 | sudo mkfs."$fs" /boot/initoverlayfs-"$release".img /run/initoverlayfs/ 118 | fi 119 | #sudo losetup -fP /boot/initoverlayfs-"$release".img 120 | # ln -s init /usr/sbin/initoverlayfs 121 | initramfs=$(sudo ls /boot/initramfs-* | grep -v rescue | tail -n1) 122 | sudo du -sh "$initramfs" 123 | #sudo dracut -v -f --strip $initramfs -M 124 | #sudo lsinitrd 125 | sudo du -sh /boot/initramfs* 126 | sudo cp -r lib/dracut/modules.d/81initoverlayfs /usr/lib/dracut/modules.d/ 127 | sudo rm -rf /usr/lib/dracut/modules.d/*pre-initramfs 128 | sudo rm -rf /usr/lib/dracut/modules.d/*pre-initoverlayfs 129 | 130 | set -x 131 | 132 | sudo clang -O3 -pedantic -Wall -Wextra -Werror -Wno-language-extension-token initoverlayfs.c -o /usr/sbin/initoverlayfs 133 | sudo gcc -O3 -pedantic -Wall -Wextra -Werror -Wno-language-extension-token -fanalyzer initoverlayfs.c -o /usr/sbin/initoverlayfs 134 | #sudo dracut $decompressor_dracut -v -m "kernel-modules udev-rules initoverlayfsramfs" -f --strip -M -o "nss-softokn bash i18n kernel-modules-extra rootfs-block dracut-systemd usrmount base fs-lib shutdown systemd systemd-initrd" # systemd-initrd (req by systemd) 135 | boot_partition=$(mount | grep "on /boot type" | awk '{print $1}') 136 | sudo /bin/bash -c "echo -e \"bootfs $boot_partition\nbootfstype ext4\n\" > /etc/initoverlayfs.conf" 137 | sudo dracut $decompressor_dracut -v -f --strip -M 138 | sudo du -sh /boot/initramfs* 139 | sudo lsinitrd | grep "initoverlayfs" 140 | sudo du -sh "$initramfs" 141 | # sed -i '/^initrd /d' /boot/loader/entries/9c03d22e1ec14ddaac4f0dabb884e434-$release.conf 142 | 143 | bls_file=$(sudo ls /boot/loader/entries/ | grep -v rescue | tail -n1) 144 | # should be ro rhgb quiet, cannot remount ro, but can fix 145 | #uuid=$(grep "boot.*ext4" /etc/fstab | awk '{print $1}' | sed s/UUID=//g) 146 | #sudo sed -i '/boot.*ext4/d' /etc/fstab 147 | sudo systemctl daemon-reload 148 | #sudo sed -i "s#options #options initoverlayfs=UUID=$uuid initoverlayfstype=ext4 rdinit=/usr/sbin/initoverlayfs #g" /boot/loader/entries/$bls_file 149 | #sudo sed -i "s#options #options initoverlayfs=$boot_partition initoverlayfstype=ext4 rdinit=/usr/sbin/initoverlayfs #g" /boot/loader/entries/$bls_file 150 | sudo sed -i "s/ quiet/ console=ttyS0/g" /boot/loader/entries/$bls_file 151 | sudo cat /boot/loader/entries/$bls_file 152 | -------------------------------------------------------------------------------- /scripts/gnuplot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if true; then 6 | true > initramfs.txt 7 | true > initoverlayfs.txt 8 | true > initramfs_systemd_after_switch_root.txt 9 | true > initoverlayfs_systemd_after_switch_root.txt 10 | fi 11 | 12 | #size=68 13 | #size="$(echo "$i * 8" | bc)" 14 | for i in {1..11} ; do 15 | initramfstime=$(grep -m1 -i "systemd 2" "initrd-$i.txt" | sed "s/\[//g" | sed "s/\]//g" | awk "{print \$1}") 16 | initoverlayfstime=$(grep -m1 -i "systemd 2" "initoverlayfs-$i.txt" | sed "s/\[//g" | sed "s/\]//g" | awk "{print \$1}") 17 | initramfstime_systemd_after_switch_root=$(grep -m2 -i "systemd 2" "initrd-$i.txt" | tail -n1 | sed "s/\[//g" | sed "s/\]//g" | awk "{print \$1}") 18 | initoverlayfstime_systemd_after_switch_root=$(grep -m2 -i "systemd 2" "initoverlayfs-$i.txt" | tail -n1 | sed "s/\[//g" | sed "s/\]//g" | awk "{print \$1}") 19 | # initramfstime=$(grep -m1 -i "Reached target initrd-switch-root" legacy-plus-data$i.txt | awk "{print \"$size \"\$1}") 20 | # initoverlayfstime=$(grep -m1 -i "starting kmod" initoverlayfs$i.txt | awk "{print \$1}") 21 | # initramfstime=$(grep -m1 -i "starting kmod" legacy-plus-data$i.txt | awk "{print \$1}") 22 | echo "$i $initoverlayfstime" >> initoverlayfs.txt 23 | echo "$i $initramfstime" >> initramfs.txt 24 | echo "$i $initoverlayfstime_systemd_after_switch_root" >> initoverlayfs_systemd_after_switch_root.txt 25 | echo "$i $initramfstime_systemd_after_switch_root" >> initramfs_systemd_after_switch_root.txt 26 | 27 | # echo "initoverlayfstime: $initoverlayfstime initramfstime: $initramfstime" 28 | # if (( $(echo "$initramfstime > $initoverlayfstime" | bc -l) )); then 29 | # echo "initoverlayfs is faster" 30 | # else 31 | # echo "initramfs is faster" 32 | # fi 33 | done 34 | 35 | if true; then 36 | t="png" 37 | echo "set terminal $t 38 | set output 'initramfs-vs-initoverlayfs.$t' 39 | set xlabel 'Iteration' 40 | set ylabel 'Seconds' 41 | set key at graph 1, 0.8 42 | 43 | plot 'initramfs.txt' using 1:(\$2/1) title 'initramfs - systemd start' with lines lw 2, \ 44 | 'initramfs_systemd_after_switch_root.txt' using 1:(\$2/1) title 'initramfs - systemd start after 1st switch-root' with lines lw 2, \ 45 | 'initoverlayfs.txt' using 1:(\$2/1) title 'initoverlayfs - systemd start' with lines lw 2, \ 46 | 'initoverlayfs_systemd_after_switch_root.txt' using 1:(\$2/1) title 'initoverlayfs - systemd start after 1st switch-root' with lines lw 2" \ 47 | | gnuplot 48 | fi 49 | -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | REL="$(git tag | tail -1)" 6 | mkdir -p "$HOME/rpmbuild/SOURCES/" 7 | git archive -o "$HOME/rpmbuild/SOURCES/initoverlayfs-$REL.tar.gz" --prefix "initoverlayfs-$REL/" HEAD 8 | ./build-scripts/create-spec.sh 9 | rpmbuild_output=$(rpmbuild -bb initoverlayfs.spec 2>&1) 10 | rpm_to_install=$(echo "$rpmbuild_output" | grep "Wrote:" | awk '{print $2}') 11 | if rpm -Uvh "$rpm_to_install"; then 12 | echo "$rpmbuild_output" 13 | fi 14 | 15 | initoverlayfs-install -f "$1" 16 | 17 | -------------------------------------------------------------------------------- /scripts/install_perf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | REL="$(git tag | tail -1)" 6 | mkdir -p "$HOME/rpmbuild/SOURCES/" 7 | git archive -o "$HOME/rpmbuild/SOURCES/initoverlayfs-$REL.tar.gz" --prefix "initoverlayfs-$REL/" HEAD 8 | ./build-scripts/create-spec.sh 9 | rpmbuild_output=$(rpmbuild -bb initoverlayfs.spec 2>&1) 10 | rpm_to_install=$(echo "$rpmbuild_output" | grep "Wrote:" | awk '{print $2}') 11 | if rpm -Uvh "$rpm_to_install"; then 12 | echo "$rpmbuild_output" 13 | fi 14 | 15 | if [ "$1" = "rootfs" ]; then 16 | head -c "$2" /dev/urandom > /usr/bin/binary 17 | cp binary-reader.service /usr/lib/systemd/system/ 18 | gcc -O3 read.c -o /usr/bin/binary-reader 19 | ln -fs ../binary-reader.service /usr/lib/systemd/system/sysinit.target.wants/ 20 | dracut -f -o initoverlayfs 21 | elif [ "$1" = "initrd" ]; then 22 | rm -f /usr/lib/systemd/system/sysinit.target.wants/binary-reader.service 23 | mkdir -p /usr/lib/dracut/modules.d/81early-service/ 24 | cp lib/dracut/modules.d/81early-service/module-setup.sh /usr/lib/dracut/modules.d/81early-service/ 25 | dracut -f -o initoverlayfs 26 | elif [ "$1" = "initoverlayfs" ]; then 27 | rm -rf /boot/initr* 28 | initoverlayfs-install -f --initoverlayfs-init 29 | fi 30 | 31 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # disable shellcheck for "Double quote to prevent globbing" until tested fix 3 | # shellcheck disable=SC2086 4 | # 5 | 6 | set -ex 7 | 8 | USER="$(id -un)" 9 | REL="$(git tag | tail -1)" 10 | UNAME_M="$(uname -m)" 11 | if command -v distrobox-enter; then 12 | distrobox-enter -r centos-stream9 -- /bin/bash -c "sudo dnf install -y epel-release && sudo dnf install -y valgrind clang gcc erofs-utils dracut rpm-build git && cd $PWD && scripts/build-bin-only.sh && mkdir -p /home/$USER/rpmbuild/SOURCES/ && git archive -o /home/$USER/rpmbuild/SOURCES/initoverlayfs-$REL.tar.gz --prefix initoverlayfs-$REL/ HEAD && rpmbuild -ba *.spec && sudo mkdir -p /boot /initrofs /overlay /overlay/upper /overlay/work /initoverlayfs && sudo rpm --force -U ~/rpmbuild/RPMS/$UNAME_M/initoverlayfs-$REL-1.el9.$UNAME_M.rpm && sudo dracut -f --no-kernel && sudo initoverlayfs-install && sudo valgrind ./a.out" 13 | else 14 | sudo podman build -t initoverlayfs . 15 | sudo podman run --rm -it -v "$PWD:$PWD" initoverlayfs -- /bin/bash -c "sudo dnf install -y epel-release && sudo dnf install -y valgrind clang gcc erofs-utils dracut rpm-build git && cd $PWD && scripts/build-bin-only.sh && mkdir -p /home/$USER/rpmbuild/SOURCES/ && git archive -o /home/$USER/rpmbuild/SOURCES/initoverlayfs-$REL.tar.gz --prefix initoverlayfs-$REL/ HEAD && rpmbuild -ba *.spec && sudo mkdir -p /boot /initrofs /overlay /overlay/upper /overlay/work /initoverlayfs && sudo rpm --force -U ~/rpmbuild/RPMS/$UNAME_M/initoverlayfs-$REL-1.el9.$UNAME_M.rpm && sudo dracut -f --no-kernel && sudo initoverlayfs-install && sudo valgrind ./a.out" 16 | fi 17 | 18 | -------------------------------------------------------------------------------- /scsi_probe.note.md: -------------------------------------------------------------------------------- 1 | # SCSI Probe note 2 | 3 | This note aims to provide information to the addition of SCSI probe 4 | functionality. 5 | The focus here is on addressing a particular practice among certain silicon 6 | vendors BSPs, where they partition storage devices into multiple LUNs and 7 | partitions. 8 | A notable scenario involves fragmenting a single UFS memory device into 8 9 | LUNs and over 100 partitions. 10 | In such cases, the boot sequence for these devices can experience 11 | significant delays due to the enumeration of numerous partitions, often with 12 | data that is only required later in the boot sequence or not needed at all. 13 | 14 | By default, the kernel scans all channels, targets, and LUNs of SCSI devices, 15 | as the UFS interface provides to the operating system. 16 | Consequently, the OS spends time enumerating all found partitions. 17 | To address this issue, the SCSI Probe feature requires that the kernel does 18 | not automatically scan all SCSI targets (`scsi_mod.scan=manual` kernel boot 19 | argument). 20 | Instead, it allows specifying the LUN where the root filesystem is located. 21 | 22 | The mechanism for specifying the LUN can be achieved either via the kernel 23 | command line (`scsi.addr=:::`) or by adding a 24 | line to the `initoverlayfs.config` file 25 | (`scsi.addr :::`). 26 | If both methods are used, the configuration in the `initoverlayfs.config` file 27 | takes precedence. 28 | 29 | It's important to note that the presence of the `scsi_mod.scan=manual` kernel 30 | argument is essential for any operation to occur. 31 | If this argument is not included in the kernel command line, no action will 32 | be taken. 33 | 34 | Although the feature was specifically developed for the UFS use case, it is 35 | intended to function with all SCSI `sd` devices. 36 | 37 | # build initoverlayfs with SCSI Probe support 38 | 39 | At the time of writing this document, there is no sophisticated build system 40 | in place for initoverlayfs. 41 | The functionality related to SCSI Probe relies on the `SCSI_PROBE` symbol, 42 | which must be passed during the build process. 43 | Presently, the build steps are specified in the `initoverlayfs.spec.in` file. 44 | To compile initoverlayfs with SCSI Probe support, you need to modify 45 | `initoverlayfs.spec.in` by adding `-D SCSI_PROBE` to the `RPM_OPT_FLAGS` 46 | variable. Alternatively, you can compile it manually, as demonstrated below: 47 | ``` 48 | gcc -static -D SCSI_PROBE -Os initoverlayfs.c \ 49 | /usr/lib/x86_64-linux-gnu/libblkid.a scsi_probe/scsi_probe.c \ 50 | -o initoverlayfs 51 | ``` 52 | 53 | This modification alone may be sufficient, as the target for manual scanning 54 | can be specified using kernel boot arguments. 55 | However, if you prefer to specify it using `initoverlayfs.conf`, you may need 56 | to modify the `scripts/build.sh` script to add a line to the configuration it 57 | generates. 58 | 59 | # Test with qemu 60 | 61 | As mentioned, the feature is tailored specifically for embedded/mobile 62 | devices utilizing UFS as storage. While it's designed to be compatible 63 | with any SCSI setup, it's advisable for individuals to test it in a 64 | nearly real-world scenario before deploying it in production. 65 | 66 | Since September 2023 mapping Version `8.0`, qemu has offered UFS PCI 67 | emulation. 68 | 69 | With qemu supporting UFS, here's an example command to test UFS: 70 | 71 | ``` 72 | qemu-system-x86_64 -m 512M -nographic -smp 1 -kernel ./bzImage \ 73 | -initrd \initramfs.img \ 74 | -append "console=ttyS0 pippo raid=noautodetect init=/sbin/init scsi_mod.scan=manual" \ 75 | -device ufs,id=bus0 -device ufs-lu,drive=ufs1,bus=bus0,lun=0 \ 76 | -drive if=none,file=ufsimage.img,format=raw,id=ufs1 77 | ``` 78 | -------------------------------------------------------------------------------- /scsi_probe/Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CFLAGS= -g 3 | #QUIET = &>/dev/null 4 | VALGRIND_FLAGS= --error-exitcode=1 --leak-check=full -s 5 | 6 | tests: tests_res/parse_kernel_cmdline.exec.log tests_res/parse_kernel_cmdline.valgrind.log tests_res/fetch_kernel_cmdline.exec.log tests_res/fetch_kernel_cmdline.valgrind.log tests_res/trigger_scan.valgrind.log tests_res/trigger_scan.exec.log 7 | 8 | ################################################################ 9 | tests_res/parse_kernel_cmdline.exec.log: tests/parse_kernel_cmdline 10 | @echo "======================= Testing... " $< " exec" 11 | @$< > $@ 12 | 13 | tests_res/parse_kernel_cmdline.valgrind.log: tests/parse_kernel_cmdline 14 | @echo "======================= Testing... " $< "valgrind" 15 | @valgrind $(VALGRIND_FLAGS) $< >$@ 2>&1 16 | 17 | tests/parse_kernel_cmdline: tests/parse_kernel_cmdline.c scsi_probe.o 18 | @echo "======================= Build... " $< 19 | @$(CC) $(CFLAGS) $^ -o $@ 20 | 21 | ########################################################################### 22 | tests_res/fetch_kernel_cmdline.exec.log: tests/fetch_kernel_cmdline 23 | @echo "======================= Testing... " $< " exec" 24 | @$< > $@ 25 | 26 | tests_res/fetch_kernel_cmdline.valgrind.log: tests/fetch_kernel_cmdline 27 | @echo "======================= Testing... " $< "valgrind" 28 | @valgrind $(VALGRIND_FLAGS) $< >$@ 2>&1 29 | 30 | tests/fetch_kernel_cmdline: tests/fetch_kernel_cmdline.c scsi_probe.o 31 | @echo "======================= Build... " $< 32 | @$(CC) $(CFLAGS) $^ -o $@ 33 | 34 | ########################################################################### 35 | tests_res/trigger_scan.exec.log: tests/trigger_scan 36 | @echo "======================= Testing... " $< " exec" 37 | @$< > $@ 38 | 39 | tests_res/trigger_scan.valgrind.log: tests/trigger_scan 40 | @echo "======================= Testing... " $< "valgrind" 41 | @valgrind $(VALGRIND_FLAGS) $< >$@ 2>&1 42 | 43 | tests/trigger_scan: tests/trigger_scan.c scsi_probe.o 44 | @echo "======================= Build... " $< 45 | @$(CC) $(CFLAGS) $^ -o $@ 46 | 47 | ########################################################################### 48 | scsi_probe.o: scsi_probe.c scsi_probe.h 49 | @echo "======================= Build... " $< 50 | @$(CC) $(CFLAGS) -c $< -o $@ 51 | 52 | clean: 53 | @echo "======================= CLEAN... " 54 | @rm -f scsi_probe.o tests/parse_kernel_cmdline tests_res/*.log 55 | -------------------------------------------------------------------------------- /scsi_probe/scsi_probe.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "scsi_probe.h" 6 | 7 | static int c2i(char *str){ 8 | char *endptr; 9 | int num; 10 | 11 | errno = 0; 12 | num = strtol(str, &endptr, 10); 13 | if ((errno != 0 && num == 0) || (num > 99)) return -1; 14 | if (endptr == str) return -1; 15 | return num; 16 | } 17 | 18 | inline int update_scsi_args(const char *ba_str, struct args *ba){ 19 | char *token; 20 | char *ba_str2; 21 | char *tmp1, *tmp2; 22 | 23 | if (!ba_str) return 0; 24 | if ((ba_str2 = strdup(ba_str)) == NULL) return 0; 25 | token = strtok(ba_str2, " "); 26 | while (token != NULL) { 27 | if (strncmp(token, SCSI_ADDR_BOOT_ARG, strlen(SCSI_ADDR_BOOT_ARG)) == 0) { 28 | tmp1 = strchr(token, '='); 29 | if (tmp1 != NULL) { 30 | tmp2 = tmp1 + 1; 31 | sscanf(tmp2, "%d:%d:%d:%d", &ba->scsi_host, &ba->scsi_channel, &ba->scsi_id, &ba->scsi_lun); 32 | } 33 | } 34 | token = strtok(NULL, " "); 35 | } 36 | free(ba_str2); 37 | return 1; 38 | } 39 | 40 | 41 | int parse_kernel_cmdline(const char *ba_str, struct args *ba){ 42 | memset(ba, 0, sizeof(struct args)); 43 | ba->scsi_manual=strstr(ba_str, "scsi_mod.scan=manual")?1:0; 44 | return update_scsi_args(ba_str, ba); 45 | } 46 | 47 | char *fetch_kernel_cmdline(const char *cmdline_fn){ 48 | FILE *file; 49 | char *cmdline; 50 | 51 | file = fopen(cmdline_fn, "r"); 52 | if (file == NULL) return NULL; 53 | cmdline = (char *) malloc(MAX_BUF); 54 | if (! fgets(cmdline, MAX_BUF, file)) { 55 | free(cmdline); 56 | return NULL; 57 | } 58 | fclose(file); 59 | return cmdline; 60 | } 61 | 62 | 63 | int trigger_scan(struct args *ba, const char *scsi_sys_tmpl, const int scsi_sys_tmpl_sz) { 64 | FILE *file; 65 | char buf[128]; 66 | int n; 67 | 68 | if (ba->scsi_manual==0) return 0; 69 | snprintf(buf, scsi_sys_tmpl_sz, scsi_sys_tmpl, ba->scsi_host); 70 | file = fopen(buf, "w"); 71 | if (file == NULL) return 0; 72 | n = snprintf(buf, MAX_BUF, SCSI_SYS_SCAN_STR, ba->scsi_channel, ba->scsi_id, ba->scsi_lun); 73 | fwrite(buf, n, 1, file); 74 | fclose(file); 75 | return 1; 76 | } 77 | -------------------------------------------------------------------------------- /scsi_probe/scsi_probe.h: -------------------------------------------------------------------------------- 1 | #define MAX_ARGS 100 2 | #define MAX_BUF 128 3 | 4 | #define SCSI_SYS_SCAN_STR "%d %d %d\n" 5 | 6 | #define SCSI_ADDR_BOOT_ARG "scsi.addr" 7 | 8 | struct args { 9 | int scsi_manual; 10 | int scsi_host; 11 | int scsi_channel; 12 | int scsi_id; 13 | int scsi_lun; 14 | }; 15 | 16 | int update_scsi_args(const char *, struct args *); 17 | int parse_kernel_cmdline(const char *, struct args *); 18 | char *fetch_kernel_cmdline(const char *); 19 | int trigger_scan(struct args *, const char *, const int); 20 | -------------------------------------------------------------------------------- /scsi_probe/testfiles/cmd1: -------------------------------------------------------------------------------- 1 | pippo=1 pluto=2 paperino=3 2 | -------------------------------------------------------------------------------- /scsi_probe/testfiles/cmd2: -------------------------------------------------------------------------------- 1 | pippo=1 pluto=2 paperino=3 2 | pippo=z pluto=x paperino=c 3 | -------------------------------------------------------------------------------- /scsi_probe/testfiles/host0scan: -------------------------------------------------------------------------------- 1 | 1 2 3 2 | -------------------------------------------------------------------------------- /scsi_probe/testfiles/host2scan: -------------------------------------------------------------------------------- 1 | 3 4 5 2 | -------------------------------------------------------------------------------- /scsi_probe/tests/fetch_kernel_cmdline.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../scsi_probe.h" 5 | 6 | #define TEST_BUFF_SIZE 200 7 | 8 | char *results[] = { 9 | "pippo=1 pluto=2 paperino=3\n", 10 | "pippo=1 pluto=2 paperino=3\n", 11 | NULL 12 | }; 13 | 14 | char *test_patterns[] = { 15 | "testfiles/cmd1", 16 | "testfiles/cmd2", 17 | "testfiles/cmd3", 18 | }; 19 | 20 | int main(){ 21 | int i; 22 | char *res; 23 | 24 | for (i=0; i< sizeof(test_patterns)/sizeof(char *); i++) { 25 | res = fetch_kernel_cmdline(test_patterns[i]); 26 | printf( "Test pattern='%s', expected result='%s' Actual result ='%s' -> ", 27 | test_patterns[i], results[i], res); 28 | if (res && results[i]) { 29 | if (strcmp(res, results[i])!=0) { 30 | printf("Failed\n"); 31 | free(res); 32 | return -1; 33 | } 34 | } 35 | printf("Success\n"); 36 | free(res); 37 | } 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /scsi_probe/tests/parse_kernel_cmdline.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../scsi_probe.h" 4 | 5 | #define TEST_BUFF_SIZE 200 6 | 7 | char *results[] = { 8 | "has manual=1 host=1 addr=2:3:45", 9 | "has manual=1 host=1 addr=2:3:45", 10 | "has manual=1 host=0 addr=0:0:0", 11 | "has manual=0 host=0 addr=0:0:0" 12 | }; 13 | 14 | char *test_patterns[] = { 15 | "pippo=1 pluto=2 scsi_mod.scan=manual scsi.addr=1:2:3:45 paperino=peppe", 16 | "pippo=1 pluto=2 scsi_mod.scan=manual posw scsi.addr=1:2:3:45 paperino=peppe", 17 | "pippo=1 pluto=2 scsi_mod.scan=manual paperino=peppe", 18 | "pippo=1 pluto=2 paperino=peppe", 19 | 20 | }; 21 | 22 | int main(){ 23 | struct args ba; 24 | int i; 25 | char res[TEST_BUFF_SIZE]; 26 | 27 | for (i=0; i< sizeof(test_patterns)/sizeof(char *); i++) { 28 | if (!parse_kernel_cmdline(test_patterns[i], &ba)) return -1; 29 | sprintf(res, "has manual=%d host=%d addr=%d:%d:%d", ba.scsi_manual, ba.scsi_host, ba.scsi_channel, ba.scsi_id, ba.scsi_lun); 30 | 31 | printf( "Test pattern='%s', expected result='%s' Actual result ='%s' -> ", 32 | test_patterns[i], results[i], res); 33 | if (strcmp(res, results[i])) { 34 | printf("Failed\n"); 35 | return -1; 36 | } 37 | printf("Success\n"); 38 | } 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /scsi_probe/tests/trigger_scan.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../scsi_probe.h" 4 | 5 | #define TEST_BUFF_SIZE 200 6 | #define CMDLINE "cmdline" 7 | #define SCSI_SYS_TMPL "testfiles/host%dscan" 8 | #define SCSI_SYS_TMPL_SZ 30 9 | 10 | struct result { 11 | int res; 12 | char *fn; 13 | char *addr; 14 | }; 15 | 16 | struct result results[] = { 17 | {1, "testfiles/host0scan", "1 2 3\n"}, 18 | {1, "testfiles/host2scan", "3 4 5\n"}, 19 | {0, "testfiles/host0scan", "0 0 0\n"}, 20 | }; 21 | 22 | struct args test_patterns[] = { 23 | {1, 0, 1, 2, 3}, 24 | {1, 2, 3, 4, 5}, 25 | {0, 0, 1, 2, 3}, 26 | }; 27 | 28 | int verify(int res, struct args *test_pattern, struct result *r){ 29 | char buf[100]; 30 | int n; 31 | FILE *file; 32 | 33 | if (res != r->res) return 1; 34 | if (!res) return 0; 35 | printf("trigger_scan ret code OK "); 36 | n = sprintf(buf, SCSI_SYS_TMPL, test_pattern->scsi_host); 37 | printf("comparefns: '%s' '%s' ", buf, r->fn); 38 | if (strncmp(buf, r->fn, n)) return 1; 39 | printf("file to read='%s' ", buf); 40 | file = fopen(buf, "r"); 41 | if (file == NULL) return 1; 42 | n = fread(buf, 100, 1, file); 43 | fclose(file); 44 | 45 | printf("Compare buf=%s expected=%s ", buf, r->addr); 46 | if (strncmp(buf, r->addr, n)) return 1; 47 | return 0; 48 | } 49 | 50 | 51 | 52 | 53 | int main(){ 54 | struct args ba; 55 | int i, r; 56 | char res[TEST_BUFF_SIZE]; 57 | 58 | puts(SCSI_SYS_TMPL); 59 | for (i=0; i< sizeof(test_patterns)/sizeof(struct args); i++) { 60 | printf("%d - ", i); 61 | r = trigger_scan(&test_patterns[i], SCSI_SYS_TMPL, SCSI_SYS_TMPL_SZ); 62 | if (verify(r, &test_patterns[i], &results[i])) { 63 | printf("Failed\n"); 64 | return -1; 65 | } 66 | printf("Success\n"); 67 | } 68 | return 0; 69 | } 70 | -------------------------------------------------------------------------------- /scsi_probe/tests_res/placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containers/initoverlayfs/df2874b349e8989b081780fc8f2b02ba1156789e/scsi_probe/tests_res/placeholder --------------------------------------------------------------------------------