├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-GPL-3.0
├── LICENSE-MIT
├── README.md
├── clippy.toml
├── dwarfs-enc
    ├── Cargo.toml
    ├── LICENSE-GPL-3.0
    ├── README.md
    ├── examples
    │   └── mkdwarfs.rs
    ├── src
    │   ├── chunker.rs
    │   ├── error.rs
    │   ├── lib.rs
    │   ├── metadata.rs
    │   ├── ordered_parallel.rs
    │   └── section.rs
    └── tests
    │   └── basic.rs
├── dwarfs-test
    ├── Cargo.toml
    ├── LICENSE-APACHE
    ├── LICENSE-MIT
    ├── src
    │   ├── lib.rs
    │   ├── main.rs
    │   ├── mtree.rs
    │   └── traverse.rs
    └── tests
    │   ├── basic.rs
    │   └── large.rs
├── dwarfs
    ├── CHANGELOG.md
    ├── Cargo.toml
    ├── LICENSE-APACHE
    ├── LICENSE-MIT
    ├── README.md
    └── src
    │   ├── archive.rs
    │   ├── fsst.rs
    │   ├── lib.rs
    │   ├── metadata.rs
    │   ├── metadata
    │       ├── de_frozen.rs
    │       ├── de_thrift.rs
    │       ├── ser_frozen.rs
    │       ├── ser_thrift.rs
    │       └── tests.rs
    │   └── section.rs
├── flake.lock
├── flake.nix
└── typos.toml
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | result
3 | result-*
4 | *.dwarfs
5 | 
6 | perf*.data*
7 | flamegraph*.svg
8 | 
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
  1 | # This file is automatically @generated by Cargo.
  2 | # It is not intended for manual editing.
  3 | version = 4
  4 | 
  5 | [[package]]
  6 | name = "aho-corasick"
  7 | version = "1.1.3"
  8 | source = "registry+https://github.com/rust-lang/crates.io-index"
  9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
 10 | dependencies = [
 11 |  "memchr",
 12 | ]
 13 | 
 14 | [[package]]
 15 | name = "allocator-api2"
 16 | version = "0.2.21"
 17 | source = "registry+https://github.com/rust-lang/crates.io-index"
 18 | checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
 19 | 
 20 | [[package]]
 21 | name = "anstream"
 22 | version = "0.6.18"
 23 | source = "registry+https://github.com/rust-lang/crates.io-index"
 24 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
 25 | dependencies = [
 26 |  "anstyle",
 27 |  "anstyle-parse",
 28 |  "anstyle-query",
 29 |  "anstyle-wincon",
 30 |  "colorchoice",
 31 |  "is_terminal_polyfill",
 32 |  "utf8parse",
 33 | ]
 34 | 
 35 | [[package]]
 36 | name = "anstyle"
 37 | version = "1.0.10"
 38 | source = "registry+https://github.com/rust-lang/crates.io-index"
 39 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
 40 | 
 41 | [[package]]
 42 | name = "anstyle-parse"
 43 | version = "0.2.6"
 44 | source = "registry+https://github.com/rust-lang/crates.io-index"
 45 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
 46 | dependencies = [
 47 |  "utf8parse",
 48 | ]
 49 | 
 50 | [[package]]
 51 | name = "anstyle-query"
 52 | version = "1.1.2"
 53 | source = "registry+https://github.com/rust-lang/crates.io-index"
 54 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
 55 | dependencies = [
 56 |  "windows-sys",
 57 | ]
 58 | 
 59 | [[package]]
 60 | name = "anstyle-wincon"
 61 | version = "3.0.7"
 62 | source = "registry+https://github.com/rust-lang/crates.io-index"
 63 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
 64 | dependencies = [
 65 |  "anstyle",
 66 |  "once_cell",
 67 |  "windows-sys",
 68 | ]
 69 | 
 70 | [[package]]
 71 | name = "bitflags"
 72 | version = "2.9.0"
 73 | source = "registry+https://github.com/rust-lang/crates.io-index"
 74 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
 75 | 
 76 | [[package]]
 77 | name = "block-buffer"
 78 | version = "0.10.4"
 79 | source = "registry+https://github.com/rust-lang/crates.io-index"
 80 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
 81 | dependencies = [
 82 |  "generic-array",
 83 | ]
 84 | 
 85 | [[package]]
 86 | name = "bstr"
 87 | version = "1.12.0"
 88 | source = "registry+https://github.com/rust-lang/crates.io-index"
 89 | checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
 90 | dependencies = [
 91 |  "memchr",
 92 |  "regex-automata",
 93 |  "serde",
 94 | ]
 95 | 
 96 | [[package]]
 97 | name = "bumpalo"
 98 | version = "3.18.1"
 99 | source = "registry+https://github.com/rust-lang/crates.io-index"
