Skip to content

Commit

Permalink
perf: use darwin's clonefile syscall (bazel-contrib#893)
Browse files Browse the repository at this point in the history
* perf: use darwin's clonefile syscall

This saves time and disk-space when copying files around on the same
device. File clones (aka reflinks) share backing disk blocks; they
differ from hardlinks in that inodes are not shared and the contents are
copy-on-write.

The Go standard library (as of v1.22) arranges to do a similar thing for file
copies on Linux (see: https://cs.opensource.google/go/go/+/refs/tags/go1.22.6:src/os/zero_copy_linux.go;l=53).
Unfortunately, Mac OS' more limited API is less amenable to that form of
transparent wrapping.

* Assign to named return params and use naked returns
  • Loading branch information
plobsing authored Aug 8, 2024
1 parent 34288d3 commit 3c121a9
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 13 deletions.
11 changes: 11 additions & 0 deletions tools/common/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,20 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "common",
srcs = [
"clonefile_darwin.go",
"clonefile_stub.go",
"copy.go",
"file.go",
],
importpath = "github.com/aspect-build/bazel-lib/tools/common",
visibility = ["//visibility:public"],
deps = select({
"@io_bazel_rules_go//go/platform:darwin": [
"@org_golang_x_sys//unix:go_default_library",
],
"@io_bazel_rules_go//go/platform:ios": [
"@org_golang_x_sys//unix:go_default_library",
],
"//conditions:default": [],
}),
)
22 changes: 22 additions & 0 deletions tools/common/clonefile_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//go:build darwin

package common

import (
"golang.org/x/sys/unix"
"os"
)

// https://keith.github.io/xcode-man-pages/clonefile.2.html
func cloneFile(src, dst string) (supported bool, err error) {
supported = true
if err = unix.Clonefile(src, dst, 0); err != nil {
err = &os.LinkError{
Op: "clonefile",
Old: src,
New: dst,
Err: err,
}
}
return
}
9 changes: 9 additions & 0 deletions tools/common/clonefile_stub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//go:build !darwin

package common

func cloneFile(src, dst string) (supported bool, err error) {
supported = false
err = nil
return
}
48 changes: 35 additions & 13 deletions tools/common/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,32 +30,54 @@ func Copy(opts CopyOpts) {
if !opts.info.Mode().IsRegular() {
log.Fatalf("%s is not a regular file", opts.src)
}

opModifier := ""
const fallback = " (fallback)"

if opts.hardlink {
// hardlink this file
if opts.verbose {
fmt.Printf("hardlink %v => %v\n", opts.src, opts.dst)
fmt.Printf("hardlink%s %v => %v\n", opModifier, opts.src, opts.dst)
}
err := os.Link(opts.src, opts.dst)
if err != nil {
// fallback to copy
if opts.verbose {
fmt.Printf("hardlink failed: %v\n", err)
fmt.Printf("copy (fallback) %v => %v\n", opts.src, opts.dst)
}
err = CopyFile(opts.src, opts.dst)
if err != nil {
log.Fatal(err)
opModifier = fallback
}
// fallback to clonefile or copy
} else {
return
}
} else {
// copy this file
}

// clone this file
if opts.verbose {
fmt.Printf("clonefile%s %v => %v\n", opModifier, opts.src, opts.dst)
}
switch supported, err := cloneFile(opts.src, opts.dst); {
case !supported:
if opts.verbose {
fmt.Printf("copy %v => %v\n", opts.src, opts.dst)
fmt.Print("clonefile skipped: not supported by platform\n")
}
err := CopyFile(opts.src, opts.dst)
if err != nil {
log.Fatal(err)
// fallback to copy
case supported && err == nil:
return
case supported && err != nil:
if opts.verbose {
fmt.Printf("clonefile failed: %v\n", err)
opModifier = fallback
}
// fallback to copy
}

// copy this file
if opts.verbose {
fmt.Printf("copy%s %v => %v\n", opModifier, opts.src, opts.dst)
}
err := CopyFile(opts.src, opts.dst)
if err != nil {
log.Fatal(err)
}
}

Expand Down

0 comments on commit 3c121a9

Please sign in to comment.