summaryrefslogtreecommitdiff
path: root/src/Agent.Windows/Recovery/TransactionalFileEditor.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Agent.Windows/Recovery/TransactionalFileEditor.cs')
-rw-r--r--src/Agent.Windows/Recovery/TransactionalFileEditor.cs41
1 files changed, 41 insertions, 0 deletions
diff --git a/src/Agent.Windows/Recovery/TransactionalFileEditor.cs b/src/Agent.Windows/Recovery/TransactionalFileEditor.cs
index 805a069..25c949d 100644
--- a/src/Agent.Windows/Recovery/TransactionalFileEditor.cs
+++ b/src/Agent.Windows/Recovery/TransactionalFileEditor.cs
@@ -93,6 +93,26 @@ public sealed class TransactionalFileEditor(TaskResourceJournal journal, string
return new(journal, entryId, canonical);
}
+ public FileRenameTransaction Rename(string sourcePath, string destinationPath)
+ {
+ string source = CanonicalPath.Resolve(sourcePath);
+ string destination = CanonicalPath.Resolve(destinationPath, requireExisting: false);
+ if (!File.Exists(source)) throw new FileNotFoundException($"Source file does not exist: '{source}'.");
+ if (File.Exists(destination)) throw new ToolValidationException($"Destination already exists: '{destination}'. Rename overwrite is not allowed.");
+ if (!source.StartsWith(System.IO.Path.GetPathRoot(source)!, StringComparison.OrdinalIgnoreCase) ||
+ !destination.StartsWith(System.IO.Path.GetPathRoot(source)!, StringComparison.OrdinalIgnoreCase))
+ throw new ToolValidationException("Cross-volume rename is not supported.");
+ byte[] content = File.ReadAllBytes(source);
+ string sourceHash = Convert.ToHexString(SHA256.HashData(content));
+ Guid entryId = journal.Prepare("file.rename", source, new Dictionary<string, string>
+ {
+ ["destination_path"] = destination, ["source_sha256"] = sourceHash
+ });
+ File.Move(source, destination);
+ journal.MarkApplied(entryId);
+ return new(journal, entryId, source, destination, sourceHash);
+ }
+
private static string NormalizeHash(string hash) => hash.Replace("-", string.Empty, StringComparison.Ordinal).Trim().ToUpperInvariant();
}
@@ -144,6 +164,27 @@ public sealed class DirectoryRmdirTransaction(TaskResourceJournal journal, Guid
}
}
+public sealed class FileRenameTransaction(TaskResourceJournal journal, Guid entryId, string sourcePath, string destinationPath, string sourceHash)
+{
+ private bool _finished;
+ public void Commit() { if (_finished) return; journal.MarkCleaned(entryId); _finished = true; }
+ public void Rollback()
+ {
+ if (_finished) return;
+ if (!File.Exists(destinationPath)) throw new InvalidOperationException("Rollback refused because the destination file is missing.");
+ if (!Hash(destinationPath).Equals(sourceHash, StringComparison.Ordinal))
+ throw new InvalidDataException("Rollback refused because the destination file hash does not match the original source hash.");
+ File.Move(destinationPath, sourcePath);
+ journal.MarkCleaned(entryId);
+ _finished = true;
+ }
+ private static string Hash(string target)
+ {
+ using FileStream stream = File.OpenRead(target);
+ return Convert.ToHexString(SHA256.HashData(stream));
+ }
+}
+
public sealed class FileEditTransaction(
TaskResourceJournal journal, Guid entryId, string path, string originalHash, string replacementHash,
string backupPath, string originalBase64, bool originallyExisted)