|
| 1 | +#[cfg(not(windows))] |
1 | 2 | use std::fs::File; |
2 | 3 | use std::path::{Component, Path, PathBuf}; |
3 | 4 |
|
@@ -578,16 +579,7 @@ fn quarantine_family_paths(database: &Path, transaction_id: &str) -> Result<[Pat |
578 | 579 | database.display() |
579 | 580 | )) |
580 | 581 | })?; |
581 | | - let transaction_component = transaction_id |
582 | | - .bytes() |
583 | | - .map(|byte| { |
584 | | - if byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_') { |
585 | | - (byte as char).to_string() |
586 | | - } else { |
587 | | - format!("_{byte:02x}") |
588 | | - } |
589 | | - }) |
590 | | - .collect::<String>(); |
| 582 | + let transaction_component = transaction_file_component(transaction_id); |
591 | 583 | let database = parent.join(format!( |
592 | 584 | ".{name}{QUARANTINE_MARKER}{transaction_component}.quarantine" |
593 | 585 | )); |
@@ -787,7 +779,8 @@ fn require_missing(path: &Path, description: &str) -> Result<()> { |
787 | 779 | fn persist_journal(tracedecay_dir: &Path, journal: &DeletionJournal) -> Result<()> { |
788 | 780 | validate_journal(tracedecay_dir, journal)?; |
789 | 781 | let path = journal_path(tracedecay_dir); |
790 | | - let temp = tracedecay_dir.join(format!("{JOURNAL_FILENAME}.tmp-{}", journal.transaction_id)); |
| 782 | + let transaction_component = transaction_file_component(&journal.transaction_id); |
| 783 | + let temp = tracedecay_dir.join(format!("{JOURNAL_FILENAME}.tmp-{transaction_component}")); |
791 | 784 | let bytes = serde_json::to_vec_pretty(journal)?; |
792 | 785 | if let Err(error) = PrivateStoreIo::write_file_atomically(&path, &temp, &bytes) { |
793 | 786 | let _ = std::fs::remove_file(&temp); |
@@ -867,12 +860,24 @@ fn clear_journal(tracedecay_dir: &Path) -> Result<()> { |
867 | 860 | } |
868 | 861 | } |
869 | 862 |
|
| 863 | +#[cfg(not(windows))] |
870 | 864 | fn sync_file(path: &Path) -> Result<()> { |
871 | | - File::open(path) |
| 865 | + std::fs::OpenOptions::new() |
| 866 | + .read(true) |
| 867 | + .write(true) |
| 868 | + .open(path) |
872 | 869 | .and_then(|file| file.sync_all()) |
873 | 870 | .map_err(|error| config_error(format!("failed to sync '{}': {error}", path.display()))) |
874 | 871 | } |
875 | 872 |
|
| 873 | +#[cfg(windows)] |
| 874 | +fn sync_file(_path: &Path) -> Result<()> { |
| 875 | + // PrivateStoreIo publishes these records with MoveFileExW's |
| 876 | + // MOVEFILE_WRITE_THROUGH. Reopening the replaced path for a second flush |
| 877 | + // is not portable on Windows and can fail with ERROR_ACCESS_DENIED. |
| 878 | + Ok(()) |
| 879 | +} |
| 880 | + |
876 | 881 | fn sync_directory(path: &Path) -> Result<()> { |
877 | 882 | #[cfg(unix)] |
878 | 883 | { |
@@ -904,6 +909,19 @@ fn transaction_id() -> String { |
904 | 909 | format!("{}-{nanos}", std::process::id()) |
905 | 910 | } |
906 | 911 |
|
| 912 | +fn transaction_file_component(transaction_id: &str) -> String { |
| 913 | + transaction_id |
| 914 | + .bytes() |
| 915 | + .map(|byte| { |
| 916 | + if byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_') { |
| 917 | + (byte as char).to_string() |
| 918 | + } else { |
| 919 | + format!("_{byte:02x}") |
| 920 | + } |
| 921 | + }) |
| 922 | + .collect() |
| 923 | +} |
| 924 | + |
907 | 925 | fn config_error(message: impl Into<String>) -> TraceDecayError { |
908 | 926 | TraceDecayError::Config { |
909 | 927 | message: message.into(), |
|
0 commit comments