blob: 504f5f1a25f7e27f545f82b4dc34e8171cf4e58f [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Implements file based implementation of the Storage trait.
use std::fs::File;
use std::io::{Error as IoError, ErrorKind, Read, Write};
use std::path::{Path, PathBuf};
use std::result::Result as StdResult;
use flexbuffers::{from_slice, FlexbufferSerializer};
use super::{to_read_data_error, to_write_data_error, Error, Result, Storable, Storage};
pub struct FileStorage {
root: PathBuf,
}
/// Provides an implementation of the Storage trait that writes the entries to files at a specified
/// path named by the id.
impl FileStorage {
pub fn new(root: PathBuf) -> StdResult<Self, IoError> {
if !root.is_dir() {
return Err(IoError::from(ErrorKind::NotFound));
}
Ok(FileStorage { root })
}
/// Return true if this path component is allowed.
fn check_path_component(component: &str) -> bool {
component != "." && component != ".."
}
/// Validity checks on the id performed before touching the file-system.
fn validate_id(&self, id: &str) -> Result<PathBuf> {
let path = Path::new(id);
// Get first component.
let mut itr = path.components();
let first = PathBuf::from(
itr.next()
.ok_or_else(|| Error::InvalidIdForStorage(id.to_string()))?
.as_os_str(),
);
// If there is more than one component or the first component is a special path
if itr.next().is_some() || !FileStorage::check_path_component(&first.to_string_lossy()) {
return Err(Error::InvalidIdForStorage(id.to_string()));
}
Ok(self.root.join(first))
}
}
impl Storage for FileStorage {
fn read_data<S: Storable>(&mut self, id: &str) -> Result<S> {
let filepath = self.validate_id(id)?;
if !filepath.exists() {
return Err(Error::EmptyRead);
}
let mut contents: Vec<u8> = Vec::new();
File::open(filepath)
.map_err(to_read_data_error)?
.read_to_end(&mut contents)
.map_err(to_read_data_error)?;
from_slice(&contents).map_err(to_read_data_error)
}
fn write_data<S: Storable>(&mut self, id: &str, data: &S) -> Result<()> {
let filename = self.validate_id(id)?;
let mut destination = File::create(filename).map_err(to_write_data_error)?;
let mut ser = FlexbufferSerializer::new();
data.serialize(&mut ser).map_err(to_write_data_error)?;
destination
.write_all(&ser.take_buffer())
.map_err(to_write_data_error)
}
}
#[cfg(test)]
mod test {
use super::*;
use std::fs::create_dir;
use std::time::{SystemTime, UNIX_EPOCH};
use libchromeos::scoped_path::{get_temp_path, ScopedPath};
const VALID_TEST_ID: &str = "Test Data";
struct TestFileStorage {
storage: FileStorage,
/// This is needed for its Drop implementation.
#[allow(dead_code)]
storage_root: ScopedPath<PathBuf>,
}
impl TestFileStorage {
fn new() -> Self {
let storage_root = ScopedPath::create(get_temp_path(None).to_path_buf()).unwrap();
let storage = FileStorage::new(storage_root.to_path_buf()).unwrap();
TestFileStorage {
storage_root,
storage,
}
}
}
impl AsMut<FileStorage> for TestFileStorage {
fn as_mut(&mut self) -> &mut FileStorage {
&mut self.storage
}
}
fn get_test_value() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
}
#[test]
fn storage_new_success() {
TestFileStorage::new();
}
#[test]
fn storage_new_notexist() {
assert!(FileStorage::new(get_temp_path(None).to_path_buf()).is_err());
}
#[test]
fn storage_readwrite_success() {
let mut storage = TestFileStorage::new();
let test_value = get_test_value();
storage
.as_mut()
.write_data(VALID_TEST_ID, &test_value)
.unwrap();
let read_value: u64 = storage.as_mut().read_data(VALID_TEST_ID).unwrap();
assert_eq!(test_value, read_value);
}
#[test]
fn storage_read_emptyread() {
let mut storage = TestFileStorage::new();
assert!(matches!(
storage.as_mut().read_data::<u64>(VALID_TEST_ID),
Err(Error::EmptyRead)
));
}
#[test]
fn storage_write_ioerror() {
let mut storage = TestFileStorage::new();
let path = storage.as_mut().validate_id(VALID_TEST_ID).unwrap();
create_dir(path).unwrap();
assert!(storage
.as_mut()
.write_data(VALID_TEST_ID, &get_test_value())
.is_err());
}
#[test]
fn storage_read_ioerror() {
let mut storage = TestFileStorage::new();
let path = storage.as_mut().validate_id(VALID_TEST_ID).unwrap();
create_dir(path).unwrap();
assert!(storage.as_mut().read_data::<u64>(VALID_TEST_ID).is_err());
}
}