├── .gitignore ├── .travis.yml ├── AUR ├── .SRCINFO ├── .gitignore └── PKGBUILD ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── configuration-schema.owl ├── example_projection.yml └── src ├── cli.yml ├── config.rs ├── fsop.rs ├── libc_bridge ├── libc_extras.rs ├── libc_wrappers.rs └── mod.rs ├── main.rs └── projfs.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | .undodir/ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | - osx 4 | language: rust 5 | rust: 6 | - stable 7 | - beta 8 | - nightly 9 | matrix: 10 | allow_failures: 11 | - rust: nightly 12 | - os: osx 13 | install: 14 | - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get -qq update && sudo apt-get install -y libfuse-dev; fi 15 | - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew update && brew cask install osxfuse; fi 16 | script: 17 | - cargo build 18 | - cargo test 19 | - cargo doc --no-deps 20 | -------------------------------------------------------------------------------- /AUR/.SRCINFO: -------------------------------------------------------------------------------- 1 | pkgbase = projfs 2 | pkgdesc = A FUSE filesystem which does projection of directory content through a custom command. Capable of doing audio/video transparent transcoding (e.g. any music file to mp3). 3 | pkgver = 0.1.3 4 | pkgrel = 2 5 | url = https://github.com/renyuneyun/projfs 6 | arch = x86_64 7 | license = Apache 8 | makedepends = rust 9 | makedepends = cargo 10 | depends = fuse2 11 | source = git+https://github.com/renyuneyun/projfs.git#tag=v0.1.3 12 | sha256sums = SKIP 13 | 14 | pkgname = projfs 15 | 16 | -------------------------------------------------------------------------------- /AUR/.gitignore: -------------------------------------------------------------------------------- 1 | src/ 2 | pkg/ 3 | projfs/ 4 | *.pkg.tar* 5 | -------------------------------------------------------------------------------- /AUR/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: renyuneyun (Rui ZHAO) 2 | pkgname=projfs 3 | pkgver=0.1.3 4 | pkgrel=2 5 | pkgdesc='A FUSE filesystem which does projection of directory content through a custom command. Capable of doing audio/video transparent transcoding (e.g. any music file to mp3).' 6 | url='https://github.com/renyuneyun/projfs' 7 | license=('Apache') 8 | depends=('fuse2') 9 | makedepends=('rust' 'cargo') 10 | arch=('x86_64') 11 | source=("git+https://github.com/renyuneyun/projfs.git#tag=v$pkgver") 12 | sha256sums=('SKIP') 13 | 14 | build() { 15 | cd "$pkgname" 16 | cargo build --release --locked 17 | } 18 | 19 | package() { 20 | cd "$pkgname" 21 | install -Dm755 "target/release/$pkgname" "$pkgdir/usr/bin/$pkgname" 22 | install -Dm755 "$srcdir/$pkgname/example_projection.yml" "$pkgdir/usr/share/projfs/$pkgname/" 23 | } 24 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "aho-corasick" 5 | version = "0.7.6" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | dependencies = [ 8 | "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 9 | ] 10 | 11 | [[package]] 12 | name = "ansi_term" 13 | version = "0.9.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | 16 | [[package]] 17 | name = "arrayref" 18 | version = "0.3.5" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | 21 | [[package]] 22 | name = "arrayvec" 23 | version = "0.4.12" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | dependencies = [ 26 | "nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 27 | ] 28 | 29 | [[package]] 30 | name = "atty" 31 | version = "0.2.13" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | dependencies = [ 34 | "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", 35 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 36 | ] 37 | 38 | [[package]] 39 | name = "backtrace" 40 | version = "0.3.40" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | dependencies = [ 43 | "backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 44 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 45 | "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", 46 | "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 47 | ] 48 | 49 | [[package]] 50 | name = "backtrace-sys" 51 | version = "0.1.32" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | dependencies = [ 54 | "cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)", 55 | "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", 56 | ] 57 | 58 | [[package]] 59 | name = "base64" 60 | version = "0.10.1" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | dependencies = [ 63 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 64 | ] 65 | 66 | [[package]] 67 | name = "bimap" 68 | version = "0.4.0" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | dependencies = [ 71 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 72 | ] 73 | 74 | [[package]] 75 | name = "bitflags" 76 | version = "0.9.1" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | 79 | [[package]] 80 | name = "bitflags" 81 | version = "1.2.1" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | 84 | [[package]] 85 | name = "blake2b_simd" 86 | version = "0.5.8" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | dependencies = [ 89 | "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 90 | "arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 91 | "constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 92 | ] 93 | 94 | [[package]] 95 | name = "byteorder" 96 | version = "1.3.2" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | 99 | [[package]] 100 | name = "cc" 101 | version = "1.0.46" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | 104 | [[package]] 105 | name = "cfg-if" 106 | version = "0.1.10" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | 109 | [[package]] 110 | name = "clap" 111 | version = "2.27.1" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | dependencies = [ 114 | "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 115 | "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 116 | "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 117 | "strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 118 | "textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 119 | "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 120 | "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 121 | "yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 122 | ] 123 | 124 | [[package]] 125 | name = "cloudabi" 126 | version = "0.0.3" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | dependencies = [ 129 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 130 | ] 131 | 132 | [[package]] 133 | name = "constant_time_eq" 134 | version = "0.1.4" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | 137 | [[package]] 138 | name = "crossbeam-utils" 139 | version = "0.6.6" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | dependencies = [ 142 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 143 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 144 | ] 145 | 146 | [[package]] 147 | name = "dirs" 148 | version = "2.0.2" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | dependencies = [ 151 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 152 | "dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 153 | ] 154 | 155 | [[package]] 156 | name = "dirs-sys" 157 | version = "0.3.4" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | dependencies = [ 160 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 161 | "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", 162 | "redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 163 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 164 | ] 165 | 166 | [[package]] 167 | name = "dtoa" 168 | version = "0.4.4" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | 171 | [[package]] 172 | name = "env_logger" 173 | version = "0.7.1" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | dependencies = [ 176 | "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 177 | "humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 178 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 179 | "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 180 | "termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 181 | ] 182 | 183 | [[package]] 184 | name = "failure" 185 | version = "0.1.6" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | dependencies = [ 188 | "backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)", 189 | "failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 190 | ] 191 | 192 | [[package]] 193 | name = "failure_derive" 194 | version = "0.1.6" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | dependencies = [ 197 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 198 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 199 | "syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 200 | "synstructure 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", 201 | ] 202 | 203 | [[package]] 204 | name = "fuchsia-cprng" 205 | version = "0.1.1" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | 208 | [[package]] 209 | name = "fuse" 210 | version = "0.3.1" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | dependencies = [ 213 | "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", 214 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 215 | "pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", 216 | "thread-scoped 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 217 | "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 218 | ] 219 | 220 | [[package]] 221 | name = "fuse_mt" 222 | version = "0.5.0" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | dependencies = [ 225 | "fuse 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 226 | "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", 227 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 228 | "threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)", 229 | "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 230 | ] 231 | 232 | [[package]] 233 | name = "humantime" 234 | version = "1.3.0" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | dependencies = [ 237 | "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 238 | ] 239 | 240 | [[package]] 241 | name = "lazy_static" 242 | version = "1.4.0" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | 245 | [[package]] 246 | name = "libc" 247 | version = "0.2.62" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | 250 | [[package]] 251 | name = "linked-hash-map" 252 | version = "0.5.2" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | 255 | [[package]] 256 | name = "log" 257 | version = "0.3.9" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | dependencies = [ 260 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 261 | ] 262 | 263 | [[package]] 264 | name = "log" 265 | version = "0.4.8" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | dependencies = [ 268 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 269 | ] 270 | 271 | [[package]] 272 | name = "memchr" 273 | version = "2.2.1" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | 276 | [[package]] 277 | name = "mime" 278 | version = "0.3.14" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | 281 | [[package]] 282 | name = "mime_guess" 283 | version = "2.0.1" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | dependencies = [ 286 | "mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", 287 | "unicase 2.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 288 | ] 289 | 290 | [[package]] 291 | name = "nodrop" 292 | version = "0.1.14" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | 295 | [[package]] 296 | name = "num_cpus" 297 | version = "1.10.1" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | dependencies = [ 300 | "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", 301 | ] 302 | 303 | [[package]] 304 | name = "pkg-config" 305 | version = "0.3.16" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | 308 | [[package]] 309 | name = "proc-macro2" 310 | version = "1.0.6" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | dependencies = [ 313 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 314 | ] 315 | 316 | [[package]] 317 | name = "projfs" 318 | version = "0.1.3" 319 | dependencies = [ 320 | "bimap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 321 | "clap 2.27.1 (registry+https://github.com/rust-lang/crates.io-index)", 322 | "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 323 | "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", 324 | "fuse_mt 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 325 | "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", 326 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 327 | "mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 328 | "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 329 | "seahash 3.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 330 | "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", 331 | "serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)", 332 | "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 333 | ] 334 | 335 | [[package]] 336 | name = "quick-error" 337 | version = "1.2.2" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | 340 | [[package]] 341 | name = "quote" 342 | version = "1.0.2" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | dependencies = [ 345 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 346 | ] 347 | 348 | [[package]] 349 | name = "rand_core" 350 | version = "0.3.1" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | dependencies = [ 353 | "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 354 | ] 355 | 356 | [[package]] 357 | name = "rand_core" 358 | version = "0.4.2" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | 361 | [[package]] 362 | name = "rand_os" 363 | version = "0.1.3" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | dependencies = [ 366 | "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 367 | "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 368 | "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", 369 | "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 370 | "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 371 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 372 | ] 373 | 374 | [[package]] 375 | name = "rdrand" 376 | version = "0.4.0" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | dependencies = [ 379 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 380 | ] 381 | 382 | [[package]] 383 | name = "redox_syscall" 384 | version = "0.1.56" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | 387 | [[package]] 388 | name = "redox_users" 389 | version = "0.3.1" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | dependencies = [ 392 | "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 393 | "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 394 | "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", 395 | "rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 396 | ] 397 | 398 | [[package]] 399 | name = "regex" 400 | version = "1.3.1" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | dependencies = [ 403 | "aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", 404 | "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 405 | "regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 406 | "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 407 | ] 408 | 409 | [[package]] 410 | name = "regex-syntax" 411 | version = "0.6.12" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | 414 | [[package]] 415 | name = "rust-argon2" 416 | version = "0.5.1" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | dependencies = [ 419 | "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", 420 | "blake2b_simd 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", 421 | "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", 422 | ] 423 | 424 | [[package]] 425 | name = "rustc-demangle" 426 | version = "0.1.16" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | 429 | [[package]] 430 | name = "seahash" 431 | version = "3.0.6" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | 434 | [[package]] 435 | name = "serde" 436 | version = "1.0.102" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | dependencies = [ 439 | "serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", 440 | ] 441 | 442 | [[package]] 443 | name = "serde_derive" 444 | version = "1.0.102" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | dependencies = [ 447 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 448 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 449 | "syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 450 | ] 451 | 452 | [[package]] 453 | name = "serde_yaml" 454 | version = "0.8.11" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | dependencies = [ 457 | "dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 458 | "linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 459 | "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", 460 | "yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", 461 | ] 462 | 463 | [[package]] 464 | name = "strsim" 465 | version = "0.6.0" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | 468 | [[package]] 469 | name = "syn" 470 | version = "1.0.7" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | dependencies = [ 473 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 474 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 475 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 476 | ] 477 | 478 | [[package]] 479 | name = "synstructure" 480 | version = "0.12.1" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | dependencies = [ 483 | "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 484 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 485 | "syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 486 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 487 | ] 488 | 489 | [[package]] 490 | name = "termcolor" 491 | version = "1.0.5" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | dependencies = [ 494 | "wincolor 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 495 | ] 496 | 497 | [[package]] 498 | name = "textwrap" 499 | version = "0.9.0" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | dependencies = [ 502 | "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 503 | ] 504 | 505 | [[package]] 506 | name = "thread-scoped" 507 | version = "1.0.2" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | 510 | [[package]] 511 | name = "thread_local" 512 | version = "0.3.6" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | dependencies = [ 515 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 516 | ] 517 | 518 | [[package]] 519 | name = "threadpool" 520 | version = "1.7.1" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | dependencies = [ 523 | "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", 524 | ] 525 | 526 | [[package]] 527 | name = "time" 528 | version = "0.1.42" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | dependencies = [ 531 | "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", 532 | "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", 533 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 534 | ] 535 | 536 | [[package]] 537 | name = "unicase" 538 | version = "2.5.1" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | dependencies = [ 541 | "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 542 | ] 543 | 544 | [[package]] 545 | name = "unicode-width" 546 | version = "0.1.6" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | 549 | [[package]] 550 | name = "unicode-xid" 551 | version = "0.2.0" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | 554 | [[package]] 555 | name = "vec_map" 556 | version = "0.8.1" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | 559 | [[package]] 560 | name = "version_check" 561 | version = "0.1.5" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | 564 | [[package]] 565 | name = "winapi" 566 | version = "0.3.8" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | dependencies = [ 569 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 570 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 571 | ] 572 | 573 | [[package]] 574 | name = "winapi-i686-pc-windows-gnu" 575 | version = "0.4.0" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | 578 | [[package]] 579 | name = "winapi-util" 580 | version = "0.1.2" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | dependencies = [ 583 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 584 | ] 585 | 586 | [[package]] 587 | name = "winapi-x86_64-pc-windows-gnu" 588 | version = "0.4.0" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | 591 | [[package]] 592 | name = "wincolor" 593 | version = "1.0.2" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | dependencies = [ 596 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 597 | "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 598 | ] 599 | 600 | [[package]] 601 | name = "yaml-rust" 602 | version = "0.3.5" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | 605 | [[package]] 606 | name = "yaml-rust" 607 | version = "0.4.3" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | dependencies = [ 610 | "linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 611 | ] 612 | 613 | [metadata] 614 | "checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" 615 | "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" 616 | "checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" 617 | "checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" 618 | "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" 619 | "checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" 620 | "checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" 621 | "checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" 622 | "checksum bimap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "783204f24fd7724ea274d327619cfa6a6018047bb0561a68aadff6f56787591b" 623 | "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" 624 | "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 625 | "checksum blake2b_simd 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "5850aeee1552f495dd0250014cf64b82b7c8879a89d83b33bbdace2cc4f63182" 626 | "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" 627 | "checksum cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)" = "0213d356d3c4ea2c18c40b037c3be23cd639825c18f25ee670ac7813beeef99c" 628 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 629 | "checksum clap 2.27.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1b8c532887f1a292d17de05ae858a8fe50a301e196f9ef0ddb7ccd0d1d00f180" 630 | "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 631 | "checksum constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120" 632 | "checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" 633 | "checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" 634 | "checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" 635 | "checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" 636 | "checksum env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 637 | "checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" 638 | "checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" 639 | "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 640 | "checksum fuse 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80e57070510966bfef93662a81cb8aa2b1c7db0964354fa9921434f04b9e8660" 641 | "checksum fuse_mt 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e63cd7846849c0edf7c8655b3e952848117b2670fa8054ea2fc123040577414b" 642 | "checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 643 | "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 644 | "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" 645 | "checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" 646 | "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" 647 | "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 648 | "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" 649 | "checksum mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "dd1d63acd1b78403cc0c325605908475dd9b9a3acbf65ed8bcab97e27014afcf" 650 | "checksum mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a0ed03949aef72dbdf3116a383d7b38b4768e6f960528cd6a6044aa9ed68599" 651 | "checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" 652 | "checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" 653 | "checksum pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "72d5370d90f49f70bd033c3d75e87fc529fbfff9d6f7cccef07d6170079d91ea" 654 | "checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" 655 | "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" 656 | "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" 657 | "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 658 | "checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 659 | "checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" 660 | "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 661 | "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 662 | "checksum redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d" 663 | "checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" 664 | "checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" 665 | "checksum rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf" 666 | "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" 667 | "checksum seahash 3.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "18d6061ff4917fac173fa07b839c8c3f805c0bf3801c52499cc85cdbad8c28df" 668 | "checksum serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4b39bd9b0b087684013a792c59e3e07a46a01d2322518d8a1104641a0b1be0" 669 | "checksum serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)" = "ca13fc1a832f793322228923fbb3aba9f3f44444898f835d31ad1b74fa0a2bf8" 670 | "checksum serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)" = "691b17f19fc1ec9d94ec0b5864859290dff279dbd7b03f017afda54eb36c3c35" 671 | "checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" 672 | "checksum syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0e7bedb3320d0f3035594b0b723c8a28d7d336a3eda3881db79e61d676fb644c" 673 | "checksum synstructure 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f085a5855930c0441ca1288cf044ea4aecf4f43a91668abdb870b4ba546a203" 674 | "checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e" 675 | "checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693" 676 | "checksum thread-scoped 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bcbb6aa301e5d3b0b5ef639c9a9c7e2f1c944f177b460c04dc24c69b1fa2bd99" 677 | "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" 678 | "checksum threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865" 679 | "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" 680 | "checksum unicase 2.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2e2e6bd1e59e56598518beb94fd6db628ded570326f0a98c679a304bd9f00150" 681 | "checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20" 682 | "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 683 | "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" 684 | "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" 685 | "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 686 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 687 | "checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" 688 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 689 | "checksum wincolor 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "96f5016b18804d24db43cebf3c77269e7569b8954a8464501c216cc5e070eaa9" 690 | "checksum yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e66366e18dc58b46801afbf2ca7661a9f59cc8c5962c29892b6039b4f86fa992" 691 | "checksum yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" 692 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "projfs" 3 | version = "0.1.3" 4 | authors = ["renyuneyun (Rui ZHAO) "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | bimap = "0.4" 9 | clap = {version = "~2.27.0", features = ["yaml"]} 10 | dirs = "2.0" 11 | env_logger = "0.7" 12 | fuse_mt = "0.5" 13 | libc = "0.2" 14 | log = "0.4" 15 | mime_guess = "2.0" 16 | regex = "1.3" 17 | seahash = "3.0" 18 | serde = { version = "1.0", features = ["derive"] } 19 | serde_yaml = "0.8" 20 | time = "0.1" 21 | 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | 3 | Version 2.0, January 2004 4 | 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 16 | 17 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 18 | 19 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 20 | 21 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 22 | 23 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 24 | 25 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 26 | 27 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 28 | 29 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 30 | 31 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 32 | 33 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 34 | 35 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 36 | 37 | You must give any other recipients of the Work or Derivative Works a copy of this License; and 38 | You must cause any modified files to carry prominent notices stating that You changed the files; and 39 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 40 | If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 41 | 42 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 43 | 44 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 45 | 46 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 47 | 48 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 49 | 50 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 51 | 52 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 53 | 54 | END OF TERMS AND CONDITIONS 55 | 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ***Proj***ection ***F***ile***S***ystem [![Build Status](https://travis-ci.org/renyuneyun/projfs.svg?branch=master)](https://travis-ci.org/renyuneyun/projfs) 2 | - - - - - - - 3 | 4 | ProjFS is a FUSE filesystem aiming at providing a general way of doing filesystem projection: project directories and files to a new mount point by converting specified files through a custom projection command. 5 | 6 | It can provide use cases such as automatically converting audio files while keeping the original file. 7 | 8 | # Build 9 | 10 | This project is written in Rust, so just follow the standard way how a Rust application is built: 11 | 12 | ``` 13 | cargo build 14 | ``` 15 | 16 | # Usage 17 | 18 | ## Basic usage 19 | 20 | ``` 21 | projfs 22 | ``` 23 | 24 | The `` is where the new projected filesystem hierarchy will appear. It is (apparently) read-only. 25 | 26 | By default, the program performs the projection by using `ffmpeg` to convert every audio and video file to `ogg` file (audio) (unless it's `ogg` audio already). 27 | 28 | It identifies files by MIME type (using the `mime_guess` crate). All files are provided as-is except for `audio/*` and `video/*` files which are going to be projected. The command used to convert is `ffmpeg -i -vn `. File suffix is changed to `ogg` where applicable. 29 | 30 | ## Advanced usage 31 | 32 | Please see the help document using: 33 | 34 | ``` 35 | projfs --help 36 | ``` 37 | 38 | An example projection specification is available in `example_projection.yml`. It also corresponds to the default behaviour. See the next section for a detailed explanation of the projection specification. 39 | 40 | # Projection Configuration 41 | 42 | An example projection configuration file is placed under `example_projection.yml`, which is the same as not specifying a projection configuration file. The detail of the schema is explained here. 43 | 44 | > The `configuration-schema.owl` file is an ontology describing the schema (i.e. the same as below). RDF/OWL may be promoted to one of the acceptable configuration file types in the future. 45 | 46 | The configuration uses YAML format. The acceptable keys are specified below. Every key is mandatory unless marked as `[optional]`. 47 | 48 | - `mime_types`: a list of strings 49 | File matching mime types specified here will be converted using the `projection_command`, unless it's specified in `ignored_mime_types`. 50 | Each string is either a `mime type` (e.g. `audio/ogg`), or a wildcard of mime types (e.g. `audio/*`). Specifically, shorthand of wildcard (e.g. `audio`, or `audio/`) is accepted, but not encouraged. 51 | - `ignored_mime_types`: [optional] a list of strings 52 | The mime types specified here will not be converted. 53 | The acceptable values are the same as `mime_type`. 54 | - `name_mapping`: a string 55 | The string is the new suffix which the converted file will have. It will replace the original file suffix if any (e.g. `file1.wav` -> `file1.ogg` if `ogg` is specified here). 56 | - `projection_command`: a string 57 | The string specifies the command used to do the conversion. It can accept two varirables, `{input}` and `{output}`: Each will be replaced with the corresponding source file path (`{input}`) and output (cache) file path (`{output}`). 58 | The string will be separated by space and passed to `Command` module. That means there should not be escaped spaces (i.e. `\ `), quoted spaced (e.g. `" qwe"`), etc. Were there any needs to use them, you can write your own script and point to it from here. 59 | 60 | 61 | # TODO 62 | 63 | * [x] Having a default cache dir 64 | * [x] Copying file attributes from source file (except for size) 65 | * [x] Different cache dirs for different source dirs 66 | * [x] Update cache only when necessary 67 | * [ ] Return placeholder information for files under-projection 68 | * [x] Accept configuration 69 | * [x] Custom filetype 70 | * [x] Custom projection command 71 | * [ ] A list of configurations 72 | * [ ] One-to-many projection 73 | * [ ] Background automatic async cache 74 | * [ ] Update cache while running 75 | * [ ] Validate configuration before loading 76 | 77 | # License 78 | 79 | Except for certain files specified afterwards, this project is licensed under the [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) as stated below. 80 | 81 | ## Copyright notice 82 | 83 | Copyright 2019 renyuneyun (Rui Zhao) 84 | 85 | Licensed under the Apache License, Version 2.0 (the "License"); 86 | you may not use this file except in compliance with the License. 87 | You may obtain a copy of the License at 88 | 89 | http://www.apache.org/licenses/LICENSE-2.0 90 | 91 | Unless required by applicable law or agreed to in writing, software 92 | distributed under the License is distributed on an "AS IS" BASIS, 93 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 94 | See the License for the specific language governing permissions and 95 | limitations under the License. 96 | 97 | ## Exception 98 | 99 | The following files under the source directory (`src/`) are directly obtained from the [fuse-mt](https://github.com/wfraser/fuse-mt) project ([commit](https://github.com/wfraser/fuse-mt/tree/97e115667682b4a7e54c1831360b8c572c667db3/example/src)), licensed under Apache 2.0 license and MIT license: 100 | 101 | * `libc_bridge/libc_extras.rs` 102 | * `libc_bridge/libc_bridge.rs` 103 | 104 | The `projfs.rs` and `libc_bridge/mod.rs` files contain unmodified code from [fuse-mt](https://github.com/wfraser/fuse-mt/blob/97e115667682b4a7e54c1831360b8c572c667db3/example/src/passthrough.rs). 105 | 106 | -------------------------------------------------------------------------------- /configuration-schema.owl: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | This ontology illustrates the structure (schema) of the projection configuration of ProjFS project. It also provides a set of individual corresponding to the default projection. 12 | This schema corresponds to the schema used by ProjFS v0.1.3, and is compatible with any earlier versions. 13 | 14 | 15 | 16 | 17 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | The key to be used in the configuration file. 32 | 33 | 34 | 35 | 36 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 1 105 | 106 | 107 | 108 | 109 | 1 110 | 111 | 112 | 113 | 114 | 1 115 | 116 | 117 | 118 | 119 | 1 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | Class of a configuration setting. A valid configuraion should satisfy the constraints of this class. 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 1 144 | 145 | 146 | 147 | ignored_mime_types 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | A value here should be a MIME type. 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 1 182 | 183 | 184 | 185 | mime_types 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 1 198 | 199 | 200 | 201 | name_mapping 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 1 214 | 215 | 216 | 217 | projection_command 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 1 229 | 230 | 231 | This class and its subclasses resemble a particular data, with necessary annotation of extra information. This wrapper exists because datatypes don't have hierarchy or complex structure. 232 | 233 | 234 | 235 | 236 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | ffmpeg -i {input} -vn {output} 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | audio/ogg 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | audio/ 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | video/ 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | .ogg 337 | 338 | 339 | 340 | 341 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | -------------------------------------------------------------------------------- /example_projection.yml: -------------------------------------------------------------------------------- 1 | mime_types: ["audio/", "video/"] 2 | ignored_mime_types: 3 | - "audio/ogg" 4 | name_mapping: ".ogg" 5 | projection_command: "ffmpeg -i {input} -vn {output}" 6 | -------------------------------------------------------------------------------- /src/cli.yml: -------------------------------------------------------------------------------- 1 | name: PROJection FileSystem 2 | version: "0.1.3" 3 | author: renyuneyun (Rui Zhao) 4 | about: A FUSE filesystem which projects an existing directory to a new mount point -- convert specified files through a projection command. 5 | 6 | args: 7 | - projection: 8 | short: p 9 | long: projection 10 | value_name: CONFIG_FILE 11 | help: |- 12 | Loads the projection configuration from CONFIG_FILE 13 | The default behaviour is to project every file with MIME type `audio/*` & `video/*` to ogg audio through ffmpeg (see the `example_projection.yml` file for detail) 14 | takes_value: true 15 | - cache: 16 | short: c 17 | long: cache 18 | value_name: DIRECTORY 19 | help: |- 20 | Sets the cache directory 21 | It defaults to `$XDG_CACHE_HOME/projfs/dir-related-to-SOURCE_DIR` 22 | - SOURCE_DIR: 23 | help: |- 24 | Sets the source directory 25 | SOURCE_DIR is where the data (directory structure) originates 26 | required: true 27 | index: 1 28 | - MOUNTPOINT: 29 | help: |- 30 | Sets the mountpoint 31 | MOUNTPOINT is where the projected filesystem locates 32 | required: true 33 | index: 2 34 | 35 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use mime_guess::{mime, Mime}; 2 | use serde::Deserialize; 3 | use std::convert::From; 4 | use std::ffi::{OsStr, OsString}; 5 | use std::fs::File; 6 | use std::path::PathBuf; 7 | use std::process::Command; 8 | 9 | pub trait ProjectionSpecification: Send + Sync { 10 | fn should_project(&self, mime: &Mime) -> bool; 11 | 12 | fn convert_filename(&self, filename: &OsStr) -> OsString; 13 | 14 | fn project(&self, input: &OsStr, output: &OsStr); 15 | } 16 | 17 | fn user_string_to_mime(string_mime_types: &Vec) -> Vec { 18 | let mut mime_types = Vec::new(); 19 | for mime in string_mime_types { 20 | let mut mime = mime.to_owned(); 21 | if !mime.contains("/") { 22 | mime.push('/'); 23 | } else if mime.ends_with("/*") { 24 | mime.pop(); 25 | } 26 | mime_types.push(mime.parse().unwrap()); 27 | } 28 | mime_types 29 | } 30 | 31 | fn in_mime_vec(mime: &Mime, mime_types: &Vec) -> bool { 32 | for m in mime_types { 33 | if mime.type_() == m.type_() { 34 | if m.subtype() == "" { 35 | return true; 36 | } else { 37 | return mime.subtype() == m.subtype(); 38 | } 39 | } 40 | } 41 | false 42 | } 43 | 44 | #[derive(Debug, PartialEq, Deserialize)] 45 | struct PlainConfig { 46 | mime_types: Vec, 47 | ignored_mime_types: Option>, 48 | name_mapping: String, 49 | projection_command: String, 50 | } 51 | 52 | struct ProjectionConfig { 53 | mime_types: Vec, 54 | ignored_mime_types: Vec, 55 | name_mapping: Box OsString + Sync + Send>, 56 | projection_command: Box, 57 | } 58 | 59 | impl From for ProjectionConfig { 60 | fn from(plain: PlainConfig) -> Self { 61 | let mime_types = user_string_to_mime(plain.mime_types.as_ref()); 62 | let ignored_mime_types = 63 | user_string_to_mime(plain.ignored_mime_types.as_ref().unwrap_or(&Vec::new())); 64 | let _name_mapping = { 65 | let mapping = &(&plain).name_mapping; 66 | if mapping.starts_with(".") { 67 | mapping[1..].to_string() 68 | } else { 69 | mapping.to_string() 70 | } 71 | }; 72 | let name_mapping = move |filename: &OsStr| { 73 | let mut path_buf = PathBuf::from(filename); 74 | path_buf.set_extension(&_name_mapping); 75 | path_buf.into_os_string() 76 | }; 77 | 78 | let parts: Vec = plain 79 | .projection_command 80 | .split(" ") 81 | .map(|s| s.into()) 82 | .collect(); 83 | let projection_command = move |input: &OsStr, output: &OsStr| { 84 | let segments: Vec = parts 85 | .iter() 86 | .map(|s| s.replace("{input}", input.to_str().unwrap())) 87 | .map(|s| s.replace("{output}", output.to_str().unwrap())) 88 | .collect(); 89 | let mut cmd = Command::new(&segments[0]) 90 | .args(&segments[1..]) 91 | .spawn() 92 | .expect("failed to execute process"); 93 | cmd.wait().unwrap(); 94 | }; 95 | ProjectionConfig { 96 | mime_types: mime_types, 97 | ignored_mime_types: ignored_mime_types, 98 | name_mapping: Box::new(name_mapping), 99 | projection_command: Box::new(projection_command), 100 | } 101 | } 102 | } 103 | 104 | impl ProjectionSpecification for ProjectionConfig { 105 | fn should_project(&self, mime: &Mime) -> bool { 106 | !in_mime_vec(mime, &self.ignored_mime_types) && in_mime_vec(mime, &self.mime_types) 107 | } 108 | 109 | fn convert_filename(&self, filename: &OsStr) -> OsString { 110 | return (self.name_mapping)(filename.as_ref()); 111 | } 112 | 113 | fn project(&self, input: &OsStr, output: &OsStr) { 114 | (self.projection_command)(input, output) 115 | } 116 | } 117 | 118 | pub fn load(filename: &OsStr) -> Option> { 119 | let f = match File::open(filename) { 120 | Ok(f) => f, 121 | Err(e) => { 122 | error!( 123 | "Error when opening projection configuration file {:?}: {}", 124 | filename, e 125 | ); 126 | return None; 127 | } 128 | }; 129 | let plain_config: PlainConfig = match serde_yaml::from_reader(f) { 130 | Ok(config) => config, 131 | Err(e) => { 132 | error!( 133 | "Error while reading projection configuration file @ {:?}", 134 | e.location() 135 | ); 136 | return None; 137 | } 138 | }; 139 | Some(Box::new(ProjectionConfig::from(plain_config))) 140 | } 141 | 142 | struct DefaultConfig; 143 | 144 | impl DefaultConfig { 145 | fn _should_project(mime_type: &Mime) -> bool { 146 | return mime_type.type_() == mime::AUDIO && mime_type.subtype() != "ogg" 147 | || mime_type.type_() == mime::VIDEO; 148 | } 149 | 150 | fn _filename_conv(partial: &OsStr) -> OsString { 151 | let mut path_buf = PathBuf::from(partial); 152 | path_buf.set_extension("ogg"); 153 | path_buf.into_os_string() 154 | } 155 | fn _do_proj(input: &OsStr, output: &OsStr) { 156 | debug!("do_proj() call: {:?} -> {:?}", input, output); 157 | let mut cmd = Command::new("ffmpeg") // Streaming 158 | .args(&[ 159 | "-i", 160 | input.to_str().unwrap(), 161 | "-vn", 162 | output.to_str().unwrap(), 163 | ]) 164 | .spawn() 165 | .expect("failed to execute process"); 166 | cmd.wait().unwrap(); 167 | } 168 | } 169 | 170 | impl ProjectionSpecification for DefaultConfig { 171 | fn should_project(&self, mime: &Mime) -> bool { 172 | DefaultConfig::_should_project(mime) 173 | } 174 | 175 | fn convert_filename(&self, filename: &OsStr) -> OsString { 176 | DefaultConfig::_filename_conv(filename) 177 | } 178 | 179 | fn project(&self, input: &OsStr, output: &OsStr) { 180 | DefaultConfig::_do_proj(input, output) 181 | } 182 | } 183 | 184 | pub fn default() -> Box { 185 | Box::new(DefaultConfig {}) 186 | } 187 | -------------------------------------------------------------------------------- /src/fsop.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::OsString; 2 | use std::fs::File; 3 | use std::io::{self, Read, Seek, SeekFrom}; 4 | use std::os::unix::io::{FromRawFd, IntoRawFd}; 5 | use std::path::{Path, PathBuf}; 6 | 7 | use crate::libc_bridge as br; 8 | use crate::libc_bridge::libc::c_int; 9 | use crate::libc_bridge::libc_wrappers; 10 | use fuse_mt::FileAttr; 11 | 12 | pub fn real_path(target: &OsString, partial: &Path) -> OsString { 13 | PathBuf::from(target) 14 | .join(partial.strip_prefix("/").unwrap()) 15 | .into_os_string() 16 | } 17 | 18 | pub fn getattr(path: OsString) -> Result { 19 | match libc_wrappers::lstat(path) { 20 | Ok(stat) => Ok(br::stat_to_fuse(stat)), 21 | Err(e) => Err(e), 22 | } 23 | } 24 | 25 | /// Test if the content of the `file` is newer than the `target`. 26 | /// This functions checks the file modification time. 27 | pub fn is_content_newer(target: OsString, file: OsString) -> Result { 28 | Ok(getattr(target)?.mtime < getattr(file)?.mtime) 29 | } 30 | 31 | /// A file that is not closed upon leaving scope. 32 | pub struct UnmanagedFile { 33 | inner: Option, 34 | } 35 | 36 | impl UnmanagedFile { 37 | pub unsafe fn new(fd: u64) -> UnmanagedFile { 38 | UnmanagedFile { 39 | inner: Some(File::from_raw_fd(fd as i32)), 40 | } 41 | } 42 | } 43 | 44 | impl Drop for UnmanagedFile { 45 | fn drop(&mut self) { 46 | // Release control of the file descriptor so it is not closed. 47 | let file = self.inner.take().unwrap(); 48 | file.into_raw_fd(); 49 | } 50 | } 51 | 52 | impl Read for UnmanagedFile { 53 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 54 | self.inner.as_ref().unwrap().read(buf) 55 | } 56 | fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { 57 | self.inner.as_ref().unwrap().read_to_end(buf) 58 | } 59 | } 60 | 61 | impl Seek for UnmanagedFile { 62 | fn seek(&mut self, pos: SeekFrom) -> io::Result { 63 | self.inner.as_ref().unwrap().seek(pos) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/libc_bridge/libc_extras.rs: -------------------------------------------------------------------------------- 1 | // libc_extras :: Functions missing from the libc crate and wrappers for better cross-platform 2 | // compatibility. 3 | // 4 | // Copyright (c) 2016-2017 by William R. Fraser 5 | // 6 | 7 | pub mod libc { 8 | #![allow(non_camel_case_types)] 9 | 10 | pub use ::libc::*; 11 | 12 | // stuff missing from the libc crate. 13 | extern "system" { 14 | // Specified by POSIX.1-2008; not sure why this is missing. 15 | pub fn fchown(fd: c_int, uid: uid_t, gid: gid_t) -> c_int; 16 | 17 | // On Mac OS X, off_t is always 64 bits. 18 | // https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/64bitPorting/transition/transition.html 19 | #[cfg(target_os = "macos")] 20 | pub fn truncate(path: *const c_char, size: off_t) -> c_int; 21 | 22 | // On Linux, off_t is architecture-dependent, and this is provided for 32-bit systems: 23 | #[cfg(target_os = "linux")] 24 | pub fn truncate64(path: *const c_char, size: off64_t) -> c_int; 25 | 26 | // These XATTR functions are missing from the libc crate on Darwin for some reason. 27 | #[cfg(target_os = "macos")] 28 | pub fn listxattr(path: *const c_char, list: *mut c_char, size: size_t, options: c_int) -> ssize_t; 29 | 30 | #[cfg(target_os = "macos")] 31 | pub fn getxattr(path: *const c_char, name: *const c_char, value: *mut c_void, size: size_t, position: u32, options: c_int) -> ssize_t; 32 | 33 | #[cfg(target_os = "macos")] 34 | pub fn setxattr(path: *const c_char, name: *const c_char, value: *const c_void, size: size_t, flags: c_int, position: u32) -> c_int; 35 | 36 | #[cfg(target_os = "macos")] 37 | pub fn removexattr(path: *const c_char, name: *const c_char, flags: c_int) -> c_int; 38 | } 39 | 40 | // 41 | // Mac-Linux 64-bit compat 42 | // 43 | 44 | #[cfg(target_os = "macos")] 45 | pub type stat64 = stat; 46 | 47 | #[cfg(target_os = "macos")] 48 | pub unsafe fn lstat64(path: *const c_char, stat: *mut stat64) -> c_int { 49 | lstat(path, stat) 50 | } 51 | 52 | #[cfg(target_os = "macos")] 53 | pub unsafe fn fstat64(fd: c_int, stat: *mut stat64) -> c_int { 54 | fstat(fd, stat) 55 | } 56 | 57 | #[cfg(target_os = "macos")] 58 | pub unsafe fn ftruncate64(fd: c_int, length: i64) -> c_int { 59 | ftruncate(fd, length as off_t) 60 | } 61 | 62 | #[cfg(target_os = "macos")] 63 | pub unsafe fn truncate64(path: *const c_char, size: off_t) -> c_int { 64 | truncate(path, size) 65 | } 66 | 67 | #[cfg(target_os = "macos")] 68 | fn timespec_to_timeval(timespec: ×pec) -> timeval { 69 | timeval { 70 | tv_sec: timespec.tv_sec, 71 | tv_usec: timespec.tv_nsec as suseconds_t * 1000, 72 | } 73 | } 74 | 75 | pub const UTIME_OMIT: time_t = ((11 << 30) - 21); 76 | 77 | // Mac OS X does not support futimens; map it to futimes with lower precision. 78 | #[cfg(target_os = "macos")] 79 | pub unsafe fn futimens(fd: c_int, times: *const timespec) -> c_int { 80 | use super::super::libc_wrappers; 81 | let mut times_osx = [timespec_to_timeval(&*times), 82 | timespec_to_timeval(&*times)]; 83 | 84 | let mut stat: Option = None; 85 | 86 | if (*times).tv_nsec == UTIME_OMIT { 87 | // atime is unspecified 88 | 89 | stat = match libc_wrappers::fstat(fd as u64) { 90 | Ok(s) => Some(s), 91 | Err(e) => return e, 92 | }; 93 | 94 | times_osx[0].tv_sec = stat.unwrap().st_atime; 95 | times_osx[0].tv_usec = stat.unwrap().st_atime_nsec as suseconds_t * 1000; 96 | } 97 | 98 | if (*times.offset(1)).tv_nsec == UTIME_OMIT { 99 | // mtime is unspecified 100 | 101 | if stat.is_none() { 102 | stat = match libc_wrappers::fstat(fd as u64) { 103 | Ok(s) => Some(s), 104 | Err(e) => return e, 105 | }; 106 | } 107 | 108 | times_osx[1].tv_sec = stat.unwrap().st_mtime; 109 | times_osx[1].tv_usec = stat.unwrap().st_mtime_nsec as suseconds_t * 1000; 110 | } 111 | 112 | futimes(fd, ×_osx as *const timeval) 113 | } 114 | 115 | // Mac OS X does not support utimensat; map it to lutimes with lower precision. 116 | // The relative path feature of utimensat is not supported by this workaround. 117 | #[cfg(target_os = "macos")] 118 | pub fn utimensat(dirfd: c_int, path: *const c_char, times: *const timespec, 119 | _flag_ignored: c_int) -> c_int { 120 | use super::super::libc_wrappers; 121 | unsafe { 122 | if dirfd != AT_FDCWD { 123 | assert_eq!(*path, b'/' as c_char, "relative paths are not supported here!"); 124 | } 125 | let mut times_osx = [timespec_to_timeval(&*times), 126 | timespec_to_timeval(&*times)]; 127 | 128 | let mut stat: Option = None; 129 | fn stat_if_needed(path: *const c_char, stat: &mut Option) -> Result<(), c_int> { 130 | use std::ffi::{CStr, OsString}; 131 | use std::os::unix::ffi::OsStringExt; 132 | if stat.is_none() { 133 | let path_c = unsafe { CStr::from_ptr(path) } .to_owned(); 134 | let path_os = OsString::from_vec(path_c.into_bytes()); 135 | *stat = Some(libc_wrappers::lstat(path_os)?); 136 | } 137 | Ok(()) 138 | } 139 | 140 | if (*times).tv_nsec == UTIME_OMIT { 141 | // atime is unspecified 142 | 143 | if let Err(e) = stat_if_needed(path, &mut stat) { 144 | return e; 145 | } 146 | 147 | times_osx[0].tv_sec = stat.unwrap().st_atime; 148 | times_osx[0].tv_usec = stat.unwrap().st_atime_nsec as suseconds_t * 1000; 149 | } 150 | 151 | if (*times.offset(1)).tv_nsec == UTIME_OMIT { 152 | // mtime is unspecified 153 | 154 | if stat.is_none() { 155 | if let Err(e) = stat_if_needed(path, &mut stat) { 156 | return e; 157 | } 158 | } 159 | times_osx[1].tv_sec = stat.unwrap().st_mtime; 160 | times_osx[1].tv_usec = stat.unwrap().st_mtime_nsec as suseconds_t * 1000; 161 | } 162 | 163 | lutimes(path, ×_osx as *const timeval) 164 | } 165 | } 166 | 167 | // the value is ignored; this is for OS X compat 168 | #[cfg(target_os = "macos")] 169 | pub const AT_FDCWD: c_int = -100; 170 | 171 | // the value is ignored; this is for OS X compat 172 | #[cfg(target_os = "macos")] 173 | pub const AT_SYMLINK_NOFOLLOW: c_int = 0x400; 174 | 175 | #[cfg(target_os = "macos")] 176 | pub const XATTR_NOFOLLOW: c_int = 1; 177 | 178 | #[cfg(target_os = "macos")] 179 | pub unsafe fn llistxattr(path: *const c_char, namebuf: *mut c_char, size: size_t) -> ssize_t { 180 | listxattr(path, namebuf, size, XATTR_NOFOLLOW) 181 | } 182 | 183 | #[cfg(target_os = "macos")] 184 | pub unsafe fn lgetxattr(path: *const c_char, name: *const c_char, value: *mut c_void, size: size_t) -> ssize_t { 185 | getxattr(path, name, value, size, 0, XATTR_NOFOLLOW) 186 | } 187 | 188 | #[cfg(target_os = "macos")] 189 | pub unsafe fn lsetxattr(path: *const c_char, name: *const c_char, value: *const c_void, size: size_t, flags: c_int, position: u32) -> c_int { 190 | setxattr(path, name, value, size, flags | XATTR_NOFOLLOW, position) 191 | } 192 | 193 | #[cfg(target_os = "macos")] 194 | pub unsafe fn lremovexattr(path: *const c_char, name: *const c_char) -> c_int { 195 | removexattr(path, name, XATTR_NOFOLLOW) 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/libc_bridge/libc_wrappers.rs: -------------------------------------------------------------------------------- 1 | // Libc Wrappers :: Safe wrappers around system calls. 2 | // 3 | // Copyright (c) 2016-2019 by William R. Fraser 4 | // 5 | 6 | use std::ffi::{CString, OsString}; 7 | use std::io; 8 | use std::mem; 9 | use std::ptr; 10 | use std::os::unix::ffi::OsStringExt; 11 | use super::libc_extras::libc; 12 | 13 | macro_rules! into_cstring { 14 | ($path:expr, $syscall:expr) => { 15 | match CString::new($path.into_vec()) { 16 | Ok(s) => s, 17 | Err(e) => { 18 | error!(concat!($syscall, ": path {:?} contains interior NUL byte"), 19 | OsString::from_vec(e.into_vec())); 20 | return Err(libc::EINVAL); 21 | } 22 | } 23 | } 24 | } 25 | 26 | pub fn opendir(path: OsString) -> Result { 27 | let path_c = into_cstring!(path, "opendir"); 28 | 29 | let dir: *mut libc::DIR = unsafe { libc::opendir(path_c.as_ptr()) }; 30 | if dir.is_null() { 31 | return Err(io::Error::last_os_error().raw_os_error().unwrap()); 32 | } 33 | 34 | Ok(dir as u64) 35 | } 36 | 37 | pub fn readdir(fh: u64) -> Result, libc::c_int> { 38 | let dir = fh as usize as *mut libc::DIR; 39 | let mut entry: libc::dirent = unsafe { mem::zeroed() }; 40 | let mut result: *mut libc::dirent = ptr::null_mut(); 41 | 42 | let error: i32 = unsafe { libc::readdir_r(dir, &mut entry, &mut result) }; 43 | if error != 0 { 44 | return Err(error); 45 | } 46 | 47 | if result.is_null() { 48 | return Ok(None); 49 | } 50 | 51 | Ok(Some(entry)) 52 | } 53 | 54 | pub fn closedir(fh: u64) -> Result<(), libc::c_int> { 55 | let dir = fh as usize as *mut libc::DIR; 56 | if -1 == unsafe { libc::closedir(dir) } { 57 | Err(io::Error::last_os_error().raw_os_error().unwrap()) 58 | } else { 59 | Ok(()) 60 | } 61 | } 62 | 63 | pub fn open(path: OsString, flags: libc::c_int) -> Result { 64 | let path_c = into_cstring!(path, "open"); 65 | 66 | let fd: libc::c_int = unsafe { libc::open(path_c.as_ptr(), flags) }; 67 | if fd == -1 { 68 | return Err(io::Error::last_os_error().raw_os_error().unwrap()); 69 | } 70 | 71 | Ok(fd as u64) 72 | } 73 | 74 | pub fn close(fh: u64) -> Result<(), libc::c_int> { 75 | let fd = fh as libc::c_int; 76 | if -1 == unsafe { libc::close(fd) } { 77 | Err(io::Error::last_os_error().raw_os_error().unwrap()) 78 | } else { 79 | Ok(()) 80 | } 81 | } 82 | 83 | pub fn lstat(path: OsString) -> Result { 84 | let path_c = into_cstring!(path, "lstat"); 85 | 86 | let mut buf: libc::stat64 = unsafe { mem::zeroed() }; 87 | if -1 == unsafe { libc::lstat64(path_c.as_ptr(), &mut buf) } { 88 | return Err(io::Error::last_os_error().raw_os_error().unwrap()); 89 | } 90 | 91 | Ok(buf) 92 | } 93 | 94 | pub fn fstat(fd: u64) -> Result { 95 | let mut buf: libc::stat64 = unsafe { mem::zeroed() }; 96 | if -1 == unsafe { libc::fstat64(fd as libc::c_int, &mut buf) } { 97 | return Err(io::Error::last_os_error().raw_os_error().unwrap()); 98 | } 99 | 100 | Ok(buf) 101 | } 102 | 103 | pub fn llistxattr(path: OsString, buf: &mut [u8]) -> Result { 104 | let path_c = into_cstring!(path, "llistxattr"); 105 | 106 | let result = unsafe { 107 | libc::llistxattr(path_c.as_ptr(), buf.as_mut_ptr() as *mut libc::c_char, buf.len()) 108 | }; 109 | match result { 110 | -1 => Err(io::Error::last_os_error().raw_os_error().unwrap()), 111 | nbytes => Ok(nbytes as usize), 112 | } 113 | } 114 | 115 | pub fn lgetxattr(path: OsString, name: OsString, buf: &mut [u8]) -> Result { 116 | let path_c = into_cstring!(path, "lgetxattr"); 117 | let name_c = into_cstring!(name, "lgetxattr"); 118 | 119 | let result = unsafe { 120 | libc::lgetxattr(path_c.as_ptr(), name_c.as_ptr(), buf.as_mut_ptr() as *mut libc::c_void, 121 | buf.len()) 122 | }; 123 | match result { 124 | -1 => Err(io::Error::last_os_error().raw_os_error().unwrap()), 125 | nbytes => Ok(nbytes as usize), 126 | } 127 | } 128 | 129 | pub fn lsetxattr(path: OsString, name: OsString, value: &[u8], flags: u32, position: u32) -> Result<(), libc::c_int> { 130 | let path_c = into_cstring!(path, "lsetxattr"); 131 | let name_c = into_cstring!(name, "lsetxattr"); 132 | 133 | // MacOS obnoxiously has an non-standard parameter at the end of their lsetxattr... 134 | #[cfg(target_os = "macos")] 135 | unsafe fn real(path: *const libc::c_char, name: *const libc::c_char, 136 | value: *const libc::c_void, size: libc::size_t, flags: libc::c_int, 137 | position: u32) -> libc::c_int { 138 | libc::lsetxattr(path, name, value, size, flags, position) 139 | } 140 | 141 | #[cfg(not(target_os = "macos"))] 142 | unsafe fn real(path: *const libc::c_char, name: *const libc::c_char, 143 | value: *const libc::c_void, size: libc::size_t, flags: libc::c_int, 144 | _position: u32) -> libc::c_int { 145 | libc::lsetxattr(path, name, value, size, flags) 146 | } 147 | 148 | if cfg!(not(target_os = "macos")) && position != 0 { 149 | error!("lsetxattr: position != 0 is only supported on MacOS"); 150 | return Err(libc::EINVAL); 151 | } 152 | 153 | let result = unsafe { 154 | real(path_c.as_ptr(), name_c.as_ptr(), value.as_ptr() as *const libc::c_void, 155 | value.len(), flags as libc::c_int, position) 156 | }; 157 | 158 | if result == -1 { 159 | Err(io::Error::last_os_error().raw_os_error().unwrap()) 160 | } else { 161 | Ok(()) 162 | } 163 | } 164 | 165 | pub fn lremovexattr(path: OsString, name: OsString) -> Result<(), libc::c_int> { 166 | let path_c = into_cstring!(path, "lremovexattr"); 167 | let name_c = into_cstring!(name, "lremovexattr"); 168 | 169 | if -1 == unsafe { libc::lremovexattr(path_c.as_ptr(), name_c.as_ptr()) } { 170 | Err(io::Error::last_os_error().raw_os_error().unwrap()) 171 | } else { 172 | Ok(()) 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/libc_bridge/mod.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | mod libc_extras; 3 | #[allow(dead_code)] 4 | pub mod libc_wrappers; 5 | 6 | use fuse_mt::{FileAttr, FileType}; 7 | use time::Timespec; 8 | 9 | pub use libc_extras::libc; 10 | 11 | pub fn mode_to_filetype(mode: libc::mode_t) -> FileType { 12 | match mode & libc::S_IFMT { 13 | libc::S_IFDIR => FileType::Directory, 14 | libc::S_IFREG => FileType::RegularFile, 15 | libc::S_IFLNK => FileType::Symlink, 16 | libc::S_IFBLK => FileType::BlockDevice, 17 | libc::S_IFCHR => FileType::CharDevice, 18 | libc::S_IFIFO => FileType::NamedPipe, 19 | libc::S_IFSOCK => FileType::Socket, 20 | _ => { 21 | panic!("unknown file type"); 22 | } 23 | } 24 | } 25 | 26 | pub fn stat_to_fuse(stat: libc::stat64) -> FileAttr { 27 | // st_mode encodes both the kind and the permissions 28 | let kind = mode_to_filetype(stat.st_mode); 29 | let perm = (stat.st_mode & 0o7777) as u16; 30 | 31 | FileAttr { 32 | size: stat.st_size as u64, 33 | blocks: stat.st_blocks as u64, 34 | atime: Timespec { 35 | sec: stat.st_atime as i64, 36 | nsec: stat.st_atime_nsec as i32, 37 | }, 38 | mtime: Timespec { 39 | sec: stat.st_mtime as i64, 40 | nsec: stat.st_mtime_nsec as i32, 41 | }, 42 | ctime: Timespec { 43 | sec: stat.st_ctime as i64, 44 | nsec: stat.st_ctime_nsec as i32, 45 | }, 46 | crtime: Timespec { sec: 0, nsec: 0 }, 47 | kind, 48 | perm, 49 | nlink: stat.st_nlink as u32, 50 | uid: stat.st_uid, 51 | gid: stat.st_gid, 52 | rdev: stat.st_rdev as u32, 53 | flags: 0, 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{OsStr, OsString}; 2 | use std::hash::{Hash, Hasher}; 3 | use std::path::Path; 4 | 5 | #[macro_use] 6 | extern crate clap; 7 | 8 | #[macro_use] 9 | extern crate log; 10 | 11 | use clap::App; 12 | use seahash::SeaHasher; 13 | 14 | mod config; 15 | mod fsop; 16 | mod libc_bridge; 17 | mod projfs; 18 | 19 | fn repr_of_path>(path: T) -> String { 20 | let path = path.as_ref(); 21 | let mut hasher = SeaHasher::new(); 22 | path.hash(&mut hasher); 23 | let hash: u64 = hasher.finish(); 24 | format!("{:x}", hash) 25 | } 26 | 27 | fn main() { 28 | env_logger::init(); 29 | 30 | let yaml = load_yaml!("cli.yml"); 31 | let matches = App::from_yaml(yaml).get_matches(); 32 | 33 | let mountpoint = matches.value_of_os("MOUNTPOINT").unwrap(); 34 | let source_dir = matches.value_of_os("SOURCE_DIR").unwrap(); 35 | let cache_dir = if let Some(cache_dir) = matches.value_of_os("cache") { 36 | OsString::from(cache_dir) 37 | } else { 38 | if let Some(mut cache_dir) = dirs::cache_dir() { 39 | cache_dir.push("projfs"); 40 | cache_dir.push(if let Ok(abs_path) = std::fs::canonicalize(&source_dir) { 41 | repr_of_path(&abs_path) 42 | } else { 43 | repr_of_path(&source_dir) 44 | }); 45 | cache_dir.into_os_string() 46 | } else { 47 | println!("Couldn't get cache directory automatically. Please explicitly specify a cache directory."); 48 | std::process::exit(-1); 49 | } 50 | }; 51 | let proj_conf = if let Some(conf_file) = matches.value_of_os("projection") { 52 | debug!("loading projection config from file {:?}", conf_file); 53 | if let Some(conf) = config::load(conf_file) { 54 | conf 55 | } else { 56 | std::process::exit(-1); 57 | } 58 | } else { 59 | debug!("use default projection"); 60 | config::default() 61 | }; 62 | 63 | info!("projection loaded @ {:p}", &proj_conf); 64 | 65 | info!( 66 | "source_dir: {:?} :: cache_dir: {:?}", 67 | &source_dir, &cache_dir 68 | ); 69 | 70 | let filesystem = projfs::ProjectionFS::new( 71 | OsString::from(source_dir), 72 | OsString::from(cache_dir), 73 | proj_conf, 74 | ); 75 | 76 | let fuse_args: Vec<&OsStr> = vec![&OsStr::new("-o"), &OsStr::new("ro,auto_unmount")]; 77 | 78 | fuse_mt::mount(fuse_mt::FuseMT::new(filesystem, 1), &mountpoint, &fuse_args).unwrap(); 79 | } 80 | -------------------------------------------------------------------------------- /src/projfs.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{CStr, OsStr, OsString}; 2 | use std::fs::{self}; 3 | use std::io::{self, Read, Seek, SeekFrom}; 4 | use std::os::unix::ffi::OsStrExt; 5 | use std::path::{Path, PathBuf}; 6 | use std::sync::Mutex; 7 | 8 | use bimap::BiMap; 9 | use fuse_mt::*; 10 | use mime_guess; 11 | use time::Timespec; 12 | 13 | use crate::config::ProjectionSpecification; 14 | use crate::fsop::{self, UnmanagedFile}; 15 | use crate::libc_bridge as br; 16 | use crate::libc_bridge::libc; 17 | use crate::libc_bridge::libc_wrappers; 18 | 19 | const TTL: Timespec = Timespec { sec: 1, nsec: 0 }; 20 | 21 | trait ProjectionResolver { 22 | fn source(&self, partial: &Path) -> OsString; 23 | fn cache(&self, partial: &Path) -> OsString; 24 | } 25 | 26 | pub struct ProjectionFS { 27 | pub source_dir: OsString, 28 | pub cache_dir: OsString, 29 | pm: ProjectionManager, 30 | } 31 | 32 | impl ProjectionFS { 33 | pub fn new( 34 | source_dir: OsString, 35 | cache_dir: OsString, 36 | conf: Box, 37 | ) -> ProjectionFS { 38 | ProjectionFS { 39 | source_dir: source_dir, 40 | cache_dir: cache_dir, 41 | pm: ProjectionManager::new(conf), 42 | } 43 | } 44 | 45 | fn resolve>(&self, partial: T) -> (AccessType, OsString) { 46 | let partial = partial.as_ref(); 47 | match self.pm.source(&partial.as_os_str().to_os_string()) { 48 | Some(_source) => { 49 | debug!("{:?} is a projected file", partial); 50 | (AccessType::Projected, self.cache_path(partial)) 51 | } 52 | None => { 53 | debug!("{:?} is a non-projected file", partial); 54 | (AccessType::PassThrough, self.source_path(partial)) 55 | } 56 | } 57 | } 58 | 59 | fn sniff_projection(&self, dir_path: &Path, filename: &OsStr) -> OsString { 60 | let partial = &PathBuf::from(dir_path).join(filename); 61 | match self.pm.access_type(self.source_path(partial)) { 62 | AccessType::PassThrough => self.source_path(partial), 63 | AccessType::Projected => { 64 | let partial_os_string = partial.as_os_str().to_os_string(); 65 | match self.pm.destination(&partial_os_string) { 66 | Some(dest) => { 67 | debug!("readdir file already projected {:?}", partial); 68 | self.cache_path(dest) 69 | } 70 | None => { 71 | let dest_partial = self.pm.project(partial, self); 72 | self.cache_path(dest_partial) 73 | } 74 | } 75 | } 76 | } 77 | } 78 | 79 | fn source_path>(&self, partial: T) -> OsString { 80 | self.source(partial.as_ref()) 81 | } 82 | 83 | fn cache_path>(&self, partial: T) -> OsString { 84 | self.cache(partial.as_ref()) 85 | } 86 | } 87 | 88 | impl ProjectionResolver for ProjectionFS { 89 | fn source(&self, partial: &Path) -> OsString { 90 | fsop::real_path(&self.source_dir, partial) 91 | } 92 | 93 | fn cache(&self, partial: &Path) -> OsString { 94 | fsop::real_path(&self.cache_dir, partial) 95 | } 96 | } 97 | 98 | impl FilesystemMT for ProjectionFS { 99 | fn init(&self, _req: RequestInfo) -> ResultEmpty { 100 | debug!("init"); 101 | Ok(()) 102 | } 103 | 104 | fn destroy(&self, _req: RequestInfo) { 105 | debug!("destroy"); 106 | } 107 | 108 | fn getattr(&self, _req: RequestInfo, path: &Path, fh: Option) -> ResultEntry { 109 | debug!( 110 | "getattr: {:?} ({} filehandle)", 111 | path, 112 | if let Some(_) = fh { "with" } else { "without" } 113 | ); 114 | 115 | if let Some(fh) = fh { 116 | // Only used in setattr. Never used for read-only filesystem 117 | match libc_wrappers::fstat(fh) { 118 | Ok(stat) => Ok((TTL, br::stat_to_fuse(stat))), 119 | Err(e) => Err(e), 120 | } 121 | } else { 122 | let (access_type, real) = self.resolve(path); 123 | 124 | match fsop::getattr(real) { 125 | Ok(stat) => { 126 | match access_type { 127 | AccessType::PassThrough => Ok((TTL, stat)), 128 | AccessType::Projected => { 129 | match fsop::getattr(self.source_path( 130 | self.pm.source(&path.as_os_str().to_os_string()).unwrap(), 131 | )) { 132 | Ok(mut stat_real) => { 133 | stat_real.size = stat.size; 134 | stat_real.blocks = stat.blocks; 135 | Ok((TTL, stat_real)) 136 | } 137 | Err(e) => { 138 | let err = io::Error::from_raw_os_error(e); 139 | error!("lstat({:?}): {}", path, err); 140 | Err(err.raw_os_error().unwrap()) 141 | } 142 | } 143 | } 144 | } 145 | } 146 | Err(e) => { 147 | let err = io::Error::from_raw_os_error(e); 148 | error!("lstat({:?}): {}", path, err); 149 | Err(err.raw_os_error().unwrap()) 150 | } 151 | } 152 | } 153 | } 154 | 155 | //checked 156 | fn opendir(&self, _req: RequestInfo, path: &Path, _flags: u32) -> ResultOpen { 157 | let real = self.source_path(path); 158 | debug!("opendir: {:?} (flags = {:#o})", real, _flags); 159 | match libc_wrappers::opendir(real) { 160 | Ok(fh) => Ok((fh, 0)), 161 | Err(e) => { 162 | let ioerr = io::Error::from_raw_os_error(e); 163 | error!("opendir({:?}): {}", path, ioerr); 164 | Err(e) 165 | } 166 | } 167 | } 168 | 169 | //checked 170 | fn releasedir(&self, _req: RequestInfo, path: &Path, fh: u64, _flags: u32) -> ResultEmpty { 171 | debug!("releasedir: {:?}", path); 172 | libc_wrappers::closedir(fh) 173 | } 174 | 175 | //checked 176 | fn readdir(&self, _req: RequestInfo, path: &Path, fh: u64) -> ResultReaddir { 177 | debug!("readdir: {:?}", path); 178 | let mut entries: Vec = vec![]; 179 | 180 | if fh == 0 { 181 | error!("readdir: missing fh"); 182 | return Err(libc::EINVAL); 183 | } 184 | 185 | loop { 186 | match libc_wrappers::readdir(fh) { 187 | Ok(Some(entry)) => { 188 | let name_c = unsafe { CStr::from_ptr(entry.d_name.as_ptr()) }; 189 | let name = OsStr::from_bytes(name_c.to_bytes()).to_owned(); 190 | 191 | let filetype = match entry.d_type { 192 | libc::DT_DIR => FileType::Directory, 193 | libc::DT_REG => FileType::RegularFile, 194 | libc::DT_LNK => FileType::Symlink, 195 | libc::DT_BLK => FileType::BlockDevice, 196 | libc::DT_CHR => FileType::CharDevice, 197 | libc::DT_FIFO => FileType::NamedPipe, 198 | libc::DT_SOCK => { 199 | warn!("FUSE doesn't support Socket file type; translating to NamedPipe instead."); 200 | FileType::NamedPipe 201 | } 202 | 0 | _ => { 203 | let entry_path = PathBuf::from(path).join(&name); 204 | let source_path = self.source_path(&entry_path); 205 | match libc_wrappers::lstat(source_path) { 206 | Ok(stat64) => br::mode_to_filetype(stat64.st_mode), 207 | Err(errno) => { 208 | let ioerr = io::Error::from_raw_os_error(errno); 209 | panic!("lstat failed after readdir_r gave no file type for {:?}: {}", 210 | entry_path, ioerr); 211 | } 212 | } 213 | } 214 | }; 215 | 216 | info!("readdir() :: filename: {:?}", &name); 217 | if filetype == FileType::RegularFile { 218 | let result_path = self.sniff_projection(path, &name); 219 | let name = Path::new(&result_path).file_name().unwrap().to_owned(); 220 | entries.push(DirectoryEntry { 221 | name, 222 | kind: filetype, 223 | }) 224 | } else { 225 | entries.push(DirectoryEntry { 226 | name, 227 | kind: filetype, 228 | }) 229 | } 230 | } 231 | Ok(None) => { 232 | break; 233 | } 234 | Err(e) => { 235 | error!("readdir: {:?}: {}", path, e); 236 | return Err(e); 237 | } 238 | } 239 | } 240 | 241 | Ok(entries) 242 | } 243 | 244 | fn open(&self, _req: RequestInfo, path: &Path, flags: u32) -> ResultOpen { 245 | debug!("open: {:?} flags={:#x}", path, flags); 246 | 247 | let (_, real) = self.resolve(path); 248 | match libc_wrappers::open(real, flags as libc::c_int) { 249 | Ok(fh) => Ok((fh, flags)), 250 | Err(e) => { 251 | error!("open({:?}): {}", path, io::Error::from_raw_os_error(e)); 252 | Err(e) 253 | } 254 | } 255 | } 256 | 257 | fn release( 258 | &self, 259 | _req: RequestInfo, 260 | path: &Path, 261 | fh: u64, 262 | _flags: u32, 263 | _lock_owner: u64, 264 | _flush: bool, 265 | ) -> ResultEmpty { 266 | debug!("release: {:?}", path); 267 | libc_wrappers::close(fh) 268 | } 269 | 270 | fn read( 271 | &self, 272 | _req: RequestInfo, 273 | path: &Path, 274 | fh: u64, 275 | offset: u64, 276 | size: u32, 277 | result: impl FnOnce(Result<&[u8], libc::c_int>), 278 | ) { 279 | debug!("read: {:?} {:#x} @ {:#x}", path, size, offset); 280 | let mut file = unsafe { UnmanagedFile::new(fh) }; 281 | 282 | let mut data = Vec::::with_capacity(size as usize); 283 | unsafe { data.set_len(size as usize) }; 284 | 285 | if let Err(e) = file.seek(SeekFrom::Start(offset)) { 286 | error!("seek({:?}, {}): {}", path, offset, e); 287 | result(Err(e.raw_os_error().unwrap())); 288 | return; 289 | } 290 | match file.read(&mut data) { 291 | Ok(n) => { 292 | data.truncate(n); 293 | } 294 | Err(e) => { 295 | error!("read {:?}, {:#x} @ {:#x}: {}", path, size, offset, e); 296 | result(Err(e.raw_os_error().unwrap())); 297 | return; 298 | } 299 | } 300 | 301 | result(Ok(&data)); 302 | } 303 | } 304 | 305 | struct ProjectionManager { 306 | projection: Mutex>, 307 | spec: Box, 308 | } 309 | 310 | impl ProjectionManager { 311 | fn new(spec: Box) -> ProjectionManager { 312 | ProjectionManager { 313 | projection: Mutex::new(BiMap::new()), 314 | spec: spec, 315 | } 316 | } 317 | 318 | fn destination(&self, filepath: &OsString) -> Option { 319 | match self.projection.lock().unwrap().get_by_left(filepath) { 320 | Some(dest) => Some(dest.clone()), 321 | None => None, 322 | } 323 | } 324 | 325 | fn source(&self, filepath: &OsString) -> Option { 326 | match self.projection.lock().unwrap().get_by_right(filepath) { 327 | Some(source) => Some(source.clone()), 328 | None => None, 329 | } 330 | } 331 | 332 | fn insert(&self, input: OsString, output: OsString) { 333 | self.projection.lock().unwrap().insert(input, output); 334 | } 335 | 336 | fn access_type>(&self, file_path: T) -> AccessType { 337 | let file_path = file_path.as_ref(); 338 | if file_path.is_dir() { 339 | error!( 340 | "atype() shouldn't be called on a directory ({:?})", 341 | file_path 342 | ); 343 | } 344 | let guess = mime_guess::from_path(file_path); 345 | if guess.is_empty() { 346 | warn!("MIME for filepath {} can't guess", file_path.display()); 347 | AccessType::PassThrough 348 | } else { 349 | let first_guess = guess.first().unwrap(); 350 | info!("MIME for {:?} is {}", file_path, first_guess); 351 | if self.spec.should_project(&first_guess) { 352 | AccessType::Projected 353 | } else { 354 | AccessType::PassThrough 355 | } 356 | } 357 | } 358 | 359 | /// parameter `partial` is the relative partial path, pointing to the *file* to be projected 360 | fn project>(&self, partial: T, resolver: &dyn ProjectionResolver) -> OsString { 361 | let source_partial = partial.as_ref(); 362 | let dest_partial = 363 | &Path::new(&self.spec.convert_filename(source_partial.as_ref())).to_owned(); 364 | let dest = &resolver.cache(dest_partial); 365 | self.insert(OsString::from(source_partial), OsString::from(dest_partial)); 366 | let source = &resolver.source(source_partial); 367 | let dest_path = Path::new(dest); 368 | let exists = dest_path.exists(); 369 | let needs_project = { 370 | if !exists { 371 | fs::create_dir_all(Path::new(dest).parent().unwrap()) 372 | .expect(&format!("cache directory {:?} can't be created", dest)); 373 | true 374 | } else { 375 | fsop::is_content_newer(dest.clone(), source.clone()).unwrap() 376 | } 377 | }; 378 | if needs_project { 379 | if exists { 380 | if let Err(e) = fs::remove_file(dest_path) { 381 | error!("{}", e); 382 | } 383 | } 384 | self.spec.project(source, dest); 385 | } 386 | dest_partial.as_os_str().to_os_string() 387 | } 388 | } 389 | 390 | #[derive(Debug)] 391 | enum AccessType { 392 | Projected, 393 | PassThrough, 394 | } 395 | --------------------------------------------------------------------------------