From 0d4e2488114af55c5402ce9accb74f4e9da34285 Mon Sep 17 00:00:00 2001 From: cnc Date: Wed, 3 Jun 2026 13:28:15 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=82=E8=80=83=E6=89=80=E6=9C=89=E5=88=86?= =?UTF-8?q?=E7=BB=84=EF=BC=8C=E5=AE=8C=E6=88=90=E5=B0=BD=E9=87=8F=E5=A4=9A?= =?UTF-8?q?=E7=9A=84=E5=86=85=E5=AE=B9=E3=80=82=E7=A6=81=E6=AD=A2=E9=A1=BA?= =?UTF-8?q?=E6=89=8B=E6=89=A9=E5=8A=9F=E8=83=BD=20smoke?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 结论:按 align-linuxcnc 约束收窄 OPFS removeFile 和 LinuxCNC 参数 .new/.bak 清理为文件专用语义,避免把同名目录递归删除。依据 ../linuxcnc/src/emc/rs274ngc/rs274ngc_pre.cc save_parameters() 的 fopen(filename.new)、unlink(filename.bak)、link、rename 行为。 验证:./test-native.sh 通过;./test-linuxcnc-source-link.sh 通过。 --- test-native.sh | 5 ++- web/src/wasm-core.js | 31 ++++++++++++++++--- ...wser-wasm-smoke-opfs-directory-sections.js | 16 ++++++++++ 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/test-native.sh b/test-native.sh index 9ba057a..2d47754 100755 --- a/test-native.sh +++ b/test-native.sh @@ -366,7 +366,10 @@ if grep -R -n -E 'localStorage|sessionStorage|indexedDB|showOpenFilePicker|showS fi grep -F 'LinuxCNC source basis: rs274ngc_pre.cc restore_parameters() reads the' web/src/wasm-core.js >/dev/null grep -F 'before replacing the main file and managing filename + ".bak".' web/src/wasm-core.js >/dev/null -grep -F 'await removeOpfsEntry(workspacePath, `${path}.new`, false);' web/src/wasm-core.js >/dev/null +grep -F 'async function removeOpfsFileEntry(workspacePath, relativePath)' web/src/wasm-core.js >/dev/null +grep -F 'await removeOpfsFileEntry(workspacePath, `${path}.new`);' web/src/wasm-core.js >/dev/null +grep -F 'removeWasmFilePath(module, `${LINUXCNC_DEFAULT_PARAMETER_FILE}.new`);' web/src/wasm-core.js >/dev/null +grep -F 'OPFS removeFile removed a directory instead of preserving file-only semantics' web/test-browser-wasm-smoke-opfs-directory-sections.js >/dev/null grep -F 'OPFS workspace is required for browser program parsing' web/src/app.js >/dev/null grep -F 'const events = await simulator.parseFileWithParameterFile(programPath, parameterPath, "linuxcnc", parseOptions);' web/src/app.js >/dev/null grep -F 'default browser simulator did not create an OPFS workspace' web/test-browser-wasm-smoke-opfs-policy-sections.js >/dev/null diff --git a/web/src/wasm-core.js b/web/src/wasm-core.js index f4828ad..513a692 100644 --- a/web/src/wasm-core.js +++ b/web/src/wasm-core.js @@ -318,6 +318,21 @@ function removeWasmPath(module, path, recursive) { } } +function removeWasmFilePath(module, path) { + let mode; + try { + mode = module.FS.stat(path).mode; + } catch (error) { + if (error?.errno !== 44) { + throw error; + } + return; + } + if (module.FS.isFile(mode)) { + module.FS.unlink(path); + } +} + function wasmPathExists(module, path) { try { module.FS.stat(path); @@ -505,9 +520,12 @@ function installOpfsWorkspace(module, options) { throw new Error("LinuxCNC parameter file was not produced by the interpreter"); } await writeOpfsFile(workspacePath, path, data); - await removeOpfsEntry(workspacePath, `${path}.new`, false); - removeWasmPath(module, `${LINUXCNC_DEFAULT_PARAMETER_FILE}.new`, false); - removeWasmPath(module, `/${LINUXCNC_DEFAULT_PARAMETER_FILE}.new`, false); + // LinuxCNC source basis: rs274ngc_pre.cc save_parameters() + // creates filename + ".new" with fopen() and later renames that + // file; OPFS cleanup must not remove a directory at that path. + await removeOpfsFileEntry(workspacePath, `${path}.new`); + removeWasmFilePath(module, `${LINUXCNC_DEFAULT_PARAMETER_FILE}.new`); + removeWasmFilePath(module, `/${LINUXCNC_DEFAULT_PARAMETER_FILE}.new`); const backupPath = `${LINUXCNC_DEFAULT_PARAMETER_FILE}.bak`; const backupData = readFirstExistingWasmFile(module, [backupPath, `/${backupPath}`]); if (backupData !== null) { @@ -518,8 +536,11 @@ function installOpfsWorkspace(module, options) { return data; }, async removeFile(path) { - await removeOpfsEntry(workspacePath, path, false); - removeWasmPath(module, this.resolvePath(path), false); + // LinuxCNC source basis: rs274ngc_pre.cc save_parameters() uses + // unlink() for backup files, so the browser bridge keeps file-only + // removal separate from recursive directory cleanup. + await removeOpfsFileEntry(workspacePath, path); + removeWasmFilePath(module, this.resolvePath(path)); }, async removeDirectory(path) { await removeOpfsEntry(workspacePath, path, true); diff --git a/web/test-browser-wasm-smoke-opfs-directory-sections.js b/web/test-browser-wasm-smoke-opfs-directory-sections.js index d033321..0755efe 100644 --- a/web/test-browser-wasm-smoke-opfs-directory-sections.js +++ b/web/test-browser-wasm-smoke-opfs-directory-sections.js @@ -171,4 +171,20 @@ export async function runBrowserOpfsDirectorySections(context, simulator) { "OPFS removeDirectory did not remove the WASM mirror", ); }); + + await runSection("opfs file removal preserves directories", async () => { + await simulator.fs.opfs.writeFile("parameters/remove-file-keeps-directory.var.new/stale.ngc", "G21 G90\n"); + const staleDirectoryPath = simulator.fs.opfs.resolvePath("parameters/remove-file-keeps-directory.var.new"); + await simulator.fs.opfs.readDirectory("parameters/remove-file-keeps-directory.var.new"); + await simulator.fs.opfs.removeFile("parameters/remove-file-keeps-directory.var.new"); + const staleDirectoryStat = await simulator.fs.opfs.stat("parameters/remove-file-keeps-directory.var.new"); + if (staleDirectoryStat.kind !== "directory" || staleDirectoryStat.size !== null) { + throw new Error("OPFS removeFile removed a directory instead of preserving file-only semantics"); + } + const staleDirectoryMode = simulator.fs.module.FS.stat(staleDirectoryPath).mode; + if (!simulator.fs.module.FS.isDir(staleDirectoryMode)) { + throw new Error("OPFS removeFile removed a WASM mirror directory instead of preserving file-only semantics"); + } + await simulator.fs.opfs.removeDirectory("parameters/remove-file-keeps-directory.var.new"); + }); }