mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-12 19:32:57 +00:00
fix(backup): accept root-relative hardlink targets (#89328)
This commit is contained in:
@@ -358,6 +358,39 @@ describe("backupVerifyCommand", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("accepts root-relative internal hardlink targets from older backups", async () => {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-backup-rootless-linkpath-"));
|
||||
const archivePath = path.join(tempDir, "backup.tar.gz");
|
||||
const rootRelativeTargetPath = "payload/posix/tmp/.openclaw/target.txt";
|
||||
const payloadArchivePath = `${TEST_ARCHIVE_ROOT}/${rootRelativeTargetPath}`;
|
||||
const hardlinkArchivePath = `${TEST_ARCHIVE_ROOT}/payload/posix/tmp/.openclaw/hardlink.txt`;
|
||||
try {
|
||||
const archive = gzipSync(
|
||||
Buffer.concat([
|
||||
encodeTarEntry({
|
||||
path: `${TEST_ARCHIVE_ROOT}/manifest.json`,
|
||||
contents: `${JSON.stringify(createBackupManifest(payloadArchivePath), null, 2)}\n`,
|
||||
}),
|
||||
encodeTarEntry({ path: payloadArchivePath, contents: "payload\n" }),
|
||||
encodeTarEntry({
|
||||
path: hardlinkArchivePath,
|
||||
type: "Link",
|
||||
linkpath: rootRelativeTargetPath,
|
||||
}),
|
||||
Buffer.alloc(1024),
|
||||
]),
|
||||
);
|
||||
await fs.writeFile(archivePath, archive);
|
||||
|
||||
const runtime = createBackupVerifyRuntime();
|
||||
await expect(backupVerifyCommand(runtime, { archive: archivePath })).resolves.toMatchObject({
|
||||
ok: true,
|
||||
});
|
||||
} finally {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects hardlink targets missing from archive entries", async () => {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-backup-missing-linkpath-"));
|
||||
const archivePath = path.join(tempDir, "broken.tar.gz");
|
||||
|
||||
@@ -301,14 +301,20 @@ function verifyHardlinkTargetsAgainstArchiveRoot(
|
||||
): void {
|
||||
const normalizedRoot = normalizeArchiveRoot(archiveRoot);
|
||||
for (const target of hardlinkTargets) {
|
||||
if (!isArchivePathWithin(target.normalized, normalizedRoot)) {
|
||||
// Older backup archives may store hardlink linkpath values relative to the
|
||||
// archive root instead of including the root segment. Accept that form only
|
||||
// when it resolves to a real entry inside this archive.
|
||||
const normalizedTarget = isArchivePathWithin(target.normalized, normalizedRoot)
|
||||
? target.normalized
|
||||
: path.posix.join(normalizedRoot, target.normalized);
|
||||
if (!isArchivePathWithin(normalizedTarget, normalizedRoot)) {
|
||||
throw new Error(
|
||||
`Archive hardlink target is outside the declared archive root: ${target.entryPath} -> ${target.normalized}`,
|
||||
`Archive hardlink target is outside the declared archive root: ${target.entryPath} -> ${normalizedTarget}`,
|
||||
);
|
||||
}
|
||||
if (!entries.has(target.normalized)) {
|
||||
if (!entries.has(normalizedTarget)) {
|
||||
throw new Error(
|
||||
`Archive hardlink target is missing from archive entries: ${target.entryPath} -> ${target.normalized}`,
|
||||
`Archive hardlink target is missing from archive entries: ${target.entryPath} -> ${normalizedTarget}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user