






















@@ -204,6 +204,82 @@ describe("sqlite WAL maintenance", () => {
204204}
205205});
206206207+it("refuses fuse.sshfs mountinfo entries", () => {
208+const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-sqlite-sshfs-"));
209+try {
210+const db = createMockDb();
211+vi.spyOn(fs, "statfsSync").mockReturnValue(statfsFixture(0));
212+vi.spyOn(fs, "readFileSync").mockReturnValue(
213+`42 12 0:41 / ${tempDir} rw,relatime - fuse.sshfs user@host:/share rw\n`,
214+);
215+216+expect(() =>
217+configureSqliteWalMaintenance(db, {
218+checkpointIntervalMs: 0,
219+databaseLabel: "test-db",
220+databasePath: path.join(tempDir, "openclaw.sqlite"),
221+}),
222+).toThrow(/test-db .*SSHFS.*refusing to open/);
223+224+expect(db["prepare"]).not.toHaveBeenCalled();
225+expect(db["exec"]).not.toHaveBeenCalled();
226+} finally {
227+fs.rmSync(tempDir, { recursive: true, force: true });
228+}
229+});
230+231+it("refuses symlinked paths into fuse.sshfs mounts", () => {
232+if (process.platform === "win32") {
233+return;
234+}
235+const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-sqlite-sshfs-link-"));
236+const mountDir = path.join(tempDir, "mount");
237+const linkedDir = path.join(tempDir, "linked");
238+try {
239+fs.mkdirSync(mountDir);
240+fs.symlinkSync(mountDir, linkedDir);
241+vi.spyOn(fs, "statfsSync").mockReturnValue(statfsFixture(0));
242+vi.spyOn(fs, "readFileSync").mockReturnValue(
243+`42 12 0:41 / ${mountDir} rw,relatime - fuse.sshfs user@host:/share rw\n`,
244+);
245+246+expect(() =>
247+configureSqliteWalMaintenance(createMockDb(), {
248+checkpointIntervalMs: 0,
249+databasePath: path.join(linkedDir, "openclaw.sqlite"),
250+}),
251+).toThrow(/SSHFS.*refusing to open/);
252+} finally {
253+fs.rmSync(tempDir, { recursive: true, force: true });
254+}
255+});
256+257+it("matches raw mount paths when the existing path canonicalizes elsewhere", () => {
258+if (process.platform === "win32") {
259+return;
260+}
261+const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-sqlite-sshfs-prefix-"));
262+const canonicalMountDir = path.join(tempDir, "canonical-mount");
263+const rawMountDir = path.join(tempDir, "raw-mount");
264+try {
265+fs.mkdirSync(canonicalMountDir);
266+fs.symlinkSync(canonicalMountDir, rawMountDir);
267+vi.spyOn(fs, "statfsSync").mockReturnValue(statfsFixture(0));
268+vi.spyOn(fs, "readFileSync").mockReturnValue(
269+`42 12 0:41 / ${rawMountDir} rw,relatime - fuse.sshfs user@host:/share rw\n`,
270+);
271+272+expect(() =>
273+configureSqliteWalMaintenance(createMockDb(), {
274+checkpointIntervalMs: 0,
275+databasePath: path.join(rawMountDir, "openclaw.sqlite"),
276+}),
277+).toThrow(/SSHFS.*refusing to open/);
278+} finally {
279+fs.rmSync(tempDir, { recursive: true, force: true });
280+}
281+});
282+207283it("uses mount command filesystem names on platforms without proc mountinfo", () => {
208284const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-sqlite-nfs-"));
209285try {
@@ -250,6 +326,60 @@ describe("sqlite WAL maintenance", () => {
250326}
251327});
252328329+it.each([
330+["macfuse", "sshfs#user@host:/share"],
331+["macfuse", "host:/share"],
332+["macfuse", "user@host:"],
333+["osxfuse", "user@host:/share"],
334+["osxfuse", "sshfs@osxfuse0"],
335+])("refuses SSHFS reported as %s by mount", (fsType, source) => {
336+const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-sqlite-sshfs-macfuse-"));
337+try {
338+const db = createMockDb();
339+vi.spyOn(fs, "statfsSync").mockReturnValue(statfsFixture(0));
340+vi.spyOn(fs, "readFileSync").mockImplementation(() => {
341+throw new Error("no proc mountinfo");
342+});
343+vi.spyOn(childProcess, "execFileSync").mockReturnValue(
344+Buffer.from(`${source} on ${tempDir} (${fsType}, nodev, nosuid)\n`),
345+);
346+347+expect(() =>
348+configureSqliteWalMaintenance(db, {
349+checkpointIntervalMs: 0,
350+databasePath: path.join(tempDir, "openclaw.sqlite"),
351+}),
352+).toThrow(/refusing to open/);
353+354+expect(db["exec"]).not.toHaveBeenCalled();
355+} finally {
356+fs.rmSync(tempDir, { recursive: true, force: true });
357+}
358+});
359+360+it("keeps WAL enabled for non-remote macFUSE mounts", () => {
361+const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-sqlite-macfuse-"));
362+try {
363+const db = createMockDb();
364+vi.spyOn(fs, "statfsSync").mockReturnValue(statfsFixture(0));
365+vi.spyOn(fs, "readFileSync").mockImplementation(() => {
366+throw new Error("no proc mountinfo");
367+});
368+vi.spyOn(childProcess, "execFileSync").mockReturnValue(
369+Buffer.from(`remote-volume on ${tempDir} (macfuse, nodev, nosuid)\n`),
370+);
371+372+configureSqliteWalMaintenance(db, {
373+checkpointIntervalMs: 0,
374+databasePath: path.join(tempDir, "openclaw.sqlite"),
375+});
376+377+expect(db["exec"]).toHaveBeenNthCalledWith(1, "PRAGMA journal_mode = WAL;");
378+} finally {
379+fs.rmSync(tempDir, { recursive: true, force: true });
380+}
381+});
382+253383it("parses Linux mount command filesystem names when proc mountinfo is unavailable", () => {
254384const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-sqlite-nfs-"));
255385try {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。