Repository navigation
fix(drivers): keep existing files when uploads fail or are canceled (smb, chunk, op) - #3330
Open
thetapilla wants to merge 4 commits into
Open
thetapilla wants to merge 4 commits into
thetapilla wants to merge 4 commits into
Conversation
- On storages with NoOverwriteUpload, Put renames the existing file to <name>.openlist_to_delete before uploading and renames it back if the upload fails. The rename-back used the upload's own context, so when the upload was canceled (client gone, multipart abort, supersede or expiry) it failed too and the original stayed hidden under the temporary name. - Recover (and clean up after success) with a context detached from the upload, bounded to one minute. - Only rename the original back while its name is still free. If another upload took the name meanwhile, keep the newer file and leave the original aside, since some drivers delete same-name "duplicates" after a rename. - Add TestPutRestoresOriginalWhenCanceled and TestPutDoesNotRestoreOverNewerUpload. Co-Authored-By: Claude <noreply@anthropic.com>
- Put wrote the destination in place and removed it by path when its context was canceled. If a later upload of the same name had finished meanwhile (e.g. a multipart session reaped by GC, aborted or superseded), that file was deleted. A failed overwrite also destroyed the previous version, and an idle upload holding the destination open made a later upload of the same name fail. - Write to a uniquely named staging file next to the destination and rename it over the destination once complete (go-smb2 renames with ReplaceIfExists). A failed or canceled upload only removes its own staging file. - On the first upload after Init, probe whether the server can rename over an existing file. Servers that cannot (e.g. Samba with vfs_streams_xattr on a filesystem without xattr support) keep in-place writes, but no longer remove anything on failure. - List hides staging files and reclaims those older than 24 hours. - Add unit tests, and live tests gated by OPENLIST_SMB_INTEGRATION=1. Co-Authored-By: Claude <noreply@anthropic.com>
…lure - Put wrote the parts into the chunk folder in place, and when a part failed or the upload was canceled it removed the whole chunk folder by path. A failed overwrite thus deleted the previous version, and if another upload of the same name had completed meanwhile (e.g. while a multipart session waited to be reaped), that upload was deleted too. Overwriting with a smaller file also left the old file's extra parts behind, which corrupted the new file's size and content. - Upload the parts into a uniquely named staging folder and move it into place once complete. The previous version is only moved aside then, and removed afterwards. A failed or canceled upload removes only its own staging folder. - Remotes without Rename keep in-place writes, but no longer remove the chunk folder on failure. - List hides staging folders and reclaims those older than 24 hours. - Add tests against a Local remote. Co-Authored-By: Claude <noreply@anthropic.com>
- Samba with vfs_streams_xattr on a filesystem without xattr support refuses to overwrite an existing file, but truncates it first. The in-place fallback therefore still destroyed the previous version on a failed overwrite. - When renaming over a file fails, also probe whether the server can overwrite one in place. A server that can do neither only gets new files: they are created exclusively, and an overwrite is refused before the existing file is touched. - Probe with fresh staging names: the fixed probe files a server could not remove made later probes fail before they learned anything. - Add TestIntegrationOverwriteNeverDestroysPrevious. Co-Authored-By: Claude <noreply@anthropic.com>
This was referenced Oct 30, 2026
jyxjjj
requested changes
Nov 4, 2026
jyxjjj
left a comment
Member
There was a problem hiding this comment.
[P1] SMB.List 和 Chunk.List 仅根据文件名和修改时间就执行 Remove。用户原本就能创建名为 .openlist-upload-0123456789abcdef 的文件或目录,超过 24 小时后,只要列一次目录就会被删除。Chunk 甚至在判断 IsDir 前就执行清理。请仅回收能确认由本次暂存机制创建的对象,并测试名称完全匹配的既有用户文件。
Chunk.publish 还会把旧版本改成同样格式的备份名。如果发布和恢复改名都失败,恢复错误被忽略,留下的旧版本也会进入上述清理路径。请保留无法恢复的备份并返回其位置。
审核提交 957887e。
Reviewed or refined by GPT-6-Astra High
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary / 摘要
#3178 的根因是:上传被取消时(客户端断开,或分片会话被中止、取代、回收),驱动按路径删除目标文件,而这时这个路径可能已经属于另一次写完的上传。#3305 预期将修复 Local 驱动;本 PR 修复 SMB、Chunk 驱动和
op.Put中的同类问题,以及 SMB 在无法替换文件的服务端上覆盖失败时毁掉原文件的问题。每处一个 commit。用户可感知的变化:
vfs_streams_xattr的 Samba),覆盖上传被拒绝、原文件保留;此前服务端会先把原文件截成 0 字节再报错NoOverwriteUpload的 12 个存储(aliyundrive_open、chaoxing、cloudreve_v4、degoo、dropbox、guangyapan、ilanzou、mediafire、quark_open、quark_uc、quark_uc_tv、wopan):覆盖上传被取消后,原文件不再滞留为<名字>.openlist_to_delete实现要点:
ReplaceIfExists);失败或取消只删除自己的暂存文件,不再按路径删除目标Init后第一次上传时,在该上传的目录里探测服务端能否改名覆盖文件、能否原地覆盖文件。能改名覆盖 → 暂存发布;只能原地覆盖 → 原地写入,失败时不删除任何文件;两样都不行 → 只创建新文件(O_EXCL),覆盖在碰到原文件之前就拒绝,因为这类服务端拒绝覆盖前会先截断文件.openlist-upload-加 16 位随机字母数字,前缀与 fix(drivers/local): isolate canceled multipart uploads #3305 一致。List只隐藏恰好符合该格式的条目,并回收超过 24 小时的残留(进程被杀时留下的);用户自己以该前缀开头的文件照常显示,也不会被回收兼容性说明:不改动驱动接口和设置项。覆盖期间 SMB 和 Chunk 需要同时放得下新旧两个版本;上传进行中,其他 SMB 客户端能看到隐藏的暂存文件。不能删除文件的 SMB 服务端上,每次
Init后的探测会留下两个 0 字节的隐藏文件。本 PR 未覆盖:SFTP、FTP 仍是原地写入(能否改名覆盖取决于服务端);139(旧上传路径)和 Teldrive 在上传前就删除原文件,已在 #3329 中报告。
/ 此 PR 包含破坏性变更。
/ 此 PR 修改了公开 API、配置、存储格式或迁移行为。
/ 此 PR 需要关联仓库同步修改。
Related repository PRs / 关联仓库 PR:
Related Issues / 关联 Issue
Relates to #3178
Testing / 测试
测试平台:macOS arm64,Go 1.27.3;Samba 4.23(servercontainers/samba,分别为正常配置,以及 macOS 绑定挂载 +
vfs_streams_xattr的错误配置);MinIO。go test ./...自动化测试:
go test -race -count=1 ./internal/op/ ./drivers/smb/ ./drivers/chunk/OPENLIST_SMB_INTEGRATION=1,见drivers/smb/integration_test.go):正常配置的 Samba 上 7/7 通过;错误配置的 Samba 上探测为只创建新文件,新文件照常上传,覆盖被拒绝且原文件完好(修复前原文件被截成 0 字节)op.CreateStorage+ 内存数据库)浏览器实测:
the SMB server cannot replace existing files, so the existing file was left as it was,原文件完好Checklist / 检查清单
/ 我已阅读 CONTRIBUTING。
/ 我确认此贡献符合仓库许可证、贡献规范和行为准则。
gofmt,go fmt, orprettierwhere applicable./ 我已按适用情况使用
gofmt、go fmt或prettier格式化变更代码。/ 我已在适用情况下请求相关维护者或代码所有者审查。
AI Disclosure / AI 使用声明
/ 此 PR 包含 AI 辅助内容。
Tools used / 使用工具:
Usage scope / 使用范围:
Code generation / 代码生成
Refactoring / 重构
Documentation / 文档
Tests / 测试
Translation / 翻译
Review assistance / 审查辅助
I have reviewed and validated all AI-assisted content included in this PR.
/ 我已审核并验证此 PR 中的所有 AI 辅助内容。
I have ensured that all AI-assisted commits include
Co-Authored-Byattribution./ 我已确保所有 AI 辅助提交都包含
Co-Authored-By归属信息。I can reproduce all AI-assisted content included in this PR without any AI tools.
/ 我可以在没有任何 AI 工具的情况下重现此 PR 中包含的所有 AI 辅助内容。