100 | checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee"
101 | 
102 | [[package]]
103 | name = "cc"
104 | version = "1.2.22"
105 | source = "registry+https://github.com/rust-lang/crates.io-index"
106 | checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1"
107 | dependencies = [
108 |  "jobserver",
109 |  "libc",
110 |  "shlex",
111 | ]
112 | 
113 | [[package]]
114 | name = "cfg-if"
115 | version = "1.0.0"
116 | source = "registry+https://github.com/rust-lang/crates.io-index"
117 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
118 | 
119 | [[package]]
120 | name = "clap"
121 | version = "4.5.39"
122 | source = "registry+https://github.com/rust-lang/crates.io-index"
123 | checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f"
124 | dependencies = [
125 |  "clap_builder",
126 |  "clap_derive",
127 | ]
128 | 
129 | [[package]]
130 | name = "clap_builder"
131 | version = "4.5.39"
132 | source = "registry+https://github.com/rust-lang/crates.io-index"
133 | checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51"
134 | dependencies = [
135 |  "anstream",
136 |  "anstyle",
137 |  "clap_lex",
138 |  "strsim",
139 | ]
140 | 
141 | [[package]]
142 | name = "clap_derive"
143 | version = "4.5.32"
144 | source = "registry+https://github.com/rust-lang/crates.io-index"
145 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
146 | dependencies = [
147 |  "heck",
148 |  "proc-macro2",
149 |  "quote",
150 |  "syn",
151 | ]
152 | 
153 | [[package]]
154 | name = "clap_lex"
155 | version = "0.7.4"
156 | source = "registry+https://github.com/rust-lang/crates.io-index"
157 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
158 | 
159 | [[package]]
160 | name = "colorchoice"
161 | version = "1.0.3"
162 | source = "registry+https://github.com/rust-lang/crates.io-index"
163 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
164 | 
165 | [[package]]
166 | name = "console"
167 | version = "0.15.11"
168 | source = "registry+https://github.com/rust-lang/crates.io-index"
169 | checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
170 | dependencies = [
171 |  "encode_unicode",
172 |  "libc",
173 |  "once_cell",
174 |  "unicode-width",
175 |  "windows-sys",
176 | ]
177 | 
178 | [[package]]
179 | name = "cpufeatures"
180 | version = "0.2.17"
181 | source = "registry+https://github.com/rust-lang/crates.io-index"
182 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
183 | dependencies = [
184 |  "libc",
185 | ]
186 | 
187 | [[package]]
188 | name = "crossbeam-channel"
189 | version = "0.5.15"
190 | source = "registry+https://github.com/rust-lang/crates.io-index"
191 | checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
192 | dependencies = [
193 |  "crossbeam-utils",
194 | ]
195 | 
196 | [[package]]
197 | name = "crossbeam-utils"
198 | version = "0.8.21"
199 | source = "registry+https://github.com/rust-lang/crates.io-index"
200 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
201 | 
202 | [[package]]
203 | name = "crypto-common"
204 | version = "0.1.6"
205 | source = "registry+https://github.com/rust-lang/crates.io-index"
206 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
207 | dependencies = [
208 |  "generic-array",
209 |  "typenum",
210 | ]
211 | 
212 | [[package]]
213 | name = "digest"
214 | version = "0.10.7"
215 | source = "registry+https://github.com/rust-lang/crates.io-index"
216 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
217 | dependencies = [
218 |  "block-buffer",
219 |  "crypto-common",
220 | ]
221 | 
222 | [[package]]
223 | name = "dwarfs"
224 | version = "0.2.1"
225 | dependencies = [
226 |  "bstr",
227 |  "indexmap",
228 |  "liblzma",
229 |  "log",
230 |  "lru",
231 |  "lz4",
232 |  "measure_time",
233 |  "positioned-io",
234 |  "serde",
235 |  "sha2",
236 |  "xxhash-rust",
237 |  "zerocopy",
238 |  "zstd-safe",
239 | ]
240 | 
241 | [[package]]
242 | name = "dwarfs-enc"
243 | version = "0.1.0"
244 | dependencies = [
245 |  "clap",
246 |  "crossbeam-channel",
247 |  "dwarfs",
248 |  "indexmap",
249 |  "indicatif",
250 |  "liblzma",
251 |  "rustic_cdc",
252 |  "rustix",
253 |  "serde",
254 |  "sha2",
255 |  "zerocopy",
256 |  "zstd-safe",
257 | ]
258 | 
259 | [[package]]
260 | name = "dwarfs-test"
261 | version = "0.0.0"
262 | dependencies = [
263 |  "dwarfs",
264 |  "env_logger",
265 |  "hex",
266 |  "rustix",
267 |  "sha2",
268 |  "tempfile",
269 |  "xshell",
270 | ]
271 | 
272 | [[package]]
273 | name = "encode_unicode"
274 | version = "1.0.0"
275 | source = "registry+https://github.com/rust-lang/crates.io-index"
276 | checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
277 | 
278 | [[package]]
279 | name = "env_filter"
280 | version = "0.1.3"
281 | source = "registry+https://github.com/rust-lang/crates.io-index"
282 | checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
283 | dependencies = [
284 |  "log",
285 |  "regex",
286 | ]
287 | 
288 | [[package]]
289 | name = "env_logger"
290 | version = "0.11.8"
291 | source = "registry+https://github.com/rust-lang/crates.io-index"
292 | checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
293 | dependencies = [
294 |  "anstream",
295 |  "anstyle",
296 |  "env_filter",
297 |  "jiff",
298 |  "log",
299 | ]
300 | 
301 | [[package]]
302 | name = "equivalent"
303 | version = "1.0.2"
304 | source = "registry+https://github.com/rust-lang/crates.io-index"
305 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
306 | 
307 | [[package]]
308 | name = "errno"
309 | version = "0.3.12"
310 | source = "registry+https://github.com/rust-lang/crates.io-index"
311 | checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
312 | dependencies = [
313 |  "libc",
314 |  "windows-sys",
315 | ]
316 | 
317 | [[package]]
318 | name = "fastrand"
319 | version = "2.3.0"
320 | source = "registry+https://github.com/rust-lang/crates.io-index"
321 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
322 | 
323 | [[package]]
324 | name = "foldhash"
325 | version = "0.1.5"
326 | source = "registry+https://github.com/rust-lang/crates.io-index"
327 | checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
328 | 
329 | [[package]]
330 | name = "generic-array"
331 | version = "0.14.7"
332 | source = "registry+https://github.com/rust-lang/crates.io-index"
333 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
334 | dependencies = [
335 |  "typenum",
336 |  "version_check",
337 | ]
338 | 
339 | [[package]]
340 | name = "getrandom"
341 | version = "0.3.3"
342 | source = "registry+https://github.com/rust-lang/crates.io-index"
343 | checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
344 | dependencies = [
345 |  "cfg-if",
346 |  "libc",
347 |  "r-efi",
348 |  "wasi",
349 | ]
350 | 
351 | [[package]]
352 | name = "hashbrown"
353 | version = "0.15.3"
354 | source = "registry+https://github.com/rust-lang/crates.io-index"
355 | checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
356 | dependencies = [
357 |  "allocator-api2",
358 |  "equivalent",
359 |  "foldhash",
360 | ]
361 | 
362 | [[package]]
363 | name = "heck"
364 | version = "0.5.0"
365 | source = "registry+https://github.com/rust-lang/crates.io-index"
366 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
367 | 
368 | [[package]]
369 | name = "hex"
370 | version = "0.4.3"
371 | source = "registry+https://github.com/rust-lang/crates.io-index"
372 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
373 | 
374 | [[package]]
375 | name = "indexmap"
376 | version = "2.9.0"
377 | source = "registry+https://github.com/rust-lang/crates.io-index"
378 | checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
379 | dependencies = [
380 |  "equivalent",
381 |  "hashbrown",
382 | ]
383 | 
384 | [[package]]
385 | name = "indicatif"
386 | version = "0.17.11"
387 | source = "registry+https://github.com/rust-lang/crates.io-index"
388 | checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
389 | dependencies = [
390 |  "console",
391 |  "number_prefix",
392 |  "portable-atomic",
393 |  "unicode-width",
394 |  "web-time",
395 | ]
396 | 
397 | [[package]]
398 | name = "is_terminal_polyfill"
399 | version = "1.70.1"
400 | source = "registry+https://github.com/rust-lang/crates.io-index"
401 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
402 | 
403 | [[package]]
404 | name = "jiff"
405 | version = "0.2.13"
406 | source = "registry+https://github.com/rust-lang/crates.io-index"
407 | checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806"
408 | dependencies = [
409 |  "jiff-static",
410 |  "log",
411 |  "portable-atomic",
412 |  "portable-atomic-util",
413 |  "serde",
414 | ]
415 | 
416 | [[package]]
417 | name = "jiff-static"
418 | version = "0.2.13"
419 | source = "registry+https://github.com/rust-lang/crates.io-index"
420 | checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48"
421 | dependencies = [
422 |  "proc-macro2",
423 |  "quote",
424 |  "syn",
425 | ]
426 | 
427 | [[package]]
428 | name = "jobserver"
429 | version = "0.1.33"
430 | source = "registry+https://github.com/rust-lang/crates.io-index"
431 | checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
432 | dependencies = [
433 |  "getrandom",
434 |  "libc",
435 | ]
436 | 
437 | [[package]]
438 | name = "js-sys"
439 | version = "0.3.77"
440 | source = "registry+https://github.com/rust-lang/crates.io-index"
441 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
442 | dependencies = [
443 |  "once_cell",
444 |  "wasm-bindgen",
445 | ]
446 | 
447 | [[package]]
448 | name = "libc"
449 | version = "0.2.172"
450 | source = "registry+https://github.com/rust-lang/crates.io-index"
451 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
452 | 
453 | [[package]]
454 | name = "liblzma"
455 | version = "0.4.1"
456 | source = "registry+https://github.com/rust-lang/crates.io-index"
457 | checksum = "66352d7a8ac12d4877b6e6ea5a9b7650ee094257dc40889955bea5bc5b08c1d0"
458 | dependencies = [
459 |  "liblzma-sys",
460 | ]
461 | 
462 | [[package]]
463 | name = "liblzma-sys"
464 | version = "0.4.3"
465 | source = "registry+https://github.com/rust-lang/crates.io-index"
466 | checksum = "5839bad90c3cc2e0b8c4ed8296b80e86040240f81d46b9c0e9bc8dd51ddd3af1"
467 | dependencies = [
468 |  "cc",
469 |  "libc",
470 |  "pkg-config",
471 | ]
472 | 
473 | [[package]]
474 | name = "linux-raw-sys"
475 | version = "0.9.4"
476 | source = "registry+https://github.com/rust-lang/crates.io-index"
477 | checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
478 | 
479 | [[package]]
480 | name = "log"
481 | version = "0.4.27"
482 | source = "registry+https://github.com/rust-lang/crates.io-index"
483 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
484 | 
485 | [[package]]
486 | name = "lru"
487 | version = "0.14.0"
488 | source = "registry+https://github.com/rust-lang/crates.io-index"
489 | checksum = "9f8cc7106155f10bdf99a6f379688f543ad6596a415375b36a59a054ceda1198"
490 | dependencies = [
491 |  "hashbrown",
492 | ]
493 | 
494 | [[package]]
495 | name = "lz4"
496 | version = "1.28.1"
497 | source = "registry+https://github.com/rust-lang/crates.io-index"
498 | checksum = "a20b523e860d03443e98350ceaac5e71c6ba89aea7d960769ec3ce37f4de5af4"
499 | dependencies = [
500 |  "lz4-sys",
501 | ]
502 | 
503 | [[package]]
504 | name = "lz4-sys"
505 | version = "1.11.1+lz4-1.10.0"
506 | source = "registry+https://github.com/rust-lang/crates.io-index"
507 | checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6"
508 | dependencies = [
509 |  "cc",
510 |  "libc",
511 | ]
512 | 
513 | [[package]]
514 | name = "measure_time"
515 | version = "0.9.0"
516 | source = "registry+https://github.com/rust-lang/crates.io-index"
517 | checksum = "51c55d61e72fc3ab704396c5fa16f4c184db37978ae4e94ca8959693a235fc0e"
518 | dependencies = [
519 |  "log",
520 | ]
521 | 
522 | [[package]]
523 | name = "memchr"
524 | version = "2.7.4"
525 | source = "registry+https://github.com/rust-lang/crates.io-index"
526 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
527 | 
528 | [[package]]
529 | name = "number_prefix"
530 | version = "0.4.0"
531 | source = "registry+https://github.com/rust-lang/crates.io-index"
532 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
533 | 
534 | [[package]]
535 | name = "once_cell"
536 | version = "1.21.3"
537 | source = "registry+https://github.com/rust-lang/crates.io-index"
538 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
539 | 
540 | [[package]]
541 | name = "pkg-config"
542 | version = "0.3.32"
543 | source = "registry+https://github.com/rust-lang/crates.io-index"
544 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
545 | 
546 | [[package]]
547 | name = "portable-atomic"
548 | version = "1.11.0"
549 | source = "registry+https://github.com/rust-lang/crates.io-index"
550 | checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
551 | 
552 | [[package]]
553 | name = "portable-atomic-util"
554 | version = "0.2.4"
555 | source = "registry+https://github.com/rust-lang/crates.io-index"
556 | checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
557 | dependencies = [
558 |  "portable-atomic",
559 | ]
560 | 
561 | [[package]]
562 | name = "positioned-io"
563 | version = "0.3.4"
564 | source = "registry+https://github.com/rust-lang/crates.io-index"
565 | checksum = "e8078ce4d22da5e8f57324d985cc9befe40c49ab0507a192d6be9e59584495c9"
566 | dependencies = [
567 |  "libc",
568 |  "winapi",
569 | ]
570 | 
571 | [[package]]
572 | name = "proc-macro2"
573 | version = "1.0.95"
574 | source = "registry+https://github.com/rust-lang/crates.io-index"
575 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
576 | dependencies = [
577 |  "unicode-ident",
578 | ]
579 | 
580 | [[package]]
581 | name = "quote"
582 | version = "1.0.40"
583 | source = "registry+https://github.com/rust-lang/crates.io-index"
584 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
585 | dependencies = [
586 |  "proc-macro2",
587 | ]
588 | 
589 | [[package]]
590 | name = "r-efi"
591 | version = "5.2.0"
592 | source = "registry+https://github.com/rust-lang/crates.io-index"
593 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
594 | 
595 | [[package]]
596 | name = "regex"
597 | version = "1.11.1"
598 | source = "registry+https://github.com/rust-lang/crates.io-index"
599 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
600 | dependencies = [
601 |  "aho-corasick",
602 |  "memchr",
603 |  "regex-automata",
604 |  "regex-syntax",
605 | ]
606 | 
607 | [[package]]
608 | name = "regex-automata"
609 | version = "0.4.9"
610 | source = "registry+https://github.com/rust-lang/crates.io-index"
611 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
612 | dependencies = [
613 |  "aho-corasick",
614 |  "memchr",
615 |  "regex-syntax",
616 | ]
617 | 
618 | [[package]]
619 | name = "regex-syntax"
620 | version = "0.8.5"
621 | source = "registry+https://github.com/rust-lang/crates.io-index"
622 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
623 | 
624 | [[package]]
625 | name = "rustic_cdc"
626 | version = "0.3.1"
627 | source = "registry+https://github.com/rust-lang/crates.io-index"
628 | checksum = "fbcebf2228827bc4b61cb54dfd84cf43aacf06ca2dfe4c014b136a0e32b876e2"
629 | 
630 | [[package]]
631 | name = "rustix"
632 | version = "1.0.7"
633 | source = "registry+https://github.com/rust-lang/crates.io-index"
634 | checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
635 | dependencies = [
636 |  "bitflags",
637 |  "errno",
638 |  "libc",
639 |  "linux-raw-sys",
640 |  "windows-sys",
641 | ]
642 | 
643 | [[package]]
644 | name = "serde"
645 | version = "1.0.219"
646 | source = "registry+https://github.com/rust-lang/crates.io-index"
647 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
648 | dependencies = [
649 |  "serde_derive",
650 | ]
651 | 
652 | [[package]]
653 | name = "serde_derive"
654 | version = "1.0.219"
655 | source = "registry+https://github.com/rust-lang/crates.io-index"
656 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
657 | dependencies = [
658 |  "proc-macro2",
659 |  "quote",
660 |  "syn",
661 | ]
662 | 
663 | [[package]]
664 | name = "sha2"
665 | version = "0.10.9"
666 | source = "registry+https://github.com/rust-lang/crates.io-index"
667 | checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
668 | dependencies = [
669 |  "cfg-if",
670 |  "cpufeatures",
671 |  "digest",
672 | ]
673 | 
674 | [[package]]
675 | name = "shlex"
676 | version = "1.3.0"
677 | source = "registry+https://github.com/rust-lang/crates.io-index"
678 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
679 | 
680 | [[package]]
681 | name = "strsim"
682 | version = "0.11.1"
683 | source = "registry+https://github.com/rust-lang/crates.io-index"
684 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
685 | 
686 | [[package]]
687 | name = "syn"
688 | version = "2.0.101"
689 | source = "registry+https://github.com/rust-lang/crates.io-index"
690 | checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
691 | dependencies = [
692 |  "proc-macro2",
693 |  "quote",
694 |  "unicode-ident",
695 | ]
696 | 
697 | [[package]]
698 | name = "tempfile"
699 | version = "3.20.0"
700 | source = "registry+https://github.com/rust-lang/crates.io-index"
701 | checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
702 | dependencies = [
703 |  "fastrand",
704 |  "getrandom",
705 |  "once_cell",
706 |  "rustix",
707 |  "windows-sys",
708 | ]
709 | 
710 | [[package]]
711 | name = "typenum"
712 | version = "1.18.0"
713 | source = "registry+https://github.com/rust-lang/crates.io-index"
714 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
715 | 
716 | [[package]]
717 | name = "unicode-ident"
718 | version = "1.0.18"
719 | source = "registry+https://github.com/rust-lang/crates.io-index"
720 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
721 | 
722 | [[package]]
723 | name = "unicode-width"
724 | version = "0.2.0"
725 | source = "registry+https://github.com/rust-lang/crates.io-index"
726 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
727 | 
728 | [[package]]
729 | name = "utf8parse"
730 | version = "0.2.2"
731 | source = "registry+https://github.com/rust-lang/crates.io-index"
732 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
733 | 
734 | [[package]]
735 | name = "version_check"
736 | version = "0.9.5"
737 | source = "registry+https://github.com/rust-lang/crates.io-index"
738 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
739 | 
740 | [[package]]
741 | name = "wasi"
742 | version = "0.14.2+wasi-0.2.4"
743 | source = "registry+https://github.com/rust-lang/crates.io-index"
744 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
745 | dependencies = [
746 |  "wit-bindgen-rt",
747 | ]
748 | 
749 | [[package]]
750 | name = "wasm-bindgen"
751 | version = "0.2.100"
752 | source = "registry+https://github.com/rust-lang/crates.io-index"
753 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
754 | dependencies = [
755 |  "cfg-if",
756 |  "once_cell",
757 |  "wasm-bindgen-macro",
758 | ]
759 | 
760 | [[package]]
761 | name = "wasm-bindgen-backend"
762 | version = "0.2.100"
763 | source = "registry+https://github.com/rust-lang/crates.io-index"
764 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
765 | dependencies = [
766 |  "bumpalo",
767 |  "log",
768 |  "proc-macro2",
769 |  "quote",
770 |  "syn",
771 |  "wasm-bindgen-shared",
772 | ]
773 | 
774 | [[package]]
775 | name = "wasm-bindgen-macro"
776 | version = "0.2.100"
777 | source = "registry+https://github.com/rust-lang/crates.io-index"
778 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
779 | dependencies = [
780 |  "quote",
781 |  "wasm-bindgen-macro-support",
782 | ]
783 | 
784 | [[package]]
785 | name = "wasm-bindgen-macro-support"
786 | version = "0.2.100"
787 | source = "registry+https://github.com/rust-lang/crates.io-index"
788 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
789 | dependencies = [
790 |  "proc-macro2",
791 |  "quote",
792 |  "syn",
793 |  "wasm-bindgen-backend",
794 |  "wasm-bindgen-shared",
795 | ]
796 | 
797 | [[package]]
798 | name = "wasm-bindgen-shared"
799 | version = "0.2.100"
800 | source = "registry+https://github.com/rust-lang/crates.io-index"
801 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
802 | dependencies = [
803 |  "unicode-ident",
804 | ]
805 | 
806 | [[package]]
807 | name = "web-time"
808 | version = "1.1.0"
809 | source = "registry+https://github.com/rust-lang/crates.io-index"
810 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
811 | dependencies = [
812 |  "js-sys",
813 |  "wasm-bindgen",
814 | ]
815 | 
816 | [[package]]
817 | name = "winapi"
818 | version = "0.3.9"
819 | source = "registry+https://github.com/rust-lang/crates.io-index"
820 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
821 | dependencies = [
822 |  "winapi-i686-pc-windows-gnu",
823 |  "winapi-x86_64-pc-windows-gnu",
824 | ]
825 | 
826 | [[package]]
827 | name = "winapi-i686-pc-windows-gnu"
828 | version = "0.4.0"
829 | source = "registry+https://github.com/rust-lang/crates.io-index"
830 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
831 | 
832 | [[package]]
833 | name = "winapi-x86_64-pc-windows-gnu"
834 | version = "0.4.0"
835 | source = "registry+https://github.com/rust-lang/crates.io-index"
836 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
837 | 
838 | [[package]]
839 | name = "windows-sys"
840 | version = "0.59.0"
841 | source = "registry+https://github.com/rust-lang/crates.io-index"
842 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
843 | dependencies = [
844 |  "windows-targets",
845 | ]
846 | 
847 | [[package]]
848 | name = "windows-targets"
849 | version = "0.52.6"
850 | source = "registry+https://github.com/rust-lang/crates.io-index"
851 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
852 | dependencies = [
853 |  "windows_aarch64_gnullvm",
854 |  "windows_aarch64_msvc",
855 |  "windows_i686_gnu",
856 |  "windows_i686_gnullvm",
857 |  "windows_i686_msvc",
858 |  "windows_x86_64_gnu",
859 |  "windows_x86_64_gnullvm",
860 |  "windows_x86_64_msvc",
861 | ]
862 | 
863 | [[package]]
864 | name = "windows_aarch64_gnullvm"
865 | version = "0.52.6"
866 | source = "registry+https://github.com/rust-lang/crates.io-index"
867 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
868 | 
869 | [[package]]
870 | name = "windows_aarch64_msvc"
871 | version = "0.52.6"
872 | source = "registry+https://github.com/rust-lang/crates.io-index"
873 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
874 | 
875 | [[package]]
876 | name = "windows_i686_gnu"
877 | version = "0.52.6"
878 | source = "registry+https://github.com/rust-lang/crates.io-index"
879 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
880 | 
881 | [[package]]
882 | name = "windows_i686_gnullvm"
883 | version = "0.52.6"
884 | source = "registry+https://github.com/rust-lang/crates.io-index"
885 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
886 | 
887 | [[package]]
888 | name = "windows_i686_msvc"
889 | version = "0.52.6"
890 | source = "registry+https://github.com/rust-lang/crates.io-index"
891 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
892 | 
893 | [[package]]
894 | name = "windows_x86_64_gnu"
895 | version = "0.52.6"
896 | source = "registry+https://github.com/rust-lang/crates.io-index"
897 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
898 | 
899 | [[package]]
900 | name = "windows_x86_64_gnullvm"
901 | version = "0.52.6"
902 | source = "registry+https://github.com/rust-lang/crates.io-index"
903 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
904 | 
905 | [[package]]
906 | name = "windows_x86_64_msvc"
907 | version = "0.52.6"
908 | source = "registry+https://github.com/rust-lang/crates.io-index"
909 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
910 | 
911 | [[package]]
912 | name = "wit-bindgen-rt"
913 | version = "0.39.0"
914 | source = "registry+https://github.com/rust-lang/crates.io-index"
915 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
916 | dependencies = [
917 |  "bitflags",
918 | ]
919 | 
920 | [[package]]
921 | name = "xshell"
922 | version = "0.2.7"
923 | source = "registry+https://github.com/rust-lang/crates.io-index"
924 | checksum = "9e7290c623014758632efe00737145b6867b66292c42167f2ec381eb566a373d"
925 | dependencies = [
926 |  "xshell-macros",
927 | ]
928 | 
929 | [[package]]
930 | name = "xshell-macros"
931 | version = "0.2.7"
932 | source = "registry+https://github.com/rust-lang/crates.io-index"
933 | checksum = "32ac00cd3f8ec9c1d33fb3e7958a82df6989c42d747bd326c822b1d625283547"
934 | 
935 | [[package]]
936 | name = "xxhash-rust"
937 | version = "0.8.15"
938 | source = "registry+https://github.com/rust-lang/crates.io-index"
939 | checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3"
940 | 
941 | [[package]]
942 | name = "zerocopy"
943 | version = "0.8.25"
944 | source = "registry+https://github.com/rust-lang/crates.io-index"
945 | checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
946 | dependencies = [
947 |  "zerocopy-derive",
948 | ]
949 | 
950 | [[package]]
951 | name = "zerocopy-derive"
952 | version = "0.8.25"
953 | source = "registry+https://github.com/rust-lang/crates.io-index"
954 | checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
955 | dependencies = [
956 |  "proc-macro2",
957 |  "quote",
958 |  "syn",
959 | ]
960 | 
961 | [[package]]
962 | name = "zstd-safe"
963 | version = "7.2.4"
964 | source = "registry+https://github.com/rust-lang/crates.io-index"
965 | checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
966 | dependencies = [
967 |  "zstd-sys",
968 | ]
969 | 
970 | [[package]]
971 | name = "zstd-sys"
972 | version = "2.0.15+zstd.1.5.7"
973 | source = "registry+https://github.com/rust-lang/crates.io-index"
974 | checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
975 | dependencies = [
976 |  "cc",
977 |  "pkg-config",
978 | ]
979 | 
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
 1 | [workspace]
 2 | resolver = "3"
 3 | members = [
 4 |     "dwarfs",
 5 |     "dwarfs-test",
 6 |     "dwarfs-enc",
 7 | ]
 8 | 
 9 | [profile.bench]
