d02e545d Andy McCall 1 month ago ↶
1
2
3
4
5
6
7
8
9
ff0af1ee unknown 1 month ago
10
5b9c6ad4 Andy McCall 1 month ago ↶
11
use crate :: attest:: Attest;
ff0af1ee unknown 1 month ago
12
use crate :: hash:: { hash_object, header, ObjectRef, ObjectType} ;
1983686f Andy McCall 1 month ago ↶
13
use crate :: identity:: { Identity, Signature} ;
6f8aeb99 Andy McCall 1 month ago ↶
14
use crate :: objects:: { Blob, Change, ObjectError, StateObject} ;
fc1a466a Andy McCall 1 month ago ↶
15
use crate :: receipt:: Receipt;
ff0af1ee unknown 1 month ago
16
use rand:: RngCore;
d02e545d Andy McCall 1 month ago ↶
17
use std:: cell:: RefCell;
ff0af1ee unknown 1 month ago
18
use std:: fs;
d02e545d Andy McCall 1 month ago ↶
19
use std:: io:: { self , Read, Seek, SeekFrom, Write} ;
ff0af1ee unknown 1 month ago
20
use std:: path:: { Path, PathBuf} ;
21
d02e545d Andy McCall 1 month ago ↶
22
const PACK_MAGIC : & [ u8 ; 8 ] = b " OXYPACK1" ;
23
const IDX_MAGIC : & [ u8 ; 8 ] = b " OXYIDX01" ;
24
const IDX_ENTRY : usize = 32 + 8 + 4 + 4 + 1 + 1 ;
25
const FLAG_ZSTD : u8 = 1 ;
26
const ZSTD_LEVEL : i32 = 3 ;
2b872d07 Andy McCall 1 month ago ↶
27
28
29
const MAX_OBJECT_LEN : u32 = 1 < < 30 ;
d02e545d Andy McCall 1 month ago ↶
30
afaba5f1 Andy McCall 1 month ago ↶
31
32
pub type Object = ( ObjectType, Vec< u8 > ) ;
33
d02e545d Andy McCall 1 month ago ↶
34
# [ derive ( Clone, Copy, Debug ) ]
35
struct PackEntry {
36
offset : u64 ,
37
stored_len : u32 ,
38
raw_len : u32 ,
39
ty : ObjectType,
40
flags : u8 ,
41
}
42
43
44
# [ derive ( Debug ) ]
45
struct PackIndex {
46
path : PathBuf,
47
refs : Vec< ObjectRef> ,
48
entries : Vec< PackEntry> ,
49
}
50
51
impl PackIndex {
52
fn load ( idx_path : & Path, pack_path : & Path) -> Result< PackIndex> {
53
let data = fs:: read( idx_path) ? ;
75167672 Andy McCall 1 month ago
54
let bad =
55
| m : & str | StoreError:: Corrupt( ObjectRef:: ZERO , format! ( " {} : {m} " , idx_path.display ( ) ) ) ;
d02e545d Andy McCall 1 month ago ↶
56
if data.len ( ) < 12 | | & data[ .. 8 ] ! = IDX_MAGIC {
57
return Err ( bad ( " bad index header" ) ) ;
58
}
59
let count = u32 :: from_le_bytes( data[ 8 .. 12 ] .try_into ( ) .unwrap ( ) ) as usize ;
60
if data.len ( ) ! = 12 + count * IDX_ENTRY {
61
return Err ( bad ( " index length mismatch" ) ) ;
62
}
2b872d07 Andy McCall 1 month ago ↶
63
let pack_len = fs:: metadata( pack_path) ? .len ( ) ;
d02e545d Andy McCall 1 month ago ↶
64
let mut refs = Vec :: with_capacity( count) ;
65
let mut entries = Vec :: with_capacity( count) ;
66
for i in 0 .. count {
67
let e = & data[ 12 + i * IDX_ENTRY .. 12 + ( i + 1 ) * IDX_ENTRY ] ;
68
let r = ObjectRef:: from_slice( & e[ .. 32 ] ) .unwrap ( ) ;
69
if let Some ( prev) = refs.last ( ) {
70
if r < = * prev {
71
return Err ( bad ( " index not sorted" ) ) ;
72
}
73
}
74
refs.push ( r) ;
2b872d07 Andy McCall 1 month ago ↶
75
let entry = PackEntry {
d02e545d Andy McCall 1 month ago ↶
76
offset: u64 :: from_le_bytes( e[ 32 .. 40 ] .try_into ( ) .unwrap ( ) ) ,
77
stored_len: u32 :: from_le_bytes( e[ 40 .. 44 ] .try_into ( ) .unwrap ( ) ) ,
78
raw_len: u32 :: from_le_bytes( e[ 44 .. 48 ] .try_into ( ) .unwrap ( ) ) ,
75167672 Andy McCall 1 month ago
79
ty: ObjectType:: ALL
80
.get ( e[ 48 ] as usize )
81
.copied ( )
82
.ok_or_else ( | | bad ( " unknown object type" ) ) ? ,
d02e545d Andy McCall 1 month ago ↶
83
flags: e[ 49 ] ,
2b872d07 Andy McCall 1 month ago ↶
84
} ;
a9f686ed Andy McCall 1 month ago ↶
85
if entry.stored_len > MAX_OBJECT_LEN | | entry.raw_len > MAX_OBJECT_LEN {
86
return Err ( StoreError:: Corrupt(
87
r,
88
format! (
89
" {} : the index claims an object of {} bytes ({} stored), more than the {MAX_OBJECT_LEN} an object may be" ,
90
idx_path.display ( ) ,
91
entry.raw_len,
92
entry.stored_len
93
) ,
94
) ) ;
95
}
96
if entry.offset.saturating_add ( entry.stored_len as u64 ) > pack_len {
2b872d07 Andy McCall 1 month ago ↶
97
return Err ( bad ( " index entry points outside the pack" ) ) ;
98
}
99
entries.push ( entry) ;
d02e545d Andy McCall 1 month ago ↶
100
}
75167672 Andy McCall 1 month ago
101
Ok ( PackIndex {
102
path: pack_path.to_path_buf ( ) ,
103
refs,
104
entries,
105
} )
d02e545d Andy McCall 1 month ago ↶
106
}
107
108
fn find ( & self , r : & ObjectRef) -> Option< PackEntry> {
109
self .refs.binary_search ( r) .ok ( ) .map ( | i | self .entries[ i] )
110
}
111
112
fn read ( & self , r : & ObjectRef, e : PackEntry) -> Result< ( ObjectType, Vec< u8 > ) > {
113
let mut f = fs:: File:: open( & self .path) ? ;
114
f.seek ( SeekFrom:: Start( e.offset) ) ? ;
115
let mut stored = vec! [ 0 u8 ; e.stored_len as usize ] ;
116
f.read_exact ( & mut stored) ? ;
117
let body = if e.flags & FLAG_ZSTD ! = 0 {
75167672 Andy McCall 1 month ago
118
zstd:: bulk:: decompress( & stored, e.raw_len as usize )
119
.map_err ( | err | StoreError:: Corrupt( * r, format! ( " zstd: {err} " ) ) ) ?
d02e545d Andy McCall 1 month ago ↶
120
} else {
121
stored
122
} ;
123
if body.len ( ) ! = e.raw_len as usize | | hash_object ( e.ty, & body) ! = * r {
75167672 Andy McCall 1 month ago
124
return Err ( StoreError:: Corrupt(
125
* r,
126
" packed object does not match its reference" .into ( ) ,
127
) ) ;
d02e545d Andy McCall 1 month ago ↶
128
}
129
Ok ( ( e.ty, body) )
130
}
131
}
132
133
fn type_index ( ty : ObjectType) -> u8 {
134
ObjectType:: ALL .iter ( ) .position ( | t | * t = = ty) .unwrap ( ) as u8
135
}
136
137
138
# [ derive ( Debug, Clone, Default, PartialEq, Eq ) ]
139
pub struct PackReport {
140
pub objects : usize ,
141
pub raw_bytes : u64 ,
142
pub packed_bytes : u64 ,
143
}
144
135212de Andy McCall 1 month ago ↶
145
146
pub const MAX_PACKS : usize = 16 ;
147
ba160f60 Andy McCall 1 month ago ↶
148
149
# [ derive ( Debug, Clone, Default, PartialEq, Eq ) ]
150
pub struct PruneReport {
151
pub objects : usize ,
152
pub bytes : u64 ,
153
154
pub deferred : usize ,
155
}
156
ff0af1ee unknown 1 month ago
157
# [ derive ( Debug, thiserror::Error ) ]
158
pub enum StoreError {
159
# [ error ( " io: {0}" ) ]
160
Io( # [ from ] io:: Error) ,
161
# [ error ( " corrupt object {0}: {1}" ) ]
162
Corrupt( ObjectRef, String ) ,
163
# [ error ( " object {0}" ) ]
164
Object( # [ from ] ObjectError) ,
165
# [ error ( " object {0} is a {1}, expected {2}" ) ]
166
WrongType( ObjectRef, & 'static str , & 'static str ) ,
167
# [ error ( " object {0} not found" ) ]
168
NotFound( ObjectRef) ,
6f8aeb99 Andy McCall 1 month ago ↶
169
# [ error ( " a {kind} object of {size} bytes is larger than the {limit} bytes an object may be; nothing was stored. A change this large adds or touches too many files at once: commit it in parts (a directory at a time)" ) ]
a9f686ed Andy McCall 1 month ago ↶
170
TooLarge {
171
kind: & 'static str ,
172
size: usize ,
173
limit: u32 ,
174
} ,
ff0af1ee unknown 1 month ago
175
}
176
177
pub type Result < T> = std:: result:: Result< T, StoreError> ;
178
d02e545d Andy McCall 1 month ago ↶
179
# [ derive ( Debug ) ]
ff0af1ee unknown 1 month ago
180
pub struct Store {
181
dir : PathBuf,
d02e545d Andy McCall 1 month ago ↶
182
packs : RefCell< Vec< PackIndex> > ,
8f10a07a Andy McCall 1 month ago ↶
183
184
185
pending : RefCell< Option< Pending> > ,
186
batch_depth : std:: cell:: Cell< u32 > ,
187
}
188
189
190
191
192
# [ derive ( Debug ) ]
193
struct Pending {
194
buffered : Vec< ( ObjectType, Vec< u8 > ) > ,
195
buffered_bytes : usize ,
196
lookup : std:: collections:: HashMap< ObjectRef, PendingEntry> ,
197
pack : Option< PendingPack> ,
198
report : PackReport,
ff0af1ee unknown 1 month ago
199
}
200
8f10a07a Andy McCall 1 month ago ↶
201
# [ derive ( Debug, Clone, Copy ) ]
202
enum PendingEntry {
203
Buffered( usize ) ,
204
Packed( PackEntry) ,
205
}
206
207
# [ derive ( Debug ) ]
208
struct PendingPack {
209
id : String,
210
tmp_path : PathBuf,
211
writer : io:: BufWriter< fs:: File> ,
212
offset : u64 ,
213
index : Vec< ( ObjectRef, PackEntry) > ,
214
}
215
216
const SPILL_OBJECTS : usize = 256 ;
217
const SPILL_BYTES : usize = 8 < < 20 ;
218
219
220
pub struct Batch < 'a > {
221
store : & 'a Store,
222
done : bool ,
223
}
224
225
impl Batch < '_ > {
226
pub fn finish ( mut self ) -> Result< ( ) > {
227
self .done = true ;
228
self .store.finish_batch ( )
229
}
230
}
231
232
impl Drop for Batch < '_ > {
233
fn drop ( & mut self ) {
234
if ! self .done {
235
let _ = self .store.finish_batch ( ) ;
236
}
237
}
238
}
239
192fad1e Andy McCall 1 month ago ↶
240
241
fn write_pack_entry (
242
pack : & mut PendingPack,
243
r : & ObjectRef,
244
ty : ObjectType,
245
raw_len : usize ,
246
flags : u8 ,
247
stored : & [u8 ],
248
report : & mut PackReport,
249
) -> Result< PackEntry> {
250
let header_len = 1 + 1 + 4 + 4 + 32 ;
251
pack.writer.write_all ( & [ type_index ( ty) , flags] ) ? ;
252
pack.writer
253
.write_all ( & ( stored.len ( ) as u32 ) .to_le_bytes ( ) ) ? ;
254
pack.writer.write_all ( & ( raw_len as u32 ) .to_le_bytes ( ) ) ? ;
255
pack.writer.write_all ( & r.0 ) ? ;
256
pack.writer.write_all ( stored) ? ;
257
let entry = PackEntry {
258
offset: pack.offset + header_len,
259
stored_len: stored.len ( ) as u32 ,
260
raw_len: raw_len as u32 ,
261
ty,
262
flags,
263
} ;
264
pack.offset + = header_len + stored.len ( ) as u64 ;
265
pack.index.push ( ( * r, entry) ) ;
266
report.objects + = 1 ;
267
report.raw_bytes + = raw_len as u64 ;
268
report.packed_bytes + = stored.len ( ) as u64 ;
269
Ok ( entry)
270
}
271
ff0af1ee unknown 1 month ago
272
impl Store {
273
pub fn open ( dir : impl Into< PathBuf> ) -> io:: Result< Store> {
274
let dir = dir.into ( ) ;
275
fs:: create_dir_all( dir.join ( " loose" ) ) ? ;
75167672 Andy McCall 1 month ago
276
let store = Store {
277
dir,
278
packs: RefCell:: new( Vec :: new( ) ) ,
279
pending: RefCell:: new( None ) ,
280
batch_depth: std:: cell:: Cell:: new( 0 ) ,
281
} ;
282
store
283
.reload_packs ( )
284
.map_err ( | e | io:: Error:: other( e.to_string ( ) ) ) ? ;
d02e545d Andy McCall 1 month ago ↶
285
Ok ( store)
286
}
287
288
fn reload_packs ( & self ) -> Result< ( ) > {
289
let mut packs = Vec :: new( ) ;
290
let mut names: Vec< PathBuf> = fs:: read_dir( & self .dir) ?
291
.filter_map ( | e | e.ok ( ) .map ( | e | e.path ( ) ) )
75167672 Andy McCall 1 month ago
292
.filter ( | p | {
293
p.extension ( ) .is_some_and ( | x | x = = " idx" )
294
& & p.file_name ( )
295
.is_some_and ( | n | n.to_string_lossy ( ) .starts_with ( " pack-" ) )
296
} )
d02e545d Andy McCall 1 month ago ↶
297
.collect ( ) ;
298
names.sort ( ) ;
299
for idx in names {
300
let pack = idx.with_extension ( " pack" ) ;
301
if pack.is_file ( ) {
302
packs.push ( PackIndex:: load( & idx, & pack) ? ) ;
303
}
304
}
305
* self .packs.borrow_mut ( ) = packs;
306
Ok ( ( ) )
307
}
308
309
pub fn pack_count ( & self ) -> usize {
310
self .packs.borrow ( ) .len ( )
311
}
312
313
fn find_packed ( & self , r : & ObjectRef) -> Option< ( usize , PackEntry) > {
75167672 Andy McCall 1 month ago
314
self .packs
315
.borrow ( )
316
.iter ( )
317
.enumerate ( )
318
.find_map ( | ( i , p ) | p.find ( r) .map ( | e | ( i, e) ) )
d02e545d Andy McCall 1 month ago ↶
319
}
320
321
322
fn loose_objects ( & self ) -> io:: Result< Vec< ( ObjectRef, PathBuf) > > {
323
let mut out = Vec :: new( ) ;
324
let loose = self .dir.join ( " loose" ) ;
325
for d in fs:: read_dir( & loose) ? {
326
let d = d? ;
327
if ! d.file_type ( ) ? .is_dir ( ) {
328
continue ;
329
}
330
let prefix = d.file_name ( ) .to_string_lossy ( ) .to_string ( ) ;
331
for f in fs:: read_dir( d.path ( ) ) ? {
332
let f = f? ;
333
let name = f.file_name ( ) .to_string_lossy ( ) .to_string ( ) ;
334
if let Some ( r) = ObjectRef:: from_hex( & format! ( " {prefix} {name} " ) ) {
335
out.push ( ( r, f.path ( ) ) ) ;
336
}
337
}
338
}
339
out.sort ( ) ;
340
Ok ( out)
341
}
342
343
pub fn loose_count ( & self ) -> io:: Result< usize > {
344
Ok ( self .loose_objects ( ) ? .len ( ) )
345
}
346
347
348
349
pub fn pack_loose ( & self ) -> Result< PackReport> {
350
let loose = self .loose_objects ( ) ? ;
75167672 Andy McCall 1 month ago
351
self .write_pack (
352
& loose.iter ( ) .map ( | ( r , _) | * r) .collect:: < Vec< _ > > ( ) ,
353
& loose.iter ( ) .map ( | ( _, p ) | p.clone ( ) ) .collect:: < Vec< _ > > ( ) ,
354
& [ ] ,
355
)
ba160f60 Andy McCall 1 month ago ↶
356
}
357
135212de Andy McCall 1 month ago ↶
358
359
360
pub fn merge_packs ( & self ) -> Result< usize > {
361
let ( refs, paths) : ( Vec< ObjectRef> , Vec< PathBuf> ) = {
362
let packs = self .packs.borrow ( ) ;
363
if packs.len ( ) < = MAX_PACKS {
364
return Ok ( 0 ) ;
365
}
75167672 Andy McCall 1 month ago
366
(
367
packs.iter ( ) .flat_map ( | p | p.refs.iter ( ) .copied ( ) ) .collect ( ) ,
368
packs.iter ( ) .map ( | p | p.path.clone ( ) ) .collect ( ) ,
369
)
135212de Andy McCall 1 month ago ↶
370
} ;
371
let n = paths.len ( ) ;
372
self .write_pack ( & refs, & [ ] , & paths) ? ;
373
Ok ( n)
374
}
375
ba160f60 Andy McCall 1 month ago ↶
376
377
378
379
380
75167672 Andy McCall 1 month ago
381
pub fn prune (
382
& self ,
383
keep : & std:: collections:: HashSet< ObjectRef> ,
384
grace : std:: time:: Duration,
385
) -> Result< PruneReport> {
ba160f60 Andy McCall 1 month ago ↶
386
let now = std:: time:: SystemTime:: now( ) ;
75167672 Andy McCall 1 month ago
387
let old_enough = | m : & fs:: Metadata| {
388
m.modified ( )
389
.map ( | t | now.duration_since ( t) .unwrap_or_default ( ) > = grace )
390
.unwrap_or ( true )
391
} ;
ba160f60 Andy McCall 1 month ago ↶
392
let mut report = PruneReport:: default( ) ;
393
394
for ( r, path) in self .loose_objects ( ) ? {
395
if keep.contains ( & r) {
396
continue ;
397
}
398
let meta = fs:: metadata( & path) ? ;
399
if ! old_enough ( & meta) {
400
report.deferred + = 1 ;
401
continue ;
402
}
403
report.bytes + = meta.len ( ) ;
404
report.objects + = 1 ;
405
fs:: remove_file( & path) ? ;
406
let _ = fs:: remove_dir( path.parent ( ) .unwrap ( ) ) ;
407
self .remove_sidecars ( & r) ? ;
408
}
409
410
let mut keep_refs: Vec< ObjectRef> = Vec :: new( ) ;
411
let mut old_paths: Vec< PathBuf> = Vec :: new( ) ;
412
let mut rewrite = false ;
413
for p in self .packs.borrow ( ) .iter ( ) {
414
let meta = fs:: metadata( & p.path) ? ;
415
let unreachable = p.refs.iter ( ) .filter ( | r | ! keep.contains ( r) ) .count ( ) ;
416
if unreachable = = 0 {
417
continue ;
418
}
419
if ! old_enough ( & meta) {
420
report.deferred + = unreachable;
421
continue ;
422
}
423
rewrite = true ;
424
old_paths.push ( p.path.clone ( ) ) ;
425
for ( r, e) in p.refs.iter ( ) .zip ( p.entries.iter ( ) ) {
426
if keep.contains ( r) {
427
keep_refs.push ( * r) ;
428
} else {
429
report.objects + = 1 ;
430
report.bytes + = e.stored_len as u64 ;
431
}
432
}
433
}
434
if rewrite {
435
let dropped: Vec< ObjectRef> = {
436
let packs = self .packs.borrow ( ) ;
75167672 Andy McCall 1 month ago
437
packs
438
.iter ( )
439
.filter ( | p | old_paths.contains ( & p.path) )
440
.flat_map ( | p | p.refs.iter ( ) .copied ( ) )
441
.filter ( | r | ! keep.contains ( r) )
442
.collect ( )
ba160f60 Andy McCall 1 month ago ↶
443
} ;
444
self .write_pack ( & keep_refs, & [ ] , & old_paths) ? ;
445
for r in dropped {
446
self .remove_sidecars ( & r) ? ;
447
}
448
}
449
Ok ( report)
450
}
451
452
fn remove_sidecars ( & self , r : & ObjectRef) -> Result< ( ) > {
fc1a466a Andy McCall 1 month ago ↶
453
let sidecars = [
454
self .sig_path ( r) ,
455
self .index_path ( " seals" , r) ,
456
self .index_path ( " receipts" , r) ,
457
] ;
458
for path in sidecars {
ba160f60 Andy McCall 1 month ago ↶
459
match fs:: remove_file( & path) {
460
Ok ( ( ) ) => {
461
let _ = fs:: remove_dir( path.parent ( ) .unwrap ( ) ) ;
462
}
463
Err ( e) if e.kind ( ) = = io:: ErrorKind:: NotFound => { }
464
Err ( e) => return Err ( e.into ( ) ) ,
465
}
466
}
467
Ok ( ( ) )
468
}
469
470
8f10a07a Andy McCall 1 month ago ↶
471
472
75167672 Andy McCall 1 month ago
473
fn finish_pack_files (
474
& self ,
475
id : & str ,
476
tmp_pack : & Path,
477
index : & [( ObjectRef , PackEntry ) ],
478
) -> Result< ( ) > {
8f10a07a Andy McCall 1 month ago ↶
479
let pack_path = self .dir.join ( format! ( " pack-{id} .pack" ) ) ;
480
let idx_path = self .dir.join ( format! ( " pack-{id} .idx" ) ) ;
481
let tmp_idx = self .dir.join ( format! ( " tmp-{id} .idx" ) ) ;
482
{
483
let mut f = io:: BufWriter:: new( fs:: File:: create( & tmp_idx) ? ) ;
484
f.write_all ( IDX_MAGIC ) ? ;
485
f.write_all ( & ( index.len ( ) as u32 ) .to_le_bytes ( ) ) ? ;
486
for ( r, e) in index {
487
f.write_all ( & r.0 ) ? ;
488
f.write_all ( & e.offset.to_le_bytes ( ) ) ? ;
489
f.write_all ( & e.stored_len.to_le_bytes ( ) ) ? ;
490
f.write_all ( & e.raw_len.to_le_bytes ( ) ) ? ;
491
f.write_all ( & [ type_index ( e.ty) , e.flags] ) ? ;
492
}
493
f.flush ( ) ? ;
494
f.get_ref ( ) .sync_all ( ) ? ;
495
}
4dcb8c09 Andy McCall 1 month ago ↶
496
497
crate :: durable:: rename( tmp_pack, & pack_path) ? ;
498
crate :: durable:: rename( & tmp_idx, & idx_path) ? ;
8f10a07a Andy McCall 1 month ago ↶
499
self .reload_packs ( )
500
}
501
75167672 Andy McCall 1 month ago
502
fn write_pack (
503
& self ,
504
refs : & [ObjectRef],
505
loose_paths : & [PathBuf],
506
old_packs : & [PathBuf],
507
) -> Result< PackReport> {
ba160f60 Andy McCall 1 month ago ↶
508
if refs.is_empty ( ) {
509
for p in old_packs {
510
fs:: remove_file( p) ? ;
511
fs:: remove_file( p.with_extension ( " idx" ) ) ? ;
512
}
513
self .reload_packs ( ) ? ;
d02e545d Andy McCall 1 month ago ↶
514
return Ok ( PackReport:: default( ) ) ;
515
}
ba160f60 Andy McCall 1 month ago ↶
516
let mut refs: Vec< ObjectRef> = refs.to_vec ( ) ;
517
refs.sort ( ) ;
518
refs.dedup ( ) ;
d02e545d Andy McCall 1 month ago ↶
519
let mut nonce = [ 0 u8 ; 8 ] ;
520
rand:: rngs:: OsRng.fill_bytes ( & mut nonce) ;
521
let id = hex:: encode( nonce) ;
522
let tmp_pack = self .dir.join ( format! ( " tmp-{id} .pack" ) ) ;
523
let mut report = PackReport:: default( ) ;
ba160f60 Andy McCall 1 month ago ↶
524
let mut index: Vec< ( ObjectRef, PackEntry) > = Vec :: with_capacity( refs.len ( ) ) ;
d02e545d Andy McCall 1 month ago ↶
525
{
526
let mut f = io:: BufWriter:: new( fs:: File:: create( & tmp_pack) ? ) ;
527
f.write_all ( PACK_MAGIC ) ? ;
528
let mut offset = PACK_MAGIC .len ( ) as u64 ;
ba160f60 Andy McCall 1 month ago ↶
529
for r in & refs {
d02e545d Andy McCall 1 month ago ↶
530
let ( ty, body) = self .get ( r) ? ;
cdb5beba Andy McCall 1 month ago ↶
531
let ( flags, stored) = if self .is_nocompress ( r) {
532
( 0 , body.clone ( ) )
533
} else {
75167672 Andy McCall 1 month ago
534
let compressed = zstd:: bulk:: compress( & body, ZSTD_LEVEL )
535
.map_err ( | e | StoreError:: Corrupt( * r, format! ( " zstd: {e} " ) ) ) ? ;
536
if compressed.len ( ) < body.len ( ) {
537
( FLAG_ZSTD , compressed)
538
} else {
539
( 0 , body.clone ( ) )
540
}
cdb5beba Andy McCall 1 month ago ↶
541
} ;
d02e545d Andy McCall 1 month ago ↶
542
let header_len = 1 + 1 + 4 + 4 + 32 ;
543
f.write_all ( & [ type_index ( ty) , flags] ) ? ;
544
f.write_all ( & ( stored.len ( ) as u32 ) .to_le_bytes ( ) ) ? ;
545
f.write_all ( & ( body.len ( ) as u32 ) .to_le_bytes ( ) ) ? ;
546
f.write_all ( & r.0 ) ? ;
547
f.write_all ( & stored) ? ;
75167672 Andy McCall 1 month ago
548
index.push ( (
549
* r,
550
PackEntry {
551
offset: offset + header_len,
552
stored_len: stored.len ( ) as u32 ,
553
raw_len: body.len ( ) as u32 ,
554
ty,
555
flags,
556
} ,
557
) ) ;
d02e545d Andy McCall 1 month ago ↶
558
offset + = header_len + stored.len ( ) as u64 ;
559
report.objects + = 1 ;
560
report.raw_bytes + = body.len ( ) as u64 ;
561
report.packed_bytes + = stored.len ( ) as u64 ;
562
}
563
f.flush ( ) ? ;
564
f.get_ref ( ) .sync_all ( ) ? ;
565
}
8f10a07a Andy McCall 1 month ago ↶
566
self .finish_pack_files ( & id, & tmp_pack, & index) ? ;
ba160f60 Andy McCall 1 month ago ↶
567
for p in old_packs {
568
fs:: remove_file( p) ? ;
569
fs:: remove_file( p.with_extension ( " idx" ) ) ? ;
570
}
d02e545d Andy McCall 1 month ago ↶
571
self .reload_packs ( ) ? ;
ba160f60 Andy McCall 1 month ago ↶
572
for path in loose_paths {
d02e545d Andy McCall 1 month ago ↶
573
fs:: remove_file( path) ? ;
574
if let Some ( dir) = path.parent ( ) {
575
let _ = fs:: remove_dir( dir) ;
576
}
577
}
578
Ok ( report)
ff0af1ee unknown 1 month ago
579
}
580
581
pub fn dir ( & self ) -> & Path {
582
& self .dir
583
}
584
387335ff Andy McCall 1 month ago ↶
585
pub ( crate ) fn path_for ( & self , r : & ObjectRef) -> PathBuf {
ff0af1ee unknown 1 month ago
586
let h = r.to_hex ( ) ;
587
self .dir.join ( " loose" ) .join ( & h[ .. 2 ] ) .join ( & h[ 2 .. ] )
588
}
589
590
pub fn has ( & self , r : & ObjectRef) -> bool {
75167672 Andy McCall 1 month ago
591
self .path_for ( r) .is_file ( )
592
| | self .find_packed ( r) .is_some ( )
593
| | self
594
.pending
595
.borrow ( )
596
.as_ref ( )
597
.is_some_and ( | p | p.lookup.contains_key ( r) )
ff0af1ee unknown 1 month ago
598
}
599
8f10a07a Andy McCall 1 month ago ↶
600
601
602
603
604
pub fn begin_batch ( & self ) -> Result< Batch< '_ > > {
605
let depth = self .batch_depth.get ( ) ;
606
if depth = = 0 {
75167672 Andy McCall 1 month ago
607
* self .pending.borrow_mut ( ) = Some ( Pending {
608
buffered: vec! [ ] ,
609
buffered_bytes: 0 ,
610
lookup: Default :: default( ) ,
611
pack: None ,
612
report: PackReport:: default( ) ,
613
} ) ;
8f10a07a Andy McCall 1 month ago ↶
614
}
615
self .batch_depth.set ( depth + 1 ) ;
75167672 Andy McCall 1 month ago
616
Ok ( Batch {
617
store: self ,
618
done: false ,
619
} )
8f10a07a Andy McCall 1 month ago ↶
620
}
621
622
fn finish_batch ( & self ) -> Result< ( ) > {
623
let depth = self .batch_depth.get ( ) ;
624
if depth = = 0 {
625
return Ok ( ( ) ) ;
626
}
627
self .batch_depth.set ( depth - 1 ) ;
628
if depth > 1 {
629
return Ok ( ( ) ) ;
630
}
75167672 Andy McCall 1 month ago
631
let Some ( pending) = self .pending.borrow_mut ( ) .take ( ) else {
632
return Ok ( ( ) ) ;
633
} ;
4dcb8c09 Andy McCall 1 month ago ↶
634
self .write_pending ( pending)
635
}
636
637
638
639
640
pub fn flush ( & self ) -> Result< ( ) > {
641
let Some ( pending) = self .pending.borrow_mut ( ) .take ( ) else {
642
return Ok ( ( ) ) ;
643
} ;
644
self .write_pending ( pending) ? ;
645
* self .pending.borrow_mut ( ) = Some ( Pending {
646
buffered: vec! [ ] ,
647
buffered_bytes: 0 ,
648
lookup: Default :: default( ) ,
649
pack: None ,
650
report: PackReport:: default( ) ,
651
} ) ;
652
Ok ( ( ) )
653
}
654
655
fn write_pending ( & self , pending : Pending) -> Result< ( ) > {
8f10a07a Andy McCall 1 month ago ↶
656
match pending.pack {
657
None => {
658
for ( ty, body) in & pending.buffered {
659
self .put_loose ( * ty, body) ? ;
660
}
661
Ok ( ( ) )
662
}
663
Some ( mut pack) => {
664
pack.writer.flush ( ) ? ;
665
pack.writer.get_ref ( ) .sync_all ( ) ? ;
666
drop ( pack.writer) ;
667
pack.index.sort_by_key ( | ( r , _) | * r) ;
668
self .finish_pack_files ( & pack.id, & pack.tmp_path, & pack.index) ? ;
669
Ok ( ( ) )
670
}
671
}
672
}
673
674
675
fn pending_get ( & self , r : & ObjectRef) -> Option< Result< ( ObjectType, Vec< u8 > ) > > {
676
let mut guard = self .pending.borrow_mut ( ) ;
677
let pending = guard.as_mut ( ) ? ;
678
let entry = * pending.lookup.get ( r) ? ;
679
Some ( match entry {
680
PendingEntry:: Buffered( i) => {
681
let ( ty, body) = & pending.buffered[ i] ;
682
Ok ( ( * ty, body.clone ( ) ) )
683
}
684
PendingEntry:: Packed( e) => {
75167672 Andy McCall 1 month ago
685
let Some ( pack) = pending.pack.as_mut ( ) else {
686
return Some ( Err ( StoreError:: NotFound( * r) ) ) ;
687
} ;
8f10a07a Andy McCall 1 month ago ↶
688
match pack.writer.flush ( ) {
75167672 Andy McCall 1 month ago
689
Ok ( ( ) ) => PackIndex {
690
path: pack.tmp_path.clone ( ) ,
691
refs: vec! [ ] ,
692
entries: vec! [ ] ,
693
}
694
.read ( r, e) ,
8f10a07a Andy McCall 1 month ago ↶
695
Err ( err) => Err ( err.into ( ) ) ,
696
}
697
}
698
} )
699
}
700
701
75167672 Andy McCall 1 month ago
702
fn pending_pack_write (
703
& self ,
704
pack : & mut PendingPack,
705
r : & ObjectRef,
706
ty : ObjectType,
707
body : & [u8 ],
708
report : & mut PackReport,
709
) -> Result< PackEntry> {
8f10a07a Andy McCall 1 month ago ↶
710
let ( flags, stored) = if self .is_nocompress ( r) {
711
( 0 , body.to_vec ( ) )
712
} else {
75167672 Andy McCall 1 month ago
713
let compressed = zstd:: bulk:: compress( body, ZSTD_LEVEL )
714
.map_err ( | e | StoreError:: Corrupt( * r, format! ( " zstd: {e} " ) ) ) ? ;
715
if compressed.len ( ) < body.len ( ) {
716
( FLAG_ZSTD , compressed)
717
} else {
718
( 0 , body.to_vec ( ) )
719
}
8f10a07a Andy McCall 1 month ago ↶
720
} ;
192fad1e Andy McCall 1 month ago ↶
721
write_pack_entry ( pack, r, ty, body.len ( ) , flags, & stored, report)
8f10a07a Andy McCall 1 month ago ↶
722
}
723
724
fn put_pending ( & self , r : ObjectRef, ty : ObjectType, body : & [u8 ]) -> Result< ( ) > {
725
let mut guard = self .pending.borrow_mut ( ) ;
726
let pending = guard.as_mut ( ) .expect ( " batch open" ) ;
727
if pending.lookup.contains_key ( & r) {
728
return Ok ( ( ) ) ;
729
}
75167672 Andy McCall 1 month ago
730
if pending.pack.is_none ( )
731
& & pending.buffered.len ( ) < SPILL_OBJECTS
732
& & pending.buffered_bytes + body.len ( ) < = SPILL_BYTES
733
{
8f10a07a Andy McCall 1 month ago ↶
734
pending.buffered.push ( ( ty, body.to_vec ( ) ) ) ;
735
pending.buffered_bytes + = body.len ( ) ;
75167672 Andy McCall 1 month ago
736
pending
737
.lookup
738
.insert ( r, PendingEntry:: Buffered( pending.buffered.len ( ) - 1 ) ) ;
8f10a07a Andy McCall 1 month ago ↶
739
return Ok ( ( ) ) ;
740
}
192fad1e Andy McCall 1 month ago ↶
741
self .ensure_pack ( pending) ? ;
742
let mut report = std:: mem:: take( & mut pending.report) ;
743
let pack = pending.pack.as_mut ( ) .unwrap ( ) ;
744
let e = self .pending_pack_write ( pack, & r, ty, body, & mut report) ? ;
745
pending.report = report;
746
pending.lookup.insert ( r, PendingEntry:: Packed( e) ) ;
747
Ok ( ( ) )
748
}
749
750
751
752
fn ensure_pack ( & self , pending : & mut Pending) -> Result< ( ) > {
8f10a07a Andy McCall 1 month ago ↶
753
if pending.pack.is_none ( ) {
754
755
let mut nonce = [ 0 u8 ; 8 ] ;
756
rand:: rngs:: OsRng.fill_bytes ( & mut nonce) ;
757
let id = hex:: encode( nonce) ;
758
let tmp_path = self .dir.join ( format! ( " tmp-{id} .pack" ) ) ;
759
let mut writer = io:: BufWriter:: new( fs:: File:: create( & tmp_path) ? ) ;
760
writer.write_all ( PACK_MAGIC ) ? ;
75167672 Andy McCall 1 month ago
761
let mut pack = PendingPack {
762
id,
763
tmp_path,
764
writer,
765
offset: PACK_MAGIC .len ( ) as u64 ,
766
index: vec! [ ] ,
767
} ;
8f10a07a Andy McCall 1 month ago ↶
768
let buffered = std:: mem:: take( & mut pending.buffered) ;
769
pending.buffered_bytes = 0 ;
770
let mut report = std:: mem:: take( & mut pending.report) ;
771
for ( bty, bbody) in & buffered {
772
let br = hash_object ( * bty, bbody) ;
773
let e = self .pending_pack_write ( & mut pack, & br, * bty, bbody, & mut report) ? ;
774
pending.lookup.insert ( br, PendingEntry:: Packed( e) ) ;
775
}
776
pending.report = report;
777
pending.pack = Some ( pack) ;
192fad1e Andy McCall 1 month ago ↶
778
}
779
Ok ( ( ) )
780
}
781
782
783
784
785
pub fn put_many ( & self , objects : & [( ObjectType , &[u8 ]) ]) -> Result< Vec< ObjectRef> > {
786
use rayon:: prelude:: * ;
787
let total: usize = objects.iter ( ) .map ( | ( _, b ) | b.len ( ) ) .sum ( ) ;
788
if self .pending.borrow ( ) .is_none ( ) | | total < SPILL_BYTES {
789
return objects.iter ( ) .map ( | ( ty , b ) | self .put ( * ty, b) ) .collect ( ) ;
790
}
791
for ( ty, b) in objects {
792
if b.len ( ) > MAX_OBJECT_LEN as usize {
793
return Err ( StoreError:: TooLarge {
794
kind: ty.name ( ) ,
795
size: b.len ( ) ,
796
limit: MAX_OBJECT_LEN ,
797
} ) ;
798
}
8f10a07a Andy McCall 1 month ago ↶
799
}
192fad1e Andy McCall 1 month ago ↶
800
let refs: Vec< ObjectRef> = objects
801
.par_iter ( )
802
.map ( | ( ty , b ) | hash_object ( * ty, b) )
803
.collect ( ) ;
804
805
let mut seen: std:: collections:: HashSet< ObjectRef> = std:: collections:: HashSet:: new( ) ;
806
let mut todo: Vec< usize > = Vec :: new( ) ;
807
for ( i, r) in refs.iter ( ) .enumerate ( ) {
808
let known = self
809
.pending
810
.borrow ( )
811
.as_ref ( )
812
.is_some_and ( | p | p.lookup.contains_key ( r) ) ;
813
if seen.insert ( * r)
814
& & ! known
815
& & ! self .path_for ( r) .is_file ( )
816
& & self .find_packed ( r) .is_none ( )
817
{
818
todo.push ( i) ;
819
}
820
}
821
let raw: std:: collections:: HashSet< ObjectRef> =
822
match fs:: read_dir( self .dir.join ( " nocompress" ) ) {
823
Ok ( rd) => rd
824
.flatten ( )
825
.filter_map ( | e | ObjectRef:: from_hex( & e.file_name ( ) .to_string_lossy ( ) ) )
826
.collect ( ) ,
827
Err ( _ ) => std:: collections:: HashSet:: new( ) ,
828
} ;
829
let packed: Vec< ( u8 , Vec< u8 > ) > = todo
830
.par_iter ( )
831
.map ( | i | {
832
let body = objects[ * i] .1 ;
833
if raw.contains ( & refs[ * i] ) {
834
return ( 0 , body.to_vec ( ) ) ;
835
}
836
match zstd:: bulk:: compress( body, ZSTD_LEVEL ) {
837
Ok ( c) if c.len ( ) < body.len ( ) => ( FLAG_ZSTD , c) ,
838
_ => ( 0 , body.to_vec ( ) ) ,
839
}
840
} )
841
.collect ( ) ;
842
let mut guard = self .pending.borrow_mut ( ) ;
843
let pending = guard.as_mut ( ) .expect ( " batch open" ) ;
844
self .ensure_pack ( pending) ? ;
8f10a07a Andy McCall 1 month ago ↶
845
let mut report = std:: mem:: take( & mut pending.report) ;
846
let pack = pending.pack.as_mut ( ) .unwrap ( ) ;
192fad1e Andy McCall 1 month ago ↶
847
for ( i, ( flags, stored) ) in todo.iter ( ) .zip ( packed) {
848
let ( ty, body) = objects[ * i] ;
849
let e = write_pack_entry ( pack, & refs[ * i] , ty, body.len ( ) , flags, & stored, & mut report) ? ;
850
pending.lookup.insert ( refs[ * i] , PendingEntry:: Packed( e) ) ;
851
}
8f10a07a Andy McCall 1 month ago ↶
852
pending.report = report;
192fad1e Andy McCall 1 month ago ↶
853
Ok ( refs)
8f10a07a Andy McCall 1 month ago ↶
854
}
855
ff0af1ee unknown 1 month ago
856
857
pub fn put ( & self , ty : ObjectType, body : & [u8 ]) -> Result< ObjectRef> {
a9f686ed Andy McCall 1 month ago ↶
858
859
860
861
if body.len ( ) > MAX_OBJECT_LEN as usize {
862
return Err ( StoreError:: TooLarge {
863
kind: ty.name ( ) ,
864
size: body.len ( ) ,
865
limit: MAX_OBJECT_LEN ,
866
} ) ;
867
}
ff0af1ee unknown 1 month ago
868
let r = hash_object ( ty, body) ;
869
let path = self .path_for ( & r) ;
d02e545d Andy McCall 1 month ago ↶
870
if path.is_file ( ) | | self .find_packed ( & r) .is_some ( ) {
ff0af1ee unknown 1 month ago
871
return Ok ( r) ;
872
}
8f10a07a Andy McCall 1 month ago ↶
873
if self .pending.borrow ( ) .is_some ( ) {
874
self .put_pending ( r, ty, body) ? ;
875
return Ok ( r) ;
876
}
877
self .put_loose ( ty, body) ? ;
878
Ok ( r)
879
}
880
881
882
fn put_loose ( & self , ty : ObjectType, body : & [u8 ]) -> Result< ObjectRef> {
883
let r = hash_object ( ty, body) ;
884
let path = self .path_for ( & r) ;
885
if path.is_file ( ) {
886
return Ok ( r) ;
887
}
4dcb8c09 Andy McCall 1 month ago ↶
888
let fanout = path.parent ( ) .unwrap ( ) ;
889
if ! fanout.is_dir ( ) {
890
fs:: create_dir_all( fanout) ? ;
891
crate :: durable:: sync_dir( & self .dir.join ( " loose" ) ) ? ;
892
}
ff0af1ee unknown 1 month ago
893
let mut nonce = [ 0 u8 ; 8 ] ;
894
rand:: rngs:: OsRng.fill_bytes ( & mut nonce) ;
75167672 Andy McCall 1 month ago
895
let tmp = self
896
.dir
897
.join ( " loose" )
898
.join ( format! ( " tmp-{} " , hex:: encode( nonce) ) ) ;
4dcb8c09 Andy McCall 1 month ago ↶
899
crate :: durable:: step( " write loose object" ) ? ;
ff0af1ee unknown 1 month ago
900
{
901
let mut f = fs:: File:: create( & tmp) ? ;
902
f.write_all ( & header ( ty, body.len ( ) ) ) ? ;
903
f.write_all ( body) ? ;
904
f.sync_all ( ) ? ;
905
}
4dcb8c09 Andy McCall 1 month ago ↶
906
crate :: durable:: rename( & tmp, & path) ? ;
ff0af1ee unknown 1 month ago
907
Ok ( r)
908
}
909
8f10a07a Andy McCall 1 month ago ↶
910
ff0af1ee unknown 1 month ago
911
pub fn get ( & self , r : & ObjectRef) -> Result< ( ObjectType, Vec< u8 > ) > {
afaba5f1 Andy McCall 1 month ago ↶
912
match read_loose_at ( & self .path_for ( r) , r) {
913
Some ( found) => found,
914
None => match self .find_packed ( r) {
915
Some ( ( i, e) ) => self .packs.borrow ( ) [ i] .read ( r, e) ,
916
None => self .pending_get ( r) .unwrap_or ( Err ( StoreError:: NotFound( * r) ) ) ,
917
} ,
918
}
919
}
920
921
922
923
924
pub fn get_many ( & self , refs : & [ObjectRef]) -> Vec< Result< Object> > {
925
use rayon:: prelude:: * ;
926
let paths: Vec< ( ObjectRef, PathBuf) > =
927
refs.iter ( ) .map ( | r | ( * r, self .path_for ( r) ) ) .collect ( ) ;
928
let mut found: Vec< Option< Result< Object> > > =
929
paths.par_iter ( ) .map ( | ( r , p ) | read_loose_at ( p, r) ) .collect ( ) ;
930
931
let packs = self .packs.borrow ( ) ;
932
let packed: Vec< ( usize , usize , PackEntry) > = found
75167672 Andy McCall 1 month ago
933
.iter ( )
afaba5f1 Andy McCall 1 month ago ↶
934
.enumerate ( )
935
.filter ( | ( _, f ) | f.is_none ( ) )
936
.filter_map ( | ( i , _) | {
937
packs
938
.iter ( )
939
.enumerate ( )
940
.find_map ( | ( p , pack ) | pack.find ( & refs[ i] ) .map ( | e | ( i, p, e) ) )
941
} )
942
.collect ( ) ;
943
let indexes: & [ PackIndex] = & packs;
944
let read: Vec< ( usize , Result< Object> ) > = packed
945
.par_iter ( )
946
.map ( | ( i , p , e ) | ( * i, indexes[ * p] .read ( & refs[ * i] , * e) ) )
947
.collect ( ) ;
948
drop ( packs) ;
949
for ( i, r) in read {
950
found[ i] = Some ( r) ;
ff0af1ee unknown 1 month ago
951
}
afaba5f1 Andy McCall 1 month ago ↶
952
found
953
.into_iter ( )
954
.zip ( refs)
955
.map ( | ( f , r ) | match f {
956
Some ( f) => f,
957
None => self .get ( r) ,
958
} )
959
.collect ( )
ff0af1ee unknown 1 month ago
960
}
961
962
pub fn get_typed ( & self , r : & ObjectRef, want : ObjectType) -> Result< Vec< u8 > > {
963
let ( ty, body) = self .get ( r) ? ;
964
if ty ! = want {
965
return Err ( StoreError:: WrongType( * r, ty.name ( ) , want.name ( ) ) ) ;
966
}
967
Ok ( body)
968
}
969
6f8aeb99 Andy McCall 1 month ago ↶
970
971
ff0af1ee unknown 1 month ago
972
pub fn put_change ( & self , c : & Change) -> Result< ObjectRef> {
6f8aeb99 Andy McCall 1 month ago ↶
973
let ( bytes, texts) = c.encode_parts ( ) ;
192fad1e Andy McCall 1 month ago ↶
974
self .put_change_encoded ( & bytes, & texts)
6f8aeb99 Andy McCall 1 month ago ↶
975
}
976
192fad1e Andy McCall 1 month ago ↶
977
978
pub fn put_change_encoded (
979
& self ,
980
bytes : & [u8 ],
981
texts : & [crate:: objects:: StoredText],
982
) -> Result< ObjectRef> {
983
984
985
let mut chunks: Vec< ( ObjectType, & [u8 ]) > = Vec :: new( ) ;
986
for t in texts {
987
if ! self .has ( & t.blob.hash ( ) ) {
988
chunks.extend (
989
crate :: chunk:: chunks( & t.content)
990
.into_iter ( )
991
.map ( | c | ( ObjectType:: Chunk, c) ) ,
992
) ;
993
}
6f8aeb99 Andy McCall 1 month ago ↶
994
}
192fad1e Andy McCall 1 month ago ↶
995
self .put_many ( & chunks) ? ;
996
for t in texts {
997
self .put ( ObjectType:: Blob, & t.blob.encode ( ) ) ? ;
6f8aeb99 Andy McCall 1 month ago ↶
998
}
192fad1e Andy McCall 1 month ago ↶
999
self .put ( ObjectType:: Change, bytes)
ff0af1ee unknown 1 month ago
1000
}
1001
1002
pub fn get_change ( & self , r : & ObjectRef) -> Result< Change> {
6f8aeb99 Andy McCall 1 month ago ↶
1003
Ok ( self .get_change_parts ( r) ? .0 )
1004
}
1005
1006
1007
1008
pub fn get_change_parts ( & self , r : & ObjectRef) -> Result< ( Change, Vec< ObjectRef> ) > {
1009
let bytes = self .get_typed ( r, ObjectType:: Change) ? ;
1010
let mut parts = Vec :: new( ) ;
1011
let mut failed = None ;
1012
let decoded = Change:: decode_with( & bytes, & mut | text, count| match self .read_text ( text) {
1013
Ok ( ( blob, content) ) => {
1014
parts.push ( * text) ;
1015
parts.extend ( blob.chunks) ;
1016
crate :: objects:: text_lines( & content, count)
1017
}
1018
Err ( e) => {
1019
let why = e.to_string ( ) ;
1020
failed = Some ( e) ;
1021
Err ( ObjectError:: Schema( format! (
1022
" text blob {} : {why} " ,
1023
text.short ( 12 )
1024
) ) )
1025
}
1026
} ) ;
1027
match ( decoded, failed) {
1028
( Ok ( c) , _ ) => Ok ( ( c, parts) ) ,
1029
( Err ( _ ) , Some ( StoreError:: NotFound( t) ) ) => Err ( StoreError:: Corrupt(
1030
* r,
1031
format! ( " its text blob {} is missing" , t.short ( 12 ) ) ,
1032
) ) ,
1033
( Err ( e) , _ ) => Err ( StoreError:: Corrupt( * r, e.to_string ( ) ) ) ,
1034
}
1035
}
1036
86e1824f Andy McCall 1 month ago ↶
1037
1038
1039
1040
1041
pub fn get_change_lazy ( & self , r : & ObjectRef) -> Result< ( Change, Vec< ( ObjectRef, u32 ) > ) > {
1042
let bytes = self .get_typed ( r, ObjectType:: Change) ? ;
1043
let mut texts = Vec :: new( ) ;
1044
let c = Change:: decode_with( & bytes, & mut | text, count| {
1045
texts.push ( ( * text, count) ) ;
1046
Ok ( Vec :: new( ) )
1047
} )
1048
.map_err ( | e | StoreError:: Corrupt( * r, e.to_string ( ) ) ) ? ;
1049
Ok ( ( c, texts) )
1050
}
1051
69816d68 Andy McCall 1 month ago ↶
1052
1053
1054
1055
pub fn get_change_meta ( & self , r : & ObjectRef) -> Result< crate:: state:: ChangeMeta> {
1056
Ok ( crate :: state:: ChangeMeta:: of( & self .get_change_lazy ( r) ? .0 ) )
1057
}
1058
6f8aeb99 Andy McCall 1 month ago ↶
1059
fn read_text ( & self , r : & ObjectRef) -> Result< ( Blob, Vec< u8 > ) > {
1060
let blob = Blob:: decode( & self .get_typed ( r, ObjectType:: Blob) ? ) ? ;
1061
let mut content = Vec :: with_capacity( blob.length as usize ) ;
1062
for c in & blob.chunks {
1063
content.extend ( self .get_typed ( c, ObjectType:: Chunk) ? ) ;
1064
}
1065
Ok ( ( blob, content) )
ff0af1ee unknown 1 month ago
1066
}
1067
1068
pub fn put_chunk ( & self , bytes : & [u8 ]) -> Result< ObjectRef> {
1069
self .put ( ObjectType:: Chunk, bytes)
1070
}
1071
cdb5beba Andy McCall 1 month ago ↶
1072
1073
pub fn put_chunk_nocompress ( & self , bytes : & [u8 ]) -> Result< ObjectRef> {
8f10a07a Andy McCall 1 month ago ↶
1074
let r = hash_object ( ObjectType:: Chunk, bytes) ;
cdb5beba Andy McCall 1 month ago ↶
1075
let dir = self .dir.join ( " nocompress" ) ;
1076
fs:: create_dir_all( & dir) ? ;
1077
let _ = fs:: File:: create( dir.join ( r.to_hex ( ) ) ) ;
8f10a07a Andy McCall 1 month ago ↶
1078
self .put ( ObjectType:: Chunk, bytes)
cdb5beba Andy McCall 1 month ago ↶
1079
}
1080
1081
fn is_nocompress ( & self , r : & ObjectRef) -> bool {
1082
self .dir.join ( " nocompress" ) .join ( r.to_hex ( ) ) .exists ( )
1083
}
1084
86e682c2 Andy McCall 1 month ago ↶
1085
pub fn put_state ( & self , st : & StateObject) -> Result< ObjectRef> {
1086
self .put ( ObjectType:: State, & st.encode ( ) )
1087
}
1088
1089
pub fn get_state ( & self , r : & ObjectRef) -> Result< StateObject> {
1090
Ok ( StateObject:: decode( & self .get_typed ( r, ObjectType:: State) ? ) ? )
1091
}
1092
5b9c6ad4 Andy McCall 1 month ago ↶
1093
pub fn put_attest ( & self , a : & Attest) -> Result< ObjectRef> {
1094
self .put ( ObjectType:: Attest, & a.encode ( ) )
1095
}
1096
1097
pub fn get_attest ( & self , r : & ObjectRef) -> Result< Attest> {
1098
Ok ( Attest:: decode( & self .get_typed ( r, ObjectType:: Attest) ? ) ? )
fc1a466a Andy McCall 1 month ago ↶
1099
}
1100
1101
pub fn put_receipt ( & self , r : & Receipt) -> Result< ObjectRef> {
1102
self .put ( ObjectType:: Receipt, & r.encode ( ) )
5b9c6ad4 Andy McCall 1 month ago ↶
1103
}
1104
fc1a466a Andy McCall 1 month ago ↶
1105
pub fn get_receipt ( & self , r : & ObjectRef) -> Result< Receipt> {
1106
Ok ( Receipt:: decode( & self .get_typed ( r, ObjectType:: Receipt) ? ) ? )
5b9c6ad4 Andy McCall 1 month ago ↶
1107
}
1108
fc1a466a Andy McCall 1 month ago ↶
1109
fn index_path ( & self , index : & str , key : & ObjectRef) -> PathBuf {
1110
let h = key.to_hex ( ) ;
1111
self .dir.join ( index) .join ( & h[ .. 2 ] ) .join ( & h[ 2 .. ] )
1112
}
1113
1114
1115
fn index_add ( & self , index : & str , key : & ObjectRef, value : & ObjectRef) -> Result< ( ) > {
1116
let mut values = self .index_get ( index, key) ? ;
1117
if values.contains ( value) {
5b9c6ad4 Andy McCall 1 month ago ↶
1118
return Ok ( ( ) ) ;
1119
}
fc1a466a Andy McCall 1 month ago ↶
1120
values.push ( * value) ;
1121
values.sort ( ) ;
1122
let path = self .index_path ( index, key) ;
5b9c6ad4 Andy McCall 1 month ago ↶
1123
fs:: create_dir_all( path.parent ( ) .unwrap ( ) ) ? ;
fc1a466a Andy McCall 1 month ago ↶
1124
let text: String = values.iter ( ) .map ( | s | format! ( " {} \n " , s.to_hex ( ) ) ) .collect ( ) ;
4dcb8c09 Andy McCall 1 month ago ↶
1125
crate :: durable:: write_atomic( & path, text.as_bytes ( ) ) ? ;
5b9c6ad4 Andy McCall 1 month ago ↶
1126
Ok ( ( ) )
1127
}
1128
fc1a466a Andy McCall 1 month ago ↶
1129
fn index_get ( & self , index : & str , key : & ObjectRef) -> Result< Vec< ObjectRef> > {
1130
match fs:: read_to_string( self .index_path ( index, key) ) {
5b9c6ad4 Andy McCall 1 month ago ↶
1131
Ok ( t) => Ok ( t.lines ( ) .filter_map ( ObjectRef:: from_hex) .collect ( ) ) ,
1132
Err ( e) if e.kind ( ) = = io:: ErrorKind:: NotFound => Ok ( vec! [ ] ) ,
1133
Err ( e) => Err ( e.into ( ) ) ,
1134
}
fc1a466a Andy McCall 1 month ago ↶
1135
}
1136
1137
1138
pub fn index_seal ( & self , change : & ObjectRef, attest : & ObjectRef) -> Result< ( ) > {
1139
self .index_add ( " seals" , change, attest)
1140
}
1141
1142
1143
pub fn seals_for ( & self , change : & ObjectRef) -> Result< Vec< ObjectRef> > {
1144
self .index_get ( " seals" , change)
1145
}
1146
1147
1148
pub fn index_receipt ( & self , state : & ObjectRef, attest : & ObjectRef) -> Result< ( ) > {
1149
self .index_add ( " receipts" , state, attest)
1150
}
1151
1152
1153
pub fn receipts_for ( & self , state : & ObjectRef) -> Result< Vec< ObjectRef> > {
1154
self .index_get ( " receipts" , state)
1155
}
1156
1157
1158
pub fn receipt_states ( & self ) -> Result< Vec< ObjectRef> > {
1159
let mut out = Vec :: new( ) ;
1160
let dir = self .dir.join ( " receipts" ) ;
1161
let top = match fs:: read_dir( & dir) {
1162
Ok ( d) => d,
1163
Err ( e) if e.kind ( ) = = io:: ErrorKind:: NotFound => return Ok ( out) ,
1164
Err ( e) => return Err ( e.into ( ) ) ,
1165
} ;
1166
for d in top {
1167
let d = d? ;
1168
let prefix = d.file_name ( ) .to_string_lossy ( ) .into_owned ( ) ;
1169
for f in fs:: read_dir( d.path ( ) ) ? {
1170
let name = f? .file_name ( ) .to_string_lossy ( ) .into_owned ( ) ;
1171
if let Some ( r) = ObjectRef:: from_hex( & format! ( " {prefix} {name} " ) ) {
1172
out.push ( r) ;
1173
}
1174
}
1175
}
1176
out.sort ( ) ;
1177
Ok ( out)
5b9c6ad4 Andy McCall 1 month ago ↶
1178
}
1179
1983686f Andy McCall 1 month ago ↶
1180
pub fn put_identity ( & self , id : & Identity) -> Result< ObjectRef> {
1181
self .put ( ObjectType:: Identity, & id.encode ( ) )
1182
}
1183
1184
pub fn get_identity ( & self , r : & ObjectRef) -> Result< Identity> {
1185
Ok ( Identity:: decode( & self .get_typed ( r, ObjectType:: Identity) ? ) ? )
1186
}
1187
1188
fn sig_path ( & self , r : & ObjectRef) -> PathBuf {
1189
let h = r.to_hex ( ) ;
1190
self .dir.join ( " sigs" ) .join ( & h[ .. 2 ] ) .join ( & h[ 2 .. ] )
1191
}
1192
1193
1194
pub fn signatures ( & self , r : & ObjectRef) -> Result< Vec< Signature> > {
1195
match fs:: read( self .sig_path ( r) ) {
1196
Ok ( b) => Ok ( Signature:: decode_list( & b) ? ) ,
1197
Err ( e) if e.kind ( ) = = io:: ErrorKind:: NotFound => Ok ( vec! [ ] ) ,
1198
Err ( e) => Err ( e.into ( ) ) ,
1199
}
1200
}
1201
1202
1203
pub fn put_signature ( & self , r : & ObjectRef, sig : & Signature) -> Result< ( ) > {
1204
let mut sigs = self .signatures ( r) ? ;
1205
sigs.retain ( | s | s.key ! = sig.key ) ;
1206
sigs.push ( sig.clone ( ) ) ;
5b1114a0 Andy McCall 1 month ago ↶
1207
sigs.sort_by_key ( | s | s.key ) ;
1983686f Andy McCall 1 month ago ↶
1208
let path = self .sig_path ( r) ;
1209
fs:: create_dir_all( path.parent ( ) .unwrap ( ) ) ? ;
1210
let mut nonce = [ 0 u8 ; 8 ] ;
1211
rand:: rngs:: OsRng.fill_bytes ( & mut nonce) ;
1212
let tmp = self .dir.join ( format! ( " sigs-tmp-{} " , hex:: encode( nonce) ) ) ;
1213
{
1214
let mut f = fs:: File:: create( & tmp) ? ;
1215
f.write_all ( & Signature:: encode_list( & sigs) ) ? ;
1216
f.sync_all ( ) ? ;
1217
}
4dcb8c09 Andy McCall 1 month ago ↶
1218
crate :: durable:: rename( & tmp, & path) ? ;
1983686f Andy McCall 1 month ago ↶
1219
Ok ( ( ) )
1220
}
1221
d02e545d Andy McCall 1 month ago ↶
1222
ff0af1ee unknown 1 month ago
1223
pub fn list ( & self ) -> io:: Result< Vec< ObjectRef> > {
d02e545d Andy McCall 1 month ago ↶
1224
let mut out: Vec< ObjectRef> = self .loose_objects ( ) ? .into_iter ( ) .map ( | ( r , _) | r ) .collect ( ) ;
1225
for p in self .packs.borrow ( ) .iter ( ) {
1226
out.extend ( p.refs.iter ( ) .copied ( ) ) ;
ff0af1ee unknown 1 month ago
1227
}
1228
out.sort ( ) ;
d02e545d Andy McCall 1 month ago ↶
1229
out.dedup ( ) ;
ff0af1ee unknown 1 month ago
1230
Ok ( out)
1231
}
afaba5f1 Andy McCall 1 month ago ↶
1232
}
1233
1234
1235
1236
fn read_loose_at ( path : & Path, r : & ObjectRef) -> Option< Result< Object> > {
1237
let data = match fs:: read( path) {
1238
Ok ( d) => d,
1239
Err ( e) if e.kind ( ) = = io:: ErrorKind:: NotFound => return None ,
1240
Err ( e) => return Some ( Err ( e.into ( ) ) ) ,
1241
} ;
1242
Some ( ( | | {
1243
let nul = data
1244
.iter ( )
1245
.position ( | & b | b = = 0 )
1246
.ok_or_else ( | | StoreError:: Corrupt( * r, " no header" .into ( ) ) ) ? ;
1247
let head = std:: str:: from_utf8( & data[ .. nul] )
1248
.map_err ( | _| StoreError:: Corrupt( * r, " header not UTF-8" .into ( ) ) ) ? ;
1249
let mut parts = head.split ( ' ' ) ;
1250
let ( Some ( " oxy1" ) , Some ( ty) , Some ( len) , None ) =
1251
( parts.next ( ) , parts.next ( ) , parts.next ( ) , parts.next ( ) )
1252
else {
1253
return Err ( StoreError:: Corrupt( * r, format! ( " bad header {head:?} " ) ) ) ;
1254
} ;
1255
let ty = ObjectType:: from_name( ty)
1256
.ok_or_else ( | | StoreError:: Corrupt( * r, format! ( " unknown type {ty} " ) ) ) ? ;
1257
let len: usize = len
1258
.parse ( )
1259
.map_err ( | _| StoreError:: Corrupt( * r, " bad length" .into ( ) ) ) ? ;
1260
let body = & data[ nul + 1 .. ] ;
1261
if body.len ( ) ! = len {
1262
return Err ( StoreError:: Corrupt(
1263
* r,
1264
format! ( " length {} != header {len} " , body.len ( ) ) ,
1265
) ) ;
1266
}
1267
if hash_object ( ty, body) ! = * r {
1268
return Err ( StoreError:: Corrupt( * r, " hash mismatch" .into ( ) ) ) ;
1269
}
1270
Ok ( ( ty, body.to_vec ( ) ) )
1271
} ) ( ) )
ff0af1ee unknown 1 month ago
1272
}
1273
1274
# [ cfg ( test ) ]
1275
mod tests {
1276
use super :: * ;
1277
1278
# [ test ]
1279
fn put_get_verify ( ) {
1280
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1281
let s = Store:: open( dir.path ( ) .join ( " objects" ) ) .unwrap ( ) ;
1282
let r = s.put ( ObjectType:: Chunk, b " hello" ) .unwrap ( ) ;
1283
assert! ( s.has ( & r) ) ;
1284
assert_eq! ( s.get ( & r) .unwrap ( ) , ( ObjectType:: Chunk, b " hello" .to_vec ( ) ) ) ;
1285
assert_eq! ( s.put ( ObjectType:: Chunk, b " hello" ) .unwrap ( ) , r) ;
1286
assert_eq! ( s.list ( ) .unwrap ( ) , vec! [ r] ) ;
75167672 Andy McCall 1 month ago
1287
assert! ( matches! (
1288
s.get_typed ( & r, ObjectType:: Blob) ,
1289
Err ( StoreError:: WrongType( .. ) )
1290
) ) ;
1291
assert! ( matches! (
1292
s.get ( & ObjectRef( [ 9 ; 32 ] ) ) ,
1293
Err ( StoreError:: NotFound( _ ) )
1294
) ) ;
ff0af1ee unknown 1 month ago
1295
1296
1297
let path = s.path_for ( & r) ;
1298
let mut data = fs:: read( & path) .unwrap ( ) ;
1299
let last = data.len ( ) - 1 ;
1300
data[ last] ^ = 1 ;
1301
fs:: write( & path, data) .unwrap ( ) ;
1302
assert! ( matches! ( s.get ( & r) , Err ( StoreError:: Corrupt( .. ) ) ) ) ;
1303
}
1304
1305
# [ test ]
d02e545d Andy McCall 1 month ago ↶
1306
fn packing_keeps_every_object_readable ( ) {
1307
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1308
let s = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1309
let mut refs = Vec :: new( ) ;
1310
for i in 0 .. 300 u32 {
1311
let body = format! ( " object {i} " ) .repeat ( 1 + ( i % 7 ) as usize ) ;
1312
refs.push ( ( s.put ( ObjectType:: Chunk, body.as_bytes ( ) ) .unwrap ( ) , body) ) ;
1313
}
1314
let c = crate :: objects:: tests:: sample_change( ) ;
1315
let cr = s.put_change ( & c) .unwrap ( ) ;
1316
assert_eq! ( s.loose_count ( ) .unwrap ( ) , 301 ) ;
1317
let report = s.pack_loose ( ) .unwrap ( ) ;
1318
assert_eq! ( report.objects, 301 ) ;
1319
assert! ( report.packed_bytes < report.raw_bytes, " {report:?}" ) ;
1320
assert_eq! ( s.loose_count ( ) .unwrap ( ) , 0 ) ;
1321
assert_eq! ( s.pack_count ( ) , 1 ) ;
1322
for ( r, body) in & refs {
1323
assert! ( s.has ( r) ) ;
75167672 Andy McCall 1 month ago
1324
assert_eq! (
1325
s.get ( r) .unwrap ( ) ,
1326
( ObjectType:: Chunk, body.as_bytes ( ) .to_vec ( ) )
1327
) ;
d02e545d Andy McCall 1 month ago ↶
1328
}
1329
assert_eq! ( s.get_change ( & cr) .unwrap ( ) , c) ;
1330
assert_eq! ( s.list ( ) .unwrap ( ) .len ( ) , 301 ) ;
1331
1332
75167672 Andy McCall 1 month ago
1333
assert_eq! (
1334
s.put ( ObjectType:: Chunk, refs[ 0 ] .1. as_bytes ( ) ) .unwrap ( ) ,
1335
refs[ 0 ] .0
1336
) ;
d02e545d Andy McCall 1 month ago ↶
1337
assert_eq! ( s.loose_count ( ) .unwrap ( ) , 0 ) ;
1338
let extra = s.put ( ObjectType:: Chunk, b " after packing" ) .unwrap ( ) ;
1339
s.pack_loose ( ) .unwrap ( ) ;
1340
assert_eq! ( s.pack_count ( ) , 2 ) ;
1341
assert_eq! ( s.get ( & extra) .unwrap ( ) .1 , b " after packing" ) ;
1342
1343
let s2 = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1344
assert_eq! ( s2.pack_count ( ) , 2 ) ;
1345
assert_eq! ( s2.get ( & refs[ 5 ] .0 ) .unwrap ( ) .1 , refs[ 5 ] .1. as_bytes ( ) ) ;
75167672 Andy McCall 1 month ago
1346
assert! ( matches! (
1347
s2.get ( & ObjectRef( [ 9 ; 32 ] ) ) ,
1348
Err ( StoreError:: NotFound( _ ) )
1349
) ) ;
d02e545d Andy McCall 1 month ago ↶
1350
1351
let target = refs[ 5 ] .0 ;
1352
let ( pack_path, offset) = {
1353
let packs = s2.packs.borrow ( ) ;
1354
let ( i, e) = s2.find_packed ( & target) .unwrap ( ) ;
1355
( packs[ i] .path.clone ( ) , e.offset as usize )
1356
} ;
1357
let mut data = fs:: read( & pack_path) .unwrap ( ) ;
1358
data[ offset] ^ = 0xff ;
1359
fs:: write( & pack_path, data) .unwrap ( ) ;
1360
let s3 = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1361
assert! ( matches! ( s3.get ( & target) , Err ( StoreError:: Corrupt( .. ) ) ) ) ;
75167672 Andy McCall 1 month ago
1362
assert_eq! (
1363
s3.get ( & refs[ 6 ] .0 ) .unwrap ( ) .1 ,
1364
refs[ 6 ] .1. as_bytes ( ) ,
1365
" other objects are unaffected"
1366
) ;
d02e545d Andy McCall 1 month ago ↶
1367
}
1368
1369
# [ test ]
cdb5beba Andy McCall 1 month ago ↶
1370
fn nocompress_chunks_pack_raw ( ) {
1371
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1372
let s = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1373
let body = vec! [ b ' a' ; 10_000 ] ;
1374
let raw = s.put_chunk_nocompress ( & body) .unwrap ( ) ;
1375
let compressible = s.put ( ObjectType:: Chunk, & vec! [ b ' b' ; 10_000 ] ) .unwrap ( ) ;
1376
s.pack_loose ( ) .unwrap ( ) ;
1377
let packs = s.packs.borrow ( ) ;
1378
let ( i, e) = s.find_packed ( & raw) .unwrap ( ) ;
1379
assert_eq! ( ( e.flags, e.stored_len as usize ) , ( 0 , 10_000 ) ) ;
1380
let ( _ , c) = s.find_packed ( & compressible) .unwrap ( ) ;
1381
assert_eq! ( c.flags, FLAG_ZSTD ) ;
1382
assert! ( c.stored_len < 10_000 ) ;
1383
assert_eq! ( packs[ i] .read ( & raw, e) .unwrap ( ) .1 , body) ;
1384
}
1385
1386
# [ test ]
135212de Andy McCall 1 month ago ↶
1387
fn many_packs_merge_into_one ( ) {
1388
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1389
let s = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1390
let mut refs = Vec :: new( ) ;
1391
for i in 0 .. ( MAX_PACKS + 2 ) {
75167672 Andy McCall 1 month ago
1392
refs.push (
1393
s.put ( ObjectType:: Chunk, format! ( " pack {i} " ) .as_bytes ( ) )
1394
.unwrap ( ) ,
1395
) ;
135212de Andy McCall 1 month ago ↶
1396
s.pack_loose ( ) .unwrap ( ) ;
1397
}
1398
assert_eq! ( s.pack_count ( ) , MAX_PACKS + 2 ) ;
1399
assert_eq! ( s.merge_packs ( ) .unwrap ( ) , MAX_PACKS + 2 ) ;
1400
assert_eq! ( s.pack_count ( ) , 1 ) ;
1401
assert_eq! ( s.merge_packs ( ) .unwrap ( ) , 0 ) ;
1402
for ( i, r) in refs.iter ( ) .enumerate ( ) {
1403
assert_eq! ( s.get ( r) .unwrap ( ) .1 , format! ( " pack {i} " ) .as_bytes ( ) ) ;
1404
}
1405
assert_eq! ( Store:: open( dir.path ( ) ) .unwrap ( ) .pack_count ( ) , 1 ) ;
1406
}
1407
1408
# [ test ]
ba160f60 Andy McCall 1 month ago ↶
1409
fn prune_removes_unreachable_loose_and_packed_objects ( ) {
1410
use std:: collections:: HashSet;
1411
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1412
let s = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1413
let keep_a = s.put ( ObjectType:: Chunk, b " keep a" ) .unwrap ( ) ;
1414
let drop_a = s.put ( ObjectType:: Chunk, b " drop a" ) .unwrap ( ) ;
1415
s.pack_loose ( ) .unwrap ( ) ;
1416
let keep_b = s.put ( ObjectType:: Chunk, b " keep b" ) .unwrap ( ) ;
1417
let drop_b = s.put ( ObjectType:: Chunk, b " drop b" ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1418
let sk = crate :: identity:: SecretKey {
1419
ed_seed: [ 1 ; 32 ] ,
1420
ml_seed: [ 2 ; 32 ] ,
1421
} ;
ba160f60 Andy McCall 1 month ago ↶
1422
let id = sk.identity ( " a" , " b" , 0 ) ;
1423
let key = s.put_identity ( & id) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1424
s.put_signature (
1425
& drop_b,
1426
& sk.sign ( crate :: identity:: Domain:: Change, & key, & drop_b) ,
1427
)
1428
.unwrap ( ) ;
ba160f60 Andy McCall 1 month ago ↶
1429
s.index_seal ( & drop_a, & ObjectRef( [ 3 ; 32 ] ) ) .unwrap ( ) ;
1430
let keep: HashSet< ObjectRef> = [ keep_a, keep_b, key] .into_iter ( ) .collect ( ) ;
1431
75167672 Andy McCall 1 month ago
1432
let r = s
1433
.prune ( & keep, std:: time:: Duration:: from_secs( 3600 ) )
1434
.unwrap ( ) ;
ba160f60 Andy McCall 1 month ago ↶
1435
assert_eq! ( ( r.objects, r.deferred) , ( 0 , 2 ) ) ;
1436
let r = s.prune ( & keep, std:: time:: Duration:: ZERO ) .unwrap ( ) ;
1437
assert_eq! ( r.objects, 2 , " {r:?}" ) ;
1438
assert! ( s.has ( & keep_a) & & s.has ( & keep_b) & & s.has ( & key) ) ;
1439
assert! ( ! s.has ( & drop_a) & & ! s.has ( & drop_b) ) ;
1440
assert! ( s.signatures ( & drop_b) .unwrap ( ) .is_empty ( ) ) ;
1441
assert! ( s.seals_for ( & drop_a) .unwrap ( ) .is_empty ( ) ) ;
1442
assert_eq! ( s.pack_count ( ) , 1 ) ;
1443
assert_eq! ( s.get ( & keep_a) .unwrap ( ) .1 , b " keep a" ) ;
1444
assert_eq! ( s.list ( ) .unwrap ( ) .len ( ) , 3 ) ;
1445
}
1446
1447
# [ test ]
1983686f Andy McCall 1 month ago ↶
1448
fn signatures_are_stored_beside_objects ( ) {
1449
use crate :: identity:: { Domain, SecretKey} ;
1450
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1451
let s = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1452
let r = s.put ( ObjectType:: Chunk, b " x" ) .unwrap ( ) ;
1453
assert! ( s.signatures ( & r) .unwrap ( ) .is_empty ( ) ) ;
75167672 Andy McCall 1 month ago
1454
let sk = SecretKey {
1455
ed_seed: [ 1 ; 32 ] ,
1456
ml_seed: [ 2 ; 32 ] ,
1457
} ;
1983686f Andy McCall 1 month ago ↶
1458
let id = sk.identity ( " a" , " b" , 0 ) ;
1459
let key = s.put_identity ( & id) .unwrap ( ) ;
1460
assert_eq! ( s.get_identity ( & key) .unwrap ( ) , id) ;
1461
let sig = sk.sign ( Domain:: Change, & key, & r) ;
1462
s.put_signature ( & r, & sig) .unwrap ( ) ;
1463
s.put_signature ( & r, & sig) .unwrap ( ) ;
1464
assert_eq! ( s.signatures ( & r) .unwrap ( ) , vec! [ sig] ) ;
1465
1466
assert_eq! ( s.get ( & r) .unwrap ( ) .1 , b " x" ) ;
1467
}
1468
1469
# [ test ]
ff0af1ee unknown 1 month ago
1470
fn change_round_trip_through_store ( ) {
1471
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1472
let s = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1473
let c = crate :: objects:: tests:: sample_change( ) ;
1474
let r = s.put_change ( & c) .unwrap ( ) ;
1475
assert_eq! ( r, c.hash ( ) ) ;
1476
assert_eq! ( s.get_change ( & r) .unwrap ( ) , c) ;
1477
}
2b872d07 Andy McCall 1 month ago ↶
1478
1479
fn xorshift ( state : & mut u64 ) -> u64 {
1480
* state ^ = * state < < 13 ;
1481
* state ^ = * state > > 7 ;
1482
* state ^ = * state < < 17 ;
1483
* state
1484
}
1485
1486
1487
1488
1489
# [ test ]
1490
fn corrupted_packs_and_indexes_fail_cleanly ( ) {
1491
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1492
let objects = dir.path ( ) .join ( " objects" ) ;
1493
let s = Store:: open( & objects) .unwrap ( ) ;
1494
let mut refs = Vec :: new( ) ;
1495
let mut seed = 0x9e3779b97f4a7c15 u64 ;
1496
for i in 0 .. 40 u32 {
1497
let len = ( xorshift ( & mut seed) % 5000 ) as usize ;
1498
let mut body = vec! [ 0 u8 ; len] ;
1499
for b in body.iter_mut ( ) {
75167672 Andy McCall 1 month ago
1500
* b = if i % 2 = = 0 {
1501
( xorshift ( & mut seed) & 0xff ) as u8
1502
} else {
1503
b ' a' + ( i % 26 ) as u8
1504
} ;
2b872d07 Andy McCall 1 month ago ↶
1505
}
1506
refs.push ( s.put ( ObjectType:: Chunk, & body) .unwrap ( ) ) ;
1507
}
1508
s.pack_loose ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1509
let pack = fs:: read_dir( & objects)
1510
.unwrap ( )
1511
.filter_map ( | e | e.ok ( ) .map ( | e | e.path ( ) ) )
1512
.find ( | p | p.extension ( ) .is_some_and ( | x | x = = " pack" ) )
1513
.unwrap ( ) ;
2b872d07 Andy McCall 1 month ago ↶
1514
let idx = pack.with_extension ( " idx" ) ;
1515
let pristine_pack = fs:: read( & pack) .unwrap ( ) ;
1516
let pristine_idx = fs:: read( & idx) .unwrap ( ) ;
1517
1518
for round in 0 .. 300 u32 {
75167672 Andy McCall 1 month ago
1519
let ( path, pristine) = if round % 2 = = 0 {
1520
( & pack, & pristine_pack)
1521
} else {
1522
( & idx, & pristine_idx)
1523
} ;
2b872d07 Andy McCall 1 month ago ↶
1524
let mut data = pristine.clone ( ) ;
1525
match xorshift ( & mut seed) % 4 {
1526
0 => {
1527
let at = ( xorshift ( & mut seed) as usize ) % data.len ( ) ;
1528
data[ at] ^ = 1 < < ( xorshift ( & mut seed) % 8 ) ;
1529
}
1530
1 => {
1531
let at = ( xorshift ( & mut seed) as usize ) % data.len ( ) ;
1532
data[ at] = ( xorshift ( & mut seed) & 0xff ) as u8 ;
1533
}
1534
2 => {
1535
let keep = ( xorshift ( & mut seed) as usize ) % data.len ( ) ;
1536
data.truncate ( keep) ;
1537
}
1538
_ => {
1539
let at = ( xorshift ( & mut seed) as usize ) % data.len ( ) ;
1540
let n = ( ( xorshift ( & mut seed) % 8 ) + 1 ) as usize ;
1541
for k in 0 .. n.min ( data.len ( ) - at) {
1542
data[ at + k] = 0xff ;
1543
}
1544
}
1545
}
1546
fs:: write( path, & data) .unwrap ( ) ;
1547
1548
1549
if let Ok ( s2) = Store:: open( & objects) {
1550
for r in & refs {
1551
match s2.get ( r) {
75167672 Andy McCall 1 month ago
1552
Ok ( ( ty, body) ) => assert_eq! (
1553
hash_object ( ty, & body) ,
1554
* r,
1555
" a returned object always matches its reference"
1556
) ,
1557
Err ( StoreError:: Corrupt( .. ) )
1558
| Err ( StoreError:: NotFound( _ ) )
1559
| Err ( StoreError:: Io( _ ) ) => { }
2b872d07 Andy McCall 1 month ago ↶
1560
Err ( e) => panic! ( " unexpected error {e}" ) ,
1561
}
1562
}
1563
}
1564
fs:: write( path, pristine) .unwrap ( ) ;
1565
}
1566
1567
let s3 = Store:: open( & objects) .unwrap ( ) ;
1568
for r in & refs {
1569
let ( ty, body) = s3.get ( r) .unwrap ( ) ;
1570
assert_eq! ( hash_object ( ty, & body) , * r) ;
1571
}
1572
}
8f10a07a Andy McCall 1 month ago ↶
1573
1574
1575
1576
# [ test ]
1577
fn batches_write_loose_or_one_pack ( ) {
1578
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1579
let s = Store:: open( dir.path ( ) .join ( " objects" ) ) .unwrap ( ) ;
1580
let outer = s.begin_batch ( ) .unwrap ( ) ;
1581
let a = s.put ( ObjectType:: Chunk, b " small a" ) .unwrap ( ) ;
1582
{
1583
let inner = s.begin_batch ( ) .unwrap ( ) ;
1584
let b = s.put ( ObjectType:: Chunk, b " small b" ) .unwrap ( ) ;
1585
assert! ( s.has ( & b) ) ;
1586
assert_eq! ( s.get ( & b) .unwrap ( ) .1 , b " small b" ) ;
1587
inner.finish ( ) .unwrap ( ) ;
75167672 Andy McCall 1 month ago
1588
assert_eq! (
1589
s.loose_count ( ) .unwrap ( ) ,
1590
0 ,
1591
" the inner batch defers to the outer one"
1592
) ;
8f10a07a Andy McCall 1 month ago ↶
1593
}
1594
assert_eq! ( s.get ( & a) .unwrap ( ) .1 , b " small a" ) ;
75167672 Andy McCall 1 month ago
1595
assert_eq! (
1596
s.put ( ObjectType:: Chunk, b " small a" ) .unwrap ( ) ,
1597
a,
1598
" idempotent while pending"
1599
) ;
8f10a07a Andy McCall 1 month ago ↶
1600
outer.finish ( ) .unwrap ( ) ;
1601
assert_eq! ( s.loose_count ( ) .unwrap ( ) , 2 ) ;
1602
assert_eq! ( s.pack_count ( ) , 0 ) ;
1603
assert_eq! ( s.get ( & a) .unwrap ( ) .1 , b " small a" ) ;
1604
1605
1606
let big = s.begin_batch ( ) .unwrap ( ) ;
1607
let mut refs = Vec :: new( ) ;
1608
let mut seed = 7 u64 ;
1609
for i in 0 .. 300 u32 {
1610
let mut body = vec! [ b ' z' ; 40_000 ] ;
1611
if i % 3 = = 0 {
1612
for b in body.iter_mut ( ) {
1613
* b = ( xorshift ( & mut seed) & 0xff ) as u8 ;
1614
}
1615
}
1616
body[ .. 4 ] .copy_from_slice ( & i.to_le_bytes ( ) ) ;
75167672 Andy McCall 1 month ago
1617
let r = if i % 5 = = 0 {
1618
s.put_chunk_nocompress ( & body) .unwrap ( )
1619
} else {
1620
s.put ( ObjectType:: Chunk, & body) .unwrap ( )
1621
} ;
1622
assert_eq! (
1623
s.get ( & r) .unwrap ( ) .1 ,
1624
body,
1625
" readable while pending (object {i})"
1626
) ;
8f10a07a Andy McCall 1 month ago ↶
1627
refs.push ( ( r, body) ) ;
1628
}
1629
assert! ( s.has ( & refs[ 0 ] .0 ) ) ;
1630
big.finish ( ) .unwrap ( ) ;
1631
assert_eq! ( s.pack_count ( ) , 1 ) ;
75167672 Andy McCall 1 month ago
1632
assert_eq! (
1633
s.loose_count ( ) .unwrap ( ) ,
1634
2 ,
1635
" nothing from the big batch is loose"
1636
) ;
1637
assert! ( fs:: read_dir( dir.path ( ) .join ( " objects" ) )
1638
.unwrap ( )
1639
.flatten ( )
1640
.all ( | e | ! e.file_name ( ) .to_string_lossy ( ) .starts_with ( " tmp-" ) ) ) ;
8f10a07a Andy McCall 1 month ago ↶
1641
let s2 = Store:: open( dir.path ( ) .join ( " objects" ) ) .unwrap ( ) ;
1642
for ( r, body) in & refs {
1643
assert_eq! ( s2.get ( r) .unwrap ( ) .1 , * body) ;
1644
}
1645
1646
{
1647
let _b = s2.begin_batch ( ) .unwrap ( ) ;
1648
s2.put ( ObjectType:: Chunk, b " dropped" ) .unwrap ( ) ;
1649
}
75167672 Andy McCall 1 month ago
1650
assert_eq! (
1651
s2.get ( & hash_object ( ObjectType:: Chunk, b " dropped" ) )
1652
.unwrap ( )
1653
.1 ,
1654
b " dropped"
1655
) ;
afaba5f1 Andy McCall 1 month ago ↶
1656
}
1657
1658
# [ test ]
1659
fn get_many_reads_loose_and_packed_objects_in_order ( ) {
1660
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1661
let s = Store:: open( dir.path ( ) .join ( " objects" ) ) .unwrap ( ) ;
1662
let packed: Vec< ObjectRef> = ( 0 .. 20 )
1663
.map ( | i | {
1664
s.put ( ObjectType:: Chunk, format! ( " packed {i} " ) .as_bytes ( ) )
1665
.unwrap ( )
1666
} )
1667
.collect ( ) ;
1668
s.pack_loose ( ) .unwrap ( ) ;
1669
let loose: Vec< ObjectRef> = ( 0 .. 20 )
1670
.map ( | i | {
1671
s.put ( ObjectType:: Chunk, format! ( " loose {i} " ) .as_bytes ( ) )
1672
.unwrap ( )
1673
} )
1674
.collect ( ) ;
1675
let missing = hash_object ( ObjectType:: Chunk, b " never stored" ) ;
1676
let mut refs: Vec< ObjectRef> = Vec :: new( ) ;
1677
for i in 0 .. 20 {
1678
refs.push ( loose[ i] ) ;
1679
refs.push ( packed[ i] ) ;
1680
}
1681
refs.push ( missing) ;
1682
let got = s.get_many ( & refs) ;
1683
assert_eq! ( got.len ( ) , refs.len ( ) ) ;
1684
for ( i, r) in refs.iter ( ) .enumerate ( ) .take ( 40 ) {
1685
let want = if i % 2 = = 0 {
1686
format! ( " loose {} " , i / 2 )
1687
} else {
1688
format! ( " packed {} " , i / 2 )
1689
} ;
1690
let ( ty, body) = got[ i] .as_ref ( ) .unwrap ( ) ;
1691
assert_eq! (
1692
( * ty, body.as_slice ( ) ) ,
1693
( ObjectType:: Chunk, want.as_bytes ( ) ) ,
1694
" {r:?}"
1695
) ;
1696
}
1697
assert! ( matches! ( got[ 40 ] , Err ( StoreError:: NotFound( _ ) ) ) ) ;
a9f686ed Andy McCall 1 month ago ↶
1698
}
1699
}
1700
1701
# [ cfg ( test ) ]
1702
mod batch_tests {
1703
use super :: * ;
1704
1705
# [ test ]
1706
fn a_large_batch_packs_every_object_and_indexes_them_all ( ) {
1707
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1708
let store = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1709
let mut refs = Vec :: new( ) ;
1710
{
1711
let batch = store.begin_batch ( ) .unwrap ( ) ;
1712
for i in 0 .. 1000 u32 {
1713
let body = format! ( " object {i} {} " , " x" .repeat ( 20_000 + i as usize ) ) .into_bytes ( ) ;
1714
refs.push ( store.put ( ObjectType:: Blob, & body) .unwrap ( ) ) ;
1715
}
1716
batch.finish ( ) .unwrap ( ) ;
1717
}
1718
let again = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1719
for r in & refs {
1720
again
1721
.get ( r)
1722
.unwrap_or_else ( | e | panic! ( " {}: {e}" , r.short ( 12 ) ) ) ;
1723
}
1724
}
1725
}
1726
1727
# [ cfg ( test ) ]
1728
mod size_tests {
1729
use super :: * ;
1730
1731
# [ test ]
1732
fn an_object_over_the_limit_is_refused_and_nothing_is_written ( ) {
1733
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1734
let store = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1735
let big = vec! [ b ' x' ; MAX_OBJECT_LEN as usize + 1 ] ;
1736
{
1737
let batch = store.begin_batch ( ) .unwrap ( ) ;
1738
store.put ( ObjectType:: Blob, b " small" ) .unwrap ( ) ;
1739
let e = store.put ( ObjectType:: Change, & big) .unwrap_err ( ) ;
1740
assert! (
1741
matches! ( e, StoreError:: TooLarge { kind: " change" , .. } ) ,
1742
" {e}"
1743
) ;
1744
assert! ( e.to_string ( ) .contains ( " commit it in parts" ) , " {e}" ) ;
1745
batch.finish ( ) .unwrap ( ) ;
1746
}
1747
1748
let again = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1749
again.get ( & hash_object ( ObjectType:: Blob, b " small" ) ) .unwrap ( ) ;
6f8aeb99 Andy McCall 1 month ago ↶
1750
}
1751
}
1752
1753
# [ cfg ( test ) ]
1754
mod text_tests {
1755
use super :: * ;
1756
use crate :: objects:: { AtomRef, Edit, CHANGE_VERSION } ;
1757
1758
fn large_change ( ) -> Change {
1759
let mut c = crate :: objects:: tests:: sample_change( ) ;
1760
assert_eq! ( c.version, CHANGE_VERSION ) ;
1761
c.edits[ 0 ] = Edit:: Insert {
1762
node: crate :: ids:: NodeId( [ 7 ; 16 ] ) ,
1763
after: AtomRef:: Start,
1764
before: AtomRef:: End,
1765
lines: ( 0 .. 2000 )
1766
.map ( | i | format! ( " line {i} \n " ) .into_bytes ( ) )
1767
.collect ( ) ,
1768
} ;
1769
c
1770
}
1771
1772
# [ test ]
1773
fn a_large_insert_is_stored_beside_its_change_and_read_back ( ) {
1774
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1775
let s = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1776
let c = large_change ( ) ;
1777
let r = s.put_change ( & c) .unwrap ( ) ;
1778
assert_eq! ( r, c.hash ( ) ) ;
1779
let ( _ , body) = s.get ( & r) .unwrap ( ) ;
1780
assert! (
1781
body.len ( ) < 1024 ,
1782
" the change holds a reference, not {} bytes of lines" ,
1783
body.len ( )
1784
) ;
1785
let ( back, parts) = s.get_change_parts ( & r) .unwrap ( ) ;
1786
assert_eq! ( back, c) ;
1787
1788
assert_eq! ( parts.len ( ) , 2 ) ;
1789
for p in & parts {
1790
assert! ( s.has ( p) ) ;
1791
}
1792
1793
let n = s.list ( ) .unwrap ( ) .len ( ) ;
1794
s.put_change ( & c) .unwrap ( ) ;
1795
assert_eq! ( s.list ( ) .unwrap ( ) .len ( ) , n) ;
1796
}
1797
1798
# [ test ]
1799
fn a_change_whose_text_blob_is_missing_is_reported_not_misread ( ) {
1800
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1801
let s = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1802
let c = large_change ( ) ;
1803
let r = s.put_change ( & c) .unwrap ( ) ;
1804
let ( _ , parts) = s.get_change_parts ( & r) .unwrap ( ) ;
1805
fs:: remove_file( s.path_for ( & parts[ 0 ] ) ) .unwrap ( ) ;
1806
let e = s.get_change ( & r) .unwrap_err ( ) ;
1807
assert! (
1808
matches! ( & e, StoreError:: Corrupt( h, why) if * h = = r & & why.contains ( " missing" ) ) ,
1809
" {e}"
1810
) ;
192fad1e Andy McCall 1 month ago ↶
1811
}
1812
}
1813
1814
# [ cfg ( test ) ]
1815
mod put_many_tests {
1816
use super :: * ;
1817
1818
# [ test ]
1819
fn many_objects_in_a_batch_are_compressed_together_and_all_read_back ( ) {
1820
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
1821
let s = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1822
let before = s.put ( ObjectType:: Chunk, b " already here" ) .unwrap ( ) ;
1823
let bodies: Vec< Vec< u8 > > = ( 0 .. 200 u32 )
1824
.map ( | i | format! ( " chunk {i} " ) .repeat ( 6000 ) .into_bytes ( ) )
1825
.collect ( ) ;
1826
let mut objects: Vec< ( ObjectType, & [u8 ]) > = bodies
1827
.iter ( )
1828
.map ( | b | ( ObjectType:: Chunk, b.as_slice ( ) ) )
1829
.collect ( ) ;
1830
objects.push ( ( ObjectType:: Chunk, bodies[ 0 ] .as_slice ( ) ) ) ;
1831
objects.push ( ( ObjectType:: Chunk, b " already here" ) ) ;
1832
let refs = {
1833
let batch = s.begin_batch ( ) .unwrap ( ) ;
1834
let refs = s.put_many ( & objects) .unwrap ( ) ;
1835
batch.finish ( ) .unwrap ( ) ;
1836
refs
1837
} ;
1838
assert_eq! ( refs.len ( ) , objects.len ( ) ) ;
1839
assert_eq! ( refs[ 200 ] , refs[ 0 ] , " a repeat is the same object" ) ;
1840
assert_eq! ( refs[ 201 ] , before) ;
1841
let again = Store:: open( dir.path ( ) ) .unwrap ( ) ;
1842
for ( r, b) in refs.iter ( ) .zip ( & objects) {
1843
assert_eq! ( again.get ( r) .unwrap ( ) .1 , b.1 ) ;
1844
}
8f10a07a Andy McCall 1 month ago ↶
1845
}
ff0af1ee unknown 1 month ago
1846
}