├── .cargo └── config.toml ├── .dockerignore ├── .github └── workflows │ ├── release.yml │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── config-examples ├── latency-example.json ├── mistake-example.json └── no-inject.json ├── example ├── Dockerfile └── example.go ├── rust-toolchain ├── rustfmt.toml ├── src ├── fuse_device.rs ├── hookfs │ ├── async_fs.rs │ ├── errors.rs │ ├── mod.rs │ ├── reply.rs │ ├── runtime.rs │ └── utils.rs ├── injector │ ├── attr_override_injector.rs │ ├── fault_injector.rs │ ├── filter.rs │ ├── injector_config.rs │ ├── latency_injector.rs │ ├── mistake_injector.rs │ ├── mod.rs │ └── multi_injector.rs ├── jsonrpc.rs ├── lib.rs ├── main.rs ├── mount.rs ├── mount_injector.rs ├── ptrace │ └── mod.rs ├── replacer │ ├── cwd_replacer.rs │ ├── fd_replacer.rs │ ├── mmap_replacer.rs │ ├── mod.rs │ └── utils.rs ├── stop.rs └── utils.rs └── tests ├── jsonrpc_test.rs └── posix_test.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = ["-Z", "relro-level=full"] -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | target 2 | .git 3 | Dockerfile 4 | example -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - 'v*' 5 | 6 | name: Create Release 7 | 8 | jobs: 9 | build: 10 | name: Create Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | - name: Build 16 | run: | 17 | make release 18 | tar -czvf ./toda.tar.gz ./toda 19 | - name: Create Release 20 | id: create_release 21 | uses: actions/create-release@v1 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | with: 25 | tag_name: ${{ github.ref }} 26 | release_name: Release ${{ github.ref }} 27 | draft: false 28 | prerelease: false 29 | - name: Upload Release Asset 30 | id: upload-release-asset 31 | uses: actions/upload-release-asset@v1 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | with: 35 | upload_url: ${{ steps.create_release.outputs.upload_url }} 36 | asset_path: ./toda.tar.gz 37 | asset_name: toda-linux-amd64.tar.gz 38 | asset_content_type: application/zip -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Install FUSE 18 | run: sudo apt install fuse libfuse-dev pkg-config -y 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Grant Permission on /tmp 22 | run: sudo chmod -R 777 /tmp 23 | - name: Add user_allow_other to /etc/fuse.conf 24 | run: echo "user_allow_other" | sudo tee -a /etc/fuse.conf 25 | - name: Run tests 26 | run: cargo test --verbose -- --test-threads=1 27 | clippy_check: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v2 31 | - name: Install FUSE 32 | run: sudo apt install fuse libfuse-dev pkg-config -y 33 | - uses: actions-rs/toolchain@v1 34 | with: 35 | components: clippy 36 | - uses: actions-rs/clippy-check@v1 37 | with: 38 | token: ${{ secrets.GITHUB_TOKEN }} 39 | args: --all-features -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /example/toda 3 | /toda -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler32" 7 | version = "1.2.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 10 | 11 | [[package]] 12 | name = "aho-corasick" 13 | version = "0.7.15" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" 16 | dependencies = [ 17 | "memchr", 18 | ] 19 | 20 | [[package]] 21 | name = "ansi_term" 22 | version = "0.11.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 25 | dependencies = [ 26 | "winapi 0.3.9", 27 | ] 28 | 29 | [[package]] 30 | name = "ansi_term" 31 | version = "0.12.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 34 | dependencies = [ 35 | "winapi 0.3.9", 36 | ] 37 | 38 | [[package]] 39 | name = "anyhow" 40 | version = "1.0.38" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1" 43 | 44 | [[package]] 45 | name = "async-trait" 46 | version = "0.1.42" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d" 49 | dependencies = [ 50 | "proc-macro2", 51 | "quote", 52 | "syn", 53 | ] 54 | 55 | [[package]] 56 | name = "atty" 57 | version = "0.2.14" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 60 | dependencies = [ 61 | "hermit-abi", 62 | "libc", 63 | "winapi 0.3.9", 64 | ] 65 | 66 | [[package]] 67 | name = "autocfg" 68 | version = "1.0.1" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 71 | 72 | [[package]] 73 | name = "bitflags" 74 | version = "1.2.1" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 77 | 78 | [[package]] 79 | name = "byteorder" 80 | version = "1.4.2" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" 83 | 84 | [[package]] 85 | name = "bytes" 86 | version = "0.5.6" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" 89 | 90 | [[package]] 91 | name = "bytes" 92 | version = "1.1.0" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 95 | 96 | [[package]] 97 | name = "cc" 98 | version = "1.0.66" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" 101 | 102 | [[package]] 103 | name = "cfg-if" 104 | version = "0.1.10" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 107 | 108 | [[package]] 109 | name = "cfg-if" 110 | version = "1.0.0" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 113 | 114 | [[package]] 115 | name = "chrono" 116 | version = "0.4.19" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 119 | dependencies = [ 120 | "libc", 121 | "num-integer", 122 | "num-traits", 123 | "time", 124 | "winapi 0.3.9", 125 | ] 126 | 127 | [[package]] 128 | name = "clap" 129 | version = "2.33.3" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 132 | dependencies = [ 133 | "ansi_term 0.11.0", 134 | "atty", 135 | "bitflags", 136 | "strsim", 137 | "textwrap", 138 | "unicode-width", 139 | "vec_map", 140 | ] 141 | 142 | [[package]] 143 | name = "crc32fast" 144 | version = "1.2.1" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" 147 | dependencies = [ 148 | "cfg-if 1.0.0", 149 | ] 150 | 151 | [[package]] 152 | name = "derive_more" 153 | version = "0.99.11" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" 156 | dependencies = [ 157 | "proc-macro2", 158 | "quote", 159 | "syn", 160 | ] 161 | 162 | [[package]] 163 | name = "dynasm" 164 | version = "1.0.1" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "3d7d1242462849390bb2ad38aeed769499f1afc7383affa2ab0c1baa894c0200" 167 | dependencies = [ 168 | "bitflags", 169 | "byteorder", 170 | "lazy_static", 171 | "proc-macro-error", 172 | "proc-macro2", 173 | "quote", 174 | "syn", 175 | ] 176 | 177 | [[package]] 178 | name = "dynasmrt" 179 | version = "1.0.1" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "c1dd4d1d5ca12258cef339a57a7643e8b233a42dea9bb849630ddd9dd7726aa9" 182 | dependencies = [ 183 | "byteorder", 184 | "dynasm", 185 | "memmap2", 186 | ] 187 | 188 | [[package]] 189 | name = "either" 190 | version = "1.6.1" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 193 | 194 | [[package]] 195 | name = "env_logger" 196 | version = "0.8.2" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "f26ecb66b4bdca6c1409b40fb255eefc2bd4f6d135dab3c3124f80ffa2a9661e" 199 | dependencies = [ 200 | "atty", 201 | "humantime", 202 | "log", 203 | "regex", 204 | "termcolor", 205 | ] 206 | 207 | [[package]] 208 | name = "fnv" 209 | version = "1.0.7" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 212 | 213 | [[package]] 214 | name = "fuchsia-zircon" 215 | version = "0.3.3" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 218 | dependencies = [ 219 | "bitflags", 220 | "fuchsia-zircon-sys", 221 | ] 222 | 223 | [[package]] 224 | name = "fuchsia-zircon-sys" 225 | version = "0.3.3" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 228 | 229 | [[package]] 230 | name = "fuser" 231 | version = "0.6.0" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "e5cd519583b0984ff2afd39b0c5b64b9520ef754c6d0330c54631595aa1de170" 234 | dependencies = [ 235 | "libc", 236 | "log", 237 | "pkg-config", 238 | "users", 239 | ] 240 | 241 | [[package]] 242 | name = "futures" 243 | version = "0.1.31" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" 246 | 247 | [[package]] 248 | name = "futures" 249 | version = "0.3.12" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "da9052a1a50244d8d5aa9bf55cbc2fb6f357c86cc52e46c62ed390a7180cf150" 252 | dependencies = [ 253 | "futures-channel", 254 | "futures-core", 255 | "futures-executor", 256 | "futures-io", 257 | "futures-sink", 258 | "futures-task", 259 | "futures-util", 260 | ] 261 | 262 | [[package]] 263 | name = "futures-channel" 264 | version = "0.3.12" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "f2d31b7ec7efab6eefc7c57233bb10b847986139d88cc2f5a02a1ae6871a1846" 267 | dependencies = [ 268 | "futures-core", 269 | "futures-sink", 270 | ] 271 | 272 | [[package]] 273 | name = "futures-core" 274 | version = "0.3.12" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "79e5145dde8da7d1b3892dad07a9c98fc04bc39892b1ecc9692cf53e2b780a65" 277 | 278 | [[package]] 279 | name = "futures-executor" 280 | version = "0.3.12" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "e9e59fdc009a4b3096bf94f740a0f2424c082521f20a9b08c5c07c48d90fd9b9" 283 | dependencies = [ 284 | "futures-core", 285 | "futures-task", 286 | "futures-util", 287 | "num_cpus", 288 | ] 289 | 290 | [[package]] 291 | name = "futures-io" 292 | version = "0.3.12" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "28be053525281ad8259d47e4de5de657b25e7bac113458555bb4b70bc6870500" 295 | 296 | [[package]] 297 | name = "futures-macro" 298 | version = "0.3.12" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "c287d25add322d9f9abdcdc5927ca398917996600182178774032e9f8258fedd" 301 | dependencies = [ 302 | "proc-macro-hack", 303 | "proc-macro2", 304 | "quote", 305 | "syn", 306 | ] 307 | 308 | [[package]] 309 | name = "futures-sink" 310 | version = "0.3.12" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "caf5c69029bda2e743fddd0582d1083951d65cc9539aebf8812f36c3491342d6" 313 | 314 | [[package]] 315 | name = "futures-task" 316 | version = "0.3.12" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "13de07eb8ea81ae445aca7b69f5f7bf15d7bf4912d8ca37d6645c77ae8a58d86" 319 | dependencies = [ 320 | "once_cell", 321 | ] 322 | 323 | [[package]] 324 | name = "futures-util" 325 | version = "0.3.12" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "632a8cd0f2a4b3fdea1657f08bde063848c3bd00f9bbf6e256b8be78802e624b" 328 | dependencies = [ 329 | "futures 0.1.31", 330 | "futures-channel", 331 | "futures-core", 332 | "futures-io", 333 | "futures-macro", 334 | "futures-sink", 335 | "futures-task", 336 | "memchr", 337 | "pin-project-lite 0.2.4", 338 | "pin-utils", 339 | "proc-macro-hack", 340 | "proc-macro-nested", 341 | "slab", 342 | ] 343 | 344 | [[package]] 345 | name = "getrandom" 346 | version = "0.1.16" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" 349 | dependencies = [ 350 | "cfg-if 1.0.0", 351 | "libc", 352 | "wasi 0.9.0+wasi-snapshot-preview1", 353 | ] 354 | 355 | [[package]] 356 | name = "glob" 357 | version = "0.3.0" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 360 | 361 | [[package]] 362 | name = "heck" 363 | version = "0.3.2" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" 366 | dependencies = [ 367 | "unicode-segmentation", 368 | ] 369 | 370 | [[package]] 371 | name = "hermit-abi" 372 | version = "0.1.18" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" 375 | dependencies = [ 376 | "libc", 377 | ] 378 | 379 | [[package]] 380 | name = "hex" 381 | version = "0.4.2" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" 384 | 385 | [[package]] 386 | name = "humantime" 387 | version = "2.1.0" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 390 | 391 | [[package]] 392 | name = "humantime-serde" 393 | version = "1.0.1" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "ac34a56cfd4acddb469cc7fff187ed5ac36f498ba085caf8bbc725e3ff474058" 396 | dependencies = [ 397 | "humantime", 398 | "serde", 399 | ] 400 | 401 | [[package]] 402 | name = "idna" 403 | version = "0.1.5" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" 406 | dependencies = [ 407 | "matches", 408 | "unicode-bidi", 409 | "unicode-normalization", 410 | ] 411 | 412 | [[package]] 413 | name = "instant" 414 | version = "0.1.9" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" 417 | dependencies = [ 418 | "cfg-if 1.0.0", 419 | ] 420 | 421 | [[package]] 422 | name = "iovec" 423 | version = "0.1.4" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 426 | dependencies = [ 427 | "libc", 428 | ] 429 | 430 | [[package]] 431 | name = "itertools" 432 | version = "0.9.0" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" 435 | dependencies = [ 436 | "either", 437 | ] 438 | 439 | [[package]] 440 | name = "itoa" 441 | version = "0.4.7" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" 444 | 445 | [[package]] 446 | name = "jsonrpc-client-transports" 447 | version = "17.0.0" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "15b6c6ad01c7354d60de493148c30ac8a82b759e22ae678c8705e9b8e0c566a4" 450 | dependencies = [ 451 | "derive_more", 452 | "futures 0.3.12", 453 | "jsonrpc-core", 454 | "jsonrpc-pubsub", 455 | "log", 456 | "serde", 457 | "serde_json", 458 | "url", 459 | ] 460 | 461 | [[package]] 462 | name = "jsonrpc-core" 463 | version = "17.0.0" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "07569945133257ff557eb37b015497104cea61a2c9edaf126c1cbd6e8332397f" 466 | dependencies = [ 467 | "futures 0.3.12", 468 | "log", 469 | "serde", 470 | "serde_derive", 471 | "serde_json", 472 | ] 473 | 474 | [[package]] 475 | name = "jsonrpc-core-client" 476 | version = "17.0.0" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "7ac9d56dc729912796637c30f475bbf834594607b27740dfea6e5fa7ba40d1f1" 479 | dependencies = [ 480 | "futures 0.3.12", 481 | "jsonrpc-client-transports", 482 | ] 483 | 484 | [[package]] 485 | name = "jsonrpc-derive" 486 | version = "17.0.0" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "b68ba7e76e5c7796cfa4d2a30e83986550c34404c6d40551c902ca6f7bd4a137" 489 | dependencies = [ 490 | "proc-macro-crate", 491 | "proc-macro2", 492 | "quote", 493 | "syn", 494 | ] 495 | 496 | [[package]] 497 | name = "jsonrpc-pubsub" 498 | version = "17.0.0" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "0c48dbebce7a9c88ab272a4db7d6478aa4c6d9596e6c086366e89efc4e9ed89e" 501 | dependencies = [ 502 | "futures 0.3.12", 503 | "jsonrpc-core", 504 | "lazy_static", 505 | "log", 506 | "parking_lot", 507 | "rand", 508 | "serde", 509 | ] 510 | 511 | [[package]] 512 | name = "jsonrpc-stdio-server" 513 | version = "17.0.0" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "369ec805537bd431d6d9a2df7297c73e214da68efc90c1b8c325d63e55aec5a7" 516 | dependencies = [ 517 | "futures 0.3.12", 518 | "jsonrpc-core", 519 | "log", 520 | "tokio 0.2.24", 521 | "tokio-util 0.3.1", 522 | ] 523 | 524 | [[package]] 525 | name = "kernel32-sys" 526 | version = "0.2.2" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 529 | dependencies = [ 530 | "winapi 0.2.8", 531 | "winapi-build", 532 | ] 533 | 534 | [[package]] 535 | name = "lazy_static" 536 | version = "1.4.0" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 539 | 540 | [[package]] 541 | name = "libc" 542 | version = "0.2.82" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929" 545 | 546 | [[package]] 547 | name = "libflate" 548 | version = "1.0.3" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "389de7875e06476365974da3e7ff85d55f1972188ccd9f6020dd7c8156e17914" 551 | dependencies = [ 552 | "adler32", 553 | "crc32fast", 554 | "libflate_lz77", 555 | "rle-decode-fast", 556 | ] 557 | 558 | [[package]] 559 | name = "libflate_lz77" 560 | version = "1.0.0" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "3286f09f7d4926fc486334f28d8d2e6ebe4f7f9994494b6dab27ddfad2c9b11b" 563 | 564 | [[package]] 565 | name = "lock_api" 566 | version = "0.4.2" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" 569 | dependencies = [ 570 | "scopeguard", 571 | ] 572 | 573 | [[package]] 574 | name = "log" 575 | version = "0.4.13" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "fcf3805d4480bb5b86070dcfeb9e2cb2ebc148adb753c5cca5f884d1d65a42b2" 578 | dependencies = [ 579 | "cfg-if 0.1.10", 580 | ] 581 | 582 | [[package]] 583 | name = "matchers" 584 | version = "0.0.1" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" 587 | dependencies = [ 588 | "regex-automata", 589 | ] 590 | 591 | [[package]] 592 | name = "matches" 593 | version = "0.1.8" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 596 | 597 | [[package]] 598 | name = "memchr" 599 | version = "2.3.4" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" 602 | 603 | [[package]] 604 | name = "memmap2" 605 | version = "0.2.0" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "e73be3b7d04a0123e933fea1d50d126cc7196bbc0362c0ce426694f777194eee" 608 | dependencies = [ 609 | "libc", 610 | ] 611 | 612 | [[package]] 613 | name = "mio" 614 | version = "0.6.23" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" 617 | dependencies = [ 618 | "cfg-if 0.1.10", 619 | "fuchsia-zircon", 620 | "fuchsia-zircon-sys", 621 | "iovec", 622 | "kernel32-sys", 623 | "libc", 624 | "log", 625 | "miow 0.2.2", 626 | "net2", 627 | "slab", 628 | "winapi 0.2.8", 629 | ] 630 | 631 | [[package]] 632 | name = "mio-named-pipes" 633 | version = "0.1.7" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656" 636 | dependencies = [ 637 | "log", 638 | "mio", 639 | "miow 0.3.6", 640 | "winapi 0.3.9", 641 | ] 642 | 643 | [[package]] 644 | name = "mio-uds" 645 | version = "0.6.8" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" 648 | dependencies = [ 649 | "iovec", 650 | "libc", 651 | "mio", 652 | ] 653 | 654 | [[package]] 655 | name = "miow" 656 | version = "0.2.2" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" 659 | dependencies = [ 660 | "kernel32-sys", 661 | "net2", 662 | "winapi 0.2.8", 663 | "ws2_32-sys", 664 | ] 665 | 666 | [[package]] 667 | name = "miow" 668 | version = "0.3.6" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" 671 | dependencies = [ 672 | "socket2", 673 | "winapi 0.3.9", 674 | ] 675 | 676 | [[package]] 677 | name = "net2" 678 | version = "0.2.37" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" 681 | dependencies = [ 682 | "cfg-if 0.1.10", 683 | "libc", 684 | "winapi 0.3.9", 685 | ] 686 | 687 | [[package]] 688 | name = "nix" 689 | version = "0.18.0" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "83450fe6a6142ddd95fb064b746083fc4ef1705fe81f64a64e1d4b39f54a1055" 692 | dependencies = [ 693 | "bitflags", 694 | "cc", 695 | "cfg-if 0.1.10", 696 | "libc", 697 | ] 698 | 699 | [[package]] 700 | name = "num-integer" 701 | version = "0.1.44" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 704 | dependencies = [ 705 | "autocfg", 706 | "num-traits", 707 | ] 708 | 709 | [[package]] 710 | name = "num-traits" 711 | version = "0.2.14" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 714 | dependencies = [ 715 | "autocfg", 716 | ] 717 | 718 | [[package]] 719 | name = "num_cpus" 720 | version = "1.13.0" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 723 | dependencies = [ 724 | "hermit-abi", 725 | "libc", 726 | ] 727 | 728 | [[package]] 729 | name = "once_cell" 730 | version = "1.5.2" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" 733 | 734 | [[package]] 735 | name = "parking_lot" 736 | version = "0.11.1" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" 739 | dependencies = [ 740 | "instant", 741 | "lock_api", 742 | "parking_lot_core", 743 | ] 744 | 745 | [[package]] 746 | name = "parking_lot_core" 747 | version = "0.8.3" 748 | source = "registry+https://github.com/rust-lang/crates.io-index" 749 | checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" 750 | dependencies = [ 751 | "cfg-if 1.0.0", 752 | "instant", 753 | "libc", 754 | "redox_syscall", 755 | "smallvec", 756 | "winapi 0.3.9", 757 | ] 758 | 759 | [[package]] 760 | name = "percent-encoding" 761 | version = "1.0.1" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" 764 | 765 | [[package]] 766 | name = "pin-project" 767 | version = "0.4.27" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15" 770 | dependencies = [ 771 | "pin-project-internal", 772 | ] 773 | 774 | [[package]] 775 | name = "pin-project-internal" 776 | version = "0.4.27" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895" 779 | dependencies = [ 780 | "proc-macro2", 781 | "quote", 782 | "syn", 783 | ] 784 | 785 | [[package]] 786 | name = "pin-project-lite" 787 | version = "0.1.11" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b" 790 | 791 | [[package]] 792 | name = "pin-project-lite" 793 | version = "0.2.4" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827" 796 | 797 | [[package]] 798 | name = "pin-utils" 799 | version = "0.1.0" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 802 | 803 | [[package]] 804 | name = "pkg-config" 805 | version = "0.3.19" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" 808 | 809 | [[package]] 810 | name = "ppv-lite86" 811 | version = "0.2.10" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 814 | 815 | [[package]] 816 | name = "proc-macro-crate" 817 | version = "0.1.5" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" 820 | dependencies = [ 821 | "toml", 822 | ] 823 | 824 | [[package]] 825 | name = "proc-macro-error" 826 | version = "1.0.4" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 829 | dependencies = [ 830 | "proc-macro-error-attr", 831 | "proc-macro2", 832 | "quote", 833 | "syn", 834 | "version_check", 835 | ] 836 | 837 | [[package]] 838 | name = "proc-macro-error-attr" 839 | version = "1.0.4" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 842 | dependencies = [ 843 | "proc-macro2", 844 | "quote", 845 | "version_check", 846 | ] 847 | 848 | [[package]] 849 | name = "proc-macro-hack" 850 | version = "0.5.19" 851 | source = "registry+https://github.com/rust-lang/crates.io-index" 852 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 853 | 854 | [[package]] 855 | name = "proc-macro-nested" 856 | version = "0.1.7" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" 859 | 860 | [[package]] 861 | name = "proc-macro2" 862 | version = "1.0.24" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 865 | dependencies = [ 866 | "unicode-xid", 867 | ] 868 | 869 | [[package]] 870 | name = "procfs" 871 | version = "0.8.1" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "c4a336c8310f4955f343935b9c11a30254d1ad8fad98ec257a4407a061a6fd49" 874 | dependencies = [ 875 | "bitflags", 876 | "byteorder", 877 | "chrono", 878 | "hex", 879 | "lazy_static", 880 | "libc", 881 | "libflate", 882 | ] 883 | 884 | [[package]] 885 | name = "quote" 886 | version = "1.0.8" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" 889 | dependencies = [ 890 | "proc-macro2", 891 | ] 892 | 893 | [[package]] 894 | name = "rand" 895 | version = "0.7.3" 896 | source = "registry+https://github.com/rust-lang/crates.io-index" 897 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 898 | dependencies = [ 899 | "getrandom", 900 | "libc", 901 | "rand_chacha", 902 | "rand_core", 903 | "rand_hc", 904 | ] 905 | 906 | [[package]] 907 | name = "rand_chacha" 908 | version = "0.2.2" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 911 | dependencies = [ 912 | "ppv-lite86", 913 | "rand_core", 914 | ] 915 | 916 | [[package]] 917 | name = "rand_core" 918 | version = "0.5.1" 919 | source = "registry+https://github.com/rust-lang/crates.io-index" 920 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 921 | dependencies = [ 922 | "getrandom", 923 | ] 924 | 925 | [[package]] 926 | name = "rand_hc" 927 | version = "0.2.0" 928 | source = "registry+https://github.com/rust-lang/crates.io-index" 929 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 930 | dependencies = [ 931 | "rand_core", 932 | ] 933 | 934 | [[package]] 935 | name = "redox_syscall" 936 | version = "0.2.5" 937 | source = "registry+https://github.com/rust-lang/crates.io-index" 938 | checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" 939 | dependencies = [ 940 | "bitflags", 941 | ] 942 | 943 | [[package]] 944 | name = "regex" 945 | version = "1.4.3" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" 948 | dependencies = [ 949 | "aho-corasick", 950 | "memchr", 951 | "regex-syntax", 952 | "thread_local", 953 | ] 954 | 955 | [[package]] 956 | name = "regex-automata" 957 | version = "0.1.9" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" 960 | dependencies = [ 961 | "byteorder", 962 | "regex-syntax", 963 | ] 964 | 965 | [[package]] 966 | name = "regex-syntax" 967 | version = "0.6.22" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" 970 | 971 | [[package]] 972 | name = "retry" 973 | version = "1.2.0" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "c15ef4789108d066d7fd85dcec330eab9b8e51244275922a9b7161afc4f46dda" 976 | dependencies = [ 977 | "rand", 978 | ] 979 | 980 | [[package]] 981 | name = "rle-decode-fast" 982 | version = "1.0.1" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac" 985 | 986 | [[package]] 987 | name = "ryu" 988 | version = "1.0.5" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 991 | 992 | [[package]] 993 | name = "scopeguard" 994 | version = "1.1.0" 995 | source = "registry+https://github.com/rust-lang/crates.io-index" 996 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 997 | 998 | [[package]] 999 | name = "serde" 1000 | version = "1.0.120" 1001 | source = "registry+https://github.com/rust-lang/crates.io-index" 1002 | checksum = "166b2349061381baf54a58e4b13c89369feb0ef2eaa57198899e2312aac30aab" 1003 | dependencies = [ 1004 | "serde_derive", 1005 | ] 1006 | 1007 | [[package]] 1008 | name = "serde_derive" 1009 | version = "1.0.120" 1010 | source = "registry+https://github.com/rust-lang/crates.io-index" 1011 | checksum = "0ca2a8cb5805ce9e3b95435e3765b7b553cecc762d938d409434338386cb5775" 1012 | dependencies = [ 1013 | "proc-macro2", 1014 | "quote", 1015 | "syn", 1016 | ] 1017 | 1018 | [[package]] 1019 | name = "serde_json" 1020 | version = "1.0.61" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a" 1023 | dependencies = [ 1024 | "itoa", 1025 | "ryu", 1026 | "serde", 1027 | ] 1028 | 1029 | [[package]] 1030 | name = "sharded-slab" 1031 | version = "0.1.1" 1032 | source = "registry+https://github.com/rust-lang/crates.io-index" 1033 | checksum = "79c719719ee05df97490f80a45acfc99e5a30ce98a1e4fb67aee422745ae14e3" 1034 | dependencies = [ 1035 | "lazy_static", 1036 | ] 1037 | 1038 | [[package]] 1039 | name = "signal-hook-registry" 1040 | version = "1.3.0" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" 1043 | dependencies = [ 1044 | "libc", 1045 | ] 1046 | 1047 | [[package]] 1048 | name = "slab" 1049 | version = "0.4.2" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 1052 | 1053 | [[package]] 1054 | name = "smallvec" 1055 | version = "1.6.1" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" 1058 | 1059 | [[package]] 1060 | name = "socket2" 1061 | version = "0.3.19" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" 1064 | dependencies = [ 1065 | "cfg-if 1.0.0", 1066 | "libc", 1067 | "winapi 0.3.9", 1068 | ] 1069 | 1070 | [[package]] 1071 | name = "strsim" 1072 | version = "0.8.0" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 1075 | 1076 | [[package]] 1077 | name = "structopt" 1078 | version = "0.3.21" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "5277acd7ee46e63e5168a80734c9f6ee81b1367a7d8772a2d765df2a3705d28c" 1081 | dependencies = [ 1082 | "clap", 1083 | "lazy_static", 1084 | "structopt-derive", 1085 | ] 1086 | 1087 | [[package]] 1088 | name = "structopt-derive" 1089 | version = "0.4.14" 1090 | source = "registry+https://github.com/rust-lang/crates.io-index" 1091 | checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90" 1092 | dependencies = [ 1093 | "heck", 1094 | "proc-macro-error", 1095 | "proc-macro2", 1096 | "quote", 1097 | "syn", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "syn" 1102 | version = "1.0.58" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5" 1105 | dependencies = [ 1106 | "proc-macro2", 1107 | "quote", 1108 | "unicode-xid", 1109 | ] 1110 | 1111 | [[package]] 1112 | name = "termcolor" 1113 | version = "1.1.2" 1114 | source = "registry+https://github.com/rust-lang/crates.io-index" 1115 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 1116 | dependencies = [ 1117 | "winapi-util", 1118 | ] 1119 | 1120 | [[package]] 1121 | name = "textwrap" 1122 | version = "0.11.0" 1123 | source = "registry+https://github.com/rust-lang/crates.io-index" 1124 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 1125 | dependencies = [ 1126 | "unicode-width", 1127 | ] 1128 | 1129 | [[package]] 1130 | name = "thiserror" 1131 | version = "1.0.23" 1132 | source = "registry+https://github.com/rust-lang/crates.io-index" 1133 | checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146" 1134 | dependencies = [ 1135 | "thiserror-impl", 1136 | ] 1137 | 1138 | [[package]] 1139 | name = "thiserror-impl" 1140 | version = "1.0.23" 1141 | source = "registry+https://github.com/rust-lang/crates.io-index" 1142 | checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1" 1143 | dependencies = [ 1144 | "proc-macro2", 1145 | "quote", 1146 | "syn", 1147 | ] 1148 | 1149 | [[package]] 1150 | name = "thread_local" 1151 | version = "1.1.0" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "bb9bc092d0d51e76b2b19d9d85534ffc9ec2db959a2523cdae0697e2972cd447" 1154 | dependencies = [ 1155 | "lazy_static", 1156 | ] 1157 | 1158 | [[package]] 1159 | name = "time" 1160 | version = "0.1.44" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 1163 | dependencies = [ 1164 | "libc", 1165 | "wasi 0.10.0+wasi-snapshot-preview1", 1166 | "winapi 0.3.9", 1167 | ] 1168 | 1169 | [[package]] 1170 | name = "tinyvec" 1171 | version = "1.1.1" 1172 | source = "registry+https://github.com/rust-lang/crates.io-index" 1173 | checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023" 1174 | dependencies = [ 1175 | "tinyvec_macros", 1176 | ] 1177 | 1178 | [[package]] 1179 | name = "tinyvec_macros" 1180 | version = "0.1.0" 1181 | source = "registry+https://github.com/rust-lang/crates.io-index" 1182 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1183 | 1184 | [[package]] 1185 | name = "toda" 1186 | version = "0.2.4" 1187 | dependencies = [ 1188 | "anyhow", 1189 | "async-trait", 1190 | "bitflags", 1191 | "derive_more", 1192 | "dynasmrt", 1193 | "env_logger", 1194 | "fuser", 1195 | "futures 0.3.12", 1196 | "glob", 1197 | "humantime-serde", 1198 | "itertools", 1199 | "jsonrpc-core", 1200 | "jsonrpc-core-client", 1201 | "jsonrpc-derive", 1202 | "jsonrpc-stdio-server", 1203 | "libc", 1204 | "nix", 1205 | "once_cell", 1206 | "procfs", 1207 | "rand", 1208 | "retry", 1209 | "serde", 1210 | "serde_json", 1211 | "slab", 1212 | "structopt", 1213 | "thiserror", 1214 | "time", 1215 | "tokio 0.2.24", 1216 | "tokio-util 0.6.9", 1217 | "tracing", 1218 | "tracing-futures", 1219 | "tracing-subscriber", 1220 | ] 1221 | 1222 | [[package]] 1223 | name = "tokio" 1224 | version = "0.2.24" 1225 | source = "registry+https://github.com/rust-lang/crates.io-index" 1226 | checksum = "099837d3464c16a808060bb3f02263b412f6fafcb5d01c533d309985fbeebe48" 1227 | dependencies = [ 1228 | "bytes 0.5.6", 1229 | "fnv", 1230 | "futures-core", 1231 | "iovec", 1232 | "lazy_static", 1233 | "libc", 1234 | "memchr", 1235 | "mio", 1236 | "mio-named-pipes", 1237 | "mio-uds", 1238 | "num_cpus", 1239 | "pin-project-lite 0.1.11", 1240 | "signal-hook-registry", 1241 | "slab", 1242 | "tokio-macros", 1243 | "winapi 0.3.9", 1244 | ] 1245 | 1246 | [[package]] 1247 | name = "tokio" 1248 | version = "1.15.0" 1249 | source = "registry+https://github.com/rust-lang/crates.io-index" 1250 | checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838" 1251 | dependencies = [ 1252 | "pin-project-lite 0.2.4", 1253 | ] 1254 | 1255 | [[package]] 1256 | name = "tokio-macros" 1257 | version = "0.2.6" 1258 | source = "registry+https://github.com/rust-lang/crates.io-index" 1259 | checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a" 1260 | dependencies = [ 1261 | "proc-macro2", 1262 | "quote", 1263 | "syn", 1264 | ] 1265 | 1266 | [[package]] 1267 | name = "tokio-util" 1268 | version = "0.3.1" 1269 | source = "registry+https://github.com/rust-lang/crates.io-index" 1270 | checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" 1271 | dependencies = [ 1272 | "bytes 0.5.6", 1273 | "futures-core", 1274 | "futures-sink", 1275 | "log", 1276 | "pin-project-lite 0.1.11", 1277 | "tokio 0.2.24", 1278 | ] 1279 | 1280 | [[package]] 1281 | name = "tokio-util" 1282 | version = "0.6.9" 1283 | source = "registry+https://github.com/rust-lang/crates.io-index" 1284 | checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" 1285 | dependencies = [ 1286 | "bytes 1.1.0", 1287 | "futures-core", 1288 | "futures-sink", 1289 | "log", 1290 | "pin-project-lite 0.2.4", 1291 | "tokio 1.15.0", 1292 | ] 1293 | 1294 | [[package]] 1295 | name = "toml" 1296 | version = "0.5.8" 1297 | source = "registry+https://github.com/rust-lang/crates.io-index" 1298 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" 1299 | dependencies = [ 1300 | "serde", 1301 | ] 1302 | 1303 | [[package]] 1304 | name = "tracing" 1305 | version = "0.1.22" 1306 | source = "registry+https://github.com/rust-lang/crates.io-index" 1307 | checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3" 1308 | dependencies = [ 1309 | "cfg-if 1.0.0", 1310 | "pin-project-lite 0.2.4", 1311 | "tracing-attributes", 1312 | "tracing-core", 1313 | ] 1314 | 1315 | [[package]] 1316 | name = "tracing-attributes" 1317 | version = "0.1.11" 1318 | source = "registry+https://github.com/rust-lang/crates.io-index" 1319 | checksum = "80e0ccfc3378da0cce270c946b676a376943f5cd16aeba64568e7939806f4ada" 1320 | dependencies = [ 1321 | "proc-macro2", 1322 | "quote", 1323 | "syn", 1324 | ] 1325 | 1326 | [[package]] 1327 | name = "tracing-core" 1328 | version = "0.1.17" 1329 | source = "registry+https://github.com/rust-lang/crates.io-index" 1330 | checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f" 1331 | dependencies = [ 1332 | "lazy_static", 1333 | ] 1334 | 1335 | [[package]] 1336 | name = "tracing-futures" 1337 | version = "0.2.4" 1338 | source = "registry+https://github.com/rust-lang/crates.io-index" 1339 | checksum = "ab7bb6f14721aa00656086e9335d363c5c8747bae02ebe32ea2c7dece5689b4c" 1340 | dependencies = [ 1341 | "pin-project", 1342 | "tracing", 1343 | ] 1344 | 1345 | [[package]] 1346 | name = "tracing-log" 1347 | version = "0.1.1" 1348 | source = "registry+https://github.com/rust-lang/crates.io-index" 1349 | checksum = "5e0f8c7178e13481ff6765bd169b33e8d554c5d2bbede5e32c356194be02b9b9" 1350 | dependencies = [ 1351 | "lazy_static", 1352 | "log", 1353 | "tracing-core", 1354 | ] 1355 | 1356 | [[package]] 1357 | name = "tracing-serde" 1358 | version = "0.1.2" 1359 | source = "registry+https://github.com/rust-lang/crates.io-index" 1360 | checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b" 1361 | dependencies = [ 1362 | "serde", 1363 | "tracing-core", 1364 | ] 1365 | 1366 | [[package]] 1367 | name = "tracing-subscriber" 1368 | version = "0.2.15" 1369 | source = "registry+https://github.com/rust-lang/crates.io-index" 1370 | checksum = "a1fa8f0c8f4c594e4fc9debc1990deab13238077271ba84dd853d54902ee3401" 1371 | dependencies = [ 1372 | "ansi_term 0.12.1", 1373 | "chrono", 1374 | "lazy_static", 1375 | "matchers", 1376 | "regex", 1377 | "serde", 1378 | "serde_json", 1379 | "sharded-slab", 1380 | "smallvec", 1381 | "thread_local", 1382 | "tracing", 1383 | "tracing-core", 1384 | "tracing-log", 1385 | "tracing-serde", 1386 | ] 1387 | 1388 | [[package]] 1389 | name = "unicode-bidi" 1390 | version = "0.3.4" 1391 | source = "registry+https://github.com/rust-lang/crates.io-index" 1392 | checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 1393 | dependencies = [ 1394 | "matches", 1395 | ] 1396 | 1397 | [[package]] 1398 | name = "unicode-normalization" 1399 | version = "0.1.17" 1400 | source = "registry+https://github.com/rust-lang/crates.io-index" 1401 | checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" 1402 | dependencies = [ 1403 | "tinyvec", 1404 | ] 1405 | 1406 | [[package]] 1407 | name = "unicode-segmentation" 1408 | version = "1.7.1" 1409 | source = "registry+https://github.com/rust-lang/crates.io-index" 1410 | checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" 1411 | 1412 | [[package]] 1413 | name = "unicode-width" 1414 | version = "0.1.8" 1415 | source = "registry+https://github.com/rust-lang/crates.io-index" 1416 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 1417 | 1418 | [[package]] 1419 | name = "unicode-xid" 1420 | version = "0.2.1" 1421 | source = "registry+https://github.com/rust-lang/crates.io-index" 1422 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 1423 | 1424 | [[package]] 1425 | name = "url" 1426 | version = "1.7.2" 1427 | source = "registry+https://github.com/rust-lang/crates.io-index" 1428 | checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" 1429 | dependencies = [ 1430 | "idna", 1431 | "matches", 1432 | "percent-encoding", 1433 | ] 1434 | 1435 | [[package]] 1436 | name = "users" 1437 | version = "0.11.0" 1438 | source = "registry+https://github.com/rust-lang/crates.io-index" 1439 | checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" 1440 | dependencies = [ 1441 | "libc", 1442 | "log", 1443 | ] 1444 | 1445 | [[package]] 1446 | name = "vec_map" 1447 | version = "0.8.2" 1448 | source = "registry+https://github.com/rust-lang/crates.io-index" 1449 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 1450 | 1451 | [[package]] 1452 | name = "version_check" 1453 | version = "0.9.2" 1454 | source = "registry+https://github.com/rust-lang/crates.io-index" 1455 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 1456 | 1457 | [[package]] 1458 | name = "wasi" 1459 | version = "0.9.0+wasi-snapshot-preview1" 1460 | source = "registry+https://github.com/rust-lang/crates.io-index" 1461 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 1462 | 1463 | [[package]] 1464 | name = "wasi" 1465 | version = "0.10.0+wasi-snapshot-preview1" 1466 | source = "registry+https://github.com/rust-lang/crates.io-index" 1467 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 1468 | 1469 | [[package]] 1470 | name = "winapi" 1471 | version = "0.2.8" 1472 | source = "registry+https://github.com/rust-lang/crates.io-index" 1473 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 1474 | 1475 | [[package]] 1476 | name = "winapi" 1477 | version = "0.3.9" 1478 | source = "registry+https://github.com/rust-lang/crates.io-index" 1479 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1480 | dependencies = [ 1481 | "winapi-i686-pc-windows-gnu", 1482 | "winapi-x86_64-pc-windows-gnu", 1483 | ] 1484 | 1485 | [[package]] 1486 | name = "winapi-build" 1487 | version = "0.1.1" 1488 | source = "registry+https://github.com/rust-lang/crates.io-index" 1489 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 1490 | 1491 | [[package]] 1492 | name = "winapi-i686-pc-windows-gnu" 1493 | version = "0.4.0" 1494 | source = "registry+https://github.com/rust-lang/crates.io-index" 1495 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1496 | 1497 | [[package]] 1498 | name = "winapi-util" 1499 | version = "0.1.5" 1500 | source = "registry+https://github.com/rust-lang/crates.io-index" 1501 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1502 | dependencies = [ 1503 | "winapi 0.3.9", 1504 | ] 1505 | 1506 | [[package]] 1507 | name = "winapi-x86_64-pc-windows-gnu" 1508 | version = "0.4.0" 1509 | source = "registry+https://github.com/rust-lang/crates.io-index" 1510 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1511 | 1512 | [[package]] 1513 | name = "ws2_32-sys" 1514 | version = "0.2.1" 1515 | source = "registry+https://github.com/rust-lang/crates.io-index" 1516 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 1517 | dependencies = [ 1518 | "winapi 0.2.8", 1519 | "winapi-build", 1520 | ] 1521 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "toda" 3 | version = "0.2.4" 4 | authors = ["Yang Keao "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | structopt = "0.3" 11 | nix = "0.18" 12 | anyhow = "1.0" 13 | fuser = {version = "0.6", features = ["abi-7-19"]} 14 | time = "0.1" 15 | libc = "0.2" 16 | async-trait = "0.1" 17 | tokio = {version = "0.2", features = ["rt-core", "rt-threaded", "sync", "fs", "time", "blocking", "macros", "full"]} 18 | tokio-util = "0.6" 19 | thiserror = "1.0" 20 | futures = "0.3" 21 | derive_more = "0.99.9" 22 | glob = "0.3" 23 | bitflags = "1.2" 24 | rand = "0.7" 25 | serde_json = "1.0" 26 | serde = { version = "1.0", features = ["derive"] } 27 | humantime-serde = "1.0" 28 | slab = "0.4" 29 | once_cell = "1.4" 30 | dynasmrt = "1.0.0" 31 | procfs = "0.8.0" 32 | itertools = "0.9.0" 33 | env_logger = "0.8" 34 | retry = "1.2.0" 35 | tracing = "0.1" 36 | tracing-futures = "0.2" 37 | tracing-subscriber = "0.2" 38 | jsonrpc-stdio-server = "17.0.0" 39 | jsonrpc-derive = "17.0.0" 40 | jsonrpc-core = "17.0.0" 41 | jsonrpc-core-client = "17.0.0" 42 | 43 | [profile.release] 44 | debug = true 45 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:experimental 2 | 3 | FROM debian:buster-slim 4 | 5 | ENV DEBIAN_FRONTEND noninteractive 6 | 7 | ARG HTTPS_PROXY 8 | ARG HTTP_PROXY 9 | 10 | ENV http_proxy $HTTP_PROXY 11 | ENV https_proxy $HTTPS_PROXY 12 | 13 | RUN apt-get update && apt-get install build-essential curl git pkg-config libfuse-dev fuse -y && rm -rf /var/lib/apt/lists/* 14 | 15 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --default-toolchain nightly-2021-12-23 -y 16 | ENV PATH "/root/.cargo/bin:${PATH}" 17 | 18 | RUN if [ -n "$HTTP_PROXY" ]; then echo "[http]\n\ 19 | proxy = \"${HTTP_PROXY}\"\n\ 20 | "\ 21 | > /root/.cargo/config ; fi 22 | 23 | COPY . /toda-build 24 | 25 | WORKDIR /toda-build 26 | 27 | ENV RUSTFLAGS "-Z relro-level=full" 28 | RUN --mount=type=cache,target=/toda-build/target \ 29 | --mount=type=cache,target=/root/.cargo/registry \ 30 | cargo build --release 31 | 32 | RUN --mount=type=cache,target=/toda-build/target \ 33 | cp /toda-build/target/release/toda /toda -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | example-image: image 2 | docker build -t io-example ./example 3 | 4 | volume: 5 | docker volume create io-example 6 | 7 | example: example-image volume 8 | docker run --privileged --ulimit nofile=5000:5000 -v io-example:/var/run/test -v /tmp:/tmp -it io-example /main-app 9 | 10 | debug: 11 | cargo build 12 | 13 | image: 14 | DOCKER_BUILDKIT=1 docker build --build-arg HTTP_PROXY=${HTTP_PROXY} --build-arg HTTPS_PROXY=${HTTPS_PROXY} . -t chaos-mesh/toda 15 | 16 | release: image 17 | docker run -v ${PWD}:/opt/mount:z --rm --entrypoint cp chaos-mesh/toda /toda /opt/mount/toda 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fchaos-mesh%2Ftoda.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fchaos-mesh%2Ftoda?ref=badge_shield) 2 | 3 | ## Notes: 4 | 5 | * Keep in mind that the result will be cached by system! 6 | 7 | If you set read error with a probability < 1, once the program reads successfully, no error will be returned until the cache misses. If you override the attributes with a probability < 1, the first lookup may decide the attributes for a long time (until the cache misses). 8 | 9 | But if you set probability == 1, which means the result will be the same all the time during the mount, there will be no problem. 10 | 11 | * Compile this binary with `-Z relro-level=full`, then it will load (mmap) all dependencies into memory at the beginning. 12 | 13 | * This program should be executed inside the target pid and mnt namespace 14 | 15 | ## Known Issues 16 | 17 | * Cannot work with too long path (near 4096 bytes) 18 | 19 | * Cannot `stat` a fd after it has been deleted 20 | 21 | ## License 22 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fchaos-mesh%2Ftoda.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fchaos-mesh%2Ftoda?ref=badge_large) 23 | -------------------------------------------------------------------------------- /config-examples/latency-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "update", 4 | "params": [ 5 | [ 6 | { 7 | "type": "latency", 8 | "path": "/var/lib/postgresql/data/**/*", 9 | "percent": 100, 10 | "latency": "10s" 11 | } 12 | ] 13 | ], 14 | "id": 1 15 | } -------------------------------------------------------------------------------- /config-examples/mistake-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "update", 4 | "params": [ 5 | [ 6 | { 7 | "type": "mistake", 8 | "path": "/var/test/**/*", 9 | "methods": [ 10 | "READ", 11 | "WRITE" 12 | ], 13 | "mistake": { 14 | "filling": "zero", 15 | "maxOccurrences": 1, 16 | "maxLength": 10000 17 | }, 18 | "percent": 100 19 | } 20 | ] 21 | ], 22 | "id": 1 23 | } -------------------------------------------------------------------------------- /config-examples/no-inject.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /example/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.12 as build-env 2 | 3 | WORKDIR /go/src/app 4 | ADD . /go/src/app 5 | 6 | RUN go get -d -v ./... 7 | 8 | RUN go build -o /go/bin/app 9 | 10 | FROM chaos-mesh/toda 11 | COPY --from=build-env /go/bin/app / 12 | COPY --from=build-env /go/bin/app /main-app 13 | 14 | ENV GOMAXPROCS 64 15 | CMD ["/app"] 16 | -------------------------------------------------------------------------------- /example/example.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chaos Mesh Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "fmt" 18 | "io/ioutil" 19 | "os" 20 | "strconv" 21 | "syscall" 22 | "time" 23 | "sync" 24 | ) 25 | 26 | func main() { 27 | content := make([]byte, 10) 28 | content = append(content, []byte("HELLO WORLD000")...) 29 | err := ioutil.WriteFile("/var/run/test/test", content, 0644) 30 | if err != nil { 31 | fmt.Printf("Error: %v+", err) 32 | return 33 | } 34 | 35 | originalLength := len([]byte("HELLO WORLD")) 36 | 37 | var wg sync.WaitGroup 38 | for i := 0; i <= 100; i++ { 39 | go func() { 40 | wg.Add(1) 41 | for { 42 | var fVec []*os.File 43 | var mMap [][]byte 44 | 45 | for i := 0; i < 20; i++ { 46 | f, err := os.OpenFile("/var/run/test/test", os.O_RDWR, 0666) 47 | if err != nil { 48 | fmt.Printf("Error: %v+", err) 49 | return 50 | } 51 | err = f.Truncate(1024) 52 | if err != nil { 53 | fmt.Printf("Error: %v\n", err) 54 | continue 55 | } 56 | _, err = f.Seek(10, os.SEEK_SET) 57 | if err != nil { 58 | fmt.Printf("Error: %v\n", err) 59 | continue 60 | } 61 | 62 | fVec = append(fVec, f) 63 | data, err := syscall.Mmap(int(f.Fd()), 0, 10+originalLength+3, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) 64 | if err != nil { 65 | fmt.Printf("Error: %v+", err) 66 | return 67 | } 68 | mMap = append(mMap, data) 69 | 70 | f = fVec[i] 71 | data = mMap[i] 72 | 73 | count := strconv.Itoa(i) 74 | for pos, char := range count { 75 | if pos < 3 { 76 | data[10+originalLength+pos] = byte(char) 77 | } 78 | } 79 | 80 | time.Sleep(time.Second) 81 | 82 | buf := make([]byte, originalLength+len(count)) 83 | n, err := f.Read(buf) 84 | if err != nil { 85 | fmt.Printf("Error: %v\n", err) 86 | continue 87 | } 88 | fmt.Printf("%v %d bytes: %s\n", time.Now(), n, string(buf[:n])) 89 | } 90 | 91 | for i := 0; i < 20; i++ { 92 | fVec[i].Close() 93 | syscall.Munmap(mMap[i]) 94 | } 95 | } 96 | }() 97 | } 98 | 99 | wg.Wait() 100 | } 101 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly-2021-12-23 -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | reorder_imports = true 2 | imports_granularity = "Module" 3 | group_imports = "StdExternalCrate" 4 | unstable_features = true -------------------------------------------------------------------------------- /src/fuse_device.rs: -------------------------------------------------------------------------------- 1 | use nix::sys::stat::{makedev, mknod, Mode, SFlag}; 2 | use nix::Error as NixError; 3 | 4 | pub fn mkfuse_node() -> anyhow::Result<()> { 5 | let mode = unsafe { Mode::from_bits_unchecked(0o666) }; 6 | let dev = makedev(10, 229); 7 | match mknod("/dev/fuse", SFlag::S_IFCHR, mode, dev) { 8 | Ok(()) => Ok(()), 9 | Err(NixError::Sys(errno)) => { 10 | if errno == nix::errno::Errno::EEXIST { 11 | Ok(()) 12 | } else { 13 | Err(NixError::from_errno(errno).into()) 14 | } 15 | } 16 | Err(err) => Err(err.into()), 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/hookfs/async_fs.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::OsString; 2 | use std::fmt::Debug; 3 | use std::future::Future; 4 | use std::path::{Path, PathBuf}; 5 | use std::sync::Arc; 6 | 7 | use async_trait::async_trait; 8 | use fuser::*; 9 | use tracing::trace_span; 10 | use tracing_futures::Instrument; 11 | 12 | use super::errors::Result; 13 | use super::reply::*; 14 | use super::runtime::spawn; 15 | 16 | pub fn spawn_reply(id: u64, reply: R, f: F) 17 | where 18 | F: Future> + Send + 'static, 19 | R: FsReply + Send + 'static, 20 | V: Debug, 21 | { 22 | spawn(async move { 23 | let result = f.instrument(trace_span!("request", id)).await; 24 | reply.reply(result); 25 | }); 26 | } 27 | 28 | #[async_trait] 29 | pub trait AsyncFileSystemImpl: Send + Sync { 30 | fn init(&self) -> Result<()>; 31 | 32 | fn destroy(&self); 33 | 34 | async fn lookup(&self, parent: u64, name: OsString) -> Result; 35 | 36 | async fn forget(&self, ino: u64, nlookup: u64); 37 | 38 | async fn getattr(&self, ino: u64) -> Result; 39 | 40 | async fn setattr( 41 | &self, 42 | ino: u64, 43 | mode: Option, 44 | uid: Option, 45 | gid: Option, 46 | size: Option, 47 | atime: Option, 48 | mtime: Option, 49 | ctime: Option, 50 | fh: Option, 51 | crtime: Option, 52 | chgtime: Option, 53 | bkuptime: Option, 54 | flags: Option, 55 | ) -> Result; 56 | 57 | async fn readlink(&self, ino: u64) -> Result; 58 | 59 | async fn mknod( 60 | &self, 61 | parent: u64, 62 | name: OsString, 63 | mode: u32, 64 | umask: u32, 65 | rdev: u32, 66 | uid: u32, 67 | gid: u32, 68 | ) -> Result; 69 | 70 | async fn mkdir( 71 | &self, 72 | parent: u64, 73 | name: OsString, 74 | mode: u32, 75 | umask: u32, 76 | uid: u32, 77 | gid: u32, 78 | ) -> Result; 79 | 80 | async fn unlink(&self, parent: u64, name: OsString) -> Result<()>; 81 | 82 | async fn rmdir(&self, parent: u64, name: OsString) -> Result<()>; 83 | 84 | async fn symlink( 85 | &self, 86 | parent: u64, 87 | name: OsString, 88 | link: PathBuf, 89 | uid: u32, 90 | gid: u32, 91 | ) -> Result; 92 | 93 | async fn rename( 94 | &self, 95 | parent: u64, 96 | name: OsString, 97 | newparent: u64, 98 | newname: OsString, 99 | flags: u32, 100 | ) -> Result<()>; 101 | 102 | async fn link(&self, ino: u64, newparent: u64, newname: OsString) -> Result; 103 | 104 | async fn open(&self, ino: u64, flags: i32) -> Result; 105 | 106 | async fn read( 107 | &self, 108 | ino: u64, 109 | fh: u64, 110 | offset: i64, 111 | size: u32, 112 | flags: i32, 113 | lock_owner: Option, 114 | ) -> Result; 115 | 116 | async fn write( 117 | &self, 118 | ino: u64, 119 | fh: u64, 120 | offset: i64, 121 | data: Vec, 122 | write_flags: u32, 123 | flags: i32, 124 | lock_owner: Option, 125 | ) -> Result; 126 | 127 | async fn flush(&self, ino: u64, fh: u64, lock_owner: u64) -> Result<()>; 128 | 129 | async fn release( 130 | &self, 131 | ino: u64, 132 | fh: u64, 133 | flags: i32, 134 | lock_owner: Option, 135 | flush: bool, 136 | ) -> Result<()>; 137 | 138 | async fn fsync(&self, ino: u64, fh: u64, datasync: bool) -> Result<()>; 139 | 140 | async fn opendir(&self, ino: u64, flags: i32) -> Result; 141 | 142 | async fn readdir( 143 | &self, 144 | ino: u64, 145 | fh: u64, 146 | offset: i64, 147 | reply: &mut ReplyDirectory, 148 | ) -> Result<()>; 149 | 150 | async fn releasedir(&self, ino: u64, fh: u64, flags: i32) -> Result<()>; 151 | 152 | async fn fsyncdir(&self, ino: u64, fh: u64, datasync: bool) -> Result<()>; 153 | 154 | async fn statfs(&self, ino: u64) -> Result; 155 | 156 | async fn setxattr( 157 | &self, 158 | ino: u64, 159 | name: OsString, 160 | value: Vec, 161 | flags: i32, 162 | position: u32, 163 | ) -> Result<()>; 164 | 165 | async fn getxattr(&self, ino: u64, name: OsString, size: u32) -> Result; 166 | 167 | async fn listxattr(&self, ino: u64, size: u32) -> Result; 168 | 169 | async fn removexattr(&self, ino: u64, name: OsString) -> Result<()>; 170 | 171 | async fn access(&self, ino: u64, mask: i32) -> Result<()>; 172 | 173 | async fn create( 174 | &self, 175 | parent: u64, 176 | name: OsString, 177 | mode: u32, 178 | umask: u32, 179 | flags: i32, 180 | uid: u32, 181 | gid: u32, 182 | ) -> Result; 183 | 184 | async fn getlk( 185 | &self, 186 | ino: u64, 187 | fh: u64, 188 | lock_owner: u64, 189 | start: u64, 190 | end: u64, 191 | typ: i32, 192 | pid: u32, 193 | ) -> Result; 194 | 195 | async fn setlk( 196 | &self, 197 | ino: u64, 198 | fh: u64, 199 | lock_owner: u64, 200 | start: u64, 201 | end: u64, 202 | typ: i32, 203 | pid: u32, 204 | sleep: bool, 205 | ) -> Result<()>; 206 | 207 | async fn bmap(&self, ino: u64, blocksize: u32, idx: u64, reply: ReplyBmap); 208 | } 209 | 210 | pub struct AsyncFileSystem(Arc); 211 | 212 | impl From> for AsyncFileSystem { 213 | fn from(inner: Arc) -> Self { 214 | Self(inner) 215 | } 216 | } 217 | 218 | impl Debug for AsyncFileSystem { 219 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 220 | self.0.fmt(f) 221 | } 222 | } 223 | 224 | impl Filesystem for AsyncFileSystem { 225 | fn init( 226 | &mut self, 227 | _req: &fuser::Request, 228 | _config: &mut fuser::KernelConfig, 229 | ) -> std::result::Result<(), nix::libc::c_int> { 230 | self.0.init().map_err(|err| err.into()) 231 | } 232 | 233 | fn destroy(&mut self, _req: &fuser::Request) { 234 | self.0.destroy() 235 | } 236 | 237 | fn lookup(&mut self, req: &Request, parent: u64, name: &std::ffi::OsStr, reply: ReplyEntry) { 238 | let async_impl = self.0.clone(); 239 | let name = name.to_owned(); 240 | spawn_reply(req.unique(), reply, async move { 241 | async_impl.lookup(parent, name).await 242 | }); 243 | } 244 | 245 | fn forget(&mut self, _req: &Request, ino: u64, nlookup: u64) { 246 | let async_impl = self.0.clone(); 247 | 248 | // TODO: union the spawn function for request without reply 249 | spawn(async move { 250 | async_impl.forget(ino, nlookup).await; 251 | }); 252 | } 253 | 254 | fn getattr(&mut self, req: &Request, ino: u64, reply: ReplyAttr) { 255 | let async_impl = self.0.clone(); 256 | spawn_reply( 257 | req.unique(), 258 | reply, 259 | async move { async_impl.getattr(ino).await }, 260 | ); 261 | } 262 | 263 | fn setattr( 264 | &mut self, 265 | req: &Request, 266 | ino: u64, 267 | mode: Option, 268 | uid: Option, 269 | gid: Option, 270 | size: Option, 271 | atime: Option, 272 | mtime: Option, 273 | ctime: Option, 274 | fh: Option, 275 | crtime: Option, 276 | chgtime: Option, 277 | bkuptime: Option, 278 | flags: Option, 279 | reply: ReplyAttr, 280 | ) { 281 | let async_impl = self.0.clone(); 282 | spawn_reply(req.unique(), reply, async move { 283 | async_impl 284 | .setattr( 285 | ino, mode, uid, gid, size, atime, mtime, ctime, fh, crtime, chgtime, bkuptime, 286 | flags, 287 | ) 288 | .await 289 | }); 290 | } 291 | 292 | fn readlink(&mut self, req: &Request, ino: u64, reply: ReplyData) { 293 | let async_impl = self.0.clone(); 294 | spawn_reply(req.unique(), reply, async move { 295 | async_impl.readlink(ino).await 296 | }); 297 | } 298 | fn mknod( 299 | &mut self, 300 | req: &Request, 301 | parent: u64, 302 | name: &std::ffi::OsStr, 303 | mode: u32, 304 | umask: u32, 305 | rdev: u32, 306 | reply: ReplyEntry, 307 | ) { 308 | let async_impl = self.0.clone(); 309 | let name = name.to_owned(); 310 | let uid = req.uid(); 311 | let gid = req.gid(); 312 | spawn_reply(req.unique(), reply, async move { 313 | async_impl 314 | .mknod(parent, name, mode, umask, rdev, uid, gid) 315 | .await 316 | }); 317 | } 318 | fn mkdir( 319 | &mut self, 320 | req: &Request, 321 | parent: u64, 322 | name: &std::ffi::OsStr, 323 | mode: u32, 324 | umask: u32, 325 | reply: ReplyEntry, 326 | ) { 327 | let uid = req.uid(); 328 | let gid = req.gid(); 329 | 330 | let async_impl = self.0.clone(); 331 | let name = name.to_owned(); 332 | spawn_reply(req.unique(), reply, async move { 333 | async_impl.mkdir(parent, name, mode, umask, uid, gid).await 334 | }); 335 | } 336 | fn unlink(&mut self, req: &Request, parent: u64, name: &std::ffi::OsStr, reply: ReplyEmpty) { 337 | let async_impl = self.0.clone(); 338 | let name = name.to_owned(); 339 | spawn_reply(req.unique(), reply, async move { 340 | async_impl.unlink(parent, name).await 341 | }); 342 | } 343 | fn rmdir(&mut self, req: &Request, parent: u64, name: &std::ffi::OsStr, reply: ReplyEmpty) { 344 | let async_impl = self.0.clone(); 345 | let name = name.to_owned(); 346 | spawn_reply(req.unique(), reply, async move { 347 | async_impl.rmdir(parent, name).await 348 | }); 349 | } 350 | fn symlink( 351 | &mut self, 352 | req: &Request, 353 | parent: u64, 354 | name: &std::ffi::OsStr, 355 | link: &Path, 356 | reply: ReplyEntry, 357 | ) { 358 | let async_impl = self.0.clone(); 359 | let name = name.to_owned(); 360 | let link = link.to_owned(); 361 | let uid = req.uid(); 362 | let gid = req.gid(); 363 | spawn_reply(req.unique(), reply, async move { 364 | async_impl.symlink(parent, name, link, uid, gid).await 365 | }); 366 | } 367 | fn rename( 368 | &mut self, 369 | req: &Request, 370 | parent: u64, 371 | name: &std::ffi::OsStr, 372 | newparent: u64, 373 | newname: &std::ffi::OsStr, 374 | flags: u32, 375 | reply: ReplyEmpty, 376 | ) { 377 | let async_impl = self.0.clone(); 378 | let name = name.to_owned(); 379 | let newname = newname.to_owned(); 380 | spawn_reply(req.unique(), reply, async move { 381 | async_impl 382 | .rename(parent, name, newparent, newname, flags) 383 | .await 384 | }); 385 | } 386 | fn link( 387 | &mut self, 388 | req: &Request, 389 | ino: u64, 390 | newparent: u64, 391 | newname: &std::ffi::OsStr, 392 | reply: ReplyEntry, 393 | ) { 394 | let async_impl = self.0.clone(); 395 | let newname = newname.to_owned(); 396 | spawn_reply(req.unique(), reply, async move { 397 | async_impl.link(ino, newparent, newname).await 398 | }); 399 | } 400 | fn open(&mut self, req: &Request, ino: u64, flags: i32, reply: ReplyOpen) { 401 | let async_impl = self.0.clone(); 402 | spawn_reply(req.unique(), reply, async move { 403 | async_impl.open(ino, flags).await 404 | }); 405 | } 406 | fn read( 407 | &mut self, 408 | req: &Request, 409 | ino: u64, 410 | fh: u64, 411 | offset: i64, 412 | size: u32, 413 | flags: i32, 414 | lock_owner: Option, 415 | reply: ReplyData, 416 | ) { 417 | let async_impl = self.0.clone(); 418 | spawn_reply(req.unique(), reply, async move { 419 | async_impl 420 | .read(ino, fh, offset, size, flags, lock_owner) 421 | .await 422 | }); 423 | } 424 | fn write( 425 | &mut self, 426 | req: &Request, 427 | ino: u64, 428 | fh: u64, 429 | offset: i64, 430 | data: &[u8], 431 | write_flags: u32, 432 | flags: i32, 433 | lock_owner: Option, 434 | reply: ReplyWrite, 435 | ) { 436 | let async_impl = self.0.clone(); 437 | let data = data.to_owned(); 438 | spawn_reply(req.unique(), reply, async move { 439 | async_impl 440 | .write(ino, fh, offset, data, write_flags, flags, lock_owner) 441 | .await 442 | }); 443 | } 444 | fn flush(&mut self, req: &Request, ino: u64, fh: u64, lock_owner: u64, reply: ReplyEmpty) { 445 | let async_impl = self.0.clone(); 446 | spawn_reply(req.unique(), reply, async move { 447 | async_impl.flush(ino, fh, lock_owner).await 448 | }); 449 | } 450 | fn release( 451 | &mut self, 452 | req: &Request, 453 | ino: u64, 454 | fh: u64, 455 | flags: i32, 456 | lock_owner: Option, 457 | flush: bool, 458 | reply: ReplyEmpty, 459 | ) { 460 | let async_impl = self.0.clone(); 461 | spawn_reply(req.unique(), reply, async move { 462 | async_impl.release(ino, fh, flags, lock_owner, flush).await 463 | }); 464 | } 465 | fn fsync(&mut self, req: &Request, ino: u64, fh: u64, datasync: bool, reply: ReplyEmpty) { 466 | let async_impl = self.0.clone(); 467 | spawn_reply(req.unique(), reply, async move { 468 | async_impl.fsync(ino, fh, datasync).await 469 | }); 470 | } 471 | fn opendir(&mut self, req: &Request, ino: u64, flags: i32, reply: ReplyOpen) { 472 | let async_impl = self.0.clone(); 473 | spawn_reply(req.unique(), reply, async move { 474 | async_impl.opendir(ino, flags).await 475 | }); 476 | } 477 | fn readdir( 478 | &mut self, 479 | _req: &Request, 480 | ino: u64, 481 | fh: u64, 482 | offset: i64, 483 | mut reply: ReplyDirectory, 484 | ) { 485 | let async_impl = self.0.clone(); 486 | spawn(async move { 487 | match async_impl.readdir(ino, fh, offset, &mut reply).await { 488 | Ok(_) => reply.ok(), 489 | Err(err) => reply.error(err.into()), 490 | } 491 | }); 492 | } 493 | fn releasedir(&mut self, req: &Request, ino: u64, fh: u64, flags: i32, reply: ReplyEmpty) { 494 | let async_impl = self.0.clone(); 495 | spawn_reply(req.unique(), reply, async move { 496 | async_impl.releasedir(ino, fh, flags).await 497 | }); 498 | } 499 | fn fsyncdir(&mut self, req: &Request, ino: u64, fh: u64, datasync: bool, reply: ReplyEmpty) { 500 | let async_impl = self.0.clone(); 501 | spawn_reply(req.unique(), reply, async move { 502 | async_impl.fsyncdir(ino, fh, datasync).await 503 | }); 504 | } 505 | fn statfs(&mut self, req: &Request, ino: u64, reply: ReplyStatfs) { 506 | let async_impl = self.0.clone(); 507 | spawn_reply( 508 | req.unique(), 509 | reply, 510 | async move { async_impl.statfs(ino).await }, 511 | ); 512 | } 513 | fn setxattr( 514 | &mut self, 515 | req: &Request, 516 | ino: u64, 517 | name: &std::ffi::OsStr, 518 | value: &[u8], 519 | flags: i32, 520 | position: u32, 521 | reply: ReplyEmpty, 522 | ) { 523 | let async_impl = self.0.clone(); 524 | let name = name.to_owned(); 525 | let value = value.to_owned(); 526 | spawn_reply(req.unique(), reply, async move { 527 | async_impl.setxattr(ino, name, value, flags, position).await 528 | }); 529 | } 530 | fn getxattr( 531 | &mut self, 532 | req: &Request, 533 | ino: u64, 534 | name: &std::ffi::OsStr, 535 | size: u32, 536 | reply: ReplyXattr, 537 | ) { 538 | let async_impl = self.0.clone(); 539 | let name = name.to_owned(); 540 | spawn_reply(req.unique(), reply, async move { 541 | async_impl.getxattr(ino, name, size).await 542 | }); 543 | } 544 | fn listxattr(&mut self, req: &Request, ino: u64, size: u32, reply: ReplyXattr) { 545 | let async_impl = self.0.clone(); 546 | spawn_reply(req.unique(), reply, async move { 547 | async_impl.listxattr(ino, size).await 548 | }); 549 | } 550 | fn removexattr(&mut self, req: &Request, ino: u64, name: &std::ffi::OsStr, reply: ReplyEmpty) { 551 | let async_impl = self.0.clone(); 552 | let name = name.to_owned(); 553 | spawn_reply(req.unique(), reply, async move { 554 | async_impl.removexattr(ino, name).await 555 | }); 556 | } 557 | fn access(&mut self, req: &Request, ino: u64, mask: i32, reply: ReplyEmpty) { 558 | let async_impl = self.0.clone(); 559 | spawn_reply(req.unique(), reply, async move { 560 | async_impl.access(ino, mask).await 561 | }); 562 | } 563 | fn create( 564 | &mut self, 565 | req: &Request, 566 | parent: u64, 567 | name: &std::ffi::OsStr, 568 | mode: u32, 569 | umask: u32, 570 | flags: i32, 571 | reply: ReplyCreate, 572 | ) { 573 | let uid = req.uid(); 574 | let gid = req.gid(); 575 | 576 | let async_impl = self.0.clone(); 577 | let name = name.to_owned(); 578 | spawn_reply(req.unique(), reply, async move { 579 | async_impl 580 | .create(parent, name, mode, umask, flags, uid, gid) 581 | .await 582 | }); 583 | } 584 | fn getlk( 585 | &mut self, 586 | req: &Request, 587 | ino: u64, 588 | fh: u64, 589 | lock_owner: u64, 590 | start: u64, 591 | end: u64, 592 | typ: i32, 593 | pid: u32, 594 | reply: ReplyLock, 595 | ) { 596 | let async_impl = self.0.clone(); 597 | spawn_reply(req.unique(), reply, async move { 598 | async_impl 599 | .getlk(ino, fh, lock_owner, start, end, typ, pid) 600 | .await 601 | }); 602 | } 603 | fn setlk( 604 | &mut self, 605 | req: &Request, 606 | ino: u64, 607 | fh: u64, 608 | lock_owner: u64, 609 | start: u64, 610 | end: u64, 611 | typ: i32, 612 | pid: u32, 613 | sleep: bool, 614 | reply: ReplyEmpty, 615 | ) { 616 | let async_impl = self.0.clone(); 617 | spawn_reply(req.unique(), reply, async move { 618 | async_impl 619 | .setlk(ino, fh, lock_owner, start, end, typ, pid, sleep) 620 | .await 621 | }); 622 | } 623 | fn bmap(&mut self, _req: &Request, ino: u64, blocksize: u32, idx: u64, reply: ReplyBmap) { 624 | let async_impl = self.0.clone(); 625 | spawn(async move { 626 | async_impl.bmap(ino, blocksize, idx, reply).await; 627 | }); 628 | } 629 | } 630 | -------------------------------------------------------------------------------- /src/hookfs/errors.rs: -------------------------------------------------------------------------------- 1 | use nix::errno::Errno; 2 | use nix::Error; 3 | use thiserror::Error; 4 | use tracing::error; 5 | 6 | #[derive(Error, Debug)] 7 | pub enum HookFsError { 8 | #[error("errno {0}")] 9 | Sys(Errno), 10 | 11 | #[error("cannot find inode({inode})")] 12 | InodeNotFound { inode: u64 }, 13 | 14 | #[error("cannot find fh({fh})")] 15 | FhNotFound { fh: u64 }, 16 | 17 | #[error("invalid string")] 18 | InvalidStr, 19 | 20 | #[error("unknown file type")] 21 | UnknownFileType, 22 | 23 | #[error("strip prefix error")] 24 | StripPrefixError(#[from] std::path::StripPrefixError), 25 | 26 | #[error("unknown error")] 27 | UnknownError, 28 | } 29 | 30 | pub type Result = std::result::Result; 31 | 32 | impl HookFsError { 33 | pub fn last() -> HookFsError { 34 | HookFsError::from(nix::Error::last()) 35 | } 36 | } 37 | 38 | impl From for HookFsError { 39 | fn from(err: Error) -> HookFsError { 40 | // TODO: match more error types 41 | match err { 42 | Error::Sys(errno) => HookFsError::Sys(errno), 43 | _ => { 44 | error!("unknown error {:?}", err); 45 | HookFsError::UnknownError 46 | } 47 | } 48 | } 49 | } 50 | 51 | impl From for HookFsError { 52 | fn from(_: std::ffi::NulError) -> HookFsError { 53 | HookFsError::InvalidStr 54 | } 55 | } 56 | 57 | impl From for HookFsError { 58 | fn from(err: std::io::Error) -> HookFsError { 59 | error!("unknown error {:?}", err); 60 | HookFsError::UnknownError 61 | } 62 | } 63 | 64 | impl From for HookFsError { 65 | fn from(err: tokio::task::JoinError) -> HookFsError { 66 | error!("unknown error {:?}", err); 67 | HookFsError::UnknownError 68 | } 69 | } 70 | 71 | impl From for libc::c_int { 72 | fn from(err: HookFsError) -> libc::c_int { 73 | use HookFsError::*; 74 | 75 | match err { 76 | Sys(errno) => errno as i32, 77 | InodeNotFound { inode: _ } => libc::EFAULT, 78 | FhNotFound { fh: _ } => libc::EFAULT, 79 | UnknownFileType => libc::EINVAL, 80 | InvalidStr => libc::EINVAL, 81 | _ => libc::EFAULT, 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/hookfs/reply.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | use std::time::Duration; 3 | 4 | use fuser::*; 5 | use tracing::{debug, error, trace}; 6 | 7 | use super::errors::Result; 8 | 9 | const TTL: Duration = Duration::from_secs(0); 10 | 11 | #[derive(Debug)] 12 | pub enum Reply<'a> { 13 | Entry(&'a mut Entry), 14 | Open(&'a mut Open), 15 | Attr(&'a mut Attr), 16 | Data(&'a mut Data), 17 | StatFs(&'a mut StatFs), 18 | Write(&'a mut Write), 19 | Create(&'a mut Create), 20 | _Lock(&'a mut Lock), 21 | Xattr(&'a mut Xattr), 22 | } 23 | 24 | #[derive(Debug)] 25 | pub struct Entry { 26 | pub stat: FileAttr, 27 | pub generation: u64, 28 | } 29 | impl Entry { 30 | pub fn new(stat: FileAttr, generation: u64) -> Self { 31 | Self { stat, generation } 32 | } 33 | } 34 | 35 | #[derive(Debug)] 36 | pub struct Open { 37 | pub fh: u64, 38 | pub flags: i32, 39 | } 40 | impl Open { 41 | pub fn new(fh: u64, flags: i32) -> Self { 42 | Self { fh, flags } 43 | } 44 | } 45 | 46 | #[derive(Debug)] 47 | pub struct Attr { 48 | pub attr: FileAttr, 49 | } 50 | impl Attr { 51 | pub fn new(attr: FileAttr) -> Self { 52 | Self { attr } 53 | } 54 | } 55 | 56 | #[derive(Debug)] 57 | pub struct Data { 58 | pub data: Vec, 59 | } 60 | impl Data { 61 | pub fn new(data: Vec) -> Self { 62 | Self { data } 63 | } 64 | } 65 | 66 | #[derive(Debug)] 67 | pub struct StatFs { 68 | pub blocks: u64, 69 | pub bfree: u64, 70 | pub bavail: u64, 71 | pub files: u64, 72 | pub ffree: u64, 73 | pub bsize: u32, 74 | pub namelen: u32, 75 | pub frsize: u32, 76 | } 77 | impl StatFs { 78 | pub fn new( 79 | blocks: u64, 80 | bfree: u64, 81 | bavail: u64, 82 | files: u64, 83 | ffree: u64, 84 | bsize: u32, 85 | namelen: u32, 86 | frsize: u32, 87 | ) -> Self { 88 | Self { 89 | blocks, 90 | bfree, 91 | bavail, 92 | files, 93 | ffree, 94 | bsize, 95 | namelen, 96 | frsize, 97 | } 98 | } 99 | } 100 | 101 | #[derive(Debug)] 102 | pub struct Write { 103 | pub size: u32, 104 | } 105 | impl Write { 106 | pub fn new(size: u32) -> Self { 107 | Self { size } 108 | } 109 | } 110 | 111 | #[derive(Debug)] 112 | pub struct Create { 113 | pub attr: FileAttr, 114 | pub generation: u64, 115 | pub fh: u64, 116 | pub flags: i32, 117 | } 118 | impl Create { 119 | pub fn new(attr: FileAttr, generation: u64, fh: u64, flags: i32) -> Self { 120 | Self { 121 | attr, 122 | generation, 123 | fh, 124 | flags, 125 | } 126 | } 127 | } 128 | 129 | #[derive(Debug)] 130 | pub struct Lock { 131 | pub start: u64, 132 | pub end: u64, 133 | pub typ: i32, 134 | pub pid: u32, 135 | } 136 | 137 | impl Lock { 138 | pub fn _new(start: u64, end: u64, typ: i32, pid: u32) -> Self { 139 | Self { 140 | start, 141 | end, 142 | typ, 143 | pid, 144 | } 145 | } 146 | } 147 | 148 | #[derive(Debug)] 149 | pub enum Xattr { 150 | Data { data: Vec }, 151 | Size { size: u32 }, 152 | } 153 | impl Xattr { 154 | pub fn data(data: Vec) -> Self { 155 | Xattr::Data { data } 156 | } 157 | pub fn size(size: u32) -> Self { 158 | Xattr::Size { size } 159 | } 160 | } 161 | 162 | pub trait FsReply: Sized { 163 | fn reply_ok(self, item: T); 164 | fn reply_err(self, err: libc::c_int); 165 | 166 | fn reply(self, result: Result) { 167 | match result { 168 | Ok(item) => { 169 | trace!("ok"); 170 | self.reply_ok(item) 171 | } 172 | Err(err) => { 173 | debug!("err. reply with {}", err); 174 | 175 | let err = err.into(); 176 | if err == -1 { 177 | error!("returned -1"); 178 | } 179 | self.reply_err(err) 180 | } 181 | } 182 | } 183 | } 184 | 185 | impl FsReply for ReplyEntry { 186 | fn reply_ok(self, item: Entry) { 187 | self.entry(&TTL, &item.stat, item.generation); 188 | } 189 | fn reply_err(self, err: libc::c_int) { 190 | self.error(err); 191 | } 192 | } 193 | 194 | impl FsReply for ReplyOpen { 195 | fn reply_ok(self, item: Open) { 196 | self.opened(item.fh, item.flags as u32); 197 | } 198 | fn reply_err(self, err: libc::c_int) { 199 | self.error(err); 200 | } 201 | } 202 | 203 | impl FsReply for ReplyAttr { 204 | fn reply_ok(self, item: Attr) { 205 | self.attr(&TTL, &item.attr); 206 | } 207 | fn reply_err(self, err: libc::c_int) { 208 | self.error(err); 209 | } 210 | } 211 | 212 | impl FsReply for ReplyData { 213 | fn reply_ok(self, item: Data) { 214 | self.data(item.data.as_slice()); 215 | } 216 | fn reply_err(self, err: libc::c_int) { 217 | self.error(err); 218 | } 219 | } 220 | 221 | impl FsReply for ReplyStatfs { 222 | fn reply_ok(self, item: StatFs) { 223 | self.statfs( 224 | item.blocks, 225 | item.bfree, 226 | item.bavail, 227 | item.files, 228 | item.ffree, 229 | item.bsize, 230 | item.namelen, 231 | item.frsize, 232 | ) 233 | } 234 | fn reply_err(self, err: libc::c_int) { 235 | self.error(err); 236 | } 237 | } 238 | 239 | impl FsReply for ReplyWrite { 240 | fn reply_ok(self, item: Write) { 241 | self.written(item.size); 242 | } 243 | fn reply_err(self, err: libc::c_int) { 244 | self.error(err); 245 | } 246 | } 247 | 248 | impl FsReply for ReplyCreate { 249 | fn reply_ok(self, item: Create) { 250 | self.created( 251 | &TTL, 252 | &item.attr, 253 | item.generation, 254 | item.fh, 255 | item.flags as u32, 256 | ); 257 | } 258 | fn reply_err(self, err: libc::c_int) { 259 | self.error(err); 260 | } 261 | } 262 | 263 | impl FsReply for ReplyLock { 264 | fn reply_ok(self, item: Lock) { 265 | self.locked(item.start, item.end, item.typ, item.pid); 266 | } 267 | fn reply_err(self, err: libc::c_int) { 268 | self.error(err); 269 | } 270 | } 271 | 272 | impl FsReply for ReplyXattr { 273 | fn reply_ok(self, item: Xattr) { 274 | use Xattr::*; 275 | match item { 276 | Data { data } => self.data(data.as_slice()), 277 | Size { size } => self.size(size), 278 | } 279 | } 280 | fn reply_err(self, err: libc::c_int) { 281 | self.error(err); 282 | } 283 | } 284 | 285 | impl FsReply<()> for ReplyEmpty { 286 | fn reply_ok(self, _: ()) { 287 | self.ok(); 288 | } 289 | fn reply_err(self, err: libc::c_int) { 290 | self.error(err); 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /src/hookfs/runtime.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::sync::RwLock; 3 | 4 | use once_cell::sync::Lazy; 5 | use tokio::runtime::Runtime; 6 | use tokio::task::JoinHandle; 7 | use tracing::trace; 8 | 9 | pub static RUNTIME: Lazy>> = Lazy::new(|| { 10 | trace!("build tokio runtime"); 11 | 12 | RwLock::new(Some( 13 | tokio::runtime::Builder::new() 14 | .threaded_scheduler() 15 | .thread_name("toda") 16 | .enable_all() 17 | .build() 18 | .unwrap(), 19 | )) 20 | }); 21 | 22 | pub fn spawn(future: F) -> JoinHandle 23 | where 24 | F: Future + Send + 'static, 25 | F::Output: Send + 'static, 26 | { 27 | if let Some(runtime) = &*RUNTIME.read().unwrap() { 28 | return runtime.spawn(future); 29 | } 30 | unreachable!() 31 | } 32 | 33 | pub fn spawn_blocking(func: F) -> JoinHandle 34 | where 35 | R: Send + 'static, 36 | F: FnOnce() -> R + Send + 'static, 37 | { 38 | if let Some(runtime) = &*RUNTIME.read().unwrap() { 39 | return runtime.handle().spawn_blocking(func); 40 | } 41 | unreachable!() 42 | } 43 | -------------------------------------------------------------------------------- /src/hookfs/utils.rs: -------------------------------------------------------------------------------- 1 | use fuser::{FileAttr, FileType, TimeOrNow}; 2 | use libc::{UTIME_NOW, UTIME_OMIT}; 3 | use nix::dir; 4 | 5 | use super::{Error, Result}; 6 | 7 | pub fn convert_filetype(file_type: dir::Type) -> FileType { 8 | match file_type { 9 | dir::Type::Fifo => FileType::NamedPipe, 10 | dir::Type::CharacterDevice => FileType::CharDevice, 11 | dir::Type::Directory => FileType::Directory, 12 | dir::Type::BlockDevice => FileType::BlockDevice, 13 | dir::Type::File => FileType::RegularFile, 14 | dir::Type::Symlink => FileType::Symlink, 15 | dir::Type::Socket => FileType::Socket, 16 | } 17 | } 18 | 19 | pub fn system_time(sec: i64, nsec: i64) -> std::time::SystemTime { 20 | std::time::UNIX_EPOCH 21 | + std::time::Duration::from_secs(sec as u64) 22 | + std::time::Duration::from_nanos(nsec as u64) 23 | } 24 | 25 | // convert_libc_stat_to_fuse_stat converts file stat from libc form into fuse form. 26 | // returns None if the file type is unknown. 27 | pub fn convert_libc_stat_to_fuse_stat(stat: libc::stat) -> Result { 28 | let kind = match stat.st_mode & libc::S_IFMT { 29 | libc::S_IFBLK => FileType::BlockDevice, 30 | libc::S_IFCHR => FileType::CharDevice, 31 | libc::S_IFDIR => FileType::Directory, 32 | libc::S_IFIFO => FileType::NamedPipe, 33 | libc::S_IFLNK => FileType::Symlink, 34 | libc::S_IFREG => FileType::RegularFile, 35 | libc::S_IFSOCK => FileType::Socket, 36 | _ => return Err(Error::UnknownFileType), 37 | }; 38 | Ok(FileAttr { 39 | ino: stat.st_ino, 40 | size: stat.st_size as u64, 41 | blocks: stat.st_blocks as u64, 42 | atime: system_time(stat.st_atime, stat.st_atime_nsec), 43 | mtime: system_time(stat.st_mtime, stat.st_mtime_nsec), 44 | ctime: system_time(stat.st_ctime, stat.st_ctime_nsec), 45 | kind, 46 | perm: (stat.st_mode & 0o7777) as u16, 47 | nlink: stat.st_nlink as u32, 48 | uid: stat.st_uid, 49 | gid: stat.st_gid, 50 | rdev: stat.st_rdev as u32, 51 | blksize: stat.st_blksize as u32, 52 | padding: 0, // unknown attr 53 | crtime: system_time(0, 0), // It's macOS only 54 | flags: 0, // It's macOS only 55 | }) 56 | } 57 | 58 | pub fn convert_time(t: Option) -> libc::timespec { 59 | match t { 60 | Some(TimeOrNow::SpecificTime(t)) => { 61 | let nano_unit = 1e9 as i64; 62 | 63 | let t = t.duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos() as i64; 64 | libc::timespec { 65 | tv_sec: t / nano_unit, 66 | tv_nsec: t % nano_unit, 67 | } 68 | } 69 | Some(TimeOrNow::Now) => libc::timespec { 70 | tv_sec: 0, 71 | tv_nsec: UTIME_NOW, 72 | }, 73 | None => libc::timespec { 74 | tv_sec: 0, 75 | tv_nsec: UTIME_OMIT, 76 | }, 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/injector/attr_override_injector.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use async_trait::async_trait; 4 | use fuser::{FileAttr, FileType}; 5 | use tracing::{debug, trace}; 6 | 7 | use super::injector_config::{AttrOverrideConfig, FileType as ConfigFileType, FilterConfig}; 8 | use super::{filter, Injector}; 9 | use crate::hookfs::Result; 10 | 11 | #[derive(Debug)] 12 | pub struct AttrOverrideInjector { 13 | filter: filter::Filter, 14 | 15 | ino: Option, 16 | size: Option, 17 | blocks: Option, 18 | atime: Option, 19 | mtime: Option, 20 | ctime: Option, 21 | kind: Option, 22 | perm: Option, 23 | nlink: Option, 24 | uid: Option, 25 | gid: Option, 26 | rdev: Option, 27 | } 28 | 29 | #[async_trait] 30 | impl Injector for AttrOverrideInjector { 31 | async fn inject(&self, _: &filter::Method, _: &Path) -> Result<()> { 32 | Ok(()) 33 | } 34 | 35 | fn inject_attr(&self, attr: &mut FileAttr, path: &Path) { 36 | // AttrOverrideInjector should always pass method filter 37 | if !self.filter.filter(&filter::Method::LOOKUP, path) { 38 | return; 39 | } 40 | 41 | if let Some(ino) = self.ino { 42 | trace!("overriding ino"); 43 | attr.ino = ino 44 | } 45 | if let Some(size) = self.size { 46 | trace!("overriding size"); 47 | attr.size = size 48 | } 49 | if let Some(blocks) = self.blocks { 50 | trace!("overriding block"); 51 | attr.blocks = blocks 52 | } 53 | if let Some(atime) = self.atime { 54 | trace!("overriding atime"); 55 | attr.atime = atime 56 | } 57 | if let Some(mtime) = self.mtime { 58 | trace!("overriding mtime"); 59 | attr.mtime = mtime 60 | } 61 | if let Some(ctime) = self.ctime { 62 | trace!("overriding ctime"); 63 | attr.ctime = ctime 64 | } 65 | if let Some(kind) = self.kind { 66 | trace!("overriding kind"); 67 | attr.kind = kind 68 | } 69 | if let Some(perm) = self.perm { 70 | trace!("overriding perm"); 71 | attr.perm = perm 72 | } 73 | if let Some(nlink) = self.nlink { 74 | trace!("overriding nlink"); 75 | attr.nlink = nlink 76 | } 77 | if let Some(uid) = self.uid { 78 | trace!("overriding uid"); 79 | attr.uid = uid 80 | } 81 | if let Some(gid) = self.gid { 82 | trace!("overriding gid"); 83 | attr.gid = gid 84 | } 85 | if let Some(rdev) = self.rdev { 86 | trace!("overriding rdev"); 87 | attr.rdev = rdev 88 | } 89 | } 90 | } 91 | 92 | impl AttrOverrideInjector { 93 | pub fn build(conf: AttrOverrideConfig) -> anyhow::Result { 94 | debug!("build attr override injector"); 95 | 96 | let filter = filter::Filter::build(FilterConfig { 97 | path: Some(conf.path), 98 | methods: None, 99 | percent: conf.percent, 100 | })?; 101 | 102 | let atime = conf.atime; 103 | let mtime = conf.mtime; 104 | let ctime = conf.ctime; 105 | 106 | let kind = conf.kind.map(|item| match item { 107 | ConfigFileType::Directory => FileType::Directory, 108 | ConfigFileType::NamedPipe => FileType::NamedPipe, 109 | ConfigFileType::RegularFile => FileType::RegularFile, 110 | ConfigFileType::Socket => FileType::Socket, 111 | ConfigFileType::Symlink => FileType::Symlink, 112 | ConfigFileType::CharDevice => FileType::CharDevice, 113 | ConfigFileType::BlockDevice => FileType::BlockDevice, 114 | }); 115 | 116 | Ok(Self { 117 | filter, 118 | 119 | ino: conf.ino, 120 | size: conf.size, 121 | blocks: conf.blocks, 122 | atime, 123 | mtime, 124 | ctime, 125 | kind, 126 | perm: conf.perm, 127 | nlink: conf.nlink, 128 | uid: conf.uid, 129 | gid: conf.gid, 130 | rdev: conf.rdev, 131 | }) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/injector/fault_injector.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use async_trait::async_trait; 4 | use nix::errno::Errno; 5 | use rand::Rng; 6 | use tracing::{debug, trace}; 7 | 8 | use super::injector_config::FaultsConfig; 9 | use super::{filter, Injector}; 10 | use crate::hookfs::{Error, Result}; 11 | 12 | #[derive(Debug)] 13 | pub struct FaultInjector { 14 | filter: filter::Filter, 15 | 16 | errnos: Vec<(Errno, i32)>, 17 | 18 | sum: i32, 19 | } 20 | 21 | #[async_trait] 22 | impl Injector for FaultInjector { 23 | async fn inject(&self, method: &filter::Method, path: &Path) -> Result<()> { 24 | debug!("test filter"); 25 | if self.filter.filter(method, path) { 26 | debug!("inject io fault"); 27 | let mut rng = rand::thread_rng(); 28 | let attempt: f64 = rng.gen(); 29 | let mut attempt = (attempt * (self.sum as f64)) as i32; 30 | 31 | for (err, p) in self.errnos.iter() { 32 | attempt -= p; 33 | 34 | if attempt < 0 { 35 | debug!("return with error {}", err); 36 | return Err(Error::Sys(*err)); 37 | } 38 | } 39 | } 40 | 41 | Ok(()) 42 | } 43 | } 44 | 45 | impl FaultInjector { 46 | pub fn build(conf: FaultsConfig) -> anyhow::Result { 47 | trace!("build fault injector"); 48 | 49 | let errnos: Vec<_> = conf 50 | .faults 51 | .iter() 52 | .map(|item| (Errno::from_i32(item.errno), item.weight)) 53 | .collect(); 54 | 55 | let sum = errnos.iter().fold(0, |acc, w| acc + w.1); 56 | Ok(Self { 57 | filter: filter::Filter::build(conf.filter)?, 58 | errnos, 59 | sum, 60 | }) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/injector/filter.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | use std::path::Path; 3 | 4 | use anyhow::{anyhow, Error, Result}; 5 | use bitflags::bitflags; 6 | use glob::{MatchOptions, Pattern}; 7 | use rand::Rng; 8 | use tracing::{info, trace}; 9 | 10 | use super::injector_config::FilterConfig; 11 | 12 | bitflags! { 13 | pub struct Method: u32 { 14 | const LOOKUP = 1; 15 | const FORGET = 1<<1; 16 | const GETATTR = 1<<2; 17 | const SETATTR = 1<<3; 18 | const READLINK = 1<<4; 19 | const MKNOD = 1<<5; 20 | const MKDIR = 1<<6; 21 | const UNLINK = 1<<7; 22 | const RMDIR = 1<<8; 23 | const SYMLINK = 1<<9; 24 | const RENAME = 1<<10; 25 | const LINK = 1<<11; 26 | const OPEN = 1<<12; 27 | const READ = 1<<13; 28 | const WRITE = 1<<14; 29 | const FLUSH = 1<<15; 30 | const RELEASE = 1<<16; 31 | const FSYNC = 1<<17; 32 | const OPENDIR = 1<<18; 33 | const READDIR = 1<<19; 34 | const RELEASEDIR = 1<<20; 35 | const FSYNCDIR = 1<<21; 36 | const STATFS = 1<<22; 37 | const SETXATTR = 1<<23; 38 | const GETXATTR = 1<<24; 39 | const LISTXATTR = 1<<25; 40 | const REMOVEXATTR = 1<<26; 41 | const ACCESS = 1<<27; 42 | const CREATE = 1<<28; 43 | const GETLK = 1<<29; 44 | const SETLK = 1<<30; 45 | const BMAP = 1<<31; 46 | } 47 | } 48 | 49 | impl TryFrom<&str> for Method { 50 | fn try_from(s: &str) -> Result { 51 | match s.to_lowercase().as_str() { 52 | "lookup" => Ok(Method::LOOKUP), 53 | "forget" => Ok(Method::FORGET), 54 | "getattr" => Ok(Method::GETATTR), 55 | "setattr" => Ok(Method::SETATTR), 56 | "readlink" => Ok(Method::READLINK), 57 | "mknod" => Ok(Method::MKNOD), 58 | "mkdir" => Ok(Method::MKDIR), 59 | "unlink" => Ok(Method::UNLINK), 60 | "rmdir" => Ok(Method::RMDIR), 61 | "symlink" => Ok(Method::SYMLINK), 62 | "rename" => Ok(Method::RENAME), 63 | "link" => Ok(Method::LINK), 64 | "open" => Ok(Method::OPEN), 65 | "read" => Ok(Method::READ), 66 | "write" => Ok(Method::WRITE), 67 | "flush" => Ok(Method::FLUSH), 68 | "release" => Ok(Method::RELEASE), 69 | "fsync" => Ok(Method::FSYNC), 70 | "opendir" => Ok(Method::OPENDIR), 71 | "readdir" => Ok(Method::READDIR), 72 | "releasedir" => Ok(Method::RELEASEDIR), 73 | "fsyncdir" => Ok(Method::FSYNCDIR), 74 | "statfs" => Ok(Method::STATFS), 75 | "setxattr" => Ok(Method::SETXATTR), 76 | "getxattr" => Ok(Method::GETXATTR), 77 | "listxattr" => Ok(Method::LISTXATTR), 78 | "removexattr" => Ok(Method::REMOVEXATTR), 79 | "access" => Ok(Method::ACCESS), 80 | "create" => Ok(Method::CREATE), 81 | "getlk" => Ok(Method::GETLK), 82 | "setlk" => Ok(Method::SETLK), 83 | "bmap" => Ok(Method::BMAP), 84 | _ => Err(anyhow!("")), 85 | } 86 | } 87 | type Error = Error; 88 | } 89 | 90 | #[derive(Debug)] 91 | pub struct Filter { 92 | path_filter: Option, 93 | methods: Method, 94 | probability: f64, 95 | } 96 | 97 | impl Filter { 98 | pub fn build(conf: FilterConfig) -> Result { 99 | info!("build filter"); 100 | let methods = conf 101 | .methods 102 | .filter(|methods| !methods.is_empty()) 103 | .map(|methods| { 104 | methods 105 | .iter() 106 | .filter_map(|method| Method::try_from(method.as_str()).ok()) 107 | .fold(Method::empty(), |methods, method| methods | method) 108 | }) 109 | .unwrap_or(Method::all()); 110 | 111 | let path_filter = conf 112 | .path.and_then(|path| -> Option { 113 | if !path.is_empty() { 114 | Pattern::new(&path).ok() 115 | } else { 116 | None 117 | } 118 | }); 119 | Ok(Self { 120 | path_filter, 121 | methods, 122 | probability: conf.percent as f64 / 100f64, 123 | }) 124 | } 125 | 126 | pub fn filter(&self, method: &Method, path: &Path) -> bool { 127 | let mut rng = rand::thread_rng(); 128 | let p: f64 = rng.gen(); 129 | 130 | let match_path = match &self.path_filter { 131 | Some(filter) => filter.matches_path_with( 132 | path, 133 | MatchOptions { 134 | case_sensitive: true, 135 | require_literal_separator: true, 136 | require_literal_leading_dot: false, 137 | }, 138 | ), 139 | None => true, 140 | }; 141 | let match_method = !(self.methods & *method).is_empty(); 142 | let match_probability = p < self.probability; 143 | trace!("path filter: {}", match_path); 144 | trace!("method filter: {}", match_method); 145 | trace!("probability: {}", match_probability); 146 | 147 | match_path && match_method && match_probability 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/injector/injector_config.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Serialize, Deserialize, Clone, Debug)] 6 | #[serde(tag = "type")] 7 | #[serde(rename_all = "camelCase")] 8 | pub enum InjectorConfig { 9 | Latency(LatencyConfig), 10 | Fault(FaultsConfig), 11 | AttrOverride(AttrOverrideConfig), 12 | Mistake(MistakesConfig), 13 | } 14 | 15 | #[derive(Serialize, Deserialize, Clone, Debug)] 16 | #[serde(rename_all = "camelCase")] 17 | pub struct LatencyConfig { 18 | #[serde(flatten)] 19 | pub filter: FilterConfig, 20 | #[serde(with = "humantime_serde")] 21 | pub latency: Duration, 22 | } 23 | 24 | #[derive(Serialize, Deserialize, Clone, Debug)] 25 | #[serde(rename_all = "camelCase")] 26 | pub struct FaultsConfig { 27 | #[serde(flatten)] 28 | pub filter: FilterConfig, 29 | 30 | pub faults: Vec, 31 | } 32 | 33 | #[derive(Serialize, Deserialize, Clone, Debug)] 34 | #[serde(rename_all = "camelCase")] 35 | pub struct FilterConfig { 36 | pub path: Option, 37 | pub methods: Option>, 38 | pub percent: i32, 39 | } 40 | 41 | #[derive(Serialize, Deserialize, Clone, Debug)] 42 | #[serde(rename_all = "camelCase")] 43 | pub struct FaultConfig { 44 | pub errno: i32, 45 | pub weight: i32, 46 | } 47 | 48 | #[derive(Serialize, Deserialize, Clone, Debug)] 49 | #[serde(rename_all = "camelCase")] 50 | pub struct AttrOverrideConfig { 51 | pub path: String, 52 | pub percent: i32, 53 | 54 | pub ino: Option, 55 | pub size: Option, 56 | pub blocks: Option, 57 | pub atime: Option, 58 | pub mtime: Option, 59 | pub ctime: Option, 60 | pub kind: Option, 61 | pub perm: Option, 62 | pub nlink: Option, 63 | pub uid: Option, 64 | pub gid: Option, 65 | pub rdev: Option, 66 | } 67 | 68 | #[derive(Serialize, Deserialize, Clone, Debug)] 69 | #[serde(rename_all = "camelCase")] 70 | pub enum FileType { 71 | NamedPipe, 72 | CharDevice, 73 | BlockDevice, 74 | Directory, 75 | RegularFile, 76 | Symlink, 77 | Socket, 78 | } 79 | 80 | #[derive(Serialize, Deserialize, Clone, Debug)] 81 | #[serde(rename_all = "camelCase")] 82 | pub struct Timespec { 83 | pub sec: i64, 84 | pub nsec: i32, 85 | } 86 | 87 | #[derive(Serialize, Deserialize, Clone, Debug)] 88 | #[serde(rename_all = "camelCase")] 89 | pub enum MistakeType { 90 | Zero, 91 | Random, 92 | } 93 | 94 | #[derive(Serialize, Deserialize, Clone, Debug)] 95 | #[serde(rename_all = "camelCase")] 96 | pub struct MistakeConfig { 97 | pub filling: MistakeType, 98 | pub max_length: usize, 99 | pub max_occurrences: usize, 100 | } 101 | 102 | #[derive(Serialize, Deserialize, Clone, Debug)] 103 | #[serde(rename_all = "camelCase")] 104 | pub struct MistakesConfig { 105 | pub mistake: MistakeConfig, 106 | #[serde(flatten)] 107 | pub filter: FilterConfig, 108 | } 109 | -------------------------------------------------------------------------------- /src/injector/latency_injector.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | use std::time::Duration; 3 | 4 | use async_trait::async_trait; 5 | use tokio::time::delay_for; 6 | use tokio::select; 7 | use tokio_util::sync::CancellationToken; 8 | use tracing::{debug, trace}; 9 | 10 | use super::injector_config::LatencyConfig; 11 | use super::{filter, Injector}; 12 | use crate::hookfs::Result; 13 | 14 | #[derive(Debug)] 15 | pub struct LatencyInjector { 16 | latency: Duration, 17 | filter: filter::Filter, 18 | cancel_token: CancellationToken, 19 | } 20 | 21 | #[async_trait] 22 | impl Injector for LatencyInjector { 23 | async fn inject(&self, method: &filter::Method, path: &Path) -> Result<()> { 24 | trace!("test for filter"); 25 | if self.filter.filter(method, path) { 26 | let token = self.cancel_token.clone(); 27 | let latency = self.latency; 28 | debug!("inject io delay {:?}", latency); 29 | 30 | select! { 31 | _ = delay_for(latency) => {} 32 | _ = token.cancelled() => { 33 | debug!("cancelled"); 34 | } 35 | } 36 | 37 | debug!("latency finished"); 38 | } 39 | 40 | Ok(()) 41 | } 42 | 43 | fn interrupt(&self) { 44 | debug!("interrupt latency"); 45 | self.cancel_token.cancel(); 46 | } 47 | } 48 | 49 | impl LatencyInjector { 50 | pub fn build(conf: LatencyConfig) -> anyhow::Result { 51 | trace!("build latency injector"); 52 | 53 | Ok(Self { 54 | latency: conf.latency, 55 | filter: filter::Filter::build(conf.filter)?, 56 | cancel_token: CancellationToken::new(), 57 | }) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/injector/mistake_injector.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::{max, min}; 2 | use std::path::Path; 3 | 4 | use async_trait::async_trait; 5 | use rand::Rng; 6 | use tracing::{debug, trace}; 7 | 8 | use super::injector_config::{MistakeConfig, MistakeType, MistakesConfig}; 9 | use super::{filter, Injector}; 10 | use crate::hookfs::{Reply, Result}; 11 | 12 | #[derive(Debug)] 13 | pub struct MistakeInjector { 14 | mistake: MistakeConfig, 15 | filter: filter::Filter, 16 | } 17 | 18 | #[async_trait] 19 | impl Injector for MistakeInjector { 20 | async fn inject(&self, _: &filter::Method, _: &Path) -> Result<()> { 21 | debug!("MI:Injecting"); 22 | Ok(()) 23 | } 24 | 25 | fn inject_reply(&self, method: &super::Method, path: &Path, reply: &mut Reply) -> Result<()> { 26 | if self.filter.filter(method, path) { 27 | debug!("MI:Injecting reply"); 28 | if let Reply::Data(data) = reply { 29 | let data = &mut data.data; 30 | self.handle(data)?; 31 | } 32 | } 33 | Ok(()) 34 | } 35 | 36 | fn inject_write_data(&self, path: &Path, data: &mut Vec) -> Result<()> { 37 | if self.filter.filter(&super::Method::WRITE, path) { 38 | debug!("MI:Injecting write data"); 39 | self.handle(data)?; 40 | } 41 | Ok(()) 42 | } 43 | } 44 | 45 | impl MistakeInjector { 46 | pub fn build(conf: MistakesConfig) -> anyhow::Result { 47 | trace!("build mistake injector"); 48 | Ok(Self { 49 | mistake: conf.mistake, 50 | filter: filter::Filter::build(conf.filter)?, 51 | }) 52 | } 53 | pub fn handle(&self, data: &mut Vec) -> Result<()> { 54 | trace!("sabotage data"); 55 | let mut rng = rand::thread_rng(); 56 | let data_length = data.len(); 57 | let mistake = &self.mistake; 58 | let occurrence = match mistake.max_occurrences { 59 | 0 => 0, 60 | mo => rng.gen_range(1, mo + 1), 61 | }; 62 | for _ in 0..occurrence { 63 | let pos = rng.gen_range(0, max(data_length, 1)); 64 | let length = match min(mistake.max_length, data_length - pos) { 65 | 0 => 0, 66 | l => rng.gen_range(1, l + 1), 67 | }; 68 | debug!( 69 | "Setting index [{},{}) to {:?}", 70 | pos, 71 | pos + length, 72 | mistake.filling 73 | ); 74 | match mistake.filling { 75 | MistakeType::Zero => { 76 | for item in data.iter_mut().skip(pos).take(length) { 77 | *item = 0; 78 | } 79 | } 80 | MistakeType::Random => rng.fill(&mut data[pos..pos + length]), 81 | } 82 | } 83 | Ok(()) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/injector/mod.rs: -------------------------------------------------------------------------------- 1 | mod attr_override_injector; 2 | mod fault_injector; 3 | mod filter; 4 | mod injector_config; 5 | mod latency_injector; 6 | mod mistake_injector; 7 | mod multi_injector; 8 | 9 | use std::path::Path; 10 | 11 | use async_trait::async_trait; 12 | pub use filter::Method; 13 | use fuser::FileAttr; 14 | pub use injector_config::InjectorConfig; 15 | pub use multi_injector::MultiInjector; 16 | 17 | use crate::hookfs::{Reply, Result}; 18 | 19 | #[async_trait] 20 | pub trait Injector: Send + Sync + std::fmt::Debug { 21 | async fn inject(&self, method: &filter::Method, path: &Path) -> Result<()>; 22 | 23 | fn inject_reply( 24 | &self, 25 | _method: &filter::Method, 26 | _path: &Path, 27 | _reply: &mut Reply, 28 | ) -> Result<()> { 29 | Ok(()) 30 | } 31 | fn inject_write_data(&self, _path: &Path, _data: &mut Vec) -> Result<()> { 32 | Ok(()) 33 | } 34 | 35 | fn inject_attr(&self, _attr: &mut FileAttr, _path: &Path) {} 36 | 37 | fn interrupt(&self) {} 38 | } 39 | -------------------------------------------------------------------------------- /src/injector/multi_injector.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use async_trait::async_trait; 4 | use fuser::FileAttr; 5 | use tracing::trace; 6 | 7 | use super::attr_override_injector::AttrOverrideInjector; 8 | use super::fault_injector::FaultInjector; 9 | use super::injector_config::InjectorConfig; 10 | use super::latency_injector::LatencyInjector; 11 | use super::mistake_injector::MistakeInjector; 12 | use super::{filter, Injector}; 13 | use crate::hookfs::{Reply, Result}; 14 | 15 | #[derive(Debug)] 16 | pub struct MultiInjector { 17 | injectors: Vec>, 18 | } 19 | 20 | impl MultiInjector { 21 | pub fn build(conf: Vec) -> anyhow::Result { 22 | trace!("build multiinjectors"); 23 | let mut injectors = Vec::new(); 24 | 25 | for injector in conf.into_iter() { 26 | let injector = match injector { 27 | InjectorConfig::Fault(faults) => { 28 | (box FaultInjector::build(faults)?) as Box 29 | } 30 | InjectorConfig::Latency(latency) => { 31 | (box LatencyInjector::build(latency)?) as Box 32 | } 33 | InjectorConfig::AttrOverride(attr_override) => { 34 | (box AttrOverrideInjector::build(attr_override)?) as Box 35 | } 36 | InjectorConfig::Mistake(mistakes) => { 37 | (box MistakeInjector::build(mistakes)?) as Box 38 | } 39 | }; 40 | injectors.push(injector) 41 | } 42 | 43 | Ok(Self { injectors }) 44 | } 45 | } 46 | 47 | #[async_trait] 48 | impl Injector for MultiInjector { 49 | async fn inject(&self, method: &filter::Method, path: &Path) -> Result<()> { 50 | for injector in self.injectors.iter() { 51 | injector.inject(method, path).await? 52 | } 53 | 54 | Ok(()) 55 | } 56 | 57 | fn inject_reply(&self, method: &filter::Method, path: &Path, reply: &mut Reply) -> Result<()> { 58 | for injector in self.injectors.iter() { 59 | injector.inject_reply(method, path, reply)? 60 | } 61 | 62 | Ok(()) 63 | } 64 | 65 | fn inject_attr(&self, attr: &mut FileAttr, path: &Path) { 66 | for injector in self.injectors.iter() { 67 | injector.inject_attr(attr, path) 68 | } 69 | } 70 | 71 | fn inject_write_data(&self, path: &Path, data: &mut Vec) -> Result<()> { 72 | for injector in self.injectors.iter() { 73 | injector.inject_write_data(path, data)?; 74 | } 75 | Ok(()) 76 | } 77 | 78 | fn interrupt(&self) { 79 | for injector in self.injectors.iter() { 80 | injector.interrupt(); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/jsonrpc.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{mpsc, Arc, Mutex}; 2 | 3 | use jsonrpc_derive::rpc; 4 | use jsonrpc_stdio_server::jsonrpc_core::*; 5 | use jsonrpc_stdio_server::ServerBuilder; 6 | use tracing::{info, trace}; 7 | 8 | use crate::hookfs::HookFs; 9 | use crate::injector::{InjectorConfig, MultiInjector}; 10 | 11 | #[derive(Debug, Clone, PartialEq, Eq)] 12 | pub enum Comm { 13 | Shutdown = 0, 14 | } 15 | 16 | pub async fn start_server(config: RpcImpl) { 17 | info!("Starting jsonrpc server"); 18 | let server = new_server(config); 19 | let server = server.build(); 20 | server.await; 21 | } 22 | 23 | pub fn new_server(config: RpcImpl) -> ServerBuilder { 24 | info!("Creating jsonrpc server"); 25 | let io = new_handler(config); 26 | ServerBuilder::new(io) 27 | } 28 | 29 | pub fn new_handler(config: RpcImpl) -> IoHandler { 30 | info!("Creating jsonrpc handler"); 31 | let mut io = IoHandler::new(); 32 | io.extend_with(config.to_delegate()); 33 | io 34 | } 35 | 36 | #[rpc] 37 | pub trait Rpc { 38 | #[rpc(name = "get_status")] 39 | fn get_status(&self, inst: String) -> Result; 40 | #[rpc(name = "update")] 41 | fn update(&self, config: Vec) -> Result; 42 | } 43 | 44 | pub struct RpcImpl { 45 | status: Mutex>, 46 | tx: Mutex>, 47 | hookfs: Option>, 48 | } 49 | 50 | impl RpcImpl { 51 | pub fn new( 52 | status: Mutex>, 53 | tx: Mutex>, 54 | hookfs: Option>, 55 | ) -> Self { 56 | Self { status, tx, hookfs } 57 | } 58 | } 59 | 60 | impl Drop for RpcImpl { 61 | fn drop(&mut self) { 62 | trace!("Dropping jrpc handler"); 63 | } 64 | } 65 | 66 | impl Rpc for RpcImpl { 67 | fn get_status(&self, _inst: String) -> Result { 68 | info!("rpc get_status called"); 69 | match &*self.status.lock().unwrap() { 70 | Ok(_) => Ok("ok".to_string()), 71 | Err(e) => { 72 | let tx = &self.tx.lock().unwrap(); 73 | tx.send(Comm::Shutdown) 74 | .expect("Send through channel failed"); 75 | Ok(e.to_string()) 76 | } 77 | } 78 | } 79 | fn update(&self, config: Vec) -> Result { 80 | info!("rpc update called"); 81 | if let Err(e) = &*self.status.lock().unwrap() { 82 | return Ok(e.to_string()); 83 | } 84 | let injectors = MultiInjector::build(config); 85 | if let Err(e) = &injectors { 86 | return Ok(e.to_string()); 87 | } 88 | futures::executor::block_on(async { 89 | let hookfs = self.hookfs.as_ref().unwrap(); 90 | let mut current_injectors = hookfs.injector.write().await; 91 | *current_injectors = injectors.unwrap(); 92 | }); 93 | Ok("ok".to_string()) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chaos Mesh Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | #![feature(box_syntax)] 15 | #![feature(async_closure)] 16 | #![feature(vec_into_raw_parts)] 17 | #![feature(atomic_mut_ptr)] 18 | #![feature(drain_filter)] 19 | #![allow(clippy::or_fun_call)] 20 | #![allow(clippy::too_many_arguments)] 21 | 22 | pub mod fuse_device; 23 | pub mod hookfs; 24 | pub mod injector; 25 | pub mod jsonrpc; 26 | pub mod mount; 27 | pub mod mount_injector; 28 | pub mod ptrace; 29 | pub mod replacer; 30 | pub mod stop; 31 | pub mod utils; 32 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chaos Mesh Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | #![feature(box_syntax)] 15 | #![feature(async_closure)] 16 | #![feature(vec_into_raw_parts)] 17 | #![feature(atomic_mut_ptr)] 18 | #![feature(drain_filter)] 19 | #![allow(clippy::or_fun_call)] 20 | #![allow(clippy::too_many_arguments)] 21 | 22 | extern crate derive_more; 23 | 24 | mod fuse_device; 25 | mod hookfs; 26 | mod injector; 27 | mod jsonrpc; 28 | mod mount; 29 | mod mount_injector; 30 | mod ptrace; 31 | mod replacer; 32 | mod stop; 33 | mod utils; 34 | 35 | use std::convert::TryFrom; 36 | use std::os::unix::io::RawFd; 37 | use std::path::PathBuf; 38 | use std::sync::{mpsc, Mutex}; 39 | use std::{io, thread}; 40 | 41 | use anyhow::Result; 42 | use injector::InjectorConfig; 43 | use jsonrpc::start_server; 44 | use mount_injector::{MountInjectionGuard, MountInjector}; 45 | use nix::sys::signal::{signal, SigHandler, Signal}; 46 | use nix::unistd::{pipe, read, write}; 47 | use replacer::{Replacer, UnionReplacer}; 48 | use structopt::StructOpt; 49 | use tokio::runtime::Runtime; 50 | use tracing::{info, instrument}; 51 | use tracing_subscriber::EnvFilter; 52 | use utils::encode_path; 53 | 54 | #[derive(StructOpt, Debug, Clone)] 55 | #[structopt(name = "basic")] 56 | struct Options { 57 | #[structopt(long)] 58 | path: PathBuf, 59 | 60 | #[structopt(long = "mount-only")] 61 | mount_only: bool, 62 | 63 | #[structopt(short = "v", long = "verbose", default_value = "trace")] 64 | verbose: String, 65 | } 66 | 67 | #[instrument(skip(option))] 68 | fn inject(option: Options, injector_config: Vec) -> Result { 69 | info!("inject with config {:?}", injector_config); 70 | 71 | let path = option.path.clone(); 72 | 73 | info!("canonicalizing path {}", path.display()); 74 | let path = path.canonicalize()?; 75 | 76 | let replacer = if !option.mount_only { 77 | let mut replacer = UnionReplacer::default(); 78 | replacer.prepare(&path, &path)?; 79 | 80 | Some(replacer) 81 | } else { 82 | None 83 | }; 84 | 85 | if let Err(err) = fuse_device::mkfuse_node() { 86 | info!("fail to make /dev/fuse node: {}", err) 87 | } 88 | 89 | let mut injection = MountInjector::create_injection(&option.path, injector_config)?; 90 | let mount_guard = injection.mount()?; 91 | info!("mount successfully"); 92 | 93 | if let Some(mut replacer) = replacer { 94 | // At this time, `mount --move` has already been executed. 95 | // Our FUSE are mounted on the "path", so we 96 | replacer.run()?; 97 | drop(replacer); 98 | info!("replacer detached"); 99 | } 100 | 101 | info!("enable injection"); 102 | mount_guard.enable_injection(); 103 | 104 | Ok(mount_guard) 105 | } 106 | 107 | #[instrument(skip(option, mount_guard))] 108 | fn resume(option: Options, mount_guard: MountInjectionGuard) -> Result<()> { 109 | info!("disable injection"); 110 | mount_guard.disable_injection(); 111 | 112 | let path = option.path.clone(); 113 | 114 | info!("canonicalizing path {}", path.display()); 115 | let path = path.canonicalize()?; 116 | let (_, new_path) = encode_path(&path)?; 117 | 118 | let replacer = if !option.mount_only { 119 | let mut replacer = UnionReplacer::default(); 120 | replacer.prepare(&path, &new_path)?; 121 | info!("running replacer"); 122 | let result = replacer.run(); 123 | info!("replace result: {:?}", result); 124 | 125 | Some(replacer) 126 | } else { 127 | None 128 | }; 129 | 130 | info!("recovering mount"); 131 | mount_guard.recover_mount()?; 132 | 133 | info!("replacers detached"); 134 | info!("recover successfully"); 135 | 136 | drop(replacer); 137 | Ok(()) 138 | } 139 | 140 | static mut SIGNAL_PIPE_WRITER: RawFd = 0; 141 | 142 | const SIGNAL_MSG: [u8; 6] = *b"SIGNAL"; 143 | 144 | extern "C" fn signal_handler(_: libc::c_int) { 145 | unsafe { 146 | write(SIGNAL_PIPE_WRITER, &SIGNAL_MSG).unwrap(); 147 | } 148 | } 149 | 150 | fn wait_for_signal(chan: RawFd) -> Result<()> { 151 | let mut buf = vec![0u8; 6]; 152 | read(chan, buf.as_mut_slice())?; 153 | Ok(()) 154 | } 155 | 156 | fn main() -> Result<()> { 157 | let (reader, writer) = pipe()?; 158 | unsafe { 159 | SIGNAL_PIPE_WRITER = writer; 160 | } 161 | 162 | unsafe { signal(Signal::SIGINT, SigHandler::Handler(signal_handler))? }; 163 | unsafe { signal(Signal::SIGTERM, SigHandler::Handler(signal_handler))? }; 164 | 165 | let option = Options::from_args(); 166 | let env_filter = EnvFilter::try_from_default_env() 167 | .or_else(|_| EnvFilter::try_from(&option.verbose)) 168 | .or_else(|_| EnvFilter::try_new("trace")) 169 | .unwrap(); 170 | tracing_subscriber::fmt() 171 | .with_writer(io::stderr) 172 | .with_env_filter(env_filter) 173 | .init(); 174 | info!("start with option: {:?}", option); 175 | let mount_injector = inject(option.clone(), vec![]); 176 | 177 | let status = match &mount_injector { 178 | Ok(_) => Ok(()), 179 | Err(e) => Err(anyhow::Error::msg(e.to_string())), 180 | }; 181 | 182 | let (tx, _) = mpsc::channel(); 183 | { 184 | let hookfs = match &mount_injector { 185 | Ok(e) => Some(e.hookfs.clone()), 186 | Err(_) => None, 187 | }; 188 | thread::spawn(|| { 189 | Runtime::new() 190 | .expect("Failed to create Tokio runtime") 191 | .block_on(start_server(jsonrpc::RpcImpl::new( 192 | Mutex::new(status), 193 | Mutex::new(tx), 194 | hookfs, 195 | ))); 196 | }); 197 | } 198 | info!("waiting for signal to exit"); 199 | wait_for_signal(reader)?; 200 | info!("start to recover and exit"); 201 | if let Ok(v) = mount_injector { 202 | resume(option, v)?; 203 | } 204 | Ok(()) 205 | } 206 | -------------------------------------------------------------------------------- /src/mount.rs: -------------------------------------------------------------------------------- 1 | use std::fs::create_dir_all; 2 | use std::path::Path; 3 | 4 | use anyhow::{Context, Result}; 5 | use nix::mount::{mount, MsFlags}; 6 | use procfs::process::{self, Process}; 7 | 8 | #[derive(Debug, Clone)] 9 | pub struct MountsInfo { 10 | mounts: Vec, 11 | } 12 | 13 | impl MountsInfo { 14 | pub fn parse_mounts() -> Result { 15 | let process = Process::myself()?; 16 | let mounts = process.mountinfo()?; 17 | 18 | Ok(MountsInfo { mounts }) 19 | } 20 | 21 | pub fn non_root>(&self, path: P) -> Result { 22 | let mount_points = self.mounts.iter().map(|item| &item.mount_point); 23 | for mount_point in mount_points { 24 | if path.as_ref().starts_with(&mount_point) { 25 | // The relationship is "contain" because if we want to inject /a/b, and /a is a mount point, we can still 26 | // use this method. 27 | return Ok(true); 28 | } 29 | } 30 | Ok(false) 31 | } 32 | 33 | pub fn move_mount, P2: AsRef>( 34 | &self, 35 | original_path: P1, 36 | target_path: P2, 37 | ) -> Result<()> { 38 | create_dir_all(target_path.as_ref())?; 39 | 40 | mount::<_, _, str, str>( 41 | Some(original_path.as_ref()), 42 | target_path.as_ref(), 43 | None, 44 | MsFlags::MS_MOVE, 45 | None, 46 | ) 47 | .context(format!( 48 | "source: {}, target: {}", 49 | original_path.as_ref().display(), 50 | target_path.as_ref().display() 51 | ))?; 52 | 53 | Ok(()) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/mount_injector.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::OsStr; 2 | use std::path::{Path, PathBuf}; 3 | use std::sync::Arc; 4 | use std::thread::JoinHandle; 5 | 6 | use anyhow::{anyhow, Result}; 7 | use nix::mount::umount; 8 | use retry::delay::Fixed; 9 | use retry::{retry, OperationResult}; 10 | use tracing::info; 11 | 12 | use crate::injector::{InjectorConfig, MultiInjector}; 13 | use crate::{hookfs, mount, stop}; 14 | 15 | #[derive(Debug)] 16 | pub struct MountInjector { 17 | original_path: PathBuf, 18 | new_path: PathBuf, 19 | injector_config: Vec, 20 | } 21 | 22 | pub struct MountInjectionGuard { 23 | original_path: PathBuf, 24 | new_path: PathBuf, 25 | pub hookfs: Arc, 26 | handler: Option>>, 27 | } 28 | 29 | impl MountInjectionGuard { 30 | pub fn enable_injection(&self) { 31 | self.hookfs.enable_injection(); 32 | } 33 | 34 | pub fn disable_injection(&self) { 35 | self.hookfs.disable_injection(); 36 | } 37 | 38 | pub fn recover_mount(mut self) -> Result<()> { 39 | let mount_point = self.original_path.clone(); 40 | 41 | retry(Fixed::from_millis(500).take(20), || { 42 | if let Err(err) = umount(mount_point.as_path()) { 43 | info!("umount returns error: {:?}", err); 44 | OperationResult::Retry(err) 45 | } else { 46 | OperationResult::Ok(()) 47 | } 48 | })?; 49 | 50 | info!("unmount successfully!"); 51 | self.handler 52 | .take() 53 | .ok_or(anyhow!("handler is empty"))? 54 | .join() 55 | .unwrap()?; 56 | 57 | let new_path = self.new_path.clone(); 58 | let original_path = self.original_path; 59 | 60 | let mounts = mount::MountsInfo::parse_mounts()?; 61 | 62 | if mounts.non_root(&original_path)? { 63 | // TODO: make the parent mount points private before move mount points 64 | mounts.move_mount(new_path, original_path)?; 65 | } else { 66 | return Err(anyhow!("inject on a root mount")); 67 | } 68 | 69 | Ok(()) 70 | } 71 | } 72 | 73 | impl MountInjector { 74 | pub fn create_injection>( 75 | path: P, 76 | injector_config: Vec, 77 | ) -> Result { 78 | let original_path: PathBuf = path.as_ref().to_owned(); 79 | 80 | let mut base_path: PathBuf = path.as_ref().to_owned(); 81 | if !base_path.pop() { 82 | return Err(anyhow!("path is the root")); 83 | } 84 | 85 | let mut new_path: PathBuf = base_path; 86 | let original_filename = original_path 87 | .file_name() 88 | .ok_or(anyhow!("the path terminates in `..` or `/`"))? 89 | .to_str() 90 | .ok_or(anyhow!("path with non-UTF-8 character"))?; 91 | let new_filename = format!("__chaosfs__{}__", original_filename); 92 | new_path.push(new_filename.as_str()); 93 | 94 | Ok(MountInjector { 95 | original_path, 96 | new_path, 97 | injector_config, 98 | }) 99 | } 100 | 101 | // This method should be called in host namespace 102 | pub fn mount(&mut self) -> Result { 103 | let original_path = self.original_path.clone(); 104 | let new_path = self.new_path.clone(); 105 | 106 | let mounts = mount::MountsInfo::parse_mounts()?; 107 | 108 | if mounts.non_root(&original_path)? { 109 | // TODO: make the parent mount points private before move mount points 110 | mounts.move_mount(original_path, new_path)?; 111 | } else { 112 | return Err(anyhow!("inject on a root mount")); 113 | } 114 | 115 | let injectors = MultiInjector::build(self.injector_config.clone())?; 116 | 117 | let hookfs = Arc::new(hookfs::HookFs::new( 118 | &self.original_path, 119 | &self.new_path, 120 | injectors, 121 | )); 122 | 123 | let original_path = self.original_path.clone(); 124 | let new_path = self.new_path.clone(); 125 | let cloned_hookfs = hookfs.clone(); 126 | 127 | let (before_mount_waiter, before_mount_guard) = stop::lock(); 128 | let handler = std::thread::spawn(box move || { 129 | let fs = hookfs::AsyncFileSystem::from(cloned_hookfs); 130 | 131 | std::fs::create_dir_all(new_path.as_path())?; 132 | 133 | let args = ["allow_other", "fsname=toda", "default_permissions", "nonempty"]; 134 | let flags: Vec<_> = args 135 | .iter() 136 | .flat_map(|item| vec![OsStr::new("-o"), OsStr::new(item)]) 137 | .collect(); 138 | 139 | info!("mount with flags {:?}", flags); 140 | 141 | drop(before_mount_guard); 142 | fuser::mount(fs, &original_path, &flags)?; 143 | 144 | drop(hookfs::runtime::RUNTIME.write().unwrap().take().unwrap()); 145 | 146 | Ok(()) 147 | }); 148 | // TODO: remove this. But wait for FUSE gets up 149 | // Related Issue: https://github.com/zargony/fuse-rs/issues/9 150 | before_mount_waiter.wait(); 151 | std::thread::sleep(std::time::Duration::from_millis(200)); 152 | 153 | Ok(MountInjectionGuard { 154 | handler: Some(handler), 155 | hookfs, 156 | original_path: self.original_path.clone(), 157 | new_path: self.new_path.clone(), 158 | }) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/ptrace/mod.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::collections::{HashMap, HashSet}; 3 | use std::ffi::CString; 4 | use std::os::unix::ffi::OsStrExt; 5 | use std::path::Path; 6 | 7 | use anyhow::{anyhow, Result}; 8 | use nix::errno::Errno; 9 | use nix::sys::mman::{MapFlags, ProtFlags}; 10 | use nix::sys::signal::Signal; 11 | use nix::sys::uio::{process_vm_writev, IoVec, RemoteIoVec}; 12 | use nix::sys::{ptrace, wait}; 13 | use nix::unistd::Pid; 14 | use nix::Error::Sys; 15 | use procfs::process::Task; 16 | use procfs::ProcError; 17 | use retry::delay::Fixed; 18 | use retry::Error::{self, Operation}; 19 | use retry::OperationResult; 20 | use tracing::{error, info, instrument, trace, warn}; 21 | use Error::Internal; 22 | 23 | // There should be only one PtraceManager in one thread. But as we don't implement TLS 24 | // , we cannot use thread-local variables safely. 25 | #[derive(Debug, Default)] 26 | pub struct PtraceManager { 27 | counter: RefCell>, 28 | } 29 | 30 | thread_local! { 31 | static PTRACE_MANAGER: PtraceManager = PtraceManager::default() 32 | } 33 | 34 | pub fn trace(pid: i32) -> Result { 35 | PTRACE_MANAGER.with(|pm| pm.trace(pid)) 36 | } 37 | 38 | fn thread_is_gone(state: char) -> bool { 39 | // return true if the process is Zombie or Dead 40 | state == 'Z' || state == 'x' || state == 'X' 41 | } 42 | 43 | #[instrument] 44 | fn attach_task(task: &Task) -> Result<()> { 45 | let pid = Pid::from_raw(task.tid); 46 | let process = procfs::process::Process::new(task.tid)?; 47 | 48 | trace!("attach task: {}", task.tid); 49 | match ptrace::attach(pid) { 50 | Err(Sys(errno)) 51 | if errno == Errno::ESRCH 52 | || (errno == Errno::EPERM && thread_is_gone(process.stat.state)) => 53 | { 54 | info!("task {} doesn't exist, maybe has stopped", task.tid) 55 | } 56 | Err(err) => { 57 | warn!("attach error: {:?}", err); 58 | return Err(err.into()); 59 | } 60 | _ => {} 61 | } 62 | info!("attach task: {} successfully", task.tid); 63 | 64 | // TODO: check wait result 65 | match wait::waitpid(pid, Some(wait::WaitPidFlag::__WALL)) { 66 | Ok(status) => { 67 | info!("wait status: {:?}", status); 68 | } 69 | Err(err) => warn!("fail to wait for process({}): {:?}", pid, err), 70 | }; 71 | 72 | Ok(()) 73 | } 74 | 75 | impl PtraceManager { 76 | #[instrument(skip(self))] 77 | pub fn trace(&self, pid: i32) -> Result { 78 | let raw_pid = pid; 79 | let pid = Pid::from_raw(pid); 80 | 81 | let mut counter_ref = self.counter.borrow_mut(); 82 | match counter_ref.get_mut(&raw_pid) { 83 | Some(count) => *count += 1, 84 | None => { 85 | trace!("stop {} successfully", pid); 86 | 87 | let mut iterations = 2; 88 | let mut traced_tasks = HashSet::::new(); 89 | 90 | while iterations > 0 { 91 | let mut new_threads_found = false; 92 | let process = procfs::process::Process::new(raw_pid)?; 93 | for task in process.tasks()?.flatten() { 94 | if traced_tasks.contains(&task.tid) { 95 | continue; 96 | } 97 | 98 | if let Ok(()) = attach_task(&task) { 99 | trace!("newly traced task: {}", task.tid); 100 | new_threads_found = true; 101 | traced_tasks.insert(task.tid); 102 | } 103 | } 104 | 105 | if !new_threads_found { 106 | iterations -= 1; 107 | } 108 | } 109 | 110 | info!("trace process: {} successfully", pid); 111 | counter_ref.insert(raw_pid, 1); 112 | } 113 | } 114 | 115 | Ok(TracedProcess { pid: raw_pid }) 116 | } 117 | 118 | #[instrument(skip(self))] 119 | pub fn detach(&self, pid: i32) -> Result<()> { 120 | let mut counter_ref = self.counter.borrow_mut(); 121 | match counter_ref.get_mut(&pid) { 122 | Some(count) => { 123 | *count -= 1; 124 | trace!("decrease counter to {}", *count); 125 | if *count < 1 { 126 | counter_ref.remove(&pid); 127 | 128 | info!("detach process: {}", pid); 129 | if let Err(err) = retry::retry::<_, _, _, anyhow::Error, _>( 130 | Fixed::from_millis(500).take(20), 131 | || match procfs::process::Process::new(pid) { 132 | Err(ProcError::NotFound(_)) => { 133 | info!("process {} not found", pid); 134 | OperationResult::Ok(()) 135 | } 136 | Err(err) => { 137 | warn!("fail to detach task: {}, retry", pid); 138 | OperationResult::Retry(err.into()) 139 | } 140 | Ok(process) => match process.tasks() { 141 | Err(err) => OperationResult::Retry(err.into()), 142 | Ok(tasks) => { 143 | for task in tasks.flatten() { 144 | match ptrace::detach(Pid::from_raw(task.tid), None) { 145 | Ok(()) => { 146 | info!("successfully detached task: {}", task.tid); 147 | } 148 | Err(Sys(Errno::ESRCH)) => trace!( 149 | "task {} doesn't exist, maybe has stopped or not traced", 150 | task.tid 151 | ), 152 | Err(err) => { 153 | warn!("fail to detach: {:?}", err) 154 | }, 155 | } 156 | trace!("detach task: {} successfully", task.tid); 157 | } 158 | info!("detach process: {} successfully", pid); 159 | OperationResult::Ok(()) 160 | } 161 | }, 162 | }, 163 | ) { 164 | warn!("fail to detach: {:?}", err); 165 | match err { 166 | Operation { 167 | error: e, 168 | total_delay: _, 169 | tries: _, 170 | } => return Err(e), 171 | Internal(err) => error!("internal error: {:?}", err), 172 | } 173 | }; 174 | } 175 | 176 | Ok(()) 177 | } 178 | None => Err(anyhow::anyhow!("haven't traced this process")), 179 | } 180 | } 181 | } 182 | 183 | #[derive(Debug)] 184 | pub struct TracedProcess { 185 | pub pid: i32, 186 | } 187 | 188 | impl Clone for TracedProcess { 189 | fn clone(&self) -> Self { 190 | // TODO: handler error here 191 | PTRACE_MANAGER.with(|pm| pm.trace(self.pid)).unwrap() 192 | } 193 | } 194 | 195 | impl TracedProcess { 196 | #[instrument] 197 | fn protect(&self) -> Result { 198 | let regs = ptrace::getregs(Pid::from_raw(self.pid))?; 199 | 200 | let rip = regs.rip; 201 | trace!("protecting regs: {:?}", regs); 202 | let rip_ins = ptrace::read(Pid::from_raw(self.pid), rip as *mut libc::c_void)?; 203 | 204 | let guard = ThreadGuard { 205 | tid: self.pid, 206 | regs, 207 | rip_ins, 208 | }; 209 | Ok(guard) 210 | } 211 | 212 | #[instrument(skip(f))] 213 | fn with_protect Result>(&self, f: F) -> Result { 214 | let guard = self.protect()?; 215 | 216 | let ret = f(self)?; 217 | 218 | drop(guard); 219 | 220 | Ok(ret) 221 | } 222 | 223 | #[instrument] 224 | fn syscall(&self, id: u64, args: &[u64]) -> Result { 225 | trace!("run syscall {} {:?}", id, args); 226 | 227 | self.with_protect(|thread| -> Result { 228 | let pid = Pid::from_raw(thread.pid); 229 | 230 | let mut regs = ptrace::getregs(pid)?; 231 | let cur_ins_ptr = regs.rip; 232 | 233 | regs.rax = id; 234 | for (index, arg) in args.iter().enumerate() { 235 | // All these registers are hard coded for x86 platform 236 | if index == 0 { 237 | regs.rdi = *arg 238 | } else if index == 1 { 239 | regs.rsi = *arg 240 | } else if index == 2 { 241 | regs.rdx = *arg 242 | } else if index == 3 { 243 | regs.r10 = *arg 244 | } else if index == 4 { 245 | regs.r8 = *arg 246 | } else if index == 5 { 247 | regs.r9 = *arg 248 | } else { 249 | return Err(anyhow!("too many arguments for a syscall")); 250 | } 251 | } 252 | trace!("setting regs for pid: {:?}, regs: {:?}", pid, regs); 253 | ptrace::setregs(pid, regs)?; 254 | 255 | // We only support x86-64 platform now, so using hard coded `LittleEndian` here is ok. 256 | unsafe { 257 | ptrace::write( 258 | pid, 259 | cur_ins_ptr as *mut libc::c_void, 260 | 0x050f as *mut libc::c_void, 261 | )? 262 | }; 263 | ptrace::step(pid, None)?; 264 | 265 | loop { 266 | let status = wait::waitpid(pid, None)?; 267 | info!("wait status: {:?}", status); 268 | match status { 269 | wait::WaitStatus::Stopped(_, Signal::SIGTRAP) => break, 270 | _ => ptrace::step(pid, None)?, 271 | } 272 | } 273 | 274 | let regs = ptrace::getregs(pid)?; 275 | 276 | trace!("returned: {:?}", regs.rax); 277 | 278 | Ok(regs.rax) 279 | }) 280 | } 281 | 282 | #[instrument] 283 | pub fn mmap(&self, length: u64, fd: u64) -> Result { 284 | let prot = ProtFlags::PROT_READ | ProtFlags::PROT_WRITE | ProtFlags::PROT_EXEC; 285 | let flags = MapFlags::MAP_PRIVATE | MapFlags::MAP_ANON; 286 | 287 | self.syscall( 288 | 9, 289 | &[0, length, prot.bits() as u64, flags.bits() as u64, fd, 0], 290 | ) 291 | } 292 | 293 | #[instrument] 294 | pub fn munmap(&self, addr: u64, len: u64) -> Result { 295 | self.syscall(11, &[addr, len]) 296 | } 297 | 298 | #[instrument(skip(f))] 299 | pub fn with_mmap Result>(&self, len: u64, f: F) -> Result { 300 | let addr = self.mmap(len, 0)?; 301 | 302 | let ret = f(self, addr)?; 303 | 304 | self.munmap(addr, len)?; 305 | 306 | Ok(ret) 307 | } 308 | 309 | #[instrument] 310 | pub fn chdir + std::fmt::Debug>(&self, filename: P) -> Result<()> { 311 | let filename = CString::new(filename.as_ref().as_os_str().as_bytes())?; 312 | let path = filename.as_bytes_with_nul(); 313 | 314 | self.with_mmap(path.len() as u64, |process, addr| { 315 | process.write_mem(addr, path)?; 316 | 317 | self.syscall(80, &[addr])?; 318 | Ok(()) 319 | }) 320 | } 321 | 322 | #[instrument] 323 | pub fn write_mem(&self, addr: u64, content: &[u8]) -> Result<()> { 324 | let pid = Pid::from_raw(self.pid); 325 | 326 | process_vm_writev( 327 | pid, 328 | &[IoVec::from_slice(content)], 329 | &[RemoteIoVec { 330 | base: addr as usize, 331 | len: content.len(), 332 | }], 333 | )?; 334 | 335 | Ok(()) 336 | } 337 | 338 | #[instrument(skip(codes))] 339 | pub fn run_codes Result<(u64, Vec)>>(&self, codes: F) -> Result<()> { 340 | let pid = Pid::from_raw(self.pid); 341 | 342 | let regs = ptrace::getregs(pid)?; 343 | let (_, ins) = codes(regs.rip)?; // generate codes to get length 344 | 345 | self.with_mmap(ins.len() as u64 + 16, |_, addr| { 346 | self.with_protect(|_| { 347 | let (offset, ins) = codes(addr)?; // generate codes 348 | 349 | let end_addr = addr + ins.len() as u64; 350 | trace!("write instructions to addr: {:X}-{:X}", addr, end_addr); 351 | self.write_mem(addr, &ins)?; 352 | 353 | let mut regs = ptrace::getregs(pid)?; 354 | trace!("modify rip to addr: {:X}", addr + offset); 355 | regs.rip = addr + offset; 356 | ptrace::setregs(pid, regs)?; 357 | 358 | let regs = ptrace::getregs(pid)?; 359 | info!("current registers: {:?}", regs); 360 | 361 | loop { 362 | info!("run instructions"); 363 | ptrace::cont(pid, None)?; 364 | 365 | info!("wait for pid: {:?}", pid); 366 | let status = wait::waitpid(pid, None)?; 367 | info!("wait status: {:?}", status); 368 | 369 | use nix::sys::signal::SIGTRAP; 370 | let regs = ptrace::getregs(pid)?; 371 | 372 | info!("current registers: {:?}", regs); 373 | match status { 374 | wait::WaitStatus::Stopped(_, SIGTRAP) => { 375 | break; 376 | } 377 | _ => info!("continue running replacers"), 378 | } 379 | } 380 | Ok(()) 381 | }) 382 | }) 383 | } 384 | } 385 | 386 | impl Drop for TracedProcess { 387 | fn drop(&mut self) { 388 | trace!("dropping traced process: {}", self.pid); 389 | 390 | if let Err(err) = PTRACE_MANAGER.with(|pm| pm.detach(self.pid)) { 391 | info!( 392 | "detaching process {} failed with error: {:?}", 393 | self.pid, err 394 | ) 395 | } 396 | } 397 | } 398 | 399 | #[derive(Debug)] 400 | struct ThreadGuard { 401 | tid: i32, 402 | regs: libc::user_regs_struct, 403 | rip_ins: i64, 404 | } 405 | 406 | impl Drop for ThreadGuard { 407 | fn drop(&mut self) { 408 | let pid = Pid::from_raw(self.tid); 409 | unsafe { 410 | ptrace::write( 411 | pid, 412 | self.regs.rip as *mut libc::c_void, 413 | self.rip_ins as *mut libc::c_void, 414 | ) 415 | .unwrap(); 416 | } 417 | ptrace::setregs(pid, self.regs).unwrap(); 418 | } 419 | } 420 | -------------------------------------------------------------------------------- /src/replacer/cwd_replacer.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | use std::path::{Path, PathBuf}; 3 | 4 | use anyhow::Result; 5 | use tracing::{error, info, trace}; 6 | 7 | use super::utils::all_processes; 8 | use super::{ptrace, Replacer}; 9 | 10 | #[derive(Debug)] 11 | pub struct CwdReplacer { 12 | processes: Vec<(ptrace::TracedProcess, PathBuf)>, 13 | } 14 | 15 | impl CwdReplacer { 16 | pub fn prepare, P2: AsRef>( 17 | detect_path: P1, 18 | new_path: P2, 19 | ) -> Result { 20 | info!("preparing cmdreplacer"); 21 | 22 | let processes = all_processes()? 23 | .filter_map(|process| -> Option<_> { 24 | let pid = process.pid; 25 | trace!("itering proc: {}", pid); 26 | 27 | match process.cwd() { 28 | Ok(cwd) => Some((pid, cwd)), 29 | Err(err) => { 30 | trace!("filter out pid({}) because of error: {:?}", pid, err); 31 | None 32 | } 33 | } 34 | }) 35 | .filter(|(_, path)| path.starts_with(detect_path.as_ref())) 36 | .filter_map(|(pid, path)| match ptrace::trace(pid) { 37 | Ok(process) => { 38 | let mut new_path = new_path.as_ref().to_path_buf(); 39 | 40 | new_path.push(path.strip_prefix(detect_path.as_ref()).unwrap()); 41 | Some((process, new_path)) 42 | } 43 | Err(err) => { 44 | error!("fail to ptrace process: pid({}) with error: {:?}", pid, err); 45 | None 46 | } 47 | }) 48 | .collect(); 49 | 50 | Ok(CwdReplacer { processes }) 51 | } 52 | } 53 | 54 | impl Replacer for CwdReplacer { 55 | fn run(&mut self) -> Result<()> { 56 | info!("running cwd replacer"); 57 | for (process, new_path) in self.processes.iter() { 58 | trace!("replacing cwd: {} to {:?}", process.pid, new_path); 59 | process.chdir(new_path)?; 60 | } 61 | 62 | Ok(()) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/replacer/fd_replacer.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fmt::Debug; 3 | use std::io::{Cursor, Read, Write}; 4 | use std::iter::FromIterator; 5 | use std::path::{Path, PathBuf}; 6 | 7 | use anyhow::{anyhow, Result}; 8 | use dynasmrt::{dynasm, DynasmApi, DynasmLabelApi}; 9 | use itertools::Itertools; 10 | use procfs::process::FDTarget; 11 | use tracing::{error, info, trace}; 12 | 13 | use super::utils::all_processes; 14 | use super::{ptrace, Replacer}; 15 | 16 | #[derive(Clone, Copy)] 17 | #[repr(packed)] 18 | #[repr(C)] 19 | struct ReplaceCase { 20 | fd: u64, 21 | new_path_offset: u64, 22 | } 23 | 24 | impl ReplaceCase { 25 | pub fn new(fd: u64, new_path_offset: u64) -> ReplaceCase { 26 | ReplaceCase { 27 | fd, 28 | new_path_offset, 29 | } 30 | } 31 | } 32 | 33 | struct ProcessAccessorBuilder { 34 | cases: Vec, 35 | new_paths: Cursor>, 36 | } 37 | 38 | impl ProcessAccessorBuilder { 39 | pub fn new() -> ProcessAccessorBuilder { 40 | ProcessAccessorBuilder { 41 | cases: Vec::new(), 42 | new_paths: Cursor::new(Vec::new()), 43 | } 44 | } 45 | 46 | pub fn build(self, process: ptrace::TracedProcess) -> Result { 47 | Ok(ProcessAccessor { 48 | process, 49 | 50 | cases: self.cases, 51 | new_paths: self.new_paths, 52 | }) 53 | } 54 | 55 | pub fn push_case(&mut self, fd: u64, new_path: PathBuf) -> anyhow::Result<()> { 56 | info!("push case fd: {}, new_path: {}", fd, new_path.display()); 57 | 58 | let mut new_path = new_path 59 | .to_str() 60 | .ok_or(anyhow!("fd contains non-UTF-8 character"))? 61 | .as_bytes() 62 | .to_vec(); 63 | 64 | new_path.push(0); 65 | 66 | let offset = self.new_paths.position(); 67 | self.new_paths.write_all(new_path.as_slice())?; 68 | 69 | self.cases.push(ReplaceCase::new(fd, offset)); 70 | 71 | Ok(()) 72 | } 73 | } 74 | 75 | impl FromIterator<(u64, PathBuf)> for ProcessAccessorBuilder { 76 | fn from_iter>(iter: T) -> Self { 77 | let mut builder = Self::new(); 78 | for (fd, path) in iter { 79 | if let Err(err) = builder.push_case(fd, path) { 80 | error!("fail to write to AccessorBuilder. Error: {:?}", err) 81 | } 82 | } 83 | 84 | builder 85 | } 86 | } 87 | 88 | struct ProcessAccessor { 89 | process: ptrace::TracedProcess, 90 | 91 | cases: Vec, 92 | new_paths: Cursor>, 93 | } 94 | 95 | impl Debug for ProcessAccessor { 96 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 97 | self.process.fmt(f) 98 | } 99 | } 100 | 101 | impl ProcessAccessor { 102 | pub fn run(&mut self) -> anyhow::Result<()> { 103 | self.new_paths.set_position(0); 104 | 105 | let mut new_paths = Vec::new(); 106 | self.new_paths.read_to_end(&mut new_paths)?; 107 | 108 | let (cases_ptr, length, _) = self.cases.clone().into_raw_parts(); 109 | let size = length * std::mem::size_of::(); 110 | let cases = unsafe { std::slice::from_raw_parts(cases_ptr as *mut u8, size) }; 111 | 112 | self.process.run_codes(|addr| { 113 | let mut vec_rt = 114 | dynasmrt::VecAssembler::::new(addr as usize); 115 | dynasm!(vec_rt 116 | ; .arch x64 117 | ; ->cases: 118 | ; .bytes cases 119 | ; ->cases_length: 120 | ; .qword cases.len() as i64 121 | ; ->new_paths: 122 | ; .bytes new_paths.as_slice() 123 | ; nop 124 | ; nop 125 | ); 126 | 127 | trace!("static bytes placed"); 128 | let replace = vec_rt.offset(); 129 | dynasm!(vec_rt 130 | ; .arch x64 131 | // set r15 to 0 132 | ; xor r15, r15 133 | ; lea r14, [-> cases] 134 | 135 | ; jmp ->end 136 | ; ->start: 137 | // fcntl 138 | ; mov rax, 0x48 139 | ; mov rdi, QWORD [r14+r15] // fd 140 | ; mov rsi, 0x3 141 | ; mov rdx, 0x0 142 | ; syscall 143 | ; mov rsi, rax 144 | // open 145 | ; mov rax, 0x2 146 | ; lea rdi, [-> new_paths] 147 | ; add rdi, QWORD [r14+r15+8] // path 148 | ; mov rdx, 0x0 149 | ; syscall 150 | ; mov r12, rax // store newly opened fd in r12 151 | // lseek 152 | ; mov rax, 0x8 153 | ; mov rdi, QWORD [r14+r15] // fd 154 | ; mov rsi, 0 155 | ; mov rdx, libc::SEEK_CUR 156 | ; syscall 157 | ; mov rdi, r12 158 | ; mov rsi, rax 159 | // lseek 160 | ; mov rax, 0x8 161 | ; mov rdx, libc::SEEK_SET 162 | ; syscall 163 | // dup2 164 | ; mov rax, 0x21 165 | ; mov rdi, r12 166 | ; mov rsi, QWORD [r14+r15] // fd 167 | ; syscall 168 | // close 169 | ; mov rax, 0x3 170 | ; mov rdi, r12 171 | ; syscall 172 | 173 | ; add r15, std::mem::size_of::() as i32 174 | ; ->end: 175 | ; mov r13, QWORD [->cases_length] 176 | ; cmp r15, r13 177 | ; jb ->start 178 | 179 | ; int3 180 | ); 181 | 182 | let instructions = vec_rt.finalize()?; 183 | 184 | Ok((replace.0 as u64, instructions)) 185 | })?; 186 | 187 | trace!("reopen successfully"); 188 | Ok(()) 189 | } 190 | } 191 | 192 | pub struct FdReplacer { 193 | processes: HashMap, 194 | } 195 | 196 | impl FdReplacer { 197 | pub fn prepare, P2: AsRef>( 198 | detect_path: P1, 199 | new_path: P2, 200 | ) -> Result { 201 | info!("preparing fd replacer"); 202 | 203 | let detect_path = detect_path.as_ref(); 204 | let new_path = new_path.as_ref(); 205 | 206 | let processes = all_processes()? 207 | .filter_map(|process| -> Option<_> { 208 | let pid = process.pid; 209 | 210 | let traced_process = match ptrace::trace(pid) { 211 | Ok(p) => p, 212 | Err(err) => { 213 | error!("fail to trace process: {} {}", pid, err); 214 | return None; 215 | } 216 | }; 217 | let fd = process.fd().ok()?; 218 | 219 | Some((traced_process, fd)) 220 | }) 221 | .flat_map(|(process, fd)| { 222 | fd.into_iter() 223 | .filter_map(|entry| match entry.target { 224 | FDTarget::Path(path) => Some((entry.fd as u64, path)), 225 | _ => None, 226 | }) 227 | .filter(|(_, path)| path.starts_with(detect_path)) 228 | .filter_map(move |(fd, path)| { 229 | trace!("replace fd({}): {}", fd, path.display()); 230 | let stripped_path = path.strip_prefix(&detect_path).ok()?; 231 | Some((process.clone(), (fd, new_path.join(stripped_path)))) 232 | }) 233 | }) 234 | .group_by(|(process, _)| process.pid) 235 | .into_iter() 236 | .filter_map(|(pid, group)| Some((ptrace::trace(pid).ok()?, group))) 237 | .map(|(process, group)| (process, group.map(|(_, group)| group))) 238 | .filter_map(|(process, group)| { 239 | let pid = process.pid; 240 | match group.collect::().build(process) { 241 | Ok(accessor) => Some((pid, accessor)), 242 | Err(err) => { 243 | error!("fail to build accessor: {:?}", err); 244 | None 245 | } 246 | } 247 | }) 248 | .collect(); 249 | 250 | Ok(FdReplacer { processes }) 251 | } 252 | } 253 | 254 | impl Replacer for FdReplacer { 255 | fn run(&mut self) -> Result<()> { 256 | info!("running fd replacer"); 257 | for (_, accessor) in self.processes.iter_mut() { 258 | accessor.run()?; 259 | } 260 | 261 | Ok(()) 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /src/replacer/mmap_replacer.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fmt::Debug; 3 | use std::io::{Cursor, Read, Write}; 4 | use std::iter::FromIterator; 5 | use std::path::{Path, PathBuf}; 6 | 7 | use anyhow::{anyhow, Result}; 8 | use dynasmrt::{dynasm, DynasmApi, DynasmLabelApi}; 9 | use itertools::Itertools; 10 | use nix::sys::mman::{MapFlags, ProtFlags}; 11 | use procfs::process::MMapPath; 12 | use tracing::{error, info, trace}; 13 | 14 | use super::utils::all_processes; 15 | use super::{ptrace, Replacer}; 16 | 17 | #[derive(Clone, Debug)] 18 | struct ReplaceCase { 19 | pub memory_addr: u64, 20 | pub length: u64, 21 | pub prot: u64, 22 | pub flags: u64, 23 | pub path: PathBuf, 24 | pub offset: u64, 25 | } 26 | 27 | #[derive(Clone, Copy)] 28 | #[repr(packed)] 29 | #[repr(C)] 30 | struct RawReplaceCase { 31 | memory_addr: u64, 32 | length: u64, 33 | prot: u64, 34 | flags: u64, 35 | new_path_offset: u64, 36 | offset: u64, 37 | } 38 | 39 | impl RawReplaceCase { 40 | pub fn new( 41 | memory_addr: u64, 42 | length: u64, 43 | prot: u64, 44 | flags: u64, 45 | new_path_offset: u64, 46 | offset: u64, 47 | ) -> RawReplaceCase { 48 | RawReplaceCase { 49 | memory_addr, 50 | length, 51 | prot, 52 | flags, 53 | new_path_offset, 54 | offset, 55 | } 56 | } 57 | } 58 | 59 | // TODO: encapsulate this struct for fd replacer and mmap replacer 60 | struct ProcessAccessorBuilder { 61 | cases: Vec, 62 | new_paths: Cursor>, 63 | } 64 | 65 | impl ProcessAccessorBuilder { 66 | pub fn new() -> ProcessAccessorBuilder { 67 | ProcessAccessorBuilder { 68 | cases: Vec::new(), 69 | new_paths: Cursor::new(Vec::new()), 70 | } 71 | } 72 | 73 | pub fn build(self, process: ptrace::TracedProcess) -> Result { 74 | Ok(ProcessAccessor { 75 | process, 76 | 77 | cases: self.cases, 78 | new_paths: self.new_paths, 79 | }) 80 | } 81 | 82 | pub fn push_case( 83 | &mut self, 84 | memory_addr: u64, 85 | length: u64, 86 | prot: u64, 87 | flags: u64, 88 | new_path: PathBuf, 89 | offset: u64, 90 | ) -> anyhow::Result<()> { 91 | info!("push case"); 92 | 93 | let mut new_path = new_path 94 | .to_str() 95 | .ok_or(anyhow!("fd contains non-UTF-8 character"))? 96 | .as_bytes() 97 | .to_vec(); 98 | 99 | new_path.push(0); 100 | 101 | let new_path_offset = self.new_paths.position(); 102 | self.new_paths.write_all(new_path.as_slice())?; 103 | 104 | self.cases.push(RawReplaceCase::new( 105 | memory_addr, 106 | length, 107 | prot, 108 | flags, 109 | new_path_offset, 110 | offset, 111 | )); 112 | 113 | Ok(()) 114 | } 115 | } 116 | 117 | impl FromIterator for ProcessAccessorBuilder { 118 | fn from_iter>(iter: T) -> Self { 119 | let mut builder = Self::new(); 120 | for case in iter { 121 | if let Err(err) = builder.push_case( 122 | case.memory_addr, 123 | case.length, 124 | case.prot, 125 | case.flags, 126 | case.path, 127 | case.offset, 128 | ) { 129 | error!("fail to write to AccessorBuilder. Error: {:?}", err) 130 | } 131 | } 132 | 133 | builder 134 | } 135 | } 136 | 137 | struct ProcessAccessor { 138 | process: ptrace::TracedProcess, 139 | 140 | cases: Vec, 141 | new_paths: Cursor>, 142 | } 143 | 144 | impl Debug for ProcessAccessor { 145 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 146 | self.process.fmt(f) 147 | } 148 | } 149 | 150 | impl ProcessAccessor { 151 | pub fn run(&mut self) -> anyhow::Result<()> { 152 | self.new_paths.set_position(0); 153 | 154 | let mut new_paths = Vec::new(); 155 | self.new_paths.read_to_end(&mut new_paths)?; 156 | 157 | let (cases_ptr, length, _) = self.cases.clone().into_raw_parts(); 158 | let size = length * std::mem::size_of::(); 159 | let cases = unsafe { std::slice::from_raw_parts(cases_ptr as *mut u8, size) }; 160 | 161 | self.process.run_codes(|addr| { 162 | let mut vec_rt = 163 | dynasmrt::VecAssembler::::new(addr as usize); 164 | dynasm!(vec_rt 165 | ; .arch x64 166 | ; ->cases: 167 | ; .bytes cases 168 | ; ->cases_length: 169 | ; .qword cases.len() as i64 170 | ; ->new_paths: 171 | ; .bytes new_paths.as_slice() 172 | ; nop 173 | ; nop 174 | ); 175 | 176 | trace!("static bytes placed"); 177 | let replace = vec_rt.offset(); 178 | dynasm!(vec_rt 179 | ; .arch x64 180 | // set r15 to 0 181 | ; xor r15, r15 182 | ; lea r14, [-> cases] 183 | 184 | ; jmp ->end 185 | ; ->start: 186 | // munmap 187 | ; mov rax, 0x0B 188 | ; mov rdi, QWORD [r14+r15] // addr 189 | ; mov rsi, QWORD [r14+r15+8] // length 190 | ; mov rdx, 0x0 191 | ; push rdi 192 | ; syscall 193 | // open 194 | ; mov rax, 0x2 195 | 196 | ; lea rdi, [-> new_paths] 197 | ; add r15, 8 * 4 // set r15 to point to path 198 | ; add rdi, QWORD [r14+r15] // path 199 | ; sub r15, 8 * 4 200 | 201 | ; mov rsi, libc::O_RDWR 202 | ; mov rdx, 0x0 203 | ; syscall 204 | ; pop rdi // addr 205 | ; push rax 206 | ; mov r8, rax // fd 207 | // mmap 208 | ; mov rax, 0x9 209 | ; add r15, 8 210 | ; mov rsi, QWORD [r14+r15] // length 211 | ; add r15, 8 212 | ; mov rdx, QWORD [r14+r15] // prot 213 | ; add r15, 8 214 | ; mov r10, QWORD [r14+r15] // flags 215 | ; add r15, 16 216 | ; mov r9, QWORD [r14+r15] // offset 217 | ; syscall 218 | ; sub r15, 8 * 5 219 | // close 220 | ; mov rax, 0x3 221 | ; pop rdi 222 | ; syscall 223 | 224 | ; add r15, std::mem::size_of::() as i32 225 | ; ->end: 226 | ; mov r13, QWORD [->cases_length] 227 | ; cmp r15, r13 228 | ; jb ->start 229 | 230 | ; int3 231 | ); 232 | 233 | let instructions = vec_rt.finalize()?; 234 | 235 | Ok((replace.0 as u64, instructions)) 236 | })?; 237 | 238 | trace!("reopen successfully"); 239 | Ok(()) 240 | } 241 | } 242 | 243 | fn get_prot_and_flags_from_perms>(perms: S) -> (u64, u64) { 244 | let bytes = perms.as_ref().as_bytes(); 245 | let mut prot = ProtFlags::empty(); 246 | let mut flags = MapFlags::MAP_PRIVATE; 247 | 248 | if bytes[0] == b'r' { 249 | prot |= ProtFlags::PROT_READ 250 | } 251 | if bytes[1] == b'w' { 252 | prot |= ProtFlags::PROT_WRITE 253 | } 254 | if bytes[2] == b'x' { 255 | prot |= ProtFlags::PROT_EXEC 256 | } 257 | if bytes[3] == b's' { 258 | flags = MapFlags::MAP_SHARED; 259 | } 260 | 261 | trace!( 262 | "perms: {}, prot: {:?}, flags: {:?}", 263 | perms.as_ref(), 264 | prot, 265 | flags 266 | ); 267 | (prot.bits() as u64, flags.bits() as u64) 268 | } 269 | 270 | pub struct MmapReplacer { 271 | processes: HashMap, 272 | } 273 | 274 | impl MmapReplacer { 275 | pub fn prepare, P2: AsRef>( 276 | detect_path: P1, 277 | new_path: P2, 278 | ) -> Result { 279 | info!("preparing mmap replacer"); 280 | 281 | let detect_path = detect_path.as_ref(); 282 | let new_path = new_path.as_ref(); 283 | 284 | let processes = all_processes()? 285 | .filter_map(|process| -> Option<_> { 286 | let pid = process.pid; 287 | 288 | let traced_process = ptrace::trace(pid).ok()?; 289 | let maps = process.maps().ok()?; 290 | 291 | Some((traced_process, maps)) 292 | }) 293 | .flat_map(|(process, maps)| { 294 | maps.into_iter() 295 | .filter_map(move |entry| { 296 | match entry.pathname { 297 | MMapPath::Path(path) => { 298 | let (start_address, end_address) = entry.address; 299 | let length = end_address - start_address; 300 | let (prot, flags) = get_prot_and_flags_from_perms(entry.perms); 301 | // TODO: extract permission from perms 302 | 303 | let case = ReplaceCase { 304 | memory_addr: start_address, 305 | length, 306 | prot, 307 | flags, 308 | path, 309 | offset: entry.offset, 310 | }; 311 | Some((process.clone(), case)) 312 | } 313 | _ => None, 314 | } 315 | }) 316 | .filter(|(_, case)| case.path.starts_with(detect_path)) 317 | .filter_map(|(process, mut case)| { 318 | let stripped_path = case.path.strip_prefix(&detect_path).ok()?; 319 | case.path = new_path.join(stripped_path); 320 | Some((process, case)) 321 | }) 322 | }) 323 | .group_by(|(process, _)| process.pid) 324 | .into_iter() 325 | .filter_map(|(pid, group)| Some((ptrace::trace(pid).ok()?, group))) 326 | .map(|(process, group)| (process, group.map(|(_, group)| group))) 327 | .filter_map(|(process, group)| { 328 | let pid = process.pid; 329 | 330 | match group.collect::().build(process) { 331 | Ok(accessor) => Some((pid, accessor)), 332 | Err(err) => { 333 | error!("fail to build accessor: {:?}", err); 334 | None 335 | } 336 | } 337 | }) 338 | .collect(); 339 | 340 | Ok(MmapReplacer { processes }) 341 | } 342 | } 343 | 344 | impl Replacer for MmapReplacer { 345 | fn run(&mut self) -> Result<()> { 346 | info!("running mmap replacer"); 347 | for (_, accessor) in self.processes.iter_mut() { 348 | accessor.run()?; 349 | } 350 | 351 | Ok(()) 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /src/replacer/mod.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use anyhow::Result; 4 | 5 | use crate::ptrace; 6 | 7 | mod cwd_replacer; 8 | mod fd_replacer; 9 | mod mmap_replacer; 10 | mod utils; 11 | 12 | use tracing::error; 13 | 14 | pub trait Replacer { 15 | fn run(&mut self) -> Result<()>; 16 | } 17 | 18 | #[derive(Default)] 19 | pub struct UnionReplacer<'a> { 20 | replacers: Vec>, 21 | } 22 | 23 | impl<'a> UnionReplacer<'a> { 24 | pub fn prepare, P2: AsRef>( 25 | &mut self, 26 | detect_path: P1, 27 | new_path: P2, 28 | ) -> Result<()> { 29 | match FdReplacer::prepare(&detect_path, &new_path) { 30 | Err(err) => error!("Error while preparing fd replacer: {:?}", err), 31 | Ok(replacer) => self.replacers.push(Box::new(replacer)), 32 | } 33 | match CwdReplacer::prepare(&detect_path, &new_path) { 34 | Err(err) => error!("Error while preparing cwd replacer: {:?}", err), 35 | Ok(replacer) => self.replacers.push(Box::new(replacer)), 36 | } 37 | match MmapReplacer::prepare(&detect_path, &new_path) { 38 | Err(err) => error!("Error while preparing mmap replacer: {:?}", err), 39 | Ok(replacer) => self.replacers.push(Box::new(replacer)), 40 | } 41 | Ok(()) 42 | } 43 | } 44 | 45 | impl<'a> Replacer for UnionReplacer<'a> { 46 | fn run(&mut self) -> Result<()> { 47 | for replacer in self.replacers.iter_mut() { 48 | replacer.run()?; 49 | } 50 | 51 | Ok(()) 52 | } 53 | } 54 | 55 | pub use cwd_replacer::CwdReplacer; 56 | pub use fd_replacer::FdReplacer; 57 | pub use mmap_replacer::MmapReplacer; 58 | -------------------------------------------------------------------------------- /src/replacer/utils.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use procfs::process::{self, Process}; 3 | 4 | pub fn all_processes() -> Result> { 5 | Ok(process::all_processes()? 6 | .into_iter() 7 | .filter(|process| -> bool { 8 | if let Ok(cmdline) = process.cmdline() { 9 | !cmdline.iter().map(|stat| stat.contains("toda")).any(|x| x) 10 | } else { 11 | true 12 | } 13 | })) 14 | } 15 | -------------------------------------------------------------------------------- /src/stop.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Condvar, Mutex}; 2 | 3 | struct Stop { 4 | inner: Mutex, 5 | condvar: Condvar, 6 | } 7 | 8 | impl Stop { 9 | fn new() -> Stop { 10 | Stop { 11 | inner: Mutex::new(false), 12 | condvar: Condvar::new(), 13 | } 14 | } 15 | fn wait(&self) { 16 | let mut inner = self.inner.lock().unwrap(); 17 | while !*inner { 18 | inner = self.condvar.wait(inner).unwrap(); 19 | } 20 | } 21 | fn wake(&self) { 22 | let mut inner = self.inner.lock().unwrap(); 23 | 24 | *inner = true; 25 | self.condvar.notify_one(); 26 | } 27 | } 28 | 29 | pub struct StopGuard { 30 | stop: Arc, 31 | } 32 | 33 | impl StopGuard { 34 | fn new(stop: Arc) -> StopGuard { 35 | StopGuard { stop } 36 | } 37 | } 38 | 39 | impl Drop for StopGuard { 40 | fn drop(&mut self) { 41 | self.stop.wake() 42 | } 43 | } 44 | 45 | pub struct StopWaiter { 46 | stop: Arc, 47 | } 48 | 49 | impl StopWaiter { 50 | fn new(stop: Arc) -> StopWaiter { 51 | StopWaiter { stop } 52 | } 53 | 54 | pub fn wait(&self) { 55 | self.stop.wait() 56 | } 57 | } 58 | 59 | pub fn lock() -> (StopWaiter, StopGuard) { 60 | let stop = Arc::new(Stop::new()); 61 | 62 | (StopWaiter::new(stop.clone()), StopGuard::new(stop)) 63 | } 64 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | use anyhow::{anyhow, Result}; 4 | 5 | pub fn encode_path>(original_path: P) -> Result<(PathBuf, PathBuf)> { 6 | let original_path: PathBuf = original_path.as_ref().to_owned(); 7 | 8 | let mut base_path: PathBuf = original_path.clone(); 9 | if !base_path.pop() { 10 | return Err(anyhow!("path is the root")); 11 | } 12 | 13 | let mut new_path: PathBuf = base_path; 14 | let original_filename = original_path 15 | .file_name() 16 | .ok_or(anyhow!("the path terminates in `..` or `/`"))? 17 | .to_str() 18 | .ok_or(anyhow!("path with non-UTF-8 character"))?; 19 | let new_filename = format!("__chaosfs__{}__", original_filename); 20 | new_path.push(new_filename.as_str()); 21 | 22 | Ok((original_path, new_path)) 23 | } 24 | -------------------------------------------------------------------------------- /tests/jsonrpc_test.rs: -------------------------------------------------------------------------------- 1 | use std::sync::mpsc::channel; 2 | use std::sync::Mutex; 3 | 4 | use anyhow::anyhow; 5 | use toda::jsonrpc::{self, new_handler, Comm}; 6 | #[test] 7 | fn test_status_good() { 8 | let (tx, _rx) = channel(); 9 | let io = new_handler(jsonrpc::RpcImpl::new( 10 | Mutex::new(Ok(())), 11 | Mutex::new(tx), 12 | None, 13 | )); 14 | let request = r#"{"jsonrpc": "2.0","method":"get_status","params":[""],"id":1}"#; 15 | let response = r#"{"jsonrpc":"2.0","result":"ok","id":1}"#; 16 | assert_eq!(io.handle_request_sync(request), Some(response.to_string())); 17 | } 18 | 19 | #[test] 20 | fn test_status_bad() { 21 | let (tx, rx) = channel(); 22 | let io = new_handler(jsonrpc::RpcImpl::new( 23 | Mutex::new(Err(anyhow!("Not good"))), 24 | Mutex::new(tx), 25 | None, 26 | )); 27 | let request = r#"{"jsonrpc": "2.0","method":"get_status","params":[""],"id":1}"#; 28 | let response = r#"{"jsonrpc":"2.0","result":"Not good","id":1}"#; 29 | assert_eq!(io.handle_request_sync(request), Some(response.to_string())); 30 | assert_eq!(rx.recv().unwrap(), Comm::Shutdown); 31 | } 32 | 33 | #[test] 34 | fn test_should_not_update_config_if_status_is_failed() { 35 | let (tx, _rx) = channel(); 36 | let request = r#"{"jsonrpc": "2.0","method":"update","params":[[]],"id":1}"#; 37 | let response = r#"{"jsonrpc":"2.0","result":"Not good","id":1}"#; 38 | let io = new_handler(jsonrpc::RpcImpl::new( 39 | Mutex::new(Err(anyhow!("Not good"))), 40 | Mutex::new(tx), 41 | None, 42 | )); 43 | assert_eq!(io.handle_request_sync(request), Some(response.to_string())); 44 | } 45 | 46 | #[test] 47 | fn test_should_fail_if_config_is_bad() { 48 | let (tx, _rx) = channel(); 49 | let request = r#"{"jsonrpc": "2.0","method":"update","params":[["blah"]],"id":1}"#; 50 | let response = r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid params: invalid type: string \"blah\", expected internally tagged enum."},"id":1}"#; 51 | let io = new_handler(jsonrpc::RpcImpl::new( 52 | Mutex::new(Ok(())), 53 | Mutex::new(tx), 54 | None, 55 | )); 56 | assert_eq!(io.handle_request_sync(request), Some(response.to_string())); 57 | } 58 | -------------------------------------------------------------------------------- /tests/posix_test.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chaos Mesh Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | use std::ffi::OsStr; 15 | use std::fs::{read_link, read_to_string, write, File, OpenOptions}; 16 | use std::io::{Read, Write}; 17 | use std::os::unix::fs::symlink; 18 | use std::path::PathBuf; 19 | use std::sync::{Arc, Once}; 20 | 21 | use nix::sys::stat; 22 | use nix::{fcntl, unistd}; 23 | use toda::hookfs; 24 | use toda::injector::MultiInjector; 25 | 26 | // These tests are port from go-fuse test 27 | 28 | static INIT: Once = Once::new(); 29 | 30 | fn init(name: &str) -> (PathBuf, fuser::BackgroundSession) { 31 | let test_path_backend: PathBuf = ["/tmp/test_mnt_backend", name].iter().collect(); 32 | let test_path: PathBuf = ["/tmp/test_mnt", name].iter().collect(); 33 | 34 | INIT.call_once(|| { 35 | env_logger::init(); 36 | }); 37 | 38 | std::fs::remove_dir_all(&test_path_backend).ok(); 39 | std::fs::remove_dir_all(&test_path).ok(); 40 | 41 | std::fs::create_dir_all(&test_path_backend).ok(); 42 | std::fs::create_dir_all(&test_path).ok(); 43 | 44 | let hookfs = Arc::new(hookfs::HookFs::new( 45 | &test_path, 46 | &test_path_backend, 47 | MultiInjector::build(Vec::new()).unwrap(), 48 | )); 49 | 50 | let fs = hookfs::AsyncFileSystem::from(hookfs); 51 | 52 | let args = [ 53 | "allow_other", 54 | "nonempty", 55 | "fsname=toda", 56 | "default_permissions", 57 | ]; 58 | let flags: Vec<_> = args 59 | .iter() 60 | .flat_map(|item| vec![OsStr::new("-o"), OsStr::new(item)]) 61 | .collect(); 62 | 63 | let session = fuser::spawn_mount(fs, &test_path, &flags).unwrap(); 64 | std::thread::sleep(std::time::Duration::from_secs(1)); 65 | (test_path, session) 66 | } 67 | 68 | #[test] 69 | fn symlink_readlink() { 70 | let (test_path, _) = init("symlink_readlink"); 71 | 72 | let expected_src = PathBuf::from("/foobar"); 73 | let dst: PathBuf = test_path.join("dst"); 74 | symlink(&expected_src, &dst).unwrap(); 75 | 76 | let src = read_link(&dst).unwrap(); 77 | 78 | assert_eq!(src, expected_src); 79 | } 80 | 81 | #[test] 82 | fn file_basic() { 83 | let (test_path, _) = init("file_basic"); 84 | 85 | let content = "hello world"; 86 | let target_file: PathBuf = test_path.join("target_file"); 87 | 88 | write(&target_file, content).unwrap(); 89 | 90 | let read_output = read_to_string(&target_file).unwrap(); 91 | assert_eq!(read_output, content); 92 | 93 | let file = File::open(&target_file).unwrap(); 94 | 95 | let stat = file.metadata().unwrap(); 96 | assert_eq!(stat.len() as usize, content.len()); 97 | 98 | drop(file); 99 | } 100 | 101 | #[test] 102 | fn truncate_file() { 103 | let (test_path, _) = init("truncate_file"); 104 | 105 | let content = b"hello world"; 106 | let target_file: PathBuf = test_path.join("target_file"); 107 | 108 | write(&target_file, content).unwrap(); 109 | 110 | let file = OpenOptions::new() 111 | .create(true) 112 | .write(true) 113 | .read(true) 114 | .open(&target_file) 115 | .unwrap(); 116 | 117 | let trunc = 5; 118 | file.set_len(trunc).unwrap(); 119 | drop(file); 120 | 121 | let read_output = read_to_string(&target_file).unwrap(); 122 | assert_eq!(read_output.as_bytes(), &content[..(trunc as usize)]); 123 | } 124 | 125 | #[test] 126 | fn mkdir_rmdir() { 127 | let (test_path, _) = init("mkdir_rmdir"); 128 | 129 | let dir: PathBuf = test_path.join("dir"); 130 | 131 | std::fs::create_dir(&dir).unwrap(); 132 | 133 | let file = File::open(&dir).unwrap(); 134 | let is_dir = file.metadata().unwrap().is_dir(); 135 | assert!(is_dir); 136 | 137 | std::fs::remove_dir(&dir).unwrap(); 138 | } 139 | 140 | #[test] 141 | fn nlink_zero() { 142 | let (test_path, _) = init("nlink_zero"); 143 | 144 | let src: PathBuf = test_path.join("src"); 145 | let dst: PathBuf = test_path.join("dst"); 146 | 147 | write(&src, "source").unwrap(); 148 | write(&dst, "dst").unwrap(); 149 | 150 | let fd = fcntl::open(&dst, fcntl::OFlag::empty(), stat::Mode::empty()).unwrap(); 151 | let st = stat::fstat(fd).unwrap(); 152 | 153 | assert_eq!(st.st_nlink, 1); 154 | 155 | fcntl::renameat(None, &src, None, &dst).unwrap(); 156 | let st = stat::fstat(fd).unwrap(); 157 | 158 | assert_eq!(st.st_nlink, 0); 159 | } 160 | 161 | // FstatDeleted is similar to NlinkZero, but Fstat()s multiple deleted files 162 | // in random order and checks that the results match an earlier Stat(). 163 | // 164 | // Excercises the fd-finding logic in rawBridge.GetAttr. 165 | #[test] 166 | fn fstat_deleted() { 167 | let (test_path, _) = init("fstat_deleted"); 168 | 169 | let i_max = 9; 170 | 171 | struct StatFile { 172 | pub file: std::os::unix::io::RawFd, 173 | pub stat: stat::FileStat, 174 | } 175 | let mut files = std::collections::HashMap::::new(); 176 | 177 | for i in 0..(i_max + 1) { 178 | let path: PathBuf = test_path.join(&format!("fstat_deleted_{}", i)); 179 | let content = vec![0u8; i]; 180 | write(&path, content).unwrap(); 181 | 182 | let st = stat::stat(&path).unwrap(); 183 | 184 | let fd = fcntl::open(&path, fcntl::OFlag::empty(), stat::Mode::empty()).unwrap(); 185 | let stat_file = StatFile { file: fd, stat: st }; 186 | files.insert(i, stat_file); 187 | 188 | unistd::unlink(&path).unwrap(); 189 | } 190 | 191 | for (_, f) in files.iter_mut() { 192 | let mut stat = stat::fstat(f.file).unwrap(); 193 | 194 | f.stat.st_nlink = 0; 195 | 196 | // ignore ctime, which changes on unlink 197 | f.stat.st_ctime_nsec = 0; 198 | f.stat.st_ctime = 0; 199 | stat.st_ctime_nsec = 0; 200 | stat.st_ctime = 0; 201 | 202 | assert_eq!(stat, f.stat); 203 | } 204 | } 205 | 206 | #[test] 207 | fn parallel_file_open() { 208 | let (test_path, _) = init("parallel_file_open"); 209 | 210 | let n = 10; 211 | 212 | let path: PathBuf = test_path.join("parallel_file_open_file"); 213 | 214 | write(&path, "content").unwrap(); 215 | 216 | let mut handlers = Vec::new(); 217 | for i in 0..n { 218 | let path = path.clone(); 219 | 220 | let handler = std::thread::spawn(move || { 221 | let mut file = OpenOptions::new() 222 | .read(true) 223 | .write(true) 224 | .open(&path) 225 | .unwrap(); 226 | 227 | let mut buf = [0u8; 7]; 228 | file.read_exact(&mut buf).unwrap(); 229 | buf[0] = i; 230 | file.write_all(&buf[0..1]).unwrap(); 231 | drop(file); 232 | }); 233 | 234 | handlers.push(handler); 235 | } 236 | 237 | for handler in handlers { 238 | handler.join().unwrap(); 239 | } 240 | } 241 | 242 | #[test] 243 | fn link() { 244 | let (test_path, _) = init("parallel_file_open"); 245 | let link = test_path.join("link"); 246 | let target = test_path.join("target"); 247 | 248 | write(&target, "hello").unwrap(); 249 | let st = stat::stat(&target).unwrap(); 250 | assert_eq!(st.st_nlink, 1); 251 | 252 | let before_ino = st.st_ino; 253 | std::fs::hard_link(&target, &link).unwrap(); 254 | 255 | let st = stat::stat(&link).unwrap(); 256 | assert_eq!(st.st_ino, before_ino); 257 | assert_eq!(st.st_nlink, 2); 258 | } 259 | 260 | #[test] 261 | fn rename_overwrite_dest_no_exist() { 262 | let (test_path, _) = init("rename_overwrite_dest_no_exist"); 263 | rename_overwrite(test_path, false) 264 | } 265 | 266 | #[test] 267 | fn rename_overwrite_dest_exist() { 268 | let (test_path, _) = init("rename_overwrite_dest_exist"); 269 | rename_overwrite(test_path, true) 270 | } 271 | 272 | fn rename_overwrite(test_path: PathBuf, dest_exist: bool) { 273 | let dir = test_path.join("dir"); 274 | let dest = dir.join("renamed"); 275 | let src = test_path.join("file"); 276 | 277 | std::fs::create_dir(dir).unwrap(); 278 | 279 | write(&src, "hello").unwrap(); 280 | 281 | if dest_exist { 282 | write(&dest, "xx").unwrap(); 283 | } 284 | 285 | let st = stat::stat(&src).unwrap(); 286 | let before_ino = st.st_ino; 287 | 288 | fcntl::renameat(None, &src, None, &dest).unwrap(); 289 | let st = stat::stat(&dest).unwrap(); 290 | 291 | assert_eq!(before_ino, st.st_ino); 292 | } 293 | 294 | #[test] 295 | fn read_unlink() { 296 | let (test_path, _) = init("rename_overwrite_dest_no_exist"); 297 | let path = test_path.join("file"); 298 | write(&path, "test content").unwrap(); 299 | 300 | let mut file = File::open(&path).unwrap(); 301 | unistd::unlink(&path).unwrap(); 302 | 303 | let mut content = String::new(); 304 | file.read_to_string(&mut content).unwrap(); 305 | assert_eq!(content, "test content"); 306 | } 307 | 308 | #[test] 309 | fn append_write() { 310 | let (test_path, _) = init("append_write"); 311 | let path = test_path.join("file"); 312 | let mut file = OpenOptions::new() 313 | .append(true) 314 | .write(true) 315 | .create(true) 316 | .open(&path) 317 | .unwrap(); 318 | 319 | file.write_all(b"hello").unwrap(); 320 | file.write_all(b" world").unwrap(); 321 | 322 | drop(file); 323 | 324 | let output = read_to_string(&path).unwrap(); 325 | assert_eq!(output, "hello world"); 326 | } 327 | 328 | #[test] 329 | fn append_unlink_write() { 330 | let (test_path, _) = init("append_unlink_write"); 331 | let path = test_path.join("file"); 332 | let mut file = OpenOptions::new() 333 | .append(true) 334 | .write(true) 335 | .create(true) 336 | .open(&path) 337 | .unwrap(); 338 | let mut read_file = File::open(&path).unwrap(); 339 | 340 | file.write_all(b"hello").unwrap(); 341 | unistd::unlink(&path).unwrap(); 342 | file.write_all(b" world").unwrap(); 343 | 344 | drop(file); 345 | 346 | let mut output = String::new(); 347 | read_file.read_to_string(&mut output).unwrap(); 348 | assert_eq!(&output, "hello world"); 349 | } 350 | 351 | // func RenameOpenDir(t *testing.T, mnt string) { 352 | // if err := os.Mkdir(mnt+"/dir1", 0755); err != nil { 353 | // t.Fatalf("Mkdir: %v", err) 354 | // } 355 | // // Different permissions so directories are easier to tell apart 356 | // if err := os.Mkdir(mnt+"/dir2", 0700); err != nil { 357 | // t.Fatalf("Mkdir: %v", err) 358 | // } 359 | 360 | // var st1 syscall.Stat_t 361 | // if err := syscall.Stat(mnt+"/dir2", &st1); err != nil { 362 | // t.Fatalf("Stat: %v", err) 363 | // } 364 | 365 | // fd, err := syscall.Open(mnt+"/dir2", syscall.O_RDONLY, 0) 366 | // if err != nil { 367 | // t.Fatalf("Open: %v", err) 368 | // } 369 | // defer syscall.Close(fd) 370 | // if err := syscall.Rename(mnt+"/dir1", mnt+"/dir2"); err != nil { 371 | // t.Fatalf("Rename: %v", err) 372 | // } 373 | 374 | // var st2 syscall.Stat_t 375 | // if err := syscall.Fstat(fd, &st2); err != nil { 376 | // t.Skipf("Fstat failed: %v. Known limitation - see https://github.com/hanwen/go-fuse/issues/55", err) 377 | // } 378 | // if st2.Mode&syscall.S_IFMT != syscall.S_IFDIR { 379 | // t.Errorf("got mode %o, want %o", st2.Mode, syscall.S_IFDIR) 380 | // } 381 | // if st2.Ino != st1.Ino { 382 | // t.Errorf("got ino %d, want %d", st2.Ino, st1.Ino) 383 | // } 384 | // if st2.Mode&0777 != st1.Mode&0777 { 385 | // t.Skipf("got permissions %#o, want %#o. Known limitation - see https://github.com/hanwen/go-fuse/issues/55", 386 | // st2.Mode&0777, st1.Mode&0777) 387 | // } 388 | // } 389 | 390 | // // ReadDir creates 110 files one by one, checking that we get the expected 391 | // // entries after each file creation. 392 | // func ReadDir(t *testing.T, mnt string) { 393 | // want := map[string]bool{} 394 | // // 40 bytes of filename, so 110 entries overflows a 395 | // // 4096 page. 396 | // for i := 0; i < 110; i++ { 397 | // nm := fmt.Sprintf("file%036x", i) 398 | // want[nm] = true 399 | // if err := ioutil.WriteFile(filepath.Join(mnt, nm), []byte("hello"), 0644); err != nil { 400 | // t.Fatalf("WriteFile %q: %v", nm, err) 401 | // } 402 | // // Verify that we get the expected entries 403 | // f, err := os.Open(mnt) 404 | // if err != nil { 405 | // t.Fatalf("Open: %v", err) 406 | // } 407 | // names, err := f.Readdirnames(-1) 408 | // if err != nil { 409 | // t.Fatalf("ReadDir: %v", err) 410 | // } 411 | // f.Close() 412 | // got := map[string]bool{} 413 | // for _, e := range names { 414 | // got[e] = true 415 | // } 416 | // if len(got) != len(want) { 417 | // t.Errorf("got %d entries, want %d", len(got), len(want)) 418 | // } 419 | // for k := range got { 420 | // if !want[k] { 421 | // t.Errorf("got unknown name %q", k) 422 | // } 423 | // } 424 | // } 425 | // } 426 | 427 | // // Readdir should pick file created after open, but before readdir. 428 | // func ReadDirPicksUpCreate(t *testing.T, mnt string) { 429 | // f, err := os.Open(mnt) 430 | // if err != nil { 431 | // t.Fatalf("Open: %v", err) 432 | // } 433 | 434 | // if err := ioutil.WriteFile(mnt+"/file", []byte{42}, 0644); err != nil { 435 | // t.Fatalf("WriteFile: %v", err) 436 | // } 437 | // names, err := f.Readdirnames(-1) 438 | // if err != nil { 439 | // t.Fatalf("ReadDir: %v", err) 440 | // } 441 | // f.Close() 442 | 443 | // if len(names) != 1 || names[0] != "file" { 444 | // t.Errorf("missing file created after opendir") 445 | // } 446 | // } 447 | 448 | // // LinkUnlinkRename implements rename with a link/unlink sequence 449 | // func LinkUnlinkRename(t *testing.T, mnt string) { 450 | // content := []byte("hello") 451 | // tmp := mnt + "/tmpfile" 452 | // if err := ioutil.WriteFile(tmp, content, 0644); err != nil { 453 | // t.Fatalf("WriteFile %q: %v", tmp, err) 454 | // } 455 | 456 | // dest := mnt + "/file" 457 | // if err := syscall.Link(tmp, dest); err != nil { 458 | // t.Fatalf("Link %q %q: %v", tmp, dest, err) 459 | // } 460 | // if err := syscall.Unlink(tmp); err != nil { 461 | // t.Fatalf("Unlink %q: %v", tmp, err) 462 | // } 463 | 464 | // if back, err := ioutil.ReadFile(dest); err != nil { 465 | // t.Fatalf("Read %q: %v", dest, err) 466 | // } else if bytes.Compare(back, content) != 0 { 467 | // t.Fatalf("Read got %q want %q", back, content) 468 | // } 469 | // } 470 | --------------------------------------------------------------------------------