├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── playground ├── fork_say.c ├── main.c └── say.c └── src ├── container ├── io.rs ├── logger.rs ├── mod.rs ├── reactor.rs ├── server.rs └── signal.rs ├── lib.rs ├── main.rs ├── nixtools ├── misc.rs ├── mod.rs ├── pipe.rs ├── process.rs ├── signal.rs └── stdio.rs ├── playground ├── fifo1.rs ├── pipe1.rs ├── pipe2.rs └── signalfd.rs ├── runtime.rs └── syncpipe.rs /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.swp 3 | **/*.rs.bk 4 | /target 5 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "android_system_properties" 7 | version = "0.1.5" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 10 | dependencies = [ 11 | "libc", 12 | ] 13 | 14 | [[package]] 15 | name = "ansi_term" 16 | version = "0.12.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 19 | dependencies = [ 20 | "winapi 0.3.9", 21 | ] 22 | 23 | [[package]] 24 | name = "atty" 25 | version = "0.2.14" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 28 | dependencies = [ 29 | "hermit-abi", 30 | "libc", 31 | "winapi 0.3.9", 32 | ] 33 | 34 | [[package]] 35 | name = "autocfg" 36 | version = "1.1.0" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 39 | 40 | [[package]] 41 | name = "bitflags" 42 | version = "1.3.2" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 45 | 46 | [[package]] 47 | name = "bumpalo" 48 | version = "3.11.1" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" 51 | 52 | [[package]] 53 | name = "cc" 54 | version = "1.0.78" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" 57 | 58 | [[package]] 59 | name = "cfg-if" 60 | version = "0.1.10" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 63 | 64 | [[package]] 65 | name = "cfg-if" 66 | version = "1.0.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 69 | 70 | [[package]] 71 | name = "chrono" 72 | version = "0.4.23" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" 75 | dependencies = [ 76 | "iana-time-zone", 77 | "js-sys", 78 | "num-integer", 79 | "num-traits", 80 | "time", 81 | "wasm-bindgen", 82 | "winapi 0.3.9", 83 | ] 84 | 85 | [[package]] 86 | name = "clap" 87 | version = "2.34.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 90 | dependencies = [ 91 | "ansi_term", 92 | "atty", 93 | "bitflags", 94 | "strsim", 95 | "textwrap", 96 | "unicode-width", 97 | "vec_map", 98 | ] 99 | 100 | [[package]] 101 | name = "codespan-reporting" 102 | version = "0.11.1" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 105 | dependencies = [ 106 | "termcolor", 107 | "unicode-width", 108 | ] 109 | 110 | [[package]] 111 | name = "core-foundation-sys" 112 | version = "0.8.3" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 115 | 116 | [[package]] 117 | name = "cxx" 118 | version = "1.0.85" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd" 121 | dependencies = [ 122 | "cc", 123 | "cxxbridge-flags", 124 | "cxxbridge-macro", 125 | "link-cplusplus", 126 | ] 127 | 128 | [[package]] 129 | name = "cxx-build" 130 | version = "1.0.85" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0" 133 | dependencies = [ 134 | "cc", 135 | "codespan-reporting", 136 | "once_cell", 137 | "proc-macro2", 138 | "quote", 139 | "scratch", 140 | "syn", 141 | ] 142 | 143 | [[package]] 144 | name = "cxxbridge-flags" 145 | version = "1.0.85" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59" 148 | 149 | [[package]] 150 | name = "cxxbridge-macro" 151 | version = "1.0.85" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6" 154 | dependencies = [ 155 | "proc-macro2", 156 | "quote", 157 | "syn", 158 | ] 159 | 160 | [[package]] 161 | name = "error-chain" 162 | version = "0.12.4" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" 165 | dependencies = [ 166 | "version_check", 167 | ] 168 | 169 | [[package]] 170 | name = "fuchsia-zircon" 171 | version = "0.3.3" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 174 | dependencies = [ 175 | "bitflags", 176 | "fuchsia-zircon-sys", 177 | ] 178 | 179 | [[package]] 180 | name = "fuchsia-zircon-sys" 181 | version = "0.3.3" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 184 | 185 | [[package]] 186 | name = "heck" 187 | version = "0.3.3" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 190 | dependencies = [ 191 | "unicode-segmentation", 192 | ] 193 | 194 | [[package]] 195 | name = "hermit-abi" 196 | version = "0.1.19" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 199 | dependencies = [ 200 | "libc", 201 | ] 202 | 203 | [[package]] 204 | name = "iana-time-zone" 205 | version = "0.1.53" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" 208 | dependencies = [ 209 | "android_system_properties", 210 | "core-foundation-sys", 211 | "iana-time-zone-haiku", 212 | "js-sys", 213 | "wasm-bindgen", 214 | "winapi 0.3.9", 215 | ] 216 | 217 | [[package]] 218 | name = "iana-time-zone-haiku" 219 | version = "0.1.1" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" 222 | dependencies = [ 223 | "cxx", 224 | "cxx-build", 225 | ] 226 | 227 | [[package]] 228 | name = "iovec" 229 | version = "0.1.4" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 232 | dependencies = [ 233 | "libc", 234 | ] 235 | 236 | [[package]] 237 | name = "itoa" 238 | version = "1.0.5" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" 241 | 242 | [[package]] 243 | name = "js-sys" 244 | version = "0.3.60" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" 247 | dependencies = [ 248 | "wasm-bindgen", 249 | ] 250 | 251 | [[package]] 252 | name = "kernel32-sys" 253 | version = "0.2.2" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 256 | dependencies = [ 257 | "winapi 0.2.8", 258 | "winapi-build", 259 | ] 260 | 261 | [[package]] 262 | name = "lazy_static" 263 | version = "1.4.0" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 266 | 267 | [[package]] 268 | name = "libc" 269 | version = "0.2.138" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" 272 | 273 | [[package]] 274 | name = "link-cplusplus" 275 | version = "1.0.8" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" 278 | dependencies = [ 279 | "cc", 280 | ] 281 | 282 | [[package]] 283 | name = "log" 284 | version = "0.4.17" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 287 | dependencies = [ 288 | "cfg-if 1.0.0", 289 | ] 290 | 291 | [[package]] 292 | name = "memoffset" 293 | version = "0.7.1" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" 296 | dependencies = [ 297 | "autocfg", 298 | ] 299 | 300 | [[package]] 301 | name = "mio" 302 | version = "0.6.23" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" 305 | dependencies = [ 306 | "cfg-if 0.1.10", 307 | "fuchsia-zircon", 308 | "fuchsia-zircon-sys", 309 | "iovec", 310 | "kernel32-sys", 311 | "libc", 312 | "log", 313 | "miow", 314 | "net2", 315 | "slab", 316 | "winapi 0.2.8", 317 | ] 318 | 319 | [[package]] 320 | name = "miow" 321 | version = "0.2.2" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" 324 | dependencies = [ 325 | "kernel32-sys", 326 | "net2", 327 | "winapi 0.2.8", 328 | "ws2_32-sys", 329 | ] 330 | 331 | [[package]] 332 | name = "net2" 333 | version = "0.2.38" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "74d0df99cfcd2530b2e694f6e17e7f37b8e26bb23983ac530c0c97408837c631" 336 | dependencies = [ 337 | "cfg-if 0.1.10", 338 | "libc", 339 | "winapi 0.3.9", 340 | ] 341 | 342 | [[package]] 343 | name = "nix" 344 | version = "0.26.1" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694" 347 | dependencies = [ 348 | "bitflags", 349 | "cfg-if 1.0.0", 350 | "libc", 351 | "memoffset", 352 | "pin-utils", 353 | "static_assertions", 354 | ] 355 | 356 | [[package]] 357 | name = "num-integer" 358 | version = "0.1.45" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 361 | dependencies = [ 362 | "autocfg", 363 | "num-traits", 364 | ] 365 | 366 | [[package]] 367 | name = "num-traits" 368 | version = "0.2.15" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 371 | dependencies = [ 372 | "autocfg", 373 | ] 374 | 375 | [[package]] 376 | name = "once_cell" 377 | version = "1.16.0" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" 380 | 381 | [[package]] 382 | name = "pin-utils" 383 | version = "0.1.0" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 386 | 387 | [[package]] 388 | name = "proc-macro-error" 389 | version = "1.0.4" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 392 | dependencies = [ 393 | "proc-macro-error-attr", 394 | "proc-macro2", 395 | "quote", 396 | "syn", 397 | "version_check", 398 | ] 399 | 400 | [[package]] 401 | name = "proc-macro-error-attr" 402 | version = "1.0.4" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 405 | dependencies = [ 406 | "proc-macro2", 407 | "quote", 408 | "version_check", 409 | ] 410 | 411 | [[package]] 412 | name = "proc-macro2" 413 | version = "1.0.49" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" 416 | dependencies = [ 417 | "unicode-ident", 418 | ] 419 | 420 | [[package]] 421 | name = "quote" 422 | version = "1.0.23" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 425 | dependencies = [ 426 | "proc-macro2", 427 | ] 428 | 429 | [[package]] 430 | name = "ryu" 431 | version = "1.0.12" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" 434 | 435 | [[package]] 436 | name = "scratch" 437 | version = "1.0.3" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" 440 | 441 | [[package]] 442 | name = "serde" 443 | version = "1.0.151" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "97fed41fc1a24994d044e6db6935e69511a1153b52c15eb42493b26fa87feba0" 446 | dependencies = [ 447 | "serde_derive", 448 | ] 449 | 450 | [[package]] 451 | name = "serde_derive" 452 | version = "1.0.151" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "255abe9a125a985c05190d687b320c12f9b1f0b99445e608c21ba0782c719ad8" 455 | dependencies = [ 456 | "proc-macro2", 457 | "quote", 458 | "syn", 459 | ] 460 | 461 | [[package]] 462 | name = "serde_json" 463 | version = "1.0.91" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" 466 | dependencies = [ 467 | "itoa", 468 | "ryu", 469 | "serde", 470 | ] 471 | 472 | [[package]] 473 | name = "shimmy" 474 | version = "0.2.0" 475 | dependencies = [ 476 | "chrono", 477 | "libc", 478 | "log", 479 | "mio", 480 | "nix", 481 | "serde", 482 | "serde_json", 483 | "structopt", 484 | "syslog", 485 | ] 486 | 487 | [[package]] 488 | name = "slab" 489 | version = "0.4.7" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" 492 | dependencies = [ 493 | "autocfg", 494 | ] 495 | 496 | [[package]] 497 | name = "static_assertions" 498 | version = "1.1.0" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 501 | 502 | [[package]] 503 | name = "strsim" 504 | version = "0.8.0" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 507 | 508 | [[package]] 509 | name = "structopt" 510 | version = "0.3.26" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" 513 | dependencies = [ 514 | "clap", 515 | "lazy_static", 516 | "structopt-derive", 517 | ] 518 | 519 | [[package]] 520 | name = "structopt-derive" 521 | version = "0.4.18" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 524 | dependencies = [ 525 | "heck", 526 | "proc-macro-error", 527 | "proc-macro2", 528 | "quote", 529 | "syn", 530 | ] 531 | 532 | [[package]] 533 | name = "syn" 534 | version = "1.0.107" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" 537 | dependencies = [ 538 | "proc-macro2", 539 | "quote", 540 | "unicode-ident", 541 | ] 542 | 543 | [[package]] 544 | name = "syslog" 545 | version = "5.0.0" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "9a5d8ef1b679c07976f3ee336a436453760c470f54b5e7237556728b8589515d" 548 | dependencies = [ 549 | "error-chain", 550 | "libc", 551 | "log", 552 | "time", 553 | ] 554 | 555 | [[package]] 556 | name = "termcolor" 557 | version = "1.1.3" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 560 | dependencies = [ 561 | "winapi-util", 562 | ] 563 | 564 | [[package]] 565 | name = "textwrap" 566 | version = "0.11.0" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 569 | dependencies = [ 570 | "unicode-width", 571 | ] 572 | 573 | [[package]] 574 | name = "time" 575 | version = "0.1.45" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" 578 | dependencies = [ 579 | "libc", 580 | "wasi", 581 | "winapi 0.3.9", 582 | ] 583 | 584 | [[package]] 585 | name = "unicode-ident" 586 | version = "1.0.6" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 589 | 590 | [[package]] 591 | name = "unicode-segmentation" 592 | version = "1.10.0" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" 595 | 596 | [[package]] 597 | name = "unicode-width" 598 | version = "0.1.10" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 601 | 602 | [[package]] 603 | name = "vec_map" 604 | version = "0.8.2" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 607 | 608 | [[package]] 609 | name = "version_check" 610 | version = "0.9.4" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 613 | 614 | [[package]] 615 | name = "wasi" 616 | version = "0.10.0+wasi-snapshot-preview1" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 619 | 620 | [[package]] 621 | name = "wasm-bindgen" 622 | version = "0.2.83" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" 625 | dependencies = [ 626 | "cfg-if 1.0.0", 627 | "wasm-bindgen-macro", 628 | ] 629 | 630 | [[package]] 631 | name = "wasm-bindgen-backend" 632 | version = "0.2.83" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" 635 | dependencies = [ 636 | "bumpalo", 637 | "log", 638 | "once_cell", 639 | "proc-macro2", 640 | "quote", 641 | "syn", 642 | "wasm-bindgen-shared", 643 | ] 644 | 645 | [[package]] 646 | name = "wasm-bindgen-macro" 647 | version = "0.2.83" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" 650 | dependencies = [ 651 | "quote", 652 | "wasm-bindgen-macro-support", 653 | ] 654 | 655 | [[package]] 656 | name = "wasm-bindgen-macro-support" 657 | version = "0.2.83" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" 660 | dependencies = [ 661 | "proc-macro2", 662 | "quote", 663 | "syn", 664 | "wasm-bindgen-backend", 665 | "wasm-bindgen-shared", 666 | ] 667 | 668 | [[package]] 669 | name = "wasm-bindgen-shared" 670 | version = "0.2.83" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" 673 | 674 | [[package]] 675 | name = "winapi" 676 | version = "0.2.8" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 679 | 680 | [[package]] 681 | name = "winapi" 682 | version = "0.3.9" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 685 | dependencies = [ 686 | "winapi-i686-pc-windows-gnu", 687 | "winapi-x86_64-pc-windows-gnu", 688 | ] 689 | 690 | [[package]] 691 | name = "winapi-build" 692 | version = "0.1.1" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 695 | 696 | [[package]] 697 | name = "winapi-i686-pc-windows-gnu" 698 | version = "0.4.0" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 701 | 702 | [[package]] 703 | name = "winapi-util" 704 | version = "0.1.5" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 707 | dependencies = [ 708 | "winapi 0.3.9", 709 | ] 710 | 711 | [[package]] 712 | name = "winapi-x86_64-pc-windows-gnu" 713 | version = "0.4.0" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 716 | 717 | [[package]] 718 | name = "ws2_32-sys" 719 | version = "0.2.1" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 722 | dependencies = [ 723 | "winapi 0.2.8", 724 | "winapi-build", 725 | ] 726 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shimmy" 3 | version = "0.2.0" 4 | authors = ["Ivan Velichko "] 5 | edition = "2018" 6 | default-run = "shimmy" 7 | 8 | [dependencies] 9 | chrono = "0.4" 10 | libc = "0.2" 11 | log = "0.4" 12 | mio = "0.6" 13 | nix = "0.26.1" 14 | serde = { version = "1.0", features = ["derive"] } 15 | serde_json = "1.0" 16 | structopt = "0.3" 17 | syslog = "5.0.0" 18 | 19 | [[bin]] 20 | name = "fifo1" 21 | path = "src/playground/fifo1.rs" 22 | 23 | [[bin]] 24 | name = "pipe1" 25 | path = "src/playground/pipe1.rs" 26 | 27 | [[bin]] 28 | name = "pipe2" 29 | path = "src/playground/pipe2.rs" 30 | 31 | [[bin]] 32 | name = "signalfd" 33 | path = "src/playground/signalfd.rs" 34 | 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # shimmy - container runtime shim 2 | 3 | Shimmy is a simplistic shim between _container manager_ and _container runtime_. It's primary designed to make programmatic _runc_ execution more friendly for the launching process. It does a couple of handy things: 4 | 5 | - Detaches container runtime process from the launching process. 6 | - Forwards container STDOUT and STDERR to logs. 7 | - Tracks container termination and writes its status on disk. 8 | - [TODO] Allows attaching to container STDIN to forward some data in. 9 | - [TODO] Allows attaching to container STDOUT & STDERR to read some data from. 10 | - [TODO] PTY-driven attaching. 11 | 12 | Similar projects: 13 | 14 | - conmon 15 | - containerd runtime shim 16 | 17 | Read more about the project on my blog: 18 | 19 | - Implementing container runtime shim: runc 20 | - Implementing container runtime shim: first code 21 | 22 | ## Usage 23 | 24 | ```bash 25 | # build debug version 26 | cargo build --bin shimmy 27 | 28 | # build release version 29 | cargo build --bin shimmy --release 30 | 31 | # integrational tests (conman is required) 32 | https://github.com/iximiuz/conman/blob/v0.0.2/test/shimmy/shimmy_test.go 33 | ``` 34 | 35 | -------------------------------------------------------------------------------- /playground/fork_say.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | if (fork()) { 6 | return 0; 7 | } 8 | 9 | for (int i = 0; i < 10; i++) { 10 | printf("i'm saying %d\n", i); 11 | fflush(stdout); 12 | sleep(1); 13 | if (i != 9) { 14 | sleep(1); 15 | } 16 | } 17 | return 0; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /playground/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int main(int argc, char *argv[]) { 8 | // create pipe 9 | // fork 10 | // child: 11 | // close stdin & stderr 12 | // dup pipe to stdout 13 | // exec say or fork_say 14 | // fork again: 15 | // child: 16 | // read from pipe until its end and pint out 17 | // waitpid for the first child and report 18 | // waitpid for the second child 19 | // exit 20 | 21 | if (argc != 2) { 22 | printf("executable is not specified\n"); 23 | return 1; 24 | } 25 | 26 | printf("start\n"); 27 | 28 | int fds[2]; // fds[0] read; fds[1] write 29 | if (0 != pipe(fds)) { 30 | perror("pipe() failed"); 31 | return 1; 32 | } 33 | 34 | int pid1 = fork(); 35 | if (pid1 < 0) { 36 | perror("fork() failed (1)"); 37 | return 1; 38 | } 39 | if (pid1 == 0) { 40 | printf("first child (pid=%d)\n", getpid()); 41 | close(fds[0]); 42 | 43 | int std_null_r = open("/dev/null", O_RDONLY); 44 | if (std_null_r < 0) { 45 | perror("open('/dev/null', O_RDONLY) failed"); 46 | exit(1); 47 | } 48 | if (dup2(std_null_r, 0) < 0) { 49 | perror("dup2(STDIN) failed"); 50 | exit(1); 51 | } 52 | 53 | int std_null_w = open("/dev/null", O_WRONLY); 54 | if (std_null_w < 0) { 55 | perror("open('/dev/null', O_RDONLY) failed"); 56 | exit(1); 57 | } 58 | if (dup2(std_null_w, 2) < 0) { 59 | perror("dup2(STDERR) failed"); 60 | exit(1); 61 | } 62 | 63 | if (dup2(fds[1], 1) < 0) { 64 | perror("dup2(STDOUT) failed"); 65 | exit(1); 66 | } 67 | 68 | execl(argv[1], argv[1], NULL); 69 | _exit(127); 70 | } 71 | 72 | close(fds[1]); 73 | 74 | int pid2 = fork(); 75 | if (pid2 < 0) { 76 | perror("fork() failed (2)"); 77 | return 1; 78 | } 79 | if (pid2 == 0) { 80 | printf("second child (pid=%d)\n", getpid()); 81 | char buf[256]; 82 | int nread = 0; 83 | do { 84 | nread = read(fds[0], buf, 254); 85 | buf[nread] = '\0'; 86 | printf("second child read %d bytes: %s\n", nread, buf); 87 | } while (nread > 0); 88 | 89 | sleep(5); 90 | printf("exiting second child\n"); 91 | return 0; 92 | } 93 | 94 | int pid3 = waitpid(-1, NULL, 0); 95 | printf("waitpid returned %d\n", pid3); 96 | 97 | int pid4 = waitpid(-1, NULL, 0); 98 | printf("waitpid returned %d\n", pid4); 99 | 100 | return 0; 101 | } 102 | 103 | -------------------------------------------------------------------------------- /playground/say.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | for (int i = 0; i < 10; i++) { 6 | printf("i'm saying %d\n", i); 7 | fflush(stdout); 8 | if (i != 9) { 9 | sleep(1); 10 | } 11 | } 12 | return 0; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/container/io.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::collections::HashMap; 3 | use std::io::{self, Read, Write}; 4 | use std::os::unix::io::AsRawFd; 5 | use std::rc::Rc; 6 | use std::result; 7 | 8 | use log::warn; 9 | use mio::{event::Evented, unix::EventedFd, Poll, PollOpt, Ready, Token}; 10 | 11 | use crate::nixtools::stdio::{IStream, OStream}; 12 | 13 | const BUF_SIZE: usize = 32 * 1024; 14 | 15 | #[derive(Debug)] 16 | pub enum Error { 17 | Sink(io::Error), 18 | Source(io::Error), 19 | } 20 | 21 | type Result = result::Result; 22 | 23 | pub struct Gatherer { 24 | sink: OStream, 25 | sources: HashMap>>, 26 | } 27 | 28 | impl Gatherer { 29 | pub fn new(sink: OStream) -> Self { 30 | Self { 31 | sink: sink, 32 | sources: HashMap::new(), 33 | } 34 | } 35 | 36 | pub fn gather(&mut self, token: Token) -> Result { 37 | match self.sources.get(&token) { 38 | Some(source) => { 39 | let mut buf = [0; BUF_SIZE]; 40 | let nread = source 41 | .borrow_mut() 42 | .read(&mut buf) 43 | .map_err(|err| Error::Source(err))?; 44 | 45 | self.sink 46 | .write_all(&buf[..nread]) 47 | .map_err(|err| Error::Sink(err))?; 48 | Ok(nread) 49 | } 50 | 51 | None => { 52 | warn!( 53 | "[shim] dubious, cannot find source stream for token {:?}", 54 | token 55 | ); 56 | Ok(0) 57 | } 58 | } 59 | } 60 | 61 | pub fn add_source(&mut self, token: Token, source: Rc>) { 62 | self.sources.insert(token, source); 63 | } 64 | 65 | pub fn remove_source(&mut self, token: Token) { 66 | self.sources.remove(&token); 67 | } 68 | } 69 | 70 | #[repr(u8)] 71 | #[derive(Copy, Clone)] 72 | enum ScattererKind { 73 | STDOUT = 1, 74 | STDERR = 2, 75 | } 76 | 77 | pub struct Scatterer { 78 | kind: ScattererKind, 79 | source: IStream, 80 | sinks: HashMap>>, 81 | next_sink_seq_no: usize, 82 | } 83 | 84 | impl Scatterer { 85 | pub fn stdout(source: IStream) -> Self { 86 | Self::new(ScattererKind::STDOUT, source) 87 | } 88 | 89 | pub fn stderr(source: IStream) -> Self { 90 | Self::new(ScattererKind::STDERR, source) 91 | } 92 | 93 | fn new(kind: ScattererKind, source: IStream) -> Self { 94 | Self { 95 | kind: kind, 96 | source: source, 97 | sinks: HashMap::new(), 98 | next_sink_seq_no: 0, 99 | } 100 | } 101 | 102 | pub fn scatter(&mut self) -> Result { 103 | let mut buf = [0; BUF_SIZE]; 104 | let nread = self 105 | .source 106 | .read(&mut buf[1..]) 107 | .map_err(|err| Error::Source(err))?; 108 | 109 | buf[0] = self.kind as u8; 110 | 111 | if nread > 0 { 112 | self.sinks.retain(|idx, writer| { 113 | match writer.borrow_mut().write_all(&buf[..nread + 1]) { 114 | Ok(_) => true, 115 | Err(err) => { 116 | warn!("[shim] failed to scatter STDIO to sink #{}: {}", idx, err); 117 | false 118 | } 119 | } 120 | }); 121 | } 122 | Ok(nread) 123 | } 124 | 125 | pub fn add_sink(&mut self, sink: Rc>) { 126 | self.sinks.insert(self.next_sink_seq_no, sink); 127 | self.next_sink_seq_no += 1; 128 | } 129 | } 130 | 131 | impl Evented for Scatterer { 132 | fn register( 133 | &self, 134 | poll: &Poll, 135 | token: Token, 136 | interest: Ready, 137 | opts: PollOpt, 138 | ) -> io::Result<()> { 139 | EventedFd(&self.source.as_raw_fd()).register(poll, token, interest, opts) 140 | } 141 | 142 | fn reregister( 143 | &self, 144 | poll: &Poll, 145 | token: Token, 146 | interest: Ready, 147 | opts: PollOpt, 148 | ) -> io::Result<()> { 149 | EventedFd(&self.source.as_raw_fd()).reregister(poll, token, interest, opts) 150 | } 151 | 152 | fn deregister(&self, poll: &Poll) -> io::Result<()> { 153 | EventedFd(&self.source.as_raw_fd()).deregister(poll) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/container/logger.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::fs::{File, OpenOptions}; 3 | use std::io::{self, Write}; 4 | use std::path::Path; 5 | use std::rc::Rc; 6 | 7 | use chrono::Utc; 8 | 9 | pub struct Logger { 10 | file: File, 11 | } 12 | 13 | impl Logger { 14 | pub fn new>(path: P) -> Self { 15 | Self { 16 | file: OpenOptions::new() 17 | .create(true) 18 | .write(true) 19 | .truncate(true) 20 | .open(path) 21 | .unwrap(), 22 | } 23 | } 24 | 25 | pub fn write(&mut self, stream: &'static str, buf: &[u8]) -> io::Result<()> { 26 | for line in buf.split(|c| *c == b'\n').filter(|l| l.len() > 0) { 27 | let message = format!( 28 | "{} {} {}\n", 29 | Utc::now().to_rfc3339(), 30 | stream, 31 | String::from_utf8_lossy(line) 32 | ); 33 | self.file.write_all(&message.as_bytes())?; 34 | } 35 | Ok(()) 36 | } 37 | } 38 | 39 | pub struct Writer { 40 | logger: Rc>, 41 | stream: &'static str, 42 | } 43 | 44 | impl Writer { 45 | pub fn stdout(logger: Rc>) -> Self { 46 | Self { 47 | logger: logger, 48 | stream: "stdout", 49 | } 50 | } 51 | 52 | pub fn stderr(logger: Rc>) -> Self { 53 | Self { 54 | logger: logger, 55 | stream: "stderr", 56 | } 57 | } 58 | } 59 | 60 | impl Write for Writer { 61 | fn write(&mut self, buf: &[u8]) -> io::Result { 62 | self.logger.borrow_mut().write(self.stream, &buf[1..])?; 63 | Ok(buf.len()) 64 | } 65 | 66 | fn flush(&mut self) -> io::Result<()> { 67 | Ok(()) // noop 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/container/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod server; 2 | 3 | mod io; 4 | mod logger; 5 | mod reactor; 6 | mod signal; 7 | -------------------------------------------------------------------------------- /src/container/reactor.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::collections::HashMap; 3 | use std::os::unix::io::{AsRawFd, RawFd}; 4 | use std::os::unix::net::{UnixListener, UnixStream}; 5 | use std::rc::Rc; 6 | use std::time::Duration; 7 | 8 | use log::{debug, error, warn}; 9 | use mio::unix::{EventedFd, UnixReady}; 10 | use mio::{Event, Events, Poll, PollOpt, Ready, Token}; 11 | 12 | use super::io; 13 | use super::signal; 14 | use crate::nixtools::process::TerminationStatus; 15 | 16 | const TOKEN_STDOUT: Token = Token(10); 17 | const TOKEN_STDERR: Token = Token(20); 18 | const TOKEN_SIGNAL: Token = Token(30); 19 | const TOKEN_ATTACH: Token = Token(40); 20 | const TOKEN_UNUSED: Token = Token(1000); 21 | 22 | pub struct Reactor { 23 | poll: Poll, 24 | heartbeat: Duration, 25 | stdin_gatherer: Option, 26 | stdin_once: bool, 27 | stdout_scatterer: Option, 28 | stderr_scatterer: Option, 29 | signal_handler: signal::Handler, 30 | attach_listener: UnixListener, 31 | attach_streams: HashMap, 32 | attach_last_token: Token, 33 | } 34 | 35 | impl Reactor { 36 | pub fn new( 37 | heartbeat: Duration, 38 | stdin_gatherer: Option, 39 | stdin_once: bool, 40 | stdout_scatterer: Option, 41 | stderr_scatterer: Option, 42 | signal_handler: signal::Handler, 43 | attach_listener: UnixListener, 44 | ) -> Self { 45 | let poll = Poll::new().expect("mio::Poll::new() failed"); 46 | 47 | if let Some(scatterer) = stdout_scatterer.as_ref() { 48 | poll.register( 49 | scatterer, 50 | TOKEN_STDOUT, 51 | Ready::readable() | UnixReady::hup(), 52 | PollOpt::level(), 53 | ) 54 | .expect("mio::Poll::register(container stdout) failed"); 55 | } 56 | 57 | if let Some(scatterer) = stderr_scatterer.as_ref() { 58 | poll.register( 59 | scatterer, 60 | TOKEN_STDERR, 61 | Ready::readable() | UnixReady::hup(), 62 | PollOpt::level(), 63 | ) 64 | .expect("mio::Poll::register(container stderr) failed"); 65 | } 66 | 67 | poll.register( 68 | &signal_handler, 69 | TOKEN_SIGNAL, 70 | Ready::readable() | UnixReady::error(), 71 | PollOpt::level(), 72 | ) 73 | .expect("mio::Poll::register(signalfd) failed"); 74 | 75 | poll.register( 76 | &EventedFd(&attach_listener.as_raw_fd()), 77 | TOKEN_ATTACH, 78 | Ready::readable() | UnixReady::error(), 79 | PollOpt::level(), 80 | ) 81 | .expect("mio::Poll::register(attach listener) failed"); 82 | 83 | Self { 84 | poll: poll, 85 | heartbeat: heartbeat, 86 | stdin_gatherer: stdin_gatherer, 87 | stdin_once: stdin_once, 88 | stdout_scatterer: stdout_scatterer, 89 | stderr_scatterer: stderr_scatterer, 90 | signal_handler: signal_handler, 91 | attach_listener: attach_listener, 92 | attach_streams: HashMap::new(), 93 | attach_last_token: TOKEN_UNUSED, 94 | } 95 | } 96 | 97 | pub fn run(&mut self) -> TerminationStatus { 98 | while self.signal_handler.container_status().is_none() { 99 | if self.poll_once() == 0 { 100 | debug!("[shim] still serving container"); 101 | } 102 | } 103 | 104 | // Drain stdout & stderr. 105 | self.poll 106 | .deregister(&self.signal_handler) 107 | .expect("mio::Poll::deregister(signalfd) failed"); 108 | self.poll 109 | .deregister(&EventedFd(&self.attach_listener.as_raw_fd())) 110 | .expect("mio::Poll::deregister(attach listener) failed"); 111 | self.heartbeat = Duration::from_millis(0); 112 | 113 | while self.poll_once() != 0 { 114 | debug!("[shim] draining container IO streams"); 115 | } 116 | 117 | self.signal_handler.container_status().unwrap() 118 | } 119 | 120 | fn poll_once(&mut self) -> i32 { 121 | let mut events = Events::with_capacity(128); 122 | self.poll 123 | .poll(&mut events, Some(self.heartbeat)) 124 | .expect("mio::Poll::poll() failed"); 125 | 126 | let mut event_count = 0; 127 | for event in events.iter() { 128 | event_count += 1; 129 | match event.token() { 130 | TOKEN_STDOUT => self.handle_stdout_event(event), 131 | TOKEN_STDERR => self.handle_stderr_event(event), 132 | TOKEN_SIGNAL => self.signal_handler.handle_signal(), 133 | TOKEN_ATTACH => self.handle_attach_listener_event(event), 134 | _ => self.handle_attach_stream_event(event), 135 | } 136 | } 137 | event_count 138 | } 139 | 140 | fn handle_stdout_event(&mut self, event: Event) { 141 | if self.stdout_scatterer.is_none() { 142 | warn!("[shim] dubious, got event on already closed STDOUT"); 143 | return; 144 | } 145 | 146 | if event.readiness().is_readable() { 147 | match self.stdout_scatterer.as_mut().unwrap().scatter() { 148 | Ok(nbytes) => { 149 | debug!( 150 | "[shim] scattered {} byte(s) from container's STDOUT", 151 | nbytes 152 | ); 153 | if nbytes == 0 { 154 | self.deregister_stdout_scatterer(); 155 | } 156 | } 157 | Err(err) => error!("[shim] failed scattering container's STDOUT: {:?}", err), 158 | } 159 | } else if UnixReady::from(event.readiness()).is_hup() { 160 | debug!("[shim] STDOUT HUP"); 161 | self.deregister_stdout_scatterer(); 162 | } 163 | } 164 | 165 | fn handle_stderr_event(&mut self, event: Event) { 166 | if self.stderr_scatterer.is_none() { 167 | warn!("[shim] dubious, got event on already closed STDERR"); 168 | return; 169 | } 170 | 171 | if event.readiness().is_readable() { 172 | match self.stderr_scatterer.as_mut().unwrap().scatter() { 173 | Ok(nbytes) => { 174 | debug!( 175 | "[shim] scattered {} byte(s) from container's STDERR", 176 | nbytes 177 | ); 178 | if nbytes == 0 { 179 | self.deregister_stderr_scatterer(); 180 | } 181 | } 182 | Err(err) => error!("[shim] failed scattering container's STDERR: {:?}", err), 183 | } 184 | } else if UnixReady::from(event.readiness()).is_hup() { 185 | debug!("[shim] STDERR HUP"); 186 | self.deregister_stderr_scatterer(); 187 | } 188 | } 189 | 190 | fn handle_attach_listener_event(&mut self, event: Event) { 191 | if UnixReady::from(event.readiness()).is_error() { 192 | match self.attach_listener.take_error() { 193 | Ok(None) => error!("[shim] attach listener event with error flag"), 194 | Ok(Some(err)) => error!("[shim] attach listener error: {}", err), 195 | Err(err) => error!("[shim] attach listener take_error() failed: {}", err), 196 | } 197 | return; 198 | } 199 | 200 | match self.attach_listener.accept() { 201 | Ok((stream, _)) => { 202 | debug!("[shim] new attach socket stream"); 203 | let token = self.register_attach_stream(stream.as_raw_fd()); 204 | let stream_rc: Rc> = Rc::new(RefCell::new(stream)); 205 | if let Some(ref mut stdin_gatherer) = self.stdin_gatherer { 206 | stdin_gatherer.add_source(token, stream_rc.clone()); 207 | } 208 | if let Some(ref mut stdout_scatterer) = self.stdout_scatterer { 209 | stdout_scatterer.add_sink(stream_rc.clone()); 210 | } 211 | if let Some(ref mut stderr_scatterer) = self.stderr_scatterer { 212 | stderr_scatterer.add_sink(stream_rc.clone()); 213 | } 214 | } 215 | Err(err) => error!("[shim] attach listener accept failed: {}", err), 216 | } 217 | } 218 | 219 | fn handle_attach_stream_event(&mut self, event: Event) { 220 | if self.stdin_gatherer.is_none() { 221 | warn!("[shim] container's STDIN has been already closed"); 222 | return; 223 | } 224 | 225 | let stdin_gatherer = self.stdin_gatherer.as_mut().unwrap(); 226 | if event.readiness().is_readable() { 227 | match stdin_gatherer.gather(event.token()) { 228 | Ok(nbytes) => { 229 | debug!("[shim] gathered {} byte(s) to container's STDIN", nbytes); 230 | if nbytes == 0 { 231 | debug!("[shim] attach socket stream eof"); 232 | stdin_gatherer.remove_source(event.token()); 233 | self.deregister_attach_stream(event.token()); 234 | if self.stdin_once { 235 | self.stdin_gatherer = None; 236 | } 237 | } 238 | } 239 | Err(io::Error::Source(err)) => { 240 | error!("[shim] attach socket stream read error: {}", err); 241 | stdin_gatherer.remove_source(event.token()); 242 | self.deregister_attach_stream(event.token()); 243 | if self.stdin_once { 244 | self.stdin_gatherer = None; 245 | } 246 | } 247 | Err(io::Error::Sink(err)) => { 248 | error!("[shim] write to container's STDIN failed: {}", err); 249 | } 250 | } 251 | } else if UnixReady::from(event.readiness()).is_hup() { 252 | debug!("[shim] attach socket stream HUP"); 253 | stdin_gatherer.remove_source(event.token()); 254 | self.deregister_attach_stream(event.token()); 255 | } 256 | } 257 | 258 | fn deregister_stdout_scatterer(&mut self) { 259 | self.poll 260 | .deregister(self.stdout_scatterer.as_ref().unwrap()) 261 | .expect("mio::Poll::deregister(container STDOUT) failed"); 262 | self.stdout_scatterer = None; 263 | } 264 | 265 | fn deregister_stderr_scatterer(&mut self) { 266 | self.poll 267 | .deregister(self.stderr_scatterer.as_ref().unwrap()) 268 | .expect("mio::Poll::deregister(container STDERR) failed"); 269 | self.stderr_scatterer = None; 270 | } 271 | 272 | fn register_attach_stream(&mut self, fd: RawFd) -> Token { 273 | self.attach_last_token = Token(usize::from(self.attach_last_token) + 1); 274 | 275 | self.poll 276 | .register( 277 | &EventedFd(&fd), 278 | self.attach_last_token, 279 | Ready::readable() | UnixReady::error() | UnixReady::hup(), 280 | PollOpt::level(), 281 | ) 282 | .expect("mio::Poll::register(attach stream) failed"); 283 | 284 | self.attach_streams.insert(self.attach_last_token, fd); 285 | 286 | self.attach_last_token 287 | } 288 | 289 | fn deregister_attach_stream(&mut self, token: Token) { 290 | if let Some(fd) = self.attach_streams.remove(&token) { 291 | self.poll 292 | .deregister(&EventedFd(&fd)) 293 | .expect("mio::Poll::deregister(attach conn) failed"); 294 | } else { 295 | warn!("[shim] attach stream with token {:?} not found", token); 296 | } 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /src/container/server.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::os::unix::net::UnixListener; 3 | use std::path::Path; 4 | use std::rc::Rc; 5 | use std::time::Duration; 6 | 7 | use log::debug; 8 | use nix::unistd::Pid; 9 | 10 | use crate::nixtools::process::TerminationStatus; 11 | use crate::nixtools::signal::Signalfd; 12 | use crate::nixtools::stdio::{IStream, OStream}; 13 | 14 | use super::io; 15 | use super::logger::{Logger, Writer}; 16 | use super::reactor::Reactor; 17 | use super::signal; 18 | 19 | pub struct Server { 20 | reactor: Reactor, 21 | } 22 | 23 | impl Server { 24 | pub fn new>( 25 | container_pid: Pid, 26 | container_attachfile: P, 27 | container_logfile: P, 28 | (container_stdin, container_stdout, container_stderr): ( 29 | Option, 30 | Option, 31 | Option, 32 | ), 33 | stdin_once: bool, 34 | sigfd: Signalfd, 35 | ) -> Self { 36 | let attach_listener = UnixListener::bind(container_attachfile).unwrap(); 37 | attach_listener 38 | .set_nonblocking(true) 39 | .expect("Couldn't set attach listener nonblocking"); 40 | 41 | let logger = Rc::new(RefCell::new(Logger::new(container_logfile))); 42 | 43 | let stdin_gatherer = match container_stdin { 44 | Some(stream) => Some(io::Gatherer::new(stream)), 45 | None => None, 46 | }; 47 | 48 | let stdout_scatterer = match container_stdout { 49 | Some(stream) => { 50 | let mut scatterer = io::Scatterer::stdout(stream); 51 | scatterer.add_sink(Rc::new(RefCell::new(Writer::stdout(logger.clone())))); 52 | Some(scatterer) 53 | } 54 | None => None, 55 | }; 56 | 57 | let stderr_scatterer = match container_stderr { 58 | Some(stream) => { 59 | let mut scatterer = io::Scatterer::stderr(stream); 60 | scatterer.add_sink(Rc::new(RefCell::new(Writer::stderr(logger.clone())))); 61 | Some(scatterer) 62 | } 63 | None => None, 64 | }; 65 | 66 | Self { 67 | reactor: Reactor::new( 68 | Duration::from_millis(5000), 69 | stdin_gatherer, 70 | stdin_once, 71 | stdout_scatterer, 72 | stderr_scatterer, 73 | signal::Handler::new(sigfd, container_pid), 74 | attach_listener, 75 | ), 76 | } 77 | } 78 | 79 | pub fn run(&mut self) -> TerminationStatus { 80 | debug!("[shim] serving container"); 81 | self.reactor.run() 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/container/signal.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use log::{debug, warn}; 4 | use mio::{Evented, Poll, PollOpt, Ready, Token}; 5 | use nix::sys::signal::{Signal, Signal::SIGCHLD}; 6 | use nix::unistd::Pid; 7 | 8 | use crate::nixtools::process::{get_child_termination_status, kill, KillResult, TerminationStatus}; 9 | use crate::nixtools::signal::Signalfd; 10 | 11 | pub struct Handler { 12 | sigfd: Signalfd, 13 | container_pid: Pid, 14 | container_status: Option, 15 | } 16 | 17 | impl Handler { 18 | pub fn new(sigfd: Signalfd, container_pid: Pid) -> Self { 19 | Self { 20 | sigfd: sigfd, 21 | container_pid: container_pid, 22 | container_status: None, 23 | } 24 | } 25 | 26 | pub fn container_status(&self) -> Option { 27 | self.container_status 28 | } 29 | 30 | pub fn handle_signal(&mut self) { 31 | match self.sigfd.read_signal() { 32 | SIGCHLD => self.handle_sigchld(), 33 | signal => forward_signal(self.container_pid, signal), 34 | } 35 | } 36 | 37 | fn handle_sigchld(&mut self) { 38 | if let Some(status) = get_child_termination_status() { 39 | assert!(self.container_pid == status.pid()); 40 | assert!(self.container_status.is_none()); 41 | self.container_status = Some(status); 42 | } 43 | } 44 | } 45 | 46 | impl Evented for Handler { 47 | fn register( 48 | &self, 49 | poll: &Poll, 50 | token: Token, 51 | interest: Ready, 52 | opts: PollOpt, 53 | ) -> io::Result<()> { 54 | self.sigfd.register(poll, token, interest, opts) 55 | } 56 | 57 | fn reregister( 58 | &self, 59 | poll: &Poll, 60 | token: Token, 61 | interest: Ready, 62 | opts: PollOpt, 63 | ) -> io::Result<()> { 64 | self.sigfd.reregister(poll, token, interest, opts) 65 | } 66 | 67 | fn deregister(&self, poll: &Poll) -> io::Result<()> { 68 | self.sigfd.deregister(poll) 69 | } 70 | } 71 | 72 | fn forward_signal(container_pid: Pid, signal: Signal) { 73 | debug!( 74 | "[shimmy] forwarding signal {} to container {}", 75 | signal, container_pid 76 | ); 77 | 78 | match kill(container_pid, signal) { 79 | Ok(KillResult::Delivered) => (), 80 | Ok(KillResult::ProcessNotFound) => { 81 | warn!("[shim] failed to forward signal to container, probably exited") 82 | } 83 | Err(err) => warn!("[shim] failed to forward signal to container: {}", err), 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod container; 2 | pub mod nixtools; 3 | pub mod runtime; 4 | pub mod syncpipe; 5 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | use std::fs; 3 | use std::io::Read; 4 | use std::panic; 5 | use std::path::{Path, PathBuf}; 6 | use std::process::exit; 7 | use std::str::FromStr; 8 | 9 | use chrono::Utc; 10 | use log::{debug, error, info, warn}; 11 | use nix::sys::signal::{ 12 | Signal, 13 | Signal::{SIGCHLD, SIGINT, SIGKILL, SIGQUIT, SIGTERM}, 14 | }; 15 | use nix::unistd::{execv, fork, ForkResult, Pid}; 16 | use structopt::StructOpt; 17 | use syslog::{BasicLogger, Facility, Formatter3164}; 18 | 19 | use shimmy::container::server::Server as ContainerServer; 20 | use shimmy::nixtools::misc::{ 21 | _exit, session_start, set_child_subreaper, set_parent_death_signal, to_pipe_fd, 22 | }; 23 | use shimmy::nixtools::process::{ 24 | kill, KillResult, TerminationStatus as ProcessTerminationStatus, 25 | TerminationStatus::{Exited, Signaled}, 26 | }; 27 | use shimmy::nixtools::signal::{signals_block, signals_restore, Signalfd}; 28 | use shimmy::nixtools::stdio::{create_pipes, set_stdio}; 29 | use shimmy::runtime::{await_runtime_termination, TerminationStatus as RuntimeTerminationStatus}; 30 | use shimmy::syncpipe::SyncPipe; 31 | 32 | #[derive(Debug, StructOpt)] 33 | #[structopt(name = "shimmy", about = "shimmy command line arguments")] 34 | struct CliOpt { 35 | #[structopt(long = "shimmy-pidfile", parse(from_os_str))] 36 | pidfile: PathBuf, 37 | 38 | #[structopt(long = "shimmy-log-level", default_value = "INFO", parse(try_from_str = log::LevelFilter::from_str))] 39 | loglevel: log::LevelFilter, 40 | 41 | /// sync pipe file descriptor 42 | #[structopt(long = "syncpipe-fd", env = "_OCI_SYNCPIPE")] 43 | syncpipe_fd: i32, 44 | 45 | /// runtime executable path (eg. /usr/bin/runc) 46 | #[structopt(long = "runtime", parse(from_os_str))] 47 | runtime_path: PathBuf, 48 | 49 | #[structopt(long = "runtime-arg", multiple = true)] 50 | runtime_args: Vec, 51 | 52 | #[structopt(long = "bundle", parse(from_os_str))] 53 | bundle: PathBuf, 54 | 55 | #[structopt(long = "container-id")] 56 | container_id: String, 57 | 58 | #[structopt(long = "container-pidfile", parse(from_os_str))] 59 | container_pidfile: PathBuf, 60 | 61 | #[structopt(long = "container-logfile", parse(from_os_str))] 62 | container_logfile: PathBuf, 63 | 64 | #[structopt(long = "container-exitfile", parse(from_os_str))] 65 | container_exitfile: PathBuf, 66 | 67 | #[structopt(long = "container-attachfile", parse(from_os_str))] 68 | container_attachfile: PathBuf, 69 | 70 | #[structopt(long = "stdin")] 71 | stdin: bool, 72 | 73 | #[structopt(long = "stdin-once")] 74 | stdin_once: bool, 75 | } 76 | 77 | fn main() { 78 | // Main process 79 | let opt = CliOpt::from_args(); 80 | 81 | setup_logger(opt.loglevel); 82 | info!("[main] shimmy says hi!"); 83 | 84 | match unsafe { fork() } { 85 | Ok(ForkResult::Parent { child }) => { 86 | // Main process (cont.) 87 | write_runtime_pidfile(opt.pidfile, child); 88 | exit(0); 89 | } 90 | Ok(ForkResult::Child) => (), // Shim process 91 | Err(err) => panic!("fork() of the shim process failed: {}", err), 92 | }; 93 | 94 | // Shim process (cont.) 95 | debug!("[shim] initializing..."); 96 | 97 | set_stdio((None, None, None)); 98 | session_start(); 99 | set_child_subreaper(); 100 | 101 | let oldmask = signals_block(&[SIGCHLD, SIGINT, SIGQUIT, SIGTERM]); 102 | let (iomaster, ioslave) = create_pipes(opt.stdin, true, true); 103 | 104 | let runtime_pid = match unsafe { fork() } { 105 | Ok(ForkResult::Parent { child }) => child, 106 | Ok(ForkResult::Child) => { 107 | // Container runtime process 108 | debug!("[runtime] I've been forked!"); 109 | 110 | // This will kill only runc top process (if it's still alive). 111 | // Forked by runc processes (i.e. init and container itself) 112 | // will not be affected (for better or for worse). 113 | set_parent_death_signal(SIGKILL); 114 | 115 | signals_restore(&oldmask); 116 | set_stdio(ioslave.streams()); 117 | exec_oci_runtime(RuntimeCommand { 118 | runtime_path: opt.runtime_path, 119 | runtime_args: opt.runtime_args, 120 | container_id: opt.container_id, 121 | pidfile: opt.container_pidfile, 122 | bundle: opt.bundle, 123 | }); 124 | _exit(127); 125 | } 126 | Err(err) => panic!("fork() of the container runtime process failed: {}", err), 127 | }; 128 | 129 | // Shim process (cont.) 130 | drop(ioslave); 131 | 132 | let mut sigfd = Signalfd::new(&[SIGCHLD, SIGINT, SIGQUIT, SIGTERM]); 133 | match await_runtime_termination(&mut sigfd, runtime_pid) { 134 | RuntimeTerminationStatus::Solitary(Exited(.., 0), inflight) => { 135 | debug!("[shim] runtime terminated normally"); 136 | 137 | let container_pid = read_container_pidfile(opt.container_pidfile); 138 | 139 | // Make sure we are ready to serve container 140 | // before reporting so back to the manager 141 | // (i.e. attach socket is ready, logger is ready, etc). 142 | let mut container_server = ContainerServer::new( 143 | container_pid, 144 | opt.container_attachfile, 145 | opt.container_logfile, 146 | iomaster.streams(), 147 | opt.stdin_once, 148 | sigfd, 149 | ); 150 | 151 | SyncPipe::new(to_pipe_fd(opt.syncpipe_fd)).report_container_pid(container_pid); 152 | 153 | if let Some(sig) = inflight { 154 | deliver_inflight_signal(container_pid, sig); 155 | } 156 | 157 | save_container_termination_status(opt.container_exitfile, container_server.run()); 158 | } 159 | 160 | ts => { 161 | warn!("[shim] runtime terminated abnormally: {}", ts); 162 | let mut buf = Vec::new(); 163 | if let (_, _, Some(mut stderr)) = iomaster.streams() { 164 | if let Err(err) = stderr.read_to_end(&mut buf) { 165 | warn!("[shim] failed to read runtime's STDERR: {}", err); 166 | } 167 | } 168 | SyncPipe::new(to_pipe_fd(opt.syncpipe_fd)) 169 | .report_abnormal_runtime_termination(ts, &buf); 170 | } 171 | } 172 | 173 | info!("[shim] shimmy says bye!"); 174 | } 175 | 176 | fn deliver_inflight_signal(container_pid: Pid, signal: Signal) { 177 | match kill(container_pid, signal) { 178 | Ok(KillResult::Delivered) => (), 179 | Ok(KillResult::ProcessNotFound) => { 180 | warn!("Failed to deliver inflight signal to container, probably exited") 181 | } 182 | Err(err) => warn!("Failed to deliver inflight signal to container: {}", err), 183 | } 184 | } 185 | 186 | fn save_container_termination_status>( 187 | filename: P, 188 | status: ProcessTerminationStatus, 189 | ) { 190 | debug!( 191 | "[shim] saving container termination status [{}] to {}", 192 | status, 193 | filename.as_ref().display() 194 | ); 195 | 196 | let now = Utc::now().to_rfc3339(); 197 | let message = match status { 198 | Exited(.., code) => format!( 199 | r#"{{"at": "{}", "reason": "exited", "exitCode": {}}}"#, 200 | now, code, 201 | ), 202 | Signaled(.., sig) => format!( 203 | r#"{{"at": "{}", "reason": "signaled", "signal": {}}}"#, 204 | now, sig as libc::c_int, 205 | ), 206 | }; 207 | if let Err(err) = fs::write(&filename, message) { 208 | panic!( 209 | "write() to container exit file {} failed: {}", 210 | filename.as_ref().display(), 211 | err 212 | ) 213 | } 214 | } 215 | 216 | fn read_container_pidfile>(filename: P) -> Pid { 217 | let content = fs::read_to_string(&filename).expect("fs::read_to_string() failed"); 218 | return Pid::from_raw( 219 | content 220 | .parse::() 221 | .expect("failed to parse container PID file"), 222 | ); 223 | } 224 | 225 | fn write_runtime_pidfile>(filename: P, pid: Pid) { 226 | debug!( 227 | "[main] writing shim PID {} to {}", 228 | pid, 229 | filename.as_ref().display() 230 | ); 231 | 232 | if let Err(err) = fs::write(&filename, format!("{}", pid)) { 233 | panic!("write() to {} failed: {}", filename.as_ref().display(), err) 234 | } 235 | } 236 | 237 | struct RuntimeCommand { 238 | runtime_path: PathBuf, 239 | runtime_args: Vec, 240 | container_id: String, 241 | pidfile: PathBuf, 242 | bundle: PathBuf, 243 | } 244 | 245 | impl RuntimeCommand { 246 | fn to_argv(&self) -> Vec { 247 | let mut argv = Vec::new(); 248 | argv.push(CString::new(self.runtime_path.to_str().unwrap()).unwrap()); 249 | 250 | for arg in self.runtime_args.iter() { 251 | argv.push(CString::new(arg.trim_matches('\'')).unwrap()); 252 | } 253 | 254 | argv.push(CString::new("create").unwrap()); 255 | argv.push(CString::new("--bundle").unwrap()); 256 | argv.push(CString::new(self.bundle.to_str().unwrap()).unwrap()); 257 | argv.push(CString::new("--pid-file").unwrap()); 258 | argv.push(CString::new(self.pidfile.to_str().unwrap()).unwrap()); 259 | argv.push(CString::new(self.container_id.as_str()).unwrap()); 260 | 261 | return argv; 262 | } 263 | } 264 | 265 | fn exec_oci_runtime(cmd: RuntimeCommand) { 266 | let argv = cmd.to_argv(); 267 | debug!("[runtime] execing runc: {:?}", &argv); 268 | 269 | if let Err(err) = execv(&argv[0], &argv) { 270 | panic!("execv() failed: {}", err); 271 | } 272 | } 273 | 274 | fn setup_logger(level: log::LevelFilter) { 275 | let formatter = Formatter3164 { 276 | facility: Facility::LOG_USER, 277 | hostname: None, 278 | process: "shimmy".into(), 279 | pid: 0, 280 | }; 281 | 282 | let logger = syslog::unix(formatter).expect("could not connect to syslog"); 283 | log::set_boxed_logger(Box::new(BasicLogger::new(logger))) 284 | .map(|()| log::set_max_level(level)) 285 | .expect("log::set_boxed_logger() failed"); 286 | 287 | panic::set_hook(Box::new(|info| { 288 | error!("{}", info); 289 | })); 290 | } 291 | -------------------------------------------------------------------------------- /src/nixtools/misc.rs: -------------------------------------------------------------------------------- 1 | use std::os::unix::io::RawFd; 2 | 3 | use libc::{self, c_int, c_ulong}; 4 | use nix::errno::Errno; 5 | use nix::fcntl::{fcntl, FcntlArg, FdFlag}; 6 | use nix::sys::signal::Signal; 7 | use nix::unistd::setsid; 8 | use nix::Result; 9 | 10 | pub fn to_pipe_fd(maybe_fd: i32) -> RawFd { 11 | let fd = maybe_fd as c_int; 12 | 13 | match fcntl(fd, FcntlArg::F_SETFD(FdFlag::FD_CLOEXEC)) { 14 | Ok(rv) if rv != -1 => (), 15 | _ => panic!("fcntl(F_SETFD, FD_CLOEXEC"), 16 | } 17 | 18 | return fd; 19 | } 20 | 21 | pub fn session_start() { 22 | setsid().expect("sessid() failed"); 23 | } 24 | 25 | pub fn set_child_subreaper() { 26 | prctl(PrctlOption::PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) 27 | .expect("prctl(PR_SET_CHILD_SUBREAPER) failed"); 28 | } 29 | 30 | pub fn set_parent_death_signal(sig: Signal) { 31 | prctl(PrctlOption::PR_SET_PDEATHSIG, sig as c_ulong, 0, 0, 0) 32 | .expect("prctl(PR_SET_PDEATHSIG) failed"); 33 | } 34 | 35 | pub fn _exit(status: libc::c_int) -> ! { 36 | unsafe { 37 | libc::_exit(status); 38 | } 39 | } 40 | 41 | #[repr(i32)] 42 | #[allow(non_camel_case_types)] 43 | enum PrctlOption { 44 | PR_SET_CHILD_SUBREAPER = libc::PR_SET_CHILD_SUBREAPER, 45 | PR_SET_PDEATHSIG = libc::PR_SET_PDEATHSIG, 46 | } 47 | 48 | fn prctl( 49 | option: PrctlOption, 50 | arg2: c_ulong, 51 | arg3: c_ulong, 52 | arg4: c_ulong, 53 | arg5: c_ulong, 54 | ) -> Result<()> { 55 | let res = unsafe { libc::prctl(option as c_int, arg2, arg3, arg4, arg5) }; 56 | Errno::result(res).map(drop) 57 | } 58 | -------------------------------------------------------------------------------- /src/nixtools/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod misc; 2 | pub mod pipe; 3 | pub mod process; 4 | pub mod signal; 5 | pub mod stdio; 6 | -------------------------------------------------------------------------------- /src/nixtools/pipe.rs: -------------------------------------------------------------------------------- 1 | use std::os::unix::io::RawFd; 2 | 3 | use log::error; 4 | use nix::fcntl::OFlag; 5 | use nix::unistd::{close, pipe2}; 6 | 7 | pub struct Pipe { 8 | rd: RawFd, 9 | wr: RawFd, 10 | } 11 | 12 | impl Pipe { 13 | pub fn new() -> Self { 14 | let (rd, wr) = pipe2(OFlag::O_CLOEXEC).expect("pipe2() failed"); 15 | Pipe { rd, wr } 16 | } 17 | 18 | pub fn rd(&self) -> RawFd { 19 | self.rd 20 | } 21 | pub fn wr(&self) -> RawFd { 22 | self.wr 23 | } 24 | } 25 | 26 | impl Drop for Pipe { 27 | fn drop(&mut self) { 28 | if let Err(err) = close(self.rd) { 29 | error!("close({}) pipe.rd failed: {}", self.rd, err); 30 | } 31 | if let Err(err) = close(self.wr) { 32 | error!("close({}) pipe.wr failed: {}", self.wr, err); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/nixtools/process.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use nix::sys::signal::{self, Signal}; 4 | use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus}; 5 | use nix::unistd::Pid; 6 | 7 | type ExitCode = i32; 8 | 9 | #[derive(Copy, Clone, Debug)] 10 | pub enum TerminationStatus { 11 | Exited(Pid, ExitCode), 12 | Signaled(Pid, Signal), 13 | } 14 | 15 | impl fmt::Display for TerminationStatus { 16 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 17 | match self { 18 | Self::Exited(.., code) => write!(f, "Exited with code {}", code), 19 | Self::Signaled(.., sig) => write!(f, "Received signal {}", sig), 20 | } 21 | } 22 | } 23 | 24 | impl TerminationStatus { 25 | pub fn pid(&self) -> Pid { 26 | match &self { 27 | Self::Exited(pid, ..) => *pid, 28 | Self::Signaled(pid, ..) => *pid, 29 | } 30 | } 31 | 32 | pub fn exit_code(&self) -> Option { 33 | match self { 34 | Self::Exited(.., code) => Some(*code), 35 | _ => None, 36 | } 37 | } 38 | } 39 | 40 | pub fn get_child_termination_status() -> Option { 41 | // Wait for any child state change: 42 | match waitpid(Pid::from_raw(-1), Some(WaitPidFlag::WNOHANG)) { 43 | Ok(WaitStatus::Exited(pid, code)) => Some(TerminationStatus::Exited(pid, code)), 44 | 45 | Ok(WaitStatus::Signaled(pid, sig, ..)) => Some(TerminationStatus::Signaled(pid, sig)), 46 | 47 | Ok(_) => None, // non-terminal state change 48 | 49 | Err(nix::Error::ECHILD) => None, // no children left 50 | 51 | Err(err) => panic!("waitpid() failed with error {:?}", err), 52 | } 53 | } 54 | 55 | pub enum KillResult { 56 | Delivered, 57 | ProcessNotFound, 58 | } 59 | 60 | pub fn kill(pid: Pid, sig: Signal) -> nix::Result { 61 | match signal::kill(pid, sig) { 62 | Ok(_) => Ok(KillResult::Delivered), 63 | 64 | Err(nix::Error::ESRCH) => Ok(KillResult::ProcessNotFound), 65 | 66 | Err(err) => Err(err), 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/nixtools/signal.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::os::unix::io::AsRawFd; 3 | 4 | use mio::{unix::EventedFd, Evented, Poll, PollOpt, Ready, Token}; 5 | use nix::sys::{ 6 | signal::{sigprocmask, SigSet, SigmaskHow, Signal}, 7 | signalfd, 8 | }; 9 | use std::convert::TryFrom; 10 | 11 | pub fn signals_block(signals: &[Signal]) -> SigSet { 12 | let mut oldmask = SigSet::empty(); 13 | sigprocmask( 14 | SigmaskHow::SIG_BLOCK, 15 | Some(&sigmask(signals)), 16 | Some(&mut oldmask), 17 | ) 18 | .expect("sigprocmask(SIG_BLOCK) failed"); 19 | return oldmask; 20 | } 21 | 22 | pub fn signals_restore(mask: &SigSet) { 23 | sigprocmask(SigmaskHow::SIG_SETMASK, Some(&mask), None) 24 | .expect("sigprocmask(SIG_SETMASK) failed"); 25 | } 26 | 27 | pub struct Signalfd(signalfd::SignalFd); 28 | 29 | impl Signalfd { 30 | pub fn new(signals: &[Signal]) -> Self { 31 | Self( 32 | signalfd::SignalFd::new(&sigmask(signals)) 33 | .expect(&format!("signalfd() failed for mask {:?}", signals)), 34 | ) 35 | } 36 | 37 | pub fn read_signal(&mut self) -> Signal { 38 | match self.0.read_signal() { 39 | Ok(Some(sinfo)) => { 40 | Signal::try_from(sinfo.ssi_signo as libc::c_int).expect("unexpected signo") 41 | } 42 | Ok(None) => panic!("wtf? We are in blocking mode"), 43 | Err(err) => panic!("read(signalfd) failed {}", err), 44 | } 45 | } 46 | } 47 | 48 | impl Evented for Signalfd { 49 | fn register( 50 | &self, 51 | poll: &Poll, 52 | token: Token, 53 | interest: Ready, 54 | opts: PollOpt, 55 | ) -> io::Result<()> { 56 | EventedFd(&self.0.as_raw_fd()).register(poll, token, interest, opts) 57 | } 58 | 59 | fn reregister( 60 | &self, 61 | poll: &Poll, 62 | token: Token, 63 | interest: Ready, 64 | opts: PollOpt, 65 | ) -> io::Result<()> { 66 | EventedFd(&self.0.as_raw_fd()).reregister(poll, token, interest, opts) 67 | } 68 | 69 | fn deregister(&self, poll: &Poll) -> io::Result<()> { 70 | EventedFd(&self.0.as_raw_fd()).deregister(poll) 71 | } 72 | } 73 | 74 | fn sigmask(signals: &[Signal]) -> SigSet { 75 | *signals.iter().fold(&mut SigSet::empty(), |mask, sig| { 76 | mask.add(*sig); 77 | mask 78 | }) 79 | } 80 | -------------------------------------------------------------------------------- /src/nixtools/stdio.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{self, Read, Write}; 3 | use std::mem; 4 | use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; 5 | 6 | use log::error; 7 | use nix::fcntl::{open, OFlag}; 8 | use nix::sys::stat::Mode; 9 | use nix::unistd::{close, dup2}; 10 | 11 | use crate::nixtools::pipe::Pipe; 12 | 13 | pub struct IStream(RawFd); 14 | 15 | impl Drop for IStream { 16 | fn drop(&mut self) { 17 | if let Err(err) = close(self.0) { 18 | error!("istream close({}) failed: {}", self.0, err); 19 | } 20 | } 21 | } 22 | 23 | impl Read for IStream { 24 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 25 | let mut file = unsafe { File::from_raw_fd(self.0) }; 26 | let res = file.read(buf); 27 | mem::forget(file); // omit the destruciton of the file, i.e. no call to close(fd). 28 | res 29 | } 30 | } 31 | 32 | impl AsRawFd for IStream { 33 | fn as_raw_fd(&self) -> RawFd { 34 | self.0 35 | } 36 | } 37 | 38 | pub struct OStream(RawFd); 39 | 40 | impl Drop for OStream { 41 | fn drop(&mut self) { 42 | if let Err(err) = close(self.0) { 43 | error!("ostream close({}) failed: {}", self.0, err); 44 | } 45 | } 46 | } 47 | 48 | impl Write for OStream { 49 | fn write(&mut self, buf: &[u8]) -> io::Result { 50 | let mut file = unsafe { File::from_raw_fd(self.0) }; 51 | let res = file.write(buf); 52 | mem::forget(file); // omit the destruciton of the file, i.e. no call to close(fd). 53 | res 54 | } 55 | 56 | fn flush(&mut self) -> io::Result<()> { 57 | Ok(()) // noop 58 | } 59 | } 60 | 61 | pub fn set_stdio((ins, outs, errs): (Option, Option, Option)) { 62 | match ins { 63 | Some(IStream(fd)) => { 64 | dup2(fd, 0).expect("dup2(fd, STDIN_FILENO) failed"); 65 | } 66 | None => { 67 | let fd = open_dev_null(OFlag::O_RDONLY); 68 | dup2(fd, 0).expect("dup2(fd, STDIN_FILENO) failed"); 69 | close(fd).expect("close('/dev/null (RDONLY)') failed"); 70 | } 71 | } 72 | 73 | match outs { 74 | Some(OStream(fd)) => { 75 | dup2(fd, 1).expect("dup2(fd, STDOUT_FILENO) failed"); 76 | } 77 | None => { 78 | let fd = open_dev_null(OFlag::O_WRONLY); 79 | dup2(fd, 1).expect("dup2(fd, STDOUT_FILENO) failed"); 80 | close(fd).expect("close('/dev/null (WRONLY)') failed"); 81 | } 82 | } 83 | 84 | match errs { 85 | Some(OStream(fd)) => { 86 | dup2(fd, 2).expect("dup2(fd, STDERR_FILENO) failed"); 87 | } 88 | None => { 89 | let fd = open_dev_null(OFlag::O_WRONLY); 90 | dup2(fd, 2).expect("dup2(fd, STDERR_FILENO) failed"); 91 | close(fd).expect("close('/dev/null (WRONLY)') failed"); 92 | } 93 | } 94 | } 95 | 96 | fn open_dev_null(flags: OFlag) -> RawFd { 97 | open("/dev/null", flags | OFlag::O_CLOEXEC, Mode::empty()).expect(&format!( 98 | "open('/dev/null') for {} failed", 99 | human_readable_mode(flags) 100 | )) 101 | } 102 | 103 | fn human_readable_mode(flags: OFlag) -> &'static str { 104 | match flags { 105 | _ if flags & OFlag::O_RDONLY == OFlag::O_RDONLY => "reading", 106 | _ if flags & OFlag::O_WRONLY == OFlag::O_WRONLY => "writing", 107 | _ => unreachable!(), 108 | } 109 | } 110 | 111 | pub struct PipeMaster { 112 | ins: Option, 113 | outs: Option, 114 | errs: Option, 115 | } 116 | 117 | impl PipeMaster { 118 | pub fn streams(self) -> (Option, Option, Option) { 119 | (self.ins, self.outs, self.errs) 120 | } 121 | } 122 | 123 | pub struct PipeSlave { 124 | ins: Option, 125 | outs: Option, 126 | errs: Option, 127 | } 128 | 129 | impl PipeSlave { 130 | pub fn streams(self) -> (Option, Option, Option) { 131 | (self.ins, self.outs, self.errs) 132 | } 133 | } 134 | 135 | pub fn create_pipes( 136 | use_stdin: bool, 137 | use_stdout: bool, 138 | use_stderr: bool, 139 | ) -> (PipeMaster, PipeSlave) { 140 | let mut master = PipeMaster { 141 | ins: None, 142 | outs: None, 143 | errs: None, 144 | }; 145 | let mut slave = PipeSlave { 146 | ins: None, 147 | outs: None, 148 | errs: None, 149 | }; 150 | 151 | if use_stdin { 152 | let stdin = Pipe::new(); 153 | master.ins = Some(OStream(stdin.wr())); 154 | slave.ins = Some(IStream(stdin.rd())); 155 | 156 | // To prevent pipe objects calling close on underlying file descriptors: 157 | mem::forget(stdin); 158 | } 159 | if use_stdout { 160 | let stdout = Pipe::new(); 161 | master.outs = Some(IStream(stdout.rd())); 162 | slave.outs = Some(OStream(stdout.wr())); 163 | 164 | // To prevent pipe objects calling close on underlying file descriptors: 165 | mem::forget(stdout); 166 | } 167 | if use_stderr { 168 | let stderr = Pipe::new(); 169 | master.errs = Some(IStream(stderr.rd())); 170 | slave.errs = Some(OStream(stderr.wr())); 171 | 172 | // To prevent pipe objects calling close on underlying file descriptors: 173 | mem::forget(stderr); 174 | } 175 | 176 | (master, slave) 177 | } 178 | -------------------------------------------------------------------------------- /src/playground/fifo1.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hi FIFO"); 3 | } 4 | -------------------------------------------------------------------------------- /src/playground/pipe1.rs: -------------------------------------------------------------------------------- 1 | use std::fs::OpenOptions; 2 | use std::io::prelude::*; 3 | use std::str; 4 | use std::thread::sleep; 5 | use std::time::Duration; 6 | 7 | use nix::fcntl::{open, OFlag}; 8 | use nix::sys::stat::Mode; 9 | use nix::unistd::{close, dup2, fork, getpid, getppid, pipe, read, ForkResult}; 10 | 11 | fn main() { 12 | println!("[main] Hi there! My pid is {}", getpid()); 13 | 14 | let (pr, pw) = match pipe() { 15 | Ok((pr, pw)) => (pr, pw), 16 | Err(err) => panic!("[main] pipe() failed: {}", err), 17 | }; 18 | 19 | match unsafe { fork() } { 20 | Ok(ForkResult::Parent { child, .. }) => { 21 | println!("[main] Forked new child with pid {}", child); 22 | } 23 | Ok(ForkResult::Child) => { 24 | close(pr).expect("[main] close(pw) failed"); 25 | 26 | let dev_null_r = open("/dev/null", OFlag::O_RDONLY, Mode::empty()).unwrap(); 27 | dup2(dev_null_r, 0).expect("dup2(STDIN) failed"); 28 | dup2(pw, 1).expect("dup2(STDOUT) failed"); 29 | dup2(pw, 2).expect("dup2(STDERR) failed"); 30 | 31 | let mut log = OpenOptions::new() 32 | .create(true) 33 | .write(true) 34 | .append(true) 35 | .open("/home/vagrant/shimmy/child.log") 36 | .unwrap(); 37 | loop { 38 | let msg = format!( 39 | "[child] I'm alive! My PID is {} and PPID is {}.", 40 | getpid(), 41 | getppid() 42 | ); 43 | println!("{}", msg); 44 | writeln!(log, "{}", msg).expect("[child] writeln!(log) failed"); 45 | sleep(Duration::from_millis(500)); 46 | } 47 | } 48 | Err(err) => panic!("[main] fork() failed: {}", err), 49 | }; 50 | 51 | close(pw).expect("[main] close(pw) failed"); 52 | 53 | for _ in 1..10 { 54 | let mut buf = vec![0; 1024]; 55 | let nread = read(pr, buf.as_mut_slice()).unwrap(); 56 | println!( 57 | "[main] read {} bytes: [{}]", 58 | nread, 59 | str::from_utf8(&buf).unwrap() 60 | ); 61 | sleep(Duration::from_millis(900)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/playground/pipe2.rs: -------------------------------------------------------------------------------- 1 | use std::str; 2 | use std::thread::sleep; 3 | use std::time::Duration; 4 | 5 | use nix::fcntl::{open, OFlag}; 6 | use nix::sys::stat::Mode; 7 | use nix::sys::wait::waitpid; 8 | use nix::unistd::{close, dup2, fork, getpid, getppid, pipe, read, ForkResult, Pid}; 9 | 10 | fn main() { 11 | println!("[main] Hi there! My pid is {}", getpid()); 12 | 13 | let (pr, pw) = match pipe() { 14 | Ok((pr, pw)) => (pr, pw), 15 | Err(err) => panic!("[main] pipe() failed: {}", err), 16 | }; 17 | 18 | match unsafe { fork() } { 19 | Ok(ForkResult::Parent { child, .. }) => { 20 | println!("[main] Forked new child with pid {}", child); 21 | } 22 | Ok(ForkResult::Child) => { 23 | close(pr).expect("[main] close(pw) failed"); 24 | dup2(pw, 1).expect("dup2(STDOUT) failed"); 25 | 26 | let dev_null_r = open("/dev/null", OFlag::O_RDONLY, Mode::empty()).unwrap(); 27 | dup2(dev_null_r, 0).expect("dup2(STDIN) failed"); 28 | 29 | let dev_null_w = open("/dev/null", OFlag::O_WRONLY, Mode::empty()).unwrap(); 30 | dup2(dev_null_w, 2).expect("dup2(STDERR) failed"); 31 | 32 | let msg = format!( 33 | "[child] I'm alive! My PID is {} and PPID is {}.", 34 | getpid(), 35 | getppid() 36 | ); 37 | println!("{}", msg); 38 | } 39 | Err(err) => panic!("[main] fork() failed: {}", err), 40 | }; 41 | 42 | close(pw).expect("[main] close(pw) failed"); 43 | waitpid(Pid::from_raw(-1), None).expect("waitpid() failed"); 44 | 45 | for _ in 1..1024 { 46 | let mut buf = vec![0; 54]; 47 | let nread = read(pr, buf.as_mut_slice()).unwrap(); 48 | println!( 49 | "[main] read {} bytes: [{}]", 50 | nread, 51 | str::from_utf8(&buf).unwrap() 52 | ); 53 | sleep(Duration::from_millis(100)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/playground/signalfd.rs: -------------------------------------------------------------------------------- 1 | use std::process::exit; 2 | use std::thread::sleep; 3 | use std::time::Duration; 4 | 5 | use nix::sys::signal::Signal::{SIGCHLD, SIGINT, SIGQUIT, SIGTERM}; 6 | use nix::sys::signalfd::*; 7 | use nix::sys::wait::{waitpid, WaitPidFlag}; 8 | use nix::unistd::{fork, getpid, ForkResult, Pid}; 9 | use nix::{self}; 10 | 11 | use shimmy::nixtools::{misc::set_child_subreaper, signal::signals_block}; 12 | 13 | fn main() { 14 | println!("Hi there! My pid is {}", getpid()); 15 | 16 | set_child_subreaper(); 17 | signals_block(&[SIGCHLD, SIGINT, SIGQUIT, SIGTERM]); 18 | println!("Signals have been blocked! Waiting for 10 seconds..."); 19 | 20 | match unsafe { fork() } { 21 | Ok(ForkResult::Parent { .. }) => (), 22 | Ok(ForkResult::Child) => { 23 | println!("[child] Hi there! My pid is {}", getpid()); 24 | match unsafe { fork() } { 25 | Ok(ForkResult::Parent { .. }) => (), 26 | Ok(ForkResult::Child) => { 27 | println!("[grandchild] Hi there! My pid is {}", getpid()); 28 | exit(124); 29 | } 30 | Err(err) => panic!("fork() failed {}", err), 31 | }; 32 | exit(123); 33 | } 34 | Err(err) => panic!("fork() failed {}", err), 35 | }; 36 | 37 | sleep(Duration::from_millis(10000)); 38 | 39 | let mut mask = SigSet::empty(); 40 | mask.add(signal::SIGCHLD); 41 | mask.add(signal::SIGINT); 42 | mask.add(signal::SIGQUIT); 43 | mask.add(signal::SIGTERM); 44 | mask.thread_block().expect("mask.thread_block() failed"); 45 | 46 | // let mut sfd = SignalFd::new(&mask).unwrap(); 47 | let mut sfd = SignalFd::with_flags(&mask, SfdFlags::SFD_NONBLOCK).unwrap(); 48 | 49 | loop { 50 | match sfd.read_signal() { 51 | Ok(Some(sig)) => { 52 | println!("Got a signal {:?}", sig); 53 | while sig.ssi_signo == SIGCHLD as u32 { 54 | match waitpid(Pid::from_raw(-1), Some(WaitPidFlag::WNOHANG)) { 55 | Ok(res) => println!("waitpid() returned {:?}", res), 56 | Err(nix::Error::ECHILD) => { 57 | break; 58 | } 59 | Err(err) => panic!("waitpid() failed {:?}", err), 60 | } 61 | } 62 | } 63 | Ok(None) => break, 64 | Err(err) => panic!("read(signalfd) failed {}", err), 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/runtime.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use log::debug; 4 | use nix::sys::signal::{ 5 | Signal, 6 | Signal::{SIGCHLD, SIGINT, SIGQUIT, SIGTERM}, 7 | }; 8 | use nix::unistd::Pid; 9 | 10 | use crate::nixtools::process::{ 11 | get_child_termination_status, kill, KillResult, TerminationStatus as ProcessTerminationStatus, 12 | }; 13 | use crate::nixtools::signal::Signalfd; 14 | 15 | #[derive(Copy, Clone, Debug)] 16 | pub enum TerminationStatus { 17 | // (runtime_status, inflight_signal) 18 | Solitary(ProcessTerminationStatus, Option), 19 | 20 | // (runtime_status, container_status, inflight_signal) 21 | Conjoint( 22 | ProcessTerminationStatus, 23 | ProcessTerminationStatus, 24 | Option, 25 | ), 26 | } 27 | 28 | impl fmt::Display for TerminationStatus { 29 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 30 | match self { 31 | Self::Solitary(rt, None) => write!(f, "Runtime {}.", rt), 32 | Self::Solitary(rt, Some(sig)) => { 33 | write!(f, "Runtime {}. Beware: inflight {} detected.", rt, sig) 34 | } 35 | Self::Conjoint(rt, ct, None) => write!(f, "Runtime {}. Container {}.", rt, ct), 36 | Self::Conjoint(rt, ct, Some(sig)) => write!( 37 | f, 38 | "Runtime {}. Container {}. Beware: inflight {} detected.", 39 | rt, ct, sig 40 | ), 41 | } 42 | } 43 | } 44 | 45 | impl TerminationStatus { 46 | fn new( 47 | runtime: ProcessTerminationStatus, 48 | container: Option, 49 | inflight: Option, 50 | ) -> Self { 51 | match container { 52 | None => Self::Solitary(runtime, inflight), 53 | Some(container) => Self::Conjoint(runtime, container, inflight), 54 | } 55 | } 56 | } 57 | 58 | pub fn await_runtime_termination(sigfd: &mut Signalfd, runtime_pid: Pid) -> TerminationStatus { 59 | debug!("[shim] awaiting runtime termination..."); 60 | 61 | let mut container: Option = None; 62 | let mut inflight: Option = None; 63 | loop { 64 | match sigfd.read_signal() { 65 | SIGCHLD => { 66 | debug!("[shim] SIGCHLD received, querying runtime status"); 67 | 68 | match get_termination_statuses(runtime_pid) { 69 | (Some(rt), ct) => { 70 | assert!( 71 | ct.is_none() || container.is_none(), 72 | "ambiguous container termination status" 73 | ); 74 | return TerminationStatus::new(rt, container.or(ct), inflight); 75 | } 76 | 77 | (None, Some(ct)) => { 78 | assert!( 79 | container.is_none(), 80 | "ambiguous container termination status" 81 | ); 82 | container = Some(ct); // Keep it for later use. 83 | } 84 | 85 | (None, None) => (), // Continue... 86 | } 87 | } 88 | 89 | sig if [SIGINT, SIGQUIT, SIGTERM].contains(&sig) => { 90 | debug!("[shim] {} received, propagating to runtime", sig); 91 | 92 | match kill(runtime_pid, sig) { 93 | Ok(KillResult::Delivered) => (), // Keep waiting for runtime termination... 94 | 95 | Ok(KillResult::ProcessNotFound) => { 96 | // Runtime already has exited, keep the signal 97 | // to send it to the container once we know its PID. 98 | inflight = Some(sig); 99 | } 100 | 101 | Err(err) => panic!("kill(runtime_pid, {}) failed: {:?}", sig, err), 102 | } 103 | } 104 | sig => panic!("unexpected signal received {:?}", sig), 105 | }; 106 | } 107 | } 108 | 109 | fn get_termination_statuses( 110 | runtime_pid: Pid, 111 | ) -> ( 112 | Option, 113 | Option, 114 | ) { 115 | let mut runtime: Option = None; 116 | let mut container: Option = None; 117 | 118 | while let Some(status) = get_child_termination_status() { 119 | if status.pid() == runtime_pid { 120 | assert!(runtime.is_none()); 121 | runtime = Some(status); 122 | } else { 123 | assert!(container.is_none()); 124 | container = Some(status); 125 | } 126 | } 127 | 128 | (runtime, container) 129 | } 130 | -------------------------------------------------------------------------------- /src/syncpipe.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Write; 3 | use std::os::unix::io::{FromRawFd, RawFd}; 4 | 5 | use nix::unistd::Pid; 6 | use serde::Serialize; 7 | 8 | use crate::runtime::TerminationStatus; 9 | 10 | #[derive(Serialize)] 11 | struct MessageRuntimeAbnormalTermination { 12 | kind: &'static str, 13 | status: String, 14 | stderr: String, 15 | } 16 | 17 | impl MessageRuntimeAbnormalTermination { 18 | fn new(status: TerminationStatus, stderr: &[u8]) -> Self { 19 | MessageRuntimeAbnormalTermination { 20 | kind: "runtime_abnormal_termination", 21 | status: format!("{}", status), 22 | stderr: String::from_utf8(stderr.to_vec()).unwrap_or(format!("{:?}", stderr)), 23 | } 24 | } 25 | } 26 | 27 | #[derive(Serialize)] 28 | struct MessageContainerPid { 29 | kind: &'static str, 30 | pid: i32, 31 | } 32 | 33 | impl MessageContainerPid { 34 | fn new(pid: Pid) -> Self { 35 | MessageContainerPid { 36 | kind: "container_pid", 37 | pid: pid.as_raw(), 38 | } 39 | } 40 | } 41 | 42 | pub struct SyncPipe(File); 43 | 44 | impl SyncPipe { 45 | pub fn new(fd: RawFd) -> Self { 46 | SyncPipe(unsafe { File::from_raw_fd(fd) }) 47 | } 48 | 49 | pub fn report_container_pid(&mut self, pid: Pid) { 50 | let msg = 51 | serde_json::to_vec(&MessageContainerPid::new(pid)).expect("JSON serialization failed"); 52 | self.0.write_all(&msg).expect("SyncPipe.write() failed"); 53 | } 54 | 55 | pub fn report_abnormal_runtime_termination( 56 | &mut self, 57 | status: TerminationStatus, 58 | stderr: &[u8], 59 | ) { 60 | let msg = serde_json::to_vec(&MessageRuntimeAbnormalTermination::new(status, stderr)) 61 | .expect("JSON serialization failed"); 62 | self.0.write_all(&msg).expect("SyncPipe.write() failed"); 63 | } 64 | } 65 | --------------------------------------------------------------------------------