├── .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 | [](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 |
--------------------------------------------------------------------------------