├── .git-hooks └── pre-commit ├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── Jenkinsfile ├── README.adoc ├── ci ├── centos │ └── Dockerfile ├── debian │ └── Dockerfile └── release.sh ├── docs ├── configuration.adoc ├── navigation.adoc └── quickstart.adoc ├── examples ├── openvpn.sh ├── openvpn │ ├── Dockerfile │ ├── client.crt │ ├── client.key │ ├── nereond.conf │ ├── openvpn-init.sh │ └── riffol.conf ├── wordpress.sh └── wordpress │ ├── Dockerfile │ ├── apache.conf │ ├── initialise-mysql.sh │ ├── riffol.conf │ ├── wordpress.php │ └── wordpress.sql ├── riffol.png ├── src ├── application.rs ├── config.rs ├── distro.rs ├── health.rs ├── init.rs ├── lib.rs ├── limit.rs ├── main.rs ├── signal.rs └── stream.rs └── tests ├── db.vars └── riffol.conf /.git-hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | result=0 4 | 5 | for file in $(git diff --name-only --cached --diff-filter d); do 6 | case $file in 7 | *.rs) 8 | rustfmt --check $file 9 | if [ $? != 0 ]; then 10 | echo ${file} needs formatting before commit 11 | result=1 12 | fi 13 | ;; 14 | esac 15 | done 16 | 17 | exit $result 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .dir-locals.el 3 | .#* 4 | \#* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | os: 3 | - osx 4 | - linux 5 | script: 6 | - cargo build --verbose --all 7 | - cargo test --verbose --all 8 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "ansi_term" 3 | version = "0.11.0" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | dependencies = [ 6 | "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 7 | ] 8 | 9 | [[package]] 10 | name = "arc-swap" 11 | version = "0.3.2" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | 14 | [[package]] 15 | name = "arrayref" 16 | version = "0.3.5" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | 19 | [[package]] 20 | name = "arrayvec" 21 | version = "0.4.7" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | dependencies = [ 24 | "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 25 | ] 26 | 27 | [[package]] 28 | name = "atty" 29 | version = "0.2.11" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | dependencies = [ 32 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 33 | "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 34 | "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 35 | ] 36 | 37 | [[package]] 38 | name = "backtrace" 39 | version = "0.3.9" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | dependencies = [ 42 | "backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)", 43 | "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 44 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 45 | "rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 46 | "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 47 | ] 48 | 49 | [[package]] 50 | name = "backtrace-sys" 51 | version = "0.1.24" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | dependencies = [ 54 | "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", 55 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 56 | ] 57 | 58 | [[package]] 59 | name = "bitflags" 60 | version = "1.0.4" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | 63 | [[package]] 64 | name = "block-buffer" 65 | version = "0.3.3" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | dependencies = [ 68 | "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 69 | "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 70 | ] 71 | 72 | [[package]] 73 | name = "byte-tools" 74 | version = "0.2.0" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | 77 | [[package]] 78 | name = "cc" 79 | version = "1.0.25" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | 82 | [[package]] 83 | name = "cfg-if" 84 | version = "0.1.5" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | 87 | [[package]] 88 | name = "chrono" 89 | version = "0.4.6" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | dependencies = [ 92 | "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", 93 | "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 94 | "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 95 | ] 96 | 97 | [[package]] 98 | name = "clap" 99 | version = "2.32.0" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | dependencies = [ 102 | "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 103 | "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 104 | "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 105 | "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 106 | "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 107 | "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 108 | "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 109 | ] 110 | 111 | [[package]] 112 | name = "cloudabi" 113 | version = "0.0.3" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | dependencies = [ 116 | "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 117 | ] 118 | 119 | [[package]] 120 | name = "crossbeam-channel" 121 | version = "0.2.6" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | dependencies = [ 124 | "crossbeam-epoch 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 125 | "crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 126 | "parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", 127 | "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", 128 | "smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 129 | ] 130 | 131 | [[package]] 132 | name = "crossbeam-epoch" 133 | version = "0.6.0" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | dependencies = [ 136 | "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", 137 | "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 138 | "crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 139 | "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 140 | "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 141 | "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 142 | ] 143 | 144 | [[package]] 145 | name = "crossbeam-utils" 146 | version = "0.5.0" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | 149 | [[package]] 150 | name = "digest" 151 | version = "0.7.6" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | dependencies = [ 154 | "generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 155 | ] 156 | 157 | [[package]] 158 | name = "error-chain" 159 | version = "0.11.0" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | dependencies = [ 162 | "backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 163 | ] 164 | 165 | [[package]] 166 | name = "fake-simd" 167 | version = "0.1.2" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | 170 | [[package]] 171 | name = "fuchsia-zircon" 172 | version = "0.3.3" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | dependencies = [ 175 | "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 176 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 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 | 184 | [[package]] 185 | name = "generic-array" 186 | version = "0.9.0" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | dependencies = [ 189 | "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 190 | ] 191 | 192 | [[package]] 193 | name = "iovec" 194 | version = "0.1.2" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | dependencies = [ 197 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 198 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 199 | ] 200 | 201 | [[package]] 202 | name = "kernel32-sys" 203 | version = "0.2.2" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | dependencies = [ 206 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 207 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 208 | ] 209 | 210 | [[package]] 211 | name = "lazy_static" 212 | version = "1.1.0" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | dependencies = [ 215 | "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 216 | ] 217 | 218 | [[package]] 219 | name = "lazycell" 220 | version = "1.2.0" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | 223 | [[package]] 224 | name = "libc" 225 | version = "0.2.43" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | 228 | [[package]] 229 | name = "lock_api" 230 | version = "0.1.4" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | dependencies = [ 233 | "owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 234 | "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 235 | ] 236 | 237 | [[package]] 238 | name = "log" 239 | version = "0.4.5" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | dependencies = [ 242 | "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 243 | ] 244 | 245 | [[package]] 246 | name = "maplit" 247 | version = "1.0.1" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | 250 | [[package]] 251 | name = "memoffset" 252 | version = "0.2.1" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | 255 | [[package]] 256 | name = "mio" 257 | version = "0.6.16" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | dependencies = [ 260 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 261 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 262 | "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 263 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 264 | "lazycell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 265 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 266 | "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", 267 | "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 268 | "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 269 | "slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 270 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 271 | ] 272 | 273 | [[package]] 274 | name = "miow" 275 | version = "0.2.1" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | dependencies = [ 278 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 279 | "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 280 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 281 | "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 282 | ] 283 | 284 | [[package]] 285 | name = "nereon" 286 | version = "0.4.2" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | dependencies = [ 289 | "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", 290 | "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 291 | "nereon_derive 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 292 | "pest 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 293 | "pest_derive 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 294 | ] 295 | 296 | [[package]] 297 | name = "nereon_derive" 298 | version = "0.4.2" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | dependencies = [ 301 | "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", 302 | "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", 303 | "syn 0.15.12 (registry+https://github.com/rust-lang/crates.io-index)", 304 | ] 305 | 306 | [[package]] 307 | name = "net2" 308 | version = "0.2.33" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | dependencies = [ 311 | "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 312 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 313 | "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 314 | ] 315 | 316 | [[package]] 317 | name = "nix" 318 | version = "0.11.0" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | dependencies = [ 321 | "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 322 | "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", 323 | "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 324 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 325 | "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 326 | ] 327 | 328 | [[package]] 329 | name = "nodrop" 330 | version = "0.1.12" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | 333 | [[package]] 334 | name = "num-integer" 335 | version = "0.1.39" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | dependencies = [ 338 | "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 339 | ] 340 | 341 | [[package]] 342 | name = "num-traits" 343 | version = "0.2.6" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | 346 | [[package]] 347 | name = "owning_ref" 348 | version = "0.3.3" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | dependencies = [ 351 | "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 352 | ] 353 | 354 | [[package]] 355 | name = "parking_lot" 356 | version = "0.6.4" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | dependencies = [ 359 | "lock_api 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 360 | "parking_lot_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 361 | ] 362 | 363 | [[package]] 364 | name = "parking_lot_core" 365 | version = "0.3.1" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | dependencies = [ 368 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 369 | "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", 370 | "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 371 | "smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 372 | "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 373 | ] 374 | 375 | [[package]] 376 | name = "pest" 377 | version = "2.0.1" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | dependencies = [ 380 | "ucd-trie 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 381 | ] 382 | 383 | [[package]] 384 | name = "pest_derive" 385 | version = "2.0.1" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | dependencies = [ 388 | "pest 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 389 | "pest_generator 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 390 | ] 391 | 392 | [[package]] 393 | name = "pest_generator" 394 | version = "2.0.0" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | dependencies = [ 397 | "pest 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 398 | "pest_meta 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 399 | "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", 400 | "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", 401 | "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)", 402 | ] 403 | 404 | [[package]] 405 | name = "pest_meta" 406 | version = "2.0.3" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | dependencies = [ 409 | "maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 410 | "pest 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 411 | "sha-1 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 412 | ] 413 | 414 | [[package]] 415 | name = "proc-macro2" 416 | version = "0.4.20" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | dependencies = [ 419 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 420 | ] 421 | 422 | [[package]] 423 | name = "quote" 424 | version = "0.6.8" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | dependencies = [ 427 | "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", 428 | ] 429 | 430 | [[package]] 431 | name = "rand" 432 | version = "0.5.5" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | dependencies = [ 435 | "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 436 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 437 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 438 | "rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 439 | "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 440 | ] 441 | 442 | [[package]] 443 | name = "rand_core" 444 | version = "0.2.2" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | dependencies = [ 447 | "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 448 | ] 449 | 450 | [[package]] 451 | name = "rand_core" 452 | version = "0.3.0" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | 455 | [[package]] 456 | name = "redox_syscall" 457 | version = "0.1.40" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | 460 | [[package]] 461 | name = "redox_termios" 462 | version = "0.1.1" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | dependencies = [ 465 | "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 466 | ] 467 | 468 | [[package]] 469 | name = "riffol" 470 | version = "0.3.0" 471 | dependencies = [ 472 | "crossbeam-channel 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 473 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 474 | "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", 475 | "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", 476 | "nereon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 477 | "nereon_derive 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 478 | "nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 479 | "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", 480 | "signal-hook 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 481 | "slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 482 | "stderrlog 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 483 | "syslog 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 484 | ] 485 | 486 | [[package]] 487 | name = "rustc-demangle" 488 | version = "0.1.9" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | 491 | [[package]] 492 | name = "rustc_version" 493 | version = "0.2.3" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | dependencies = [ 496 | "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 497 | ] 498 | 499 | [[package]] 500 | name = "scopeguard" 501 | version = "0.3.3" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | 504 | [[package]] 505 | name = "semver" 506 | version = "0.9.0" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | dependencies = [ 509 | "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 510 | ] 511 | 512 | [[package]] 513 | name = "semver-parser" 514 | version = "0.7.0" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | 517 | [[package]] 518 | name = "sha-1" 519 | version = "0.7.0" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | dependencies = [ 522 | "block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 523 | "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 524 | "digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", 525 | "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 526 | ] 527 | 528 | [[package]] 529 | name = "signal-hook" 530 | version = "0.1.5" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | dependencies = [ 533 | "arc-swap 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 534 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 535 | ] 536 | 537 | [[package]] 538 | name = "slab" 539 | version = "0.4.1" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | 542 | [[package]] 543 | name = "smallvec" 544 | version = "0.6.5" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | dependencies = [ 547 | "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 548 | ] 549 | 550 | [[package]] 551 | name = "stable_deref_trait" 552 | version = "1.1.1" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | 555 | [[package]] 556 | name = "stderrlog" 557 | version = "0.4.1" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | dependencies = [ 560 | "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 561 | "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", 562 | "termcolor 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 563 | "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 564 | ] 565 | 566 | [[package]] 567 | name = "strsim" 568 | version = "0.7.0" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | 571 | [[package]] 572 | name = "syn" 573 | version = "0.14.9" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | dependencies = [ 576 | "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", 577 | "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", 578 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 579 | ] 580 | 581 | [[package]] 582 | name = "syn" 583 | version = "0.15.12" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | dependencies = [ 586 | "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", 587 | "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", 588 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 589 | ] 590 | 591 | [[package]] 592 | name = "syslog" 593 | version = "4.0.1" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | dependencies = [ 596 | "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 597 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 598 | "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", 599 | "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 600 | ] 601 | 602 | [[package]] 603 | name = "termcolor" 604 | version = "0.3.6" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | dependencies = [ 607 | "wincolor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 608 | ] 609 | 610 | [[package]] 611 | name = "termion" 612 | version = "1.5.1" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | dependencies = [ 615 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 616 | "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 617 | "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 618 | ] 619 | 620 | [[package]] 621 | name = "textwrap" 622 | version = "0.10.0" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | dependencies = [ 625 | "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 626 | ] 627 | 628 | [[package]] 629 | name = "thread_local" 630 | version = "0.3.6" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | dependencies = [ 633 | "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 634 | ] 635 | 636 | [[package]] 637 | name = "time" 638 | version = "0.1.40" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | dependencies = [ 641 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 642 | "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 643 | "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 644 | ] 645 | 646 | [[package]] 647 | name = "typenum" 648 | version = "1.10.0" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | 651 | [[package]] 652 | name = "ucd-trie" 653 | version = "0.1.1" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | 656 | [[package]] 657 | name = "unicode-width" 658 | version = "0.1.5" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | 661 | [[package]] 662 | name = "unicode-xid" 663 | version = "0.1.0" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | 666 | [[package]] 667 | name = "unreachable" 668 | version = "1.0.0" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | dependencies = [ 671 | "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 672 | ] 673 | 674 | [[package]] 675 | name = "vec_map" 676 | version = "0.8.1" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | 679 | [[package]] 680 | name = "version_check" 681 | version = "0.1.5" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | 684 | [[package]] 685 | name = "void" 686 | version = "1.0.2" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | 689 | [[package]] 690 | name = "winapi" 691 | version = "0.2.8" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | 694 | [[package]] 695 | name = "winapi" 696 | version = "0.3.6" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | dependencies = [ 699 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 700 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 701 | ] 702 | 703 | [[package]] 704 | name = "winapi-build" 705 | version = "0.1.1" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | 708 | [[package]] 709 | name = "winapi-i686-pc-windows-gnu" 710 | version = "0.4.0" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | 713 | [[package]] 714 | name = "winapi-x86_64-pc-windows-gnu" 715 | version = "0.4.0" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | 718 | [[package]] 719 | name = "wincolor" 720 | version = "0.1.6" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | dependencies = [ 723 | "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 724 | ] 725 | 726 | [[package]] 727 | name = "ws2_32-sys" 728 | version = "0.2.1" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | dependencies = [ 731 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 732 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 733 | ] 734 | 735 | [metadata] 736 | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 737 | "checksum arc-swap 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2f344c31716d7f1afc56f8cc08163f7d1826b223924c04b89b0a533459d5f99f" 738 | "checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" 739 | "checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef" 740 | "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" 741 | "checksum backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "89a47830402e9981c5c41223151efcced65a0510c13097c769cede7efb34782a" 742 | "checksum backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "c66d56ac8dabd07f6aacdaf633f4b8262f5b3601a810a0dcddffd5c22c69daa0" 743 | "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" 744 | "checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab" 745 | "checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" 746 | "checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16" 747 | "checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3" 748 | "checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" 749 | "checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" 750 | "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 751 | "checksum crossbeam-channel 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7b85741761b7f160bc5e7e0c14986ef685b7f8bf9b7ad081c60c604bb4649827" 752 | "checksum crossbeam-epoch 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c90f1474584f38e270b5b613e898c8c328aa4f3dea85e0a27ac2e642f009416" 753 | "checksum crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "677d453a17e8bd2b913fa38e8b9cf04bcdbb5be790aa294f2389661d72036015" 754 | "checksum digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90" 755 | "checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3" 756 | "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" 757 | "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 758 | "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 759 | "checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" 760 | "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" 761 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 762 | "checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7" 763 | "checksum lazycell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ddba4c30a78328befecec92fc94970e53b3ae385827d28620f0f5bb2493081e0" 764 | "checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" 765 | "checksum lock_api 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "775751a3e69bde4df9b38dd00a1b5d6ac13791e4223d4a0506577f0dd27cfb7a" 766 | "checksum log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f" 767 | "checksum maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08cbb6b4fef96b6d77bfc40ec491b1690c779e77b05cd9f07f787ed376fd4c43" 768 | "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" 769 | "checksum mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)" = "71646331f2619b1026cc302f87a2b8b648d5c6dd6937846a16cc8ce0f347f432" 770 | "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 771 | "checksum nereon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "62768d8312c61ba65cafdc47f30b55492b01e7777e8607215e5b91ed2d64f872" 772 | "checksum nereon_derive 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "58034b763474813216f2248f3bace22bd1b8cf5e887b31a8719d0b808176b767" 773 | "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" 774 | "checksum nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37e713a259ff641624b6cb20e3b12b2952313ba36b6823c0f16e6cfd9e5de17" 775 | "checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" 776 | "checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" 777 | "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" 778 | "checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" 779 | "checksum parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f0802bff09003b291ba756dc7e79313e51cc31667e94afbe847def490424cde5" 780 | "checksum parking_lot_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad7f7e6ebdc79edff6fdcb87a55b620174f7a989e3eb31b65231f4af57f00b8c" 781 | "checksum pest 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c3abb0d36ede865dcc689fd3bee2ff39094eff6e57a814f4a53c3c6108088353" 782 | "checksum pest_derive 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b76f477146419bc539a63f4ef40e902166cb43b3e51cecc71d9136fd12c567e7" 783 | "checksum pest_generator 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ebee4e9680be4fd162e6f3394ae4192a6b60b1e4d17d845e631f0c68d1a3386" 784 | "checksum pest_meta 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1f6d5f6f0e6082578c86af197d780dc38328e3f768cec06aac9bc46d714e8221" 785 | "checksum proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)" = "3d7b7eaaa90b4a90a932a9ea6666c95a389e424eff347f0f793979289429feee" 786 | "checksum quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "dd636425967c33af890042c483632d33fa7a18f19ad1d7ea72e8998c6ef8dea5" 787 | "checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c" 788 | "checksum rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1961a422c4d189dfb50ffa9320bf1f2a9bd54ecb92792fb9477f99a1045f3372" 789 | "checksum rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0905b6b7079ec73b314d4c748701f6931eb79fd97c668caa3f1899b22b32c6db" 790 | "checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" 791 | "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" 792 | "checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395" 793 | "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 794 | "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" 795 | "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 796 | "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 797 | "checksum sha-1 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51b9d1f3b5de8a167ab06834a7c883bd197f2191e1dda1a22d9ccfeedbf9aded" 798 | "checksum signal-hook 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f7ca1f1c0ed6c8beaab713ad902c041e4f09d06e1b4bb74c5fc553c078ed0110" 799 | "checksum slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5f9776d6b986f77b35c6cf846c11ad986ff128fe0b2b63a3628e3755e8d3102d" 800 | "checksum smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "153ffa32fd170e9944f7e0838edf824a754ec4c1fc64746fcc9fe1f8fa602e5d" 801 | "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" 802 | "checksum stderrlog 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "61dc66b7ae72b65636dbf36326f9638fb3ba27871bb737a62e2c309b87d91b70" 803 | "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" 804 | "checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" 805 | "checksum syn 0.15.12 (registry+https://github.com/rust-lang/crates.io-index)" = "34ab9797e47d24cb76b8dc4d24ff36807018c7cc549c4cba050b068be0c586b0" 806 | "checksum syslog 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a0641142b4081d3d44beffa4eefd7346a228cdf91ed70186db2ca2cef762d327" 807 | "checksum termcolor 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "adc4587ead41bf016f11af03e55a624c06568b5a19db4e90fde573d805074f83" 808 | "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" 809 | "checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" 810 | "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" 811 | "checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b" 812 | "checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" 813 | "checksum ucd-trie 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "71a9c5b1fe77426cf144cc30e49e955270f5086e31a6441dfa8b32efc09b9d77" 814 | "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" 815 | "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 816 | "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" 817 | "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" 818 | "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" 819 | "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 820 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 821 | "checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" 822 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 823 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 824 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 825 | "checksum wincolor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eeb06499a3a4d44302791052df005d5232b927ed1a9658146d842165c4de7767" 826 | "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 827 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "riffol" 3 | description = "Riffol is a supervising process that can run as a traditional daemon on Unix-like systems or as an `init` system for containers" 4 | version = "0.3.0" 5 | repository = "https://github.com/riboseinc/riffol" 6 | authors = ["John Hedges ", "Ribose Inc. "] 7 | license = "BSD-2-Clause" 8 | exclude = [ 9 | "ci/**", 10 | "examples/**", 11 | ".git-hooks/pre-commit", 12 | ".gitignore", 13 | "Jenkinsfile", 14 | "riffol.png", 15 | ".travis.yml", 16 | ] 17 | 18 | [dependencies] 19 | crossbeam-channel = "0.2" 20 | libc = "0.2" 21 | log = "0.4" 22 | mio = "0.6" 23 | nereon = "0.4" 24 | nereon_derive = "0.4" 25 | nix = "0.11" 26 | rand = "0.5" 27 | signal-hook = "0.1" 28 | slab = "0.4" 29 | stderrlog = "0.4" 30 | syslog = "4.0" 31 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent none 3 | stages { 4 | stage("Test and Build") { 5 | environment { 6 | CARGO = "~/.cargo/bin/cargo" 7 | OAUTH = credentials("GitHub") 8 | } 9 | when { 10 | branch 'master' 11 | } 12 | stages { 13 | stage("Debian") { 14 | agent { 15 | dockerfile { 16 | dir "ci/debian" 17 | } 18 | } 19 | steps { 20 | sh """ 21 | $CARGO clean 22 | $CARGO update 23 | $CARGO test 24 | $CARGO build --release 25 | """ 26 | sh ''' 27 | LIBC_VERSION=$(ldd --version | head -n1 | sed -r 's/(.* )//') 28 | mkdir -p assets 29 | tar -C target/release -zcvf assets/riffol-libc-$LIBC_VERSION.tar.gz riffol 30 | ci/release.sh riboseinc/riffol 31 | ''' 32 | } 33 | } 34 | stage("CentOS") { 35 | agent { 36 | dockerfile { 37 | dir "ci/centos" 38 | } 39 | } 40 | steps { 41 | sh """ 42 | $CARGO clean 43 | $CARGO update 44 | $CARGO test 45 | $CARGO build --release 46 | """ 47 | sh ''' 48 | LIBC_VERSION=$(ldd --version | head -n1 | sed -r 's/(.* )//') 49 | mkdir -p assets 50 | tar -C target/release -zcvf assets/riffol-libc-$LIBC_VERSION.tar.gz riffol 51 | ci/release.sh riboseinc/riffol 52 | ''' 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Riffol 2 | 3 | Riffol is a supervising process that can run as a traditional daemon 4 | on Unix-like systems or as an `init` system for containers (such as 5 | https://github.com/krallin/tini[tini]). 6 | 7 | Riffol can be configured by creating application groups that consist 8 | of applications and health checks. 9 | 10 | [source] 11 | ---- 12 | +--------+ 13 | | Riffol | 14 | +----+---+ 15 | | 16 | | 17 | | 18 | +------v------+ 19 | | Application | 20 | | Group +-------+-------------+ 21 | +------+------+ | | 22 | | | | 23 | | | | 24 | | | | 25 | +------v------+ +---v----+ +----v-----+ 26 | | Application | | Health | | Resource | 27 | +-------------+ | Check | | Limits | 28 | +--------+ +----------+ 29 | ---- 30 | 31 | Riffol uses the unified configuration model from 32 | https://github.com/riboseinc/event-configuration-models for its 33 | configuration syntax definition, startup configuration and also 34 | runtime configuration. 35 | 36 | Riffol is implemented in Rust. 37 | 38 | === Building From Source 39 | 40 | Make sure there is a Rust environment installed. Otherwise follow the 41 | https://www.rust-lang.org/en-US/install.html[Rust installation guide]. 42 | 43 | [source,shell] 44 | ---- 45 | cargo install --git https:/github.com/riboseinc/riffol 46 | ---- 47 | 48 | This command will build a `riffol` binary and store it in the `bin` 49 | directory under `$CARGO_HOME` - usually `~/.cargo/bin/riffol`. 50 | 51 | == Usage 52 | 53 | riffol [-f config-file] 54 | 55 | Riffol requires a configuration file. The default location of this 56 | file is `/etc/riffol.conf`. 57 | 58 | This location can be specified either via the `RIFFOL_CONFIG` 59 | environment variable or by using the `-f` command line flag. 60 | 61 | == link:docs/configuration.adoc[Configuration] 62 | 63 | == ... Riffol? 64 | 65 | https://en.wikipedia.org/wiki/Salmon_run#The_spawning 66 | 67 | > The eggs of a female salmon are called her roe. To lay her roe, the 68 | female salmon builds a **spawn**ing nest, called a redd, in a riffle 69 | with gravel as its streambed. A **riffle** is a relatively shallow 70 | length of stream where the water is turbulent and flows faster. 71 | 72 | By spelling "riffol" with an O, we are putting the chemical symbol for 73 | oxygen in the word: we are 74 | https://en.wikipedia.org/wiki/Redox[oxidising] Riffol. Which makes 75 | sense, since Riffol is in Rust. 76 | -------------------------------------------------------------------------------- /ci/centos/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:latest 2 | 3 | RUN yum install -y gcc git make autoconf automake libtool epel-release sysvinit-tools 4 | 5 | RUN useradd -u 1000 docker-user 6 | 7 | RUN su - docker-user -c 'curl https://sh.rustup.rs -sSf | sh -s -- -y && ~/.cargo/bin/cargo search nereon' 8 | 9 | RUN curl -L -o/usr/bin/jq \ 10 | https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 \ 11 | && chmod +x /usr/bin/jq 12 | 13 | RUN git clone https://github.com/vstakhov/libucl.git \ 14 | && cd libucl \ 15 | && ./autogen.sh \ 16 | && ./configure --prefix=/usr \ 17 | && make install \ 18 | && ldconfig 19 | -------------------------------------------------------------------------------- /ci/debian/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:latest 2 | 3 | RUN apt-get update \ 4 | && apt-get -y upgrade \ 5 | && apt-get -y install --no-install-recommends autoconf automake libtool make pkg-config \ 6 | git curl ca-certificates 7 | 8 | RUN useradd -u 1000 docker-user && \ 9 | mkdir -p /home/docker-user && \ 10 | chown docker-user:docker-user /home/docker-user 11 | 12 | RUN su - docker-user -c 'curl https://sh.rustup.rs -sSf | sh -s -- -y && ~/.cargo/bin/cargo search nereon' 13 | 14 | RUN curl -L -o/usr/bin/jq \ 15 | https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 \ 16 | && chmod +x /usr/bin/jq 17 | 18 | RUN git clone https://github.com/vstakhov/libucl.git \ 19 | && cd libucl \ 20 | && ./autogen.sh \ 21 | && ./configure --prefix=/usr \ 22 | && make install 23 | -------------------------------------------------------------------------------- /ci/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -lx 2 | 3 | if ! jq --version >/dev/null 2>&1 ; then 4 | echo "No jq in \$PATH" 1>&2 5 | exit 1 6 | fi 7 | 8 | if [ -z $OAUTH ]; then 9 | echo "No \$OAUTH in environment" 1>&2 10 | exit 1 11 | fi 12 | 13 | if [ $# -ne 1 ]; then 14 | echo "Usage $(basename $0) /organisation/repo" 1>&2 15 | exit 1 16 | fi 17 | 18 | # get version from Cargo.toml 19 | version=$(grep ^version /dev/null) 20 | 21 | if [ -z $version ]; then 22 | echo "Couldn't get version from Cargo.toml" 23 | exit 1 24 | fi 25 | 26 | release=https://api.github.com/repos/$1/releases 27 | 28 | # get release id 29 | release_id=$( 30 | curl -s $release \ 31 | | jq -r '.[] | "\(.tag_name) \(.id)"' \ 32 | | grep -F $version | head -n1 | cut -d' ' -f2 33 | ) 34 | 35 | # create a release if none already for this version 36 | if [ -z $release_id ]; then 37 | echo "Creating release for $version" 38 | 39 | release_id=$(curl -s -X POST -d '{"tag_name":"'$version'"}' $release?access_token=$OAUTH | jq -r '"\(.id)"' 2>/dev/null) 40 | 41 | echo "Release ID: $release_id" 42 | 43 | if [ "$release_id" = "null" ] || [ -z $release_id ]; then 44 | echo "Failed to find/create release" 1>&2 45 | exit 1 46 | fi 47 | else 48 | echo "Updating assets for release $version" 49 | fi 50 | 51 | # get upload url 52 | upload=$(curl -s $release/$release_id?access_token=$OAUTH | jq -r '"\(.upload_url)"' | cut -d'{' -f1) 53 | for i in assets/*.tar.gz; do 54 | echo "Asset: $i" 55 | 56 | if [ -f $i ]; then 57 | asset_name=$(basename $i) 58 | echo "Asset name: $asset_name" 59 | asset_id=$( 60 | curl -s $release/$release_id/assets?access_token=$OAUTH \ 61 | | jq -r '.[] | "\(.name) \(.id)"' \ 62 | | grep -F $asset_name | cut -d' ' -f2 63 | ) 64 | # delete asset if already exists 65 | if ! [ -z $asset_id ]; then 66 | echo "Deleting existing $asset_name asset" 67 | curl -s -X DELETE $release/assets/$asset_id?access_token=$OAUTH 68 | fi 69 | # upload asset 70 | echo "Uploading $asset_name asset" 71 | curl -X POST -H "Content-Type: application/tar+gzip" --data-binary @"$i" "$upload?name=$asset_name&access_token=$OAUTH" >/dev/null 72 | fi 73 | done 74 | -------------------------------------------------------------------------------- /docs/configuration.adoc: -------------------------------------------------------------------------------- 1 | = Configuration 2 | 3 | == Init 4 | 5 | The main configuration to create application groups: 6 | 7 | [source] 8 | ---- 9 | init name { 10 | application_groups [ 11 | applicationgroup.name 12 | ] 13 | } 14 | ---- 15 | 16 | E.g.: 17 | 18 | [source] 19 | ---- 20 | init web { 21 | application_groups ["applicationgroup.webstack"] 22 | } 23 | ---- 24 | 25 | == Application Group 26 | 27 | [source] 28 | ---- 29 | application_group name { 30 | applications [ 31 | "application.name" 32 | ] 33 | } 34 | ---- 35 | 36 | E.g.: 37 | 38 | [source] 39 | ---- 40 | application_group webstack { 41 | applications [ 42 | "application.db" 43 | "application.www" 44 | ] 45 | } 46 | ---- 47 | 48 | == Application 49 | 50 | [source] 51 | ---- 52 | application "name" { 53 | mode application_mode 54 | requires [other applications] 55 | start [executable start args] 56 | stop [executable stop args] 57 | pidfile file 58 | dir working_directory 59 | env { 60 | new var1 value 61 | new var2 value 62 | pass oldname newname 63 | } 64 | env_file env_filename 65 | stdout stream_destination 66 | stderr stream_destination 67 | uid int 68 | gid int 69 | healthchecks [ 70 | "healthcheck.name" 71 | ] 72 | } 73 | ---- 74 | 75 | `application_mode` can be one of `oneshot`, `simple` or `forking` 76 | 77 | Applications are started with a clean environment. Environment 78 | variables can be added with `env` and `env_file` fields. 79 | 80 | `env` values can take two forms. `env new ` creates a 81 | new variable with the supplied value. `env pass ` 82 | creates a new variable named `newname` with the value taken from 83 | Riffol's environment variable `oldname`. 84 | 85 | [source] 86 | ---- 87 | env { 88 | new { 89 | VAR1 value1 90 | VAR2 value2 91 | } 92 | pass { 93 | ENV1 VAR3 94 | ENV2 VAR4 95 | } 96 | } 97 | ---- 98 | 99 | This creates two new environment variables, `VAR1` and `VAR2` with 100 | values `value1` and `value2` respectively. It also passes the 101 | variables `ENV1` and `ENV2` into new variables `VAR3` nad `VAR4` 102 | respectively. 103 | 104 | `env_file ` reads a file of environment variables, one per 105 | line in the following format: 106 | 107 | [source] 108 | ---- 109 | VAR1=value1 110 | VAR2=value2 111 | VAR3 112 | VAR4= 113 | ---- 114 | 115 | `VAR1` and `VAR2` are set to `value1` and `value2` 116 | respectively. `VAR3` and `VAR4` are set to the empty value "". 117 | 118 | The `env_file` field is processed before the `env` field so variables 119 | set up using `env` will override those read from `env_file`. 120 | 121 | `stream_destination` can be one of: 122 | [source] 123 | ---- 124 | file [ 125 | filename 126 | ] 127 | ---- 128 | [source] 129 | ---- 130 | syslog { 131 | socket unix_sock_address 132 | facility syslog_facility 133 | severity syslog_severity 134 | } 135 | ---- 136 | [source] 137 | ---- 138 | rsyslog { 139 | address remote_inet_address 140 | local local_inet_address 141 | facility syslog_facility 142 | severity syslog_severity 143 | } 144 | ---- 145 | 146 | `syslog_facility` is one of `kern`, `user`, `mail`, `daemon`, `auth`, 147 | `syslog`, `lpr`, `news`, `uucp`, `cron`, `authpriv`, `ftp`, `local0`, 148 | `local1`, `local2`, `local3`, `local4`, `local5`, `local6` or 149 | `local7`. (default `daemon`) 150 | 151 | `syslog_severity` is one of `emerg`, `alert`, `crit`, `err`, 152 | `warning`, `notice`, `info` or `debug` (default `debug`) 153 | 154 | `healthcheckfail` can be one of `start`, `restart` or `stop`. 155 | E.g.: 156 | 157 | [source] 158 | ---- 159 | application www { 160 | exec "/etc/init.d/http" 161 | dir "/var/www" 162 | env { 163 | new var1key var1value 164 | new var2key var2value 165 | pass oldname newname 166 | } 167 | env_file "/etc/httpd/morevars" 168 | start start 169 | stop stop 170 | restart restart 171 | stdout file "/var/log/riffol_www.log" 172 | stderr syslog { 173 | } 174 | uid 0 175 | gid 0 176 | healthchecks [ 177 | "healthcheck.www" 178 | ] 179 | healthcheckfail restart 180 | } 181 | ---- 182 | 183 | == Health Check 184 | 185 | [source] 186 | ---- 187 | healthcheck name { 188 | checks [ 189 | "class://value" 190 | ] 191 | interval int 192 | timeout int 193 | } 194 | ---- 195 | 196 | There are several `checks` classes: 197 | 198 | . `df`, disk free space 199 | . `proc`, process name 200 | . `tcp`, TCP connection 201 | . `udp`, UDP connection 202 | . `http`, establish a http connection 203 | . `https`, establish a https connection 204 | 205 | Parameters: 206 | 207 | . `interval`, the interval of the check defined in seconds 208 | . `timeout`, the timeout of network connections defined in seconds 209 | 210 | E.g.: 211 | 212 | [source] 213 | ---- 214 | healthcheck db { 215 | checks [ 216 | "df:///var/lib/mysql:512" 217 | "proc://mysqld", 218 | "tcp://127.0.0.1:3306" 219 | ] 220 | interval 60 221 | timeout 10 222 | } 223 | ---- 224 | 225 | == Resource Limits 226 | 227 | [source] 228 | ---- 229 | limits name { 230 | max_procs int 231 | max_mem int 232 | } 233 | ---- 234 | 235 | e.g.: 236 | 237 | [source] 238 | ---- 239 | limits db { 240 | max_procs 4 241 | max_mem 1024 242 | } 243 | ---- 244 | -------------------------------------------------------------------------------- /docs/navigation.adoc: -------------------------------------------------------------------------------- 1 | --- 2 | items: 3 | - { title: Quick Start, path: quickstart/ } 4 | - { title: Configuration, path: configuration/ } 5 | --- 6 | 7 | = Navigation 8 | -------------------------------------------------------------------------------- /docs/quickstart.adoc: -------------------------------------------------------------------------------- 1 | = Quick start 2 | 3 | == Installation 4 | 5 | Make sure there is a Rust environment installed. Otherwise follow the 6 | https://www.rust-lang.org/en-US/install.html[Rust installation guide]. 7 | 8 | [source,shell] 9 | ---- 10 | cargo install --git https:/github.com/riboseinc/riffol 11 | ---- 12 | 13 | This command will build a `riffol` binary and store it in the `bin` 14 | directory under `$CARGO_HOME` - usually `~/.cargo/bin/riffol`. 15 | 16 | == Usage 17 | 18 | riffol [-f config-file] 19 | 20 | Riffol requires a configuration file. The default location of this 21 | file is `/etc/riffol.conf`. 22 | 23 | This location can be specified either via the `RIFFOL_CONFIG` 24 | environment variable or by using the `-f` command line flag. 25 | -------------------------------------------------------------------------------- /examples/openvpn.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | NAME=riffol-openvpn 4 | localport=1194 5 | 6 | cd $(dirname $0) 7 | 8 | docker build -t $NAME openvpn 9 | docker run -t -i --rm -p $localport:1194 --cap-add=NET_ADMIN -e "NEREON_FILESET=$(base64 -w 0 4 | DocumentRoot /usr/share/wordpress 5 | Alias /wp-content /var/lib/wordpress/wp-content 6 | 7 | Options FollowSymLinks 8 | AllowOverride Limit Options FileInfo 9 | DirectoryIndex index.php 10 | Require all granted 11 | 12 | 13 | Options FollowSymLinks 14 | Require all granted 15 | 16 | -------------------------------------------------------------------------------- /examples/wordpress/initialise-mysql.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mysql --defaults-extra-file=/etc/mysql/debian.cnf /dev/null 2>&1 4 | 5 | exit 0 6 | -------------------------------------------------------------------------------- /examples/wordpress/riffol.conf: -------------------------------------------------------------------------------- 1 | init wordpress { 2 | application_groups [wordpress] 3 | } 4 | 5 | application_group wordpress { 6 | applications [ 7 | mysql 8 | initialise-mysql 9 | apache 10 | ] 11 | } 12 | 13 | application mysql { 14 | mode forking 15 | start [/etc/init.d/mysql, start] 16 | stop [/etc/init.d/mysql, stop] 17 | healthchecks [db] 18 | } 19 | 20 | application apache { 21 | mode forking 22 | pidfile /var/run/apache2/apache2.pid 23 | start [/etc/init.d/apache2, start] 24 | stop [/etc/init.d/apache2, stop] 25 | healthchecks [www, fail] 26 | limits [www] 27 | requires [initialise-mysql] 28 | } 29 | 30 | application initialise-mysql { 31 | mode oneshot 32 | start [/tmp/initialise-mysql.sh] 33 | requires [mysql] 34 | } 35 | 36 | healthchecks db { 37 | checks [ 38 | df:///var/lib/mysql:150 39 | proc://mysqld 40 | tcp://127.0.0.1:3306 41 | ] 42 | timeout 5 43 | interval 10 44 | } 45 | 46 | healthchecks www { 47 | checks [ 48 | tcp://127.0.0.1:80 49 | ] 50 | timeout 2 51 | interval 6 52 | } 53 | 54 | healthchecks fail { 55 | checks [ 56 | tcp://127.0.0.1:81 57 | ] 58 | timeout 2 59 | interval 30 60 | } 61 | 62 | limits www { 63 | max_mem 1024 64 | } -------------------------------------------------------------------------------- /examples/wordpress/wordpress.php: -------------------------------------------------------------------------------- 1 | , 48 | pub env: HashMap, 49 | pub start: Vec, 50 | pub stop: Vec, 51 | pub healthchecks: Vec, 52 | pub limits: Vec, 53 | pub stdout: Option, 54 | pub stderr: Option, 55 | pub state: AppState, 56 | pub requires: Vec, 57 | } 58 | 59 | #[derive(Debug, PartialEq, Clone)] 60 | pub enum AppState { 61 | Idle, 62 | Starting { 63 | exec_pid: u32, 64 | }, 65 | Running { 66 | app_pid: Option, 67 | }, 68 | Stopping { 69 | app_pid: Option, 70 | exec_pid: Option, 71 | }, 72 | Complete, 73 | } 74 | 75 | impl Application { 76 | pub fn start(&mut self, stream_handler: &mut stream::Handler) -> bool { 77 | self.start_process(&self.start) 78 | .map_err(|e| warn!("Failed to start application {}: {:?}", self.id, e)) 79 | .ok() 80 | .map(|mut child| { 81 | if let Some(stdout) = child.stdout.take().map(|s| s.into_raw_fd()) { 82 | stream_handler.add_stream(stdout, self.stdout.clone().unwrap()); 83 | } 84 | if let Some(stderr) = child.stderr.take().map(|s| s.into_raw_fd()) { 85 | stream_handler.add_stream(stderr, self.stderr.clone().unwrap()); 86 | } 87 | match self.mode { 88 | Mode::Simple => { 89 | self.state = AppState::Running { 90 | app_pid: Some(child.id()), 91 | }; 92 | false 93 | } 94 | _ => { 95 | self.state = AppState::Starting { 96 | exec_pid: child.id(), 97 | }; 98 | true 99 | } 100 | } 101 | }).unwrap_or(false) 102 | } 103 | 104 | pub fn stop(&mut self) -> bool { 105 | let app_pid = self.get_app_pid(); 106 | if self.mode == Mode::OneShot { 107 | false 108 | } else if self.mode == Mode::Simple { 109 | signal(app_pid.unwrap(), libc::SIGTERM); 110 | self.state = AppState::Stopping { 111 | exec_pid: None, 112 | app_pid, 113 | }; 114 | true 115 | } else { 116 | let child = self 117 | .start_process(&self.stop) 118 | .map_err(|e| warn!("Failed to stop application {}: {:?}", self.id, e)) 119 | .ok(); 120 | if let Some(child) = child { 121 | self.state = AppState::Stopping { 122 | exec_pid: Some(child.id()), 123 | app_pid, 124 | }; 125 | true 126 | } else { 127 | match app_pid { 128 | None | Some(0) => { 129 | self.state = AppState::Idle; 130 | false 131 | } 132 | Some(pid) => { 133 | signal(pid, libc::SIGTERM); 134 | self.state = AppState::Stopping { 135 | exec_pid: None, 136 | app_pid: Some(pid), 137 | }; 138 | true 139 | } 140 | } 141 | } 142 | } 143 | } 144 | 145 | pub fn kill(&mut self) { 146 | let pid = match self.state { 147 | AppState::Starting { exec_pid, .. } => exec_pid, 148 | AppState::Running { 149 | app_pid: Some(app_pid), 150 | .. 151 | } => app_pid, 152 | AppState::Stopping { 153 | exec_pid: Some(exec_pid), 154 | .. 155 | } => exec_pid, 156 | AppState::Stopping { 157 | app_pid: Some(app_pid), 158 | .. 159 | } => app_pid, 160 | _ => unreachable!(), 161 | }; 162 | signal(pid, libc::SIGKILL); 163 | } 164 | 165 | pub fn claim_child(&mut self, child: u32, status: i32) -> bool { 166 | match self.state { 167 | AppState::Starting { exec_pid } if exec_pid == child => { 168 | match self.mode { 169 | Mode::OneShot => { 170 | if status == 0 { 171 | info!("Application {} completed successfully", self.id); 172 | self.state = AppState::Complete; 173 | } else { 174 | warn!("Application {} failed. Exit code {}", self.id, status); 175 | self.state = AppState::Idle; 176 | } 177 | } 178 | Mode::Forking => { 179 | if status == 0 { 180 | info!("Application {} started successfully", self.id); 181 | let pid = self.read_pidfile(); 182 | if pid == None { 183 | warn!("Couldn't read pidfile for {}", self.id); 184 | } 185 | self.state = AppState::Running { app_pid: pid }; 186 | } else { 187 | warn!( 188 | "Application {} failed to start. Exit code {}", 189 | self.id, status 190 | ); 191 | self.state = AppState::Idle; 192 | } 193 | } 194 | Mode::Simple => unreachable!(), 195 | } 196 | true 197 | } 198 | AppState::Running { app_pid: pid, .. } if pid == Some(child) => { 199 | warn!( 200 | "Application {} died unexpectedly. Exit code {}", 201 | self.id, status 202 | ); 203 | // This is an error regardless of exit status We need 204 | // to run the exec stop command but can't do it from 205 | // here as we'd bypass Init's timeouts so we need to 206 | // signal a failure and Init can clean up ... hence Some(0) 207 | self.state = AppState::Running { app_pid: Some(0) }; 208 | true 209 | } 210 | AppState::Stopping { app_pid, exec_pid } 211 | if app_pid == Some(child) || exec_pid == Some(child) => 212 | { 213 | if app_pid.is_none() || exec_pid.is_none() { 214 | info!("Application {} stopped", self.id); 215 | self.state = AppState::Idle; 216 | } else if app_pid == Some(child) { 217 | self.state = AppState::Stopping { 218 | app_pid: None, 219 | exec_pid, 220 | } 221 | } else { 222 | if status != 0 { 223 | warn!("Application {} stop failed. Exit code {}", self.id, status); 224 | } 225 | self.state = AppState::Stopping { 226 | app_pid, 227 | exec_pid: None, 228 | } 229 | } 230 | true 231 | } 232 | _ => false, 233 | } 234 | } 235 | 236 | pub fn is_idle(&self) -> bool { 237 | self.state == AppState::Idle 238 | } 239 | 240 | pub fn is_stopped(&self) -> bool { 241 | self.state == AppState::Idle || self.state == AppState::Complete 242 | } 243 | 244 | pub fn is_started(&self) -> bool { 245 | match self.state { 246 | AppState::Complete | AppState::Running { .. } => true, 247 | _ => false, 248 | } 249 | } 250 | 251 | pub fn is_runaway(&self) -> bool { 252 | match self.state { 253 | AppState::Stopping { exec_pid: None, .. } => true, 254 | _ => false, 255 | } 256 | } 257 | 258 | pub fn is_dead(&self) -> bool { 259 | match self.state { 260 | AppState::Running { app_pid: Some(0) } => true, 261 | _ => false, 262 | } 263 | } 264 | 265 | fn get_app_pid(&self) -> Option { 266 | match self.state { 267 | AppState::Running { app_pid, .. } => app_pid, 268 | AppState::Stopping { app_pid, .. } => app_pid, 269 | _ => None, 270 | } 271 | } 272 | 273 | fn start_process(&self, args: &[String]) -> io::Result { 274 | fn stdio(stream: &Option) -> Stdio { 275 | stream 276 | .as_ref() 277 | .map(|stream| match stream { 278 | stream::Stream::File { filename: f } if f == "/dev/null" => Stdio::null(), 279 | _ => Stdio::piped(), 280 | }).unwrap_or_else(Stdio::inherit) 281 | } 282 | 283 | let limits = self.limits.clone(); 284 | 285 | Command::new(&args[0]) 286 | .current_dir(&self.dir) 287 | .env_clear() 288 | .envs(self.env.iter()) 289 | .before_exec(move || { 290 | limits.iter().for_each(|l| setlimit(l)); 291 | Ok(()) 292 | }).stdout(stdio(&self.stdout)) 293 | .stderr(stdio(&self.stderr)) 294 | .args(&args[1..]) 295 | .spawn() 296 | } 297 | 298 | fn read_pidfile(&self) -> Option { 299 | self.pidfile.as_ref().and_then(|pidfile| { 300 | fs::read_to_string(pidfile) 301 | .map_err(|e| format!("{:?}", e)) 302 | .and_then(|s| { 303 | s.lines().next().map_or_else( 304 | || Err("Empty file".to_owned()), 305 | |s| s.parse::().map_err(|e| format!("{:?}", e)), 306 | ) 307 | }).map_err(|e| { 308 | warn!("Couldn't read pidfile ({}): {}", pidfile, e); 309 | }).ok() 310 | }) 311 | } 312 | } 313 | 314 | #[cfg(test)] 315 | mod tests {} 316 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, [Ribose Inc](https://www.ribose.com). 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions 5 | // are met: 6 | // 1. Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright 9 | // notice, this list of conditions and the following disclaimer in the 10 | // documentation and/or other materials provided with the distribution. 11 | // 12 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 13 | // ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 14 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 15 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 16 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 17 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 18 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 20 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | use application::{self, AppState, Mode}; 25 | use health::{DfCheck, HealthCheck, IntervalHealthCheck, ProcCheck, TcpCheck}; 26 | use limit::{Limit, RLimit}; 27 | use nereon::{self, FromValue, Value}; 28 | use std::collections::HashMap; 29 | use std::iter::Iterator; 30 | use std::net::SocketAddr; 31 | use std::path::Path; 32 | use std::str::FromStr; 33 | use std::time::Duration; 34 | use std::{env, fs}; 35 | use stream; 36 | use syslog; 37 | 38 | const VERSION: &str = env!("CARGO_PKG_VERSION"); 39 | const AUTHORS: &str = env!("CARGO_PKG_AUTHORS"); 40 | const LICENSE: &str = "BSD-2-Clause"; 41 | const APPNAME: &str = env!("CARGO_PKG_NAME"); 42 | 43 | #[derive(FromValue)] 44 | struct Config { 45 | init: HashMap, 46 | application_group: HashMap, 47 | application: HashMap, 48 | healthchecks: HashMap, 49 | limits: HashMap, 50 | } 51 | 52 | #[derive(FromValue)] 53 | struct Init { 54 | application_groups: Vec, 55 | } 56 | 57 | #[derive(FromValue)] 58 | struct AppGroup { 59 | applications: Vec, 60 | } 61 | 62 | #[derive(FromValue)] 63 | struct Environment { 64 | pass: HashMap, 65 | new: HashMap, 66 | } 67 | 68 | #[derive(FromValue)] 69 | struct Application { 70 | mode: Option, 71 | dir: Option, 72 | pidfile: Option, 73 | env: Option, 74 | env_file: Option, 75 | start: Vec, 76 | stop: Vec, 77 | healthchecks: Vec, 78 | limits: Vec, 79 | stdout: Option, 80 | stderr: Option, 81 | requires: Vec, 82 | } 83 | 84 | type Limits = HashMap; 85 | 86 | #[derive(FromValue)] 87 | struct HealthChecks { 88 | checks: Vec, 89 | timeout: u64, 90 | interval: u64, 91 | } 92 | 93 | #[derive(FromValue)] 94 | enum SyslogSeverity { 95 | EMERG, 96 | ALERT, 97 | CRIT, 98 | ERR, 99 | WARNING, 100 | NOTICE, 101 | INFO, 102 | DEBUG, 103 | } 104 | 105 | #[derive(FromValue)] 106 | enum SyslogFacility { 107 | KERN, 108 | USER, 109 | MAIL, 110 | DAEMON, 111 | AUTH, 112 | SYSLOG, 113 | LPR, 114 | NEWS, 115 | UUCP, 116 | CRON, 117 | AUTHPRIV, 118 | FTP, 119 | LOCAL0, 120 | LOCAL1, 121 | LOCAL2, 122 | LOCAL3, 123 | LOCAL4, 124 | LOCAL5, 125 | LOCAL6, 126 | LOCAL7, 127 | } 128 | 129 | #[derive(FromValue)] 130 | enum Stream { 131 | File(String), 132 | Syslog { 133 | socket: Option, 134 | facility: Option, 135 | severity: Option, 136 | }, 137 | RSyslog { 138 | server: String, 139 | local: Option, 140 | facility: Option, 141 | severity: Option, 142 | }, 143 | } 144 | 145 | #[derive(Debug)] 146 | pub struct Riffol { 147 | pub applications: Vec, 148 | pub healthchecks: Vec, 149 | } 150 | 151 | pub fn get_config>(args: T) -> Result { 152 | let nos = format!( 153 | r#" 154 | authors ["{}"] 155 | license "{}" 156 | name "{}" 157 | version {} 158 | option config {{ 159 | flags [takesvalue, required] 160 | short f 161 | long file 162 | default "/etc/riffol.conf" 163 | env RIFFOL_CONFIG 164 | hint FILE 165 | usage "Configuration file" 166 | }}"#, 167 | AUTHORS, LICENSE, APPNAME, VERSION 168 | ); 169 | 170 | let config = nereon::configure::(&nos, args)?; 171 | 172 | let mut riffol = Riffol { 173 | applications: Vec::new(), 174 | healthchecks: Vec::new(), 175 | }; 176 | 177 | for (_, init) in config.init { 178 | for group_name in init.application_groups { 179 | match config.application_group.get(&group_name) { 180 | Some(group) => { 181 | for id in &group.applications { 182 | match config.application.get(id) { 183 | Some(ap) => { 184 | let mode = ap.mode.as_ref().map_or_else( 185 | || Ok(Mode::Simple), 186 | |s| match s.as_ref() { 187 | "simple" => Ok(Mode::Simple), 188 | "forking" => Ok(Mode::Forking), 189 | "oneshot" => Ok(Mode::OneShot), 190 | _ => Err(format!("Invalid application mode ({})", s)), 191 | }, 192 | )?; 193 | 194 | let healthchecks = ap.healthchecks.clone(); 195 | let limits = match get_limits(&config.limits, &ap.limits) { 196 | Ok(ls) => ls, 197 | Err(e) => return Err(e), 198 | }; 199 | let mut env = ap 200 | .env_file 201 | .as_ref() 202 | .map_or_else(|| Ok(HashMap::new()), |f| read_env_file(&f))?; 203 | 204 | if let Some(ref vars) = ap.env { 205 | vars.pass.iter().for_each(|(old, new)| { 206 | if let Ok(value) = env::var(old) { 207 | env.insert(new.to_owned(), value.to_owned()); 208 | } 209 | }); 210 | vars.new.iter().for_each(|(k, v)| { 211 | env.insert(k.to_owned(), v.to_owned()); 212 | }); 213 | } 214 | 215 | let stderr = match ap.stderr.as_ref() { 216 | None => None, 217 | Some(s) => match mk_stream(&s) { 218 | Ok(s) => Some(s), 219 | Err(e) => return Err(format!("Invalid stream {}", e)), 220 | }, 221 | }; 222 | 223 | let stdout = match ap.stdout.as_ref() { 224 | None => None, 225 | Some(s) => match mk_stream(&s) { 226 | Ok(s) => Some(s), 227 | Err(e) => return Err(format!("Invalid stream {}", e)), 228 | }, 229 | }; 230 | 231 | riffol.applications.push(application::Application { 232 | id: id.to_owned(), 233 | mode, 234 | dir: ap.dir.clone().unwrap_or_else(|| "/tmp".to_owned()), 235 | pidfile: ap.pidfile.clone(), 236 | env, 237 | start: ap.start.clone(), 238 | stop: ap.stop.clone(), 239 | healthchecks, 240 | limits, 241 | stdout, 242 | stderr, 243 | /* TODO: check requires are valid */ 244 | requires: ap.requires.clone(), 245 | state: AppState::Idle, 246 | }); 247 | } 248 | None => return Err(format!("No such application \"{}\"", id)), 249 | } 250 | } 251 | } 252 | None => return Err(format!("No such application_group \"{}\"", group_name)), 253 | } 254 | } 255 | } 256 | riffol.healthchecks = 257 | config 258 | .healthchecks 259 | .iter() 260 | .try_fold(Vec::new(), |checks, (group, check)| { 261 | check.checks.iter().try_fold(checks, |mut checks, params| { 262 | mk_interval_healthcheck(group, check.interval, check.timeout, params).map( 263 | |check| { 264 | checks.push(check); 265 | checks 266 | }, 267 | ) 268 | }) 269 | })?; 270 | 271 | Ok(riffol) 272 | } 273 | 274 | fn read_env_file(filename: &str) -> Result, String> { 275 | fs::read_to_string(filename) 276 | .map_err(|e| format!("Cant't read env_file {}: {:?}", filename, e)) 277 | .map(|s| { 278 | s.lines() 279 | .map(|v| { 280 | let kv = v.splitn(2, '=').collect::>(); 281 | match kv.len() { 282 | 1 => (kv[0].to_owned(), "".to_owned()), 283 | _ => (kv[0].to_owned(), kv[1].to_owned()), 284 | } 285 | }).collect() 286 | }) 287 | } 288 | 289 | fn mk_interval_healthcheck( 290 | group: &str, 291 | interval: u64, 292 | timeout: u64, 293 | check: &str, 294 | ) -> Result { 295 | Ok(IntervalHealthCheck { 296 | group: group.to_owned(), 297 | interval: Duration::from_secs(interval), 298 | timeout: Duration::from_secs(timeout), 299 | check: mk_healthcheck(check)?, 300 | }) 301 | } 302 | 303 | fn mk_healthcheck(params: &str) -> Result { 304 | let split2 = |p, s: &str| { 305 | let svec: Vec<&str> = s.splitn(2, p).collect(); 306 | match (svec.get(0), svec.get(1)) { 307 | (Some(&s1), Some(&s2)) => (s1.to_owned(), s2.to_owned()), 308 | _ => (s.to_owned(), "".to_owned()), 309 | } 310 | }; 311 | 312 | let (check, args) = split2("://", params); 313 | let bad = |u| Err(format!("Bad {0} healthcheck. Use \"{0}//{1}\"", check, u)); 314 | match check.as_ref() { 315 | "proc" => match args { 316 | ref s if !s.is_empty() => Ok(HealthCheck::ProcCheck(ProcCheck::new(&args))), 317 | _ => bad(""), 318 | }, 319 | "df" => { 320 | let bad_df = || bad(":"); 321 | match split2(":", &args) { 322 | (ref file, ref free) if !file.is_empty() => match free.parse() { 323 | Ok(n) => Ok(HealthCheck::DfCheck(DfCheck::new(Path::new(&file), n))), 324 | _ => bad_df(), 325 | }, 326 | _ => bad_df(), 327 | } 328 | } 329 | "tcp" => match args.parse() { 330 | Ok(addr) => Ok(HealthCheck::TcpCheck(TcpCheck::new(&addr))), 331 | _ => bad(""), 332 | }, 333 | p => Err(format!("Unknown healthcheck type {}", p)), 334 | } 335 | } 336 | 337 | fn get_limits(configs: &HashMap, limits: &[String]) -> Result, String> { 338 | let min = |a, b| match (a, b) { 339 | (x, Limit::Infinity) => x, 340 | (Limit::Num(x), Limit::Num(y)) if x < y => Limit::Num(x), 341 | (_, y) => y, 342 | }; 343 | 344 | let mut procs = Limit::Infinity; 345 | let mut mem = Limit::Infinity; 346 | let mut files = Limit::Infinity; 347 | 348 | for name in limits.iter() { 349 | match configs.get(name) { 350 | Some(config) => { 351 | for (k, v) in config.iter() { 352 | match k.as_ref() { 353 | "max_procs" => procs = min(procs, Limit::Num(*v)), 354 | "max_mem" => mem = min(mem, Limit::Num(*v * 1024 * 1024)), 355 | "max_files" => files = min(files, Limit::Num(*v)), 356 | _ => return Err(format!("No such limit ({}).", k)), 357 | } 358 | } 359 | } 360 | None => return Err(format!("No such limits \"{}\"", name)), 361 | } 362 | } 363 | Ok(vec![ 364 | RLimit::Procs(procs), 365 | RLimit::Memory(mem), 366 | RLimit::Files(files), 367 | ]) 368 | } 369 | 370 | fn mk_stream(stream: &Stream) -> Result { 371 | match stream { 372 | Stream::File(filename) => Ok(stream::Stream::File { 373 | filename: filename.to_owned(), 374 | }), 375 | Stream::Syslog { 376 | socket, 377 | facility, 378 | severity, 379 | } => Ok(stream::Stream::Syslog { 380 | address: stream::Address::Unix(socket.to_owned()), 381 | facility: config_to_syslog_facility(facility), 382 | severity: config_to_syslog_severity(severity), 383 | }), 384 | Stream::RSyslog { 385 | server, 386 | local, 387 | facility: f, 388 | severity: s, 389 | } => Ok(stream::Stream::Syslog { 390 | address: { 391 | if let Ok(server) = SocketAddr::from_str(server) { 392 | match local { 393 | Some(local) => match SocketAddr::from_str(local) { 394 | Ok(local) => stream::Address::Udp { server, local }, 395 | Err(_) => return Err(format!("Not a valid inet address: {}", local)), 396 | }, 397 | None => stream::Address::Tcp(server), 398 | } 399 | } else { 400 | return Err(format!("Not a valid inet address: {}", server)); 401 | } 402 | }, 403 | facility: config_to_syslog_facility(f), 404 | severity: config_to_syslog_severity(s), 405 | }), 406 | } 407 | } 408 | 409 | fn config_to_syslog_facility(f: &Option) -> syslog::Facility { 410 | f.as_ref() 411 | .map(|f| match f { 412 | SyslogFacility::KERN => syslog::Facility::LOG_KERN, 413 | SyslogFacility::USER => syslog::Facility::LOG_USER, 414 | SyslogFacility::MAIL => syslog::Facility::LOG_MAIL, 415 | SyslogFacility::DAEMON => syslog::Facility::LOG_DAEMON, 416 | SyslogFacility::AUTH => syslog::Facility::LOG_AUTH, 417 | SyslogFacility::SYSLOG => syslog::Facility::LOG_SYSLOG, 418 | SyslogFacility::LPR => syslog::Facility::LOG_LPR, 419 | SyslogFacility::NEWS => syslog::Facility::LOG_NEWS, 420 | SyslogFacility::UUCP => syslog::Facility::LOG_UUCP, 421 | SyslogFacility::CRON => syslog::Facility::LOG_CRON, 422 | SyslogFacility::AUTHPRIV => syslog::Facility::LOG_AUTHPRIV, 423 | SyslogFacility::FTP => syslog::Facility::LOG_FTP, 424 | SyslogFacility::LOCAL0 => syslog::Facility::LOG_LOCAL0, 425 | SyslogFacility::LOCAL1 => syslog::Facility::LOG_LOCAL1, 426 | SyslogFacility::LOCAL2 => syslog::Facility::LOG_LOCAL2, 427 | SyslogFacility::LOCAL3 => syslog::Facility::LOG_LOCAL3, 428 | SyslogFacility::LOCAL4 => syslog::Facility::LOG_LOCAL4, 429 | SyslogFacility::LOCAL5 => syslog::Facility::LOG_LOCAL5, 430 | SyslogFacility::LOCAL6 => syslog::Facility::LOG_LOCAL6, 431 | SyslogFacility::LOCAL7 => syslog::Facility::LOG_LOCAL7, 432 | }).unwrap_or(syslog::Facility::LOG_DAEMON) 433 | } 434 | 435 | fn config_to_syslog_severity(s: &Option) -> u32 { 436 | s.as_ref() 437 | .map(|s| match s { 438 | SyslogSeverity::EMERG => syslog::Severity::LOG_EMERG, 439 | SyslogSeverity::ALERT => syslog::Severity::LOG_ALERT, 440 | SyslogSeverity::CRIT => syslog::Severity::LOG_CRIT, 441 | SyslogSeverity::ERR => syslog::Severity::LOG_ERR, 442 | SyslogSeverity::WARNING => syslog::Severity::LOG_WARNING, 443 | SyslogSeverity::NOTICE => syslog::Severity::LOG_NOTICE, 444 | SyslogSeverity::INFO => syslog::Severity::LOG_INFO, 445 | SyslogSeverity::DEBUG => syslog::Severity::LOG_DEBUG, 446 | }).unwrap_or(syslog::Severity::LOG_DEBUG) as u32 447 | } 448 | 449 | #[cfg(test)] 450 | mod tests { 451 | use super::get_limits; 452 | use super::mk_healthcheck; 453 | use std::collections::HashMap; 454 | 455 | #[test] 456 | fn test() { 457 | let args = vec!["riffol", "-f", "tests/riffol.conf"] 458 | .iter() 459 | .map(|s| s.to_string()) 460 | .collect::>(); 461 | let config = super::get_config(args); 462 | println!("{:?}", config); 463 | assert!(config.is_ok()); 464 | 465 | // test mk_healthcheck 466 | assert!(mk_healthcheck("unknown").is_err()); 467 | assert!(mk_healthcheck("").is_err()); 468 | assert!(mk_healthcheck("tcp").is_err()); 469 | assert!(mk_healthcheck("tcp://").is_err()); 470 | assert!(mk_healthcheck("tcp://invalid").is_err()); 471 | assert!(mk_healthcheck("tcp://127.0.0.1:80").is_ok()); 472 | assert!(mk_healthcheck("df://").is_err()); 473 | assert!(mk_healthcheck("df:///dev/sda").is_err()); 474 | assert!(mk_healthcheck("df:///dev/sda:nan").is_err()); 475 | assert!(mk_healthcheck("df:///dev/sda:2.0").is_err()); 476 | assert!(mk_healthcheck("df:///dev/sda:100").is_ok()); 477 | assert!(mk_healthcheck("proc://").is_err()); 478 | assert!(mk_healthcheck("proc://anything").is_ok()); 479 | 480 | // test get_limits 481 | let limits: HashMap = [("max_procs".to_owned(), 64)].iter().cloned().collect(); 482 | let config: HashMap> = 483 | vec![("1".to_owned(), limits)].iter().cloned().collect(); 484 | assert!(get_limits(&config, &vec!["2".to_owned()]).is_err()); 485 | assert!(get_limits(&config, &vec!["1".to_owned()]).is_ok()); 486 | 487 | let limits: HashMap = [("nonono".to_owned(), 64)].iter().cloned().collect(); 488 | let config: HashMap> = 489 | vec![("1".to_owned(), limits)].iter().cloned().collect(); 490 | assert!(get_limits(&config, &vec!["1".to_owned()]).is_err()); 491 | } 492 | } 493 | -------------------------------------------------------------------------------- /src/distro.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, [Ribose Inc](https://www.ribose.com). 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions 5 | // are met: 6 | // 1. Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright 9 | // notice, this list of conditions and the following disclaimer in the 10 | // documentation and/or other materials provided with the distribution. 11 | // 12 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 13 | // ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 14 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 15 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 16 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 17 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 18 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 20 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | use std::env; 25 | use std::fs; 26 | use std::os::unix::fs::PermissionsExt; 27 | use std::path::Path; 28 | use std::process::Command; 29 | 30 | fn which(cmd: &str) -> bool { 31 | env::var("PATH") 32 | .unwrap_or_else(|_| "".to_owned()) 33 | .split(':') 34 | .map(|d| match fs::metadata(Path::new(d).join(Path::new(cmd))) { 35 | Ok(m) => m.is_file() && (m.permissions().mode() & 0o111) != 0, 36 | Err(_) => false, 37 | }).any(|x| x) 38 | } 39 | 40 | fn get_installer() -> Box bool> { 41 | if which("apt") { 42 | Box::new(|pkg| { 43 | Command::new("apt-get") 44 | .arg("-y") 45 | .arg("--no-install-recommends") 46 | .arg("install") 47 | .arg(&pkg) 48 | .status() 49 | .map(|s| s.success()) 50 | .unwrap_or(false) 51 | }) 52 | } else if which("yum") { 53 | Box::new(|pkg| { 54 | Command::new("yum") 55 | .arg("-y") 56 | .arg("install") 57 | .arg(&pkg) 58 | .status() 59 | .map(|s| s.success()) 60 | .unwrap_or(false) 61 | }) 62 | } else { 63 | Box::new(|_| false) 64 | } 65 | } 66 | 67 | pub fn install_packages(depends: &[String]) -> Result<(), String> { 68 | let install = get_installer(); 69 | for d in depends { 70 | if !install(d) { 71 | return Err(format!("Couldn't install dependency: {}", d)); 72 | } 73 | } 74 | Ok(()) 75 | } 76 | 77 | #[cfg(test)] 78 | mod tests { 79 | #[test] 80 | fn test_which() { 81 | assert_eq!(super::which("ls"), true); 82 | assert_eq!(super::which("lllsss"), false); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/health.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, [Ribose Inc](https://www.ribose.com). 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions 5 | // are met: 6 | // 1. Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright 9 | // notice, this list of conditions and the following disclaimer in the 10 | // documentation and/or other materials provided with the distribution. 11 | // 12 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 13 | // ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NO/T 14 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 15 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 16 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 17 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 18 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 20 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | use crossbeam_channel as cc; 25 | use rand::{thread_rng, Rng}; 26 | use std::ffi::CString; 27 | use std::fs::{read_dir, File}; 28 | use std::io::Read; 29 | use std::net::{SocketAddr, TcpStream}; 30 | use std::path::Path; 31 | use std::sync::mpsc; 32 | use std::thread; 33 | use std::time::{Duration, Instant}; 34 | 35 | pub fn recv_checks(checks: &[IntervalHealthCheck]) -> cc::Receiver<(String, String)> { 36 | let (fail_send, fail_recv) = cc::unbounded(); 37 | checks.iter().for_each(|check| { 38 | let fail_tx = fail_send.clone(); 39 | let group = check.group.to_owned(); 40 | let check = check.clone(); 41 | thread::spawn(move || { 42 | // make the first check happen at random point between now and now + check.interval 43 | let mut next = 44 | Instant::now() 45 | + Duration::from_secs(thread_rng().gen_range(0, check.interval.as_secs())); 46 | let message = check.to_string(); 47 | loop { 48 | thread::sleep(next - (Instant::now().min(next))); 49 | next += check.interval; 50 | debug!("Healthcheck: {}", message); 51 | check 52 | .do_check() 53 | .map_err(|e| { 54 | debug!("Healthcheck failed: {} [{}].", message, e); 55 | fail_tx.send((group.to_owned(), message.to_owned())); 56 | }).ok(); 57 | } 58 | }); 59 | }); 60 | fail_recv 61 | } 62 | 63 | #[derive(Debug, Clone)] 64 | pub enum HealthCheck { 65 | DfCheck(DfCheck), 66 | ProcCheck(ProcCheck), 67 | TcpCheck(TcpCheck), 68 | } 69 | 70 | impl HealthCheck { 71 | pub fn do_check(&self) -> Result<(), String> { 72 | match self { 73 | HealthCheck::DfCheck(s) => s.do_check(), 74 | HealthCheck::ProcCheck(s) => s.do_check(), 75 | HealthCheck::TcpCheck(s) => s.do_check(), 76 | } 77 | } 78 | 79 | pub fn to_string(&self) -> String { 80 | match self { 81 | HealthCheck::DfCheck(s) => s.to_string(), 82 | HealthCheck::ProcCheck(s) => s.to_string(), 83 | HealthCheck::TcpCheck(s) => s.to_string(), 84 | } 85 | } 86 | } 87 | 88 | #[derive(Debug, Clone)] 89 | pub struct IntervalHealthCheck { 90 | pub group: String, 91 | pub interval: Duration, 92 | pub timeout: Duration, 93 | pub check: HealthCheck, 94 | } 95 | 96 | impl IntervalHealthCheck { 97 | pub fn to_string(&self) -> String { 98 | self.check.to_string() 99 | } 100 | 101 | pub fn do_check(&self) -> Result<(), String> { 102 | let (tx, rx) = mpsc::channel(); 103 | let check = self.check.clone(); 104 | thread::spawn(move || { 105 | let _t = tx.send(check.do_check()); 106 | }); 107 | rx.recv_timeout(self.timeout) 108 | .map_err(|_| "Timeout".to_owned()) 109 | .and_then(|res| res) 110 | } 111 | } 112 | 113 | #[derive(Debug, Clone)] 114 | pub struct DfCheck { 115 | free: u64, 116 | path: CString, 117 | } 118 | 119 | impl DfCheck { 120 | pub fn new(path: &Path, free: u64) -> DfCheck { 121 | DfCheck { 122 | path: CString::new(path.to_string_lossy().into_owned()).unwrap(), 123 | free, 124 | } 125 | } 126 | 127 | fn to_string(&self) -> String { 128 | format!("DF healthcheck, min {}MB for {:?}", self.free, self.path) 129 | } 130 | 131 | fn do_check(&self) -> Result<(), String> { 132 | #[cfg(statvfs64)] 133 | // use statvfs to get blocks available to unpriviliged users 134 | let free = unsafe { 135 | use libc::statvfs64; 136 | let mut stats: statvfs64 = ::std::mem::uninitialized(); 137 | if statvfs64(self.path.as_ptr(), &mut stats) == 0 { 138 | Ok(stats.f_bsize as u64 * stats.f_bavail / 1024 / 1024) 139 | } else { 140 | Err("Couldn't read".to_owned()) 141 | //Err(libc::strerror(*__errno_location())) 142 | } 143 | }; 144 | 145 | #[cfg(not(statvfs64))] 146 | // use statvfs to get blocks available to unpriviliged users 147 | let free = unsafe { 148 | use libc::statvfs; 149 | let mut stats: statvfs = ::std::mem::uninitialized(); 150 | if statvfs(self.path.as_ptr(), &mut stats) == 0 { 151 | Ok(stats.f_bsize as u64 * (stats.f_bavail / 1024 / 1024) as u64) 152 | } else { 153 | Err("Couldn't read".to_owned()) 154 | //Err(libc::strerror(*__errno_location())) 155 | } 156 | }; 157 | 158 | free.and_then(|f| { 159 | if f > self.free { 160 | Ok(()) 161 | } else { 162 | Err(format!( 163 | "Insufficient disk space. {}MB available, {}MB required", 164 | f, self.free 165 | )) 166 | } 167 | }) 168 | } 169 | } 170 | 171 | #[derive(Debug, Clone)] 172 | pub struct ProcCheck { 173 | process: String, 174 | } 175 | 176 | impl ProcCheck { 177 | pub fn new(process: &str) -> ProcCheck { 178 | ProcCheck { 179 | process: String::from(process), 180 | } 181 | } 182 | 183 | fn to_string(&self) -> String { 184 | format!("Proc healthcheck, checking for {}", self.process) 185 | } 186 | 187 | // match against /proc/pid/comm 188 | // zombie if /proc/pid/cmdline is empty 189 | fn do_check(&self) -> Result<(), String> { 190 | read_dir("/proc") 191 | .map_err(|_| "Couldn't read procfs".to_owned()) 192 | .and_then(|mut it| { 193 | it.find(|ref result| { 194 | result 195 | .as_ref() 196 | .ok() 197 | .and_then(|entry| entry.file_name().to_string_lossy().parse::().ok()) 198 | .filter(|pid| { 199 | let mut contents = String::new(); 200 | File::open(format!("/proc/{}/comm", pid)) 201 | .and_then(|mut f| f.read_to_string(&mut contents)) 202 | .map(|_| { 203 | contents.pop(); 204 | contents == self.process 205 | }).unwrap_or(false) 206 | }).map(|pid| { 207 | File::open(format!("/proc/{}/cmdline", pid)) 208 | .ok() 209 | .map_or_else(|| false, |f| f.bytes().next().is_some()) 210 | }).unwrap_or(false) 211 | }).ok_or_else(|| "No such process".to_owned()) 212 | .map(|_| ()) 213 | }) 214 | } 215 | } 216 | 217 | #[derive(Debug, Clone)] 218 | pub struct TcpCheck { 219 | addr: SocketAddr, 220 | } 221 | 222 | impl TcpCheck { 223 | pub fn new(addr: &SocketAddr) -> TcpCheck { 224 | TcpCheck { addr: (*addr) } 225 | } 226 | 227 | fn to_string(&self) -> String { 228 | format!("TCP healthcheck, connect to {:?}", self.addr) 229 | } 230 | 231 | fn do_check(&self) -> Result<(), String> { 232 | TcpStream::connect(&self.addr) 233 | .map(|_| ()) 234 | .map_err(|e| format!("Failed ({})", e)) 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/init.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, [Ribose Inc](https://www.ribose.com). 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions 5 | // are met: 6 | // 1. Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright 9 | // notice, this list of conditions and the following disclaimer in the 10 | // documentation and/or other materials provided with the distribution. 11 | // 12 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 13 | // ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 14 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 15 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 16 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 17 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 18 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 20 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | use application::Application; 25 | use crossbeam_channel as cc; 26 | use libc; 27 | use signal_hook; 28 | use std::collections::HashMap; 29 | use std::time::{Duration, Instant}; 30 | use stream; 31 | 32 | struct InitApp { 33 | inner: Application, 34 | needs_stop: bool, 35 | kill_time: Option, 36 | start_time: Option, 37 | depends: Vec, 38 | rdepends: Vec, 39 | } 40 | 41 | impl InitApp { 42 | fn new(app: Application) -> Self { 43 | Self { 44 | inner: app, 45 | needs_stop: false, 46 | kill_time: None, 47 | start_time: None, 48 | depends: Vec::new(), 49 | rdepends: Vec::new(), 50 | } 51 | } 52 | } 53 | 54 | pub struct Init { 55 | applications: Vec, 56 | } 57 | 58 | impl Init { 59 | pub fn run( 60 | mut applications: Vec, 61 | sig_recv: &cc::Receiver, 62 | fail_recv: &cc::Receiver<(String, String)>, 63 | ) { 64 | let mut apps = Self { 65 | applications: applications.drain(..).map(InitApp::new).collect(), 66 | }; 67 | 68 | apps.setup_dependencies(); 69 | 70 | let mut stream_handler = stream::Handler::new(); 71 | 72 | let mut shutdown = false; 73 | while !(shutdown && apps.all_stopped()) { 74 | apps.do_kills(); 75 | apps.do_stops(); 76 | if !shutdown { 77 | apps.do_starts(&mut stream_handler); 78 | } 79 | 80 | let timer = apps.get_next_timeout().map(cc::after); 81 | let mut select = cc::Select::new() 82 | .recv(&sig_recv, |s| s.map(Event::Signal)) 83 | .recv(&fail_recv, |f| f.map(Event::Fail)); 84 | if let Some(timer) = timer.as_ref() { 85 | select = select.recv(timer, |t| t.map(|_| Event::Timer)); 86 | } 87 | 88 | match select.wait() { 89 | Some(Event::Signal(signal)) => { 90 | apps.handle_signal(signal); 91 | shutdown = 92 | shutdown || signal == signal_hook::SIGTERM || signal == signal_hook::SIGINT; 93 | } 94 | Some(Event::Fail((group, msg))) => apps.handle_healthcheck_fail(&group, &msg), 95 | Some(Event::Timer) => (), 96 | None => unreachable!(), 97 | } 98 | 99 | enum Event { 100 | Signal(i32), 101 | Fail((String, String)), 102 | Timer, 103 | } 104 | } 105 | } 106 | 107 | fn handle_signal(&mut self, sig: i32) { 108 | if sig == signal_hook::SIGCHLD { 109 | let mut status: libc::c_int = 0; 110 | let child = unsafe { libc::wait(&mut status) } as u32; 111 | debug!("SIGCHLD received {} {}", child, status); 112 | 113 | let index = self 114 | .applications 115 | .iter_mut() 116 | .position(|app: &mut InitApp| app.inner.claim_child(child, status)); 117 | 118 | let mut stop_idx = None; 119 | if let Some(idx) = index { 120 | let app = &mut self.applications[idx]; 121 | // remove kill timer as process has died by some other means 122 | app.kill_time = None; 123 | if app.inner.is_dead() { 124 | // The application just died unexpectedly. We 125 | // still need to run the stop command to 126 | // perform any cleanup. 127 | stop_idx = Some(idx); 128 | } else if app.inner.is_runaway() { 129 | // The child was the stop process for an 130 | // application and the main process is still 131 | // active. We set a kill timer in case the 132 | // applicatiion doesn't die naturally 133 | app.kill_time = Some(Instant::now() + Duration::from_secs(5)); 134 | } else if app.inner.is_idle() { 135 | // Application has gone idle so we can set a restart time 136 | app.start_time = Some(Instant::now() + Duration::from_secs(1)); 137 | } 138 | } else { 139 | info!("Reaped zombie with PID {}", child); 140 | } 141 | 142 | stop_idx.map_or((), |idx| self.schedule_stop(idx)); 143 | } else if sig == signal_hook::SIGTERM || sig == signal_hook::SIGINT { 144 | debug!("Received termination signal ({})", sig); 145 | self.applications.iter_mut().for_each(|app| { 146 | if !(app.inner.is_stopped()) { 147 | app.needs_stop = true; 148 | } 149 | }); 150 | } 151 | } 152 | 153 | fn handle_healthcheck_fail(&mut self, group: &str, _message: &str) { 154 | let fails = self.app_idxs(|app| app.inner.healthchecks.iter().any(|h| *h == group)); 155 | fails.iter().for_each(|&idx| self.schedule_stop(idx)); 156 | } 157 | 158 | fn schedule_stop(&mut self, idx: usize) { 159 | let mut stops = self.applications[idx].rdepends.clone(); 160 | stops.push(idx); 161 | 162 | stops.iter().for_each(|&idx| { 163 | let app = &mut self.applications[idx]; 164 | if !(app.inner.is_stopped()) { 165 | app.needs_stop = true; 166 | } 167 | }); 168 | } 169 | 170 | fn all_stopped(&self) -> bool { 171 | self.applications.iter().all(|app| app.inner.is_stopped()) 172 | } 173 | 174 | fn do_starts(&mut self, stream_handler: &mut stream::Handler) { 175 | let mut starts = self 176 | .applications 177 | .iter() 178 | .enumerate() 179 | .filter(|(_, app)| app.inner.is_idle()) 180 | .filter(|(_, app)| app.start_time.map(|t| t <= Instant::now()).unwrap_or(true)) 181 | .filter(|(_, app)| { 182 | app.depends.iter().all(|idx| { 183 | let dep = &self.applications[*idx]; 184 | !dep.needs_stop && dep.inner.is_started() 185 | }) 186 | }).map(|(idx, _)| idx) 187 | .collect::>(); 188 | 189 | starts.drain(..).for_each(|idx| { 190 | let app = &mut self.applications[idx]; 191 | app.start_time = None; 192 | if app.inner.start(stream_handler) { 193 | app.kill_time = Some(Instant::now() + Duration::from_secs(30)); 194 | } else if app.inner.is_idle() { 195 | app.start_time = Some(Instant::now() + Duration::from_secs(1)); 196 | } 197 | }); 198 | } 199 | 200 | fn do_stops(&mut self) { 201 | let stops = self 202 | .applications 203 | .iter() 204 | .enumerate() 205 | .filter(|(_, app)| app.needs_stop) 206 | .filter(|(_, app)| { 207 | app.rdepends 208 | .iter() 209 | .all(|&idx| self.applications[idx].inner.is_stopped()) 210 | }).map(|(idx, _)| idx) 211 | .collect::>(); 212 | 213 | stops.iter().for_each(|idx| { 214 | let app = &mut self.applications[*idx]; 215 | if app.inner.stop() { 216 | app.kill_time = Some(Instant::now() + Duration::from_secs(5)); 217 | } 218 | app.needs_stop = false; 219 | }); 220 | } 221 | 222 | fn do_kills(&mut self) { 223 | let kills = self 224 | .applications 225 | .iter() 226 | .enumerate() 227 | .filter(|(_, app)| app.kill_time.map(|t| t <= Instant::now()).unwrap_or(false)) 228 | .map(|(idx, _)| idx) 229 | .collect::>(); 230 | 231 | kills.iter().for_each(|idx| { 232 | self.applications[*idx].inner.kill(); 233 | self.applications[*idx].kill_time = None; 234 | }); 235 | } 236 | 237 | fn app_idxs(&self, filter: F) -> Vec 238 | where 239 | F: Fn(&InitApp) -> bool, 240 | { 241 | self.applications 242 | .iter() 243 | .enumerate() 244 | .filter(|(_, app)| filter(app)) 245 | .map(|(idx, _)| idx) 246 | .collect() 247 | } 248 | 249 | fn get_next_timeout(&self) -> Option { 250 | let times = self.applications.iter().fold(Vec::new(), |mut times, app| { 251 | if let Some(t) = app.kill_time { 252 | times.push(t); 253 | } 254 | if let Some(t) = app.start_time { 255 | times.push(t); 256 | } 257 | times 258 | }); 259 | times.iter().min().map(|t| *t - (Instant::now().min(*t))) 260 | } 261 | 262 | fn setup_dependencies(&mut self) { 263 | let mut all_deps = { 264 | let idxs = self 265 | .applications 266 | .iter() 267 | .enumerate() 268 | .map(|(idx, app)| (app.inner.id.as_str(), idx)) 269 | .collect::>(); 270 | 271 | self.applications 272 | .iter() 273 | .enumerate() 274 | .map(|(idx, _)| { 275 | let (mut deps, mut others): (Vec<_>, Vec<_>) = self 276 | .applications 277 | .iter() 278 | .enumerate() 279 | .map(|(i, _)| i) 280 | .partition(|&i| i == idx); 281 | loop { 282 | let (mut pass, fail): (Vec<_>, Vec<_>) = others.iter().partition(|&oidx| { 283 | deps.iter().any(|&didx| { 284 | self.applications[didx].inner.requires.iter().any(|id| { 285 | idxs.get(id.as_str()) 286 | .map(|idx| idx == oidx) 287 | .unwrap_or(false) 288 | }) 289 | }) 290 | }); 291 | if pass.is_empty() { 292 | break; 293 | } 294 | deps.append(&mut pass); 295 | others = fail; 296 | } 297 | deps.swap_remove(0); 298 | deps 299 | }).collect::>() 300 | }; 301 | 302 | self.applications 303 | .iter_mut() 304 | .zip(all_deps.drain(..)) 305 | .for_each(|(app, deps)| app.depends = deps); 306 | 307 | let mut all_rdeps = self 308 | .applications 309 | .iter() 310 | .enumerate() 311 | .map(|(app_idx, _)| { 312 | self.applications 313 | .iter() 314 | .enumerate() 315 | .filter(|(_, app)| app.depends.iter().any(|&idx| idx == app_idx)) 316 | .map(|(idx, _)| idx) 317 | .collect::>() 318 | }).collect::>(); 319 | 320 | self.applications 321 | .iter_mut() 322 | .zip(all_rdeps.drain(..)) 323 | .for_each(|(app, rdeps)| app.rdepends = rdeps); 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, [Ribose Inc](https://www.ribose.com). 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions 5 | // are met: 6 | // 1. Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright 9 | // notice, this list of conditions and the following disclaimer in the 10 | // documentation and/or other materials provided with the distribution. 11 | // 12 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 13 | // ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 14 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 15 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 16 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 17 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 18 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 20 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | extern crate crossbeam_channel; 25 | extern crate libc; 26 | extern crate mio; 27 | extern crate nereon; 28 | extern crate nix; 29 | extern crate rand; 30 | extern crate signal_hook; 31 | extern crate slab; 32 | extern crate syslog; 33 | 34 | #[macro_use] 35 | extern crate nereon_derive; 36 | 37 | #[macro_use] 38 | extern crate log; 39 | 40 | mod application; 41 | mod config; 42 | mod health; 43 | mod init; 44 | mod limit; 45 | mod signal; 46 | mod stream; 47 | 48 | pub fn riffol>(args: T) -> Result<(), String> { 49 | let config::Riffol { 50 | applications: apps, 51 | healthchecks: checks, 52 | } = config::get_config(args)?; 53 | 54 | let sig_recv = signal::recv_signals(); 55 | let check_recv = health::recv_checks(&checks); 56 | init::Init::run(apps, &sig_recv, &check_recv); 57 | Ok(()) 58 | } 59 | -------------------------------------------------------------------------------- /src/limit.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, [Ribose Inc](https://www.ribose.com). 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions 5 | // are met: 6 | // 1. Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright 9 | // notice, this list of conditions and the following disclaimer in the 10 | // documentation and/or other materials provided with the distribution. 11 | // 12 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 13 | // ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 14 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 15 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 16 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 17 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 18 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 20 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | extern crate libc; 25 | 26 | #[derive(Debug, Clone)] 27 | pub enum Limit { 28 | Num(u64), 29 | Infinity, 30 | } 31 | 32 | #[derive(Debug, Clone)] 33 | pub enum RLimit { 34 | Memory(Limit), 35 | Procs(Limit), 36 | Files(Limit), 37 | } 38 | 39 | pub fn setlimit(rlimit: &RLimit) { 40 | let (resource, limit) = match rlimit { 41 | RLimit::Memory(v) => (libc::RLIMIT_AS, v), 42 | RLimit::Procs(v) => (libc::RLIMIT_NPROC, v), 43 | RLimit::Files(v) => (libc::RLIMIT_NOFILE, v), 44 | }; 45 | let limit = match limit { 46 | Limit::Num(v) => *v, 47 | Limit::Infinity => libc::RLIM_INFINITY, 48 | }; 49 | unsafe { 50 | let _result = libc::setrlimit( 51 | resource, 52 | &libc::rlimit { 53 | rlim_cur: limit, 54 | rlim_max: limit, 55 | }, 56 | ); 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, [Ribose Inc](https://www.ribose.com). 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions 5 | // are met: 6 | // 1. Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright 9 | // notice, this list of conditions and the following disclaimer in the 10 | // documentation and/or other materials provided with the distribution. 11 | // 12 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 13 | // ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 14 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 15 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 16 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 17 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 18 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 20 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | extern crate riffol; 25 | extern crate stderrlog; 26 | 27 | use std::process::exit; 28 | 29 | fn main() { 30 | stderrlog::new() 31 | .module(module_path!()) 32 | .verbosity(12) 33 | .init() 34 | .unwrap(); 35 | 36 | match riffol::riffol(std::env::args()) { 37 | Err(e) => { 38 | eprintln!("{:?}", e); 39 | exit(1); 40 | } 41 | _ => (), 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/signal.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, [Ribose Inc](https://www.ribose.com). 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions 5 | // are met: 6 | // 1. Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright 9 | // notice, this list of conditions and the following disclaimer in the 10 | // documentation and/or other materials provided with the distribution. 11 | // 12 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 13 | // ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 14 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 15 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 16 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 17 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 18 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 20 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | use crossbeam_channel as cc; 25 | use libc; 26 | use signal_hook; 27 | use std::thread; 28 | 29 | pub fn recv_signals() -> cc::Receiver { 30 | // set us up to adopt zombies from subprocesses 31 | #[cfg(target_os = "linux")] 32 | { 33 | use libc; 34 | static PR_SET_CHILD_SUBREAPER: libc::c_int = 36; 35 | 36 | if unsafe { libc::getpid() != 1 && libc::prctl(PR_SET_CHILD_SUBREAPER, 1) != 0 } { 37 | warn!("Couldn't set PR_CHILD_SUBREAPER",); 38 | } 39 | } 40 | 41 | let signals = [ 42 | signal_hook::SIGINT, 43 | signal_hook::SIGTERM, 44 | signal_hook::SIGCHLD, 45 | ]; 46 | let signals = signal_hook::iterator::Signals::new(&signals).unwrap(); 47 | let (sig_send, sig_recv) = cc::unbounded(); 48 | thread::spawn(move || { 49 | for signal in signals.forever() { 50 | sig_send.send(signal); 51 | } 52 | }); 53 | sig_recv 54 | } 55 | 56 | pub fn signal(pid: u32, sig: i32) { 57 | unsafe { libc::kill(pid as libc::pid_t, sig) }; 58 | } 59 | -------------------------------------------------------------------------------- /src/stream.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, [Ribose Inc](https://www.ribose.com). 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions 5 | // are met: 6 | // 1. Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 2. Redistributions in binary form must reproduce the above copyright 9 | // notice, this list of conditions and the following disclaimer in the 10 | // documentation and/or other materials provided with the distribution. 11 | // 12 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 13 | // ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 14 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 15 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 16 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 17 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 18 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 20 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | use crossbeam_channel as cc; 25 | use mio::unix::{EventedFd, UnixReady}; 26 | use mio::{Events, Poll, PollOpt, Ready, Token}; 27 | use nix::fcntl::{fcntl, FcntlArg::F_SETFL, OFlag}; 28 | use slab::Slab; 29 | use std::fs::{File, OpenOptions}; 30 | use std::io::{self, BufRead, BufReader, Write}; 31 | use std::net::SocketAddr; 32 | use std::os::unix::io::{FromRawFd, IntoRawFd, RawFd}; 33 | use std::thread; 34 | use std::time::Duration; 35 | use syslog::{self, Formatter3164, Logger, LoggerBackend, Severity::*}; 36 | 37 | /// Address used for various flavours of Syslog. 38 | #[derive(Debug, Clone)] 39 | pub enum Address { 40 | Tcp(SocketAddr), 41 | Udp { 42 | server: SocketAddr, 43 | local: SocketAddr, 44 | }, 45 | Unix(Option), 46 | } 47 | 48 | /// Stream descriptions. Currently supported are `Syslog` 49 | /// (TCP/UDP/Unix) and files 50 | #[derive(Debug, Clone)] 51 | pub enum Stream { 52 | File { 53 | filename: String, 54 | }, 55 | Syslog { 56 | address: Address, 57 | facility: syslog::Facility, 58 | severity: u32, // syslog::Severity doesn't implement Debug, 59 | }, 60 | #[cfg(test)] 61 | Stdout, 62 | } 63 | 64 | /// `Connection` is used to associate a source `fd` with a `Stream` description 65 | struct Connection { 66 | source: BufReader, 67 | sink: Stream, 68 | } 69 | 70 | impl Connection { 71 | /// Associate `fd` with `Stream`: first ensures the `fd` is set to 72 | /// non-blocking, then converts it into a `BufReader` 73 | fn new(fd: RawFd, stream: Stream) -> Connection { 74 | // set fd to non-blocking and convert to File 75 | fcntl(fd, F_SETFL(OFlag::O_NONBLOCK)).unwrap(); // TODO: check result 76 | Connection { 77 | source: BufReader::new(unsafe { File::from_raw_fd(fd) }), 78 | sink: stream, 79 | } 80 | } 81 | } 82 | 83 | /// One `Handler` is used to asynchronously stream many `fd`s to 84 | /// destinations described by `Stream` structs. It uses a thread with 85 | /// a `mio:Poll` loop to read. Data is syncronously written to 86 | /// relevent destinations. 87 | /// 88 | /// Note: Sychronous writes might cause blocking (especially TCP 89 | /// syslog writes) and may change to async in the future. 90 | /// 91 | /// Note: Write endpoints are constructed and torn down for each write 92 | /// (ie. TCP connect/send/close and file open/write/close). This is 93 | /// highly inefficient but saves having to maintain TCP connections 94 | /// and has avoids writing to unlinked (eg. `logrotate`d) files. 95 | pub struct Handler { 96 | channel: cc::Sender, 97 | thread: Option>, 98 | } 99 | 100 | impl Handler { 101 | /// Creates a `Handler` with an mpsc communication channel and 102 | /// background thread to shuffle data between standard streams and 103 | /// destintions specified by `Stream` 104 | pub fn new() -> Handler { 105 | let (tx, rx) = cc::unbounded(); 106 | Handler { 107 | channel: tx, 108 | thread: Some(thread::spawn(move || { 109 | handler(&rx); 110 | })), 111 | } 112 | } 113 | 114 | /// Sends a message to background thread to monitor source `fd` and 115 | /// write to `Stream` 116 | pub fn add_stream(&self, fd: RawFd, stream: Stream) { 117 | // send message to handler thread 118 | debug!("Adding stream for fd {}", fd); 119 | self.channel.send(Message::Add(fd, stream)) 120 | } 121 | } 122 | 123 | /// Before `Handler` is `Drop`ped, send the close message 124 | /// to the mio thread and wait for it to finish. 125 | impl Drop for Handler { 126 | fn drop(&mut self) { 127 | self.channel.send(Message::Close); 128 | self.thread.take().unwrap().join().unwrap(); 129 | } 130 | } 131 | 132 | /// Message type for communication between `Handler` instance and 133 | /// its background logging thread. 134 | enum Message { 135 | Add(RawFd, Stream), 136 | Close, 137 | } 138 | 139 | /// Background thread routine to handle standard streams. 140 | /// 141 | /// Loop: 142 | /// check for new channels 143 | /// poll fds with mio (using a timeout) 144 | /// read all readable fds until `WouldBlock` 145 | /// deregister any fds that have closed 146 | fn handler(channel: &cc::Receiver) { 147 | let mut connections = Slab::with_capacity(128); 148 | let poll = Poll::new().unwrap(); 149 | let mut closed = false; 150 | while !closed { 151 | if let Some(message) = channel.try_recv() { 152 | match message { 153 | Message::Add(fd, stream) => { 154 | if let Err(e) = poll.register( 155 | &EventedFd(&fd), 156 | Token(connections.insert(Connection::new(fd, stream))), 157 | Ready::readable() | UnixReady::hup(), 158 | PollOpt::edge(), 159 | ) { 160 | error!("Failed to register stream with mio ({})", e); 161 | } 162 | } 163 | Message::Close => closed = true, 164 | } 165 | } 166 | 167 | let mut events = Events::with_capacity(128); 168 | if let Err(e) = poll.poll(&mut events, Some(Duration::from_millis(50))) { 169 | error!("Failed to poll mio ({}). Abandoning stream redirection.", e); 170 | break; 171 | } 172 | 173 | for event in &events { 174 | let Token(handle) = event.token(); 175 | if event.readiness().is_readable() { 176 | let mut connection = connections.get_mut(handle).unwrap(); 177 | loop { 178 | match read_line(&mut connection.source) { 179 | Ok(None) => break, // WouldBlock 180 | Ok(Some(ref line)) if line.is_empty() => break, // imminent HUP 181 | Ok(Some(line)) => { 182 | match write_line(&connection.sink, &line[..line.len() - 1]) { 183 | Ok(()) => (), 184 | Err(e) => warn!("Stream redirection failure ({}): {}", e, line), 185 | } 186 | } 187 | Err(e) => { 188 | warn!("Stream error {}", e); 189 | break; 190 | } 191 | } 192 | } 193 | } 194 | if UnixReady::from(event.readiness()).is_hup() { 195 | let mut connection = connections.remove(handle); 196 | poll.deregister(&EventedFd(&connection.source.into_inner().into_raw_fd())) 197 | .unwrap(); 198 | } 199 | } 200 | } 201 | } 202 | 203 | /// Reads a line from the BufReader. 204 | /// 205 | /// Note: The underlying fd is already set O_NONBLOCK 206 | /// 207 | /// Returns `Ok(Some(String))` on success 208 | /// `Ok(None)` if `BufReader::read_line()` returns `WouldBlock` 209 | /// `Err(io::Error()` otherwise 210 | fn read_line(source: &mut BufReader) -> io::Result> { 211 | let mut line = String::new(); 212 | match source.read_line(&mut line) { 213 | Ok(_) => Ok(Some(line)), 214 | Err(e) => match e.kind() { 215 | io::ErrorKind::WouldBlock => Ok(None), 216 | _ => Err(e), 217 | }, 218 | } 219 | } 220 | 221 | /// Writes a line to file or to syslog (TCP or UDP) 222 | fn write_line(sink: &Stream, line: &str) -> io::Result<()> { 223 | match sink { 224 | Stream::File { filename } => { 225 | OpenOptions::new() 226 | .create_new(true) 227 | .append(true) 228 | .open(filename)? 229 | .write_all(line.as_ref())?; 230 | Ok(()) 231 | } 232 | Stream::Syslog { 233 | address, 234 | facility, 235 | severity, 236 | } => { 237 | let formatter = Formatter3164 { 238 | facility: *facility, 239 | hostname: None, 240 | process: String::from("riffol"), 241 | pid: 0, 242 | }; 243 | let mut logger: syslog::Result< 244 | Logger, 245 | > = match address { 246 | Address::Unix(address) => match address { 247 | Some(address) => syslog::unix_custom(formatter, address), 248 | None => syslog::unix(formatter), 249 | }, 250 | Address::Tcp(server) => syslog::tcp(formatter, server), 251 | Address::Udp { server, local } => syslog::udp(formatter, local, server), 252 | }; 253 | let line = line.to_owned(); 254 | 255 | match logger { 256 | Ok(mut logger) => match severity { 257 | x if *x == LOG_EMERG as u32 => logger.emerg(line), 258 | x if *x == LOG_ALERT as u32 => logger.alert(line), 259 | x if *x == LOG_CRIT as u32 => logger.crit(line), 260 | x if *x == LOG_ERR as u32 => logger.err(line), 261 | x if *x == LOG_WARNING as u32 => logger.warning(line), 262 | x if *x == LOG_NOTICE as u32 => logger.notice(line), 263 | x if *x == LOG_INFO as u32 => logger.info(line), 264 | _ => logger.debug(line), 265 | }.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e))), 266 | _ => Err(io::Error::new( 267 | io::ErrorKind::Other, 268 | "Couldn't create logging instance", 269 | )), 270 | } 271 | } 272 | #[cfg(test)] 273 | Stream::Stdout => Ok(println!("{}", line)), 274 | } 275 | } 276 | 277 | #[cfg(test)] 278 | mod test { 279 | use super::Stream; 280 | use std::os::unix::io::IntoRawFd; 281 | use std::process::{Command, Stdio}; 282 | 283 | #[test] 284 | fn test1() { 285 | let handler = super::Handler::new(); 286 | let mut child1 = Command::new("ping") 287 | .arg("-c2") 288 | .arg("8.8.4.4") 289 | .stdout(Stdio::piped()) 290 | .stderr(Stdio::piped()) 291 | .spawn() 292 | .unwrap(); 293 | 294 | let mut child2 = Command::new("ls") 295 | .arg("-l") 296 | .arg("/") 297 | .stdout(Stdio::piped()) 298 | .stderr(Stdio::piped()) 299 | .spawn() 300 | .unwrap(); 301 | 302 | handler.add_stream( 303 | child1.stdout.take().unwrap().into_raw_fd(), 304 | Stream::Stdout.clone(), 305 | ); 306 | handler.add_stream( 307 | child1.stderr.take().unwrap().into_raw_fd(), 308 | Stream::Stdout.clone(), 309 | ); 310 | handler.add_stream( 311 | child2.stdout.take().unwrap().into_raw_fd(), 312 | Stream::Stdout.clone(), 313 | ); 314 | handler.add_stream( 315 | child2.stderr.take().unwrap().into_raw_fd(), 316 | Stream::Stdout.clone(), 317 | ); 318 | 319 | child2.wait().unwrap(); 320 | child1.wait().unwrap(); 321 | } 322 | } 323 | -------------------------------------------------------------------------------- /tests/db.vars: -------------------------------------------------------------------------------- 1 | MYSQL_TCP_PORT=3306 2 | MYSQL_UNIX_PORT=/var/run/mysql -------------------------------------------------------------------------------- /tests/riffol.conf: -------------------------------------------------------------------------------- 1 | init web { 2 | application_groups [webstack] 3 | } 4 | 5 | application_group webstack { 6 | applications [db, www] 7 | dependencies [webstack] 8 | } 9 | 10 | application db { 11 | exec "/etc/init.d/db" 12 | healthchecks [db] 13 | env_file "tests/db.vars" 14 | healthcheckfail restart 15 | stdout file ["/var/log/riffol"] 16 | stderr syslog socket "/dev/log" 17 | } 18 | 19 | application www { 20 | exec "/etc/init.d/http" 21 | dir "/var/www" 22 | env { 23 | SERVER_ROOT "/var/www" 24 | } 25 | healthchecks [www] 26 | healthcheckfail restart 27 | stdout rsyslog server "127.0.0.1:514" 28 | stderr syslog {} 29 | } 30 | 31 | healthchecks www { 32 | checks [ 33 | "tcp://127.0.0.1:80" 34 | "proc://nginx" 35 | ] 36 | timeout 5 37 | interval 90 38 | } 39 | 40 | healthchecks db { 41 | checks [ 42 | "df:///var/lib/mysql:512" 43 | "proc://mysqld" 44 | "tcp://127.0.0.1:3306" 45 | ] 46 | timeout 10 47 | interval 60 48 | } 49 | 50 | dependency webstack { 51 | packages [httpd, mariadb] 52 | } 53 | 54 | limits db { 55 | max_procs 4 56 | max_mem 1024 57 | } 58 | --------------------------------------------------------------------------------