1
#![allow(unused_variables)]
2
use super::{BackendError, Result};
3
use crate::repository::backend::common::files::LockedFile;
4
use crate::repository::backend::{
5
    backend_to_object, Backend, BackendObject, Chunk, EncryptedKey, Manifest, SegmentDescriptor,
6
};
7
use crate::repository::{ChunkSettings, Key};
8

9
use async_trait::async_trait;
10
use serde_cbor as cbor;
11
use uuid::Uuid;
12

13
use std::fs::{create_dir_all, remove_file, File, OpenOptions};
14
use std::path::{Path, PathBuf};
15
use std::sync::Arc;
16

17
pub mod index;
18
pub mod manifest;
19
pub mod segment;
20

21
#[derive(Debug, Clone)]
22
pub struct MultiFile {
23
    index_handle: index::Index,
24
    manifest_handle: manifest::Manifest,
25
    segment_handle: segment::SegmentHandler,
26
    path: PathBuf,
27
    /// Connection uuid, used for read locks.
28
    uuid: Uuid,
29
    /// Path to readlock for this connection, must be deleted on close
30
    read_lock_path: Arc<PathBuf>,
31
}
32

33
impl MultiFile {
34
    /// Opens a new `MultiFile` backend with default settings
35
    ///
36
    /// Subject to change in the near future
37
    ///
38
    /// # Errors
39
    ///
40
    /// Will error if creating or locking any of the index or manifest files
41
    /// fails (such as if the user does not have permissions for that
42
    /// directory), or if any other I/O error occurs
43 1
    pub async fn open_defaults(
44
        path: impl AsRef<Path>,
45
        chunk_settings: Option<ChunkSettings>,
46
        key: &Key,
47
        queue_depth: usize,
48
    ) -> Result<MultiFile> {
49
        // First, check to see if the global lock exists, and return an error early if it does
50 1
        let global_lock_path = path.as_ref().join("lock");
51 1
        if Path::exists(&global_lock_path) {
52 1
            return Err(BackendError::RepositoryGloballyLocked(format!(
53 0
                "Global lock for this repository already exists at: {:?}",
54 1
                global_lock_path
55
            )));
56
        }
57
        // Generate a uuid
58 1
        let uuid = Uuid::new_v4();
59 1
        let size_limit = 2_000_000_000;
60 1
        let segments_per_directory = 100;
61
        // Open up an index connection
62 1
        let index_handle = index::Index::open(&path, queue_depth)?;
63
        // Open up a manifest connection
64 1
        let mut manifest_handle =
65 0
            manifest::Manifest::open(&path, chunk_settings, key, queue_depth)?;
66 1
        let chunk_settings = if let Some(chunk_settings) = chunk_settings {
67 1
            chunk_settings
68
        } else {
69 0
            manifest_handle.chunk_settings().await
70
        };
71
        // Open up a segment handler connection
72
        let segment_handle = segment::SegmentHandler::open(
73 1
            &path,
74 1
            size_limit,
75 1
            segments_per_directory,
76 1
            chunk_settings,
77 1
            key.clone(),
78 1
            queue_depth,
79
        )?;
80
        // Make sure the readlocks directory exists
81 1
        create_dir_all(path.as_ref().join("readlocks"))?;
82
        // generate a path to our readlock
83 1
        let read_lock_path = path
84
            .as_ref()
85
            .join("readlocks")
86 1
            .join(uuid.to_simple().to_string());
87
        // Create the read_lock file
88 1
        OpenOptions::new()
89
            .create(true)
90
            .write(true)
91 1
            .open(&read_lock_path)?;
92

93 1
        let path = path.as_ref().to_path_buf();
94 1
        Ok(MultiFile {
95 1
            index_handle,
96 1
            manifest_handle,
97 1
            segment_handle,
98 1
            path,
99 1
            uuid,
100 1
            read_lock_path: Arc::new(read_lock_path),
101
        })
102
    }
103

104
    /// Reads the encrypted key off the disk
105
    ///
106
    /// Does not require that the repository be opened first
107
    ///
108
    /// Note: this path is the repository root path, not the key path
109
    ///
110
    /// # Errors
111
    ///
112
    /// Will error if the key is corrupted or deserialization otherwise fails
113 1
    pub fn read_key(path: impl AsRef<Path>) -> Result<EncryptedKey> {
114 1
        let key_path = path.as_ref().join("key");
115 1
        let file = File::open(&key_path)?;
116 1
        Ok(cbor::de::from_reader(&file)?)
117
    }
118
}
119

