]> git.proxmox.com Git - proxmox-backup.git/blob - src/backup/datastore.rs
src/backup/chunk_store.rs: fix GC
[proxmox-backup.git] / src / backup / datastore.rs
1 use failure::*;
2
3 use std::io;
4 use std::path::{PathBuf, Path};
5 use std::collections::HashMap;
6 use lazy_static::lazy_static;
7 use std::sync::{Mutex, Arc};
8
9 use crate::tools;
10 use crate::config::datastore;
11 use super::chunk_store::*;
12 use super::fixed_index::*;
13 use super::dynamic_index::*;
14 use super::index::*;
15 use super::backup_info::*;
16
17 lazy_static!{
18 static ref DATASTORE_MAP: Mutex<HashMap<String, Arc<DataStore>>> = Mutex::new(HashMap::new());
19 }
20
21 /// Datastore Management
22 ///
23 /// A Datastore can store severals backups, and provides the
24 /// management interface for backup.
25 pub struct DataStore {
26 chunk_store: Arc<ChunkStore>,
27 gc_mutex: Mutex<bool>,
28 }
29
30 impl DataStore {
31
32 pub fn lookup_datastore(name: &str) -> Result<Arc<DataStore>, Error> {
33
34 let config = datastore::config()?;
35 let (_, store_config) = config.sections.get(name)
36 .ok_or(format_err!("no such datastore '{}'", name))?;
37
38 let path = store_config["path"].as_str().unwrap();
39
40 let mut map = DATASTORE_MAP.lock().unwrap();
41
42 if let Some(datastore) = map.get(name) {
43 // Compare Config - if changed, create new Datastore object!
44 if datastore.chunk_store.base == PathBuf::from(path) {
45 return Ok(datastore.clone());
46 }
47 }
48
49 let datastore = DataStore::open(name)?;
50
51 let datastore = Arc::new(datastore);
52 map.insert(name.to_string(), datastore.clone());
53
54 Ok(datastore)
55 }
56
57 pub fn open(store_name: &str) -> Result<Self, Error> {
58
59 let config = datastore::config()?;
60 let (_, store_config) = config.sections.get(store_name)
61 .ok_or(format_err!("no such datastore '{}'", store_name))?;
62
63 let path = store_config["path"].as_str().unwrap();
64
65 let chunk_store = ChunkStore::open(store_name, path)?;
66
67 Ok(Self {
68 chunk_store: Arc::new(chunk_store),
69 gc_mutex: Mutex::new(false),
70 })
71 }
72
73 pub fn get_chunk_iterator(
74 &self,
75 print_percentage: bool,
76 ) -> Result<
77 impl Iterator<Item = Result<tools::fs::ReadDirEntry, Error>>,
78 Error
79 > {
80 self.chunk_store.get_chunk_iterator(print_percentage)
81 }
82
83 pub fn create_fixed_writer<P: AsRef<Path>>(&self, filename: P, size: usize, chunk_size: usize) -> Result<FixedIndexWriter, Error> {
84
85 let index = FixedIndexWriter::create(self.chunk_store.clone(), filename.as_ref(), size, chunk_size)?;
86
87 Ok(index)
88 }
89
90 pub fn open_fixed_reader<P: AsRef<Path>>(&self, filename: P) -> Result<FixedIndexReader, Error> {
91
92 let index = FixedIndexReader::open(self.chunk_store.clone(), filename.as_ref())?;
93
94 Ok(index)
95 }
96
97 pub fn create_dynamic_writer<P: AsRef<Path>>(
98 &self, filename: P,
99 chunk_size: usize
100 ) -> Result<DynamicIndexWriter, Error> {
101
102 let index = DynamicIndexWriter::create(
103 self.chunk_store.clone(), filename.as_ref(), chunk_size)?;
104
105 Ok(index)
106 }
107
108 pub fn open_dynamic_reader<P: AsRef<Path>>(&self, filename: P) -> Result<DynamicIndexReader, Error> {
109
110 let index = DynamicIndexReader::open(self.chunk_store.clone(), filename.as_ref())?;
111
112 Ok(index)
113 }
114
115 pub fn open_index<P>(&self, filename: P) -> Result<Box<dyn IndexFile + Send>, Error>
116 where
117 P: AsRef<Path>,
118 {
119 let filename = filename.as_ref();
120 let out: Box<dyn IndexFile + Send> =
121 match filename.extension().and_then(|ext| ext.to_str()) {
122 Some("didx") => Box::new(self.open_dynamic_reader(filename)?),
123 Some("fidx") => Box::new(self.open_fixed_reader(filename)?),
124 _ => bail!("cannot open index file of unknown type: {:?}", filename),
125 };
126 Ok(out)
127 }
128
129 pub fn base_path(&self) -> PathBuf {
130 self.chunk_store.base_path()
131 }
132
133 /// Remove a backup directory including all content
134 pub fn remove_backup_dir(&self, backup_dir: &BackupDir,
135 ) -> Result<(), io::Error> {
136
137 let relative_path = backup_dir.relative_path();
138 let mut full_path = self.base_path();
139 full_path.push(&relative_path);
140
141 log::info!("removing backup {:?}", full_path);
142 std::fs::remove_dir_all(full_path)?;
143
144 Ok(())
145 }
146
147 pub fn create_backup_dir(&self, backup_dir: &BackupDir) -> Result<(PathBuf, bool), io::Error> {
148
149 // create intermediate path first:
150 let mut full_path = self.base_path();
151 full_path.push(backup_dir.group().group_path());
152 std::fs::create_dir_all(&full_path)?;
153
154 let relative_path = backup_dir.relative_path();
155 let mut full_path = self.base_path();
156 full_path.push(&relative_path);
157
158 // create the last component now
159 match std::fs::create_dir(&full_path) {
160 Ok(_) => Ok((relative_path, true)),
161 Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => Ok((relative_path, false)),
162 Err(e) => Err(e)
163 }
164 }
165
166 pub fn list_backups(&self) -> Result<Vec<BackupInfo>, Error> {
167 let path = self.base_path();
168 BackupInfo::list_backups(&path)
169 }
170
171 pub fn list_files(&self, backup_dir: &BackupDir) -> Result<Vec<String>, Error> {
172 let path = self.base_path();
173 BackupInfo::list_files(&path, backup_dir)
174 }
175
176 pub fn list_images(&self) -> Result<Vec<PathBuf>, Error> {
177 let base = self.base_path();
178
179 let mut list = vec![];
180
181 use walkdir::WalkDir;
182
183 let walker = WalkDir::new(&base).same_file_system(true).into_iter();
184
185 // make sure we skip .chunks (and other hidden files to keep it simple)
186 fn is_hidden(entry: &walkdir::DirEntry) -> bool {
187 entry.file_name()
188 .to_str()
189 .map(|s| s.starts_with("."))
190 .unwrap_or(false)
191 }
192
193 for entry in walker.filter_entry(|e| !is_hidden(e)) {
194 let path = entry?.into_path();
195 if let Some(ext) = path.extension() {
196 if ext == "fidx" {
197 list.push(path);
198 } else if ext == "didx" {
199 list.push(path);
200 }
201 }
202 }
203
204 Ok(list)
205 }
206
207 fn mark_used_chunks(&self, status: &mut GarbageCollectionStatus) -> Result<(), Error> {
208
209 let image_list = self.list_images()?;
210
211 for path in image_list {
212 if let Some(ext) = path.extension() {
213 if ext == "fidx" {
214 let index = self.open_fixed_reader(&path)?;
215 index.mark_used_chunks(status)?;
216 } else if ext == "didx" {
217 let index = self.open_dynamic_reader(&path)?;
218 index.mark_used_chunks(status)?;
219 }
220 }
221 }
222
223 Ok(())
224 }
225
226 pub fn garbage_collection(&self) -> Result<(), Error> {
227
228 if let Ok(ref mut _mutex) = self.gc_mutex.try_lock() {
229
230 let _exclusive_lock = self.chunk_store.try_exclusive_lock()?;
231
232 let oldest_writer = self.chunk_store.oldest_writer();
233
234 let mut gc_status = GarbageCollectionStatus::default();
235 gc_status.used_bytes = 0;
236
237 println!("Start GC phase1 (mark chunks)");
238
239 self.mark_used_chunks(&mut gc_status)?;
240
241 println!("Start GC phase2 (sweep unused chunks)");
242 self.chunk_store.sweep_unused_chunks(oldest_writer, &mut gc_status)?;
243
244 println!("Used bytes: {}", gc_status.used_bytes);
245 println!("Used chunks: {}", gc_status.used_chunks);
246 println!("Disk bytes: {}", gc_status.disk_bytes);
247 println!("Disk chunks: {}", gc_status.disk_chunks);
248
249 } else {
250 println!("Start GC failed - (already running/locked)");
251 }
252
253 Ok(())
254 }
255
256 pub fn insert_chunk(&self, chunk: &[u8]) -> Result<(bool, [u8; 32], u64), Error> {
257 self.chunk_store.insert_chunk(chunk)
258 }
259
260 pub fn insert_chunk_noverify(
261 &self,
262 digest: &[u8; 32],
263 chunk: &[u8],
264 ) -> Result<(bool, u64), Error> {
265 self.chunk_store.insert_chunk_noverify(digest, chunk)
266 }
267 }