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