pkg/system: move to daemon/internal

It has no external users, and this package still has too many different
responsibilities, some of which may be available elsewhere, so moving it
internal so that we can decide to dismantle it further.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2025-07-29 11:48:57 +02:00
parent d94171bfaa
commit 5535e81a79
19 changed files with 9 additions and 9 deletions

View File

@@ -1,48 +0,0 @@
package system
import (
"os"
"syscall"
"time"
"unsafe"
)
// Used by Chtimes
var unixEpochTime, unixMaxTime time.Time
func init() {
unixEpochTime = time.Unix(0, 0)
if unsafe.Sizeof(syscall.Timespec{}.Nsec) == 8 {
// This is a 64 bit timespec
// os.Chtimes limits time to the following
//
// Note that this intentionally sets nsec (not sec), which sets both sec
// and nsec internally in time.Unix();
// https://github.com/golang/go/blob/go1.19.2/src/time/time.go#L1364-L1380
unixMaxTime = time.Unix(0, 1<<63-1)
} else {
// This is a 32 bit timespec
unixMaxTime = time.Unix(1<<31-1, 0)
}
}
// Chtimes changes the access time and modified time of a file at the given path.
// If the modified time is prior to the Unix Epoch (unixMinTime), or after the
// end of Unix Time (unixEpochTime), os.Chtimes has undefined behavior. In this
// case, Chtimes defaults to Unix Epoch, just in case.
func Chtimes(name string, atime time.Time, mtime time.Time) error {
if atime.Before(unixEpochTime) || atime.After(unixMaxTime) {
atime = unixEpochTime
}
if mtime.Before(unixEpochTime) || mtime.After(unixMaxTime) {
mtime = unixEpochTime
}
if err := os.Chtimes(name, atime, mtime); err != nil {
return err
}
// Take platform specific action for setting create time.
return setCTime(name, mtime)
}

View File

