]> git.proxmox.com Git - proxmox-backup.git/blame - src/backup/fixed_index.rs
src/backup/dynamic_index.rs: compute checksum over the index
[proxmox-backup.git] / src / backup / fixed_index.rs
CommitLineData
606ce64b
DM
1use failure::*;
2
22968600 3use crate::tools;
7bc1d727 4use super::IndexFile;
7e336555 5use super::chunk_stat::*;
606ce64b
DM
6use super::chunk_store::*;
7
150f1bd8 8use std::sync::Arc;
4818c8b6 9use std::io::{Read, Write};
10eea49d 10use std::fs::File;
606ce64b
DM
11use std::path::{Path, PathBuf};
12use std::os::unix::io::AsRawFd;
d13e3745 13use uuid::Uuid;
4818c8b6 14use chrono::{Local, TimeZone};
f98ac774 15use super::ChunkInfo;
606ce64b 16
8e39232a 17/// Header format definition for fixed index files (`.fidx`)
d13e3745 18#[repr(C)]
91a905b6 19pub struct FixedIndexHeader {
e5064ba6 20 /// The string `PROXMOX-FIDX`
d13e3745
DM
21 pub magic: [u8; 12],
22 pub version: u32,
23 pub uuid: [u8; 16],
5e5b7f1c 24 pub ctime: u64,
d13e3745 25 pub size: u64,
4818c8b6 26 pub chunk_size: u64,
44b3f62b 27 reserved: [u8; 4040], // overall size is one page (4096 bytes)
d13e3745 28}
606ce64b
DM
29
30// split image into fixed size chunks
31
91a905b6 32pub struct FixedIndexReader {
150f1bd8 33 store: Arc<ChunkStore>,
10eea49d 34 _file: File,
4818c8b6 35 filename: PathBuf,
29ae5c86 36 pub chunk_size: usize,
9f49fe1d 37 pub size: usize,
e1225de4 38 index_length: usize,
4818c8b6 39 index: *mut u8,
9f49fe1d
DM
40 pub uuid: [u8; 16],
41 pub ctime: u64,
4818c8b6
DM
42}
43
5be4065b
WB
44// `index` is mmap()ed which cannot be thread-local so should be sendable
45unsafe impl Send for FixedIndexReader {}
46
91a905b6 47impl Drop for FixedIndexReader {
4818c8b6
DM
48
49 fn drop(&mut self) {
50 if let Err(err) = self.unmap() {
51 eprintln!("Unable to unmap file {:?} - {}", self.filename, err);
52 }
53 }
54}
55
91a905b6 56impl FixedIndexReader {
4818c8b6 57
150f1bd8 58 pub fn open(store: Arc<ChunkStore>, path: &Path) -> Result<Self, Error> {
4818c8b6
DM
59
60 let full_path = store.relative_path(path);
61
10eea49d 62 let mut file = File::open(&full_path)?;
4818c8b6 63
c597a92c
DM
64 if let Err(err) = nix::fcntl::flock(file.as_raw_fd(), nix::fcntl::FlockArg::LockSharedNonblock) {
65 bail!("unable to get shared lock on {:?} - {}", full_path, err);
66 }
67
91a905b6 68 let header_size = std::mem::size_of::<FixedIndexHeader>();
4818c8b6
DM
69
70 // todo: use static assertion when available in rust
a360f6fa 71 if header_size != 4096 { bail!("got unexpected header size for {:?}", path); }
4818c8b6
DM
72
73 let mut buffer = vec![0u8; header_size];
74 file.read_exact(&mut buffer)?;
75
91a905b6 76 let header = unsafe { &mut * (buffer.as_ptr() as *mut FixedIndexHeader) };
4818c8b6 77
91a905b6 78 if header.magic != *b"PROXMOX-FIDX" {
a360f6fa
DM
79 bail!("got unknown magic number for {:?}", path);
80 }
81
48d0d356 82 let version = u32::from_le(header.version);
a360f6fa
DM
83 if version != 1 {
84 bail!("got unsupported version number ({})", version);
85 }
86
48d0d356
DM
87 let size = u64::from_le(header.size) as usize;
88 let ctime = u64::from_le(header.ctime);
89 let chunk_size = u64::from_le(header.chunk_size) as usize;
4818c8b6 90
e1225de4
DM
91 let index_length = (size + chunk_size - 1)/chunk_size;
92 let index_size = index_length*32;
4818c8b6 93
0b8e75ed
DM
94 let rawfd = file.as_raw_fd();
95
96 let stat = match nix::sys::stat::fstat(rawfd) {
97 Ok(stat) => stat,
98 Err(err) => bail!("fstat {:?} failed - {}", path, err),
99 };
100
ddbdf80d 101 let expected_index_size = (stat.st_size as usize) - header_size;
0b8e75ed
DM
102 if index_size != expected_index_size {
103 bail!("got unexpected file size for {:?} ({} != {})",
104 path, index_size, expected_index_size);
105 }
4818c8b6
DM
106
107 let data = unsafe { nix::sys::mman::mmap(
108 std::ptr::null_mut(),
109 index_size,
110 nix::sys::mman::ProtFlags::PROT_READ,
111 nix::sys::mman::MapFlags::MAP_PRIVATE,
112 file.as_raw_fd(),
113 header_size as i64) }? as *mut u8;
114
115 Ok(Self {
116 store,
117 filename: full_path,
10eea49d 118 _file: file,
4818c8b6
DM
119 chunk_size,
120 size,
e1225de4 121 index_length,
4818c8b6
DM
122 index: data,
123 ctime,
124 uuid: header.uuid,
125 })
126 }
127
128 fn unmap(&mut self) -> Result<(), Error> {
129
130 if self.index == std::ptr::null_mut() { return Ok(()); }
131
e1225de4 132 let index_size = self.index_length*32;
4818c8b6
DM
133
134 if let Err(err) = unsafe { nix::sys::mman::munmap(self.index as *mut std::ffi::c_void, index_size) } {
135 bail!("unmap file {:?} failed - {}", self.filename, err);
136 }
137
138 self.index = std::ptr::null_mut();
139
140 Ok(())
141 }
142
64e53b28 143 pub fn mark_used_chunks(&self, status: &mut GarbageCollectionStatus) -> Result<(), Error> {
3d5c11e5
DM
144
145 if self.index == std::ptr::null_mut() { bail!("detected closed index file."); }
146
e1225de4
DM
147 status.used_bytes += self.index_length * self.chunk_size;
148 status.used_chunks += self.index_length;
3d5c11e5 149
e1225de4 150 for pos in 0..self.index_length {
3d5c11e5 151
92da93b2
DM
152 tools::fail_on_shutdown()?;
153
7bc1d727 154 let digest = self.index_digest(pos).unwrap();
3d5c11e5
DM
155 if let Err(err) = self.store.touch_chunk(digest) {
156 bail!("unable to access chunk {}, required by {:?} - {}",
22968600 157 tools::digest_to_hex(digest), self.filename, err);
3d5c11e5
DM
158 }
159 }
160
161 Ok(())
162 }
163
4818c8b6
DM
164 pub fn print_info(&self) {
165 println!("Filename: {:?}", self.filename);
166 println!("Size: {}", self.size);
167 println!("ChunkSize: {}", self.chunk_size);
168 println!("CTime: {}", Local.timestamp(self.ctime as i64, 0).format("%c"));
169 println!("UUID: {:?}", self.uuid);
170 }
171}
172
7bc1d727
WB
173impl IndexFile for FixedIndexReader {
174 fn index_count(&self) -> usize {
e1225de4 175 self.index_length
7bc1d727
WB
176 }
177
178 fn index_digest(&self, pos: usize) -> Option<&[u8; 32]> {
e1225de4 179 if pos >= self.index_length {
7bc1d727
WB
180 None
181 } else {
182 Some(unsafe { std::mem::transmute(self.index.add(pos*32)) })
183 }
184 }
185}
186
91a905b6 187pub struct FixedIndexWriter {
150f1bd8 188 store: Arc<ChunkStore>,
43b13033 189 _lock: tools::ProcessLockSharedGuard,
4fbb72a8
DM
190 filename: PathBuf,
191 tmp_filename: PathBuf,
606ce64b
DM
192 chunk_size: usize,
193 size: usize,
e1225de4 194 index_length: usize,
606ce64b 195 index: *mut u8,
9f49fe1d
DM
196 pub uuid: [u8; 16],
197 pub ctime: u64,
606ce64b
DM
198}
199
c3bb97e5
WB
200// `index` is mmap()ed which cannot be thread-local so should be sendable
201unsafe impl Send for FixedIndexWriter {}
202
91a905b6 203impl Drop for FixedIndexWriter {
4fbb72a8
DM
204
205 fn drop(&mut self) {
206 let _ = std::fs::remove_file(&self.tmp_filename); // ignore errors
207 if let Err(err) = self.unmap() {
0cd9d420 208 eprintln!("Unable to unmap file {:?} - {}", self.tmp_filename, err);
4fbb72a8
DM
209 }
210 }
211}
212
91a905b6 213impl FixedIndexWriter {
606ce64b 214
150f1bd8 215 pub fn create(store: Arc<ChunkStore>, path: &Path, size: usize, chunk_size: usize) -> Result<Self, Error> {
606ce64b 216
43b13033
DM
217 let shared_lock = store.try_shared_lock()?;
218
606ce64b 219 let full_path = store.relative_path(path);
4fbb72a8 220 let mut tmp_path = full_path.clone();
91a905b6 221 tmp_path.set_extension("tmp_fidx");
606ce64b
DM
222
223 let mut file = std::fs::OpenOptions::new()
d13e3745 224 .create(true).truncate(true)
606ce64b
DM
225 .read(true)
226 .write(true)
4fbb72a8 227 .open(&tmp_path)?;
606ce64b 228
91a905b6 229 let header_size = std::mem::size_of::<FixedIndexHeader>();
d13e3745
DM
230
231 // todo: use static assertion when available in rust
232 if header_size != 4096 { panic!("got unexpected header size"); }
233
234 let ctime = std::time::SystemTime::now().duration_since(
5e5b7f1c 235 std::time::SystemTime::UNIX_EPOCH)?.as_secs();
d13e3745
DM
236
237 let uuid = Uuid::new_v4();
238
0cd9d420 239 let buffer = vec![0u8; header_size];
91a905b6 240 let header = unsafe { &mut * (buffer.as_ptr() as *mut FixedIndexHeader) };
d13e3745 241
91a905b6 242 header.magic = *b"PROXMOX-FIDX";
48d0d356
DM
243 header.version = u32::to_le(1);
244 header.ctime = u64::to_le(ctime);
245 header.size = u64::to_le(size as u64);
246 header.chunk_size = u64::to_le(chunk_size as u64);
d13e3745
DM
247 header.uuid = *uuid.as_bytes();
248
5e5b7f1c 249 file.write_all(&buffer)?;
d13e3745 250
e1225de4
DM
251 let index_length = (size + chunk_size - 1)/chunk_size;
252 let index_size = index_length*32;
d13e3745
DM
253 nix::unistd::ftruncate(file.as_raw_fd(), (header_size + index_size) as i64)?;
254
606ce64b
DM
255 let data = unsafe { nix::sys::mman::mmap(
256 std::ptr::null_mut(),
257 index_size,
258 nix::sys::mman::ProtFlags::PROT_READ | nix::sys::mman::ProtFlags::PROT_WRITE,
259 nix::sys::mman::MapFlags::MAP_SHARED,
260 file.as_raw_fd(),
d13e3745
DM
261 header_size as i64) }? as *mut u8;
262
606ce64b
DM
263
264 Ok(Self {
265 store,
43b13033 266 _lock: shared_lock,
4fbb72a8
DM
267 filename: full_path,
268 tmp_filename: tmp_path,
606ce64b
DM
269 chunk_size,
270 size,
e1225de4 271 index_length,
606ce64b 272 index: data,
d13e3745
DM
273 ctime,
274 uuid: *uuid.as_bytes(),
606ce64b
DM
275 })
276 }
277
006f3ff4
DM
278 pub fn index_length(&self) -> usize {
279 self.index_length
280 }
281
4fbb72a8
DM
282 fn unmap(&mut self) -> Result<(), Error> {
283
284 if self.index == std::ptr::null_mut() { return Ok(()); }
285
e1225de4 286 let index_size = self.index_length*32;
4fbb72a8
DM
287
288 if let Err(err) = unsafe { nix::sys::mman::munmap(self.index as *mut std::ffi::c_void, index_size) } {
0cd9d420 289 bail!("unmap file {:?} failed - {}", self.tmp_filename, err);
4fbb72a8
DM
290 }
291
292 self.index = std::ptr::null_mut();
293
294 Ok(())
295 }
296
297 pub fn close(&mut self) -> Result<(), Error> {
298
299 if self.index == std::ptr::null_mut() { bail!("cannot close already closed index file."); }
300
301 self.unmap()?;
302
303 if let Err(err) = std::fs::rename(&self.tmp_filename, &self.filename) {
304 bail!("Atomic rename file {:?} failed - {}", self.filename, err);
305 }
306
307 Ok(())
308 }
309
01af11f3 310 // Note: We want to add data out of order, so do not assume any order here.
f98ac774 311 pub fn add_chunk(&mut self, chunk_info: &ChunkInfo, stat: &mut ChunkStat) -> Result<(), Error> {
606ce64b 312
f98ac774
DM
313 let chunk_len = chunk_info.chunk_len as usize;
314 let end = chunk_info.offset as usize;
315
316 if end < chunk_len {
317 bail!("got chunk with small offset ({} < {}", end, chunk_len);
318 }
319
320 let pos = end - chunk_len;
606ce64b
DM
321
322 if end > self.size {
323 bail!("write chunk data exceeds size ({} >= {})", end, self.size);
324 }
325
326 // last chunk can be smaller
f98ac774
DM
327 if ((end != self.size) && (chunk_len != self.chunk_size)) ||
328 (chunk_len > self.chunk_size) || (chunk_len == 0) {
329 bail!("got chunk with wrong length ({} != {}", chunk_len, self.chunk_size);
606ce64b
DM
330 }
331
606ce64b
DM
332 if pos & (self.chunk_size-1) != 0 { bail!("add unaligned chunk (pos = {})", pos); }
333
f98ac774
DM
334 if (end as u64) != chunk_info.offset {
335 bail!("got chunk with wrong offset ({} != {}", end, chunk_info.offset);
336 }
337
338 let (is_duplicate, compressed_size) = self.store.insert_chunk(&chunk_info.chunk)?;
798f7fa0 339
cb0708dd
DM
340 stat.chunk_count += 1;
341 stat.compressed_size += compressed_size;
606ce64b 342
f98ac774
DM
343 let digest = chunk_info.chunk.digest();
344
345 println!("ADD CHUNK {} {} {}% {} {}", pos, chunk_len,
346 (compressed_size*100)/(chunk_len as u64), is_duplicate, tools::digest_to_hex(digest));
798f7fa0
DM
347
348 if is_duplicate {
cb0708dd 349 stat.duplicate_chunks += 1;
798f7fa0 350 } else {
cb0708dd 351 stat.disk_size += compressed_size;
798f7fa0 352 }
606ce64b 353
f98ac774 354 self.add_digest(pos / self.chunk_size, digest)
e3062f87
WB
355 }
356
357 pub fn add_digest(&mut self, index: usize, digest: &[u8; 32]) -> Result<(), Error> {
01af11f3 358
fc14b849
DM
359 if index >= self.index_length {
360 bail!("add digest failed - index out of range ({} >= {})", index, self.index_length);
361 }
362
01af11f3
DM
363 if self.index == std::ptr::null_mut() { bail!("cannot write to closed index file."); }
364
e3062f87 365 let index_pos = index*32;
606ce64b
DM
366 unsafe {
367 let dst = self.index.add(index_pos);
368 dst.copy_from_nonoverlapping(digest.as_ptr(), 32);
369 }
370
371 Ok(())
372 }
373}