├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .lgtm ├── CHANGELOG.md ├── COPYING ├── MAINTAINERS ├── Makefile.am ├── README.md ├── autogen.sh ├── catatonit.c ├── config.h.in ├── configure.ac └── hack └── release.sh /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-or-later 2 | # 3 | # catatonit: a container init so simple it's effectively brain-dead 4 | # Copyright (C) 2018-2023 SUSE LLC 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | name: ci 20 | 21 | on: 22 | push: 23 | branches: [ main ] 24 | pull_request: 25 | branches: [ main ] 26 | release: 27 | types: [ published ] 28 | schedule: 29 | - cron: '0 0 * * *' 30 | 31 | jobs: 32 | build: 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v2 36 | - run: | 37 | autoreconf -fi && ./configure && make 38 | file ./catatonit | grep 'statically linked' 39 | # TODO: Add unit tests. 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Our binary. 2 | /catatonit 3 | /release/ 4 | 5 | # Autoconf generated files for binaries. 6 | /Makefile 7 | /Makefile.in 8 | /aclocal.m4 9 | /autom4te.cache/ 10 | /config.* 11 | !/config.h.in 12 | /compile 13 | /configure 14 | /depcomp 15 | /install-sh 16 | /missing 17 | /ltmain.sh 18 | /libtool 19 | /m4/ 20 | /stamp-h? 21 | .deps/ 22 | .dirstamp 23 | *.o 24 | *~ 25 | -------------------------------------------------------------------------------- /.lgtm: -------------------------------------------------------------------------------- 1 | approvals = 1 2 | pattern = "^LGTM" 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog # 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | ## [Unreleased] ## 8 | 9 | ## [0.2.1] - 2024-12-14 ## 10 | 11 | ### Fixed ### 12 | * `catatonit` will now `chdir` to `/` in order to avoid keeping the directory 13 | it was running in busy. This was causing issues with rootless Podman's pause 14 | container keeping some `/home/...` paths busy and blocking `umount`s. (#28, 15 | #33) 16 | 17 | ## [0.2.0] - 2023-10-02 18 | 19 | ### Changed ### 20 | * `catatonit` has now been relicensed to GPLv2-or-later. (#27) 21 | 22 | ## [0.1.7] - 2021-11-01 23 | 24 | ### Added ### 25 | * Running `catatonit` as the only process in a pause container (i.e. no 26 | children are spawned, `catatonit` just runs in a busy loop) is now supported 27 | in the form of `catatonit -P`. 28 | 29 | ## [0.1.6] - 2021-09-16 30 | 31 | ### Fixed ### 32 | * File descriptors passed to the pid2 process were not closed by `catatonit` 33 | after starting pid2 which could lead to several negative scenarios (such as 34 | resources being kept around unnecessarily). Now `catatonit` will close all 35 | `>=3` (i.e. non-stdio) file descriptors after spawning the child process. 36 | (#12, #14) 37 | 38 | * Container-based socket activation support with runc sets `LISTEN_PID=1` when 39 | forwarding socket-activation-related file descriptors, but with `catatonit` 40 | this doesn't work because `catatonit` is pid1 not the actual container 41 | process. As such, `catatonit` will now rewrite the `LISTEN_PIDS` environment 42 | variable to equal the pid2 pid if `LISTEN_PIDS` is equal to the pid of 43 | `catatonit`. (#13, #15) 44 | 45 | ## [0.1.5] - 2020-03-03 46 | 47 | ### Fixed ### 48 | * Some cases where catatonit could hang if pid1 died and the death signal was 49 | coalesced are now correctly handled. (#4) 50 | 51 | ## [0.1.4] - 2019-01-29 52 | 53 | ### Added ### 54 | * We now support the `-g` option (from `tini`) for signals to be sent to the 55 | process group. This is necessary for Rook to switch to `catatonit`. 56 | 57 | ## [0.1.3] - 2018-04-18 58 | 59 | ### Fixed ## 60 | * Improve Docker compatibility by reporting ourselves as `tini` when providing 61 | version information from `catatonit -V`. 62 | 63 | ## [0.1.2] - 2018-03-29 64 | 65 | ### Changed ### 66 | * Minor cosmetic changes, as we are now an openSUSE project. 67 | 68 | ## [0.1.1] - 2018-03-27 69 | 70 | ### Fixed ### 71 | * Add a small fix for the libtool requirements to allow building catatonit on 72 | older distributions. 73 | 74 | ## 0.1.0 - 2018-03-27 75 | 76 | This is the first release of catatonit. At this point it works fully (to 77 | the best of my ability) and is incredibly simple to use and maintain. 78 | 79 | [Unreleased]: https://github.com/openSUSE/catatonit/compare/v0.2.1...HEAD 80 | [0.2.1]: https://github.com/openSUSE/catatonit/compare/v0.2.0...v0.2.1 81 | [0.2.0]: https://github.com/openSUSE/catatonit/compare/v0.1.7...v0.2.0 82 | [0.1.7]: https://github.com/openSUSE/catatonit/compare/v0.1.6...v0.1.7 83 | [0.1.6]: https://github.com/openSUSE/catatonit/compare/v0.1.5...v0.1.6 84 | [0.1.5]: https://github.com/openSUSE/catatonit/compare/v0.1.4...v0.1.5 85 | [0.1.4]: https://github.com/openSUSE/catatonit/compare/v0.1.3...v0.1.4 86 | [0.1.3]: https://github.com/openSUSE/catatonit/compare/v0.1.2...v0.1.3 87 | [0.1.2]: https://github.com/openSUSE/catatonit/compare/v0.1.1...v0.1.2 88 | [0.1.1]: https://github.com/openSUSE/catatonit/compare/v0.1.0...v0.1.1 89 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | Aleksa Sarai (@cyphar) 2 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-or-later 2 | # 3 | # catatonit: a container init so simple it's effectively brain-dead 4 | # Copyright (C) 2018-2023 SUSE LLC 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | bin_PROGRAMS = catatonit 20 | 21 | catatonit_LDFLAGS = -all-static 22 | catatonit_SOURCES = catatonit.c 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## catatonit ## 2 | 3 | [![Build Status](https://github.com/openSUSE/catatonit/actions/workflows/ci.yml/badge.svg)](https://github.com/openSUSE/catatonit/actions/workflows/ci.yml) 4 | 5 | A container init that is so simple it's effectively brain-dead. This is a 6 | rewrite of [initrs][initrs] in C, because we found that it is not possible to 7 | statically compile Rust binaries without using musl. That was, in turn, a 8 | reimplementation of other container inits like `tini` and `dumb-init`. 9 | 10 | The reason for re-implementing `docker-init` is because it appears as though 11 | all of the other implementations do not handle signals as correctly as they 12 | should. In particular, they all appear to make use of `sigwait(2)` (`tini` does 13 | a `sigtimedwait(2)` for an interval and then will do a `waitpid(2)` even if it 14 | didn't detect a `SIGCHLD`). `catatonit` uses `signalfd(2)`, which [has its own 15 | warts][signalfd-broken], but the improvements over `sigwait(2)` are significant 16 | in terms of stability. Ideally we would just write a patch for the other 17 | projects to use `signalfd(2)` rather than creating a new project, but after 18 | some time spent looking at `tini` and `dumb-init` we felt that such patches 19 | would be closer to full rewrites. 20 | 21 | In addition, the purpose of `catatonit` is to only support the key usage by 22 | `docker-init` which is `/dev/init -- `. With few exceptions, no 23 | other features will be added. 24 | 25 | [initrs]: https://github.com/cyphar/initrs 26 | [signalfd-broken]: https://ldpreload.com/blog/signalfd-is-useless 27 | 28 | ### Usage ### 29 | 30 | catatonit has identical usage to other basic `docker-init`'s -- you give it the 31 | command and list of arguments to that command. 32 | 33 | If you install `catatonit` to `/usr/bin/docker-init`, `docker run --init` will 34 | use `catatonit` as its container pid1. Alternatively, you can configure the 35 | Docker daemon to use `catatonit` without deleting any previously installed 36 | `/usr/bin/docker-init` by using `--init-path` (or adding an `init-path` setting 37 | in `/etc/docker/daemon.json`). Podman has similar options. 38 | 39 | Catatonit supports a very limit subset of features, in order to keep the code 40 | as simple as possible: 41 | 42 | * If catatonit is not pid1 (in other words, you are not in a PID namespace), it 43 | will try to use the sub-reaper support in the kernel to act as a 44 | "pseudo-init" for the process you requested. 45 | 46 | * You can pass `-g` if you want signals to be forwarded to the entire process 47 | group of your spawned process (otherwise it's just forwarded to the process 48 | spawned). 49 | 50 | * If you wish to use catatonit as a convenient pause container (do not spawn a 51 | child process nor do any signal handling), you can pass `-P`. 52 | 53 | If you want to include `catatonit` in your images, you can conveniently add it 54 | to your Dockerfile as an entrypoint: 55 | 56 | ```dockerfile 57 | # Runs "catatonit -- /my/amazing/script --with --args" 58 | ENTRYPOINT ["catatonit", "--"] 59 | 60 | # or if you use --rewrite or other cli flags 61 | # ENTRYPOINT ["catatonit", "--rewrite", "2:3", "--"] 62 | 63 | CMD ["/my/amazing/script", "--with", "--args"] 64 | ``` 65 | 66 | ### Installation ### 67 | 68 | catatonit uses autotools for building, so building is a fairly standard: 69 | 70 | ``` 71 | % ./autogen.sh 72 | % ./configure 73 | % make 74 | % sudo make install 75 | ``` 76 | 77 | Note that this install the `catatonit` binary to `/usr/bin/catatonit`. If you 78 | want to use `docker run --init` you may need to symlink `/usr/bin/docker-init` 79 | to `catatonit` or configure Docker to use `catatonit`. 80 | 81 | ### License ### 82 | 83 | catatonit is licensed under the GNU General Public License version 2 or later. 84 | 85 | ``` 86 | catatonit: a container init so simple it's effectively brain-dead 87 | Copyright (C) 2018-2023 SUSE LLC 88 | 89 | This program is free software; you can redistribute it and/or modify 90 | it under the terms of the GNU General Public License as published by 91 | the Free Software Foundation, either version 2 of the License, or 92 | (at your option) any later version. 93 | 94 | This program is distributed in the hope that it will be useful, 95 | but WITHOUT ANY WARRANTY; without even the implied warranty of 96 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 97 | GNU General Public License for more details. 98 | 99 | You should have received a copy of the GNU General Public License 100 | along with this program. If not, see . 101 | ``` 102 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | exec autoreconf -fiv 4 | -------------------------------------------------------------------------------- /catatonit.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * catatonit: a container init so simple it's effectively brain-dead 4 | * Copyright (C) 2018-2023 SUSE LLC 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #define _GNU_SOURCE 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #ifdef HAVE_LINUX_CLOSE_RANGE_H 40 | # include 41 | #else 42 | # include 43 | #endif 44 | 45 | #include "config.h" 46 | 47 | static enum loglevel_t { 48 | LOG_FATAL = 0, 49 | LOG_ERROR = 1, 50 | LOG_WARN = 2, 51 | LOG_INFO = 3, 52 | LOG_DEBUG = 4, 53 | } global_log_level = LOG_ERROR; 54 | 55 | static void _log(enum loglevel_t level, char *fmt, ...) 56 | { 57 | va_list ap; 58 | int old_errno = errno; 59 | char *level_str = "*"; 60 | 61 | if (global_log_level < level) 62 | return; 63 | 64 | switch (level) { 65 | case LOG_FATAL: 66 | level_str = "FATAL"; 67 | break; 68 | case LOG_ERROR: 69 | default: 70 | level_str = "ERROR"; 71 | break; 72 | case LOG_WARN: 73 | level_str = "WARN"; 74 | break; 75 | case LOG_INFO: 76 | level_str = "INFO"; 77 | break; 78 | case LOG_DEBUG: 79 | level_str = "DEBUG"; 80 | break; 81 | } 82 | 83 | fprintf(stderr, "%s (%s:%d): ", level_str, PROGRAM_NAME, getpid()); 84 | 85 | va_start(ap, fmt); 86 | errno = old_errno; 87 | vfprintf(stderr, fmt, ap); 88 | va_end(ap); 89 | 90 | fprintf(stderr, "\n"); 91 | errno = old_errno; 92 | } 93 | 94 | static void usage(void) 95 | { 96 | fprintf(stderr, "usage: %s [-ghLPV] [--] [...]\n", PROGRAM_NAME); 97 | } 98 | 99 | static void help(void) 100 | { 101 | usage(); 102 | fprintf(stderr, "\n"); 103 | fprintf(stderr, "options:\n"); 104 | fprintf(stderr, " -g Forward signals to pid1's process group.\n"); 105 | fprintf(stderr, " -h Print this help page.\n"); 106 | fprintf(stderr, " -L Print license information.\n"); 107 | fprintf(stderr, " -P Run in pause mode (no program is run and quit on SIGINT).\n"); 108 | fprintf(stderr, " -V, --version Print version information.\n"); 109 | fprintf(stderr, "\n"); 110 | fprintf(stderr, "The source code can be found at <%s>.\n", PROGRAM_URL); 111 | fprintf(stderr, "For bug reporting instructions, please see: <%s>.\n", PROGRAM_BUGURL); 112 | } 113 | 114 | static void version(void) 115 | { 116 | // The name is intentional to make `docker-info` happy: docker is hard-coded 117 | // against `tini`. This is an (unfortunate) hack to make it work nicely with 118 | // catatonit. 119 | fprintf(stdout, "tini version %s_%s\n", PROGRAM_VERSION, PROGRAM_NAME); 120 | } 121 | 122 | static void license(void) 123 | { 124 | fprintf(stdout, "%s", PROGRAM_LICENSE); 125 | } 126 | 127 | #define LOG(level, ...) \ 128 | do { _log(level, __VA_ARGS__); } while (0) 129 | 130 | #define fatal(...) LOG(LOG_FATAL, __VA_ARGS__) 131 | #define error(...) LOG(LOG_ERROR, __VA_ARGS__) 132 | #define warn(...) LOG(LOG_WARN, __VA_ARGS__) 133 | #define info(...) LOG(LOG_INFO, __VA_ARGS__) 134 | #define debug(...) LOG(LOG_DEBUG, __VA_ARGS__) 135 | 136 | #define bail(...) \ 137 | do { error(__VA_ARGS__); exit(1); } while (0) 138 | #define bail_usage(...) \ 139 | do { error(__VA_ARGS__); usage(); exit(1); } while (0) 140 | 141 | /* 142 | * Set of signals that the kernel sends us if *we* screwed something up. We 143 | * don't want to forward these to the child, as it will just confuse them. If 144 | * we get one of these, we let ourselves die rather than just carrying on. 145 | */ 146 | int kernel_signals[] = {SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGABRT, SIGTRAP, SIGSYS}; 147 | 148 | #define ARRAY_LEN(arr) (sizeof(arr) / sizeof(*arr)) 149 | 150 | #ifndef HAVE_CLOSE_RANGE 151 | static int close_range(unsigned int fd, unsigned int max_fd, unsigned int flags) 152 | { 153 | # ifdef __NR_close_range 154 | return (int) syscall(__NR_close_range, fd, max_fd, flags); 155 | # else 156 | errno = ENOSYS; 157 | return -1; 158 | # endif 159 | } 160 | #endif 161 | 162 | /* 163 | * Close every fd >= n that is different from exclude_fd using close_range. 164 | */ 165 | static int close_range_fds_ge_than(int n, int exclude_fd) 166 | { 167 | int r, saved_errno = 0; 168 | 169 | /* exclude_fd is not in the [n, UINT_MAX] range. */ 170 | if (exclude_fd < n) 171 | return close_range(n, UINT_MAX, 0); 172 | 173 | /* exclude_fd is the first fd in the [n, UINT_MAX] range. */ 174 | if (exclude_fd == n) 175 | return close_range(n + 1, UINT_MAX, 0); 176 | 177 | /* exclude_fd is between n and UINT_MAX. */ 178 | errno = 0; 179 | r = close_range(n, exclude_fd - 1, 0); 180 | /* 181 | * attempt to close as many FDs as possible but return an error 182 | * if the close_range() failed. 183 | */ 184 | if (exclude_fd < UINT_MAX) { 185 | saved_errno = errno; 186 | r = close_range(exclude_fd + 1, UINT_MAX, 0); 187 | /* If the previous call failed, restore errno. */ 188 | if (saved_errno != 0) { 189 | r = -1; 190 | errno = saved_errno; 191 | } 192 | } 193 | return r; 194 | } 195 | 196 | /* 197 | * Close every fd >= n that is different from exclude_fd. 198 | */ 199 | static int close_fds_ge_than(int n, int exclude_fd) 200 | { 201 | struct dirent *next; 202 | int failures = 0; 203 | DIR *dir; 204 | int fd; 205 | int r; 206 | 207 | if (close_range_fds_ge_than(n, exclude_fd) == 0) 208 | return 0; 209 | 210 | /* Fallback when close_range fails. */ 211 | debug("close_range() failed, fallback to close() each open FD: %m"); 212 | 213 | dir = opendir("/proc/self/fd"); 214 | if (dir == NULL) { 215 | debug("cannot opendir /proc/self/fd: %m"); 216 | return -1; 217 | } 218 | 219 | fd = dirfd(dir); 220 | for (next = readdir(dir); next; next = readdir(dir)) { 221 | const char *name = next->d_name; 222 | long long val; 223 | 224 | if (name[0] == '.') 225 | continue; 226 | 227 | val = strtoll(name, NULL, 10); 228 | if (val < n || val == fd || val == exclude_fd) 229 | continue; 230 | 231 | r = close(val); 232 | if (r < 0) { 233 | debug("cannot close %d: %m", val); 234 | failures++; 235 | } 236 | } 237 | 238 | r = closedir(dir); 239 | if (r < 0) { 240 | debug("cannot close %d: %m", fd); 241 | failures++; 242 | } 243 | 244 | return -failures; 245 | } 246 | 247 | /* 248 | * Makes the current process a "foreground" process, by making it the leader of 249 | * a process group and session leader. It also updates the sigmask to include 250 | * signals that should be blocked. 251 | */ 252 | static int make_foreground(sigset_t *sigmask) 253 | { 254 | /* Create a new process group. */ 255 | if (setpgid(0, 0) < 0) 256 | bail("failed to create process group"); 257 | pid_t pgrp = getpgrp(); 258 | if (pgrp < 0) 259 | bail("failed to get new process group id"); 260 | 261 | /* 262 | * We open /dev/tty directly here. The reason for this (rather than just 263 | * using STDIN_FILENO) is the the file descriptor could be duped over, but 264 | * we still should become the controlling process. 265 | */ 266 | int ttyfd = open("/dev/tty", O_RDWR|O_CLOEXEC); 267 | if (ttyfd < 0) { 268 | info("using stdin as tty fd: could not open /dev/tty: %m"); 269 | ttyfd = STDIN_FILENO; 270 | } 271 | 272 | /* 273 | * Add TTY signals to ignored mask for pid1. This isn't strictly necessary, 274 | * but we do it anyway to avoid pid1 being stopped inadvertently. 275 | */ 276 | if (sigaddset(sigmask, SIGTSTP) < 0) 277 | bail("failed to add SIGTSTP to pid1 mask"); 278 | if (sigaddset(sigmask, SIGTTOU) < 0) 279 | bail("failed to add SIGTTOU to pid1 mask"); 280 | if (sigaddset(sigmask, SIGTTIN) < 0) 281 | bail("failed to add SIGTTIN to pid1 mask"); 282 | 283 | /* Try to set ourselves as the owner of the terminal. */ 284 | if (tcsetpgrp(ttyfd, pgrp) < 0) { 285 | switch (errno) { 286 | /* The fd wasn't a tty. This isn't a problem. */ 287 | case ENOTTY: 288 | case EBADF: 289 | debug("setting foreground process failed: no tty present: %m"); 290 | break; 291 | /* Can happen on lx-branded zones. Not a problem. */ 292 | case ENXIO: 293 | debug("setting foreground process failed: no such device"); 294 | break; 295 | /* Other errors are a problem. */ 296 | default: 297 | bail("setting foreground process failed: %m"); 298 | break; 299 | } 300 | } 301 | if (ttyfd != STDIN_FILENO) 302 | close(ttyfd); 303 | return 0; 304 | } 305 | 306 | /* 307 | * If the LISTEN_PID environment variable is set to the parent pid, rewrite it to 308 | * point to the current pid. 309 | */ 310 | static void rewrite_listen_pid_env() 311 | { 312 | char *listen_pid = getenv("LISTEN_PID"); 313 | long long val; 314 | 315 | if (listen_pid == NULL) 316 | return; 317 | 318 | errno = 0; 319 | val = strtoll(listen_pid, NULL, 10); 320 | if (errno == ERANGE) { 321 | warn("LISTEN_PID has an invalid value"); 322 | return; 323 | } 324 | 325 | if (val == getppid()) { 326 | char pid_str[32]; 327 | int r; 328 | 329 | snprintf(pid_str, sizeof(pid_str), "%d", getpid()); 330 | 331 | r = setenv("LISTEN_PID", pid_str, 1); 332 | if (r < 0) 333 | warn("could not overwrite env variable LISTEN_PID: %m"); 334 | } 335 | } 336 | 337 | /* 338 | * Spawn a child process with the given arguments and signal map and make it a 339 | * faux-pid1 by placing it in the foreground. This is the main process which 340 | * catatonit is going to be managing throughout its life. 341 | */ 342 | static int spawn_pid1(char *file, char **argv, sigset_t *sigmask) 343 | { 344 | pid_t child = fork(); 345 | if (child != 0) { 346 | if (child < 0) 347 | error("failed to fork child: %m"); 348 | return child; 349 | } 350 | 351 | rewrite_listen_pid_env(); 352 | 353 | /* 354 | * We are now in the child. Set up our sigmask, put ourselves in the 355 | * foreground, and then finally exec (with the environment inherited). 356 | */ 357 | if (make_foreground(sigmask) < 0) 358 | bail("failed to become foreground: %m"); 359 | if (sigprocmask(SIG_SETMASK, sigmask, NULL) < 0) 360 | bail("failed to reset sigmask: %m"); 361 | 362 | execvpe(file, argv, environ); 363 | bail("failed to exec pid1: %m"); 364 | } 365 | 366 | /* 367 | * Handles any queued zombies which need to be reaped using waitpid(2). We 368 | * continually wait for child process deaths until none are reported (or we 369 | * have no children left). 370 | */ 371 | static int reap_zombies(pid_t pid1, int *pid1_exitcode) 372 | { 373 | for (;;) { 374 | int wstatus = 0; 375 | 376 | pid_t child = waitpid(-1, &wstatus, WNOHANG); 377 | if (child <= 0) { 378 | if (errno == ECHILD) { 379 | debug("got ECHILD: no children left to monitor"); 380 | child = 0; 381 | } 382 | return child; 383 | } 384 | 385 | /* 386 | * There is a special-case for our pid1. If the process exits we 387 | * inherit its exit code, otherwise we assume an exit code of 127. 388 | * This will cause us to exit immediately, since pid1 is now dead. 389 | */ 390 | if (child == pid1) { 391 | /* Did it die from an exit(2)? */ 392 | if (WIFEXITED(wstatus)) 393 | *pid1_exitcode = WEXITSTATUS(wstatus); 394 | /* What about from a signal? */ 395 | else if (WIFSIGNALED(wstatus)) 396 | *pid1_exitcode = 128 + WTERMSIG(wstatus); 397 | /* Is the child actually dead? */ 398 | else if (kill(pid1, 0) < 0) 399 | *pid1_exitcode = 127; 400 | /* It hasn't died... */ 401 | else 402 | warn("received SIGCHLD from pid1 (%d) but it's still alive", pid1); 403 | continue; 404 | } 405 | 406 | if (WIFEXITED(wstatus)) 407 | debug("child process %d exited with code %d", child, WEXITSTATUS(wstatus)); 408 | else if (WIFSIGNALED(wstatus)) 409 | debug("child process %d exited due to signal %d", child, WTERMSIG(wstatus)); 410 | else 411 | warn("observed unexpected status for process %d: %#x", child, wstatus); 412 | } 413 | } 414 | 415 | int main(int argc, char **argv) 416 | { 417 | /* If CATATONIT_DEBUG is defined we change the global log level. */ 418 | char *logstring = getenv("CATATONIT_DEBUG"); 419 | if (logstring != NULL) 420 | global_log_level = LOG_DEBUG; 421 | /* CATATONIT_LOG is reserved for future use. */ 422 | if (getenv("CATATONIT_LOG")) 423 | bail("CATATONIT_LOG is reserved for future use"); 424 | 425 | /* 426 | * Set up signal handling before *anything else*. We block *all* signals 427 | * (except for signals that the kernel generates to try to kill us) since 428 | * they will be read from the signalfd we set up. We also keep a copy of 429 | * the original sigmask so we can re-set it on our faux-pid1. 430 | */ 431 | sigset_t init_sigmask, pid1_sigmask; 432 | if (sigfillset(&init_sigmask) < 0) 433 | bail("failed to fill init_sigmask: %m"); 434 | int i; 435 | for (i = 0; i < ARRAY_LEN(kernel_signals); i++) { 436 | if (sigdelset(&init_sigmask, kernel_signals[i]) < 0) 437 | bail("failed to clear signal %d from init_sigmask: %m", kernel_signals[i]); 438 | } 439 | if (sigprocmask(SIG_SETMASK, &init_sigmask, &pid1_sigmask) < 0) 440 | bail("failed to block all signals: %m"); 441 | 442 | int sfd = signalfd(-1, &init_sigmask, SFD_CLOEXEC); 443 | if (sfd < 0) 444 | bail("failed to create signalfd: %m"); 445 | 446 | /* 447 | * We need to support "--" as well as provide license information and so 448 | * on. Aside from that we also need to update argv so it points at the 449 | * first *pid1* argv argument rather than our own. 450 | */ 451 | int opt; 452 | bool kill_pgid = false; 453 | bool run_as_pause = false; 454 | const struct option longopts[] = { 455 | {name: "version", has_arg: no_argument, flag: NULL, val: 'V'}, 456 | {}, 457 | }; 458 | while ((opt = getopt_long(argc, argv, "ghLPV", longopts, NULL)) != -1) { 459 | switch (opt) { 460 | case 'g': 461 | kill_pgid = true; 462 | break; 463 | case 'P': 464 | run_as_pause = true; 465 | break; 466 | case 'h': 467 | help(); 468 | exit(0); 469 | case 'L': 470 | license(); 471 | exit(0); 472 | case 'V': 473 | version(); 474 | exit(0); 475 | default: 476 | usage(); 477 | exit(1); 478 | } 479 | } 480 | argv += optind; 481 | argc -= optind; 482 | if (argc < 1 && !run_as_pause) 483 | bail_usage("missing program name"); 484 | 485 | /* 486 | * If we aren't pid1, we have to set subreaper or bail. Otherwise zombies 487 | * will collect on the host and that's just not a good idea. We don't just 488 | * bail in all cases because users can run us in a container, but with the 489 | * pid namespace shared with the host (though the benefit of using a 490 | * container init is effectively zero in that instance). 491 | */ 492 | if (getpid() != 1) { 493 | #if defined(PR_SET_CHILD_SUBREAPER) 494 | if (prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) < 0) 495 | bail("failed to set child-reaper as non-pid1: %m"); 496 | #else 497 | bail("cannot run as non-pid1 without child-reaper support in kernel"); 498 | #endif 499 | } 500 | 501 | /* Spawn the faux-pid1. */ 502 | pid_t pid1 = 0; 503 | 504 | if (!run_as_pause) { 505 | pid1 = spawn_pid1(argv[0], argv, &pid1_sigmask); 506 | if (pid1 <= 0) 507 | bail("failed to spawn pid1: %m"); 508 | 509 | /* One final check to make sure that it actually spawned. */ 510 | if (kill(pid1, 0) < 0) 511 | bail("self-check that pid1 (%d) was spawned failed: %m", pid1); 512 | debug("pid1 (%d) spawned: %s", pid1, argv[0]); 513 | } 514 | 515 | /* 516 | * Switch to / explicitly, to work around a known podman issue where podman 517 | * runs "catatonit -P" in a subdirectory which causes umount to fail 518 | * because the directory is pinned by catatonit. Ignore errors since we 519 | * don't care about the cwd anyway. 520 | */ 521 | (void) chdir("/"); 522 | 523 | if (close_fds_ge_than(3, sfd) < 0) 524 | warn("failed to close some file descriptor in range >=3"); 525 | 526 | /* 527 | * The "pid" we send signals to. With -g we send signals to the entire 528 | * process group which pid1 is in, which is represented by a -ve pid. 529 | */ 530 | pid_t pid1_target = run_as_pause ? 0 : (kill_pgid ? -pid1 : pid1); 531 | 532 | /* 533 | * Wait for signals and process them as necessary. At this point we are no 534 | * longer allowed to bail(), because if anything breaks it's ultimately our 535 | * fault since a pid1 death will kill the container. 536 | */ 537 | int pid1_exitcode = -1; 538 | while (pid1_exitcode < 0) { 539 | /* 540 | * Wait for a signal. read(2) will block here and we don't care about 541 | * anything else, so no need for select(2) or epoll(2) or anything 542 | * equivalently clever. 543 | */ 544 | struct signalfd_siginfo ssi = {0}; 545 | 546 | int n = read(sfd, &ssi, sizeof(ssi)); 547 | if (n != sizeof(ssi)) { 548 | if (n < 0) 549 | warn("signalfd read failed: %m"); 550 | else 551 | warn("signalfd had %d-byte partial-read: %m", n); 552 | continue; 553 | } 554 | 555 | switch (ssi.ssi_signo) { 556 | /* 557 | * Signals that we get sent if we are a background job in the current 558 | * terminal (if it has TOSTOP set), which is possible since we make 559 | * pid1 the foreground process. We just ignore them. 560 | */ 561 | case SIGTSTP: case SIGTTOU: case SIGTTIN: 562 | debug("ignoring kernel attempting to stop us: tty has TOSTOP set"); 563 | break; 564 | 565 | /* A child has died or a zombie has been re-parented to us. */ 566 | /* 567 | * TODO: We really should check ssi_pid, to see whether the sender was 568 | * inside our pid namespace. This would help avoid cases where someone 569 | * (foolishly) wants us to forward SIGCHLD to our pid1. Not sure why 570 | * you'd ever want that, but no reason to not support it. 571 | */ 572 | case SIGCHLD: 573 | if (reap_zombies(pid1, &pid1_exitcode) < 0) 574 | warn("problem occurred while reaping zombies: %m"); 575 | break; 576 | 577 | /* A signal sent to us by a user which we must forward to pid1. */ 578 | default: 579 | /* We just forward the signal to pid1. */ 580 | if (run_as_pause) { 581 | if (ssi.ssi_signo == SIGTERM || ssi.ssi_signo == SIGINT) 582 | return 0; 583 | } else if (kill(pid1_target, ssi.ssi_signo) < 0) { 584 | warn("forwarding of signal %d to pid1 (%d) failed: %m", ssi.ssi_signo, pid1_target); 585 | } 586 | break; 587 | } 588 | } 589 | return pid1_exitcode; 590 | } 591 | -------------------------------------------------------------------------------- /config.h.in: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | /* 3 | * catatonit: a container init so simple it's effectively brain-dead 4 | * Copyright (C) 2018-2023 SUSE LLC 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #if !defined(CONFIG_H) 21 | #define CONFIG_H 22 | 23 | #define PROGRAM_NAME "@PACKAGE_NAME@" 24 | #define PROGRAM_VERSION "@PACKAGE_VERSION@" 25 | #define PROGRAM_BUGURL "@PACKAGE_BUGREPORT@" 26 | #define PROGRAM_URL "@PACKAGE_URL@" 27 | #define PROGRAM_LICENSE \ 28 | PROGRAM_NAME ": a container init so simple it's effectively brain-dead\n" \ 29 | "Copyright (C) 2018-2023 SUSE LLC\n" \ 30 | "\n" \ 31 | "This program is free software; you can redistribute it and/or modify\n" \ 32 | "it under the terms of the GNU General Public License as published by\n" \ 33 | "the Free Software Foundation, either version 2 of the License, or\n" \ 34 | "(at your option) any later version.\n" \ 35 | "\n" \ 36 | "This program is distributed in the hope that it will be useful,\n" \ 37 | "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" \ 38 | "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" \ 39 | "GNU General Public License for more details.\n" \ 40 | "\n" \ 41 | "You should have received a copy of the GNU General Public License\n" \ 42 | "along with this program. If not, see .\n" \ 43 | 44 | #endif /* !defined(CONFIG_H) */ 45 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-or-later 2 | # 3 | # catatonit: a container init so simple it's effectively brain-dead 4 | # Copyright (C) 2018-2023 SUSE LLC 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | AC_PREREQ([2.69]) 20 | AC_INIT([catatonit], [0.2.1+dev], [https://bugs.opensuse.org/], [], [https://github.com/openSUSE/catatonit/]) 21 | AM_INIT_AUTOMAKE([-Wall foreign]) 22 | 23 | LT_PREREQ([2.4.2]) 24 | LT_INIT([disable-shared]) 25 | 26 | AC_CHECK_HEADERS([errno.h fcntl.h signal.h stdarg.h stdio.h stdlib.h unistd.h]) 27 | AC_CHECK_HEADERS([linux/close_range.h sys/prctl.h sys/signalfd.h sys/stat.h sys/types.h sys/wait.h]) 28 | 29 | AC_CHECK_FUNCS([close_range]) 30 | 31 | AC_TYPE_PID_T 32 | AC_FUNC_FORK 33 | 34 | AC_CONFIG_FILES([Makefile config.h]) 35 | AC_OUTPUT 36 | -------------------------------------------------------------------------------- /hack/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # release.sh: configurable signed-artefact release script 3 | # Copyright (C) 2016-2019 SUSE LLC. 4 | # 5 | # This Source Code Form is subject to the terms of the Mozilla Public 6 | # License, v2.0. If a copy of the MPL was not distributed with this 7 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | set -Eeuo pipefail 10 | 11 | ## ---> 12 | # Project-specific options and functions. In *theory* you shouldn't need to 13 | # touch anything else in this script in order to use this elsewhere. 14 | project="catatonit" 15 | root="$(readlink -f "$(dirname "${BASH_SOURCE}")/..")" 16 | 17 | # Make pushd and popd silent. 18 | function pushd() { command pushd "$@" &>/dev/null ; } 19 | function popd() { command popd "$@" &>/dev/null ; } 20 | 21 | # These functions allow you to configure how the defaults are computed. 22 | function get_arch() { uname -m ; } 23 | function get_version() { echo '@PACKAGE_VERSION@' | "$root/config.status" --file - ; } 24 | 25 | # Any pre-configuration steps should be done here -- for instance ./configure. 26 | function setup_project() { 27 | pushd "$root" 28 | ./autogen.sh 29 | ./configure LDFLAGS="-static" --prefix=/ --bindir=/bin 30 | popd 31 | } 32 | 33 | # This function takes an output path as an argument, where the built 34 | # (preferably static) binary should be placed. 35 | function build_project() { 36 | tmprootfs="$(mktemp -d --tmpdir "$project-build.XXXXXX")" 37 | 38 | make -C "$root" clean all install DESTDIR="$tmprootfs" 39 | 40 | mv "$tmprootfs/bin/$project" "$1" 41 | rm -rf "$tmprootfs" 42 | } 43 | # End of the easy-to-configure portion. 44 | ## <--- 45 | 46 | # Print usage information. 47 | function usage() { 48 | echo "usage: release.sh [-h] [-v ] [-c ] [-o ]" >&2 49 | echo " [-H ] [-S ]" >&2 50 | } 51 | 52 | # Log something to stderr. 53 | function log() { 54 | echo "[*]" "$@" >&2 55 | } 56 | 57 | # Log something to stderr and then exit with 0. 58 | function quit() { 59 | log "$@" 60 | exit 0 61 | } 62 | 63 | # Conduct a sanity-check to make sure that GPG provided with the given 64 | # arguments can sign something. Inability to sign things is not a fatal error. 65 | function gpg_cansign() { 66 | gpg "$@" --clear-sign /dev/null 67 | } 68 | 69 | # When creating releases we need to build (ideally static) binaries, an archive 70 | # of the current commit, and generate detached signatures for both. 71 | keyid="" 72 | version="" 73 | arch="" 74 | commit="HEAD" 75 | hashcmd="sha256sum" 76 | while getopts ":h:v:c:o:S:H:" opt; do 77 | case "$opt" in 78 | S) 79 | keyid="$OPTARG" 80 | ;; 81 | c) 82 | commit="$OPTARG" 83 | ;; 84 | o) 85 | outputdir="$OPTARG" 86 | ;; 87 | v) 88 | version="$OPTARG" 89 | ;; 90 | H) 91 | hashcmd="$OPTARG" 92 | ;; 93 | h) 94 | usage ; exit 0 95 | ;; 96 | \:) 97 | echo "Missing argument: -$OPTARG" >&2 98 | usage ; exit 1 99 | ;; 100 | \?) 101 | echo "Invalid option: -$OPTARG" >&2 102 | usage ; exit 1 103 | ;; 104 | esac 105 | done 106 | 107 | # Run project setup first... 108 | ( set -x ; setup_project ) 109 | 110 | # Generate the defaults for version and so on *after* argument parsing and 111 | # setup_project, to avoid calling get_version() needlessly. 112 | version="${version:-$(get_version)}" 113 | arch="${arch:-$(get_arch)}" 114 | outputdir="${outputdir:-release/$version}" 115 | 116 | log "[[ $project ]]" 117 | log "version: $version" 118 | log "commit: $commit" 119 | log "output_dir: $outputdir" 120 | log "key: ${keyid:-(default)}" 121 | log "hash_cmd: $hashcmd" 122 | 123 | # Make explicit what we're doing. 124 | set -x 125 | 126 | # Make the release directory. 127 | rm -rf "$outputdir" && mkdir -p "$outputdir" 128 | 129 | # Build project. 130 | build_project "$outputdir/$project.$arch" 131 | 132 | # Generate new archive. 133 | git archive --format=tar --prefix="$project-$version/" "$commit" | xz > "$outputdir/$project.tar.xz" 134 | 135 | # Generate sha256 checksums for both. 136 | ( cd "$outputdir" ; "$hashcmd" "$project".{"$arch",tar.xz} > "$project.$hashcmd" ; ) 137 | 138 | # Set up the gpgflags. 139 | gpgflags=() 140 | [[ -z "$keyid" ]] || gpgflags+=("--default-key=$keyid") 141 | gpg_cansign "${gpgflags[@]}" || quit "Could not find suitable GPG key, skipping signing step." 142 | 143 | # Sign everything. 144 | gpg "${gpgflags[@]}" --detach-sign --armor "$outputdir/$project.$arch" 145 | gpg "${gpgflags[@]}" --detach-sign --armor "$outputdir/$project.tar.xz" 146 | gpg "${gpgflags[@]}" --clear-sign --armor \ 147 | --output "$outputdir/$project.$hashcmd"{.tmp,} && \ 148 | mv "$outputdir/$project.$hashcmd"{.tmp,} 149 | --------------------------------------------------------------------------------