OpenListTeam / OpenListTeam/OpenList

[BUG] 跨文件系统删除文件时出现 "invalid cross-device link" 错误

Open
#1,856 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Go
Stars
24.7k
Forks
2.3k
Avg merge
1d 20h
Merged PRs (30d)
36

Description

请确认以下事项
  • 我已确认阅读并同意 AGPL-3.0 第15条
    本程序不提供任何明示或暗示的担保,使用风险由您自行承担。

  • 我已确认阅读并同意 AGPL-3.0 第16条
    无论何种情况,版权持有人或其他分发者均不对使用本程序所造成的任何损失承担责任。

  • 我确认我的描述清晰,语法礼貌,能帮助开发者快速定位问题,并符合社区规则。

  • 我已确认阅读了OpenList文档

  • 我已确认没有重复的问题或讨论。

  • 我已确认是OpenList的问题,而不是其他原因(例如 网络依赖操作)。

  • 我认为此问题必须由OpenList处理,而非第三方。

  • 我已确认这个问题在最新版本中没有被修复。

OpenList 版本(必填)

v4.1.8

使用的存储驱动(必填)

本机存储

问题描述(必填)

当 Openlist 运行在 Docker 容器中,且源文件和回收站路径位于不同文件系统时,删除文件会失败,报错:invalid cross-device link

Rename /extra/DiskGenius.exe /opt/openlist/data/.clash/DiskGenius.exe: invalid cross-device link

/extra 是 xfs 文件系统,/opt/openlist/data/.clash是 ext4 文件系统。

分析

程序尝试使用 rename() 系统调用来移动文件时,源路径和目标路径位于不同的文件系统上。

Linux 系统中 rename() 系统调用只能在同一文件系统内工作。当需要跨文件系统移动文件时,应该使用复制+删除的方式而不是重命名操作。

核心应该是跨文件系统的问题,与是否部署到 docker 无关。


[!NOTE]

在 Alist 项目中找到了相关的 【 PR

https://github.com/AlistGo/alist/pull/7430/files

Alist 有一个历史 Bug,当Alist使用移动,将一个文件移动到不同的文件系统挂载的路径时,同样报错 invalid cross-device link

这在 AlistGo/alist#7430 中得到修复

大致的逻辑为:

  • 检测到 invalid cross-device link 错误时
  • 改用文件复制(copy)然后删除(remove)的方式
  • 而不是直接使用重命名(rename)操作

可能需要修改 drivers/local/driver.go#L359 (未实验)

func (d *Local) Remove(ctx context.Context, obj model.Obj) error {
	var err error
	if utils.SliceContains([]string{"", "delete permanently"}, d.RecycleBinPath) {
		if obj.IsDir() {
			err = os.RemoveAll(obj.GetPath())
		} else {
			err = os.Remove(obj.GetPath())
		}
	} else {
		objPath := obj.GetPath()
		objName := obj.GetName()
		var relPath string
		relPath, err = filepath.Rel(d.GetRootPath(), filepath.Dir(objPath))
		if err != nil {
			return err
		}
		recycleBinPath := filepath.Join(d.RecycleBinPath, relPath)
		if !utils.Exists(recycleBinPath) {
			err = os.MkdirAll(recycleBinPath, 0o755)
			if err != nil {
				return err
			}
		}

		dstPath := filepath.Join(recycleBinPath, objName)
		if utils.Exists(dstPath) {
			dstPath = filepath.Join(recycleBinPath, objName+"_"+time.Now().Format("20060102150405"))
		}
		err = os.Rename(objPath, dstPath)
	}
	if err != nil {
		return err
	}
	if obj.IsDir() {
		if d.directoryMap.Has(obj.GetPath()) {
			d.directoryMap.DeleteDirNode(obj.GetPath())
			d.directoryMap.UpdateDirSize(filepath.Dir(obj.GetPath()))
			d.directoryMap.UpdateDirParents(filepath.Dir(obj.GetPath()))
		}
	} else {
		if d.directoryMap.Has(filepath.Dir(obj.GetPath())) {
			d.directoryMap.UpdateDirSize(filepath.Dir(obj.GetPath()))
			d.directoryMap.UpdateDirParents(filepath.Dir(obj.GetPath()))
		}
	}

	return nil
}
日志(必填)
[GIN] 2025/12/23 - 23:10:14 | 200 |     364.736µs |   89.185.*.131 | POST     "/api/fs/dirs"
[GIN] 2025/12/23 - 23:10:17 | 200 |     429.903µs |   89.185.*.131 | POST     "/api/fs/move"
2025/12/23 23:10:17 INFO worker execute task worker=0 task=APk_WQdzbU2LeZNtG7AKi
ERRO[2025-12-23 23:10:17] failed remove /DiskGenius.exe: rename /extra/DiskGenius.exe /opt/openlist/data/.clash/DiskGenius.exe: invalid cross-device link
github.com/OpenListTeam/OpenList/v4/internal/op.Remove
	/home/runner/work/OpenList/OpenList/internal/op/fs.go:498
github.com/OpenListTeam/OpenList/v4/internal/task_group.verifyAndRemove
	/home/runner/work/OpenList/OpenList/internal/task_group/transfer.go:89
github.com/OpenListTeam/OpenList/v4/internal/task_group.RefreshAndRemove
	/home/runner/work/OpenList/OpenList/internal/task_group/transfer.go:68
github.com/OpenListTeam/OpenList/v4/internal/task_group.(*TaskGroupCoordinator).Done
	/home/runner/work/OpenList/OpenList/internal/task_group/group.go:74
github.com/OpenListTeam/OpenList/v4/internal/fs.(*FileTransferTask).OnSucceeded
	/home/runner/work/OpenList/OpenList/internal/fs/copy_move.go:83
github.com/OpenListTeam/tache.Worker[...].Execute.func2
	/home/runner/go/pkg/mod/github.com/!open!list!team/tache@v0.2.1/worker.go:60
github.com/OpenListTeam/tache.Worker[...].Execute
	/home/runner/go/pkg/mod/github.com/!open!list!team/tache@v0.2.1/worker.go:63
github.com/OpenListTeam/tache.(*Manager[...]).next.func1
	/home/runner/go/pkg/mod/github.com/!open!list!team/tache@v0.2.1/manager.go:130
runtime.goexit
	/opt/hostedtoolcache/go/1.25.0/x64/src/runtime/asm_amd64.s:1693 
配置文件内容(必填)
    "copy": {
      "workers": 5,
      "max_retry": 2,
      "task_persistant": false
    },
    "move": {
      "workers": 5,
      "max_retry": 2,
      "task_persistant": false
    },
Image
复现链接(可选)

No response

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with drivers/local/driver.go around Local.Remove, then inspect internal/op/fs.go:498 and the transfer call path shown in the logs. Reproduce deletion with the source and recycle-bin paths on different filesystems. Done means local-storage deletion succeeds across filesystems without the invalid cross-device link error and the source is removed.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
backend, operating-systems
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.