我想在Golang中运行clonefile→尝试发出系统调用
首先,
去年年底(2018年末),在我的工作场所也有人谈论Mac的cp命令的选项。
最近的macOS据说可以在瞬间复制文件。
在APFS(Apple文件系统)中,如果在cp命令中指定-c选项,它将使用clonefile系统调用,因此可以在瞬间内复制几GB级别的文件。
然后,我想要用Golang编写一个使用clonefile函数进行文件复制的工具。
特别是有经验的人可以直接访问链接:https://github.com/n-someya/goclonefile。
在Golang中用于发出系统调用的软件包
在Golang中,系统调用发行的包有两个存在着历史原因。
syscall包是Golang的标准包,提供了用于发行系统调用的功能。
然而,由于一系列问题,如对不同操作系统的特定支持等,目前功能的增加已经转移到了golang.org/x/sys包中。
建议查看 golang.org/x/sys/unix 包,因为 clonefile 系统调用是 macOS 的特有功能(macOS 是基于 Unix 的)。
在Unix系统中,每个系统调用都被分配了一个特定的编号。指定了每个系统调用被分配的编号的文件是zsysnum_darwin_amd64.go。
const (
//中略
SYS_CLONEFILEAT = 462
//中略
)
在其中还记载了clonefile系统调用的号码。
这听起来很有希望。
使用上述的常量,实际上发起了系统调用的文件为zsyscall_darwin_amd64.1_11.go。
然而,无论在哪里都找不到使用SYS_CLONEFILEAT的部分…
看起来没有实施…
用Go语言实现clonefile系统调用的发出处理
因为没有办法,只能自己来实现。
根据Apple的说法,clonefile系统调用(准确地说是clonefileat系统调用)接受5个参数。
就Unix包中的系统调用发起函数而言,有三个函数:Syscall()接受三个参数,Syscall6()接受六个参数,Syscall9()接受九个参数。因此,我们可以将第六个参数设为0,调用Syscall6()应该是一个好的选择。
最终结果代码如下。
var (
// Flags
CLONE_NOFOLLOW CLONEFILE_FLAG = 0x0001 /* Don't follow symbolic links */
CLONE_NOOWNERCOPY CLONEFILE_FLAG = 0x0002 /* Don't copy ownership information from source */
AT_FDCWD = -2
)
// Clonefile clonefile is fast copy method for darwin(MacOS)
func Clonefile(src, dst string) (err error) {
if runtime.GOOS != OS_DARWIN {
return fmt.Errorf("Clonefile is implemented for macOS")
}
var _p0, _p1 *byte
_p0, err = unix.BytePtrFromString(src)
if err != nil {
return
}
_p1, err = unix.BytePtrFromString(dst)
if err != nil {
return
}
_, _, e1 := unix.Syscall6(unix.SYS_CLONEFILEAT, uintptr(AT_FDCWD), uintptr(unsafe.Pointer(_p0)), uintptr(AT_FDCWD), uintptr(unsafe.Pointer(_p1)), uintptr(CLONE_NOFOLLOW), 0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}
各种不同的补充说明
【额外附加1】
从Go 1.12开始,在macOS上,系统调用的实现似乎发生了变化。
系统调用的函数现在是小写字母+下划线的形式,例如syscall_syscall6,并且系统调用的编号也是通过从libc获取的方式获得。
https://github.com/golang/sys/blob/9fbf701fc3/unix/zsyscall_darwin_arm64.go
(为什么会这样呢)
【附加2】
或许作为规范,可以直接调用C语言函数,而不是直接调用系统调用,就像这位的做法一样:https://github.com/go-darwin/apfs/blob/master/clone.go