summary refs log tree commit diff
diff options
context:
space:
mode:
authoredef <edef@unfathomable.blue>2021-08-14 21:28:14 +0000
committeredef <edef@unfathomable.blue>2021-08-14 21:28:14 +0000
commitdb7c54f92f386a94db8af7a12626d2657b4dd640 (patch)
tree4baba57bac54c68823a834c0f8aa97b24cfec7a2
parentdcae0f9c8a94f05bf55cf9b6fbc773502ab5784f (diff)
downloadunf-legacy-db7c54f92f386a94db8af7a12626d2657b4dd640.tar.zst
ripple/fossil: a basic content-addressable store
Fossil stores content-addressed blobs of file contents and
Protobuf-encoded directory listings, backed by Sled.

Change-Id: I8b49de6342218ca00755cec980b1d0cfb18878a7
-rw-r--r--ripple/Cargo.lock640
-rw-r--r--ripple/Cargo.lock.license1
-rw-r--r--ripple/Cargo.toml3
-rw-r--r--ripple/fossil/.gitignore4
-rw-r--r--ripple/fossil/Cargo.toml17
-rw-r--r--ripple/fossil/build.rs9
-rw-r--r--ripple/fossil/src/bin/add.rs31
-rw-r--r--ripple/fossil/src/bin/extract.rs57
-rw-r--r--ripple/fossil/src/lib.rs206
-rw-r--r--ripple/fossil/src/store.proto28
-rw-r--r--ripple/shell.nix7
11 files changed, 1003 insertions, 0 deletions
diff --git a/ripple/Cargo.lock b/ripple/Cargo.lock
index d280e4a..fdab2f2 100644
--- a/ripple/Cargo.lock
+++ b/ripple/Cargo.lock
@@ -3,5 +3,645 @@
 version = 3
 
 [[package]]