10 | debug = "line-tables-only"
11 | 
--------------------------------------------------------------------------------
/LICENSE-APACHE:
--------------------------------------------------------------------------------
  1 |                               Apache License
  2 |                         Version 2.0, January 2004
  3 |                      http://www.apache.org/licenses/
  4 | 
  5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
  6 | 
  7 | 1. Definitions.
  8 | 
  9 |    "License" shall mean the terms and conditions for use, reproduction,
 10 |    and distribution as defined by Sections 1 through 9 of this document.
 11 | 
 12 |    "Licensor" shall mean the copyright owner or entity authorized by
 13 |    the copyright owner that is granting the License.
 14 | 
 15 |    "Legal Entity" shall mean the union of the acting entity and all
 16 |    other entities that control, are controlled by, or are under common
 17 |    control with that entity. For the purposes of this definition,
 18 |    "control" means (i) the power, direct or indirect, to cause the
 19 |    direction or management of such entity, whether by contract or
 20 |    otherwise, or (ii) ownership of fifty percent (50%) or more of the
 21 |    outstanding shares, or (iii) beneficial ownership of such entity.
 22 | 
 23 |    "You" (or "Your") shall mean an individual or Legal Entity
 24 |    exercising permissions granted by this License.
 25 | 
 26 |    "Source" form shall mean the preferred form for making modifications,
 27 |    including but not limited to software source code, documentation
 28 |    source, and configuration files.
 29 | 
 30 |    "Object" form shall mean any form resulting from mechanical
 31 |    transformation or translation of a Source form, including but
 32 |    not limited to compiled object code, generated documentation,
 33 |    and conversions to other media types.
 34 | 
 35 |    "Work" shall mean the work of authorship, whether in Source or
 36 |    Object form, made available under the License, as indicated by a
 37 |    copyright notice that is included in or attached to the work
 38 |    (an example is provided in the Appendix below).
 39 | 
 40 |    "Derivative Works" shall mean any work, whether in Source or Object
 41 |    form, that is based on (or derived from) the Work and for which the
 42 |    editorial revisions, annotations, elaborations, or other modifications
 43 |    represent, as a whole, an original work of authorship. For the purposes
 44 |    of this License, Derivative Works shall not include works that remain
 45 |    separable from, or merely link (or bind by name) to the interfaces of,
 46 |    the Work and Derivative Works thereof.
 47 | 
 48 |    "Contribution" shall mean any work of authorship, including
 49 |    the original version of the Work and any modifications or additions
 50 |    to that Work or Derivative Works thereof, that is intentionally
 51 |    submitted to Licensor for inclusion in the Work by the copyright owner
 52 |    or by an individual or Legal Entity authorized to submit on behalf of
 53 |    the copyright owner. For the purposes of this definition, "submitted"
 54 |    means any form of electronic, verbal, or written communication sent
 55 |    to the Licensor or its representatives, including but not limited to
 56 |    communication on electronic mailing lists, source code control systems,
 57 |    and issue tracking systems that are managed by, or on behalf of, the
 58 |    Licensor for the purpose of discussing and improving the Work, but
 59 |    excluding communication that is conspicuously marked or otherwise
 60 |    designated in writing by the copyright owner as "Not a Contribution."
 61 | 
 62 |    "Contributor" shall mean Licensor and any individual or Legal Entity
 63 |    on behalf of whom a Contribution has been received by Licensor and
 64 |    subsequently incorporated within the Work.
 65 | 
 66 | 2. Grant of Copyright License. Subject to the terms and conditions of
 67 |    this License, each Contributor hereby grants to You a perpetual,
 68 |    worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 69 |    copyright license to reproduce, prepare Derivative Works of,
 70 |    publicly display, publicly perform, sublicense, and distribute the
 71 |    Work and such Derivative Works in Source or Object form.
 72 | 
 73 | 3. Grant of Patent License. Subject to the terms and conditions of
 74 |    this License, each Contributor hereby grants to You a perpetual,
 75 |    worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 76 |    (except as stated in this section) patent license to make, have made,
 77 |    use, offer to sell, sell, import, and otherwise transfer the Work,
 78 |    where such license applies only to those patent claims licensable
 79 |    by such Contributor that are necessarily infringed by their
 80 |    Contribution(s) alone or by combination of their Contribution(s)
 81 |    with the Work to which such Contribution(s) was submitted. If You
 82 |    institute patent litigation against any entity (including a
 83 |    cross-claim or counterclaim in a lawsuit) alleging that the Work
 84 |    or a Contribution incorporated within the Work constitutes direct
 85 |    or contributory patent infringement, then any patent licenses
 86 |    granted to You under this License for that Work shall terminate
 87 |    as of the date such litigation is filed.
 88 | 
 89 | 4. Redistribution. You may reproduce and distribute copies of the
 90 |    Work or Derivative Works thereof in any medium, with or without
 91 |    modifications, and in Source or Object form, provided that You
 92 |    meet the following conditions:
 93 | 
 94 |    (a) You must give any other recipients of the Work or
 95 |        Derivative Works a copy of this License; and
 96 | 
 97 |    (b) You must cause any modified files to carry prominent notices
 98 |        stating that You changed the files; and
 99 | 
