Golangでもclonefileしたい→システムコール発行してみた

# はじめに

昨年(2018年)末、Macのcpコマンドのオプションが私の職場でも話題となった。
[最近のmacOSでは一瞬でファイルがコピーできるという話](https://qiita.com/hnw/items/ba721a34790d50686563)

それは、APFS (Apple File System)において、cpコマンドで`-c`オプションを指定すると、
clonefileシステムコールを使ってくれるので、数GB級のファイルも一瞬でコピーを行うことができるというものだ。

そして、私はclonefileコールを使ってファイルコピーをするGolangのツールを書きたくなった。

特に経緯とか良い人はソースを直接どうぞ → https://github.com/n-someya/goclonefile

# Golangにおけるシステムコール発行パッケージ

Golangにおける、システムコール発行のパッケージは歴史的経緯から2つ存在する。
[syscallパッケージ](https://godoc.org/syscall)は、Golangの標準パッケージでシステムコールを発行するための機能を提供している。
しかし、OSごとに異なる固有仕様のサポートなど一連の問題[^1] により、現在はsyscallパッケージではなく、
[golang.org/x/sysパッケージ](https://godoc.org/golang.org/x/sys)に、機能追加がされている。

clonefileシステムコールはmacOSの固有機能のため、golang.org/x/sys/unixパッケージをみた方が良い。(macOSはUnixベース)

Unixの場合、システムコールにはそれぞれ番号が割り当たっている。
どのシステムコールにどの番号が振られているかを定数定義しているファイルが、
[zsysnum_darwin_amd64.go](https://github.com/golang/sys/blob/master/unix/zsysnum_darwin_amd64.go)
である。

```golang
const (
//中略
	SYS_CLONEFILEAT                    = 462
//中略
)
```
その中にclonefileのシステムコール番号も記載されている。
これは期待できそうだ。

上記の定数を利用して、実際にシステムコールを発行しているのが、
[zsyscall_darwin_amd64.1_11.go](https://github.com/golang/sys/blob/master/unix/zsyscall_darwin_amd64.1_11.go)
このファイルだ。

しかし、どこを見ても`SYS_CLONEFILEAT`を使っている箇所がない。。。
実装されていないようだ。。。

# Golangによるclonefileシステムコールの発行処理実装

しょうがないので、自分で実装するしかない。

Appleによるとclonefileシステムコール（正確にはclonefileatシステムコール）は、[5つの引数を受け付ける](https://opensource.apple.com/source/xnu/xnu-3789.21.4/bsd/man/man2/clonefile.2.auto.html)。


| 引数 | パラメータ|
|-----|---|
|  1  |第2引数が相対パスの場合のカレントディレクトリのディスクリプタ|
|  2  |コピー**元**ファイルのパス|
|  3  |第4引数が相対パスの場合のカレントディレクトリのディスクリプタ|
|  4  |コピー**先**ファイルのパス|
|  5  | フラグ（現状は、NOFOLLOW: シンボリックリンクのリンク元までは追わないのみ？) |

一方、unixパッケージのシステムコール発行関数は、引数3つのSyscall()、引数6つのSyscall6()
、引数9つのSyscall9()の3つあるので、第6引数を0として、Syscall6を呼び出すのが良さそうだ。

最終的には以下のようなコードとなった。

```golang
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
}
```

# 色々な補足

【補足１】

go1.12からはmacOSに関しては、システムコール発行の実装が変更となっているようだ。
システムコール関数は、syscall_syscall6などの小文字＋アンダーバーの関数となっており、、システムコール番号もlibcから取得する方法となっている。
https://github.com/golang/sys/blob/9fbf701fc3/unix/zsyscall_darwin_arm64.go
（なんでだろう）

【補足２】

直接システムコールを呼ぶのではなく、
https://github.com/go-darwin/apfs/blob/master/clone.go
こちらの方のように、C言語の関数を直接呼ぶ方が作法としては良いのかもしれない。




[^1]: [Go Binary Hacks - syscallとgolang.org/x/sys/ #golang](https://qiita.com/sonatard/items/f03883ac061cf9fe4718), や[googleのドキュメント](https://docs.google.com/document/d/1QXzI9I1pOfZPujQzxhyRy6EeHYTQitKKjHfpq0zpxZs/edit)に詳しい話が掲載されている