Fix: Deleting an Entry could leave artifacts in cache

This patch fixes a bug we did not even hit (yet). It is: When deleting
an Entry from the store, this could potentially leave artifacts in the
cache.

Szenario: An Entry, which was loaded (via `Store::get()` for example),
gets `Store::delete()`ed twice. The first call would work as expected,
but leave the Entry in the Store cache. The second call would then fail,
as the Entry is already removed on the FS, but still in the cache. This
would fail - which is the right thing to do here - but with the wrong
error (with a FileError rather than a FileNotFound error).

This patch fixes this.

First of all, the appropriate `PathBuf` object is calculated in all
cases, as this object is needed to check whether the file is actually
there (which could be the case if the Entry is in the cache and if it is
not).

If the entry is in the cache and is borrowed: error. If not, remove the
entry from the cache. Afterwards the file is deleted on disk.

If the entry is not in the cache, but the file exists, the file is removed.
If the file does not exist: error.

Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
This commit is contained in:
Matthias Beyer 2018-06-08 01:42:28 +02:00
parent 9315a23754
commit b774ac0e67

View file

@ -444,7 +444,7 @@ impl Store {
StoreEntry::new(id, &self.backend)?.get_entry() StoreEntry::new(id, &self.backend)?.get_entry()
} }
/// Delete an entry /// Delete an entry and the corrosponding file on disk
/// ///
/// # Return value /// # Return value
/// ///
@ -460,6 +460,12 @@ impl Store {
debug!("Deleting id: '{}'", id); debug!("Deleting id: '{}'", id);
// Small optimization: We need the pathbuf for deleting, but when calling
// StoreId::exists(), a PathBuf object gets allocated. So we simply get a
// PathBuf here, check whether it is there and if it is, we can re-use it to
// delete the filesystem file.
let pb = id.clone().into_pathbuf()?;
{ {
let mut entries = self let mut entries = self
.entries .entries
@ -467,44 +473,41 @@ impl Store {
.map_err(|_| SE::from_kind(SEK::LockPoisoned)) .map_err(|_| SE::from_kind(SEK::LockPoisoned))
.chain_err(|| SEK::DeleteCallError(id.clone()))?; .chain_err(|| SEK::DeleteCallError(id.clone()))?;
// if the entry is currently modified by the user, we cannot drop it let do_remove = match entries.get(&id) {
match entries.get(&id) { Some(e) => if e.is_borrowed() { // entry is currently borrowed, we cannot delete it
return Err(SE::from_kind(SEK::IdLocked)).chain_err(|| SEK::DeleteCallError(id));
// false
} else { // Entry is in the cache
// Remove Entry from the cache
true
},
None => { None => {
// The entry is not in the internal cache. But maybe on the filesystem? // The entry is not in the internal cache. But maybe on the filesystem?
debug!("Seems like {:?} is not in the internal cache", id); debug!("Seems like {:?} is not in the internal cache", id);
// Small optimization: We need the pathbuf for deleting, but when calling if !self.backend.exists(&pb)? {
// StoreId::exists(), a PathBuf object gets allocated. So we simply get a
// PathBuf here, check whether it is there and if it is, we can re-use it to
// delete the filesystem file.
let pb = id.clone().into_pathbuf()?;
if self.backend.exists(&pb)? {
// looks like we're deleting a not-loaded file from the store.
debug!("Seems like {:?} is on the FS", pb);
return self.backend.remove_file(&pb)
} else {
debug!("Seems like {:?} is not even on the FS", pb); debug!("Seems like {:?} is not even on the FS", pb);
return Err(SE::from_kind(SEK::FileNotFound)) return Err(SE::from_kind(SEK::FileNotFound))
.chain_err(|| SEK::DeleteCallError(id)) .chain_err(|| SEK::DeleteCallError(id))
} } // else { continue }
},
Some(e) => if e.is_borrowed() {
return Err(SE::from_kind(SEK::IdLocked))
.chain_err(|| SEK::DeleteCallError(id))
}
}
// remove the entry first, then the file false
entries.remove(&id); },
let pb = id.clone().with_base(self.path().clone()).into_pathbuf()?; };
let _ = self
.backend if do_remove {
.remove_file(&pb) let _ = entries.remove(&id);
.chain_err(|| SEK::FileError) }
.chain_err(|| SEK::DeleteCallError(id))?;
} }
debug!("Seems like {:?} is on the FS", pb);
let _ = self
.backend
.remove_file(&pb)
.chain_err(|| SEK::FileError)
.chain_err(|| SEK::DeleteCallError(id))?;
debug!("Deleted"); debug!("Deleted");
Ok(()) Ok(())
} }