WIP: rebase for 2026-05-18 #7
@@ -73,7 +73,11 @@ type Binaries struct {
|
|||||||
|
|
||||||
// BinaryInfo describes a tailscale or tailscaled binary.
|
// BinaryInfo describes a tailscale or tailscaled binary.
|
||||||
type BinaryInfo struct {
|
type BinaryInfo struct {
|
||||||
Path string // abs path to tailscale or tailscaled binary
|
// Path is the absolute path to the tailscale or tailscaled binary.
|
||||||
|
// This path may become invalid after the owning test's TempDir is
|
||||||
|
// cleaned up; use FD (or Contents on Windows) to access the binary
|
||||||
|
// contents.
|
||||||
|
Path string
|
||||||
Size int64
|
Size int64
|
||||||
|
|
||||||
// FD and FDmu are set on Unix to efficiently copy the binary to a new
|
// FD and FDmu are set on Unix to efficiently copy the binary to a new
|
||||||
@@ -88,16 +92,24 @@ type BinaryInfo struct {
|
|||||||
Contents []byte
|
Contents []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CopyTo copies or hardlinks the binary into dir, returning a new BinaryInfo
|
||||||
|
// with an updated Path. The source bytes come from FD (or Contents on Windows),
|
||||||
|
// not from b.Path, which may have been deleted when its owning test's TempDir
|
||||||
|
// was cleaned up.
|
||||||
func (b BinaryInfo) CopyTo(dir string) (BinaryInfo, error) {
|
func (b BinaryInfo) CopyTo(dir string) (BinaryInfo, error) {
|
||||||
ret := b
|
ret := b
|
||||||
ret.Path = filepath.Join(dir, path.Base(b.Path))
|
ret.Path = filepath.Join(dir, path.Base(b.Path))
|
||||||
|
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "linux":
|
case "linux":
|
||||||
// TODO(bradfitz): be fancy and use linkat with AT_EMPTY_PATH to avoid
|
// Try to hardlink from the open FD via /proc/self/fd, avoiding a
|
||||||
// copying? I couldn't get it to work, though.
|
// full copy of the binary. We can't use os.Link(b.Path, ret.Path)
|
||||||
// For now, just do the same thing as every other Unix and copy
|
// because b.Path is in the first test's TempDir, which may be
|
||||||
// the binary.
|
// cleaned up before later tests call CopyTo. The open FD keeps the
|
||||||
|
// inode alive after the path is deleted.
|
||||||
|
if err := tryLinkat(b.FD, ret.Path); err == nil {
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
fallthrough
|
fallthrough
|
||||||
case "darwin", "freebsd", "openbsd", "netbsd":
|
case "darwin", "freebsd", "openbsd", "netbsd":
|
||||||
f, err := os.OpenFile(ret.Path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o755)
|
f, err := os.OpenFile(ret.Path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o755)
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & contributors
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// tryLinkat attempts to hardlink the file referenced by fd to newpath,
|
||||||
|
// avoiding a full copy of the binary. It uses /proc/self/fd/<N> with
|
||||||
|
// AT_SYMLINK_FOLLOW, which works without elevated privileges (unlike
|
||||||
|
// AT_EMPTY_PATH which requires CAP_DAC_READ_SEARCH).
|
||||||
|
func tryLinkat(fd *os.File, newpath string) error {
|
||||||
|
procPath := fmt.Sprintf("/proc/self/fd/%d", fd.Fd())
|
||||||
|
err := unix.Linkat(unix.AT_FDCWD, procPath, unix.AT_FDCWD, newpath, unix.AT_SYMLINK_FOLLOW)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("linkat via /proc/self/fd: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & contributors
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTryLinkat(t *testing.T) {
|
||||||
|
src := filepath.Join(t.TempDir(), "src")
|
||||||
|
if err := os.WriteFile(src, []byte("hello world"), 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
fd, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
|
||||||
|
dst := filepath.Join(t.TempDir(), "dst")
|
||||||
|
if err := tryLinkat(fd, dst); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := os.ReadFile(dst)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(got) != "hello world" {
|
||||||
|
t.Fatalf("got %q, want %q", got, "hello world")
|
||||||
|
}
|
||||||
|
|
||||||
|
var stSrc, stDst unix.Stat_t
|
||||||
|
if err := unix.Stat(src, &stSrc); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := unix.Stat(dst, &stDst); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if stSrc.Ino != stDst.Ino {
|
||||||
|
t.Fatalf("inodes differ: src=%d, dst=%d", stSrc.Ino, stDst.Ino)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & contributors
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
//go:build !linux
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func tryLinkat(_ *os.File, _ string) error {
|
||||||
|
return errors.New("linkat with AT_EMPTY_PATH not supported on this OS")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user