Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

はじめに

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

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

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

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

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

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

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

Unixの場合、システムコールにはそれぞれ番号が割り当たっている。
どのシステムコールにどの番号が振られているかを定数定義しているファイルが、
zsysnum_darwin_amd64.go
である。

const (
//中略
    SYS_CLONEFILEAT                    = 462
//中略
)

その中にclonefileのシステムコール番号も記載されている。
これは期待できそうだ。

上記の定数を利用して、実際にシステムコールを発行しているのが、
zsyscall_darwin_amd64.1_11.go
このファイルだ。

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

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

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

Appleによるとclonefileシステムコール(正確にはclonefileatシステムコール)は、5つの引数を受け付ける

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

一方、unixパッケージのシステムコール発行関数は、引数3つのSyscall()、引数6つのSyscall6()
、引数9つのSyscall9()の3つあるので、第6引数を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】

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

【補足2】

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away