]> git.proxmox.com Git - proxmox-backup.git/blame - src/backup/chunk_store.rs
cleanup - avoid too much indentation
[proxmox-backup.git] / src / backup / chunk_store.rs
CommitLineData
35cf5daa
DM
1use failure::*;
2use std::path::{Path, PathBuf};
128b37fe 3use std::io::Write;
1628a4c7 4use std::time::Duration;
128b37fe
DM
5
6use crypto::digest::Digest;
7use crypto::sha2::Sha512Trunc256;
c5d82e5f
DM
8use std::sync::Mutex;
9
365bb90f 10use std::fs::File;
45773720 11use std::os::unix::io::AsRawFd;
35cf5daa 12
365bb90f
DM
13use crate::tools;
14
35cf5daa
DM
15pub struct ChunkStore {
16 base: PathBuf,
17 chunk_dir: PathBuf,
128b37fe 18 hasher: Sha512Trunc256,
c5d82e5f 19 mutex: Mutex<bool>,
45773720 20 lockfile: File,
128b37fe
DM
21}
22
23const HEX_CHARS: &'static [u8; 16] = b"0123456789abcdef";
24
08481a0b 25pub fn digest_to_hex(digest: &[u8]) -> String {
128b37fe 26
3d5c11e5 27 let mut buf = Vec::<u8>::with_capacity(digest.len()*2);
128b37fe 28
3d5c11e5 29 for i in 0..digest.len() {
128b37fe
DM
30 buf.push(HEX_CHARS[(digest[i] >> 4) as usize]);
31 buf.push(HEX_CHARS[(digest[i] & 0xf) as usize]);
32 }
33
34 unsafe { String::from_utf8_unchecked(buf) }
35cf5daa
DM
35}
36
08481a0b 37fn digest_to_prefix(digest: &[u8]) -> PathBuf {
128b37fe
DM
38
39 let mut buf = Vec::<u8>::with_capacity(3+1+2+1);
40
41 buf.push(HEX_CHARS[(digest[0] as usize) >> 4]);
42 buf.push(HEX_CHARS[(digest[0] as usize) &0xf]);
43 buf.push(HEX_CHARS[(digest[1] as usize) >> 4]);
44 buf.push('/' as u8);
35cf5daa 45
128b37fe
DM
46 buf.push(HEX_CHARS[(digest[1] as usize) & 0xf]);
47 buf.push(HEX_CHARS[(digest[2] as usize) >> 4]);
48 buf.push('/' as u8);
49
50 let path = unsafe { String::from_utf8_unchecked(buf)};
51
52 path.into()
35cf5daa
DM
53}
54
12bb93b3 55
35cf5daa
DM
56impl ChunkStore {
57
45773720
DM
58 fn chunk_dir<P: AsRef<Path>>(path: P) -> PathBuf {
59
60 let mut chunk_dir: PathBuf = PathBuf::from(path.as_ref());
61 chunk_dir.push(".chunks");
62
63 chunk_dir
64 }
65
35cf5daa
DM
66 pub fn create<P: Into<PathBuf>>(path: P) -> Result<Self, Error> {
67
45773720
DM
68 let base: PathBuf = path.into();
69 let chunk_dir = Self::chunk_dir(&base);
35cf5daa 70
2989f6bf
DM
71 if let Err(err) = std::fs::create_dir(&base) {
72 bail!("unable to create chunk store {:?} - {}", base, err);
73 }
74
75 if let Err(err) = std::fs::create_dir(&chunk_dir) {
76 bail!("unable to create chunk store subdir {:?} - {}", chunk_dir, err);
77 }
35cf5daa 78
128b37fe
DM
79 // create 4096 subdir
80 for i in 0..4096 {
af3e7d75 81 let mut l1path = chunk_dir.clone();
128b37fe 82 l1path.push(format!("{:03x}",i));
2989f6bf
DM
83 if let Err(err) = std::fs::create_dir(&l1path) {
84 bail!("unable to create chunk subdir {:?} - {}", l1path, err);
85 }
35cf5daa
DM
86 }
87
45773720 88 Self::open(base)
35cf5daa
DM
89 }
90
91 pub fn open<P: Into<PathBuf>>(path: P) -> Result<Self, Error> {
92
45773720
DM
93 let base: PathBuf = path.into();
94 let chunk_dir = Self::chunk_dir(&base);
95
ce55dbbc
DM
96 if let Err(err) = std::fs::metadata(&chunk_dir) {
97 bail!("unable to open chunk store {:?} - {}", chunk_dir, err);
98 }
45773720
DM
99
100 let mut lockfile_path = base.clone();
101 lockfile_path.push(".lock");
102
5ba69689 103 // make sure only one process/thread/task can use it
1628a4c7
WB
104 let lockfile = tools::open_file_locked(
105 lockfile_path, Duration::from_secs(10))?;
35cf5daa 106
b8d4766a
DM
107 Ok(ChunkStore {
108 base,
109 chunk_dir,
110 hasher: Sha512Trunc256::new(),
111 lockfile,
112 mutex: Mutex::new(false)
113 })
35cf5daa
DM
114 }
115
3d5c11e5
DM
116 pub fn touch_chunk(&mut self, digest:&[u8]) -> Result<(), Error> {
117
08481a0b 118 // fixme: nix::sys::stat::utimensat
3d5c11e5 119 let mut chunk_path = self.chunk_dir.clone();
08481a0b 120 let prefix = digest_to_prefix(&digest);
3d5c11e5 121 chunk_path.push(&prefix);
08481a0b 122 let digest_str = digest_to_hex(&digest);
3d5c11e5
DM
123 chunk_path.push(&digest_str);
124
125 std::fs::metadata(&chunk_path)?;
126 Ok(())
127 }
128
2bf5f6b2 129 fn sweep_old_files(&self, handle: &mut nix::dir::Dir) {
08481a0b
DM
130
131 let rawfd = handle.as_raw_fd();
132
133 let now = unsafe { libc::time(std::ptr::null_mut()) };
134
135 for entry in handle.iter() {
136 match entry {
137 Ok(entry) => {
138 if let Some(file_type) = entry.file_type() {
139 if file_type == nix::dir::Type::File {
140 let filename = entry.file_name();
141 if let Ok(stat) = nix::sys::stat::fstatat(rawfd, filename, nix::fcntl::AtFlags::AT_SYMLINK_NOFOLLOW) {
142 let age = now - stat.st_atime;
143 println!("FOUND {} {:?}", age/(3600*24), filename);
144 if age/(3600*24) >= 2 {
145 println!("UNLINK {} {:?}", age/(3600*24), filename);
146 unsafe { libc::unlinkat(rawfd, filename.as_ptr(), 0); }
147 }
148 }
149 }
150 }
151 }
152 Err(_) => {
153 // fixme ??
154 }
155 }
156 }
157
158 }
159
6ea3a0b7
DM
160 pub fn sweep_used_chunks(&mut self) -> Result<(), Error> {
161
1c43c56b
DM
162 use nix::fcntl::OFlag;
163 use nix::sys::stat::Mode;
164 use nix::dir::Dir;
165
166 let mut base_handle = match Dir::open(
167 &self.chunk_dir, OFlag::O_RDONLY, Mode::empty()) {
2bf5f6b2
DM
168 Ok(h) => h,
169 Err(err) => bail!("unable to open base chunk dir {:?} - {}", self.chunk_dir, err),
170 };
171
172 let base_fd = base_handle.as_raw_fd();
173
08481a0b 174 for i in 0..4096 {
2bf5f6b2
DM
175 let l1name = PathBuf::from(format!("{:03x}", i));
176 let mut l1_handle = match nix::dir::Dir::openat(
1c43c56b 177 base_fd, &l1name, OFlag::O_RDONLY, Mode::empty()) {
2bf5f6b2
DM
178 Ok(h) => h,
179 Err(err) => bail!("unable to open l1 chunk dir {:?}/{:?} - {}", self.chunk_dir, l1name, err),
180 };
181
182 let l1_fd = l1_handle.as_raw_fd();
183
184 for l1_entry in l1_handle.iter() {
6c20a13d
DM
185 let l1_entry = match l1_entry {
186 Ok(l1_entry) => l1_entry,
187 Err(_) => continue /* ignore errors? */,
188 };
189 let file_type = match l1_entry.file_type() {
190 Some(file_type) => file_type,
191 None => bail!("unsupported file system type on {:?}/{:?}", self.chunk_dir, l1name),
192 };
193 if file_type != nix::dir::Type::Directory { continue; }
194
195 let l2name = l1_entry.file_name();
196 if l2name.to_bytes_with_nul()[0] == b'.' { continue; }
197
198 let mut l2_handle = match Dir::openat(
199 l1_fd, l2name, OFlag::O_RDONLY, Mode::empty()) {
200 Ok(h) => h,
201 Err(err) => bail!(
202 "unable to open l2 chunk dir {:?}/{:?}/{:?} - {}",
203 self.chunk_dir, l1name, l2name, err),
204 };
205 self.sweep_old_files(&mut l2_handle);
08481a0b
DM
206 }
207 }
6ea3a0b7
DM
208 Ok(())
209 }
210
391a2e43 211 pub fn insert_chunk(&mut self, chunk: &[u8]) -> Result<(bool, [u8; 32]), Error> {
128b37fe
DM
212
213 self.hasher.reset();
214 self.hasher.input(chunk);
215
216 let mut digest = [0u8; 32];
217 self.hasher.result(&mut digest);
08481a0b 218 //println!("DIGEST {}", digest_to_hex(&digest));
128b37fe 219
af3e7d75 220 let mut chunk_path = self.chunk_dir.clone();
08481a0b 221 let prefix = digest_to_prefix(&digest);
c5d82e5f 222 chunk_path.push(&prefix);
08481a0b 223 let digest_str = digest_to_hex(&digest);
c5d82e5f
DM
224 chunk_path.push(&digest_str);
225
226 let lock = self.mutex.lock();
227
228 if let Ok(metadata) = std::fs::metadata(&chunk_path) {
229 if metadata.is_file() {
391a2e43 230 return Ok((true, digest));
c5d82e5f
DM
231 } else {
232 bail!("Got unexpected file type for chunk {}", digest_str);
233 }
234 }
128b37fe 235
af3e7d75 236 let mut chunk_dir = self.chunk_dir.clone();
c5d82e5f 237 chunk_dir.push(&prefix);
128b37fe 238
c5d82e5f 239 if let Err(_) = std::fs::create_dir(&chunk_dir) { /* ignore */ }
128b37fe 240
c5d82e5f
DM
241 let mut tmp_path = chunk_path.clone();
242 tmp_path.set_extension("tmp");
243 let mut f = std::fs::File::create(&tmp_path)?;
128b37fe
DM
244 f.write_all(chunk)?;
245
c5d82e5f
DM
246 if let Err(err) = std::fs::rename(&tmp_path, &chunk_path) {
247 if let Err(_) = std::fs::remove_file(&tmp_path) { /* ignore */ }
248 bail!("Atomic rename failed for chunk {} - {}", digest_str, err);
249 }
250
128b37fe
DM
251 println!("PATH {:?}", chunk_path);
252
c5d82e5f
DM
253 drop(lock);
254
391a2e43 255 Ok((false, digest))
128b37fe
DM
256 }
257
606ce64b
DM
258 pub fn relative_path(&self, path: &Path) -> PathBuf {
259
260 let mut full_path = self.base.clone();
261 full_path.push(path);
262 full_path
263 }
264
3d5c11e5
DM
265 pub fn base_path(&self) -> PathBuf {
266 self.base.clone()
267 }
268
35cf5daa
DM
269}
270
271
272#[test]
273fn test_chunk_store1() {
274
275 if let Err(_e) = std::fs::remove_dir_all(".testdir") { /* ignore */ }
276
277 let chunk_store = ChunkStore::open(".testdir");
278 assert!(chunk_store.is_err());
279
128b37fe 280 let mut chunk_store = ChunkStore::create(".testdir").unwrap();
391a2e43
DM
281 let (exists, _) = chunk_store.insert_chunk(&[0u8, 1u8]).unwrap();
282 assert!(!exists);
283
284 let (exists, _) = chunk_store.insert_chunk(&[0u8, 1u8]).unwrap();
285 assert!(exists);
128b37fe 286
35cf5daa
DM
287
288 let chunk_store = ChunkStore::create(".testdir");
289 assert!(chunk_store.is_err());
290
291
292}