+name = "anyhow"
+version = "1.0.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf"
+
+[[package]]
+name = "arrayref"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
+
+[[package]]
+name = "arrayvec"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "bitflags"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2da1976d75adbe5fbc88130ecd119529cf1cc6a93ae1546d8696ee66f0d21af1"
+
+[[package]]
+name = "blake3"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b64485778c4f16a6a5a9d335e80d449ac6c70cdd6a06d2af18a6f6f775a125b3"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "cc",
+ "cfg-if 0.1.10",
+ "constant_time_eq",
+ "crypto-mac",
+ "digest",
+ "rayon",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "bytes"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
+
+[[package]]
+name = "cc"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "constant_time_eq"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
+
+[[package]]
+name = "crc32fast"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crossbeam-utils",
+ "lazy_static",
+ "memoffset",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
+dependencies = [
+ "cfg-if 1.0.0",
+ "lazy_static",
+]
+
+[[package]]
+name = "crypto-mac"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
+dependencies = [
+ "generic-array",
+ "subtle",
+]
+
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "either"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+
+[[package]]
+name = "fixedbitset"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
+
+[[package]]
+name = "fossil"
+version = "0.1.0"
+dependencies = [
+ "blake3",
+ "byteorder",
+ "bytes",
+ "prost",
+ "prost-build",
+ "sled",
+]
+
+[[package]]
+name = "fs2"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "fxhash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+
+[[package]]
+name = "heck"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.99"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765"
+
+[[package]]
+name = "lock_api"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "multimap"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
+
+[[package]]
+name = "num_cpus"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
+dependencies = [
+ "cfg-if 1.0.0",
+ "instant",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "winapi",
+]
+
+[[package]]
+name = "petgraph"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7"
+dependencies = [
+ "fixedbitset",
+ "indexmap",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "prost"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de5e2533f59d08fcf364fd374ebda0692a70bd6d7e66ef97f306f45c6c5d8020"
+dependencies = [
+ "bytes",
+ "prost-derive",
+]
+
+[[package]]
+name = "prost-build"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "355f634b43cdd80724ee7848f95770e7e70eefa6dcf14fea676216573b8fd603"
+dependencies = [
+ "bytes",
+ "heck",
+ "itertools",
+ "log",
+ "multimap",
+ "petgraph",
+ "prost",
+ "prost-types",
+ "tempfile",
+ "which",
+]
+
+[[package]]
+name = "prost-derive"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "600d2f334aa05acb02a755e217ef1ab6dea4d51b58b7846588b747edec04efba"
+dependencies = [
+ "anyhow",
+ "itertools",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "prost-types"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "603bbd6394701d13f3f25aada59c7de9d35a6a5887cfc156181234a44002771b"
+dependencies = [
+ "bytes",
+ "prost",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "rayon"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90"
+dependencies = [
+ "autocfg",
+ "crossbeam-deque",
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-deque",
+ "crossbeam-utils",
+ "lazy_static",
+ "num_cpus",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
 name = "ripple"
 version = "0.1.0"
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "sled"
+version = "0.34.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d0132f3e393bcb7390c60bb45769498cf4550bcb7a21d7f95c02b69f6362cdc"
+dependencies = [
+ "crc32fast",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+ "fs2",
+ "fxhash",
+ "libc",
+ "log",
+ "parking_lot",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
+
+[[package]]
+name = "subtle"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
+
+[[package]]
+name = "syn"
+version = "1.0.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "rand",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi",
+]
+
+[[package]]
+name = "typenum"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "version_check"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
+
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+
+[[package]]
+name = "which"
+version = "4.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9"
+dependencies = [
+ "either",
+ "lazy_static",
+ "libc",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/ripple/Cargo.lock.license b/ripple/Cargo.lock.license
index dddf7fd..26667bf 100644
--- a/ripple/Cargo.lock.license
+++ b/ripple/Cargo.lock.license
@@ -1,2 +1,3 @@
 SPDX-FileCopyrightText: V <v@unfathomable.blue>
+SPDX-FileCopyrightText: edef <edef@unfathomable.blue>
 SPDX-License-Identifier: CC0-1.0
diff --git a/ripple/Cargo.toml b/ripple/Cargo.toml
index 654f89b..5255ca8 100644
--- a/ripple/Cargo.toml
+++ b/ripple/Cargo.toml
@@ -7,3 +7,6 @@ version = "0.1.0"
 edition = "2018"
 
 [dependencies]
+
+[workspace]
+members = ["fossil"]
diff --git a/ripple/fossil/.gitignore b/ripple/fossil/.gitignore
new file mode 100644
index 0000000..be75022
--- /dev/null
+++ b/ripple/fossil/.gitignore
@@ -0,0 +1,4 @@
+# SPDX-FileCopyrightText: edef <edef@unfathomable.blue>
+# SPDX-License-Identifier: OSL-3.0
+
+/target
diff --git a/ripple/fossil/Cargo.toml b/ripple/fossil/Cargo.toml
new file mode 100644
index 0000000..a88a5f8
--- /dev/null
+++ b/ripple/fossil/Cargo.toml
@@ -0,0 +1,17 @@
+# SPDX-FileCopyrightText: edef <edef@unfathomable.blue>
+# SPDX-License-Identifier: OSL-3.0
+
+[package]
+name = "fossil"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
+prost = "0.8.0"
+bytes = "1.0.1"
+blake3 = { version = "0.3.8", features = ["rayon"] }
+sled = "0.34.6"
+byteorder = "1.4.3"
+
+[build-dependencies]
+prost-build = "0.8.0"
diff --git a/ripple/fossil/build.rs b/ripple/fossil/build.rs
new file mode 100644
index 0000000..412c2d2
--- /dev/null
+++ b/ripple/fossil/build.rs
@@ -0,0 +1,9 @@
+// SPDX-FileCopyrightText: edef <edef@unfathomable.blue>
+// SPDX-License-Identifier: OSL-3.0
+
+use std::io::Result;
+
+fn main() -> Result<()> {
+	prost_build::compile_protos(&["src/store.proto"], &["src/"])?;
+	Ok(())
+}
diff --git a/ripple/fossil/src/bin/add.rs b/ripple/fossil/src/bin/add.rs
new file mode 100644
index 0000000..114f893
--- /dev/null
+++ b/ripple/fossil/src/bin/add.rs
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: edef <edef@unfathomable.blue>
+// SPDX-License-Identifier: OSL-3.0
+
+use {
+	fossil::Directory,
+	prost::Message,
+	std::{
+		env,
+		io::{self, Write},
+		path::Path,
+	},
+};
+
+fn main() {
+	let store = fossil::Store::open("fossil.db").unwrap();
+	let mut root = Directory::new();
+
+	for name in env::args().skip(1) {
+		let path = Path::new(&name);
+		let name = path
+			.file_name()
+			.and_then(|s| s.to_str())
+			.expect("invalid path")
+			.to_owned();
+
+		root.children.insert(name, store.add_path(path));
+	}
+
+	let mut stdout = io::stdout();
+	stdout.write_all(&root.into_pb().encode_to_vec()).unwrap();
+}
diff --git a/ripple/fossil/src/bin/extract.rs b/ripple/fossil/src/bin/extract.rs
new file mode 100644
index 0000000..f83ce0e
--- /dev/null
+++ b/ripple/fossil/src/bin/extract.rs
@@ -0,0 +1,57 @@
+// SPDX-FileCopyrightText: edef <edef@unfathomable.blue>
+// SPDX-License-Identifier: OSL-3.0
+
+use {
+	fossil::{store, Directory},
+	prost::Message,
+	std::{
+		fs,
+		io::{self, Read, Write},
+		os::unix::{fs::symlink, prelude::OpenOptionsExt},
+		path::Path,
+	},
+};
+
+fn main() {
+	let store = fossil::Store::open("fossil.db").unwrap();
+	let root = {
+		let mut stdin = io::stdin();
+
+		let mut bytes = Vec::new();
+		stdin.read_to_end(&mut bytes).unwrap();
+
+		let pb = store::Directory::decode(&*bytes).unwrap();
+		Directory::from_pb(pb)
+	};
+
+	let root_path = Path::new(".");
+	extract(&store, root_path, &root);
+}
+
+fn extract(store: &fossil::Store, path: &Path, dir: &Directory) {
+	for (name, node) in &dir.children {
+		let path = path.join(name);
+		match node.clone() {
+			fossil::Node::Directory { r#ref } => {
+				let blob = store.read_blob(r#ref);
+				let pb = store::Directory::decode(&*blob).unwrap();
+				fs::create_dir(&path).unwrap();
+				extract(store, &path, &Directory::from_pb(pb));
+			}
+			fossil::Node::File { r#ref, executable } => {
+				let mode = if executable { 0o755 } else { 0o644 };
+				let mut f = fs::OpenOptions::new()
+					.write(true)
+					.create_new(true)
+					.mode(mode)
+					.open(path)
+					.unwrap();
+				let blob = store.read_blob(r#ref);
+				f.write_all(&blob).unwrap();
+			}
+			fossil::Node::Link { target } => {
+				symlink(target, path).unwrap();
+			}
+		}
+	}
+}
diff --git a/ripple/fossil/src/lib.rs b/ripple/fossil/src/lib.rs
new file mode 100644
index 0000000..6fb5269
--- /dev/null
+++ b/ripple/fossil/src/lib.rs
@@ -0,0 +1,206 @@
+// SPDX-FileCopyrightText: edef <edef@unfathomable.blue>
+// SPDX-License-Identifier: OSL-3.0
+
+use {
+	byteorder::{BigEndian, ByteOrder},
+	prost::Message,
+	std::{collections::BTreeMap, fs, io, os::unix::fs::PermissionsExt, path::Path},
+};
+
+pub mod store {
+	include!(concat!(env!("OUT_DIR"), "/fossil.store.rs"));
+}
+
+const DIGEST_BYTES: usize = blake3::OUT_LEN;
+const OFFSET_BYTES: usize = 4;
+
+pub struct Store {
+	db: sled::Db,
+}
+
+impl Store {
+	pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Store> {
+		let db = sled::open(path)?;
+		Ok(Store { db })
+	}
+
+	pub fn add_path<P: AsRef<Path>>(&self, path: P) -> Node {
+		let path = path.as_ref();
+		let meta = fs::symlink_metadata(path).unwrap();
+
+		match meta.file_type() {
+			ty if ty.is_dir() => {
+				let mut d = Directory::new();
+
+				for entry in path.read_dir().unwrap() {
+					let entry = entry.unwrap();
+					let name = entry.file_name().into_string().unwrap();
+					d.children.insert(name, self.add_path(entry.path()));
+				}
+
+				let blob = d.into_pb().encode_to_vec();
+
+				Node::Directory {
+					r#ref: self.write_blob(&blob),
+				}
+			}
+			ty if ty.is_file() => {
+				let executable = (meta.permissions().mode() & 0o100) != 0;
+
+				let blob = fs::read(path).unwrap();
+				Node::File {
+					executable,
+					r#ref: self.write_blob(&blob),
+				}
+			}
+			ty if ty.is_symlink() => {
+				let target = path
+					.read_link()
+					.unwrap()
+					.to_str()
+					.expect("symlink target is invalid UTF-8")
+					.to_owned();
+
+				Node::Link { target }
+			}
+			_ => panic!("not a symlink or a regular file"),
+		}
+	}
+
+	fn write_blob(&self, data: &[u8]) -> Digest {
+		let digest = {
+			let mut h = blake3::Hasher::new();
+			h.update_with_join::<blake3::join::RayonJoin>(&data);
+			*h.finalize().as_bytes()
+		};
+
+		// TODO(edef): maybe don't use the default tree?
+		// we should probably have a "blob" tree,
+		// and reserve the default tree for DB metadata
+
+		self.db
+			.transaction::<_, _, sled::Error>(|db| {
+				for (n, chunk) in data.chunks(4096).enumerate() {
+					let mut key = [0u8; DIGEST_BYTES + OFFSET_BYTES];
+					key[..DIGEST_BYTES].copy_from_slice(&digest);
+					BigEndian::write_u32(&mut key[DIGEST_BYTES..], n as u32);
+					db.insert(&key[..], chunk)?;
+				}
+				Ok(())
+			})
+			.unwrap();
+
+		digest.into()
+	}
+
+	pub fn read_blob(&self, r#ref: Digest) -> Vec<u8> {
+		let mut buffer = Vec::new();
+		let mut h = blake3::Hasher::new();
+		for element in self.db.scan_prefix(r#ref.as_bytes()) {
+			let (_, chunk) = element.unwrap();
+			h.update(&chunk);
+			buffer.extend_from_slice(&chunk);
+		}
+
+		if buffer.len() == 0 {
+			panic!("blob not found");
+		}
+
+		if h.finalize() != r#ref {
+			panic!("hash mismatch");
+		}
+
+		buffer
+	}
+}
+
+pub type Digest = blake3::Hash;
+
+pub struct Directory {
+	pub children: BTreeMap<String, Node>,
+}
+
+#[derive(Clone)]
+pub enum Node {
+	Directory { r#ref: Digest },
+	File { r#ref: Digest, executable: bool },
+	Link { target: String },
+}
+
+impl Directory {
+	pub fn new() -> Directory {
+		Directory {
+			children: BTreeMap::new(),
+		}
+	}
+
+	pub fn into_pb(self) -> store::Directory {
+		let mut d = store::Directory::default();
+
+		for (name, node) in self.children.into_iter() {
+			match node {
+				Node::Directory { r#ref } => d.directories.push(store::DirectoryNode {
+					name,
+					r#ref: r#ref.as_bytes().to_vec(),
+				}),
+				Node::File { r#ref, executable } => d.files.push(store::FileNode {
+					name,
+					r#ref: r#ref.as_bytes().to_vec(),
+					executable,
+				}),
+				Node::Link { target } => d.links.push(store::LinkNode { name, target }),
+			}
+		}
+
+		d
+	}
+
+	pub fn from_pb(pb: store::Directory) -> Directory {
+		let mut children = BTreeMap::new();
+
+		for child in pb.directories {
+			children.insert(
+				child.name,
+				Node::Directory {
+					r#ref: digest_from_bytes(&child.r#ref),
+				},
+			);
+		}
+
+		for child in pb.files {
+			children.insert(
+				child.name,
+				Node::File {
+					r#ref: digest_from_bytes(&child.r#ref),
+					executable: child.executable,
+				},
+			);
+		}
+
+		for child in pb.links {
+			children.insert(
+				child.name,
+				Node::Link {
+					target: child.target,
+				},
+			);
+		}
+
+		Directory { children }
+	}
+}
+
+#[track_caller]
+fn digest_from_bytes(bytes: &[u8]) -> Digest {
+	if bytes.len() != DIGEST_BYTES {
+		panic!(
+			"digest is {} bytes, expecting {} bytes",
+			bytes.len(),
+			DIGEST_BYTES
+		);
+	}
+
+	let mut buffer = [0; DIGEST_BYTES];
+	buffer.copy_from_slice(bytes);
+	buffer.into()
+}
diff --git a/ripple/fossil/src/store.proto b/ripple/fossil/src/store.proto
new file mode 100644
index 0000000..58832f0
--- /dev/null
+++ b/ripple/fossil/src/store.proto
@@ -0,0 +1,28 @@
+// SPDX-FileCopyrightText: edef <edef@unfathomable.blue>
+// SPDX-License-Identifier: OSL-3.0
+
+syntax = "proto3";
+
+package fossil.store;
+
+message Directory {
+    repeated DirectoryNode directories = 1;
+    repeated FileNode files = 2;
+    repeated LinkNode links = 3;
+}
+
+message DirectoryNode {
+    string name = 1;
+    bytes ref = 2;
+}
+
+message FileNode {
+    string name = 1;
+    bytes ref = 2;
+    bool executable = 3;
+}
+
+message LinkNode {
+    string name = 1;
+    string target = 2;
+}
diff --git a/ripple/shell.nix b/ripple/shell.nix
index 9282613..03804c7 100644
--- a/ripple/shell.nix
+++ b/ripple/shell.nix
@@ -1,4 +1,5 @@
 # SPDX-FileCopyrightText: V <v@unfathomable.blue>
+# SPDX-FileCopyrightText: edef <edef@unfathomable.blue>
 # SPDX-License-Identifier: OSL-3.0
 
 with import ./nix;
@@ -12,5 +13,11 @@ mkShell {
     # needed by rust-analyzer
     rustc  # core crate code
     rustfmt  # format-on-save
+
+    # needed by prost-build
+    protobuf
   ];
+
+  # needed by prost-build
+  PROTOC = "protoc";
 }