100 |    (c) You must retain, in the Source form of any Derivative Works
101 |        that You distribute, all copyright, patent, trademark, and
102 |        attribution notices from the Source form of the Work,
103 |        excluding those notices that do not pertain to any part of
104 |        the Derivative Works; and
105 | 
106 |    (d) If the Work includes a "NOTICE" text file as part of its
107 |        distribution, then any Derivative Works that You distribute must
108 |        include a readable copy of the attribution notices contained
109 |        within such NOTICE file, excluding those notices that do not
110 |        pertain to any part of the Derivative Works, in at least one
111 |        of the following places: within a NOTICE text file distributed
112 |        as part of the Derivative Works; within the Source form or
113 |        documentation, if provided along with the Derivative Works; or,
114 |        within a display generated by the Derivative Works, if and
115 |        wherever such third-party notices normally appear. The contents
116 |        of the NOTICE file are for informational purposes only and
117 |        do not modify the License. You may add Your own attribution
118 |        notices within Derivative Works that You distribute, alongside
119 |        or as an addendum to the NOTICE text from the Work, provided
120 |        that such additional attribution notices cannot be construed
121 |        as modifying the License.
122 | 
123 |    You may add Your own copyright statement to Your modifications and
124 |    may provide additional or different license terms and conditions
125 |    for use, reproduction, or distribution of Your modifications, or
126 |    for any such Derivative Works as a whole, provided Your use,
127 |    reproduction, and distribution of the Work otherwise complies with
128 |    the conditions stated in this License.
129 | 
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 |    any Contribution intentionally submitted for inclusion in the Work
132 |    by You to the Licensor shall be under the terms and conditions of
133 |    this License, without any additional terms or conditions.
134 |    Notwithstanding the above, nothing herein shall supersede or modify
135 |    the terms of any separate license agreement you may have executed
136 |    with Licensor regarding such Contributions.
137 | 
138 | 6. Trademarks. This License does not grant permission to use the trade
139 |    names, trademarks, service marks, or product names of the Licensor,
140 |    except as required for reasonable and customary use in describing the
141 |    origin of the Work and reproducing the content of the NOTICE file.
142 | 
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 |    agreed to in writing, Licensor provides the Work (and each
145 |    Contributor provides its Contributions) on an "AS IS" BASIS,
146 |    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 |    implied, including, without limitation, any warranties or conditions
148 |    of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 |    PARTICULAR PURPOSE. You are solely responsible for determining the
150 |    appropriateness of using or redistributing the Work and assume any
151 |    risks associated with Your exercise of permissions under this License.
152 | 
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 |    whether in tort (including negligence), contract, or otherwise,
155 |    unless required by applicable law (such as deliberate and grossly
156 |    negligent acts) or agreed to in writing, shall any Contributor be
157 |    liable to You for damages, including any direct, indirect, special,
158 |    incidental, or consequential damages of any character arising as a
159 |    result of this License or out of the use or inability to use the
160 |    Work (including but not limited to damages for loss of goodwill,
161 |    work stoppage, computer failure or malfunction, or any and all
162 |    other commercial damages or losses), even if such Contributor
163 |    has been advised of the possibility of such damages.
164 | 
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 |    the Work or Derivative Works thereof, You may choose to offer,
167 |    and charge a fee for, acceptance of support, warranty, indemnity,
168 |    or other liability obligations and/or rights consistent with this
169 |    License. However, in accepting such obligations, You may act only
170 |    on Your own behalf and on Your sole responsibility, not on behalf
171 |    of any other Contributor, and only if You agree to indemnify,
172 |    defend, and hold each Contributor harmless for any liability
173 |    incurred by, or claims asserted against, such Contributor by reason
174 |    of your accepting any such warranty or additional liability.
175 | 
176 | END OF TERMS AND CONDITIONS
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
 1 | Permission is hereby granted, free of charge, to any
 2 | person obtaining a copy of this software and associated
 3 | documentation files (the "Software"), to deal in the
 4 | Software without restriction, including without
 5 | limitation the rights to use, copy, modify, merge,
 6 | publish, distribute, sublicense, and/or sell copies of
 7 | the Software, and to permit persons to whom the Software
 8 | is furnished to do so, subject to the following
 9 | conditions:
