Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ rmdebug:
go build -gcflags "all=-N -l" -ldflags="-X github.com/pelican-dev/wings/system.Version=$(GIT_HEAD)" -race
sudo dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec ./wings -- --debug --ignore-certificate-errors --config config.yml

cross-build: clean build compress
build-darwin:
GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -gcflags "all=-trimpath=$(pwd)" -o build/wings_darwin_arm64 -v wings.go
GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -gcflags "all=-trimpath=$(pwd)" -o build/wings_darwin_amd64 -v wings.go

cross-build: clean build build-darwin

clean:
rm -rf build/wings_*

.PHONY: all build compress clean
.PHONY: all build build-darwin cross-build clean test debug rmdebug
51 changes: 16 additions & 35 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import (
"path"
"path/filepath"
"regexp"
"runtime"
"strings"
"sync"
"sync/atomic"
"text/template"
"time"

Expand All @@ -22,7 +22,6 @@ import (
"github.com/apex/log"
"github.com/creasty/defaults"
"github.com/gbrlsnchs/jwt/v3"
"golang.org/x/sys/unix"
"gopkg.in/yaml.v2"

"github.com/pelican-dev/wings/system"
Expand Down Expand Up @@ -503,6 +502,18 @@ func EnsurePelicanUser() error {
return err
}

// macOS doesn't have useradd, use the current user like rootless mode.
if sysName == "darwin" {
u, err := user.Current()
if err != nil {
return err
}
_config.System.Username = u.Username
_config.System.User.Uid = system.MustInt(u.Uid)
_config.System.User.Gid = system.MustInt(u.Gid)
return nil
}

// Our way of detecting if wings is running inside of Docker.
if sysName == "distroless" {
_config.System.Username = system.FirstNotEmpty(os.Getenv("WINGS_USERNAME"), "pelican")
Expand Down Expand Up @@ -791,6 +802,9 @@ func ConfigureTimezone() error {

// Gets the system release name.
func getSystemName() (string, error) {
if runtime.GOOS == "darwin" {
return "darwin", nil
}
// use osrelease to get release version and ID
release, err := osrelease.Read()
if err != nil {
Expand All @@ -799,39 +813,6 @@ func getSystemName() (string, error) {
return release["ID"], nil
}

var (
openat2 atomic.Bool
openat2Set atomic.Bool
)

func UseOpenat2() bool {
if openat2Set.Load() {
return openat2.Load()
}
defer openat2Set.Store(true)

c := Get()
openatMode := c.System.OpenatMode
switch openatMode {
case "openat2":
openat2.Store(true)
return true
case "openat":
openat2.Store(false)
return false
default:
fd, err := unix.Openat2(unix.AT_FDCWD, "/", &unix.OpenHow{})
if err != nil {
log.WithError(err).Warn("error occurred while checking for openat2 support, falling back to openat")
openat2.Store(false)
return false
}
_ = unix.Close(fd)
openat2.Store(true)
return true
}
}

// Expand expands an input string by calling [os.ExpandEnv] to expand all
// environment variables, then checks if the value is prefixed with `file://`
// to support reading the value from a file.
Expand Down
7 changes: 7 additions & 0 deletions config/config_openat_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package config

// UseOpenat2 always returns false on Darwin as the openat2 syscall is
// Linux-specific (kernel 5.6+).
func UseOpenat2() bool {
return false
}
41 changes: 41 additions & 0 deletions config/config_openat_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package config

import (
"sync/atomic"

"github.com/apex/log"
"golang.org/x/sys/unix"
)

var (
openat2 atomic.Bool
openat2Set atomic.Bool
)

func UseOpenat2() bool {
if openat2Set.Load() {
return openat2.Load()
}
defer openat2Set.Store(true)

c := Get()
openatMode := c.System.OpenatMode
switch openatMode {
case "openat2":
openat2.Store(true)
return true
case "openat":
openat2.Store(false)
return false
default:
fd, err := unix.Openat2(unix.AT_FDCWD, "/", &unix.OpenHow{})
if err != nil {
log.WithError(err).Warn("error occurred while checking for openat2 support, falling back to openat")
openat2.Store(false)
return false
}
_ = unix.Close(fd)
openat2.Store(true)
return true
}
}
26 changes: 26 additions & 0 deletions config/config_openat_linux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package config

import "testing"

func TestUseOpenat2ConfigOverride(t *testing.T) {
Set(&Configuration{
AuthenticationToken: "test",
System: SystemConfiguration{
OpenatMode: "openat",
},
})
openat2Set.Store(false)

if UseOpenat2() {
t.Error("expected UseOpenat2() to return false when mode is 'openat'")
}

openat2Set.Store(false)
Update(func(c *Configuration) {
c.System.OpenatMode = "openat2"
})

if !UseOpenat2() {
t.Error("expected UseOpenat2() to return true when mode is 'openat2'")
}
}
29 changes: 29 additions & 0 deletions config/config_openat_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package config

import (
"runtime"
"testing"
)

func TestUseOpenat2(t *testing.T) {
// Ensure UseOpenat2 doesn't panic.
Set(&Configuration{
AuthenticationToken: "test",
System: SystemConfiguration{
OpenatMode: "auto",
},
})

result := UseOpenat2()

switch runtime.GOOS {
case "darwin":
if result {
t.Error("expected UseOpenat2() to return false on Darwin")
}
case "linux":
// On Linux it may be true or false depending on kernel version.
// Just verify it returns without error.
t.Logf("UseOpenat2() returned %v on Linux", result)
}
}
6 changes: 5 additions & 1 deletion environment/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package environment
import (
"fmt"
"math"
"runtime"
"strconv"

"github.com/apex/log"
Expand Down Expand Up @@ -112,11 +113,14 @@ func (l Limits) AsContainerResources() container.Resources {
Memory: l.BoundedMemoryLimit(),
MemoryReservation: l.MemoryLimit * 1024 * 1024,
MemorySwap: l.ConvertedSwap(),
BlkioWeight: l.IoWeight,
OomKillDisable: boolPtr(!l.OOMKiller),
PidsLimit: &pids,
}

if runtime.GOOS == "linux" {
resources.BlkioWeight = l.IoWeight
}

// If the CPU Limit is not set, don't send any of these fields through. Providing
// them seems to break some Java services that try to read the available processors.
//
Expand Down
4 changes: 1 addition & 3 deletions internal/ufs/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,12 +169,10 @@ const (
O_DIRECTORY = unix.O_DIRECTORY
// O_NOFOLLOW opens the exact path given without following symlinks.
O_NOFOLLOW = unix.O_NOFOLLOW
O_CLOEXEC = unix.O_CLOEXEC
O_LARGEFILE = unix.O_LARGEFILE
O_CLOEXEC = unix.O_CLOEXEC
)

const (
AT_SYMLINK_NOFOLLOW = unix.AT_SYMLINK_NOFOLLOW
AT_REMOVEDIR = unix.AT_REMOVEDIR
AT_EMPTY_PATH = unix.AT_EMPTY_PATH
)
4 changes: 4 additions & 0 deletions internal/ufs/file_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package ufs

// O_LARGEFILE is a no-op on Darwin as all files support large offsets.
const O_LARGEFILE = 0
5 changes: 5 additions & 0 deletions internal/ufs/file_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package ufs

import "golang.org/x/sys/unix"

const O_LARGEFILE = unix.O_LARGEFILE
59 changes: 59 additions & 0 deletions internal/ufs/fs_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package ufs

import (
"bytes"
"path/filepath"
"runtime"
"time"
"unsafe"

"golang.org/x/sys/unix"
)

// fdPath returns the filesystem path associated with a file descriptor using
// the F_GETPATH fcntl command on macOS.
func fdPath(fd int) (string, error) {
buf := make([]byte, unix.PathMax)
_, _, errno := unix.Syscall(unix.SYS_FCNTL, uintptr(fd), uintptr(unix.F_GETPATH), uintptr(unsafe.Pointer(&buf[0])))
runtime.KeepAlive(buf)
if errno != 0 {
return "", errno
}
n := bytes.IndexByte(buf, 0)
if n < 0 {
n = len(buf)
}
return filepath.EvalSymlinks(string(buf[:n]))
}

// _openat2 is a stub on Darwin. The openat2 syscall is Linux-specific (kernel
// 5.6+). On Darwin, this always returns ENOSYS to signal that the caller
// should fall back to the regular openat path.
func (fs *UnixFS) _openat2(dirfd int, name string, flag, mode uint64) (int, error) {
return 0, unix.ENOSYS
}

// Chtimesat is like Chtimes but allows passing an existing directory file
// descriptor rather than needing to resolve one. On Darwin, UTIME_OMIT is not
// available, so zero times are handled by reading the current timestamps and
// preserving them.
func (fs *UnixFS) Chtimesat(dirfd int, name string, atime, mtime time.Time) error {
if atime.IsZero() || mtime.IsZero() {
var st unix.Stat_t
if err := unix.Fstatat(dirfd, name, &st, 0); err != nil {
return ensurePathError(err, "chtimes", name)
}
if atime.IsZero() {
atime = time.Unix(st.Atim.Unix())
}
if mtime.IsZero() {
mtime = time.Unix(st.Mtim.Unix())
}
}

utimes := [2]unix.Timespec{
unix.NsecToTimespec(atime.UnixNano()),
unix.NsecToTimespec(mtime.UnixNano()),
}
return ensurePathError(unix.UtimesNanoAt(dirfd, name, utimes[0:], 0), "chtimes", name)
Comment thread
lancepioch marked this conversation as resolved.
}
72 changes: 72 additions & 0 deletions internal/ufs/fs_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package ufs

import (
"path/filepath"
"strconv"
"time"

"golang.org/x/sys/unix"
)

// fdPath returns the filesystem path associated with a file descriptor by
// reading the /proc/self/fd/ symlink.
func fdPath(fd int) (string, error) {
return filepath.EvalSymlinks(filepath.Join("/proc/self/fd/", strconv.Itoa(fd)))
}

// _openat2 is a wonderful syscall that supersedes the `openat` syscall. It has
// improved validation and security characteristics that weren't available or
// considered when `openat` was originally implemented. As such, it is only
// present in Kernel 5.6 and above.
//
// This method should never be directly called, use `openat` instead.
func (fs *UnixFS) _openat2(dirfd int, name string, flag, mode uint64) (int, error) {
// Ensure the O_CLOEXEC flag is set.
// Go sets this when using the os package, but since we are directly using
// the unix package we need to set it ourselves.
if flag&O_CLOEXEC == 0 {
flag |= O_CLOEXEC
}
// Ensure the O_LARGEFILE flag is set.
// Go sets this for unix.Open, unix.Openat, but not unix.Openat2.
if flag&O_LARGEFILE == 0 {
flag |= O_LARGEFILE
}
fd, err := unix.Openat2(dirfd, name, &unix.OpenHow{
Flags: flag,
Mode: mode,
// This is the bread and butter of preventing a symlink escape, without
// this option, we have to handle path validation fully on our own.
//
// This is why using Openat2 over Openat is preferred if available.
Resolve: unix.RESOLVE_BENEATH,
})
switch {
case err == nil:
return fd, nil
case err == unix.EINTR:
return fd, err
case err == unix.EAGAIN:
return fd, err
default:
return fd, ensurePathError(err, "openat2", name)
}
}

// Chtimesat is like Chtimes but allows passing an existing directory file
// descriptor rather than needing to resolve one.
func (fs *UnixFS) Chtimesat(dirfd int, name string, atime, mtime time.Time) error {
var utimes [2]unix.Timespec
set := func(i int, t time.Time) {
if t.IsZero() {
utimes[i] = unix.Timespec{Sec: unix.UTIME_OMIT, Nsec: unix.UTIME_OMIT}
} else {
utimes[i] = unix.NsecToTimespec(t.UnixNano())
}
}
set(0, atime)
set(1, mtime)

// This does support `AT_SYMLINK_NOFOLLOW` as well if needed.
return ensurePathError(unix.UtimesNanoAt(dirfd, name, utimes[0:], 0), "chtimes", name)
}
Loading
Loading