120
#[async_trait]
121
impl Backend for MultiFile {
122
    type Manifest = manifest::Manifest;
123
    type Index = index::Index;
124

125
    /// Clones the internal MFManifest
126 1
    fn get_index(&self) -> Self::Index {
127 1
        self.index_handle.clone()
128
    }
129
    /// Clones the internal MFIndex
130 1
    fn get_manifest(&self) -> Self::Manifest {
131 1
        self.manifest_handle.clone()
132
    }
133
    /// Locks the keyfile and writes the key
134
    ///
135
    /// Will return Err if writing the key fails
136 1
    async fn write_key(&self, key: &EncryptedKey) -> Result<()> {
137 1
        let key_path = self.path.join("key");
138 1
        let mut file =
139 0
            LockedFile::open_read_write(&key_path)?.ok_or(BackendError::FileLockError)?;
140 1
        Ok(cbor::ser::to_writer(&mut file, key)?)
141
    }
142
    /// Attempts to read the key from the repository
143
    ///
144
    /// Returns Err if the key doesn't exist or of another error occurs
145 1
    async fn read_key(&self) -> Result<EncryptedKey> {
146 1
        let key_path = self.path.join("key");
147 1
        let file = File::open(&key_path)?;
148 1
        Ok(cbor::de::from_reader(&file)?)
149
    }
150

151
    /// Starts reading a chunk, and returns a oneshot recieve with the result of that process
152 1
    async fn read_chunk(&mut self, location: SegmentDescriptor) -> Result<Chunk> {
153 1
        self.segment_handle.read_chunk(location).await
154
    }
155

156
    /// Starts writing a chunk, and returns a oneshot reciever with the result of that process
157 1
    async fn write_chunk(&mut self, chunk: Chunk) -> Result<SegmentDescriptor> {
158 1
        self.segment_handle.write_chunk(chunk).await
159
    }
160

161
    /// Closes out the index, segment handler, and manifest cleanly, making sure all operations are
162
    /// completed and all drop impls from inside the tasks are called
163 1
    async fn close(&mut self) {
164 1
        self.index_handle.close().await;
165 1
        self.manifest_handle.close().await;
166 1
        self.segment_handle.close().await;
167
        // Check if the read_lock_file exists and delete it
168 1
        if self.read_lock_path.exists() {
169
            // FIXME: We ignore this error for now, as this method does not currently return a
170
            // result
171 1
            let _ = remove_file(self.read_lock_path.as_ref());
172
        }
173
    }
174

175 0
    fn get_object_handle(&self) -> BackendObject {
176 0
        backend_to_object(self.clone())
177
    }
178
}
179

180
#[cfg(test)]
181
mod tests {
182
    use super::*;
183
    use crate::repository::Encryption;
184
    use tempfile::{tempdir, TempDir};
185

186
    // Utility function, sets up a tempdir and opens a MultiFile Backend
187
    async fn setup(key: &Key) -> (TempDir, MultiFile) {
188
        let tempdir = tempdir().unwrap();
189
        let path = tempdir.path().to_path_buf();
190
        let mf = MultiFile::open_defaults(path, Some(ChunkSettings::lightweight()), key, 4)
191
            .await
192
            .unwrap();
193
        (tempdir, mf)
194
    }
195

196
    #[test]
197
    fn key_store_load() {
198
        smol::block_on(async {
199
            let key = Key::random(32);
200
            let (tempdir, mut mf) = setup(&key).await;
201
            // Encrypt the key and store it
202
            let enc_key = EncryptedKey::encrypt(&key, 512, 1, Encryption::new_aes256ctr(), b"");
203
            mf.write_key(&enc_key).await.expect("Unable to write key");
204
            // Load the key back out without unloading
205
            let enc_key = mf
206
                .read_key()
207
                .await
208
                .expect("Unable to read key (before drop)");
209
            // Decrypt it and verify equality
210
            let new_key = enc_key
211
                .decrypt(b"")
212
                .expect("Unable to decrypt key (before drop)");
213
            assert_eq!(key, new_key);
214
            // Drop the backend and try reading it from scratch
215
            mf.close().await;
216
            let enc_key =
217
                MultiFile::read_key(tempdir.path()).expect("Unable to read key (after drop)");
218
            let new_key = enc_key
219
                .decrypt(b"")
220
                .expect("Unable to decrypt key (after drop)");
221
            assert_eq!(key, new_key);
222
        });
223
    }
224

225
    // Test to make sure that attempting to open a repository respects an existing global lock
226
    #[test]
227
    fn repository_global_lock() {
228
        smol::block_on(async {
229
            let tempdir = tempdir().unwrap();
230
            let path = tempdir.path().to_path_buf();
231
            let key = Key::random(32);
232
            // Create the lock
233
            OpenOptions::new()
234
                .create(true)
235
                .write(true)
236
                .open(path.join("lock"))
237
                .unwrap();
238
            // Attempt to open the backend
239
            let mf =
240
                MultiFile::open_defaults(path, Some(ChunkSettings::lightweight()), &key, 4).await;
241
            // This should error
242
            assert!(mf.is_err());
243
            // It should also, specifically, be a RepositoryGloballyLocked
244
            assert!(matches!(mf, Err(BackendError::RepositoryGloballyLocked(_))));
245
        });
246
    }
247

248
    // Tests to make sure that readlocks are created and destroyed properly
249
    #[test]
250
    fn read_lock_create_destroy() {
251
        smol::block_on(async {
252
            let key = Key::random(32);
253
            let (tempdir, mut mf) = setup(&key).await;
254
            let lock_path: Arc<PathBuf> = mf.read_lock_path.clone();
255
            // the connection is open, assert that the lock exists
256
            assert!(lock_path.exists());
257
            // Close the connection
258
            mf.close().await;
259
            std::mem::drop(mf);
260
            // The connection is now closed, assert that the lock does not exist
261
            assert!(!lock_path.exists());
262
        });
263
    }
264
}

Read our documentation on viewing source code .

Loading