10 | 
11 | The above copyright notice and this permission notice
12 | shall be included in all copies or substantial portions
13 | of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 | DEALINGS IN THE SOFTWARE.
24 | 
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
 1 | # dwarfs
 2 | 
 3 | [](https://crates.io/crates/dwarfs)
 4 | [](https://docs.rs/dwarfs)
 5 | [](https://crates.io/crates/dwarfs-enc)
 6 | [](https://docs.rs/dwarfs-enc)
 7 | 
 8 | Libraries for reading and writing [DwarFS][dwarfs] archives (aka. DwarFS images),
 9 | in pure Rust without `unsafe`.
10 | 
11 | #### License
12 | 
13 | TL;DR: We mostly follow [upstream][dwarfs]: the package for constructing
14 | DwarFS archives (dwarfs-enc) is GPL-3.0. Other code is "(MIT OR Apache-2.0)".
15 | 
16 | Long version:
17 | 
18 | All files under directory `dwarfs-enc` are licensed under GNU General Public
19 | License, version 3. Check `./dwarfs-enc/README.md` and `./LICENSE-GPL-3.0` for
20 | details.
21 | 
22 | Other files in this repository outside `dwarfs-enc`, including `dwarfs` and
23 | `dwarfs-test` packages, are licensed under Apache
24 | License 2.0 or MIT license at your option. Check `./dwarfs/README.md`,
25 | `./LICENSE-APACHE` and `./LICENSE-MIT` for details.
26 | 
27 | [dwarfs]: https://github.com/mhx/dwarfs
28 | 
--------------------------------------------------------------------------------
/clippy.toml:
--------------------------------------------------------------------------------
1 | doc-valid-idents = ["DwarFS", ".."]
--------------------------------------------------------------------------------
/dwarfs-enc/Cargo.toml:
--------------------------------------------------------------------------------
 1 | [package]
 2 | name = "dwarfs-enc"
 3 | version = "0.1.0"
 4 | edition = "2024"
 5 | license = "GPL-3.0-only"
 6 | description = "A library for writing DwarFS archives (aka. DwarFS images)"
 7 | keywords = ["dwarfs", "archive", "compression"]
 8 | categories = ["compression", "filesystem"]
 9 | repository = "https://github.com/oxalica/dwarfs-rs"
10 | 
11 | [features]
12 | default = ["zstd"]
13 | 
14 | zstd = ["dep:zstd-safe"]
15 | lzma = ["dep:liblzma"]
16 | 
17 | [dependencies]
18 | crossbeam-channel = "0.5.15"
19 | dwarfs = { version = "0.2.1", path = "../dwarfs", features = ["serialize"] }
20 | indexmap = "2.9.0"
21 | liblzma = { version = "0.4.1", optional = true }
22 | rustic_cdc = "0.3.1"
23 | rustix = { version = "1.0.7", features = ["fs"] }
24 | serde = "1.0.219"
25 | sha2 = "0.10.9"
26 | zerocopy = { version = "0.8.25", features = ["derive", "std"] }
27 | zstd-safe = { version = "7.2.4", default-features = false, optional = true }
28 | 
29 | [dev-dependencies]
30 | clap = { version = "4.5.39", features = ["derive"] }
31 | indicatif = "0.17.11"
32 | 
33 | [[example]]
34 | name = "mkdwarfs"
35 | required-features = ["zstd", "lzma"]
36 | 
37 | [lints.clippy]
38 | dbg-macro = "warn"
39 | todo = "warn"
40 | print-stdout = "warn"
41 | print-stderr = "warn"
--------------------------------------------------------------------------------
/dwarfs-enc/LICENSE-GPL-3.0:
--------------------------------------------------------------------------------
1 | ../LICENSE-GPL-3.0
--------------------------------------------------------------------------------
/dwarfs-enc/README.md:
--------------------------------------------------------------------------------
 1 | # dwarfs-enc
 2 | 
 3 | [](https://crates.io/crates/dwarfs-enc)
 4 | [](https://docs.rs/dwarfs-enc)
 5 | 
 6 | A library for writing [DwarFS][dwarfs] archives (aka. DwarFS images),
 7 | building on top of [`dwarfs` crate][dwarfs-rs].
 8 | 
 9 | [dwarfs]: https://github.com/mhx/dwarfs
10 | [dwarfs-rs]: https://crates.io/crates/dwarfs
11 | 
12 | #### License
13 | 
14 | SPDX-License-Identifier: GPL-3.0-only
15 | 
16 | Copyright (C) 2025 Oxalica
17 | 
18 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3.
19 | 
20 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
21 | 
22 | You should have received a copy of the GNU General Public License along with this program. If not, see . 
23 | 
--------------------------------------------------------------------------------
/dwarfs-enc/examples/mkdwarfs.rs:
--------------------------------------------------------------------------------
  1 | #![expect(clippy::print_stderr, reason = "allowed in examples")]
  2 | use std::{
  3 |     borrow::Cow,
  4 |     fs,
  5 |     path::{Path, PathBuf},
  6 |     time::Instant,
  7 | };
  8 | 
  9 | use dwarfs_enc::{
 10 |     chunker::{self, Chunker},
 11 |     metadata::{Builder as MetadataBuilder, InodeMetadata},
 12 |     section::{self, CompressParam},
 13 | };
 14 | use indicatif::{HumanBytes, HumanCount, MultiProgress, ProgressBar, ProgressStyle};
 15 | 
 16 | #[derive(Debug, clap::Parser)]
 17 | struct Cli {
 18 |     #[arg(short, long)]
 19 |     input: PathBuf,
 20 |     #[arg(short, long)]
 21 |     output: PathBuf,
 22 | 
 23 |     #[arg(short, long)]
 24 |     force: bool,
 25 | 
 26 |     #[arg(long, conflicts_with = "lzma")]
 27 |     zstd: Option,
 28 |     #[arg(long)]
 29 |     lzma: Option,
 30 | }
 31 | 
 32 | fn main() -> Result<(), Box> {
 33 |     let cli: Cli = clap::Parser::parse();
 34 | 
 35 |     let inst = Instant::now();
 36 | 
 37 |     let fout = fs::OpenOptions::new()
 38 |         .write(true)
 39 |         .create(true)
 40 |         .truncate(true)
 41 |         .create_new(!cli.force)
 42 |         .open(&cli.output)?;
 43 | 
 44 |     let root_meta = fs::metadata(&cli.input)?;
 45 |     let root_meta = InodeMetadata::from(&root_meta);
 46 | 
 47 |     let stat = {
 48 |         let progress = ProgressBar::new_spinner();
 49 |         let mut stat = Stats::default();
 50 |         traverse_stats(&cli.input, &mut stat, &progress)?;
 51 |         progress.finish();
 52 |         stat
 53 |     };
 54 | 
 55 |     let compress = match (cli.zstd, cli.lzma) {
 56 |         (None, None) => CompressParam::None,
 57 |         (Some(zstd), None) => CompressParam::Zstd(zstd),
 58 |         (None, Some(lzma)) => CompressParam::Lzma(lzma),
 59 |         _ => unreachable!(),
 60 |     };
 61 |     eprintln!("using compression: {compress:?}");
 62 | 
 63 |     let pb_in_bytes = ProgressBar::new(stat.total_bytes).with_style(
 64 |         ProgressStyle::with_template(
 65 |             "input : {binary_bytes}/{binary_total_bytes} ({binary_bytes_per_sec}) {wide_bar}",
 66 |         )
 67 |         .unwrap(),
 68 |     );
 69 |     let pb_out_bytes = ProgressBar::no_length()
 70 |         .with_style(ProgressStyle::with_template("output: {binary_bytes} {spinner}").unwrap());
 71 |     let fout_pb = pb_out_bytes.wrap_write(&fout);
 72 | 
 73 |     let pbs = MultiProgress::new();
 74 |     pbs.add(pb_in_bytes.clone());
 75 |     pbs.add(pb_out_bytes.clone());
 76 | 
 77 |     // Make bars visible now, or there would be a delay on the second bar,
 78 |     // because block compression takes quite some time to finish.
 79 |     pb_in_bytes.tick();
 80 |     pb_out_bytes.tick();
 81 | 
 82 |     let mut builder = MetadataBuilder::new(&root_meta);
 83 |     let writer = section::Writer::new(fout_pb)?;
 84 |     let chunker = chunker::BasicChunker::new(writer, builder.block_size(), compress);
 85 |     let mut chunker = chunker::CdcChunker::new(chunker);
 86 | 
 87 |     build_archive(&mut builder, &mut chunker, &cli.input, &pb_in_bytes)?;
 88 | 
 89 |     pb_in_bytes.finish();
 90 |     pbs.println(format!(
 91 |         "deduplicated {}",
 92 |         HumanBytes(chunker.deduplicated_bytes()),
 93 |     ))?;
 94 | 
 95 |     pbs.println("finalizing metadata")?;
 96 |     let mut w = chunker.finish()?;
 97 |     w.write_metadata_sections(&builder.finish()?, compress)?;
 98 | 
 99 |     pbs.println("waiting for compression to finish")?;
100 |     w.finish()?;
101 |     pb_out_bytes.finish();
102 | 
103 |     let output_len = fout.metadata()?.len();
104 | 
105 |     let elapsed = inst.elapsed();
106 |     eprintln!(
107 |         "completed in {:?}, with compression ratio {:.2}%",
108 |         elapsed,
109 |         output_len as f32 / stat.total_bytes as f32 * 100.0,
110 |     );
111 | 
112 |     Ok(())
113 | }
114 | 
115 | #[derive(Debug, Default)]
116 | struct Stats {
117 |     files: u64,
118 |     total_bytes: u64,
119 | }
120 | 
121 | fn traverse_stats(
122 |     root_path: &Path,
123 |     stat: &mut Stats,
124 |     progress: &ProgressBar,
125 | ) -> std::io::Result<()> {
126 |     for ent in fs::read_dir(root_path)? {
127 |         let ent = ent?;
128 |         let ft = ent.file_type()?;
129 |         if ft.is_dir() {
130 |             traverse_stats(&ent.path(), stat, progress)?;
131 |         } else if ft.is_file() {
132 |             stat.files += 1;
133 |             stat.total_bytes += fs::symlink_metadata(ent.path())?.len();
134 | 
135 |             if stat.files % 1024 == 0 {
136 |                 progress.set_message(format!(
137 |                     "found {} files, total {}",
138 |                     HumanCount(stat.files),
139 |                     HumanBytes(stat.total_bytes),
140 |                 ));
141 |             }
142 |         }
143 |     }
144 |     Ok(())
145 | }
146 | 
147 | fn build_archive(
148 |     meta_builder: &mut MetadataBuilder,
149 |     chunker: &mut dyn Chunker,
150 |     root_path: &Path,
151 |     pb_in_bytes: &ProgressBar,
152 | ) -> dwarfs_enc::Result<()> {
153 |     let mut stack = Vec::new();
154 |     stack.push((
155 |         meta_builder.root(),
156 |         root_path.to_owned(),
157 |         fs::read_dir(root_path)?,
158 |     ));
159 | 
160 |     while let Some(&mut (dir, ref dir_path, ref mut iter)) = stack.last_mut() {
161 |         let Some(ent) = iter.next().transpose()? else {
162 |             stack.pop();
163 |             continue;
164 |         };
165 | 
166 |         let name = ent.file_name();
167 |         let name_str = name.to_string_lossy();
168 |         if matches!(name_str, Cow::Owned(_)) {
169 |             eprintln!("normalized non-UTF-8 name: {name:?} -> {name_str:?}");
170 |         }
171 |         let subpath = dir_path.join(&name);
172 | 
173 |         let ft = ent.file_type()?;
174 |         let os_meta = ent.metadata()?;
175 |         let inode_meta = InodeMetadata::from(&os_meta);
176 | 
177 |         if ft.is_dir() {
178 |             let subdir = meta_builder.put_dir(dir, &name_str, &inode_meta)?;
179 |             let subiter = fs::read_dir(&subpath)?;
180 |             stack.push((subdir, subpath, subiter));
181 |         } else if ft.is_file() {
182 |             let os_file = fs::File::open(&subpath)?;
183 |             let chunks = chunker.put_reader(&mut pb_in_bytes.wrap_read(os_file))?;
184 |             meta_builder.put_file(dir, &name_str, &inode_meta, chunks)?;
185 |         } else if ft.is_symlink() {
186 |             let target = fs::read_link(&subpath)?;
187 |             let target_str = target.to_string_lossy();
188 |             if matches!(target_str, Cow::Owned(_)) {
189 |                 eprintln!("normalized non-UTF-8 symlink target: {target:?} -> {target_str:?}");
190 |             }
191 |             meta_builder.put_symlink(dir, &name_str, &inode_meta, &target_str)?;
192 |         } else {
193 |             eprintln!(
194 |                 "ignore unsupported file type {:?} for path: {}",
195 |                 ft,
196 |                 subpath.display(),
197 |             );
198 |         }
199 |     }
200 |     Ok(())
201 | }
202 | 
--------------------------------------------------------------------------------
/dwarfs-enc/src/chunker.rs:
--------------------------------------------------------------------------------
  1 | //! File data slicing and/or deduplication.
  2 | use std::{
  3 |     collections::{HashMap, hash_map::Entry},
  4 |     fmt,
  5 |     io::{Read, Write},
  6 |     num::NonZero,
  7 | };
  8 | 
  9 | use dwarfs::section::SectionType;
 10 | use rustic_cdc::{Rabin64, RollingHash64};
 11 | use sha2::{Digest, Sha512_256};
 12 | 
 13 | use crate::{
 14 |     Error, Result,
 15 |     metadata::Chunk,
 16 |     section::{self, CompressParam},
 17 | };
 18 | 
 19 | type Chunks = Vec;
 20 | 
 21 | /// Algorithm to slice and/or deduplicate file content.
 22 | pub trait Chunker {
 23 |     /// Put data via a [`Read`] instance into the archive, and return the
 24 |     /// chunking result ready for [`crate::metadata::Builder::put_file`].
 25 |     fn put_reader(&mut self, rdr: &mut dyn Read) -> Result;
 26 | 
 27 |     /// Put in-memory data into the archive.
 28 |     ///
 29 |     /// This is a shortcut to [`Chunker::put_reader`].
 30 |     fn put_bytes(&mut self, mut bytes: &[u8]) -> Result {
 31 |         self.put_reader(&mut bytes)
 32 |     }
 33 | }
 34 | 
 35 | /// The simplest chunker to concat all files and slice data at block size.
 36 | ///
 37 | /// This does no deduplication.
 38 | pub struct BasicChunker {
 39 |     buf: Box<[u8]>,
 40 |     buf_len: usize,
 41 |     compression: CompressParam,
 42 |     w: section::Writer,
 43 | }
 44 | 
 45 | impl fmt::Debug for BasicChunker {
 46 |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 47 |         f.debug_struct("BasicChunker")
 48 |             .field("buf", &format_args!("{}/{}", self.buf_len, self.buf.len()))
 49 |             .field("compression", &self.compression)
 50 |             .field("w", &self.w)
 51 |             .finish()
 52 |     }
 53 | }
 54 | 
 55 | impl BasicChunker {
 56 |     /// Create a basic chunker with given section writer and parameters.
 57 |     ///
 58 |     /// Note: `block_size` must match the block size configured for
 59 |     /// [`crate::metadata::Builder`]. You should always get it from
 60 |     /// [`crate::metadata::Builder::block_size`].
 61 |     pub fn new(
 62 |         w: section::Writer,
 63 |         block_size: NonZero,
 64 |         compression: CompressParam,
 65 |     ) -> Self {
 66 |         Self {
 67 |             buf: vec![0u8; block_size.get() as usize].into_boxed_slice(),
 68 |             buf_len: 0,
 69 |             compression,
 70 |             w,
 71 |         }
 72 |     }
 73 | 
 74 |     /// Finalize data chunks and get back the underlying section writer.
 75 |     pub fn finish(mut self) -> Result>
 76 |     where
 77 |         W: Write,
 78 |     {
 79 |         if self.buf_len != 0 {
 80 |             self.w.write_section(
 81 |                 SectionType::BLOCK,
 82 |                 self.compression,
 83 |                 &self.buf[..self.buf_len],
 84 |             )?;
 85 |             self.buf_len = 0;
 86 |         }
 87 |         Ok(self.w)
 88 |     }
 89 | 
 90 |     fn put_reader_inner(&mut self, rdr: &mut dyn Read) -> Result
 91 |     where
 92 |         W: Write,
 93 |     {
 94 |         let mut chunks = SeqChunks {
 95 |             start_section_idx: self.w.section_count(),
 96 |             start_offset: self.buf_len as u32,
 97 |             len: 0,
 98 |         };
 99 |         loop {
100 |             while self.buf_len < self.buf.len() {
101 |                 match rdr.read(&mut self.buf[self.buf_len..]) {
102 |                     Ok(0) => return Ok(chunks),
103 |                     Ok(n) => {
104 |                         self.buf_len += n;
105 |                         chunks.len += n as u64;
106 |                     }
107 |                     Err(err) if err.kind() == std::io::ErrorKind::Interrupted => continue,
108 |                     Err(err) => return Err(err.into()),
109 |                 }
110 |             }
111 | 
112 |             debug_assert_eq!(self.buf_len, self.buf.len());
113 |             self.w
114 |                 .write_section(SectionType::BLOCK, self.compression, &self.buf)?;
115 |             self.buf_len = 0;
116 |         }
117 |     }
118 | }
119 | 
120 | #[derive(Debug, Clone, Copy)]
121 | struct SeqChunks {
122 |     start_section_idx: u32,
123 |     start_offset: u32,
124 |     len: u64,
125 | }
126 | 
127 | impl SeqChunks {
128 |     fn to_chunks(mut self, block_size: u32) -> impl Iterator-  {
129 |         std::iter::from_fn(move || {
130 |             let rest_len = block_size - self.start_offset;
131 |             if self.len == 0 {
132 |                 None
133 |             } else if self.len <= u64::from(rest_len) {
134 |                 let c = Chunk {
135 |                     section_idx: self.start_section_idx,
136 |                     offset: self.start_offset,
137 |                     size: self.len as u32,
138 |                 };
139 |                 self.len = 0;
140 |                 Some(c)
141 |             } else {
142 |                 let c = Chunk {
143 |                     section_idx: self.start_section_idx,
144 |                     offset: self.start_offset,
145 |                     size: rest_len,
146 |                 };
147 |                 self.len -= u64::from(rest_len);
148 |                 self.start_section_idx += 1;
149 |                 self.start_offset = 0;
150 |                 Some(c)
151 |             }
152 |         })
153 |     }
154 | }
155 | 
156 | impl Chunker for BasicChunker {
157 |     fn put_reader(&mut self, rdr: &mut dyn Read) -> Result {
158 |         let seq = self.put_reader_inner(rdr)?;
159 |         Ok(seq.to_chunks(self.buf.len() as u32).collect())
160 |     }
161 | }
162 | 
163 | /// The deduplicating chunker using Content Defined Chunking (CDC).
164 | ///
165 | /// The exact algorithm used may change. Currently it uses [rustic_cdc].
166 | pub struct CdcChunker {
167 |     inner: BasicChunker,
168 |     // TODO: This struct is too large.
169 |     rabin: Rabin64,
170 |     chunk_buf: Box<[u8]>,
171 | 
172 |     table: HashMap,
173 |     deduplicated_bytes: u64,
174 | }
175 | 
176 | impl fmt::Debug for CdcChunker {
177 |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 |         f.debug_struct("CdcChunker")
179 |             .field("inner", &self.inner)
180 |             .field("table_size", &self.table.len())
181 |             .field("deduplicated_bytes", &self.deduplicated_bytes)
182 |             .finish_non_exhaustive()
183 |     }
184 | }
185 | 
186 | struct CdcChunk {
187 |     sha256_suffix: [u8; 24],
188 |     start_section_idx: u32,
189 |     start_offset: u32,
190 | }
191 | 
192 | impl CdcChunker {
193 |     const WINDOW_SIZE_BITS: u32 = 6;
194 |     const WINDOW_SIZE: usize = 1usize << Self::WINDOW_SIZE_BITS;
195 |     const CUT_MASK: u64 = (1u64 << 11) - 1;
196 |     const MIN_CHUNK_SIZE: usize = Self::WINDOW_SIZE;
197 |     const MAX_CHUNK_SIZE: usize = 64 << 10;
198 | 
199 |     /// Create the deduplicating chunker on top of a [`BasicChunker`].
200 |     pub fn new(inner: BasicChunker) -> Self {
201 |         let rabin = Rabin64::new(Self::WINDOW_SIZE_BITS);
202 |         CdcChunker {
203 |             inner,
204 |             rabin,
205 |             chunk_buf: vec![0u8; Self::MAX_CHUNK_SIZE].into_boxed_slice(),
206 |             table: HashMap::new(),
207 |             deduplicated_bytes: 0,
208 |         }
209 |     }
210 | 
211 |     /// Get the total deduplicated bytes.
212 |     pub fn deduplicated_bytes(&self) -> u64 {
213 |         self.deduplicated_bytes
214 |     }
215 | 
216 |     /// Finalize data chunks and get back the underlying section writer.
217 |     pub fn finish(self) -> Result>
218 |     where
219 |         W: Write,
220 |     {
221 |         self.inner.finish()
222 |     }
223 | }
224 | 
225 | impl Chunker for CdcChunker {
226 |     fn put_reader(&mut self, rdr: &mut dyn Read) -> Result {
227 |         let block_size = self.inner.buf.len() as u32;
228 | 
229 |         let mut chunks = Chunks::new();
230 |         let mut record_chunk = |cdchunk: &[u8]| {
231 |             debug_assert_ne!(cdchunk.len(), 0);
232 | 
233 |             let hash = Sha512_256::new_with_prefix(cdchunk).finalize();
234 |             let (&hash_prefix, hash_suffix) = hash.split_first_chunk::<8>().expect("hash is 32B");
235 |             let hash_suffix: [u8; 24] = hash_suffix.try_into().expect("hash is 32B");
236 | 
237 |             let seq = match self.table.entry(u64::from_ne_bytes(hash_prefix)) {
238 |                 Entry::Vacant(ent) => {
239 |                     let seq = self.inner.put_reader_inner(&mut { cdchunk })?;
240 |                     ent.insert(CdcChunk {
241 |                         sha256_suffix: hash_suffix,
242 |                         start_section_idx: seq.start_section_idx,
243 |                         start_offset: seq.start_offset,
244 |                     });
245 |                     seq
246 |                 }
247 |                 Entry::Occupied(ent) if ent.get().sha256_suffix == hash_suffix => {
248 |                     self.deduplicated_bytes += cdchunk.len() as u64;
249 |                     SeqChunks {
250 |                         start_section_idx: ent.get().start_section_idx,
251 |                         start_offset: ent.get().start_offset,
252 |                         len: cdchunk.len() as u64,
253 |                     }
254 |                 }
255 |                 // Hash prefix collision.
256 |                 Entry::Occupied(_) => self.inner.put_reader_inner(&mut { cdchunk })?,
257 |             };
258 | 
259 |             // Merge chunks if possible.
260 |             for c in seq.to_chunks(block_size) {
261 |                 if let Some(p) = chunks
262 |                     .last_mut()
263 |                     .filter(|p| (p.section_idx, p.offset + p.size) == (c.section_idx, c.offset))
264 |                 {
265 |                     p.size += c.size;
266 |                 } else {
267 |                     chunks.push(c);
268 |                 }
269 |             }
270 | 
271 |             Ok::<_, Error>(())
272 |         };
273 | 
274 |         self.rabin.reset();
275 | 
276 |         // |               chunk_buf                            |
277 |         // | ...chunk | chunk | partial chunk | next read | ... |
278 |         //                    ^cut_pos        ^end_pos
279 |         //                                     ~~~~~~~~~~~ read_len
280 |         let mut cut_pos = 0usize;
281 |         let mut end_pos = 0usize;
282 |         loop {
283 |             assert_ne!(end_pos, self.chunk_buf.len());
284 |             let read_len = match rdr.read(&mut self.chunk_buf[end_pos..]) {
285 |                 Ok(0) => break,
286 |                 Ok(n) => n,
287 |                 Err(err) if err.kind() == std::io::ErrorKind::Interrupted => continue,
288 |                 Err(err) => return Err(err.into()),
289 |             };
290 | 
291 |             for (&b, pos) in self.chunk_buf[end_pos..end_pos + read_len]
292 |                 .iter()
293 |                 .zip(end_pos..)
294 |             {
295 |                 self.rabin.slide(b);
296 |                 // This is the length of the whole chunk, including previous partial data.
297 |                 // NB. the current byte at `pos` is included, hereby `+1`.
298 |                 let len = pos - cut_pos + 1;
299 | 
300 |                 // The `MIN_CHUNK_SIZE` guarantees the sliding window is always filled.
301 |                 if len >= Self::MIN_CHUNK_SIZE && self.rabin.hash & Self::CUT_MASK == Self::CUT_MASK
302 |                     || len >= Self::MAX_CHUNK_SIZE
303 |                 {
304 |                     let cdchunk = &self.chunk_buf[cut_pos..pos];
305 |                     cut_pos = pos;
306 |                     record_chunk(cdchunk)?;
307 |                 }
308 |             }
309 |             end_pos += read_len;
310 | 
311 |             // Shift-down the last partial chunk if we reached the end of buffer.
312 |             // For files smaller than `MAX_CHUNK_SIZE`, this path is never entered.
313 |             if end_pos >= self.chunk_buf.len() {
314 |                 debug_assert_eq!(end_pos, self.chunk_buf.len());
315 |                 self.chunk_buf.copy_within(cut_pos.., 0);
316 |                 end_pos -= cut_pos;
317 |                 cut_pos = 0;
318 |             }
319 |         }
320 | 
321 |         if cut_pos < end_pos {
322 |             record_chunk(&self.chunk_buf[cut_pos..end_pos])?;
323 |         }
324 | 
325 |         Ok(chunks)
326 |     }
327 | }
328 | 
--------------------------------------------------------------------------------
/dwarfs-enc/src/error.rs:
--------------------------------------------------------------------------------
 1 | use std::fmt;
 2 | 
 3 | /// A `Result` with default error [`Error`].
 4 | pub type Result = std::result::Result;
 5 | 
 6 | /// An error representing any possible error raised from this crate.
 7 | pub struct Error(Box);
 8 | 
 9 | #[derive(Debug)]
10 | #[cfg_attr(not(feature = "default"), allow(dead_code))]
11 | pub(crate) enum ErrorInner {
12 |     Limit(&'static str),
13 |     SerializeMetadata(dwarfs::metadata::Error),
14 |     DuplicatedEntry,
15 |     Compress(std::io::Error),
16 | 
17 |     Io(std::io::Error),
18 | }
19 | 
20 | impl fmt::Debug for Error {
21 |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 |         self.0.fmt(f)
23 |     }
24 | }
25 | 
26 | impl fmt::Display for Error {
27 |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 |         match &*self.0 {
29 |             ErrorInner::DuplicatedEntry => f.pad("duplicated entry names in a directory"),
30 |             ErrorInner::Limit(msg) => write!(f, "{msg}"),
31 |             ErrorInner::SerializeMetadata(err) => err.fmt(f),
32 |             ErrorInner::Compress(err) | ErrorInner::Io(err) => err.fmt(f),
33 |         }
34 |     }
35 | }
36 | 
37 | impl std::error::Error for Error {
38 |     fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
39 |         match &*self.0 {
40 |             ErrorInner::Compress(err) | ErrorInner::Io(err) => Some(err),
41 |             ErrorInner::SerializeMetadata(err) => Some(err),
42 |             _ => None,
43 |         }
44 |     }
45 | }
46 | 
47 | impl From for Error {
48 |     #[cold]
49 |     fn from(err: ErrorInner) -> Self {
50 |         Self(Box::new(err))
51 |     }
52 | }
53 | 
54 | impl From for Error {
55 |     #[cold]
56 |     fn from(err: std::io::Error) -> Self {
57 |         Self(Box::new(ErrorInner::Io(err)))
58 |     }
59 | }
60 | 
61 | impl From for Error {
62 |     #[cold]
63 |     fn from(err: dwarfs::metadata::Error) -> Self {
64 |         Self(Box::new(ErrorInner::SerializeMetadata(err)))
65 |     }
66 | }
67 | 
--------------------------------------------------------------------------------
/dwarfs-enc/src/lib.rs:
--------------------------------------------------------------------------------
 1 | //! A library for writing [DwarFS][dwarfs] archives (aka. images),
 2 | //! building on top of [`dwarfs` crate][::dwarfs].
 3 | //!
 4 | //! For reading archives only, check [`dwarfs` crate][::dwarfs] instead.
 5 | //!
 6 | //! [dwarfs]: https://github.com/mhx/dwarfs
 7 | //!
 8 | //! Currently, this crate writes DwarFS archive with filesystem version v2.5,
 9 | //! which should be compatible with upstream dwarfs v0.7.0..=v0.12.4 (latest at
10 | //! the time of writing).
11 | //!
12 | //! ## Examples
13 | //!
14 | //! ```
15 | //! use dwarfs_enc::{
16 | //!     chunker::{Chunker, BasicChunker, CdcChunker},
17 | //!     metadata::{Builder as MetaBuilder, InodeMetadata},
18 | //!     section::{Writer as SectionWriter, CompressParam},
19 | //! };
20 | //! use std::{fs, time::SystemTime};
21 | //!
22 | //! # fn work() -> dwarfs_enc::Result<()> {
23 | //! let f = fs::File::create("out.dwarfs")?;
24 | //!
25 | //! // Create inode metadata.
26 | //! let mut dir_meta = InodeMetadata::new(0o755);
27 | //! dir_meta.uid(1000).gid(1000).atime(SystemTime::now());
28 | //! // ... or initialize from OS metadata.
29 | //! let file_meta = InodeMetadata::from(&fs::metadata("./bar")?);
30 | //!
31 | //! // Create a hierarchy builder initialized with a root inode.
32 | //! let mut meta = MetaBuilder::new(&dir_meta);
33 | //!
34 | //! // Use ZSTD compression level 22, Content Defined Chunking (CDC) for deduplication.
35 | //! let compress = CompressParam::Zstd(22);
36 | //! let writer = SectionWriter::new(f)?;
37 | //! let chunker = BasicChunker::new(writer, meta.block_size(), compress);
38 | //! let mut chunker = CdcChunker::new(chunker);
39 | //!
40 | //! // Put a directories and a symlink.
41 | //! let root = meta.root();
42 | //! let subdir = meta.put_dir(root, "subdir", &dir_meta)?;
43 | //! meta.put_symlink(subdir, "symlink", &file_meta, "./subdir")?;
44 | //!
45 | //! // Put a regular file, using in-memory data.
46 | //! meta.put_file(root, "foo", &file_meta, chunker.put_bytes(b"hello world")?)?;
47 | //! // Put a regular file, reading from an OS File.
48 | //! let chunks = chunker.put_reader(&mut fs::File::open("bar")?)?;
49 | //! let bar = meta.put_file(root, "bar", &file_meta, chunks)?;
50 | //!
51 | //! // Hard links are also supported.
52 | //! meta.put_hard_link(root, "hardlink", bar)?;
53 | //!
54 | //! // Finalizing data chunks, metadata, and section writer in order.
55 | //! let mut writer = chunker.finish()?;
56 | //! writer.write_metadata_sections(&meta.finish()?, compress)?;
57 | //! writer.finish()?;
58 | //!
59 | //! # Ok(()) }
60 | //! ```
61 | //!
62 | //! See also the simple `mkdwarfs` impl at `./examples/mkdwarfs.rs`.
63 | //!
64 | //! ## Cargo features
65 | //!
66 | //! - `zstd`, `lzma` *(Only `zstd` is enabled by default)*
67 | //!
68 | //!   Enable relevant compression algorithm support. `zstd` is the default
69 | //!   compression algorithm `mkdwarfs` uses and it should be enough for most cases.
70 | #![cfg_attr(docsrs, feature(doc_auto_cfg))]
71 | #![forbid(unsafe_code)]
72 | #![warn(missing_debug_implementations)]
73 | #![warn(missing_docs)]
74 | mod error;
75 | 
76 | pub mod chunker;
77 | pub mod metadata;
78 | mod ordered_parallel;
79 | pub mod section;
80 | 
81 | use self::error::ErrorInner;
82 | pub use self::error::{Error, Result};
83 | 
--------------------------------------------------------------------------------
/dwarfs-enc/src/metadata.rs:
--------------------------------------------------------------------------------
  1 | //! DwarFS archive hierarchy builder.
  2 | //!
  3 | //! This module provides [`Builder`] to build [`dwarfs::metadata::Metadata`] of
  4 | //! a DwarFS archive, which is the spine structure for directory hierarchy and
  5 | //! file chunks information.
  6 | //!
  7 | //! ## Limitations
  8 | //!
  9 | //! Due to implementation limitations, the `Metadata` structure cannot exceeds
 10 | //! 2³² bytes. This also implies lengths of all substructures, eg. number of
 11 | //! files, directories, chunks and etc, must also not exceed 2³².
 12 | //!
 13 | //! Note that this limitation only applies to `Metadata` itself, not file
 14 | //! (chunk) data. The total length of chunks is not limited, as long as it
 15 | //! is addressable. Eg. It's possible to have 2²⁰ files each consists of 2²⁰
 16 | //! chunks of 2²⁰ bytes without any issue.
 17 | use std::{
 18 |     borrow::Cow,
 19 |     hash::{Hash, Hasher},
 20 |     num::NonZero,
 21 |     time::{Duration, SystemTime},
 22 | };
 23 | 
 24 | use dwarfs::metadata;
 25 | use indexmap::IndexSet;
 26 | 
 27 | use crate::{Error, ErrorInner, Result};
 28 | 
 29 | // These values are stored on disk, thus should be platform-agnostic.
 30 | // But `rustix` does not expose them on non-UNIX platforms yet.
 31 | // TODO: Maybe define them in `dwarfs`?
 32 | // From: 
 33 | const S_IFSOCK: u32 = 0o0140000;
 34 | const S_IFLNK: u32 = 0o0120000;
 35 | const S_IFREG: u32 = 0o0100000;
 36 | const S_IFBLK: u32 = 0o0060000;
 37 | const S_IFDIR: u32 = 0o0040000;
 38 | const S_IFCHR: u32 = 0o0020000;
 39 | const S_IFIFO: u32 = 0o0010000;
 40 | 
 41 | /// Metadata construction configurations.
 42 | #[derive(Debug, Clone)]
 43 | pub struct Config {
 44 |     block_size: NonZero,
 45 |     mtime_only: bool,
 46 |     time_resolution_sec: NonZero,
 47 |     source_date_epoch: u64,
 48 |     creator: Option>,
 49 |     created_timestamp: Option,
 50 | }
 51 | 
 52 | impl Default for Config {
 53 |     fn default() -> Self {
 54 |         Self {
 55 |             block_size: NonZero::new(16 << 20).expect("not zero"),
 56 |             mtime_only: false,
 57 |             time_resolution_sec: NonZero::new(1).expect("not zero"),
 58 |             source_date_epoch: u64::MAX,
 59 |             creator: Some(Cow::Borrowed(Self::DEFAULT_CREATOR_VERSION)),
 60 |             created_timestamp: None,
 61 |         }
 62 |     }
 63 | }
 64 | 
 65 | impl Config {
 66 |     const DEFAULT_CREATOR_VERSION: &str =
 67 |         concat!(env!("CARGO_PKG_NAME"), " ", env!("CARGO_PKG_VERSION"));
 68 | 
 69 |     /// Set the block size of this archive.
 70 |     ///
 71 |     /// Default value is 16MiB.
 72 |     ///
 73 |     /// Each [`BLOCK` section][dwarfs::section::SectionType::BLOCK] must have
 74 |     /// this size (before compression) except for the last one.
 75 |     ///
 76 |     /// # Panics
 77 |     ///
 78 |     /// Panics if `bytes` is not a power of two.
 79 |     pub fn block_size(&mut self, bytes: NonZero) -> &mut Self {
 80 |         assert!(bytes.is_power_of_two());
 81 |         self.block_size = bytes;
 82 |         self
 83 |     }
 84 | 
 85 |     /// Only store file modification time (mtime) and ignore access (atime) or
 86 |     /// change (ctime) times.
 87 |     ///
 88 |     /// Default value is `false`.
 89 |     ///
 90 |     /// This will cause all access and change times to be ignored, and will set
 91 |     /// a flag in metadata informing their unavailability.
 92 |     pub fn mtime_only(&mut self, yes: bool) -> &mut Self {
 93 |         self.mtime_only = yes;
 94 |         self
 95 |     }
 96 | 
 97 |     /// Set the minimum resolution of all file times.
 98 |     ///
 99 |     /// Default value is 1 second, which is also the minimal possible value.
100 |     ///
101 |     /// A non-one resolution will cause all file times to be truncated to the
102 |     /// max multiples of the resolution not-greater than the original value.
103 |     pub fn time_resolution_sec(&mut self, sec: NonZero) -> &mut Self {
104 |         self.time_resolution_sec = sec;
105 |         self
106 |     }
107 | 
108 |     /// Set the [`SOURCE_DATE_EPOCH`](https://reproducible-builds.org/specs/source-date-epoch/)
109 |     /// which clamps all timestamps after it to it.
110 |     pub fn source_date_epoch(&mut self, timestamp: u64) -> &mut Self {
111 |         self.source_date_epoch = timestamp;
112 |         self.clamp_timestamp();
113 |         self
114 |     }
115 | 
116 |     /// Set a custom string indicating the name and version of the creator program.
117 |     ///
118 |     /// Default value is
119 |     #[doc = concat!("`\"", env!("CARGO_PKG_NAME"), " ", env!("CARGO_PKG_VERSION"), "\"`.")]
120 |     pub fn creator(&mut self, info: impl Into