@@ -1,110 +0,0 @@
package system
import (
"os"
"path/filepath"
"syscall"
"testing"
"time"
)
// TestChtimesATime tests Chtimes access time on a tempfile.
func TestChtimesATime(t *testing.T) {
file := filepath.Join(t.TempDir(), "exist")
if err := os.WriteFile(file, []byte("hello"), 0o644); err != nil {
t.Fatal(err)
}
beforeUnixEpochTime := unixEpochTime.Add(-100 * time.Second)
afterUnixEpochTime := unixEpochTime.Add(100 * time.Second)
// Test both aTime and mTime set to Unix Epoch
t.Run("both aTime and mTime set to Unix Epoch", func(t *testing.T) {
if err := Chtimes(file, unixEpochTime, unixEpochTime); err != nil {
t.Error(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
stat := f.Sys().(*syscall.Stat_t)
aTime := time.Unix(stat.Atim.Unix())
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
})
// Test aTime before Unix Epoch and mTime set to Unix Epoch
t.Run("aTime before Unix Epoch and mTime set to Unix Epoch", func(t *testing.T) {
if err := Chtimes(file, beforeUnixEpochTime, unixEpochTime); err != nil {
t.Error(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
stat := f.Sys().(*syscall.Stat_t)
aTime := time.Unix(stat.Atim.Unix())
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
})
// Test aTime set to Unix Epoch and mTime before Unix Epoch
t.Run("aTime set to Unix Epoch and mTime before Unix Epoch", func(t *testing.T) {
if err := Chtimes(file, unixEpochTime, beforeUnixEpochTime); err != nil {
t.Error(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
stat := f.Sys().(*syscall.Stat_t)
aTime := time.Unix(stat.Atim.Unix())
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
})
// Test both aTime and mTime set to after Unix Epoch (valid time)
t.Run("both aTime and mTime set to after Unix Epoch (valid time)", func(t *testing.T) {
if err := Chtimes(file, afterUnixEpochTime, afterUnixEpochTime); err != nil {
t.Error(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
stat := f.Sys().(*syscall.Stat_t)
aTime := time.Unix(stat.Atim.Unix())
if aTime != afterUnixEpochTime {
t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, aTime)
}
})
// Test both aTime and mTime set to Unix max time
t.Run("both aTime and mTime set to Unix max time", func(t *testing.T) {
if err := Chtimes(file, unixMaxTime, unixMaxTime); err != nil {
t.Error(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
stat := f.Sys().(*syscall.Stat_t)
aTime := time.Unix(stat.Atim.Unix())
if aTime.Truncate(time.Second) != unixMaxTime.Truncate(time.Second) {
t.Fatalf("Expected: %s, got: %s", unixMaxTime.Truncate(time.Second), aTime.Truncate(time.Second))
}
})
}

View File

@@ -1,14 +0,0 @@
//go:build !windows
package system
import (
"time"
)
// setCTime will set the create time on a file. On Unix, the create
// time is updated as a side effect of setting the modified time, so
// no action is required.
func setCTime(path string, ctime time.Time) error {
return nil
}

View File

@@ -1,100 +0,0 @@
package system
import (
"os"
"path/filepath"
"testing"
"time"
)
// TestChtimesModTime tests Chtimes on a tempfile. Test only mTime, because
// aTime is OS dependent.
func TestChtimesModTime(t *testing.T) {
file := filepath.Join(t.TempDir(), "exist")
if err := os.WriteFile(file, []byte("hello"), 0o644); err != nil {
t.Fatal(err)
}
beforeUnixEpochTime := unixEpochTime.Add(-100 * time.Second)
afterUnixEpochTime := unixEpochTime.Add(100 * time.Second)
// Test both aTime and mTime set to Unix Epoch
t.Run("both aTime and mTime set to Unix Epoch", func(t *testing.T) {
if err := Chtimes(file, unixEpochTime, unixEpochTime); err != nil {
t.Error(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
if f.ModTime() != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
}
})
// Test aTime before Unix Epoch and mTime set to Unix Epoch
t.Run("aTime before Unix Epoch and mTime set to Unix Epoch", func(t *testing.T) {
if err := Chtimes(file, beforeUnixEpochTime, unixEpochTime); err != nil {
t.Error(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
if f.ModTime() != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
}
})
// Test aTime set to Unix Epoch and mTime before Unix Epoch
t.Run("aTime set to Unix Epoch and mTime before Unix Epoch", func(t *testing.T) {
if err := Chtimes(file, unixEpochTime, beforeUnixEpochTime); err != nil {
t.Error(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
if f.ModTime() != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
}
})
// Test both aTime and mTime set to after Unix Epoch (valid time)
t.Run("both aTime and mTime set to after Unix Epoch (valid time)", func(t *testing.T) {
if err := Chtimes(file, afterUnixEpochTime, afterUnixEpochTime); err != nil {
t.Error(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
if f.ModTime() != afterUnixEpochTime {
t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, f.ModTime())
}
})
// Test both aTime and mTime set to Unix max time
t.Run("both aTime and mTime set to Unix max time", func(t *testing.T) {
if err := Chtimes(file, unixMaxTime, unixMaxTime); err != nil {
t.Error(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
if f.ModTime().Truncate(time.Second) != unixMaxTime.Truncate(time.Second) {
t.Fatalf("Expected: %s, got: %s", unixMaxTime.Truncate(time.Second), f.ModTime().Truncate(time.Second))
}
})
}

View File

@@ -1,25 +0,0 @@
package system
import (
"time"
"golang.org/x/sys/windows"
)
// setCTime will set the create time on a file. On Windows, this requires
// calling SetFileTime and explicitly including the create time.
func setCTime(path string, ctime time.Time) error {
pathp, err := windows.UTF16PtrFromString(path)
if err != nil {
return err
}
h, err := windows.CreateFile(pathp,
windows.FILE_WRITE_ATTRIBUTES, windows.FILE_SHARE_WRITE, nil,
windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS, 0)
if err != nil {
return err
}
defer windows.Close(h)
c := windows.NsecToFiletime(ctime.UnixNano())
return windows.SetFileTime(h, &c, nil, nil)
}

View File

@@ -1,107 +0,0 @@
//go:build windows
package system
import (
"os"
"path/filepath"
"syscall"
"testing"
"time"
)
// TestChtimesATimeWindows tests Chtimes access time on a tempfile on Windows.
func TestChtimesATimeWindows(t *testing.T) {
file := filepath.Join(t.TempDir(), "exist")
if err := os.WriteFile(file, []byte("hello"), 0o644); err != nil {
t.Fatal(err)
}
beforeUnixEpochTime := unixEpochTime.Add(-100 * time.Second)
afterUnixEpochTime := unixEpochTime.Add(100 * time.Second)
// Test both aTime and mTime set to Unix Epoch
t.Run("both aTime and mTime set to Unix Epoch", func(t *testing.T) {
if err := Chtimes(file, unixEpochTime, unixEpochTime); err != nil {
t.Error(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
aTime := time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
})
// Test aTime before Unix Epoch and mTime set to Unix Epoch
t.Run("aTime before Unix Epoch and mTime set to Unix Epoch", func(t *testing.T) {
if err := Chtimes(file, beforeUnixEpochTime, unixEpochTime); err != nil {
t.Error(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
aTime := time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
})
// Test aTime set to Unix Epoch and mTime before Unix Epoch
t.Run("aTime set to Unix Epoch and mTime before Unix Epoch", func(t *testing.T) {
if err := Chtimes(file, unixEpochTime, beforeUnixEpochTime); err != nil {
t.Error(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
aTime := time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
})
// Test both aTime and mTime set to after Unix Epoch (valid time)
t.Run("both aTime and mTime set to after Unix Epoch (valid time)", func(t *testing.T) {
if err := Chtimes(file, afterUnixEpochTime, afterUnixEpochTime); err != nil {
t.Error(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
aTime := time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
if aTime != afterUnixEpochTime {
t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, aTime)
}
})
// Test both aTime and mTime set to Unix max time
t.Run("both aTime and mTime set to Unix max time", func(t *testing.T) {
if err := Chtimes(file, unixMaxTime, unixMaxTime); err != nil {
t.Error(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
aTime := time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
if aTime.Truncate(time.Second) != unixMaxTime.Truncate(time.Second) {
t.Fatalf("Expected: %s, got: %s", unixMaxTime.Truncate(time.Second), aTime.Truncate(time.Second))
}
})
}

View File

@@ -1,122 +0,0 @@
package system
import (
"os"
"path/filepath"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
// SddlAdministratorsLocalSystem is local administrators plus NT AUTHORITY\System.
const SddlAdministratorsLocalSystem = "D:P(A;OICI;GA;;;BA)(A;OICI;GA;;;SY)"
// MkdirAllWithACL is a custom version of os.MkdirAll modified for use on Windows
// so that it is both volume path aware, and can create a directory with
// an appropriate SDDL defined ACL.
func MkdirAllWithACL(path string, sddl string) error {
sa, err := makeSecurityAttributes(sddl)
if err != nil {
return &os.PathError{Op: "mkdirall", Path: path, Err: err}
}
return mkdirAllWithACL(path, sa)
}
// mkdirAllWithACL is a custom version of os.MkdirAll with DACL support on Windows.
// It is fully identical to [os.MkdirAll] if no DACL is provided.
//
// Code in this function is based on the implementation in [go1.23.4].
//
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// [go1.23.4]: https://github.com/golang/go/blob/go1.23.4/src/os/path.go#L12-L66
func mkdirAllWithACL(path string, perm *windows.SecurityAttributes) error {
if perm == nil {
return os.MkdirAll(path, 0)
}
// Fast path: if we can tell whether path is a directory or file, stop with success or error.
dir, err := os.Stat(path)
if err == nil {
if dir.IsDir() {
return nil
}
return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
}
// Slow path: make sure parent exists and then call Mkdir for path.
// Extract the parent folder from path by first removing any trailing
// path separator and then scanning backward until finding a path
// separator or reaching the beginning of the string.
i := len(path) - 1
for i >= 0 && os.IsPathSeparator(path[i]) {
i--
}
for i >= 0 && !os.IsPathSeparator(path[i]) {
i--
}
if i < 0 {
i = 0
}
// If there is a parent directory, and it is not the volume name,
// recurse to ensure parent directory exists.
if parent := path[:i]; len(parent) > len(filepath.VolumeName(path)) {
err = mkdirAllWithACL(parent, perm)
if err != nil {
return err
}
}
// Parent now exists; invoke Mkdir and use its result.
err = mkdirWithACL(path, perm)
if err != nil {
// Handle arguments like "foo/." by
// double-checking that directory doesn't exist.
dir, err1 := os.Lstat(path)
if err1 == nil && dir.IsDir() {
return nil
}
return err
}
return nil
}
// mkdirWithACL creates a new directory. If there is an error, it will be of
// type *PathError. .
//
// This is a modified and combined version of os.Mkdir and windows.Mkdir
// in golang to cater for creating a directory am ACL permitting full
// access, with inheritance, to any subfolder/file for Built-in Administrators
// and Local System.
func mkdirWithACL(name string, sa *windows.SecurityAttributes) error {
if sa == nil {
return os.Mkdir(name, 0)
}
namep, err := windows.UTF16PtrFromString(name)
if err != nil {
return &os.PathError{Op: "mkdir", Path: name, Err: err}
}
err = windows.CreateDirectory(namep, sa)
if err != nil {
return &os.PathError{Op: "mkdir", Path: name, Err: err}
}
return nil
}
func makeSecurityAttributes(sddl string) (*windows.SecurityAttributes, error) {
var sa windows.SecurityAttributes
sa.Length = uint32(unsafe.Sizeof(sa))
sa.InheritHandle = 1
var err error
sa.SecurityDescriptor, err = windows.SecurityDescriptorFromString(sddl)
if err != nil {
return nil, err
}
return &sa, nil
}

View File

@@ -1,25 +0,0 @@
//go:build linux || freebsd
package system
import (
"errors"
"syscall"
"golang.org/x/sys/unix"
)
// LUtimesNano is used to change access and modification time of the specified path.
// It's used for symbol link file because unix.UtimesNano doesn't support a NOFOLLOW flag atm.
func LUtimesNano(path string, ts []syscall.Timespec) error {
uts := []unix.Timespec{
unix.NsecToTimespec(syscall.TimespecToNsec(ts[0])),
unix.NsecToTimespec(syscall.TimespecToNsec(ts[1])),
}
err := unix.UtimesNanoAt(unix.AT_FDCWD, path, uts, unix.AT_SYMLINK_NOFOLLOW)
if err != nil && !errors.Is(err, unix.ENOSYS) {
return err
}
return nil
}

View File

@@ -1,63 +0,0 @@
//go:build linux || freebsd
package system
import (
"os"
"path/filepath"
"syscall"
"testing"
)
// prepareFiles creates files for testing in the temp directory
func prepareFiles(t *testing.T) (file, invalid, symlink string) {
t.Helper()
dir := t.TempDir()
file = filepath.Join(dir, "exist")
if err := os.WriteFile(file, []byte("hello"), 0o644); err != nil {
t.Fatal(err)
}
invalid = filepath.Join(dir, "doesnt-exist")
symlink = filepath.Join(dir, "symlink")
if err := os.Symlink(file, symlink); err != nil {
t.Fatal(err)
}
return file, invalid, symlink
}
func TestLUtimesNano(t *testing.T) {
file, invalid, symlink := prepareFiles(t)
before, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
ts := []syscall.Timespec{{Sec: 0, Nsec: 0}, {Sec: 0, Nsec: 0}}
if err := LUtimesNano(symlink, ts); err != nil {
t.Fatal(err)
}
symlinkInfo, err := os.Lstat(symlink)
if err != nil {
t.Fatal(err)
}
if before.ModTime().Unix() == symlinkInfo.ModTime().Unix() {
t.Fatal("The modification time of the symlink should be different")
}
fileInfo, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
if before.ModTime().Unix() != fileInfo.ModTime().Unix() {
t.Fatal("The modification time of the file should be same")
}
if err := LUtimesNano(invalid, ts); err == nil {
t.Fatal("Doesn't return an error on a non-existing file")
}
}

View File

@@ -1,66 +0,0 @@
package system
import (
"errors"
"golang.org/x/sys/unix"
)
type XattrError struct {
Op string
Attr string
Path string
Err error
}
func (e *XattrError) Error() string { return e.Op + " " + e.Attr + " " + e.Path + ": " + e.Err.Error() }
func (e *XattrError) Unwrap() error { return e.Err }
// Timeout reports whether this error represents a timeout.
func (e *XattrError) Timeout() bool {
t, ok := e.Err.(interface{ Timeout() bool })
return ok && t.Timeout()
}
// Lgetxattr retrieves the value of the extended attribute identified by attr
// and associated with the given path in the file system.
// It returns a nil slice and nil error if the xattr is not set.
func Lgetxattr(path string, attr string) ([]byte, error) {
sysErr := func(err error) ([]byte, error) {
return nil, &XattrError{Op: "lgetxattr", Attr: attr, Path: path, Err: err}
}
// Start with a 128 length byte array
dest := make([]byte, 128)
sz, errno := unix.Lgetxattr(path, attr, dest)
for errors.Is(errno, unix.ERANGE) {
// Buffer too small, use zero-sized buffer to get the actual size
sz, errno = unix.Lgetxattr(path, attr, []byte{})
if errno != nil {
return sysErr(errno)
}
dest = make([]byte, sz)
sz, errno = unix.Lgetxattr(path, attr, dest)
}
switch {
case errors.Is(errno, unix.ENODATA):
return nil, nil
case errno != nil:
return sysErr(errno)
}
return dest[:sz], nil
}
// Lsetxattr sets the value of the extended attribute identified by attr
// and associated with the given path in the file system.
func Lsetxattr(path string, attr string, data []byte, flags int) error {
err := unix.Lsetxattr(path, attr, data, flags)
if err != nil {
return &XattrError{Op: "lsetxattr", Attr: attr, Path: path, Err: err}
}
return nil
}