Golang os.Rename invalid cross-device link的原因
文章目录
- 背景
- 运行环境
- 文件系统对比
- linux下的文件系统
- mac下的文件系统
- linux下的mv指令
- golang的os.Rename源码
- os.Rename
- syscall.Rename
- syscall.Renameat
- SYS_RENAMEAT是什么
- 查看系统调用函数文档
- 什么是man page
- man page的用法
- user commands
- system calls
- renameat不支持跨挂载点调用
- strace确定程序调用了renameat
- 怎么避免错误
- 总结
背景
在执行go程序的时候,其中有一步是把/tmp目录下的一个文件移动到用户目录下,使用go的os.Rename函数来实现。经测试在mac上是可以正常跑的,但是在linux机器上却报错了。报错如下:
go run ./script/download-go go1.22.3
download from https://go.dev/dl/go1.22.3.linux-amd64.tar.gz% Total % Received % Xferd Average Speed Time Time Time CurrentDload Upload Total Spent Left Speed
100 75 100 75 0 0 53 0 0:00:01 0:00:01 --:--:-- 53
100 65.7M 100 65.7M 0 0 160k 0 0:07:00 0:07:00 --:--:-- 419k
rename /tmp/go2503368948/go go-release/go1.22.3: invalid cross-device link
exit status 1
错误信息 invalid cross-device link 表明 /tmp 目录和 go-release 目录是在不同的文件系统中。也就是说不能把一个文件从一个文件系统移动(或重命名)到另一个文件系统。
os.Rename算是比较常用的文件操作函数,博主一直把os.Rename当作mv在使用,也一直没有遇到过这个问题,还是挺奇怪的,值得探索一下出错的原因。
运行环境
操作系统: linux_x86_64
CPU架构: amd_64
Golang: 1.22.2
文件系统对比
linux下的文件系统
dfFilesystem Size Used Avail Use% Mounted on
dev 7.6G 0 7.6G 0% /dev
run 7.7G 3.2M 7.7G 1% /run
/dev/nvme0n1p2 916G 299G 571G 35% /
tmpfs 7.7G 50M 7.6G 1% /dev/shm
tmpfs 7.7G 24K 7.7G 1% /tmp
mac下的文件系统
df
Filesystem 512-blocks Used Available Capacity iused ifree %iused Mounted on
/dev/disk3s3s1 965595304 28934208 217744920 12% 387452 1088724600 0% /
devfs 400 400 0 100% 692 0 100% /dev
/dev/disk3s6 965595304 40 217744920 1% 0 1088724600 0% /System/Volumes/VM
/dev/disk3s4 965595304 22879080 217744920 10% 1372 1088724600 0% /System/Volumes/Preboot
可以看到mac系统上,/根目录都对应同一个挂载点和同一个文件系统。而在我的linux开发机上/tmp是另一个挂载点和文件系统,根据报错信息来看,也是符合预期的。
linux下的mv指令
测试从/tmp目录移动文件到用户目录下,结果是可行的,并没有报错。
golang的os.Rename源码
直接去源码里面找代码看下,源码目录为GOROOT目录下面的src目录。因为没有配置vim环境,所以只能通过rg的方式来找了。
os.Rename
rg "Rename"// file_unix.go
return syscall.Rename(oldname, newname)
syscall.Rename
// cd syscall
// rg "Raname"// syscall_linux.go文件unc Rename(oldpath string, newpath string) (err error) {return Renameat(_AT_FDCWD, oldpath, _AT_FDCWD, newpath)
syscall.Renameat
// zsyscall_linux_amd64.go 文件func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) (err error) {var _p0 *byte_p0, err = BytePtrFromString(oldpath)if err != nil {return}var _p1 *byte_p1, err = BytePtrFromString(newpath)if err != nil {return}// 调用的是SYS_RENAMEAT这个系统调用_, _, e1 := Syscall6(SYS_RENAMEAT, uintptr(olddirfd), uintptr(unsafe.Pointer(_p0)), uintptr(newdirfd), uintptr(unsafe.Pointer(_p1)), 0, 0)if e1 != 0 {err = errnoErr(e1)}return}// zsysnum_linux_amd64.go// 对应的系统调用编号是264SYS_RENAMEAT = 264
SYS_RENAMEAT是什么
对于linux操作系统,每个系统调用在系统调用表中都有一个唯一的编号。这个编号就是系统调用的标识,当用户的程序想要进行系统调用时,会使用这个编号对系统调用进行引用。用户的程序只能通过这个编号与系统调用交互,及进行相关的读、写、打开文件或者申请内存等操作。
而这里的SYS_RENAMEAT对应的就是系统调用的编号,可以搜索: **Linux System Call Table **来查看不同CPU架构对应的系统调用编号。
参考:https://www.chromium.org/chromium-os/developer-library/reference/linux-constants/syscalls/
x86_64
查看系统调用函数文档
man page文档
什么是man page
简单来说就是linux系统的API文档介绍,主要介绍系统提供的命令含义及用法。但是系统文档相对来说还是比较长的,因此著名的开源项目TLDR(https://github.com/tldr-pages/tldr) 也是由此而来,旨在简化man page带来的长文本负担。
TL;DR 代表“太长;没有读”。它起源于互联网俚语,用于表示长文本(或其中的一部分)因太长而被跳过。
man page的用法
用法介绍网上一大堆,这里只聚焦我们的问题,怎么查看系统调用函数的介绍。首先man page对这些系统函数是做了分区的,如下:
user commands
就是常用的命令行函数都在这里,例如ls。左上角的LS(1)就代表在分区1
// man 1 ls
LS(1) User Commands LS(1)NAMEls - list directory contentsSYNOPSISls [OPTION]... [FILE]...DESCRIPTIONList information about the FILEs (the current directory by default). Sort entries alphabetically if none of -cftuvSUX nor --sort is specified.Mandatory arguments to long options are mandatory for short options too.-a, --alldo not ignore entries starting with .-A, --almost-alldo not list implied . and ..
system calls
系统调用函数,这里拿renameat举例子,左上角的rename(2)代表的就是系统调用函数。
man 2 renamertrename(2) System Calls Manual rename(2)NAMErename, renameat, renameat2 - change the name or location of a fileLIBRARYStandard C library (libc, -lc)SYNOPSIS#include <stdio.h>int rename(const char *oldpath, const char *newpath);#include <fcntl.h> /* Definition of AT_* constants */#include <stdio.h>int renameat(int olddirfd, const char *oldpath,int newdirfd, const char *newpath);int renameat2(int olddirfd, const char *oldpath,int newdirfd, const char *newpath, unsigned int flags);Feature Test Macro Requirements for glibc (see feature_test_macros(7)):renameat():Since glibc 2.10:_POSIX_C_SOURCE >= 200809LBefore glibc 2.10:_ATFILE_SOURCErenameat2():_GNU_SOURCE
renameat不支持跨挂载点调用
EXDEV oldpath and newpath are not on the same mounted filesystem. (Linux permits a filesystem to be mounted at multiple points, but rename() does notwork across different mount points, even if the same filesystem is mounted on both.)
这里已经明确说了,不允许跨挂载点的调用,哪怕是同一个文件系统也不行。
strace确定程序调用了renameat
// 只显示 renameat 的调用情况
strace -f -e trace=renameat go run ./script/download-go go1.22.3// 结果
[pid 92270] renameat(AT_FDCWD, "/tmp/go734096947/go", AT_FDCWD,
"go-release/go1.22.3") = -1 EXDEV (Invalid cross-device link)
结合上面的系统调用函数分析,已经明确了根因。
怎么避免错误
参考:
golang社区关于os.Rename的讨论
开源社区的方案
csdn上的避免方案
目前os包没有直接提供类似于mv的函数,常规解决方案就是先copy,再rename,这样就能避免跨挂载点工作导致的错误。
总结
本来只是个小问题,知道报错的含义之后,很容易就会想到避免错误的方案。但是寻根问底也是工程师的天性,知其然更要知其所以然。
哀吾生之须臾,羡知识之无穷。
end
相关文章:

Golang os.Rename invalid cross-device link的原因
文章目录 背景运行环境 文件系统对比linux下的文件系统mac下的文件系统linux下的mv指令 golang的os.Rename源码os.Renamesyscall.Renamesyscall.RenameatSYS_RENAMEAT是什么 查看系统调用函数文档什么是man pageman page的用法user commandssystem calls renameat不支持跨挂载点…...
Flutter 中的 Badge 小部件:全面指南
Flutter 中的 Badge 小部件:全面指南 在移动应用设计中,徽章(Badge)是一种常见的UI元素,用于吸引用户注意并展示重要信息,如未读消息数量、新通知等。Flutter 通过各种第三方包提供了徽章小部件࿰…...

Java 多线程抢红包
问题需求 一个人在群里发了1个100元的红包,被分成了8个,群里有10个人一起来抢红包,有抢到的金额随机分配。 红包功能需要满足哪些具体规则呢? 1、被分的人数抢到的金额之和要等于红包金额,不能多也不能少。 2、每个人至少抢到1元…...

【PB案例学习笔记】-08 控件拖动实现
写在前面 这是PB案例学习笔记系列文章的第8篇,该系列文章适合具有一定PB基础的读者。 通过一个个由浅入深的编程实战案例学习,提高编程技巧,以保证小伙伴们能应付公司的各种开发需求。 文章中设计到的源码,小凡都上传到了gitee…...
读书笔记整理
1.对天才而言,任何努力都如做游戏般容易和有趣,兴趣是发展的原动力。从这个角度来看,通过普通人的劳动是无法创造天才的。 2.让孩子理解语法是很困难的。苦背不如练才是行之有效的办法。孩子们永远是故事迷,在教孩子们外语时&…...
uniapp蓝牙打印图片
前言 这是个蓝牙打印图片的功能,业务是打印界面固定的demo范围,这里通过html2canvas插件生成的图片base64,然后图片base64绘制到canvas中去后,获取canvas中的像素信息,然后对像素信息进行一个灰度值处理,灰…...

Ajax用法总结(包括原生Ajax、Jquery、Axois)
HTTP知识 HTTP(hypertext transport protocol)协议『超文本传输协议』,协议详细规定了浏览器和万维网服务器之间互相通信的规则。 请求报文 请求行: GET、POST /s?ieutf-8...(url的一长串参数) HTTP/1.1 请求头…...
LeetCode 题解:112. 路径总和,递归,JavaScript,详细注释
原题链接: 112. 路径总和 解题思路: 如果求根节点到叶子节点的路径上的节点值之和,假设共有3个节点,那么写成计算式是val1 val2 val3 sum那么将计算式转换就可以得到val3 sum - val1 - val2也就是说,问题可以从…...
Spring (15)Spring Boot的自动配置是如何工作的
Spring Boot的自动配置是通过条件注解(如ConditionalOnClass,ConditionalOnBean等)和EnableAutoConfiguration注解来实现的。这一机制主要是为了简化配置过程,让开发者在满足特定条件时自动配置Spring应用。接下来,我们…...

【机器学习】—机器学习和NLP预训练模型探索之旅
目录 一.预训练模型的基本概念 1.BERT模型 2 .GPT模型 二、预训练模型的应用 1.文本分类 使用BERT进行文本分类 2. 问答系统 使用BERT进行问答 三、预训练模型的优化 1.模型压缩 1.1 剪枝 权重剪枝 2.模型量化 2.1 定点量化 使用PyTorch进行定点量化 3. 知识蒸馏…...

54. UE5 RPG 增加伤害类型
在正常的RPG游戏中,都存在一个类别就是属性伤害,比如,在一个游戏里面有一个火属性的技能,它造成的伤害就是火属性类型的,并且它还有可能有附加伤害,比如给予目标一个灼烧效果,每秒造成多少的火属…...

llama3 微调教程之 llama factory 的 安装部署与模型微调过程,模型量化和gguf转换。
本文记录了从环境部署到微调模型、效果测试的全过程,以及遇到几个常见问题的解决办法,亲测可用(The installed version of bitsandbytes was compiled without GPU support. NotImplementedError: Architecture ‘LlamaForCausalLM’ not sup…...

C++三剑客之std::any(二) : 源码剖析
目录 1.引言 2.std::any的存储分析 3._Any_big_RTTI与_Any_small_RTTI 4.std::any的构造函数 4.1.从std::any构造 4.2.可变参数模板构造函数 4.3.赋值构造与emplace函数 5.reset函数 6._Cast函数 7.make_any模版函数 8.std::any_cast函数 9.总结 1.引言 C三剑客之s…...
【C语言】8.C语言操作符详解(2)
文章目录 6.单⽬操作符7.逗号表达式8.下标访问[]、函数调⽤()8.1 [ ] 下标引⽤操作符8.2 函数调⽤操作符 9.结构成员访问操作符9.1 结构体9.1.1 结构的声明9.1.2 结构体变量的定义和初始化 9.2 结构成员访问操作符9.2.1 结构体成员的直接访问9.2.2 结构体成员的间接访问 6.单⽬…...
vivado 物理约束KEEP_HIERARCHY
KEEP_HIERARCHY Applied To Cells Constraint Values • TRUE • FALSE • YES • NO UCF Example INST u1 KEEP_HIERARCHY TRUE; XDC Example set_property DONT_TOUCH true [get_cells u1] IOB Applied To Cells Constraint Values IOB_XnYn UCF Examp…...
Linux 三十六章
🐶博主主页:ᰔᩚ. 一怀明月ꦿ ❤️🔥专栏系列:线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C,linux 🔥座右铭:“不要…...
ntsd用法+安装包
ntsd是一个强大的进程终止软件,除了少数系统进程之外一律杀掉 用法 1.ntsd -c q -p 进程的pid 2.ntsd -c q -pn 进程名 记得解压到System32里面 当然,资源管理器的进程是可以杀的所以也可以让电脑黑屏 同样可以让电脑黑屏的还有taskkill /f /im 进程…...

Nacos 微服务管理
Nacos 本教程将为您提供Nacos的基本介绍,并带您完成Nacos的安装、服务注册与发现、配置管理等功能。在这个过程中,您将学到如何使用Nacos进行微服务管理。下方是官方文档: Nacos官方文档 1. Nacos 简介 Nacos(Naming and Confi…...

Kubernetes集群上的Etcd备份和恢复
在本教程中,您将学习如何在Kubernetes集群上使用etcd快照进行etcd备份和恢复。 在Kubernetes架构中,etcd是集群的重要组成部分。所有集群对象及其状态都存储在etcd中。为了更好地理解Kubernetes,有几点关于etcd的信息是您需要了解的。 它是…...
创建型模式 (Python版)
单例模式 懒汉式 class SingleTon:# 类属性_obj None # 用来存储对象# 创造对象def __new__(cls, *args, **kwargs):# 如果对象不存在,就创造一个对象if cls._obj is None:cls._obj super().__new__(cls, *args, *kwargs)# 返回对象return cls._objif __name__…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
5月28日,中天合创屋面分布式光伏发电项目顺利并网发电,该项目位于内蒙古自治区鄂尔多斯市乌审旗,项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站,总装机容量为9.96MWp。 项目投运后,每年可节约标煤3670…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...

DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...

EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...
书籍“之“字形打印矩阵(8)0609
题目 给定一个矩阵matrix,按照"之"字形的方式打印这个矩阵,例如: 1 2 3 4 5 6 7 8 9 10 11 12 ”之“字形打印的结果为:1,…...

yaml读取写入常见错误 (‘cannot represent an object‘, 117)
错误一:yaml.representer.RepresenterError: (‘cannot represent an object’, 117) 出现这个问题一直没找到原因,后面把yaml.safe_dump直接替换成yaml.dump,确实能保存,但出现乱码: 放弃yaml.dump,又切…...

Vue3 PC端 UI组件库我更推荐Naive UI
一、Vue3生态现状与UI库选择的重要性 随着Vue3的稳定发布和Composition API的广泛采用,前端开发者面临着UI组件库的重新选择。一个好的UI库不仅能提升开发效率,还能确保项目的长期可维护性。本文将对比三大主流Vue3 UI库(Naive UI、Element …...