]>
Commit | Line | Data |
---|---|---|
c4d8542e DM |
1 | //! Media Pool |
2 | //! | |
3 | //! A set of backup medias. | |
4 | //! | |
5 | //! This struct manages backup media state during backup. The main | |
d1d74c43 | 6 | //! purpose is to allocate media sets and assign new tapes to it. |
c4d8542e DM |
7 | //! |
8 | //! | |
9 | ||
1057b1f5 | 10 | use std::path::{PathBuf, Path}; |
30316192 | 11 | |
c4d8542e DM |
12 | use anyhow::{bail, Error}; |
13 | use ::serde::{Deserialize, Serialize}; | |
14 | ||
15 | use proxmox::tools::Uuid; | |
16 | ||
17 | use crate::{ | |
7526d864 | 18 | backup::{Fingerprint, BackupLockGuard}, |
c4d8542e DM |
19 | api2::types::{ |
20 | MediaStatus, | |
c1c2c8f6 | 21 | MediaLocation, |
c4d8542e DM |
22 | MediaSetPolicy, |
23 | RetentionPolicy, | |
24 | MediaPoolConfig, | |
25 | }, | |
26 | tools::systemd::time::compute_next_event, | |
27 | tape::{ | |
28 | MediaId, | |
29 | MediaSet, | |
c4d8542e | 30 | Inventory, |
f281b8d3 | 31 | MediaCatalog, |
30316192 DM |
32 | lock_media_set, |
33 | lock_media_pool, | |
34 | lock_unassigned_media_pool, | |
c4d8542e | 35 | file_formats::{ |
a78348ac | 36 | MediaLabel, |
c4d8542e DM |
37 | MediaSetLabel, |
38 | }, | |
39 | } | |
40 | }; | |
41 | ||
c4d8542e DM |
42 | /// Media Pool |
43 | pub struct MediaPool { | |
44 | ||
45 | name: String, | |
1057b1f5 | 46 | state_path: PathBuf, |
c4d8542e DM |
47 | |
48 | media_set_policy: MediaSetPolicy, | |
49 | retention: RetentionPolicy, | |
cdf39e62 DM |
50 | |
51 | changer_name: Option<String>, | |
ab77d660 | 52 | force_media_availability: bool, |
cdf39e62 | 53 | |
30316192 DM |
54 | // Set this if you do not need to allocate writeable media - this |
55 | // is useful for list_media() | |
56 | no_media_set_locking: bool, | |
57 | ||
8a0046f5 | 58 | encrypt_fingerprint: Option<Fingerprint>, |
c4d8542e DM |
59 | |
60 | inventory: Inventory, | |
c4d8542e DM |
61 | |
62 | current_media_set: MediaSet, | |
7526d864 | 63 | current_media_set_lock: Option<BackupLockGuard>, |
c4d8542e DM |
64 | } |
65 | ||
66 | impl MediaPool { | |
67 | ||
68 | /// Creates a new instance | |
cdf39e62 DM |
69 | /// |
70 | /// If you specify a `changer_name`, only media accessible via | |
71 | /// that changer is considered available. If you pass `None` for | |
72 | /// `changer`, all offline media is considered available (backups | |
73 | /// to standalone drives may not use media from inside a tape | |
74 | /// library). | |
c4d8542e DM |
75 | pub fn new( |
76 | name: &str, | |
77 | state_path: &Path, | |
78 | media_set_policy: MediaSetPolicy, | |
79 | retention: RetentionPolicy, | |
cdf39e62 | 80 | changer_name: Option<String>, |
8a0046f5 | 81 | encrypt_fingerprint: Option<Fingerprint>, |
30316192 | 82 | no_media_set_locking: bool, // for list_media() |
c4d8542e DM |
83 | ) -> Result<Self, Error> { |
84 | ||
30316192 DM |
85 | let _pool_lock = if no_media_set_locking { |
86 | None | |
87 | } else { | |
88 | Some(lock_media_pool(state_path, name)?) | |
89 | }; | |
90 | ||
c4d8542e DM |
91 | let inventory = Inventory::load(state_path)?; |
92 | ||
93 | let current_media_set = match inventory.latest_media_set(name) { | |
94 | Some(set_uuid) => inventory.compute_media_set_members(&set_uuid)?, | |
95 | None => MediaSet::new(), | |
96 | }; | |
97 | ||
30316192 DM |
98 | let current_media_set_lock = if no_media_set_locking { |
99 | None | |
100 | } else { | |
101 | Some(lock_media_set(state_path, current_media_set.uuid(), None)?) | |
102 | }; | |
103 | ||
c4d8542e DM |
104 | Ok(MediaPool { |
105 | name: String::from(name), | |
1057b1f5 | 106 | state_path: state_path.to_owned(), |
c4d8542e DM |
107 | media_set_policy, |
108 | retention, | |
cdf39e62 | 109 | changer_name, |
c4d8542e | 110 | inventory, |
c4d8542e | 111 | current_media_set, |
30316192 | 112 | current_media_set_lock, |
8a0046f5 | 113 | encrypt_fingerprint, |
ab77d660 | 114 | force_media_availability: false, |
30316192 | 115 | no_media_set_locking, |
c4d8542e DM |
116 | }) |
117 | } | |
118 | ||
ab77d660 DM |
119 | /// Pretend all Online(x) and Offline media is available |
120 | /// | |
121 | /// Only media in Vault(y) is considered unavailable. | |
122 | pub fn force_media_availability(&mut self) { | |
123 | self.force_media_availability = true; | |
124 | } | |
125 | ||
32b75d36 DM |
126 | /// Returns the the current media set |
127 | pub fn current_media_set(&self) -> &MediaSet { | |
128 | &self.current_media_set | |
90e16be3 DM |
129 | } |
130 | ||
c4d8542e DM |
131 | /// Creates a new instance using the media pool configuration |
132 | pub fn with_config( | |
c4d8542e DM |
133 | state_path: &Path, |
134 | config: &MediaPoolConfig, | |
cdf39e62 | 135 | changer_name: Option<String>, |
30316192 | 136 | no_media_set_locking: bool, // for list_media() |
c4d8542e DM |
137 | ) -> Result<Self, Error> { |
138 | ||
e062ebbc | 139 | let allocation = config.allocation.clone().unwrap_or_else(|| String::from("continue")).parse()?; |
c4d8542e | 140 | |
e062ebbc | 141 | let retention = config.retention.clone().unwrap_or_else(|| String::from("keep")).parse()?; |
c4d8542e | 142 | |
8a0046f5 DM |
143 | let encrypt_fingerprint = match config.encrypt { |
144 | Some(ref fingerprint) => Some(fingerprint.parse()?), | |
145 | None => None, | |
146 | }; | |
147 | ||
148 | MediaPool::new( | |
149 | &config.name, | |
150 | state_path, | |
151 | allocation, | |
152 | retention, | |
cdf39e62 | 153 | changer_name, |
8a0046f5 | 154 | encrypt_fingerprint, |
30316192 | 155 | no_media_set_locking, |
8a0046f5 | 156 | ) |
c4d8542e DM |
157 | } |
158 | ||
159 | /// Returns the pool name | |
160 | pub fn name(&self) -> &str { | |
161 | &self.name | |
162 | } | |
163 | ||
d1d74c43 | 164 | /// Returns encryption settings |
8a0046f5 DM |
165 | pub fn encrypt_fingerprint(&self) -> Option<Fingerprint> { |
166 | self.encrypt_fingerprint.clone() | |
167 | } | |
168 | ||
25350f33 DM |
169 | pub fn set_media_status_damaged(&mut self, uuid: &Uuid) -> Result<(), Error> { |
170 | self.inventory.set_media_status_damaged(uuid) | |
171 | } | |
8a0046f5 | 172 | |
c4d8542e DM |
173 | fn compute_media_state(&self, media_id: &MediaId) -> (MediaStatus, MediaLocation) { |
174 | ||
cfae8f06 | 175 | let (status, location) = self.inventory.status_and_location(&media_id.label.uuid); |
c4d8542e DM |
176 | |
177 | match status { | |
178 | MediaStatus::Full | MediaStatus::Damaged | MediaStatus::Retired => { | |
179 | return (status, location); | |
180 | } | |
181 | MediaStatus::Unknown | MediaStatus::Writable => { | |
182 | /* possibly writable - fall through to check */ | |
183 | } | |
184 | } | |
185 | ||
186 | let set = match media_id.media_set_label { | |
187 | None => return (MediaStatus::Writable, location), // not assigned to any pool | |
188 | Some(ref set) => set, | |
189 | }; | |
190 | ||
191 | if set.pool != self.name { // should never trigger | |
192 | return (MediaStatus::Unknown, location); // belong to another pool | |
193 | } | |
194 | if set.uuid.as_ref() == [0u8;16] { // not assigned to any pool | |
195 | return (MediaStatus::Writable, location); | |
196 | } | |
197 | ||
198 | if &set.uuid != self.current_media_set.uuid() { | |
199 | return (MediaStatus::Full, location); // assume FULL | |
200 | } | |
201 | ||
202 | // media is member of current set | |
203 | if self.current_media_set.is_last_media(&media_id.label.uuid) { | |
204 | (MediaStatus::Writable, location) // last set member is writable | |
205 | } else { | |
206 | (MediaStatus::Full, location) | |
207 | } | |
208 | } | |
209 | ||
210 | /// Returns the 'MediaId' with associated state | |
211 | pub fn lookup_media(&self, uuid: &Uuid) -> Result<BackupMedia, Error> { | |
212 | let media_id = match self.inventory.lookup_media(uuid) { | |
213 | None => bail!("unable to lookup media {}", uuid), | |
214 | Some(media_id) => media_id.clone(), | |
215 | }; | |
216 | ||
217 | if let Some(ref set) = media_id.media_set_label { | |
218 | if set.pool != self.name { | |
219 | bail!("media does not belong to pool ({} != {})", set.pool, self.name); | |
220 | } | |
221 | } | |
222 | ||
223 | let (status, location) = self.compute_media_state(&media_id); | |
224 | ||
225 | Ok(BackupMedia::with_media_id( | |
226 | media_id, | |
227 | location, | |
228 | status, | |
229 | )) | |
230 | } | |
231 | ||
232 | /// List all media associated with this pool | |
233 | pub fn list_media(&self) -> Vec<BackupMedia> { | |
234 | let media_id_list = self.inventory.list_pool_media(&self.name); | |
235 | ||
236 | media_id_list.into_iter() | |
237 | .map(|media_id| { | |
238 | let (status, location) = self.compute_media_state(&media_id); | |
239 | BackupMedia::with_media_id( | |
240 | media_id, | |
241 | location, | |
242 | status, | |
243 | ) | |
244 | }) | |
245 | .collect() | |
246 | } | |
247 | ||
248 | /// Set media status to FULL. | |
249 | pub fn set_media_status_full(&mut self, uuid: &Uuid) -> Result<(), Error> { | |
250 | let media = self.lookup_media(uuid)?; // check if media belongs to this pool | |
251 | if media.status() != &MediaStatus::Full { | |
cfae8f06 | 252 | self.inventory.set_media_status_full(uuid)?; |
c4d8542e DM |
253 | } |
254 | Ok(()) | |
255 | } | |
256 | ||
257 | /// Make sure the current media set is usable for writing | |
258 | /// | |
259 | /// If not, starts a new media set. Also creates a new | |
e953029e | 260 | /// set if media_set_policy implies it, or if 'force' is true. |
ab77d660 DM |
261 | /// |
262 | /// Note: We also call this in list_media to compute correct media | |
263 | /// status, so this must not change persistent/saved state. | |
90e16be3 DM |
264 | /// |
265 | /// Returns the reason why we started a new media set (if we do) | |
30316192 DM |
266 | pub fn start_write_session( |
267 | &mut self, | |
268 | current_time: i64, | |
e953029e | 269 | force: bool, |
30316192 DM |
270 | ) -> Result<Option<String>, Error> { |
271 | ||
272 | let _pool_lock = if self.no_media_set_locking { | |
273 | None | |
274 | } else { | |
275 | Some(lock_media_pool(&self.state_path, &self.name)?) | |
276 | }; | |
90e16be3 | 277 | |
30316192 DM |
278 | self.inventory.reload()?; |
279 | ||
e953029e DC |
280 | let mut create_new_set = if force { |
281 | Some(String::from("forced")) | |
282 | } else { | |
283 | match self.current_set_usable() { | |
284 | Err(err) => { | |
285 | Some(err.to_string()) | |
286 | } | |
287 | Ok(_) => None, | |
288 | } | |
c4d8542e DM |
289 | }; |
290 | ||
90e16be3 | 291 | if create_new_set.is_none() { |
c4d8542e DM |
292 | match &self.media_set_policy { |
293 | MediaSetPolicy::AlwaysCreate => { | |
90e16be3 | 294 | create_new_set = Some(String::from("policy is AlwaysCreate")); |
c4d8542e DM |
295 | } |
296 | MediaSetPolicy::CreateAt(event) => { | |
297 | if let Some(set_start_time) = self.inventory.media_set_start_time(&self.current_media_set.uuid()) { | |
298 | if let Ok(Some(alloc_time)) = compute_next_event(event, set_start_time as i64, false) { | |
90e16be3 DM |
299 | if current_time >= alloc_time { |
300 | create_new_set = Some(String::from("policy CreateAt event triggered")); | |
c4d8542e DM |
301 | } |
302 | } | |
303 | } | |
304 | } | |
305 | MediaSetPolicy::ContinueCurrent => { /* do nothing here */ } | |
306 | } | |
307 | } | |
308 | ||
90e16be3 | 309 | if create_new_set.is_some() { |
c4d8542e | 310 | let media_set = MediaSet::new(); |
30316192 DM |
311 | |
312 | let current_media_set_lock = if self.no_media_set_locking { | |
313 | None | |
314 | } else { | |
315 | Some(lock_media_set(&self.state_path, media_set.uuid(), None)?) | |
316 | }; | |
317 | ||
318 | self.current_media_set_lock = current_media_set_lock; | |
c4d8542e DM |
319 | self.current_media_set = media_set; |
320 | } | |
321 | ||
90e16be3 | 322 | Ok(create_new_set) |
c4d8542e DM |
323 | } |
324 | ||
325 | /// List media in current media set | |
326 | pub fn current_media_list(&self) -> Result<Vec<&Uuid>, Error> { | |
327 | let mut list = Vec::new(); | |
328 | for opt_uuid in self.current_media_set.media_list().iter() { | |
329 | match opt_uuid { | |
330 | Some(ref uuid) => list.push(uuid), | |
331 | None => bail!("current_media_list failed - media set is incomplete"), | |
332 | } | |
333 | } | |
334 | Ok(list) | |
335 | } | |
336 | ||
d1d74c43 | 337 | // tests if the media data is considered as expired at specified time |
c4d8542e DM |
338 | pub fn media_is_expired(&self, media: &BackupMedia, current_time: i64) -> bool { |
339 | if media.status() != &MediaStatus::Full { | |
340 | return false; | |
341 | } | |
342 | ||
343 | let expire_time = self.inventory.media_expire_time( | |
344 | media.id(), &self.media_set_policy, &self.retention); | |
345 | ||
1bed3aed | 346 | current_time >= expire_time |
c4d8542e DM |
347 | } |
348 | ||
8a0046f5 DM |
349 | // check if a location is considered on site |
350 | pub fn location_is_available(&self, location: &MediaLocation) -> bool { | |
351 | match location { | |
cdf39e62 | 352 | MediaLocation::Online(name) => { |
ab77d660 DM |
353 | if self.force_media_availability { |
354 | true | |
cdf39e62 | 355 | } else { |
ab77d660 DM |
356 | if let Some(ref changer_name) = self.changer_name { |
357 | name == changer_name | |
358 | } else { | |
359 | // a standalone drive cannot use media currently inside a library | |
360 | false | |
361 | } | |
cdf39e62 DM |
362 | } |
363 | } | |
364 | MediaLocation::Offline => { | |
ab77d660 DM |
365 | if self.force_media_availability { |
366 | true | |
367 | } else { | |
368 | // consider available for standalone drives | |
369 | self.changer_name.is_none() | |
370 | } | |
cdf39e62 | 371 | } |
8a0046f5 DM |
372 | MediaLocation::Vault(_) => false, |
373 | } | |
374 | } | |
375 | ||
376 | fn add_media_to_current_set(&mut self, mut media_id: MediaId, current_time: i64) -> Result<(), Error> { | |
377 | ||
30316192 DM |
378 | if self.current_media_set_lock.is_none() { |
379 | bail!("add_media_to_current_set: media set is not locked - internal error"); | |
380 | } | |
381 | ||
8a0046f5 DM |
382 | let seq_nr = self.current_media_set.media_list().len() as u64; |
383 | ||
384 | let pool = self.name.clone(); | |
385 | ||
386 | let encrypt_fingerprint = self.encrypt_fingerprint(); | |
387 | ||
388 | let set = MediaSetLabel::with_data( | |
389 | &pool, | |
390 | self.current_media_set.uuid().clone(), | |
391 | seq_nr, | |
392 | current_time, | |
393 | encrypt_fingerprint, | |
394 | ); | |
395 | ||
396 | media_id.media_set_label = Some(set); | |
397 | ||
398 | let uuid = media_id.label.uuid.clone(); | |
399 | ||
f281b8d3 | 400 | MediaCatalog::overwrite(&self.state_path, &media_id, false)?; // overwite catalog |
8a0046f5 DM |
401 | let clear_media_status = true; // remove Full status |
402 | self.inventory.store(media_id, clear_media_status)?; // store persistently | |
403 | ||
404 | self.current_media_set.add_media(uuid); | |
405 | ||
406 | Ok(()) | |
407 | } | |
408 | ||
409 | ||
c4d8542e DM |
410 | /// Allocates a writable media to the current media set |
411 | pub fn alloc_writable_media(&mut self, current_time: i64) -> Result<Uuid, Error> { | |
412 | ||
30316192 DM |
413 | if self.current_media_set_lock.is_none() { |
414 | bail!("alloc_writable_media: media set is not locked - internal error"); | |
415 | } | |
416 | ||
c4d8542e DM |
417 | let last_is_writable = self.current_set_usable()?; |
418 | ||
c4d8542e DM |
419 | if last_is_writable { |
420 | let last_uuid = self.current_media_set.last_media_uuid().unwrap(); | |
421 | let media = self.lookup_media(last_uuid)?; | |
422 | return Ok(media.uuid().clone()); | |
423 | } | |
424 | ||
425 | // try to find empty media in pool, add to media set | |
426 | ||
30316192 DM |
427 | { // limit pool lock scope |
428 | let _pool_lock = lock_media_pool(&self.state_path, &self.name)?; | |
c4d8542e | 429 | |
30316192 | 430 | self.inventory.reload()?; |
8a0046f5 | 431 | |
30316192 DM |
432 | let media_list = self.list_media(); |
433 | ||
434 | let mut empty_media = Vec::new(); | |
435 | let mut used_media = Vec::new(); | |
436 | ||
437 | for media in media_list.into_iter() { | |
438 | if !self.location_is_available(media.location()) { | |
439 | continue; | |
440 | } | |
441 | // already part of a media set? | |
442 | if media.media_set_label().is_some() { | |
443 | used_media.push(media); | |
444 | } else { | |
445 | // only consider writable empty media | |
446 | if media.status() == &MediaStatus::Writable { | |
447 | empty_media.push(media); | |
448 | } | |
8a0046f5 | 449 | } |
c4d8542e | 450 | } |
c4d8542e | 451 | |
30316192 DM |
452 | // sort empty_media, newest first -> oldest last |
453 | empty_media.sort_unstable_by(|a, b| { | |
454 | let mut res = b.label().ctime.cmp(&a.label().ctime); | |
455 | if res == std::cmp::Ordering::Equal { | |
456 | res = b.label().label_text.cmp(&a.label().label_text); | |
457 | } | |
458 | res | |
459 | }); | |
460 | ||
461 | if let Some(media) = empty_media.pop() { | |
462 | // found empty media, add to media set an use it | |
463 | let uuid = media.uuid().clone(); | |
464 | self.add_media_to_current_set(media.into_id(), current_time)?; | |
465 | return Ok(uuid); | |
af762341 | 466 | } |
c4d8542e | 467 | |
30316192 | 468 | println!("no empty media in pool, try to reuse expired media"); |
c4d8542e | 469 | |
30316192 | 470 | let mut expired_media = Vec::new(); |
c4d8542e | 471 | |
30316192 DM |
472 | for media in used_media.into_iter() { |
473 | if let Some(set) = media.media_set_label() { | |
474 | if &set.uuid == self.current_media_set.uuid() { | |
475 | continue; | |
476 | } | |
477 | } else { | |
c4d8542e DM |
478 | continue; |
479 | } | |
8a0046f5 | 480 | |
30316192 DM |
481 | if self.media_is_expired(&media, current_time) { |
482 | println!("found expired media on media '{}'", media.label_text()); | |
483 | expired_media.push(media); | |
484 | } | |
c4d8542e | 485 | } |
c4d8542e | 486 | |
30316192 DM |
487 | // sort expired_media, newest first -> oldest last |
488 | expired_media.sort_unstable_by(|a, b| { | |
489 | let mut res = b.media_set_label().unwrap().ctime.cmp(&a.media_set_label().unwrap().ctime); | |
490 | if res == std::cmp::Ordering::Equal { | |
491 | res = b.label().label_text.cmp(&a.label().label_text); | |
492 | } | |
493 | res | |
494 | }); | |
495 | ||
496 | while let Some(media) = expired_media.pop() { | |
497 | // check if we can modify the media-set (i.e. skip | |
498 | // media used by a restore job) | |
499 | if let Ok(_media_set_lock) = lock_media_set( | |
500 | &self.state_path, | |
501 | &media.media_set_label().unwrap().uuid, | |
502 | Some(std::time::Duration::new(0, 0)), // do not wait | |
503 | ) { | |
504 | println!("reuse expired media '{}'", media.label_text()); | |
505 | let uuid = media.uuid().clone(); | |
506 | self.add_media_to_current_set(media.into_id(), current_time)?; | |
507 | return Ok(uuid); | |
508 | } | |
af762341 | 509 | } |
25e464c5 | 510 | } |
c4d8542e | 511 | |
25e464c5 | 512 | println!("no expired media in pool, try to find unassigned/free media"); |
c4d8542e | 513 | |
25e464c5 | 514 | // try unassigned media |
30316192 | 515 | let _lock = lock_unassigned_media_pool(&self.state_path)?; |
1057b1f5 DM |
516 | |
517 | self.inventory.reload()?; | |
518 | ||
25e464c5 DM |
519 | let mut free_media = Vec::new(); |
520 | ||
521 | for media_id in self.inventory.list_unassigned_media() { | |
522 | ||
523 | let (status, location) = self.compute_media_state(&media_id); | |
524 | if media_id.media_set_label.is_some() { continue; } // should not happen | |
525 | ||
8a0046f5 DM |
526 | if !self.location_is_available(&location) { |
527 | continue; | |
c4d8542e | 528 | } |
25e464c5 DM |
529 | |
530 | // only consider writable media | |
531 | if status != MediaStatus::Writable { continue; } | |
532 | ||
533 | free_media.push(media_id); | |
534 | } | |
535 | ||
af762341 DM |
536 | // sort free_media, newest first -> oldest last |
537 | free_media.sort_unstable_by(|a, b| { | |
538 | let mut res = b.label.ctime.cmp(&a.label.ctime); | |
539 | if res == std::cmp::Ordering::Equal { | |
540 | res = b.label.label_text.cmp(&a.label.label_text); | |
541 | } | |
542 | res | |
543 | }); | |
544 | ||
8a0046f5 DM |
545 | if let Some(media_id) = free_media.pop() { |
546 | println!("use free media '{}'", media_id.label.label_text); | |
547 | let uuid = media_id.label.uuid.clone(); | |
548 | self.add_media_to_current_set(media_id, current_time)?; | |
549 | return Ok(uuid); | |
c4d8542e | 550 | } |
25e464c5 | 551 | |
25e464c5 | 552 | bail!("alloc writable media in pool '{}' failed: no usable media found", self.name()); |
c4d8542e DM |
553 | } |
554 | ||
555 | /// check if the current media set is usable for writing | |
556 | /// | |
557 | /// This does several consistency checks, and return if | |
558 | /// the last media in the current set is in writable state. | |
559 | /// | |
560 | /// This return error when the media set must not be used any | |
561 | /// longer because of consistency errors. | |
562 | pub fn current_set_usable(&self) -> Result<bool, Error> { | |
563 | ||
6dd05135 DM |
564 | let media_list = self.current_media_set.media_list(); |
565 | ||
566 | let media_count = media_list.len(); | |
c4d8542e DM |
567 | if media_count == 0 { |
568 | return Ok(false); | |
569 | } | |
570 | ||
571 | let set_uuid = self.current_media_set.uuid(); | |
572 | let mut last_is_writable = false; | |
573 | ||
6dd05135 DM |
574 | let mut last_enc: Option<Option<Fingerprint>> = None; |
575 | ||
576 | for (seq, opt_uuid) in media_list.iter().enumerate() { | |
c4d8542e DM |
577 | let uuid = match opt_uuid { |
578 | None => bail!("media set is incomplete (missing media information)"), | |
579 | Some(uuid) => uuid, | |
580 | }; | |
581 | let media = self.lookup_media(uuid)?; | |
582 | match media.media_set_label() { | |
583 | Some(MediaSetLabel { seq_nr, uuid, ..}) if *seq_nr == seq as u64 && uuid == set_uuid => { /* OK */ }, | |
584 | Some(MediaSetLabel { seq_nr, uuid, ..}) if uuid == set_uuid => { | |
585 | bail!("media sequence error ({} != {})", *seq_nr, seq); | |
586 | }, | |
587 | Some(MediaSetLabel { uuid, ..}) => bail!("media owner error ({} != {}", uuid, set_uuid), | |
588 | None => bail!("media owner error (no owner)"), | |
589 | } | |
6dd05135 DM |
590 | |
591 | if let Some(set) = media.media_set_label() { // always true here | |
592 | if set.encryption_key_fingerprint != self.encrypt_fingerprint { | |
593 | bail!("pool encryption key changed"); | |
594 | } | |
595 | match last_enc { | |
596 | None => { | |
597 | last_enc = Some(set.encryption_key_fingerprint.clone()); | |
598 | } | |
599 | Some(ref last_enc) => { | |
600 | if last_enc != &set.encryption_key_fingerprint { | |
601 | bail!("inconsistent media encryption key"); | |
602 | } | |
603 | } | |
604 | } | |
605 | } | |
606 | ||
c4d8542e DM |
607 | match media.status() { |
608 | MediaStatus::Full => { /* OK */ }, | |
609 | MediaStatus::Writable if (seq + 1) == media_count => { | |
b81e37f6 DM |
610 | let media_location = media.location(); |
611 | if self.location_is_available(media_location) { | |
612 | last_is_writable = true; | |
613 | } else { | |
614 | if let MediaLocation::Vault(vault) = media_location { | |
c4d8542e DM |
615 | bail!("writable media offsite in vault '{}'", vault); |
616 | } | |
617 | } | |
618 | }, | |
619 | _ => bail!("unable to use media set - wrong media status {:?}", media.status()), | |
620 | } | |
621 | } | |
ab77d660 | 622 | |
c4d8542e DM |
623 | Ok(last_is_writable) |
624 | } | |
625 | ||
626 | /// Generate a human readable name for the media set | |
627 | pub fn generate_media_set_name( | |
628 | &self, | |
629 | media_set_uuid: &Uuid, | |
630 | template: Option<String>, | |
631 | ) -> Result<String, Error> { | |
632 | self.inventory.generate_media_set_name(media_set_uuid, template) | |
633 | } | |
634 | ||
c4d8542e DM |
635 | } |
636 | ||
637 | /// Backup media | |
638 | /// | |
639 | /// Combines 'MediaId' with 'MediaLocation' and 'MediaStatus' | |
640 | /// information. | |
641 | #[derive(Debug,Serialize,Deserialize,Clone)] | |
642 | pub struct BackupMedia { | |
643 | /// Media ID | |
644 | id: MediaId, | |
645 | /// Media location | |
646 | location: MediaLocation, | |
647 | /// Media status | |
648 | status: MediaStatus, | |
649 | } | |
650 | ||
651 | impl BackupMedia { | |
652 | ||
653 | /// Creates a new instance | |
654 | pub fn with_media_id( | |
655 | id: MediaId, | |
656 | location: MediaLocation, | |
657 | status: MediaStatus, | |
658 | ) -> Self { | |
659 | Self { id, location, status } | |
660 | } | |
661 | ||
662 | /// Returns the media location | |
663 | pub fn location(&self) -> &MediaLocation { | |
664 | &self.location | |
665 | } | |
666 | ||
667 | /// Returns the media status | |
668 | pub fn status(&self) -> &MediaStatus { | |
669 | &self.status | |
670 | } | |
671 | ||
672 | /// Returns the media uuid | |
673 | pub fn uuid(&self) -> &Uuid { | |
674 | &self.id.label.uuid | |
675 | } | |
676 | ||
677 | /// Returns the media set label | |
6543214d DM |
678 | pub fn media_set_label(&self) -> Option<&MediaSetLabel> { |
679 | self.id.media_set_label.as_ref() | |
680 | } | |
681 | ||
682 | /// Returns the media creation time | |
683 | pub fn ctime(&self) -> i64 { | |
684 | self.id.label.ctime | |
c4d8542e DM |
685 | } |
686 | ||
687 | /// Updates the media set label | |
688 | pub fn set_media_set_label(&mut self, set_label: MediaSetLabel) { | |
689 | self.id.media_set_label = Some(set_label); | |
690 | } | |
54f4ecd4 | 691 | |
c4d8542e | 692 | /// Returns the drive label |
a78348ac | 693 | pub fn label(&self) -> &MediaLabel { |
c4d8542e DM |
694 | &self.id.label |
695 | } | |
696 | ||
697 | /// Returns the media id (drive label + media set label) | |
698 | pub fn id(&self) -> &MediaId { | |
699 | &self.id | |
700 | } | |
701 | ||
8a0046f5 DM |
702 | /// Returns the media id, consumes self) |
703 | pub fn into_id(self) -> MediaId { | |
704 | self.id | |
705 | } | |
706 | ||
c4d8542e | 707 | /// Returns the media label (Barcode) |
8446fbca DM |
708 | pub fn label_text(&self) -> &str { |
709 | &self.id.label.label_text | |
c4d8542e DM |
710 | } |
711 | } |