backup_suite/core/
restore.rs

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/// 復元結果
14///
15/// バックアップからの復元処理の結果を保持します。
16#[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
28// RestoreResult は直接構築されるため、new() メソッドは不要
29
30/// 復元エンジン
31///
32/// バックアップからファイルを復元します。
33/// 暗号化、圧縮の自動検出と展開に対応しています。
34pub struct RestoreEngine {
35    dry_run: bool,
36    show_progress: bool,
37    verify_integrity: bool,
38    audit_log: Option<AuditLog>,
39}
40
41impl RestoreEngine {
42    /// 新しいRestoreEngineを作成
43    #[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    /// 進捗表示の有効/無効を設定
58    #[must_use]
59    pub fn with_progress(mut self, show_progress: bool) -> Self {
60        self.show_progress = show_progress;
61        self
62    }
63
64    /// 整合性検証の有効/無効を設定
65    #[must_use]
66    pub fn with_verification(mut self, verify: bool) -> Self {
67        self.verify_integrity = verify;
68        self
69    }
70
71    /// バックアップから復元
72    ///
73    /// # 引数
74    ///
75    /// * `backup_dir` - バックアップディレクトリのパス
76    /// * `dest_dir` - 復元先ディレクトリ
77    /// * `password` - 暗号化されている場合のパスワード(Optional)
78    ///
79    /// # 戻り値
80    ///
81    /// 成功時は RestoreResult、失敗時はエラー
82    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        // 監査ログ: 復元開始
92        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            // 監査ログ: 復元失敗
100            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        // 増分バックアップチェーンの解決
116        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        // 復元先ディレクトリを作成
129        if !self.dry_run {
130            std::fs::create_dir_all(dest_dir)
131                .context("復元先ディレクトリ作成失敗: dest_dir.display()".to_string())?;
132        }
133
134        // チェーン内のすべてのバックアップからファイル一覧を収集
135        let mut all_files: Vec<(PathBuf, PathBuf)> = Vec::new(); // (source_backup_dir, file_path)
136        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                    // .integrityファイルを除外
143                    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        // プログレスバーの初期化
177        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        // マスターキー(遅延初期化)
193        let mut master_key_opt: Option<std::sync::Arc<crate::crypto::MasterKey>> = None;
194
195        // 各バックアップディレクトリの整合性メタデータを読み込み
196        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            // プログレス更新
224            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            // 相対パスを取得(元のバックアップディレクトリを基準に)
231            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            // 復元先パスを安全に結合(パストラバーサル対策)
244            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            // 親ディレクトリを作成
259            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            // ファイルを安全に読み込み(シンボリックリンク攻撃対策)
271            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            // 暗号化データかどうか判定して復号
299            let final_data = if let Ok(encrypted_data) = EncryptedData::from_bytes(&file_data) {
300                // 暗号化されたファイル
301                encrypted_count.fetch_add(1, Ordering::Relaxed);
302
303                // マスターキーがまだ作成されていない場合
304                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                    // マスターキー生成
320                    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                // 復号化
337                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                        // 復号化されたデータを展開(圧縮されている可能性)
343                        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                // 通常のファイル(暗号化されていない)
356                // 圧縮されている可能性を確認
357                self.decompress_if_needed(&file_data)?
358            };
359
360            // 復元先に書き込み
361            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                    // 整合性検証(該当するバックアップディレクトリのメタデータを使用)
367                    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        // プログレスバー完了
396        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        // 監査ログ: 復元完了 or 失敗
417        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    /// 圧縮されている場合に展開
447    fn decompress_if_needed(&self, data: &[u8]) -> Result<Vec<u8>> {
448        // zstd → gzip → 無圧縮の順で試す
449        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                // 圧縮されていないと判断
458                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        // テストデータを作成
477        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        // Note: verification is 0 because no .integrity metadata exists in this test
488        assert_eq!(result.verified_files, 0);
489        assert_eq!(result.verification_failures, 0);
490
491        // 復元されたファイルを確認
492        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); // ドライランなので実行なし
510        assert!(!restore_dir.exists()); // ディレクトリも作成されない
511    }
512}