├── .cargo └── config.toml ├── .dir-locals.el ├── .gitignore ├── .vim └── coc-settings.json ├── .vscode └── settings.json ├── .whitesource ├── Cargo.lock ├── Cargo.toml ├── INSTALL.md ├── README.md ├── etc └── daemon.toml ├── ttyrecall-common ├── Cargo.toml ├── LICENSE └── src │ └── lib.rs ├── ttyrecall-ebpf ├── .cargo │ └── config.toml ├── .helix │ └── config.toml ├── .vim │ └── coc-settings.json ├── .vscode │ └── settings.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── rust-toolchain.toml └── src │ ├── generate.sh │ ├── generated_vmlinux.rs │ └── main.rs ├── ttyrecall.code-workspace ├── ttyrecall ├── Cargo.toml ├── LICENSE └── src │ ├── cli.rs │ ├── daemon.rs │ ├── daemon │ └── config.rs │ ├── main.rs │ ├── manager.rs │ └── session.rs └── xtask ├── Cargo.toml ├── LICENSE └── src ├── build.rs ├── build_ebpf.rs ├── main.rs └── run.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --package xtask --" 3 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((prog-mode . ((lsp-rust-analyzer-linked-projects . ["Cargo.toml" "ttyrecall-ebpf/Cargo.toml"])))) 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### https://raw.github.com/github/gitignore/master/Rust.gitignore 2 | 3 | # Generated by Cargo 4 | # will have compiled files and executables 5 | debug/ 6 | target/ 7 | 8 | # These are backup files generated by rustfmt 9 | **/*.rs.bk 10 | -------------------------------------------------------------------------------- /.vim/coc-settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.linkedProjects": ["Cargo.toml", "ttyrecall-ebpf/Cargo.toml"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.linkedProjects": ["Cargo.toml", "ttyrecall-ebpf/Cargo.toml"] 3 | } 4 | -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "scanSettings": { 3 | "baseBranches": [] 4 | }, 5 | "checkRunSettings": { 6 | "vulnerableCheckRunConclusionLevel": "failure", 7 | "displayMode": "diff", 8 | "useMendCheckNames": true 9 | }, 10 | "issueSettings": { 11 | "minSeverityLevel": "LOW", 12 | "issueType": "DEPENDENCY" 13 | } 14 | } -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "ahash" 22 | version = "0.8.11" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 25 | dependencies = [ 26 | "cfg-if", 27 | "once_cell", 28 | "version_check", 29 | "zerocopy", 30 | ] 31 | 32 | [[package]] 33 | name = "aho-corasick" 34 | version = "1.1.3" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 37 | dependencies = [ 38 | "memchr", 39 | ] 40 | 41 | [[package]] 42 | name = "allocator-api2" 43 | version = "0.2.18" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" 46 | 47 | [[package]] 48 | name = "android-tzdata" 49 | version = "0.1.1" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 52 | 53 | [[package]] 54 | name = "android_system_properties" 55 | version = "0.1.5" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 58 | dependencies = [ 59 | "libc", 60 | ] 61 | 62 | [[package]] 63 | name = "anstream" 64 | version = "0.6.15" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" 67 | dependencies = [ 68 | "anstyle", 69 | "anstyle-parse", 70 | "anstyle-query", 71 | "anstyle-wincon", 72 | "colorchoice", 73 | "is_terminal_polyfill", 74 | "utf8parse", 75 | ] 76 | 77 | [[package]] 78 | name = "anstyle" 79 | version = "1.0.8" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" 82 | 83 | [[package]] 84 | name = "anstyle-parse" 85 | version = "0.2.5" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" 88 | dependencies = [ 89 | "utf8parse", 90 | ] 91 | 92 | [[package]] 93 | name = "anstyle-query" 94 | version = "1.1.1" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" 97 | dependencies = [ 98 | "windows-sys 0.52.0", 99 | ] 100 | 101 | [[package]] 102 | name = "anstyle-wincon" 103 | version = "3.0.4" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" 106 | dependencies = [ 107 | "anstyle", 108 | "windows-sys 0.52.0", 109 | ] 110 | 111 | [[package]] 112 | name = "anyhow" 113 | version = "1.0.89" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" 116 | 117 | [[package]] 118 | name = "assert_matches" 119 | version = "1.5.0" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" 122 | 123 | [[package]] 124 | name = "autocfg" 125 | version = "1.4.0" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 128 | 129 | [[package]] 130 | name = "aya" 131 | version = "0.12.0" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "90eea657cc8028447cbda5068f4e10c4fadba0131624f4f7dd1a9c46ffc8d81f" 134 | dependencies = [ 135 | "assert_matches", 136 | "aya-obj", 137 | "bitflags", 138 | "bytes", 139 | "lazy_static", 140 | "libc", 141 | "log", 142 | "object", 143 | "thiserror", 144 | "tokio", 145 | ] 146 | 147 | [[package]] 148 | name = "aya-log" 149 | version = "0.2.0" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "1f11a92f305b983e9f53433457dede617a4ad0aa22e4702220092f39e844c1a2" 152 | dependencies = [ 153 | "aya", 154 | "aya-log-common", 155 | "bytes", 156 | "log", 157 | "thiserror", 158 | "tokio", 159 | ] 160 | 161 | [[package]] 162 | name = "aya-log-common" 163 | version = "0.1.14" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "b6d38a351ee2d5dc24e04cac6184b1b39408642d9a8b585892c99146f8dd4edb" 166 | dependencies = [ 167 | "num_enum", 168 | ] 169 | 170 | [[package]] 171 | name = "aya-obj" 172 | version = "0.1.0" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "2c02024a307161cf3d1f052161958fd13b1a33e3e038083e58082c0700fdab85" 175 | dependencies = [ 176 | "bytes", 177 | "core-error", 178 | "hashbrown", 179 | "log", 180 | "object", 181 | "thiserror", 182 | ] 183 | 184 | [[package]] 185 | name = "backtrace" 186 | version = "0.3.71" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" 189 | dependencies = [ 190 | "addr2line", 191 | "cc", 192 | "cfg-if", 193 | "libc", 194 | "miniz_oxide", 195 | "object", 196 | "rustc-demangle", 197 | ] 198 | 199 | [[package]] 200 | name = "bitflags" 201 | version = "2.6.0" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 204 | 205 | [[package]] 206 | name = "bstr" 207 | version = "1.10.0" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" 210 | dependencies = [ 211 | "memchr", 212 | "regex-automata", 213 | "serde", 214 | ] 215 | 216 | [[package]] 217 | name = "bumpalo" 218 | version = "3.16.0" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 221 | 222 | [[package]] 223 | name = "bytes" 224 | version = "1.7.2" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" 227 | 228 | [[package]] 229 | name = "cc" 230 | version = "1.1.23" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "3bbb537bb4a30b90362caddba8f360c0a56bc13d3a5570028e7197204cb54a17" 233 | dependencies = [ 234 | "jobserver", 235 | "libc", 236 | "shlex", 237 | ] 238 | 239 | [[package]] 240 | name = "cfg-if" 241 | version = "1.0.0" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 244 | 245 | [[package]] 246 | name = "cfg_aliases" 247 | version = "0.2.1" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 250 | 251 | [[package]] 252 | name = "chrono" 253 | version = "0.4.38" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" 256 | dependencies = [ 257 | "android-tzdata", 258 | "iana-time-zone", 259 | "js-sys", 260 | "num-traits", 261 | "wasm-bindgen", 262 | "windows-targets", 263 | ] 264 | 265 | [[package]] 266 | name = "clap" 267 | version = "4.5.18" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" 270 | dependencies = [ 271 | "clap_builder", 272 | "clap_derive", 273 | ] 274 | 275 | [[package]] 276 | name = "clap_builder" 277 | version = "4.5.18" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" 280 | dependencies = [ 281 | "anstream", 282 | "anstyle", 283 | "clap_lex", 284 | "strsim", 285 | ] 286 | 287 | [[package]] 288 | name = "clap_complete" 289 | version = "4.5.32" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "74a01f4f9ee6c066d42a1c8dedf0dcddad16c72a8981a309d6398de3a75b0c39" 292 | dependencies = [ 293 | "clap", 294 | ] 295 | 296 | [[package]] 297 | name = "clap_derive" 298 | version = "4.5.18" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 301 | dependencies = [ 302 | "heck", 303 | "proc-macro2", 304 | "quote", 305 | "syn", 306 | ] 307 | 308 | [[package]] 309 | name = "clap_lex" 310 | version = "0.7.2" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" 313 | 314 | [[package]] 315 | name = "color-eyre" 316 | version = "0.6.3" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" 319 | dependencies = [ 320 | "backtrace", 321 | "color-spantrace", 322 | "eyre", 323 | "indenter", 324 | "once_cell", 325 | "owo-colors", 326 | "tracing-error", 327 | ] 328 | 329 | [[package]] 330 | name = "color-spantrace" 331 | version = "0.2.1" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" 334 | dependencies = [ 335 | "once_cell", 336 | "owo-colors", 337 | "tracing-core", 338 | "tracing-error", 339 | ] 340 | 341 | [[package]] 342 | name = "colorchoice" 343 | version = "1.0.2" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" 346 | 347 | [[package]] 348 | name = "core-error" 349 | version = "0.0.0" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "efcdb2972eb64230b4c50646d8498ff73f5128d196a90c7236eec4cbe8619b8f" 352 | dependencies = [ 353 | "version_check", 354 | ] 355 | 356 | [[package]] 357 | name = "core-foundation-sys" 358 | version = "0.8.7" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 361 | 362 | [[package]] 363 | name = "either" 364 | version = "1.13.0" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 367 | 368 | [[package]] 369 | name = "env_logger" 370 | version = "0.10.2" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" 373 | dependencies = [ 374 | "humantime", 375 | "is-terminal", 376 | "log", 377 | "regex", 378 | "termcolor", 379 | ] 380 | 381 | [[package]] 382 | name = "equivalent" 383 | version = "1.0.1" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 386 | 387 | [[package]] 388 | name = "eyre" 389 | version = "0.6.12" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" 392 | dependencies = [ 393 | "indenter", 394 | "once_cell", 395 | ] 396 | 397 | [[package]] 398 | name = "gimli" 399 | version = "0.28.1" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 402 | 403 | [[package]] 404 | name = "hashbrown" 405 | version = "0.14.5" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 408 | dependencies = [ 409 | "ahash", 410 | "allocator-api2", 411 | ] 412 | 413 | [[package]] 414 | name = "heck" 415 | version = "0.5.0" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 418 | 419 | [[package]] 420 | name = "hermit-abi" 421 | version = "0.3.9" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 424 | 425 | [[package]] 426 | name = "hermit-abi" 427 | version = "0.4.0" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" 430 | 431 | [[package]] 432 | name = "humantime" 433 | version = "2.1.0" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 436 | 437 | [[package]] 438 | name = "iana-time-zone" 439 | version = "0.1.61" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" 442 | dependencies = [ 443 | "android_system_properties", 444 | "core-foundation-sys", 445 | "iana-time-zone-haiku", 446 | "js-sys", 447 | "wasm-bindgen", 448 | "windows-core", 449 | ] 450 | 451 | [[package]] 452 | name = "iana-time-zone-haiku" 453 | version = "0.1.2" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 456 | dependencies = [ 457 | "cc", 458 | ] 459 | 460 | [[package]] 461 | name = "indenter" 462 | version = "0.3.3" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" 465 | 466 | [[package]] 467 | name = "indexmap" 468 | version = "2.5.0" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" 471 | dependencies = [ 472 | "equivalent", 473 | "hashbrown", 474 | ] 475 | 476 | [[package]] 477 | name = "is-terminal" 478 | version = "0.4.13" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" 481 | dependencies = [ 482 | "hermit-abi 0.4.0", 483 | "libc", 484 | "windows-sys 0.52.0", 485 | ] 486 | 487 | [[package]] 488 | name = "is_terminal_polyfill" 489 | version = "1.70.1" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 492 | 493 | [[package]] 494 | name = "itoa" 495 | version = "1.0.11" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 498 | 499 | [[package]] 500 | name = "jobserver" 501 | version = "0.1.32" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" 504 | dependencies = [ 505 | "libc", 506 | ] 507 | 508 | [[package]] 509 | name = "js-sys" 510 | version = "0.3.70" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" 513 | dependencies = [ 514 | "wasm-bindgen", 515 | ] 516 | 517 | [[package]] 518 | name = "lazy_static" 519 | version = "1.5.0" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 522 | 523 | [[package]] 524 | name = "libc" 525 | version = "0.2.171" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" 528 | 529 | [[package]] 530 | name = "log" 531 | version = "0.4.22" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 534 | 535 | [[package]] 536 | name = "memchr" 537 | version = "2.7.4" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 540 | 541 | [[package]] 542 | name = "miniz_oxide" 543 | version = "0.7.4" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" 546 | dependencies = [ 547 | "adler", 548 | ] 549 | 550 | [[package]] 551 | name = "mio" 552 | version = "1.0.2" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 555 | dependencies = [ 556 | "hermit-abi 0.3.9", 557 | "libc", 558 | "wasi", 559 | "windows-sys 0.52.0", 560 | ] 561 | 562 | [[package]] 563 | name = "nix" 564 | version = "0.29.0" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 567 | dependencies = [ 568 | "bitflags", 569 | "cfg-if", 570 | "cfg_aliases", 571 | "libc", 572 | ] 573 | 574 | [[package]] 575 | name = "num-traits" 576 | version = "0.2.19" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 579 | dependencies = [ 580 | "autocfg", 581 | ] 582 | 583 | [[package]] 584 | name = "num_enum" 585 | version = "0.7.3" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" 588 | dependencies = [ 589 | "num_enum_derive", 590 | ] 591 | 592 | [[package]] 593 | name = "num_enum_derive" 594 | version = "0.7.3" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" 597 | dependencies = [ 598 | "proc-macro2", 599 | "quote", 600 | "syn", 601 | ] 602 | 603 | [[package]] 604 | name = "object" 605 | version = "0.32.2" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 608 | dependencies = [ 609 | "memchr", 610 | ] 611 | 612 | [[package]] 613 | name = "once_cell" 614 | version = "1.19.0" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 617 | 618 | [[package]] 619 | name = "owo-colors" 620 | version = "3.5.0" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" 623 | 624 | [[package]] 625 | name = "pin-project-lite" 626 | version = "0.2.14" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 629 | 630 | [[package]] 631 | name = "pkg-config" 632 | version = "0.3.31" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 635 | 636 | [[package]] 637 | name = "proc-macro2" 638 | version = "1.0.86" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 641 | dependencies = [ 642 | "unicode-ident", 643 | ] 644 | 645 | [[package]] 646 | name = "quote" 647 | version = "1.0.37" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 650 | dependencies = [ 651 | "proc-macro2", 652 | ] 653 | 654 | [[package]] 655 | name = "regex" 656 | version = "1.10.6" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" 659 | dependencies = [ 660 | "aho-corasick", 661 | "memchr", 662 | "regex-automata", 663 | "regex-syntax", 664 | ] 665 | 666 | [[package]] 667 | name = "regex-automata" 668 | version = "0.4.7" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" 671 | dependencies = [ 672 | "aho-corasick", 673 | "memchr", 674 | "regex-syntax", 675 | ] 676 | 677 | [[package]] 678 | name = "regex-syntax" 679 | version = "0.8.4" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 682 | 683 | [[package]] 684 | name = "rustc-demangle" 685 | version = "0.1.24" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 688 | 689 | [[package]] 690 | name = "ryu" 691 | version = "1.0.18" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 694 | 695 | [[package]] 696 | name = "serde" 697 | version = "1.0.210" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" 700 | dependencies = [ 701 | "serde_derive", 702 | ] 703 | 704 | [[package]] 705 | name = "serde_derive" 706 | version = "1.0.210" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" 709 | dependencies = [ 710 | "proc-macro2", 711 | "quote", 712 | "syn", 713 | ] 714 | 715 | [[package]] 716 | name = "serde_json" 717 | version = "1.0.128" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" 720 | dependencies = [ 721 | "itoa", 722 | "memchr", 723 | "ryu", 724 | "serde", 725 | ] 726 | 727 | [[package]] 728 | name = "serde_spanned" 729 | version = "0.6.8" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 732 | dependencies = [ 733 | "serde", 734 | ] 735 | 736 | [[package]] 737 | name = "sharded-slab" 738 | version = "0.1.7" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 741 | dependencies = [ 742 | "lazy_static", 743 | ] 744 | 745 | [[package]] 746 | name = "shlex" 747 | version = "1.3.0" 748 | source = "registry+https://github.com/rust-lang/crates.io-index" 749 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 750 | 751 | [[package]] 752 | name = "signal-hook-registry" 753 | version = "1.4.2" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 756 | dependencies = [ 757 | "libc", 758 | ] 759 | 760 | [[package]] 761 | name = "socket2" 762 | version = "0.5.7" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 765 | dependencies = [ 766 | "libc", 767 | "windows-sys 0.52.0", 768 | ] 769 | 770 | [[package]] 771 | name = "strsim" 772 | version = "0.11.1" 773 | source = "registry+https://github.com/rust-lang/crates.io-index" 774 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 775 | 776 | [[package]] 777 | name = "syn" 778 | version = "2.0.77" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" 781 | dependencies = [ 782 | "proc-macro2", 783 | "quote", 784 | "unicode-ident", 785 | ] 786 | 787 | [[package]] 788 | name = "termcolor" 789 | version = "1.4.1" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 792 | dependencies = [ 793 | "winapi-util", 794 | ] 795 | 796 | [[package]] 797 | name = "thiserror" 798 | version = "1.0.64" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" 801 | dependencies = [ 802 | "thiserror-impl", 803 | ] 804 | 805 | [[package]] 806 | name = "thiserror-impl" 807 | version = "1.0.64" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" 810 | dependencies = [ 811 | "proc-macro2", 812 | "quote", 813 | "syn", 814 | ] 815 | 816 | [[package]] 817 | name = "thread_local" 818 | version = "1.1.8" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 821 | dependencies = [ 822 | "cfg-if", 823 | "once_cell", 824 | ] 825 | 826 | [[package]] 827 | name = "tokio" 828 | version = "1.43.1" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "492a604e2fd7f814268a378409e6c92b5525d747d10db9a229723f55a417958c" 831 | dependencies = [ 832 | "backtrace", 833 | "libc", 834 | "mio", 835 | "pin-project-lite", 836 | "signal-hook-registry", 837 | "socket2", 838 | "tokio-macros", 839 | "windows-sys 0.52.0", 840 | ] 841 | 842 | [[package]] 843 | name = "tokio-macros" 844 | version = "2.5.0" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 847 | dependencies = [ 848 | "proc-macro2", 849 | "quote", 850 | "syn", 851 | ] 852 | 853 | [[package]] 854 | name = "toml" 855 | version = "0.8.19" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" 858 | dependencies = [ 859 | "serde", 860 | "serde_spanned", 861 | "toml_datetime", 862 | "toml_edit", 863 | ] 864 | 865 | [[package]] 866 | name = "toml_datetime" 867 | version = "0.6.8" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 870 | dependencies = [ 871 | "serde", 872 | ] 873 | 874 | [[package]] 875 | name = "toml_edit" 876 | version = "0.22.22" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" 879 | dependencies = [ 880 | "indexmap", 881 | "serde", 882 | "serde_spanned", 883 | "toml_datetime", 884 | "winnow", 885 | ] 886 | 887 | [[package]] 888 | name = "tracing" 889 | version = "0.1.40" 890 | source = "registry+https://github.com/rust-lang/crates.io-index" 891 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 892 | dependencies = [ 893 | "pin-project-lite", 894 | "tracing-core", 895 | ] 896 | 897 | [[package]] 898 | name = "tracing-core" 899 | version = "0.1.32" 900 | source = "registry+https://github.com/rust-lang/crates.io-index" 901 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 902 | dependencies = [ 903 | "once_cell", 904 | "valuable", 905 | ] 906 | 907 | [[package]] 908 | name = "tracing-error" 909 | version = "0.2.0" 910 | source = "registry+https://github.com/rust-lang/crates.io-index" 911 | checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" 912 | dependencies = [ 913 | "tracing", 914 | "tracing-subscriber", 915 | ] 916 | 917 | [[package]] 918 | name = "tracing-subscriber" 919 | version = "0.3.18" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" 922 | dependencies = [ 923 | "sharded-slab", 924 | "thread_local", 925 | "tracing-core", 926 | ] 927 | 928 | [[package]] 929 | name = "ttyrecall" 930 | version = "0.1.0" 931 | dependencies = [ 932 | "aya", 933 | "aya-log", 934 | "bstr", 935 | "chrono", 936 | "clap", 937 | "clap_complete", 938 | "color-eyre", 939 | "either", 940 | "env_logger", 941 | "libc", 942 | "log", 943 | "nix", 944 | "serde", 945 | "serde_json", 946 | "thiserror", 947 | "tokio", 948 | "toml", 949 | "ttyrecall-common", 950 | "zstd", 951 | ] 952 | 953 | [[package]] 954 | name = "ttyrecall-common" 955 | version = "0.1.0" 956 | dependencies = [ 957 | "aya", 958 | ] 959 | 960 | [[package]] 961 | name = "unicode-ident" 962 | version = "1.0.13" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 965 | 966 | [[package]] 967 | name = "utf8parse" 968 | version = "0.2.2" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 971 | 972 | [[package]] 973 | name = "valuable" 974 | version = "0.1.0" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 977 | 978 | [[package]] 979 | name = "version_check" 980 | version = "0.9.5" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 983 | 984 | [[package]] 985 | name = "wasi" 986 | version = "0.11.0+wasi-snapshot-preview1" 987 | source = "registry+https://github.com/rust-lang/crates.io-index" 988 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 989 | 990 | [[package]] 991 | name = "wasm-bindgen" 992 | version = "0.2.93" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" 995 | dependencies = [ 996 | "cfg-if", 997 | "once_cell", 998 | "wasm-bindgen-macro", 999 | ] 1000 | 1001 | [[package]] 1002 | name = "wasm-bindgen-backend" 1003 | version = "0.2.93" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" 1006 | dependencies = [ 1007 | "bumpalo", 1008 | "log", 1009 | "once_cell", 1010 | "proc-macro2", 1011 | "quote", 1012 | "syn", 1013 | "wasm-bindgen-shared", 1014 | ] 1015 | 1016 | [[package]] 1017 | name = "wasm-bindgen-macro" 1018 | version = "0.2.93" 1019 | source = "registry+https://github.com/rust-lang/crates.io-index" 1020 | checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" 1021 | dependencies = [ 1022 | "quote", 1023 | "wasm-bindgen-macro-support", 1024 | ] 1025 | 1026 | [[package]] 1027 | name = "wasm-bindgen-macro-support" 1028 | version = "0.2.93" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" 1031 | dependencies = [ 1032 | "proc-macro2", 1033 | "quote", 1034 | "syn", 1035 | "wasm-bindgen-backend", 1036 | "wasm-bindgen-shared", 1037 | ] 1038 | 1039 | [[package]] 1040 | name = "wasm-bindgen-shared" 1041 | version = "0.2.93" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" 1044 | 1045 | [[package]] 1046 | name = "winapi-util" 1047 | version = "0.1.9" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 1050 | dependencies = [ 1051 | "windows-sys 0.59.0", 1052 | ] 1053 | 1054 | [[package]] 1055 | name = "windows-core" 1056 | version = "0.52.0" 1057 | source = "registry+https://github.com/rust-lang/crates.io-index" 1058 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 1059 | dependencies = [ 1060 | "windows-targets", 1061 | ] 1062 | 1063 | [[package]] 1064 | name = "windows-sys" 1065 | version = "0.52.0" 1066 | source = "registry+https://github.com/rust-lang/crates.io-index" 1067 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1068 | dependencies = [ 1069 | "windows-targets", 1070 | ] 1071 | 1072 | [[package]] 1073 | name = "windows-sys" 1074 | version = "0.59.0" 1075 | source = "registry+https://github.com/rust-lang/crates.io-index" 1076 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1077 | dependencies = [ 1078 | "windows-targets", 1079 | ] 1080 | 1081 | [[package]] 1082 | name = "windows-targets" 1083 | version = "0.52.6" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1086 | dependencies = [ 1087 | "windows_aarch64_gnullvm", 1088 | "windows_aarch64_msvc", 1089 | "windows_i686_gnu", 1090 | "windows_i686_gnullvm", 1091 | "windows_i686_msvc", 1092 | "windows_x86_64_gnu", 1093 | "windows_x86_64_gnullvm", 1094 | "windows_x86_64_msvc", 1095 | ] 1096 | 1097 | [[package]] 1098 | name = "windows_aarch64_gnullvm" 1099 | version = "0.52.6" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1102 | 1103 | [[package]] 1104 | name = "windows_aarch64_msvc" 1105 | version = "0.52.6" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1108 | 1109 | [[package]] 1110 | name = "windows_i686_gnu" 1111 | version = "0.52.6" 1112 | source = "registry+https://github.com/rust-lang/crates.io-index" 1113 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1114 | 1115 | [[package]] 1116 | name = "windows_i686_gnullvm" 1117 | version = "0.52.6" 1118 | source = "registry+https://github.com/rust-lang/crates.io-index" 1119 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1120 | 1121 | [[package]] 1122 | name = "windows_i686_msvc" 1123 | version = "0.52.6" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1126 | 1127 | [[package]] 1128 | name = "windows_x86_64_gnu" 1129 | version = "0.52.6" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1132 | 1133 | [[package]] 1134 | name = "windows_x86_64_gnullvm" 1135 | version = "0.52.6" 1136 | source = "registry+https://github.com/rust-lang/crates.io-index" 1137 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1138 | 1139 | [[package]] 1140 | name = "windows_x86_64_msvc" 1141 | version = "0.52.6" 1142 | source = "registry+https://github.com/rust-lang/crates.io-index" 1143 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1144 | 1145 | [[package]] 1146 | name = "winnow" 1147 | version = "0.6.20" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" 1150 | dependencies = [ 1151 | "memchr", 1152 | ] 1153 | 1154 | [[package]] 1155 | name = "xtask" 1156 | version = "0.1.0" 1157 | dependencies = [ 1158 | "anyhow", 1159 | "clap", 1160 | ] 1161 | 1162 | [[package]] 1163 | name = "zerocopy" 1164 | version = "0.7.35" 1165 | source = "registry+https://github.com/rust-lang/crates.io-index" 1166 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 1167 | dependencies = [ 1168 | "zerocopy-derive", 1169 | ] 1170 | 1171 | [[package]] 1172 | name = "zerocopy-derive" 1173 | version = "0.7.35" 1174 | source = "registry+https://github.com/rust-lang/crates.io-index" 1175 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 1176 | dependencies = [ 1177 | "proc-macro2", 1178 | "quote", 1179 | "syn", 1180 | ] 1181 | 1182 | [[package]] 1183 | name = "zstd" 1184 | version = "0.13.2" 1185 | source = "registry+https://github.com/rust-lang/crates.io-index" 1186 | checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" 1187 | dependencies = [ 1188 | "zstd-safe", 1189 | ] 1190 | 1191 | [[package]] 1192 | name = "zstd-safe" 1193 | version = "7.2.1" 1194 | source = "registry+https://github.com/rust-lang/crates.io-index" 1195 | checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" 1196 | dependencies = [ 1197 | "zstd-sys", 1198 | ] 1199 | 1200 | [[package]] 1201 | name = "zstd-sys" 1202 | version = "2.0.13+zstd.1.5.6" 1203 | source = "registry+https://github.com/rust-lang/crates.io-index" 1204 | checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" 1205 | dependencies = [ 1206 | "cc", 1207 | "pkg-config", 1208 | ] 1209 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["xtask", "ttyrecall", "ttyrecall-common"] 4 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Install 2 | 3 | ## Packages 4 | 5 | - AUR: `ttyrecall-git`(Uses git main branch) 6 | 7 | ## Prerequisites 8 | 9 | - Rust toolchain (optional) 10 | - Rust nightly toolchain with rust source (necessary for building eBPF). 11 | - bpf-linker: `cargo install bpf-linker` or `pacman -S bpf-linker` (Arch Linux). 12 | 13 | ## Build 14 | 15 | ```bash 16 | cargo xtask build --release 17 | ``` 18 | 19 | Set env `ZSTD_SYS_USE_PKG_CONFIG=1` to dynamically link to system zstd library. 20 | 21 | ## Config 22 | 23 | `etc/daemon.toml` provides a sample daemon config file. 24 | 25 | See the `ttyrecall-git` AUR package for a simple systemd service. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ttyrecall 2 | 3 | Recall, but for terminals. 4 | 5 | - [Installation Guide](./INSTALL.md) 6 | 7 | # Work In Progress!!! 8 | 9 | This project is still in its infancy. Bugs, non working features, breaking changes are expected. 10 | 11 | For now it can be considered as an asciinema but it is always on, continuously recording your terminals. 12 | 13 | # Origin 14 | 15 | Inspired by [Microsoft's new controversial recall feature for Windows 11](https://support.microsoft.com/en-us/windows/retrace-your-steps-with-recall-aa03f8a0-a78b-4b3e-b0a1-2eb8ac48701c), 16 | I wonder if I could create something similar for Linux. 17 | It is very resource and compute intensive to continuously capture and analyze screenshots in the background so I prefer 18 | to avoid it. But actually on Linux, we are doing a lot of things in terminals so why not create something similar that 19 | is based on text instead of screenshots? 20 | 21 | Before adding AI features(if I ever want to do that), `ttyrecall` will focus on collecting and archiving terminal outputs. 22 | So it can be considered as [asciinema](https://asciinema.org/), but always on. 23 | 24 | # Current Status 25 | 26 | - [x] Record tty in the background to asciicast-v2 format 27 | - [x] Save the recordings in a directory structure that makes sense 28 | - [x] DAC so that users by default can only access their own recordings. (recording file is owned by `user:ttyrecall`) 29 | - [x] Control which users' tty are recorded via a blocklist or allowlist 30 | - [x] Zstd compression 31 | - [x] A simple systemd service (See `ttyrecall-git` package). 32 | - [x] Stop a recording if it overruns a specified soft budget. 33 | 34 | Here is what the collected recordings look like: 35 | 36 | ```bash 37 | ls -lah /var/lib/ttyrecall/1000/2024/10/04 -lah 38 | total 236K 39 | drwxrwx--- 1 kxxt root 774 Oct 4 22:45 . 40 | drwxrwx--- 1 kxxt root 12 Oct 4 12:52 .. 41 | -rw-rw---- 1 kxxt root 23K Oct 4 22:53 codium-pty6-22:37.cast.zst 42 | -rw-rw---- 1 kxxt root 106 Oct 4 21:57 kded6-pty0-21:53.cast.zst 43 | -rw-rw---- 1 kxxt root 1021 Oct 4 21:57 konsole-pty1-21:56.cast.zst 44 | -rw-rw---- 1 kxxt root 663 Oct 4 21:59 konsole-pty2-21:58.cast.zst 45 | -rw-rw---- 1 kxxt root 8.0K Oct 4 22:01 konsole-pty3-22:00.cast.zst 46 | -rw-rw---- 1 kxxt root 33K Oct 4 22:24 konsole-pty4-22:08.cast.zst 47 | -rw-rw---- 1 kxxt root 0 Oct 4 22:12 konsole-pty5-22:12.cast.zst 48 | -rw-rw---- 1 kxxt root 63K Oct 4 22:50 konsole-pty7-22:42.cast.zst 49 | -rw-rw---- 1 kxxt root 791 Oct 4 12:53 konsole-pty9-12:52.cast.zst 50 | -rw-rw---- 1 kxxt root 779 Oct 4 12:52 sudo-pty11-12:52.cast.zst 51 | -rw-rw---- 1 kxxt root 1.1K Oct 4 22:42 sudo-pty7-22:42.cast.zst 52 | -rw-rw---- 1 kxxt root 31K Oct 4 22:45 sudo-pty8-22:43.cast.zst 53 | -rw-rw---- 1 kxxt root 39K Oct 4 22:51 sudo-pty8-22:45.cast.zst 54 | -rw-rw---- 1 kxxt root 777 Oct 4 12:52 sudo-pty9-12:52-1.cast.zst 55 | -rw-rw---- 1 kxxt root 221 Oct 4 12:53 sudo-pty9-12:53.cast.zst 56 | ``` 57 | 58 | The zstd compressed recordings can be played by the following command: 59 | 60 | ```bash 61 | zstd -cd /var/lib/ttyrecall/1000/2024/10/03/konsole-pty8-12:19.cast.zst | asciinema play - 62 | ``` 63 | 64 | # TODO 65 | 66 | - [ ] Implement a player that could directly take zstd compressed asciicast v2 files. 67 | - [ ] Implement a TUI interface to easily browse and manage the recordings. 68 | - [ ] Implement a web interface to easily browse and manage the recordings. 69 | - [ ] Automatically remove some old recordings in some way. 70 | - [ ] Allow users to sync the recordings to their server. 71 | - [ ] Search for something and we can return some sessions that mentioned it and jump to the corresponding timestamp. 72 | - [ ] Store the recordings in databases or more structured formats to speed up search and indexing. 73 | - [ ] Add AI to it. (Seriously, should I do this????) 74 | 75 | # License 76 | 77 | Please see the license of the individual crates. 78 | 79 | - The eBPF module is licensed under `GPL-2.0-or-later`. 80 | - The xtask crate is also licensed under `GPL-2.0-or-later`. 81 | - The main binary, `ttyrecall`, is licensed under `AGPL-3.0-or-later`. 82 | - The common library, `ttyrecall-common`, is licensed under `MIT-0`(MIT No Attribution). 83 | -------------------------------------------------------------------------------- /etc/daemon.toml: -------------------------------------------------------------------------------- 1 | ###################################### 2 | # Configuration for ttyrecall daemon # 3 | ###################################### 4 | 5 | root = "/var/lib/ttyrecall" 6 | # Compression 7 | # - none : no compression 8 | # - zstd : zstd with default compression level 9 | # - zstd:$level : zstd with custom compression level 10 | compress = "zstd" 11 | 12 | # Mode 13 | # - blocklist: Record all pty sessions by default, but sessions from listed users/uids won't be recorded 14 | # - allowlist: Only record the pty sessions from listed users/uids 15 | mode = "blocklist" 16 | # User names 17 | users = [] 18 | # UIDs 19 | uids = [ 20 | 0, # The root user's session is not interesting most of the time 21 | ] 22 | # Ignore pty sessions opened by process with following comms 23 | excluded_comms = [ 24 | "sudo", # While sudo opens a new pty but we also have the contents available in parent pty. 25 | "asciinema", # Usually it's not helpful to record it again when it is already being recorded by asciinema. 26 | ] 27 | 28 | # Soft budget for every recording file. The recording will stop after it overruns the budget. 29 | # Unit: bytes 30 | # A special value of zero will disable the soft budget. 31 | soft_budget = 52428800 32 | -------------------------------------------------------------------------------- /ttyrecall-common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ttyrecall-common" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT-0" 6 | 7 | [features] 8 | default = [] 9 | user = ["aya"] 10 | 11 | [dependencies] 12 | aya = { version = "0.12", optional = true } 13 | 14 | [lib] 15 | path = "src/lib.rs" 16 | -------------------------------------------------------------------------------- /ttyrecall-common/LICENSE: -------------------------------------------------------------------------------- 1 | MIT No Attribution 2 | 3 | Copyright 2024 Levi Zim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 13 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 14 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 15 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 16 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /ttyrecall-common/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | #[derive(Debug, Clone, Copy, Default)] 4 | pub struct Size { 5 | pub width: u16, 6 | pub height: u16, 7 | } 8 | 9 | impl Size { 10 | pub fn is_zero(&self) -> bool { 11 | self.width == 0 && self.height == 0 12 | } 13 | } 14 | 15 | #[derive(Debug)] 16 | pub enum EventKind { 17 | PtyInstall { comm: [u8; 16] }, 18 | PtyResize { size: Size }, 19 | PtyRemove, 20 | } 21 | 22 | #[derive(Debug)] 23 | pub struct ShortEvent { 24 | pub uid: u32, 25 | pub id: u32, 26 | pub time: u64, 27 | pub kind: EventKind, 28 | } 29 | 30 | #[derive(Debug, Default)] 31 | #[repr(C)] 32 | // This struct should be bigger than `ShortEvent` 33 | // because we are leaveraging this to determine if a event 34 | // is a `ShortEvent` 35 | pub struct WriteEventHead { 36 | pub id: u32, 37 | pub time: u64, 38 | pub comm: [u8; 16], 39 | pub _padding: [u8; 16], 40 | } 41 | 42 | const _: () = assert!( 43 | size_of::() < size_of::(), 44 | "ShortEvent should be shorter than WriteEventHead!" 45 | ); 46 | 47 | #[derive(Debug)] 48 | #[repr(C)] 49 | pub struct WriteEvent { 50 | pub head: WriteEventHead, 51 | // There is no padding between the two members! Do NOT BREAK it! 52 | pub data: [u8], 53 | } 54 | 55 | // TTY_BUFFER_PAGE: https://elixir.bootlin.com/linux/v6.11/source/drivers/tty/tty_buffer.c#L41 56 | // #define TTY_BUFFER_PAGE (((PAGE_SIZE - sizeof(struct tty_buffer)) / 2) & ~TTYB_ALIGN_MASK) 57 | 58 | const PAGE_SIZE: usize = 4096; 59 | // This should be enough for most systems. 60 | pub const TTY_WRITE_MAX: usize = PAGE_SIZE / 2; 61 | 62 | pub const RECALL_CONFIG_INDEX_MODE: u32 = 0; 63 | 64 | pub const RECALL_CONFIG_MODE_BLOCKLIST: u64 = 0; 65 | pub const RECALL_CONFIG_MODE_ALLOWLIST: u64 = 1; 66 | -------------------------------------------------------------------------------- /ttyrecall-ebpf/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target-dir = "../target" 3 | target = "bpfel-unknown-none" 4 | 5 | [unstable] 6 | build-std = ["core"] 7 | -------------------------------------------------------------------------------- /ttyrecall-ebpf/.helix/config.toml: -------------------------------------------------------------------------------- 1 | [editor] 2 | workspace-lsp-roots = [] 3 | -------------------------------------------------------------------------------- /ttyrecall-ebpf/.vim/coc-settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.target": "bpfel-unknown-none", 3 | "rust-analyzer.checkOnSave.allTargets": false 4 | } 5 | -------------------------------------------------------------------------------- /ttyrecall-ebpf/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.target": "bpfel-unknown-none", 3 | "rust-analyzer.checkOnSave.allTargets": false 4 | } 5 | -------------------------------------------------------------------------------- /ttyrecall-ebpf/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 = "aya-ebpf" 7 | version = "0.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b7318de0c49a17873182763831cb22f74fb30d04e2eb7e6d7b7e9b7d86d70ed3" 10 | dependencies = [ 11 | "aya-ebpf-bindings", 12 | "aya-ebpf-cty", 13 | "aya-ebpf-macros", 14 | "rustversion", 15 | ] 16 | 17 | [[package]] 18 | name = "aya-ebpf-bindings" 19 | version = "0.1.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "a8536b7e39b232ecd854e587f473ba15640c09afc3e08408fc28144a7404ae75" 22 | dependencies = [ 23 | "aya-ebpf-cty", 24 | ] 25 | 26 | [[package]] 27 | name = "aya-ebpf-cty" 28 | version = "0.2.1" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "d5c130d898322b9698937465b3b749095dae85dba0da4ee648235947eb95738d" 31 | 32 | [[package]] 33 | name = "aya-ebpf-macros" 34 | version = "0.1.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "ce7820cc83547582284a140ffbdd46ab527d7ee2d9d0cfedf3f184fad3f8e15c" 37 | dependencies = [ 38 | "proc-macro-error", 39 | "proc-macro2", 40 | "quote", 41 | "syn", 42 | ] 43 | 44 | [[package]] 45 | name = "aya-log-common" 46 | version = "0.1.14" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "b6d38a351ee2d5dc24e04cac6184b1b39408642d9a8b585892c99146f8dd4edb" 49 | dependencies = [ 50 | "num_enum", 51 | ] 52 | 53 | [[package]] 54 | name = "aya-log-ebpf" 55 | version = "0.1.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "2a10bbadd0829895a91eb1cd2bb02d7af145704087f03812bed60cb9fe65dbb3" 58 | dependencies = [ 59 | "aya-ebpf", 60 | "aya-log-common", 61 | "aya-log-ebpf-macros", 62 | ] 63 | 64 | [[package]] 65 | name = "aya-log-ebpf-macros" 66 | version = "0.1.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "f6d8251a75f56077db51892041aa6b77c70ef2723845d7a210979700b2f01bc4" 69 | dependencies = [ 70 | "aya-log-common", 71 | "aya-log-parser", 72 | "proc-macro2", 73 | "quote", 74 | "syn", 75 | ] 76 | 77 | [[package]] 78 | name = "aya-log-parser" 79 | version = "0.1.13" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "14b102eb5c88c9aa0b49102d3fbcee08ecb0dfa81014f39b373311de7a7032cb" 82 | dependencies = [ 83 | "aya-log-common", 84 | ] 85 | 86 | [[package]] 87 | name = "num_enum" 88 | version = "0.7.3" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" 91 | dependencies = [ 92 | "num_enum_derive", 93 | ] 94 | 95 | [[package]] 96 | name = "num_enum_derive" 97 | version = "0.7.3" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" 100 | dependencies = [ 101 | "proc-macro2", 102 | "quote", 103 | "syn", 104 | ] 105 | 106 | [[package]] 107 | name = "proc-macro-error" 108 | version = "1.0.4" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 111 | dependencies = [ 112 | "proc-macro-error-attr", 113 | "proc-macro2", 114 | "quote", 115 | "version_check", 116 | ] 117 | 118 | [[package]] 119 | name = "proc-macro-error-attr" 120 | version = "1.0.4" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 123 | dependencies = [ 124 | "proc-macro2", 125 | "quote", 126 | "version_check", 127 | ] 128 | 129 | [[package]] 130 | name = "proc-macro2" 131 | version = "1.0.86" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 134 | dependencies = [ 135 | "unicode-ident", 136 | ] 137 | 138 | [[package]] 139 | name = "quote" 140 | version = "1.0.37" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 143 | dependencies = [ 144 | "proc-macro2", 145 | ] 146 | 147 | [[package]] 148 | name = "rustversion" 149 | version = "1.0.17" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" 152 | 153 | [[package]] 154 | name = "syn" 155 | version = "2.0.77" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" 158 | dependencies = [ 159 | "proc-macro2", 160 | "quote", 161 | "unicode-ident", 162 | ] 163 | 164 | [[package]] 165 | name = "ttyrecall-common" 166 | version = "0.1.0" 167 | 168 | [[package]] 169 | name = "ttyrecall-ebpf" 170 | version = "0.1.0" 171 | dependencies = [ 172 | "aya-ebpf", 173 | "aya-log-ebpf", 174 | "ttyrecall-common", 175 | ] 176 | 177 | [[package]] 178 | name = "unicode-ident" 179 | version = "1.0.13" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 182 | 183 | [[package]] 184 | name = "version_check" 185 | version = "0.9.5" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 188 | -------------------------------------------------------------------------------- /ttyrecall-ebpf/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ttyrecall-ebpf" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "GPL-2.0-or-later" 6 | 7 | [dependencies] 8 | aya-ebpf = "0.1.0" 9 | aya-log-ebpf = "0.1.0" 10 | ttyrecall-common = { path = "../ttyrecall-common" } 11 | 12 | [[bin]] 13 | name = "ttyrecall" 14 | path = "src/main.rs" 15 | 16 | [features] 17 | default = ["resource-saving"] 18 | resource-saving = [] 19 | 20 | [profile.dev] 21 | opt-level = 3 22 | debug = false 23 | debug-assertions = false 24 | overflow-checks = false 25 | lto = true 26 | panic = "abort" 27 | incremental = false 28 | codegen-units = 1 29 | rpath = false 30 | 31 | [profile.release] 32 | lto = true 33 | panic = "abort" 34 | codegen-units = 1 35 | 36 | [workspace] 37 | members = [] 38 | -------------------------------------------------------------------------------- /ttyrecall-ebpf/LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /ttyrecall-ebpf/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | # The source code of rustc, provided by the rust-src component, is needed for 4 | # building eBPF programs. 5 | components = [ 6 | "cargo", 7 | "clippy", 8 | "rust-docs", 9 | "rust-src", 10 | "rust-std", 11 | "rustc", 12 | "rustfmt", 13 | ] 14 | -------------------------------------------------------------------------------- /ttyrecall-ebpf/src/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | aya-tool generate tty_driver tty_struct > generated_vmlinux.rs 4 | -------------------------------------------------------------------------------- /ttyrecall-ebpf/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | #[allow(dead_code)] 5 | #[allow(non_snake_case)] 6 | #[allow(non_camel_case_types)] 7 | #[allow(clippy::absolute_paths)] 8 | #[allow(clippy::upper_case_acronyms)] 9 | #[allow(clippy::zero_repeat_side_effects)] 10 | #[allow(non_upper_case_globals)] 11 | #[warn(single_use_lifetimes)] 12 | mod vmlinux { 13 | include!("generated_vmlinux.rs"); 14 | } 15 | 16 | use core::{mem::MaybeUninit, ptr::addr_of, usize}; 17 | 18 | use aya_ebpf::{ 19 | bindings::{bpf_dynptr, BPF_F_NO_PREALLOC}, 20 | cty::{c_int, c_void, ssize_t}, 21 | helpers::{ 22 | bpf_dynptr_data, bpf_dynptr_write, bpf_get_current_comm, bpf_ktime_get_tai_ns, 23 | bpf_probe_read_kernel, bpf_ringbuf_discard_dynptr, bpf_ringbuf_reserve_dynptr, 24 | bpf_ringbuf_submit_dynptr, gen::bpf_probe_read_kernel as raw_bpf_probe_read_kernel, 25 | }, 26 | macros::{fexit, map}, 27 | maps::{Array, HashMap, PerCpuArray, RingBuf}, 28 | programs::FExitContext, 29 | EbpfContext, 30 | }; 31 | use aya_log_ebpf::{error, info, trace, warn}; 32 | use ttyrecall_common::{ 33 | EventKind, ShortEvent, Size, WriteEventHead, RECALL_CONFIG_MODE_ALLOWLIST, 34 | RECALL_CONFIG_MODE_BLOCKLIST, TTY_WRITE_MAX, 35 | }; 36 | use vmlinux::{tty_driver, tty_struct, winsize}; 37 | 38 | // assuming we have 128 cores, each core is writing 2048 byte to a different pty, that 39 | // will cause 2048 * 128 bytes to be accumlated on our buffer. 40 | // Let's reserve 512 times it for now. It should be enough. 41 | #[map] 42 | static EVENT_RING: RingBuf = RingBuf::with_byte_size( 43 | if cfg!(feature = "resource-saving") { 44 | 128 45 | } else { 46 | 128 47 | } * 2048 48 | * 512, 49 | 0, 50 | ); // 128 MiB 51 | 52 | #[map] 53 | static CONFIG: Array = Array::with_max_entries(1, 0); 54 | 55 | #[map] 56 | static USERS: HashMap = HashMap::with_max_entries( 57 | if cfg!(feature = "resource-saving") { 58 | // Only allow 1024 users in the list 59 | 1024 60 | } else { 61 | 32768 62 | }, 63 | BPF_F_NO_PREALLOC, 64 | ); 65 | 66 | /// The hash set of traced ptys 67 | /// NR_UNIX98_PTY_MAX is 1<<20 (1048576) 68 | /// pty slaves can have a major of 136-143 69 | /// So it appears that we can have (143-136+1)*2**20 = 8388608 pty slaves at most 70 | /// This needs further confirmation. 71 | #[map] 72 | static TRACED_PTYS: HashMap = HashMap::with_max_entries( 73 | if cfg!(feature = "resource-saving") { 74 | // Only allow 4096 ptys to be traced in parallel 75 | 4096 76 | } else { 77 | 8388608 78 | }, 79 | BPF_F_NO_PREALLOC, 80 | ); 81 | 82 | /// The map of excluded comms. We won't record sessions started from such processes. 83 | /// 84 | /// The comm must be NUL terminated and all bytes after it must also be NUL. 85 | #[map] 86 | static EXCLUDED_COMMS: HashMap<[u8; 16], u8> = HashMap::with_max_entries(1024, BPF_F_NO_PREALLOC); 87 | 88 | const INTERMEDIATE_BUFFER_SIZE: usize = 128; 89 | const TRANSFER_LOOP_CNT_MAX: usize = TTY_WRITE_MAX / INTERMEDIATE_BUFFER_SIZE; 90 | 91 | #[map] 92 | static INTERMEDIATE_BUFFER: PerCpuArray<[u8; INTERMEDIATE_BUFFER_SIZE]> = 93 | PerCpuArray::with_max_entries(1, 0); 94 | 95 | #[fexit(function = "pty_write")] 96 | pub fn pty_write(ctx: FExitContext) -> u32 { 97 | match try_pty_write(ctx) { 98 | Ok(ret) => ret, 99 | Err(ret) => ret, 100 | } 101 | } 102 | 103 | #[fexit(function = "pty_unix98_install")] 104 | pub fn pty_unix98_install(ctx: FExitContext) -> u32 { 105 | match try_pty_unix98_install(ctx) { 106 | Ok(ret) => ret, 107 | Err(ret) => ret, 108 | } 109 | } 110 | 111 | #[fexit(function = "pty_unix98_remove")] 112 | pub fn pty_unix98_remove(ctx: FExitContext) -> u32 { 113 | match try_pty_unix98_remove(ctx) { 114 | Ok(ret) => ret, 115 | Err(ret) => ret, 116 | } 117 | } 118 | 119 | #[fexit(function = "pty_resize")] 120 | pub fn pty_resize(ctx: FExitContext) -> u32 { 121 | match try_pty_resize(ctx) { 122 | Ok(ret) => ret, 123 | Err(ret) => ret, 124 | } 125 | } 126 | 127 | #[fexit(function = "tty_do_resize")] 128 | pub fn tty_do_resize(ctx: FExitContext) -> u32 { 129 | match try_tty_do_resize(ctx) { 130 | Ok(ret) => ret, 131 | Err(ret) => ret, 132 | } 133 | } 134 | 135 | // C 136 | // static ssize_t pty_write(struct tty_struct *tty, const u8 *buf, size_t c) 137 | fn try_pty_write(ctx: FExitContext) -> Result { 138 | trace!(&ctx, "function pty_write called"); 139 | // Arguments 140 | let tty: *const tty_struct = unsafe { ctx.arg(0) }; 141 | let buf: *const u8 = unsafe { ctx.arg(1) }; 142 | let size: ssize_t = unsafe { ctx.arg(2) }; 143 | let ret: ssize_t = unsafe { ctx.arg(3) }; 144 | if ret < 0 { 145 | return Err(u32::MAX); 146 | } 147 | // Creds 148 | // id: /dev/pts/{id} 149 | let id = unsafe { bpf_probe_read_kernel(&(*tty).index).unwrap() } as u32; 150 | if !should_trace(id) { 151 | return Ok(3); 152 | } 153 | let driver = unsafe { bpf_probe_read_kernel(&(*tty).driver).unwrap() }; 154 | // https://elixir.bootlin.com/linux/v6.11/source/include/linux/tty_driver.h#L568-L571 155 | let subtype = unsafe { bpf_probe_read_kernel(&(*driver).subtype).unwrap() }; 156 | const PTY_TYPE_SLAVE: i16 = 0x0002; 157 | if subtype != PTY_TYPE_SLAVE { 158 | return Ok(0); 159 | } 160 | let time = unsafe { bpf_ktime_get_tai_ns() }; 161 | let slice_size = (ret as usize).min(TTY_WRITE_MAX); 162 | let mut dynptr = MaybeUninit::::uninit(); 163 | unsafe { 164 | let result = bpf_ringbuf_reserve_dynptr( 165 | addr_of!(EVENT_RING) as *mut c_void, 166 | size_of::() as u32 + slice_size as u32, 167 | 0, 168 | dynptr.as_mut_ptr(), 169 | ); 170 | if result < 0 { 171 | warn!(&ctx, "No space on ringbuf"); 172 | bpf_ringbuf_discard_dynptr(dynptr.as_mut_ptr(), 0); 173 | return Err(u32::MAX); 174 | } 175 | }; 176 | let head = WriteEventHead { 177 | id, 178 | time, 179 | ..Default::default() 180 | }; 181 | unsafe { 182 | bpf_dynptr_write( 183 | dynptr.as_mut_ptr(), 184 | 0, 185 | addr_of!(head) as *mut c_void, // Actually we don't need mut. 186 | size_of_val(&head) as u32, 187 | 0, 188 | ); 189 | // Guaranteed success 190 | } 191 | // Write payload chunk by chunk 192 | unsafe { 193 | let cnt = ((slice_size + INTERMEDIATE_BUFFER_SIZE - 1) / INTERMEDIATE_BUFFER_SIZE) 194 | .min(TRANSFER_LOOP_CNT_MAX); 195 | let base_offset = size_of_val(&head); 196 | let mut offset = 0; 197 | for _ in 0..cnt { 198 | let chunk_size = INTERMEDIATE_BUFFER_SIZE.min(slice_size - offset); 199 | if chunk_size == INTERMEDIATE_BUFFER_SIZE { 200 | let chunk = bpf_dynptr_data( 201 | dynptr.as_mut_ptr(), 202 | (base_offset + offset) as u32, 203 | INTERMEDIATE_BUFFER_SIZE as u32, 204 | ); 205 | if chunk.is_null() { 206 | warn!(&ctx, "BUG! bpf_dynptr_data failure"); 207 | bpf_ringbuf_discard_dynptr(dynptr.as_mut_ptr(), 0); 208 | return Err(u32::MAX); 209 | } 210 | raw_bpf_probe_read_kernel(chunk, chunk_size as u32, buf.byte_add(offset).cast()); 211 | // MAYBE check result? 212 | } else { 213 | let Some(intermediate) = INTERMEDIATE_BUFFER.get_ptr(0) else { 214 | // Should not happen 215 | bpf_ringbuf_discard_dynptr(dynptr.as_mut_ptr(), 0); 216 | return Err(u32::MAX); 217 | }; 218 | let intermediate = intermediate as *mut c_void; 219 | raw_bpf_probe_read_kernel( 220 | intermediate, 221 | chunk_size as u32, 222 | buf.byte_add(offset).cast(), 223 | ); 224 | // MAYBE check result? 225 | bpf_dynptr_write( 226 | dynptr.as_mut_ptr(), 227 | (base_offset + offset) as u32, 228 | intermediate, 229 | chunk_size as u32, 230 | 0, 231 | ); 232 | } 233 | offset += chunk_size; 234 | } 235 | } 236 | unsafe { 237 | bpf_ringbuf_submit_dynptr(dynptr.as_mut_ptr(), 0); 238 | } 239 | Ok(0) 240 | } 241 | 242 | // C 243 | // static int pty_unix98_install(struct tty_driver *driver, struct tty_struct *tty) 244 | fn try_pty_unix98_install(ctx: FExitContext) -> Result { 245 | let mode = CONFIG 246 | .get(ttyrecall_common::RECALL_CONFIG_INDEX_MODE) 247 | .map(|t| *t) 248 | .unwrap_or_default(); 249 | let uid = ctx.uid(); 250 | let should_trace = match mode { 251 | RECALL_CONFIG_MODE_BLOCKLIST => unsafe { USERS.get(&uid) }.is_none(), 252 | RECALL_CONFIG_MODE_ALLOWLIST => unsafe { USERS.get(&uid) }.is_some(), 253 | _ => { 254 | error!(&ctx, "Invalid mode: {}", mode); 255 | false 256 | } 257 | }; 258 | info!(&ctx, "function pty_unix98_install called"); 259 | // Arguments 260 | let _driver: *const tty_driver = unsafe { ctx.arg(0) }; 261 | let tty: *const tty_struct = unsafe { ctx.arg(1) }; 262 | let ret: c_int = unsafe { ctx.arg(2) }; 263 | if ret < 0 { 264 | return Ok(1); 265 | } 266 | // Read Info 267 | // id: /dev/pts/{id} 268 | let id = unsafe { bpf_probe_read_kernel(&(*tty).index).unwrap() } as u32; 269 | let time = unsafe { bpf_ktime_get_tai_ns() }; 270 | let comm = canonicalized_comm(); 271 | if should_trace && unsafe { EXCLUDED_COMMS.get(&comm) }.is_none() { 272 | TRACED_PTYS.insert(&id, &0, 0).unwrap(); 273 | } else { 274 | return Ok(2); 275 | } 276 | let Some(mut reserved) = EVENT_RING.reserve::(0) else { 277 | error!(&ctx, "Failed to reserve event!"); 278 | return Err(u32::MAX); 279 | }; 280 | reserved.write(ShortEvent { 281 | uid, 282 | id, 283 | time, 284 | kind: EventKind::PtyInstall { comm }, 285 | }); 286 | reserved.submit(0); 287 | info!( 288 | &ctx, 289 | "pty_unix98_install uid={}, id={}, ret={}", uid, id, ret 290 | ); 291 | Ok(0) 292 | } 293 | 294 | // C 295 | // /* this is called once with whichever end is closed last */ 296 | // static void pty_unix98_remove(struct tty_driver *driver, struct tty_struct *tty) 297 | fn try_pty_unix98_remove(ctx: FExitContext) -> Result { 298 | info!(&ctx, "function pty_unix98_remove called"); 299 | // Arguments 300 | let _driver: *const tty_driver = unsafe { ctx.arg(0) }; 301 | let tty: *const tty_struct = unsafe { ctx.arg(1) }; 302 | // Creds 303 | let uid = ctx.uid(); 304 | // Read Info 305 | // id: /dev/pts/{id} 306 | let id = unsafe { bpf_probe_read_kernel(&(*tty).index).unwrap() } as u32; 307 | if !should_trace(id) { 308 | return Ok(3); 309 | } 310 | TRACED_PTYS.remove(&id).unwrap(); 311 | let time = unsafe { bpf_ktime_get_tai_ns() }; 312 | let Some(mut reserved) = EVENT_RING.reserve::(0) else { 313 | error!(&ctx, "Failed to reserve event!"); 314 | return Err(u32::MAX); 315 | }; 316 | reserved.write(ShortEvent { 317 | uid, 318 | id, 319 | time, 320 | kind: EventKind::PtyRemove, 321 | }); 322 | reserved.submit(0); 323 | info!(&ctx, "pty_unix98_remove uid={}, id={}", uid, id,); 324 | Ok(0) 325 | } 326 | 327 | // pty *master side* resize 328 | // only pty master implements resize in tty_operations 329 | // C 330 | // static int pty_resize(struct tty_struct *tty, struct winsize *ws) 331 | fn try_pty_resize(ctx: FExitContext) -> Result { 332 | // Arguments 333 | let tty: *const tty_struct = unsafe { ctx.arg(0) }; 334 | let ws: *const winsize = unsafe { ctx.arg(1) }; 335 | let ret: c_int = unsafe { ctx.arg(2) }; 336 | if ret < 0 { 337 | return Ok(1); 338 | } 339 | // Creds 340 | let uid = ctx.uid(); 341 | // Read Info 342 | // id: /dev/pts/{id} 343 | let time = unsafe { bpf_ktime_get_tai_ns() }; 344 | let id = unsafe { bpf_probe_read_kernel(&(*tty).index).unwrap() } as u32; 345 | if !should_trace(id) { 346 | return Ok(3); 347 | } 348 | let winsize = unsafe { bpf_probe_read_kernel(ws).unwrap() }; 349 | let Some(mut reserved) = EVENT_RING.reserve::(0) else { 350 | error!(&ctx, "Failed to reserve event!"); 351 | return Err(u32::MAX); 352 | }; 353 | reserved.write(ShortEvent { 354 | uid, 355 | id, 356 | time, 357 | kind: EventKind::PtyResize { 358 | size: Size { 359 | width: winsize.ws_col, 360 | height: winsize.ws_row, 361 | }, 362 | }, 363 | }); 364 | reserved.submit(0); 365 | info!( 366 | &ctx, 367 | "pty_resize master{} to {}x{}", id, winsize.ws_col, winsize.ws_row 368 | ); 369 | Ok(0) 370 | } 371 | 372 | // tty default resize 373 | // C 374 | // int tty_do_resize(struct tty_struct *tty, struct winsize *ws) 375 | fn try_tty_do_resize(ctx: FExitContext) -> Result { 376 | // Arguments 377 | let tty: *const tty_struct = unsafe { ctx.arg(0) }; 378 | let ws: *const winsize = unsafe { ctx.arg(1) }; 379 | let ret: c_int = unsafe { ctx.arg(2) }; 380 | if ret < 0 { 381 | return Ok(1); 382 | } 383 | // Read Info 384 | // id: /dev/pts/{id} 385 | let time = unsafe { bpf_ktime_get_tai_ns() }; 386 | let driver_major = unsafe { bpf_probe_read_kernel(&(*(*tty).driver).major).unwrap() }; 387 | // According to https://www.kernel.org/doc/Documentation/admin-guide/devices.txt 388 | // pty slaves has a major of 136-143 389 | if !(136..=143).contains(&driver_major) { 390 | return Ok(2); 391 | } 392 | let id = unsafe { bpf_probe_read_kernel(&(*tty).index).unwrap() } as u32; 393 | if !should_trace(id) { 394 | return Ok(3); 395 | } 396 | let winsize = unsafe { bpf_probe_read_kernel(ws).unwrap() }; 397 | let Some(mut reserved) = EVENT_RING.reserve::(0) else { 398 | error!(&ctx, "Failed to reserve event!"); 399 | return Err(u32::MAX); 400 | }; 401 | reserved.write(ShortEvent { 402 | uid: ctx.uid(), 403 | id, 404 | time, 405 | kind: EventKind::PtyResize { 406 | size: Size { 407 | width: winsize.ws_col, 408 | height: winsize.ws_row, 409 | }, 410 | }, 411 | }); 412 | reserved.submit(0); 413 | info!( 414 | &ctx, 415 | "pty_resize slave{} to {}x{}", id, winsize.ws_col, winsize.ws_row 416 | ); 417 | Ok(0) 418 | } 419 | 420 | fn canonicalized_comm() -> [u8; 16] { 421 | let mut comm = bpf_get_current_comm().unwrap(); 422 | // index of first nul 423 | let mut idx_nul = usize::MAX; 424 | // Ensure the comm ends with NUL bytes 425 | for i in 0..comm.len() { 426 | if i > idx_nul { 427 | comm[i] = 0; 428 | } else if comm[i] == 0 { 429 | idx_nul = i; 430 | } 431 | } 432 | comm 433 | } 434 | 435 | fn should_trace(id: u32) -> bool { 436 | unsafe { TRACED_PTYS.get(&id) }.is_some() 437 | } 438 | 439 | #[panic_handler] 440 | fn panic(_info: &core::panic::PanicInfo) -> ! { 441 | unsafe { core::hint::unreachable_unchecked() } 442 | } 443 | -------------------------------------------------------------------------------- /ttyrecall.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | }, 6 | { 7 | "path": "./ttyrecall-ebpf" 8 | } 9 | ], 10 | "settings": { 11 | "rust-analyzer.check.allTargets": false 12 | }, 13 | } -------------------------------------------------------------------------------- /ttyrecall/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ttyrecall" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "AGPL-3.0-or-later" 6 | publish = false 7 | 8 | [dependencies] 9 | aya = { version = "0.12", features = ["async_tokio"] } 10 | aya-log = "0.2" 11 | ttyrecall-common = { path = "../ttyrecall-common", features = ["user"] } 12 | env_logger = "0.10" 13 | libc = "0.2" 14 | log = "0.4" 15 | tokio = { version = "1.43", features = ["macros", "rt", "rt-multi-thread", "net", "signal"] } 16 | bstr = { version = "1.10.0", features = ["serde"] } 17 | nix = { version = "0.29.0", features = ["fs", "user"] } 18 | chrono = "0.4.38" 19 | color-eyre = "0.6.3" 20 | either = "1.13.0" 21 | serde = { version = "1.0.210", features = ["derive"] } 22 | serde_json = "1.0.128" 23 | clap = { version = "4.5.18", features = ["derive"] } 24 | toml = "0.8.19" 25 | zstd = "0.13.2" 26 | clap_complete = "4.5.32" 27 | thiserror = "1.0.64" 28 | 29 | [[bin]] 30 | name = "ttyrecall" 31 | path = "src/main.rs" 32 | -------------------------------------------------------------------------------- /ttyrecall/LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /ttyrecall/src/cli.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use clap::{Parser, Subcommand}; 4 | use clap_complete::Shell; 5 | 6 | #[derive(Debug, Clone, Parser)] 7 | pub struct CommandLine { 8 | #[clap(subcommand)] 9 | pub command: Command, 10 | } 11 | 12 | #[derive(Debug, Clone, Subcommand)] 13 | pub enum Command { 14 | #[clap(about = "1984 is now! Big brother is watching!")] 15 | Telescreen, 16 | #[clap(about = "Run ttyrecall daemon")] 17 | Daemon { 18 | #[clap( 19 | long, 20 | help = "Path to configuration file", 21 | default_value = "/etc/ttyrecall/daemon.toml" 22 | )] 23 | config: String, 24 | }, 25 | #[clap(about = "Play recorded file(s)")] 26 | Play { 27 | #[arg(last = true, required = true, help = "files to play")] 28 | files: Vec, 29 | }, 30 | #[clap(about = "Browse recorded file(s)")] 31 | Browse {}, 32 | // We want to support two kinds of web interfaces, 33 | // One that could be configured by sysadmin as a service to be used by all users, 34 | // and one that a user could launch to view their own archive. 35 | #[clap(about = "Run ttyrecall web interface without previllege")] 36 | WebService { 37 | #[clap(long, help = "Path to config file, /etc/ttyrecall/web.toml by default")] 38 | config: Option, 39 | }, 40 | #[clap(about = "Run ttyrecall web interface without previllege")] 41 | Web { 42 | #[clap(long, help = "Open the web interface in your browser")] 43 | open: bool, 44 | #[clap( 45 | long, 46 | help = "Path to config file, $XDG_CONFIG_HOME/ttyrecall/web.toml by default" 47 | )] 48 | config: Option, 49 | }, 50 | #[clap(about = "Generate shell completion file")] 51 | GenerateCompletion { shell: Shell }, 52 | } 53 | -------------------------------------------------------------------------------- /ttyrecall/src/daemon.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, collections::HashSet, num::NonZeroUsize, rc::Rc}; 2 | 3 | use aya::{include_bytes_aligned, maps::MapData, programs::FExit, Bpf, Btf}; 4 | use aya_log::BpfLogger; 5 | use color_eyre::eyre::eyre; 6 | use log::{debug, error, info, warn}; 7 | use nix::unistd::User; 8 | use tokio::{ 9 | io::unix::AsyncFd, 10 | select, 11 | signal::{unix::signal, unix::SignalKind}, 12 | }; 13 | use ttyrecall_common::{ 14 | EventKind, ShortEvent, WriteEvent, WriteEventHead, RECALL_CONFIG_INDEX_MODE, 15 | }; 16 | 17 | use crate::{manager::Manager, session::PtySessionManager}; 18 | 19 | mod config; 20 | 21 | pub use config::*; 22 | 23 | pub struct Daemon { 24 | manager: Rc, 25 | mode: Mode, 26 | uids: HashSet, 27 | excluded_comms: HashSet, 28 | budget: Option, 29 | } 30 | 31 | impl Daemon { 32 | pub fn new(config: DaemonConfig) -> color_eyre::Result { 33 | Ok(Self { 34 | manager: Rc::new(Manager::new(config.root, true, config.compress)?), 35 | mode: config.mode, 36 | uids: { 37 | let mut uids = config.uids; 38 | for user in config.users { 39 | uids.insert( 40 | User::from_name(&user)? 41 | .ok_or_else(|| eyre!("User {user} listed in `users` does not exist"))? 42 | .uid 43 | .as_raw(), 44 | ); 45 | } 46 | uids 47 | }, 48 | excluded_comms: config.excluded_comms, 49 | budget: NonZeroUsize::new(config.soft_budget), 50 | }) 51 | } 52 | 53 | pub async fn run(&self) -> color_eyre::Result<()> { 54 | // Bump the memlock rlimit. This is needed for older kernels that don't use the 55 | // new memcg based accounting, see https://lwn.net/Articles/837122/ 56 | let rlim = libc::rlimit { 57 | rlim_cur: libc::RLIM_INFINITY, 58 | rlim_max: libc::RLIM_INFINITY, 59 | }; 60 | let ret = unsafe { libc::setrlimit(libc::RLIMIT_MEMLOCK, &rlim) }; 61 | if ret != 0 { 62 | debug!("remove limit on locked memory failed, ret is: {}", ret); 63 | } 64 | 65 | // This will include your eBPF object file as raw bytes at compile-time and load it at 66 | // runtime. This approach is recommended for most real-world use cases. If you would 67 | // like to specify the eBPF program at runtime rather than at compile-time, you can 68 | // reach for `Bpf::load_file` instead. 69 | #[cfg(debug_assertions)] 70 | let mut bpf = Bpf::load(include_bytes_aligned!( 71 | "../../target/bpfel-unknown-none/debug/ttyrecall" 72 | ))?; 73 | #[cfg(not(debug_assertions))] 74 | let mut bpf = Bpf::load(include_bytes_aligned!( 75 | "../../target/bpfel-unknown-none/release/ttyrecall" 76 | ))?; 77 | if let Err(e) = BpfLogger::init(&mut bpf) { 78 | // This can happen if you remove all log statements from your eBPF program. 79 | warn!("failed to initialize eBPF logger: {}", e); 80 | } 81 | let btf = Btf::from_sys_fs()?; 82 | let mut config = 83 | aya::maps::Array::<&mut MapData, u64>::try_from(bpf.map_mut("CONFIG").unwrap())?; 84 | config.set(RECALL_CONFIG_INDEX_MODE, self.mode as u64, 0)?; 85 | let mut users = 86 | aya::maps::HashMap::<&mut MapData, u32, u8>::try_from(bpf.map_mut("USERS").unwrap())?; 87 | for uid in self.uids.iter() { 88 | users.insert(uid, 0u8, 0)?; 89 | } 90 | let mut excluded_comms = aya::maps::HashMap::<&mut MapData, [u8; 16], u8>::try_from( 91 | bpf.map_mut("EXCLUDED_COMMS").unwrap(), 92 | )?; 93 | for comm in self.excluded_comms.iter() { 94 | excluded_comms.insert(comm.0, 0u8, 0)?; 95 | } 96 | let install_prog: &mut FExit = bpf.program_mut("pty_unix98_install").unwrap().try_into()?; 97 | install_prog.load("pty_unix98_install", &btf)?; 98 | install_prog.attach()?; 99 | let remove_prog: &mut FExit = bpf.program_mut("pty_unix98_remove").unwrap().try_into()?; 100 | remove_prog.load("pty_unix98_remove", &btf)?; 101 | remove_prog.attach()?; 102 | let pty_resize_prog: &mut FExit = bpf.program_mut("pty_resize").unwrap().try_into()?; 103 | pty_resize_prog.load("pty_resize", &btf)?; 104 | pty_resize_prog.attach()?; 105 | let tty_do_resize_prog: &mut FExit = 106 | bpf.program_mut("tty_do_resize").unwrap().try_into()?; 107 | tty_do_resize_prog.load("tty_do_resize", &btf)?; 108 | tty_do_resize_prog.attach()?; 109 | let pty_write_prog: &mut FExit = bpf.program_mut("pty_write").unwrap().try_into()?; 110 | pty_write_prog.load("pty_write", &btf)?; 111 | pty_write_prog.attach()?; 112 | info!("Waiting for Ctrl-C..."); 113 | let event_ring = aya::maps::RingBuf::try_from(bpf.map_mut("EVENT_RING").unwrap())?; 114 | let mut async_fd = AsyncFd::new(event_ring)?; 115 | let mut manager = PtySessionManager::new(self.manager.clone(), self.budget); 116 | let mut interrupt_stream = signal(SignalKind::interrupt())?; 117 | let mut termination_stream = signal(SignalKind::terminate())?; 118 | loop { 119 | select! { 120 | _ = termination_stream.recv() => { 121 | warn!("Termination signal received. Exiting"); 122 | break; 123 | } 124 | _ = interrupt_stream.recv() => { 125 | break; 126 | } 127 | guard = async_fd.readable_mut() => { 128 | let mut guard = guard?; 129 | let rb = guard.get_inner_mut(); 130 | while let Some(read) = rb.next() { 131 | const SHORT_EVENT_SIZE: usize = std::mem::size_of::(); 132 | match read.len() { 133 | SHORT_EVENT_SIZE => { 134 | let event: &ShortEvent = unsafe { &*(read.as_ptr().cast()) }; 135 | match event.kind { 136 | EventKind::PtyInstall { comm } => { 137 | manager.add_session(event.id, event.uid, Self::escape_comm(comm), event.time)?; 138 | }, 139 | EventKind::PtyRemove => { 140 | manager.remove_session(event.id); 141 | }, 142 | EventKind::PtyResize { size } => { 143 | if manager.exists(event.id) { 144 | manager.resize_session(event.id, event.time, size)?; 145 | } 146 | } 147 | } 148 | } 149 | size if size > SHORT_EVENT_SIZE => { 150 | assert!(size > size_of::()); 151 | let event: &WriteEvent = unsafe { 152 | // SAFETY: 153 | // *const [T] encodes the number of elements and when we cast it to another fat pointer, 154 | // the encoded length is preserved. 155 | // https://github.com/rust-lang/reference/pull/1417 156 | &*(&read[..(size - size_of::())] as *const [u8] as *const WriteEvent) 157 | }; 158 | if manager.exists(event.head.id) { 159 | let slice = &event.data; 160 | let str = match std::str::from_utf8(slice) { 161 | Ok(s) => Cow::Borrowed(s), 162 | Err(e) => { 163 | error!("Not valid utf8: {e}: {slice:?}"); 164 | String::from_utf8_lossy(slice) 165 | } 166 | }; 167 | manager.write_to(event.head.id, &str, event.head.time)?; 168 | } 169 | } 170 | _ => unreachable!() 171 | } 172 | } 173 | guard.clear_ready(); 174 | } 175 | } 176 | } 177 | info!("Exiting..."); 178 | Ok(()) 179 | } 180 | 181 | /// Escaped path safe comm 182 | fn escape_comm(comm: [u8; 16]) -> String { 183 | String::from_utf8_lossy( 184 | std::ffi::CStr::from_bytes_until_nul(&comm) 185 | .unwrap() 186 | .to_bytes(), 187 | ) 188 | .into_owned() 189 | .replace('/', "_") 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /ttyrecall/src/daemon/config.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashSet, fmt::Display}; 2 | 3 | use serde::Deserialize; 4 | use ttyrecall_common::{RECALL_CONFIG_MODE_ALLOWLIST, RECALL_CONFIG_MODE_BLOCKLIST}; 5 | 6 | #[derive(Debug, Deserialize)] 7 | pub struct DaemonConfig { 8 | /// A list of users. 9 | pub users: HashSet, 10 | /// A list of uids 11 | pub uids: HashSet, 12 | /// Mode that determines the meaning of users/uids 13 | pub mode: Mode, 14 | /// The root dir for storing recordings. 15 | pub root: String, 16 | /// Compression 17 | pub compress: Compress, 18 | /// Excluded comms 19 | pub excluded_comms: HashSet, 20 | /// Soft budget 21 | pub soft_budget: usize, 22 | } 23 | 24 | #[derive(Debug)] 25 | pub enum Compress { 26 | None, 27 | Zstd(Option), 28 | } 29 | 30 | #[derive(Debug, Clone, Copy, Deserialize)] 31 | #[serde(rename_all = "lowercase")] 32 | pub enum Mode { 33 | /// Don't capture ptys from block listed user/uids 34 | BlockList = RECALL_CONFIG_MODE_BLOCKLIST as isize, 35 | /// Only capture ptys from allow listed user/uids 36 | AllowList = RECALL_CONFIG_MODE_ALLOWLIST as isize, 37 | } 38 | 39 | impl Display for Compress { 40 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 41 | match self { 42 | Compress::None => f.write_str("none"), 43 | Compress::Zstd(level) => { 44 | if let Some(level) = level { 45 | write!(f, "zstd:{level}") 46 | } else { 47 | f.write_str("zstd") 48 | } 49 | } 50 | } 51 | } 52 | } 53 | 54 | impl<'de> Deserialize<'de> for Compress { 55 | fn deserialize(deserializer: D) -> Result 56 | where 57 | D: serde::Deserializer<'de>, 58 | { 59 | let s = String::deserialize(deserializer)?; 60 | let s = s.as_str(); 61 | Ok(match s { 62 | "none" => Compress::None, 63 | "zstd" => Compress::Zstd(None), 64 | s => { 65 | let Some(("zstd", level)) = s.split_once(':') else { 66 | return Err(serde::de::Error::invalid_value( 67 | serde::de::Unexpected::Str(s), 68 | &"none or zstd or zstd:$level", 69 | )); 70 | }; 71 | match level.parse::() { 72 | Ok(i) if (1..=22).contains(&i) => Compress::Zstd(Some(i)), 73 | _ => { 74 | return Err(serde::de::Error::invalid_value( 75 | serde::de::Unexpected::Str(level), 76 | &"A valid zstd compression level (1..=22)", 77 | )) 78 | } 79 | } 80 | } 81 | }) 82 | } 83 | } 84 | 85 | #[derive(Debug, PartialEq, Eq, Hash)] 86 | pub struct Comm(pub [u8; 16]); 87 | 88 | impl<'de> Deserialize<'de> for Comm { 89 | fn deserialize(deserializer: D) -> Result 90 | where 91 | D: serde::Deserializer<'de>, 92 | { 93 | let s = String::deserialize(deserializer)?; 94 | let bytes = s.as_bytes(); 95 | if s.len() > 15 { 96 | return Err(serde::de::Error::invalid_value( 97 | serde::de::Unexpected::Str(&s), 98 | &"A valid comm string (byte length is less than 16)", 99 | )); 100 | } 101 | let mut comm = [0; 16]; 102 | comm[..bytes.len()].copy_from_slice(bytes); 103 | Ok(Self(comm)) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /ttyrecall/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::stdout; 2 | 3 | use clap::{CommandFactory, Parser}; 4 | use cli::{Command, CommandLine}; 5 | use color_eyre::eyre::bail; 6 | use daemon::Daemon; 7 | 8 | mod cli; 9 | mod daemon; 10 | mod manager; 11 | mod session; 12 | 13 | #[tokio::main(worker_threads = 2)] 14 | async fn main() -> color_eyre::Result<()> { 15 | color_eyre::install()?; 16 | env_logger::init(); 17 | let cmdline = cli::CommandLine::parse(); 18 | match cmdline.command { 19 | Command::Daemon { config } => { 20 | Daemon::new(toml::from_str(&std::fs::read_to_string(config)?)?)? 21 | .run() 22 | .await?; 23 | } 24 | Command::GenerateCompletion { shell } => { 25 | let mut cmd = CommandLine::command(); 26 | clap_complete::generate(shell, &mut cmd, env!("CARGO_CRATE_NAME"), &mut stdout()) 27 | } 28 | _ => { 29 | bail!("Sorry, this feature hasn't been implemented."); 30 | } 31 | }; 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /ttyrecall/src/manager.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | borrow::Cow, 3 | fs::{create_dir, set_permissions, File, Permissions}, 4 | io::{self, ErrorKind}, 5 | os::{linux::fs::MetadataExt, unix::fs::PermissionsExt}, 6 | path::PathBuf, 7 | }; 8 | 9 | use chrono::{DateTime, Datelike, Local, Timelike}; 10 | use color_eyre::{eyre::bail, Section}; 11 | use log::warn; 12 | use nix::{ 13 | sys::stat::{umask, Mode}, 14 | unistd::{chown, Gid, Group, Uid}, 15 | }; 16 | 17 | use crate::daemon::Compress; 18 | 19 | /// A manager for on-disk recordings 20 | #[derive(Debug)] 21 | pub struct Manager { 22 | root: PathBuf, 23 | group: Option, 24 | pub compress: Compress, 25 | } 26 | 27 | impl Manager { 28 | /// Create a new manager, 29 | /// It could be opened in exclusive mode to ensure two daemons won't step 30 | /// on each other's toes. 31 | pub fn new(dir: String, exclusive: bool, compress: Compress) -> color_eyre::Result { 32 | let root = PathBuf::from(dir); 33 | let meta = root 34 | .metadata() 35 | .with_note(|| format!("Does the storage root {root:?} exist?"))?; 36 | if !meta.is_dir() { 37 | bail!("Storage root dir {root:?} does not exist or inaccessible."); 38 | } 39 | // Check ownership. It should be owned by root:ttyrecall 40 | let uid = Uid::from_raw(meta.st_uid()); 41 | if !uid.is_root() { 42 | warn!("Storage root dir {root:?} is not owned by root user!"); 43 | } 44 | let group = Group::from_name("ttyrecall")?; 45 | if let Some(group) = group.as_ref() { 46 | let gid = Gid::from_raw(meta.st_gid()); 47 | if gid != group.gid { 48 | warn!("Storage root dir {root:?} is not owned by ttyrecall group!") 49 | } 50 | } else { 51 | warn!("Group ttyrecall does not exist!"); 52 | } 53 | // Set umask to 007 54 | umask(Mode::S_IXOTH | Mode::S_IROTH | Mode::S_IWOTH); 55 | // TODO: Maybe check permissions 56 | Ok(Self { 57 | root, 58 | group, 59 | compress, 60 | }) 61 | } 62 | 63 | pub fn create_recording_file( 64 | &self, 65 | uid: Uid, 66 | pty_id: u32, 67 | comm: &str, 68 | ) -> color_eyre::Result { 69 | let now = chrono::Local::now(); 70 | 71 | let path_for_recording = |counter: usize| { 72 | self.root.join(format!( 73 | "{uid}/{year}/{month:02}/{day:02}/{comm}-pty{pty_id}-{hour:02}:{minte:02}:{second:02}{dash}{cnt}.cast{compress}", 74 | year = now.year(), 75 | month = now.month(), 76 | day = now.day(), 77 | hour = now.hour(), 78 | minte = now.minute(), 79 | second = now.second(), 80 | dash = if counter > 0 { "-" } else { "" }, 81 | cnt = if counter > 0 { Cow::Owned(counter.to_string()) } else { Cow::Borrowed("") }, 82 | compress = if let Compress::Zstd(_) = self.compress { ".zst" } else { "" } 83 | )) 84 | }; 85 | for counter in 0..32768 { 86 | let path = path_for_recording(counter); 87 | match File::create_new(&path) { 88 | Ok(f) => { 89 | chown(&path, Some(uid), self.group.as_ref().map(|g| g.gid))?; 90 | return Ok(f); 91 | } 92 | Err(e) => match e.kind() { 93 | ErrorKind::AlreadyExists => continue, 94 | ErrorKind::NotFound => { 95 | self.create_dir_for_date(uid, now)?; 96 | continue; 97 | } 98 | _ => return Err(e.into()), 99 | }, 100 | } 101 | } 102 | bail!("Failed to create recording file for pty {pty_id}"); 103 | } 104 | 105 | fn create_dir_for_date(&self, uid: Uid, date: DateTime) -> color_eyre::Result { 106 | let mut dir = self.root.join(format!("{}", uid.as_raw())); 107 | if !dir.is_dir() { 108 | self.add_user(uid)?; 109 | } 110 | dir.push(date.year().to_string()); 111 | self.create_dir_helper(&dir, uid)?; 112 | dir.push(format!("{:02}", date.month())); 113 | self.create_dir_helper(&dir, uid)?; 114 | dir.push(format!("{:02}", date.day())); 115 | self.create_dir_helper(&dir, uid)?; 116 | Ok(dir) 117 | } 118 | 119 | fn add_user(&self, uid: Uid) -> color_eyre::Result<()> { 120 | // Create user's directory 121 | let dir = self.root.join(format!("{}", uid.as_raw())); 122 | self.create_dir_helper(&dir, uid)?; 123 | // Changing Permission to rwxrwx--- 124 | set_permissions(&dir, Permissions::from_mode(0o770))?; 125 | Ok(()) 126 | } 127 | 128 | /// Creates the directory and set owner to user:ttyrecall 129 | fn create_dir_helper(&self, dir: &PathBuf, uid: Uid) -> color_eyre::Result<()> { 130 | match create_dir(dir) { 131 | Ok(_) => (), 132 | Err(e) if e.kind() == io::ErrorKind::AlreadyExists => (), 133 | r => r?, 134 | } 135 | // Changing ownership to user:ttyrecall 136 | chown(dir, Some(uid), self.group.as_ref().map(|g| g.gid))?; 137 | Ok(()) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /ttyrecall/src/session.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::RefCell, 3 | collections::HashMap, 4 | fmt::Debug, 5 | fs::File, 6 | io::{self, BufWriter, Write}, 7 | num::NonZeroUsize, 8 | rc::Rc, 9 | time::Duration, 10 | }; 11 | 12 | use chrono::Utc; 13 | use color_eyre::eyre::{bail, Report}; 14 | use log::info; 15 | use serde::Serialize; 16 | use thiserror::Error; 17 | use ttyrecall_common::Size; 18 | 19 | use crate::{daemon::Compress, manager::Manager}; 20 | 21 | /// A running pty session 22 | struct PtySession { 23 | writer: Box, 24 | measurer: Measurer, 25 | start_ns: u64, 26 | comm: String, 27 | /// Wait for the first resize event to correctly populate the width/height metadata. 28 | staged_events: Option>, 29 | budget: Option, 30 | } 31 | 32 | #[derive(Error, Debug)] 33 | enum Error { 34 | #[error("io failure")] 35 | Io(#[from] io::Error), 36 | #[error("budget overran, {actual} > {budget}")] 37 | BudgetOverran { budget: usize, actual: usize }, 38 | #[error("json serialization failure")] 39 | JsonSerialization(#[from] serde_json::Error), 40 | #[error("other")] 41 | Other(#[from] Report), 42 | } 43 | 44 | impl PtySession { 45 | pub fn new( 46 | manager: &Manager, 47 | pty_id: u32, 48 | uid: u32, 49 | comm: String, 50 | start_ns: u64, 51 | ) -> Result { 52 | let file = MeasuredFile::from(manager.create_recording_file(uid.into(), pty_id, &comm)?); 53 | let measurer = file.measurer(); 54 | let writer: Box = match manager.compress { 55 | Compress::None => Box::new(BufWriter::new(file)), 56 | // zstd has its own internal buffer 57 | Compress::Zstd(level) => { 58 | Box::new(zstd::Encoder::new(file, level.unwrap_or(0))?.auto_finish()) 59 | } 60 | }; 61 | Ok(Self { 62 | writer, 63 | start_ns, 64 | measurer, 65 | comm, 66 | staged_events: Some(Vec::new()), 67 | budget: None, 68 | }) 69 | } 70 | 71 | pub fn with_budget(mut self, budget: Option) -> Self { 72 | self.budget = budget; 73 | self 74 | } 75 | 76 | /// Write all staged events and remove staging buffer 77 | pub fn flush_staged(&mut self) -> Result<(), Error> { 78 | for e in self.staged_events.take().unwrap() { 79 | match e { 80 | StagedEvent::Metadata { size, timestamp } => { 81 | writeln!( 82 | self.writer, 83 | r#"{{"version": 2, "width": {}, "height": {}, "timestamp": {}, "env": {{"TERM": "xterm-256color"}}}}"#, 84 | size.width, size.height, timestamp 85 | )?; 86 | } 87 | StagedEvent::Write { content, time_ns } => { 88 | self.write(&content, time_ns)?; 89 | } 90 | } 91 | } 92 | Ok(()) 93 | } 94 | 95 | pub fn stage_event(&mut self, value: StagedEvent) { 96 | if let Some(staged) = self.staged_events.as_mut() { 97 | staged.push(value); 98 | } else { 99 | panic!("No staging buffer"); 100 | } 101 | } 102 | 103 | pub fn staged_event_count(&self) -> Option { 104 | self.staged_events.as_ref().map(|e| e.len()) 105 | } 106 | 107 | pub fn write(&mut self, content: &str, time_ns: u64) -> Result<(), Error> { 108 | self.budget_overran()?; 109 | let diff_secs = Duration::from_nanos(time_ns - self.start_ns).as_secs_f64(); 110 | let mut ser = serde_json::Serializer::new(&mut self.writer); 111 | (diff_secs, "o", content).serialize(&mut ser)?; 112 | writeln!(self.writer)?; 113 | Ok(()) 114 | } 115 | 116 | pub fn resize(&mut self, size: Size, time_ns: u64) -> Result<(), Error> { 117 | self.budget_overran()?; 118 | let diff_secs = Duration::from_nanos(time_ns - self.start_ns).as_secs_f64(); 119 | let mut ser = serde_json::Serializer::new(&mut self.writer); 120 | (diff_secs, "r", format!("{}x{}", size.width, size.height)).serialize(&mut ser)?; 121 | writeln!(self.writer)?; 122 | Ok(()) 123 | } 124 | 125 | pub fn budget_overran(&self) -> Result<(), Error> { 126 | if let Some(budget) = self.budget { 127 | let measure = self.measurer.measure(); 128 | let budget = budget.into(); 129 | if measure > budget { 130 | Err(Error::BudgetOverran { 131 | budget, 132 | actual: measure, 133 | }) 134 | } else { 135 | Ok(()) 136 | } 137 | } else { 138 | Ok(()) 139 | } 140 | } 141 | 142 | pub fn first_staged_event_mut(&mut self) -> Option<&mut StagedEvent> { 143 | self.staged_events.as_mut().and_then(|e| e.first_mut()) 144 | } 145 | } 146 | 147 | impl Drop for PtySession { 148 | fn drop(&mut self) { 149 | // flush all staged events 150 | if self.staged_events.is_some() { 151 | self.flush_staged().unwrap(); 152 | } 153 | // By default BufWriter will ignore errors when dropping. 154 | self.writer.flush().unwrap(); 155 | } 156 | } 157 | 158 | pub struct PtySessionManager { 159 | sessions: HashMap, 160 | manager: Rc, 161 | budget: Option, 162 | } 163 | 164 | const STAGED_EVENT_MAX: usize = 50; 165 | 166 | impl PtySessionManager { 167 | pub fn new(manager: Rc, budget: Option) -> Self { 168 | Self { 169 | sessions: HashMap::new(), 170 | manager, 171 | budget, 172 | } 173 | } 174 | 175 | pub fn add_session( 176 | &mut self, 177 | pty_id: u32, 178 | uid: u32, 179 | comm: String, 180 | start_ns: u64, 181 | ) -> color_eyre::Result<()> { 182 | info!("add_session({pty_id}, {uid}, {comm}, {start_ns})"); 183 | if self.sessions.contains_key(&pty_id) { 184 | bail!("A pty session numbered {pty_id} already exists!"); 185 | } 186 | let mut session = 187 | PtySession::new(&self.manager, pty_id, uid, comm, start_ns)?.with_budget(self.budget); 188 | session.stage_event(StagedEvent::Metadata { 189 | size: Size::default(), 190 | timestamp: Utc::now().timestamp(), 191 | }); 192 | self.sessions.insert(pty_id, session); 193 | Ok(()) 194 | } 195 | 196 | pub fn resize_session(&mut self, id: u32, time_ns: u64, size: Size) -> color_eyre::Result<()> { 197 | let Some(session) = self.sessions.get_mut(&id) else { 198 | bail!("Pty session {id} does not exist"); 199 | }; 200 | if size.is_zero() { 201 | // Ignore resize event with zero size 202 | return Ok(()); 203 | } 204 | let r = if let Some(first) = session.first_staged_event_mut() { 205 | match first { 206 | StagedEvent::Metadata { size: psize, .. } => *psize = size, 207 | _ => unreachable!(), 208 | } 209 | session.flush_staged() 210 | } else { 211 | session.resize(size, time_ns) 212 | }; 213 | match r { 214 | Err(Error::BudgetOverran { budget, actual }) => { 215 | info!( 216 | "pty{id} from {comm} has written {actual} bytes, overran budget {budget}. Stop tracking it.", 217 | comm = session.comm 218 | ); 219 | self.sessions.remove(&id); 220 | } 221 | r => r?, 222 | } 223 | Ok(()) 224 | } 225 | 226 | pub fn write_to(&mut self, id: u32, content: &str, time_ns: u64) -> color_eyre::Result<()> { 227 | let Some(session) = self.sessions.get_mut(&id) else { 228 | bail!("Pty session {id} does not exist"); 229 | }; 230 | let r = if let Some(cnt) = session.staged_event_count() { 231 | if cnt < STAGED_EVENT_MAX { 232 | session.stage_event(StagedEvent::Write { 233 | content: content.to_owned(), 234 | time_ns, 235 | }); 236 | return Ok(()); 237 | } else { 238 | session.flush_staged() 239 | } 240 | } else { 241 | session.write(content, time_ns) 242 | }; 243 | match r { 244 | Err(Error::BudgetOverran { budget, actual }) => { 245 | info!( 246 | "pty{id} from {comm} has written {actual} bytes, overran budget {budget}. Stop tracking it.", 247 | comm = session.comm 248 | ); 249 | self.sessions.remove(&id); 250 | } 251 | r => r?, 252 | } 253 | Ok(()) 254 | } 255 | 256 | pub fn exists(&self, id: u32) -> bool { 257 | self.sessions.contains_key(&id) 258 | } 259 | 260 | pub fn remove_session(&mut self, id: u32) { 261 | info!("remove_session({id})"); 262 | self.sessions.remove(&id); 263 | } 264 | } 265 | 266 | #[derive(Debug)] 267 | enum StagedEvent { 268 | Metadata { size: Size, timestamp: i64 }, 269 | Write { content: String, time_ns: u64 }, 270 | } 271 | 272 | /// A measured [`File`]` that records the amount of writes occurred. 273 | struct MeasuredFile { 274 | inner: File, 275 | total_writes: Rc>, 276 | } 277 | 278 | struct Measurer(Rc>); 279 | 280 | impl MeasuredFile { 281 | pub fn measurer(&self) -> Measurer { 282 | Measurer(self.total_writes.clone()) 283 | } 284 | } 285 | 286 | impl From for MeasuredFile { 287 | fn from(value: File) -> Self { 288 | Self { 289 | inner: value, 290 | total_writes: Rc::new(RefCell::new(0)), 291 | } 292 | } 293 | } 294 | 295 | impl Measurer { 296 | pub fn measure(&self) -> usize { 297 | *self.0.borrow() 298 | } 299 | } 300 | 301 | impl Write for MeasuredFile { 302 | fn write(&mut self, buf: &[u8]) -> std::io::Result { 303 | self.inner 304 | .write(buf) 305 | .inspect(|size| *self.total_writes.borrow_mut() += size) 306 | } 307 | 308 | fn flush(&mut self) -> std::io::Result<()> { 309 | self.inner.flush() 310 | } 311 | } 312 | 313 | #[cfg(test)] 314 | mod test {} 315 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "GPL-2.0-or-later" 6 | 7 | [dependencies] 8 | anyhow = "1" 9 | clap = { version = "4.1", features = ["derive"] } 10 | -------------------------------------------------------------------------------- /xtask/LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /xtask/src/build.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | 3 | use anyhow::Context as _; 4 | use clap::Parser; 5 | 6 | use crate::build_ebpf::{build_ebpf, Architecture, Options as BuildOptions}; 7 | 8 | #[derive(Debug, Parser)] 9 | pub struct Options { 10 | /// Set the endianness of the BPF target 11 | #[clap(default_value = "bpfel-unknown-none", long)] 12 | pub bpf_target: Architecture, 13 | /// Build and run the release target 14 | #[clap(long)] 15 | pub release: bool, 16 | #[clap(long)] 17 | pub disable_resource_saving: bool, 18 | } 19 | 20 | /// Build the project 21 | fn build_project(opts: &Options) -> Result<(), anyhow::Error> { 22 | let mut args = vec!["build"]; 23 | if opts.release { 24 | args.push("--release") 25 | } 26 | let status = Command::new("cargo") 27 | .args(&args) 28 | .status() 29 | .expect("failed to build userspace"); 30 | assert!(status.success()); 31 | Ok(()) 32 | } 33 | 34 | /// Build our ebpf program and the project 35 | pub fn build(opts: Options) -> Result<(), anyhow::Error> { 36 | // build our ebpf program followed by our application 37 | build_ebpf(BuildOptions { 38 | target: opts.bpf_target, 39 | release: opts.release, 40 | disable_resource_saving: opts.disable_resource_saving, 41 | }) 42 | .context("Error while building eBPF program")?; 43 | build_project(&opts).context("Error while building userspace application")?; 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /xtask/src/build_ebpf.rs: -------------------------------------------------------------------------------- 1 | use std::{path::PathBuf, process::Command}; 2 | 3 | use clap::Parser; 4 | 5 | #[derive(Debug, Copy, Clone)] 6 | pub enum Architecture { 7 | BpfEl, 8 | BpfEb, 9 | } 10 | 11 | impl std::str::FromStr for Architecture { 12 | type Err = String; 13 | 14 | fn from_str(s: &str) -> Result { 15 | Ok(match s { 16 | "bpfel-unknown-none" => Architecture::BpfEl, 17 | "bpfeb-unknown-none" => Architecture::BpfEb, 18 | _ => return Err("invalid target".to_owned()), 19 | }) 20 | } 21 | } 22 | 23 | impl std::fmt::Display for Architecture { 24 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 25 | f.write_str(match self { 26 | Architecture::BpfEl => "bpfel-unknown-none", 27 | Architecture::BpfEb => "bpfeb-unknown-none", 28 | }) 29 | } 30 | } 31 | 32 | #[derive(Debug, Parser)] 33 | pub struct Options { 34 | /// Set the endianness of the BPF target 35 | #[clap(default_value = "bpfel-unknown-none", long)] 36 | pub target: Architecture, 37 | /// Build the release target 38 | #[clap(long)] 39 | pub release: bool, 40 | #[clap(long)] 41 | pub disable_resource_saving: bool, 42 | } 43 | 44 | pub fn build_ebpf(opts: Options) -> Result<(), anyhow::Error> { 45 | let dir = PathBuf::from("ttyrecall-ebpf"); 46 | let target = format!("--target={}", opts.target); 47 | let mut args = vec!["build", target.as_str(), "-Z", "build-std=core"]; 48 | if opts.release { 49 | args.push("--release") 50 | } 51 | if opts.disable_resource_saving { 52 | args.push("--no-default-features"); 53 | } 54 | 55 | // Command::new creates a child process which inherits all env variables. This means env 56 | // vars set by the cargo xtask command are also inherited. RUSTUP_TOOLCHAIN is removed 57 | // so the rust-toolchain.toml file in the -ebpf folder is honored. 58 | 59 | let status = Command::new("cargo") 60 | .current_dir(dir) 61 | .env_remove("RUSTUP_TOOLCHAIN") 62 | .args(&args) 63 | .status() 64 | .expect("failed to build bpf program"); 65 | assert!(status.success()); 66 | Ok(()) 67 | } 68 | -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | mod build; 2 | mod build_ebpf; 3 | mod run; 4 | 5 | use std::process::exit; 6 | 7 | use clap::Parser; 8 | 9 | #[derive(Debug, Parser)] 10 | pub struct Options { 11 | #[clap(subcommand)] 12 | command: Command, 13 | } 14 | 15 | #[derive(Debug, Parser)] 16 | enum Command { 17 | BuildEbpf(build_ebpf::Options), 18 | Build(build::Options), 19 | Run(run::Options), 20 | } 21 | 22 | fn main() { 23 | let opts = Options::parse(); 24 | 25 | use Command::*; 26 | let ret = match opts.command { 27 | BuildEbpf(opts) => build_ebpf::build_ebpf(opts), 28 | Run(opts) => run::run(opts), 29 | Build(opts) => build::build(opts), 30 | }; 31 | 32 | if let Err(e) = ret { 33 | eprintln!("{e:#}"); 34 | exit(1); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /xtask/src/run.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | 3 | use anyhow::Context as _; 4 | use clap::Parser; 5 | 6 | use crate::{ 7 | build::{build, Options as BuildOptions}, 8 | build_ebpf::Architecture, 9 | }; 10 | 11 | #[derive(Debug, Parser)] 12 | pub struct Options { 13 | /// Set the endianness of the BPF target 14 | #[clap(default_value = "bpfel-unknown-none", long)] 15 | pub bpf_target: Architecture, 16 | /// Build and run the release target 17 | #[clap(long)] 18 | pub release: bool, 19 | /// The command used to wrap your application 20 | #[clap(short, long, default_value = "sudo -E")] 21 | pub runner: String, 22 | #[clap(long)] 23 | pub disable_resource_saving: bool, 24 | /// Arguments to pass to your application 25 | #[clap(name = "args", last = true)] 26 | pub run_args: Vec, 27 | } 28 | 29 | /// Build and run the project 30 | pub fn run(opts: Options) -> Result<(), anyhow::Error> { 31 | // Build our ebpf program and the project 32 | build(BuildOptions { 33 | bpf_target: opts.bpf_target, 34 | release: opts.release, 35 | disable_resource_saving: opts.disable_resource_saving, 36 | }) 37 | .context("Error while building project")?; 38 | 39 | // profile we are building (release or debug) 40 | let profile = if opts.release { "release" } else { "debug" }; 41 | let bin_path = format!("target/{profile}/ttyrecall"); 42 | 43 | // arguments to pass to the application 44 | let mut run_args: Vec<_> = opts.run_args.iter().map(String::as_str).collect(); 45 | 46 | // configure args 47 | let mut args: Vec<_> = opts.runner.trim().split_terminator(' ').collect(); 48 | args.push(bin_path.as_str()); 49 | args.append(&mut run_args); 50 | 51 | // run the command 52 | let status = Command::new(args.first().expect("No first argument")) 53 | .args(args.iter().skip(1)) 54 | .status() 55 | .expect("failed to run the command"); 56 | 57 | if !status.success() { 58 | anyhow::bail!("Failed to run `{}`", args.join(" ")); 59 | } 60 | Ok(()) 61 | } 62 | --------------------------------------------------------------------------------