1use anyhow::{Context, Result};
2use std::io::Read;
3use std::path::{Path, PathBuf};
4use std::sync::atomic::{AtomicUsize, Ordering};
5use walkdir::WalkDir;
6
7use super::incremental::resolve_backup_chain;
8use super::integrity::BackupMetadata;
9use crate::crypto::{EncryptedData, KeyManager};
10use crate::security::{safe_join, safe_open, AuditEvent, AuditLog};
11use crate::ui::progress::BackupProgress;
12
13#[derive(Debug)]
17pub struct RestoreResult {
18 pub total_files: usize,
19 pub restored: usize,
20 pub failed: usize,
21 pub encrypted_files: usize,
22 pub verified_files: usize,
23 pub verification_failures: usize,
24 pub total_bytes: u64,
25 pub errors: Vec<String>,
26}
27
28pub struct RestoreEngine {
35 dry_run: bool,
36 show_progress: bool,
37 verify_integrity: bool,
38 audit_log: Option<AuditLog>,
39}
40
41impl RestoreEngine {
42 #[must_use]
44 pub fn new(dry_run: bool) -> Self {
45 let audit_log = AuditLog::new()
46 .map_err(|e| eprintln!("警告: 監査ログの初期化に失敗しました: {e}"))
47 .ok();
48
49 Self {
50 dry_run,
51 show_progress: true,
52 verify_integrity: true,
53 audit_log,
54 }
55 }
56
57 #[must_use]
59 pub fn with_progress(mut self, show_progress: bool) -> Self {
60 self.show_progress = show_progress;
61 self
62 }
63
64 #[must_use]
66 pub fn with_verification(mut self, verify: bool) -> Self {
67 self.verify_integrity = verify;
68 self
69 }
70
71 pub fn restore(
83 &mut self,
84 backup_dir: &Path,
85 dest_dir: &Path,
86 password: Option<&str>,
87 ) -> Result<RestoreResult> {
88 let user = AuditLog::current_user();
89 let target_desc = "backup_dir.display() → dest_dir.display()".to_string();
90
91 if let Some(ref mut audit_log) = self.audit_log {
93 let _ = audit_log
94 .log(AuditEvent::restore_started(&target_desc, &user))
95 .map_err(|e| eprintln!("警告: 監査ログの記録に失敗しました: {e}"));
96 }
97
98 if !backup_dir.exists() {
99 if let Some(ref mut audit_log) = self.audit_log {
101 let _ = audit_log
102 .log(AuditEvent::restore_failed(
103 &target_desc,
104 &user,
105 "バックアップディレクトリが存在しません",
106 ))
107 .map_err(|e| eprintln!("警告: 監査ログの記録に失敗しました: {e}"));
108 }
109
110 return Err(anyhow::anyhow!(
111 "バックアップディレクトリが存在しません: backup_dir.display()".to_string()
112 ));
113 }
114
115 let backup_chain = resolve_backup_chain(backup_dir)?;
117
118 if backup_chain.len() > 1 {
119 println!(
120 "📦 増分バックアップチェーン検出: {} 個のバックアップを順次復元",
121 backup_chain.len()
122 );
123 for (i, backup) in backup_chain.iter().enumerate() {
124 println!(" {}. {:?}", i + 1, backup.file_name().unwrap_or_default());
125 }
126 }
127
128 if !self.dry_run {
130 std::fs::create_dir_all(dest_dir)
131 .context("復元先ディレクトリ作成失敗: dest_dir.display()".to_string())?;
132 }
133
134 let mut all_files: Vec<(PathBuf, PathBuf)> = Vec::new(); for backup in &backup_chain {
137 let files_in_backup: Vec<PathBuf> = WalkDir::new(backup)
138 .into_iter()
139 .filter_map(std::result::Result::ok)
140 .filter(|e| e.file_type().is_file())
141 .filter(|e| {
142 e.file_name() != ".integrity"
144 })
145 .map(|e| e.path().to_path_buf())
146 .collect();
147
148 for file_path in files_in_backup {
149 all_files.push((backup.clone(), file_path));
150 }
151 }
152
153 let files: Vec<PathBuf> = all_files.iter().map(|(_, path)| path.clone()).collect();
154
155 let total_files = files.len();
156
157 if self.dry_run {
158 println!("📋 ドライランモード: {total_files} ファイルを復元対象として検出");
159 for (backup_src, file) in &all_files {
160 if let Ok(relative) = file.strip_prefix(backup_src) {
161 println!(" {}", relative.display());
162 }
163 }
164 return Ok(RestoreResult {
165 total_files,
166 restored: 0,
167 failed: 0,
168 encrypted_files: 0,
169 verified_files: 0,
170 verification_failures: 0,
171 total_bytes: 0,
172 errors: Vec::new(),
173 });
174 }
175
176 let progress = if self.show_progress {
178 let pb = BackupProgress::new(total_files as u64);
179 pb.set_message("復元中...");
180 Some(pb)
181 } else {
182 None
183 };
184
185 let restored_count = AtomicUsize::new(0);
186 let failed_count = AtomicUsize::new(0);
187 let encrypted_count = AtomicUsize::new(0);
188 let verified_count = AtomicUsize::new(0);
189 let verification_failed_count = AtomicUsize::new(0);
190 let total_bytes = AtomicUsize::new(0);
191
192 let mut master_key_opt: Option<std::sync::Arc<crate::crypto::MasterKey>> = None;
194
195 let mut backup_metadata_map: std::collections::HashMap<PathBuf, BackupMetadata> =
197 std::collections::HashMap::new();
198 if self.verify_integrity {
199 for backup in &backup_chain {
200 match BackupMetadata::load(backup) {
201 Ok(metadata) => {
202 backup_metadata_map.insert(backup.clone(), metadata);
203 }
204 Err(e) => {
205 eprintln!(
206 "警告: 整合性メタデータの読み込みに失敗しました ({}): {e}",
207 backup.display()
208 );
209 }
210 }
211 }
212 if !backup_metadata_map.is_empty() {
213 println!(
214 "✓ 整合性メタデータを読み込みました({}個のバックアップ)",
215 backup_metadata_map.len()
216 );
217 }
218 }
219
220 let mut errors = Vec::new();
221
222 for (source_backup_dir, source_path) in &all_files {
223 if let Some(ref pb) = progress {
225 if let Some(file_name) = source_path.file_name() {
226 pb.set_message(&format!("復元中: {file_name:?}"));
227 }
228 }
229
230 let relative_path = match source_path.strip_prefix(source_backup_dir) {
232 Ok(r) => r,
233 Err(e) => {
234 errors.push(format!("相対パス取得失敗: source_path.display(): {e}"));
235 failed_count.fetch_add(1, Ordering::Relaxed);
236 if let Some(ref pb) = progress {
237 pb.inc(1);
238 }
239 continue;
240 }
241 };
242
243 let dest_path = match safe_join(dest_dir, relative_path) {
245 Ok(p) => p,
246 Err(e) => {
247 errors.push(format!(
248 "パストラバーサル検出: relative_path.display(): {e}"
249 ));
250 failed_count.fetch_add(1, Ordering::Relaxed);
251 if let Some(ref pb) = progress {
252 pb.inc(1);
253 }
254 continue;
255 }
256 };
257
258 if let Some(parent) = dest_path.parent() {
260 if let Err(e) = std::fs::create_dir_all(parent) {
261 errors.push(format!("ディレクトリ作成失敗: {}: {e}", parent.display()));
262 failed_count.fetch_add(1, Ordering::Relaxed);
263 if let Some(ref pb) = progress {
264 pb.inc(1);
265 }
266 continue;
267 }
268 }
269
270 let file_data = match safe_open(source_path) {
272 Ok(mut file) => {
273 let mut buffer = Vec::new();
274 match file.read_to_end(&mut buffer) {
275 Ok(_) => buffer,
276 Err(e) => {
277 errors.push(format!("ファイル読み込み失敗: {e}"));
278 failed_count.fetch_add(1, Ordering::Relaxed);
279 if let Some(ref pb) = progress {
280 pb.inc(1);
281 }
282 continue;
283 }
284 }
285 }
286 Err(e) => {
287 errors.push(format!(
288 "ファイルオープン失敗(シンボリックリンク検出の可能性): {e}"
289 ));
290 failed_count.fetch_add(1, Ordering::Relaxed);
291 if let Some(ref pb) = progress {
292 pb.inc(1);
293 }
294 continue;
295 }
296 };
297
298 let final_data = if let Ok(encrypted_data) = EncryptedData::from_bytes(&file_data) {
300 encrypted_count.fetch_add(1, Ordering::Relaxed);
302
303 if master_key_opt.is_none() {
305 let pwd = match password {
306 Some(p) => p.to_string(),
307 None => {
308 errors.push(
309 "暗号化されたファイルですがパスワードが未指定: relative_path.display()".to_string()
310 );
311 failed_count.fetch_add(1, Ordering::Relaxed);
312 if let Some(ref pb) = progress {
313 pb.inc(1);
314 }
315 continue;
316 }
317 };
318
319 let km = KeyManager::default();
321 match km.restore_master_key(&pwd, &encrypted_data.salt) {
322 Ok(mk) => {
323 master_key_opt = Some(std::sync::Arc::new(mk));
324 }
325 Err(e) => {
326 errors.push(format!("マスターキー復元失敗: {e}"));
327 failed_count.fetch_add(1, Ordering::Relaxed);
328 if let Some(ref pb) = progress {
329 pb.inc(1);
330 }
331 continue;
332 }
333 }
334 }
335
336 let master_key = master_key_opt.as_ref().unwrap();
338 let encryption_engine = crate::crypto::EncryptionEngine::default();
339
340 match encryption_engine.decrypt(&encrypted_data, master_key) {
341 Ok(decrypted_data) => {
342 self.decompress_if_needed(&decrypted_data)?
344 }
345 Err(e) => {
346 errors.push(format!("復号化失敗: relative_path.display(): {e}"));
347 failed_count.fetch_add(1, Ordering::Relaxed);
348 if let Some(ref pb) = progress {
349 pb.inc(1);
350 }
351 continue;
352 }
353 }
354 } else {
355 self.decompress_if_needed(&file_data)?
358 };
359
360 match std::fs::write(&dest_path, &final_data) {
362 Ok(_) => {
363 restored_count.fetch_add(1, Ordering::Relaxed);
364 total_bytes.fetch_add(final_data.len(), Ordering::Relaxed);
365
366 if let Some(metadata) = backup_metadata_map.get(source_backup_dir) {
368 match metadata.verify_file(relative_path, &dest_path) {
369 Ok(true) => {
370 verified_count.fetch_add(1, Ordering::Relaxed);
371 }
372 Ok(false) => {
373 verification_failed_count.fetch_add(1, Ordering::Relaxed);
374 errors.push(
375 "⚠ 整合性検証失敗(ファイルが改ざんされています): relative_path.display()".to_string()
376 );
377 }
378 Err(e) => {
379 eprintln!("警告: 整合性検証エラー: relative_path.display(): {e}");
380 }
381 }
382 }
383 }
384 Err(e) => {
385 errors.push(format!("ファイル書き込み失敗: dest_path.display(): {e}"));
386 failed_count.fetch_add(1, Ordering::Relaxed);
387 }
388 }
389
390 if let Some(ref pb) = progress {
391 pb.inc(1);
392 }
393 }
394
395 if let Some(pb) = progress {
397 let failed = failed_count.load(Ordering::Relaxed);
398 if failed == 0 {
399 pb.finish("✓ 復元完了");
400 } else {
401 pb.finish(&format!("⚠ 復元完了({failed}件失敗)"));
402 }
403 }
404
405 let result = RestoreResult {
406 total_files,
407 restored: restored_count.load(Ordering::Relaxed),
408 failed: failed_count.load(Ordering::Relaxed),
409 encrypted_files: encrypted_count.load(Ordering::Relaxed),
410 verified_files: verified_count.load(Ordering::Relaxed),
411 verification_failures: verification_failed_count.load(Ordering::Relaxed),
412 total_bytes: total_bytes.load(Ordering::Relaxed) as u64,
413 errors,
414 };
415
416 if let Some(ref mut audit_log) = self.audit_log {
418 let metadata = serde_json::json!({
419 "total_files": result.total_files,
420 "restored": result.restored,
421 "failed": result.failed,
422 "encrypted_files": result.encrypted_files,
423 "verified_files": result.verified_files,
424 "verification_failures": result.verification_failures,
425 "total_bytes": result.total_bytes,
426 });
427
428 let event = if result.failed == 0 {
429 AuditEvent::restore_completed(&target_desc, &user, metadata)
430 } else {
431 AuditEvent::restore_failed(
432 &target_desc,
433 &user,
434 format!("{}件のファイルでエラーが発生しました", result.failed),
435 )
436 };
437
438 let _ = audit_log
439 .log(event)
440 .map_err(|e| eprintln!("警告: 監査ログの記録に失敗しました: {e}"));
441 }
442
443 Ok(result)
444 }
445
446 fn decompress_if_needed(&self, data: &[u8]) -> Result<Vec<u8>> {
448 if let Ok(decompressed) = zstd::decode_all(data) {
450 Ok(decompressed)
451 } else {
452 let mut decoder = flate2::read::GzDecoder::new(data);
453 let mut decompressed = Vec::new();
454 if decoder.read_to_end(&mut decompressed).is_ok() && !decompressed.is_empty() {
455 Ok(decompressed)
456 } else {
457 Ok(data.to_vec())
459 }
460 }
461 }
462}
463
464#[cfg(test)]
465mod tests {
466 use super::*;
467 use std::fs;
468 use tempfile::TempDir;
469
470 #[test]
471 fn test_restore_unencrypted() {
472 let temp = TempDir::new().unwrap();
473 let backup_dir = temp.path().join("backup");
474 let restore_dir = temp.path().join("restore");
475
476 fs::create_dir_all(&backup_dir).unwrap();
478 fs::write(backup_dir.join("test.txt"), b"test content").unwrap();
479
480 let mut engine = RestoreEngine::new(false).with_progress(false);
481 let result = engine.restore(&backup_dir, &restore_dir, None).unwrap();
482
483 assert_eq!(result.total_files, 1);
484 assert_eq!(result.restored, 1);
485 assert_eq!(result.failed, 0);
486 assert_eq!(result.encrypted_files, 0);
487 assert_eq!(result.verified_files, 0);
489 assert_eq!(result.verification_failures, 0);
490
491 let restored_content = fs::read_to_string(restore_dir.join("test.txt")).unwrap();
493 assert_eq!(restored_content, "test content");
494 }
495
496 #[test]
497 fn test_restore_dry_run() {
498 let temp = TempDir::new().unwrap();
499 let backup_dir = temp.path().join("backup");
500 let restore_dir = temp.path().join("restore");
501
502 fs::create_dir_all(&backup_dir).unwrap();
503 fs::write(backup_dir.join("test.txt"), b"test").unwrap();
504
505 let mut engine = RestoreEngine::new(true).with_progress(false);
506 let result = engine.restore(&backup_dir, &restore_dir, None).unwrap();
507
508 assert_eq!(result.total_files, 1);
509 assert_eq!(result.restored, 0); assert!(!restore_dir.exists()); }
512}