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