]>
Commit | Line | Data |
---|---|---|
5ad40a3d DM |
1 | use std::path::Path; |
2 | use std::io::{BufRead, BufReader}; | |
3 | ||
4 | use anyhow::{format_err, bail, Error}; | |
5 | ||
25877d05 | 6 | use proxmox_sys::fs::CreateOptions; |
5ad40a3d DM |
7 | |
8 | use crate::tape::{MediaCatalog, MediaId}; | |
9 | ||
10 | /// Returns a list of (store, snapshot) for a given MediaId | |
11 | /// | |
12 | /// To speedup things for large catalogs, we cache the list of | |
13 | /// snapshots into a separate file. | |
14 | pub fn media_catalog_snapshot_list( | |
15 | base_path: &Path, | |
16 | media_id: &MediaId, | |
17 | ) -> Result<Vec<(String, String)>, Error> { | |
18 | ||
19 | let uuid = &media_id.label.uuid; | |
20 | ||
21 | let mut cache_path = base_path.to_owned(); | |
22 | cache_path.push(uuid.to_string()); | |
23 | let mut catalog_path = cache_path.clone(); | |
24 | cache_path.set_extension("index"); | |
25 | catalog_path.set_extension("log"); | |
26 | ||
27 | let stat = match nix::sys::stat::stat(&catalog_path) { | |
28 | Ok(stat) => stat, | |
29 | Err(err) => bail!("unable to stat media catalog {:?} - {}", catalog_path, err), | |
30 | }; | |
31 | ||
32 | let cache_id = format!("{:016X}-{:016X}-{:016X}", stat.st_ino, stat.st_size as u64, stat.st_mtime as u64); | |
33 | ||
34 | match std::fs::OpenOptions::new().read(true).open(&cache_path) { | |
35 | Ok(file) => { | |
36 | let mut list = Vec::new(); | |
37 | let file = BufReader::new(file); | |
38 | let mut lines = file.lines(); | |
39 | match lines.next() { | |
40 | Some(Ok(id)) => { | |
41 | if id != cache_id { // cache is outdated - rewrite | |
42 | return write_snapshot_cache(base_path, media_id, &cache_path, &cache_id); | |
43 | } | |
44 | } | |
45 | _ => bail!("unable to read catalog cache firstline {:?}", cache_path), | |
46 | } | |
47 | ||
48 | for line in lines { | |
49 | let mut line = line?; | |
50 | ||
51 | let idx = line | |
52 | .find(':') | |
53 | .ok_or_else(|| format_err!("invalid line format (no store found)"))?; | |
54 | ||
55 | let snapshot = line.split_off(idx + 1); | |
56 | line.truncate(idx); | |
57 | list.push((line, snapshot)); | |
58 | } | |
59 | ||
60 | Ok(list) | |
61 | } | |
62 | Err(err) if err.kind() == std::io::ErrorKind::NotFound => { | |
63 | write_snapshot_cache(base_path, media_id, &cache_path, &cache_id) | |
64 | } | |
65 | Err(err) => bail!("unable to open catalog cache - {}", err), | |
66 | } | |
67 | } | |
68 | ||
69 | fn write_snapshot_cache( | |
70 | base_path: &Path, | |
71 | media_id: &MediaId, | |
72 | cache_path: &Path, | |
73 | cache_id: &str, | |
74 | ) -> Result<Vec<(String, String)>, Error> { | |
75 | ||
76 | // open normal catalog and write cache | |
77 | let catalog = MediaCatalog::open(base_path, media_id, false, false)?; | |
78 | ||
79 | let mut data = String::new(); | |
80 | data.push_str(cache_id); | |
81 | data.push('\n'); | |
82 | ||
83 | let mut list = Vec::new(); | |
84 | for (store, content) in catalog.content() { | |
85 | for snapshot in content.snapshot_index.keys() { | |
86 | list.push((store.to_string(), snapshot.to_string())); | |
87 | data.push_str(store); | |
88 | data.push(':'); | |
89 | data.push_str(snapshot); | |
90 | data.push('\n'); | |
91 | } | |
92 | } | |
93 | ||
21211748 | 94 | let backup_user = pbs_config::backup_user()?; |
5ad40a3d DM |
95 | let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); |
96 | let options = CreateOptions::new() | |
97 | .perm(mode) | |
98 | .owner(backup_user.uid) | |
99 | .group(backup_user.gid); | |
100 | ||
25877d05 | 101 | proxmox_sys::fs::replace_file( |
5ad40a3d DM |
102 | cache_path, |
103 | data.as_bytes(), | |
104 | options, | |
e0a19d33 | 105 | false, |
5ad40a3d DM |
106 | )?; |
107 | ||
108 | Ok(list) | |
109 | } |