44//! For a lower memory footprint, consider using [`crate::corpus::CachedOnDiskCorpus`]
55//! which only stores a certain number of [`Testcase`]s and removes additional ones in a FIFO manner.
66
7- use alloc:: string:: String ;
7+ use alloc:: string:: { String , ToString } ;
88use core:: cell:: { Ref , RefCell , RefMut } ;
99use std:: {
1010 fs,
1111 fs:: { File , OpenOptions } ,
1212 io,
13- io:: Write ,
13+ io:: { Read , Seek , SeekFrom , Write } ,
1414 path:: { Path , PathBuf } ,
1515} ;
1616
17+ use fs2:: FileExt ;
1718#[ cfg( feature = "gzip" ) ]
1819use libafl_bolts:: compress:: GzipCompressor ;
1920use serde:: { Deserialize , Serialize } ;
@@ -33,7 +34,11 @@ use crate::{
3334/// If the create fails for _any_ reason, including, but not limited to, a preexisting existing file of that name,
3435/// it will instead return the respective [`io::Error`].
3536fn create_new < P : AsRef < Path > > ( path : P ) -> Result < File , io:: Error > {
36- OpenOptions :: new ( ) . write ( true ) . create_new ( true ) . open ( path)
37+ OpenOptions :: new ( )
38+ . write ( true )
39+ . read ( true )
40+ . create_new ( true )
41+ . open ( path)
3742}
3843
3944/// Tries to create the given `path` and returns `None` _only_ if the file already existed.
8590 fn add ( & mut self , testcase : Testcase < I > ) -> Result < CorpusId , Error > {
8691 let id = self . inner . add ( testcase) ?;
8792 let testcase = & mut self . get ( id) . unwrap ( ) . borrow_mut ( ) ;
88- self . save_testcase ( testcase, id ) ?;
93+ self . save_testcase ( testcase, Some ( id ) ) ?;
8994 * testcase. input_mut ( ) = None ;
9095 Ok ( id)
9196 }
95100 fn add_disabled ( & mut self , testcase : Testcase < I > ) -> Result < CorpusId , Error > {
96101 let id = self . inner . add_disabled ( testcase) ?;
97102 let testcase = & mut self . get_from_all ( id) . unwrap ( ) . borrow_mut ( ) ;
98- self . save_testcase ( testcase, id ) ?;
103+ self . save_testcase ( testcase, Some ( id ) ) ?;
99104 * testcase. input_mut ( ) = None ;
100105 Ok ( id)
101106 }
@@ -106,7 +111,7 @@ where
106111 let entry = self . inner . replace ( id, testcase) ?;
107112 self . remove_testcase ( & entry) ?;
108113 let testcase = & mut self . get ( id) . unwrap ( ) . borrow_mut ( ) ;
109- self . save_testcase ( testcase, id ) ?;
114+ self . save_testcase ( testcase, Some ( id ) ) ?;
110115 * testcase. input_mut ( ) = None ;
111116 Ok ( entry)
112117 }
@@ -309,12 +314,19 @@ impl<I> InMemoryOnDiskCorpus<I> {
309314
310315 /// Sets the filename for a [`Testcase`].
311316 /// If an error gets returned from the corpus (i.e., file exists), we'll have to retry with a different filename.
317+ /// Renaming testcases will most likely cause duplicate testcases to not be handled correctly
318+ /// if testcases with the same input are not given the same filename.
319+ /// Only rename when you know what you are doing.
312320 #[ inline]
313321 pub fn rename_testcase (
314322 & self ,
315323 testcase : & mut Testcase < I > ,
316324 filename : String ,
317- ) -> Result < ( ) , Error > {
325+ id : Option < CorpusId > ,
326+ ) -> Result < ( ) , Error >
327+ where
328+ I : Input ,
329+ {
318330 if testcase. filename ( ) . is_some ( ) {
319331 // We are renaming!
320332
@@ -327,36 +339,10 @@ impl<I> InMemoryOnDiskCorpus<I> {
327339 return Ok ( ( ) ) ;
328340 }
329341
330- if self . locking {
331- let new_lock_filename = format ! ( ".{new_filename}.lafl_lock" ) ;
332-
333- // Try to create lock file for new testcases
334- if let Err ( err) = create_new ( self . dir_path . join ( & new_lock_filename) ) {
335- * testcase. filename_mut ( ) = Some ( old_filename) ;
336- return Err ( Error :: illegal_state ( format ! (
337- "Unable to create lock file {new_lock_filename} for new testcase: {err}"
338- ) ) ) ;
339- }
340- }
341-
342342 let new_file_path = self . dir_path . join ( & new_filename) ;
343-
344- fs:: rename ( testcase. file_path ( ) . as_ref ( ) . unwrap ( ) , & new_file_path) ?;
345-
346- let new_metadata_path = {
347- if let Some ( old_metadata_path) = testcase. metadata_path ( ) {
348- // We have metadata. Let's rename it.
349- let new_metadata_path = self . dir_path . join ( format ! ( ".{new_filename}.metadata" ) ) ;
350- fs:: rename ( old_metadata_path, & new_metadata_path) ?;
351-
352- Some ( new_metadata_path)
353- } else {
354- None
355- }
356- } ;
357-
358- * testcase. metadata_path_mut ( ) = new_metadata_path;
343+ self . remove_testcase ( testcase) ?;
359344 * testcase. filename_mut ( ) = Some ( new_filename) ;
345+ self . save_testcase ( testcase, id) ?;
360346 * testcase. file_path_mut ( ) = Some ( new_file_path) ;
361347
362348 Ok ( ( ) )
@@ -367,42 +353,54 @@ impl<I> InMemoryOnDiskCorpus<I> {
367353 }
368354 }
369355
370- fn save_testcase ( & self , testcase : & mut Testcase < I > , id : CorpusId ) -> Result < ( ) , Error >
356+ fn save_testcase ( & self , testcase : & mut Testcase < I > , id : Option < CorpusId > ) -> Result < ( ) , Error >
371357 where
372358 I : Input ,
373359 {
374- let file_name_orig = testcase. filename_mut ( ) . take ( ) . unwrap_or_else ( || {
360+ let file_name = testcase. filename_mut ( ) . take ( ) . unwrap_or_else ( || {
375361 // TODO walk entry metadata to ask for pieces of filename (e.g. :havoc in AFL)
376- testcase. input ( ) . as_ref ( ) . unwrap ( ) . generate_name ( Some ( id ) )
362+ testcase. input ( ) . as_ref ( ) . unwrap ( ) . generate_name ( id )
377363 } ) ;
378364
379- // New testcase, we need to save it.
380- let mut file_name = file_name_orig. clone ( ) ;
381-
382- let mut ctr = 2 ;
383- let file_name = if self . locking {
384- loop {
385- let lockfile_name = format ! ( ".{file_name}.lafl_lock" ) ;
386- let lockfile_path = self . dir_path . join ( lockfile_name) ;
387-
388- if try_create_new ( lockfile_path) ?. is_some ( ) {
389- break file_name;
390- }
365+ let mut ctr = String :: new ( ) ;
366+ if self . locking {
367+ let lockfile_name = format ! ( ".{file_name}" ) ;
368+ let lockfile_path = self . dir_path . join ( lockfile_name) ;
369+
370+ let mut lockfile = try_create_new ( & lockfile_path) ?. unwrap_or (
371+ OpenOptions :: new ( )
372+ . write ( true )
373+ . read ( true )
374+ . open ( & lockfile_path) ?,
375+ ) ;
376+ lockfile. lock_exclusive ( ) ?;
377+
378+ lockfile. read_to_string ( & mut ctr) ?;
379+ ctr = if ctr. is_empty ( ) {
380+ String :: from ( "1" )
381+ } else {
382+ ( ctr. trim ( ) . parse :: < u32 > ( ) ? + 1 ) . to_string ( )
383+ } ;
391384
392- file_name = format ! ( "{file_name_orig}-{ctr}" ) ;
393- ctr += 1 ;
394- }
395- } else {
396- file_name
397- } ;
385+ lockfile. seek ( SeekFrom :: Start ( 0 ) ) ?;
386+ lockfile. write_all ( ctr. as_bytes ( ) ) ?;
387+ }
398388
399389 if testcase. file_path ( ) . is_none ( ) {
400390 * testcase. file_path_mut ( ) = Some ( self . dir_path . join ( & file_name) ) ;
401391 }
402392 * testcase. filename_mut ( ) = Some ( file_name) ;
403393
404394 if self . meta_format . is_some ( ) {
405- let metafile_name = format ! ( ".{}.metadata" , testcase. filename( ) . as_ref( ) . unwrap( ) ) ;
395+ let metafile_name = if self . locking {
396+ format ! (
397+ ".{}_{}.metadata" ,
398+ testcase. filename( ) . as_ref( ) . unwrap( ) ,
399+ ctr
400+ )
401+ } else {
402+ format ! ( ".{}.metadata" , testcase. filename( ) . as_ref( ) . unwrap( ) )
403+ } ;
406404 let metafile_path = self . dir_path . join ( & metafile_name) ;
407405 let mut tmpfile_path = metafile_path. clone ( ) ;
408406 tmpfile_path. set_file_name ( format ! ( ".{metafile_name}.tmp" , ) ) ;
@@ -445,15 +443,36 @@ impl<I> InMemoryOnDiskCorpus<I> {
445443
446444 fn remove_testcase ( & self , testcase : & Testcase < I > ) -> Result < ( ) , Error > {
447445 if let Some ( filename) = testcase. filename ( ) {
446+ let mut ctr = String :: new ( ) ;
447+ if self . locking {
448+ let lockfile_path = self . dir_path . join ( format ! ( ".{filename}" ) ) ;
449+ let mut lockfile = OpenOptions :: new ( )
450+ . write ( true )
451+ . read ( true )
452+ . open ( & lockfile_path) ?;
453+
454+ lockfile. lock_exclusive ( ) ?;
455+ lockfile. read_to_string ( & mut ctr) ?;
456+ ctr = ctr. trim ( ) . to_string ( ) ;
457+
458+ if ctr == "1" {
459+ FileExt :: unlock ( & lockfile) ?;
460+ drop ( fs:: remove_file ( lockfile_path) ) ;
461+ } else {
462+ lockfile. seek ( SeekFrom :: Start ( 0 ) ) ?;
463+ lockfile. write_all ( & ( ctr. parse :: < u32 > ( ) ? - 1 ) . to_le_bytes ( ) ) ?;
464+ return Ok ( ( ) ) ;
465+ }
466+ }
467+
448468 fs:: remove_file ( self . dir_path . join ( filename) ) ?;
449469 if self . meta_format . is_some ( ) {
450- fs:: remove_file ( self . dir_path . join ( format ! ( ".{filename}.metadata" ) ) ) ?;
470+ if self . locking {
471+ fs:: remove_file ( self . dir_path . join ( format ! ( ".{filename}_{ctr}.metadata" ) ) ) ?;
472+ } else {
473+ fs:: remove_file ( self . dir_path . join ( format ! ( ".{filename}.metadata" ) ) ) ?;
474+ }
451475 }
452- // also try to remove the corresponding `.lafl_lock` file if it still exists
453- // (even though it shouldn't exist anymore, at this point in time)
454- drop ( fs:: remove_file (
455- self . dir_path . join ( format ! ( ".{filename}.lafl_lock" ) ) ,
456- ) ) ;
457476 }
458477 Ok ( ( ) )
459478 }
0 commit comments